chat: fixes

This commit is contained in:
Tyler Brown Cifu Shuster 2020-09-15 21:13:04 -07:00
parent e35bd1a2c9
commit ec99d96112
8 changed files with 83 additions and 81 deletions

View File

@ -1,11 +1,7 @@
import React, { Component, Fragment } from "react";
import React, { Component } from "react";
import moment from "moment";
import { RouteComponentProps } from "react-router-dom";
import { Link, RouteComponentProps } from "react-router-dom";
import { ChatWindow } from './lib/chat-window';
import { ChatHeader } from './lib/chat-header';
import { ChatInput } from "./lib/chat-input";
import { deSig } from "~/logic/lib/util";
import { ChatHookUpdate } from "~/types/chat-hook-update";
import { Inbox, Envelope } from "~/types/chat-update";
@ -15,8 +11,11 @@ import GlobalApi from "~/logic/api/global";
import { Association } from "~/types/metadata-update";
import {Group} from "~/types/group-update";
import { LocalUpdateRemoteContentPolicy } from "~/types";
import { S3Upload, SubmitDragger } from '~/views/components/s3-upload';
import { IUnControlledCodeMirror } from "react-codemirror2";
import { SubmitDragger } from '~/views/components/s3-upload';
import ChatWindow from './lib/ChatWindow';
import ChatHeader from './lib/ChatHeader';
import ChatInput from "./lib/ChatInput";
type ChatScreenProps = RouteComponentProps<{

View File

@ -5,7 +5,7 @@ import { TabBar } from '~/views/components/chat-link-tabbar';
import { SidebarSwitcher } from '~/views/components/SidebarSwitch';
import { deSig } from '~/logic/lib/util';
export const ChatHeader = (props) => {
const ChatHeader = (props) => {
const isInPopout = props.popout ? 'popout/' : '';
const group = Array.from(props.group.members);
let title = props.station.substr(1);
@ -59,3 +59,5 @@ export const ChatHeader = (props) => {
</Fragment>
);
};
export default ChatHeader;

View File

@ -33,7 +33,7 @@ interface ChatInputState {
}
export class ChatInput extends Component<ChatInputProps, ChatInputState> {
export default class ChatInput extends Component<ChatInputProps, ChatInputState> {
public s3Uploader: React.RefObject<S3Upload>;
private chatEditor: React.RefObject<ChatEditor>;

View File

@ -1,4 +1,4 @@
import React, { PureComponent } from "react";
import React, { Component, PureComponent } from "react";
import moment from "moment";
import _ from "lodash";
@ -13,7 +13,11 @@ import RemoteContent from '~/views/components/RemoteContent';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
export const UnreadMarker = React.forwardRef(({ dayBreak, when, style }, ref) => (
<div ref={ref} className="green2 flex items-center f9 absolute w-100" style={style}>
<div ref={element => {
setTimeout(() => {
element.style.opacity = '1';
}, 250);
}} className="green2 flex items-center f9 absolute w-100" style={{...style, opacity: '0'}}>
<hr className="dn-s ma0 w2 b--green2 bt-0" />
<p className="mh4">New messages below</p>
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
@ -49,7 +53,7 @@ interface ChatMessageProps {
scrollWindow: HTMLDivElement;
}
export default class ChatMessage extends PureComponent<ChatMessageProps> {
export default class ChatMessage extends Component<ChatMessageProps> {
private divRef: React.RefObject<HTMLDivElement>;
constructor(props) {

View File

@ -12,7 +12,7 @@ import { LocalUpdateRemoteContentPolicy } from "~/types";
import VirtualScroller from "~/views/components/VirtualScroller";
import ChatMessage, { MessagePlaceholder } from './chat-message';
import ChatMessage, { MessagePlaceholder } from './ChatMessage';
import { UnreadNotice } from "./unread-notice";
import { ResubscribeElement } from "./resubscribe-element";
import { BacklogElement } from "./backlog-element";
@ -48,12 +48,9 @@ interface ChatWindowState {
fetchPending: boolean;
idle: boolean;
initialized: boolean;
lastMessageNumber: number;
messagesToRender: Map<number, Envelope | IMessage>;
}
export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
private unreadReference: React.RefObject<HTMLDivElement>;
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
private virtualList: VirtualScroller | null;
INITIALIZATION_MAX_TIME = 1500;
@ -64,9 +61,7 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
this.state = {
fetchPending: false,
idle: true,
initialized: false,
lastMessageNumber: 0,
messagesToRender: new Map(),
initialized: false
};
this.dismissUnread = this.dismissUnread.bind(this);
@ -75,17 +70,15 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
this.handleWindowBlur = this.handleWindowBlur.bind(this);
this.handleWindowFocus = this.handleWindowFocus.bind(this);
this.stayLockedIfActive = this.stayLockedIfActive.bind(this);
this.assembleMessages = this.assembleMessages.bind(this);
this.firstUnread = this.firstUnread.bind(this);
this.virtualList = null;
this.unreadReference = React.createRef();
}
componentDidMount() {
window.addEventListener('blur', this.handleWindowBlur);
window.addEventListener('focus', this.handleWindowFocus);
this.initialFetch();
this.assembleMessages();
setTimeout(() => {
this.setState({ initialized: true });
}, this.INITIALIZATION_MAX_TIME);
@ -105,14 +98,11 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
}
initialIndex() {
const { unreadCount } = this.props;
const { lastMessageNumber } = this.state;
return Math.min(Math.max(lastMessageNumber - 1 < INITIAL_LOAD
const { mailboxSize, unreadCount } = this.props;
return Math.min(Math.max(mailboxSize - 1 < INITIAL_LOAD
? 0
: unreadCount // otherwise if there are unread messages
? lastMessageNumber - unreadCount // put the one right before at the top
: lastMessageNumber,
0), lastMessageNumber);
: this.firstUnread(),
0), mailboxSize);
}
initialFetch() {
@ -153,13 +143,11 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
if ((mailboxSize !== prevProps.mailboxSize) || (envelopes.length !== prevProps.envelopes.length)) {
this.virtualList?.calculateVisibleItems();
this.stayLockedIfActive();
this.assembleMessages();
}
if (stationPendingMessages.length !== prevProps.stationPendingMessages.length) {
this.virtualList?.calculateVisibleItems();
this.virtualList?.scrollToData(mailboxSize);
this.assembleMessages();
}
if (!this.state.fetchPending && prevState.fetchPending) {
@ -167,31 +155,6 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
}
}
assembleMessages() {
const { envelopes, stationPendingMessages } = this.props;
const messages: Map<number, Envelope | IMessage> = new Map();
let lastMessageNumber = 0;
[...envelopes]
.sort((a, b) => a.when - b.when)
.forEach((message) => {
messages.set(message.number, message);
if (message.number > lastMessageNumber) {
lastMessageNumber = message.number;
}
});
if (lastMessageNumber !== this.state.lastMessageNumber) {
this.setState({ lastMessageNumber });
}
stationPendingMessages.sort((a, b) => a.when - b.when).forEach((message, index) => {
messages.set(lastMessageNumber + index + 1, message);
});
this.setState({ messagesToRender: messages });
}
stayLockedIfActive() {
if (this.virtualList && !this.state.idle) {
this.virtualList.resetScroll();
@ -231,9 +194,15 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
this.setState({ fetchPending: false });
});
}
firstUnread() {
const { mailboxSize, unreadCount } = this.props;
return mailboxSize - unreadCount + 1;
}
render() {
const {
envelopes,
stationPendingMessages,
unreadCount,
unreadMsg,
@ -250,8 +219,26 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
hideNicknames,
remoteContentPolicy,
} = this.props;
const messages = new Map();
let lastMessage = 0;
const messages = this.state.messagesToRender;
[...envelopes]
.sort((a, b) => a.when - b.when)
.forEach(message => {
messages.set(message.number, message);
lastMessage = message.number;
});
stationPendingMessages
.sort((a, b) => a.when - b.when)
.forEach((message, index) => {
index = index + 1; // To 1-index it
messages.set(envelopes.length + index, message);
lastMessage = envelopes.length + index;
});
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy };
return (
<>
@ -279,33 +266,22 @@ export class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
data={messages}
size={mailboxSize + stationPendingMessages.length}
renderer={({ index, measure, scrollWindow }) => {
const msg = messages.get(index);
const msg: Envelope | IMessage = messages.get(index);
if (!msg) return null;
if (!this.state.initialized) {
return <MessagePlaceholder key={index} height="64px" index={index} />;
}
const isPending: boolean = 'pending' in msg && Boolean(msg.pending);
const isFirstUnread: boolean = Boolean(unreadCount && index === this.firstUnread());
const isLastMessage: boolean = Boolean(index === lastMessage)
const props = { measure, scrollWindow, isPending, isFirstUnread, msg, ...messageProps };
return (
<ChatMessage
measure={measure}
scrollWindow={scrollWindow}
key={index}
unreadRef={this.unreadReference}
isPending={msg && 'pending' in msg && Boolean(msg.pending)}
isFirstUnread={
Boolean(unreadCount
&& this.state.lastMessageNumber - unreadCount === index
&& !(unreadCount === 1 && msg.author === window.ship))
}
msg={msg}
previousMsg={messages.get(index + 1)}
nextMsg={messages.get(index - 1)}
association={association}
group={group}
contacts={contacts}
hideAvatars={hideAvatars}
hideNicknames={hideNicknames}
remoteContentPolicy={remoteContentPolicy}
className={index === this.state.lastMessageNumber + stationPendingMessages.length ? 'pb3' : ''}
className={isLastMessage ? 'pb3' : ''}
{...props}
/>
);
}}

View File

@ -79,6 +79,7 @@ export class GroupItem extends Component {
if (props.index === 'dm') {
dmLink = <Link
key="link"
className="absolute right-0 f9 top-0 mr4 green2 bg-gray5 bg-gray1-d b--transparent br1"
to="/~chat/new/dm"
style={{ padding: '0rem 0.2rem' }}
@ -88,7 +89,7 @@ export class GroupItem extends Component {
}
return (
<div className={first + 'relative'}>
<p className="f9 ph4 gray3">{title}</p>
<p className="f9 ph4 gray3" key="p">{title}</p>
{dmLink}
{channelItems}
</div>

View File

@ -1,11 +1,12 @@
import React, { Component, Fragment } from 'react';
import { deSig } from '~/logic/lib/util';
import { ChatHeader } from './lib/chat-header';
import { deSig } from '~/logic/lib/util';
import { MetadataSettings } from '~/views/components/metadata/settings';
import { Spinner } from '~/views/components/Spinner';
import ChatHeader from './lib/ChatHeader';
import { DeleteButton } from './lib/delete-button';
import { GroupifyButton } from './lib/groupify-button';
import { Spinner } from '~/views/components/Spinner';
export class SettingsScreen extends Component {
constructor(props) {

View File

@ -53,6 +53,7 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
this.recalculateTotalHeight = this.recalculateTotalHeight.bind(this);
this.calculateVisibleItems = this.calculateVisibleItems.bind(this);
this.estimateIndexFromScrollTop = this.estimateIndexFromScrollTop.bind(this);
this.invertedKeyHandler = this.invertedKeyHandler.bind(this);
this.heightOf = this.heightOf.bind(this);
this.setScrollTop = this.setScrollTop.bind(this);
this.scrollToData = this.scrollToData.bind(this);
@ -195,6 +196,23 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
start, end
};
}
invertedKeyHandler(event): void | false {
if (event.code === 'ArrowUp' || event.code === 'ArrowDown') {
event.preventDefault();
event.stopImmediatePropagation();
if (event.code === 'ArrowUp') {
this.window.scrollBy(0, 30);
} else if (event.code === 'ArrowDown') {
this.window.scrollBy(0, -30);
}
return false;
}
}
componentWillUnmount() {
window.removeEventListener('keydown', this.invertedKeyHandler, true);
}
setWindow(element) {
if (this.window) return;
@ -205,6 +223,7 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
element.scrollBy(0, event.deltaY * -1);
return false;
}, { passive: false });
window.addEventListener('keydown', this.invertedKeyHandler, { passive: false });
}
this.resetScroll();
}