Merge pull request #3491 from tylershuster/misc-fixes

chat: fixes
This commit is contained in:
matildepark 2020-09-16 00:26:12 -04:00 committed by GitHub
commit fd49748c9f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 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 { deSig } from "~/logic/lib/util";
import { ChatHookUpdate } from "~/types/chat-hook-update"; import { ChatHookUpdate } from "~/types/chat-hook-update";
import { Inbox, Envelope } from "~/types/chat-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 { Association } from "~/types/metadata-update";
import {Group} from "~/types/group-update"; import {Group} from "~/types/group-update";
import { LocalUpdateRemoteContentPolicy } from "~/types"; import { LocalUpdateRemoteContentPolicy } from "~/types";
import { S3Upload, SubmitDragger } from '~/views/components/s3-upload'; import { SubmitDragger } from '~/views/components/s3-upload';
import { IUnControlledCodeMirror } from "react-codemirror2";
import ChatWindow from './lib/ChatWindow';
import ChatHeader from './lib/ChatHeader';
import ChatInput from "./lib/ChatInput";
type ChatScreenProps = RouteComponentProps<{ type ChatScreenProps = RouteComponentProps<{

View File

@ -5,7 +5,7 @@ import { TabBar } from '~/views/components/chat-link-tabbar';
import { SidebarSwitcher } from '~/views/components/SidebarSwitch'; import { SidebarSwitcher } from '~/views/components/SidebarSwitch';
import { deSig } from '~/logic/lib/util'; import { deSig } from '~/logic/lib/util';
export const ChatHeader = (props) => { const ChatHeader = (props) => {
const isInPopout = props.popout ? 'popout/' : ''; const isInPopout = props.popout ? 'popout/' : '';
const group = Array.from(props.group.members); const group = Array.from(props.group.members);
let title = props.station.substr(1); let title = props.station.substr(1);
@ -59,3 +59,5 @@ export const ChatHeader = (props) => {
</Fragment> </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>; public s3Uploader: React.RefObject<S3Upload>;
private chatEditor: React.RefObject<ChatEditor>; 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 moment from "moment";
import _ from "lodash"; import _ from "lodash";
@ -13,7 +13,11 @@ import RemoteContent from '~/views/components/RemoteContent';
export const DATESTAMP_FORMAT = '[~]YYYY.M.D'; export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
export const UnreadMarker = React.forwardRef(({ dayBreak, when, style }, ref) => ( 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" /> <hr className="dn-s ma0 w2 b--green2 bt-0" />
<p className="mh4">New messages below</p> <p className="mh4">New messages below</p>
<hr className="ma0 flex-grow-1 b--green2 bt-0" /> <hr className="ma0 flex-grow-1 b--green2 bt-0" />
@ -49,7 +53,7 @@ interface ChatMessageProps {
scrollWindow: HTMLDivElement; scrollWindow: HTMLDivElement;
} }
export default class ChatMessage extends PureComponent<ChatMessageProps> { export default class ChatMessage extends Component<ChatMessageProps> {
private divRef: React.RefObject<HTMLDivElement>; private divRef: React.RefObject<HTMLDivElement>;
constructor(props) { constructor(props) {

View File

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

View File

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

View File

@ -1,11 +1,12 @@
import React, { Component, Fragment } from 'react'; 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 { MetadataSettings } from '~/views/components/metadata/settings';
import { Spinner } from '~/views/components/Spinner';
import ChatHeader from './lib/ChatHeader';
import { DeleteButton } from './lib/delete-button'; import { DeleteButton } from './lib/delete-button';
import { GroupifyButton } from './lib/groupify-button'; import { GroupifyButton } from './lib/groupify-button';
import { Spinner } from '~/views/components/Spinner';
export class SettingsScreen extends Component { export class SettingsScreen extends Component {
constructor(props) { constructor(props) {

View File

@ -53,6 +53,7 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
this.recalculateTotalHeight = this.recalculateTotalHeight.bind(this); this.recalculateTotalHeight = this.recalculateTotalHeight.bind(this);
this.calculateVisibleItems = this.calculateVisibleItems.bind(this); this.calculateVisibleItems = this.calculateVisibleItems.bind(this);
this.estimateIndexFromScrollTop = this.estimateIndexFromScrollTop.bind(this); this.estimateIndexFromScrollTop = this.estimateIndexFromScrollTop.bind(this);
this.invertedKeyHandler = this.invertedKeyHandler.bind(this);
this.heightOf = this.heightOf.bind(this); this.heightOf = this.heightOf.bind(this);
this.setScrollTop = this.setScrollTop.bind(this); this.setScrollTop = this.setScrollTop.bind(this);
this.scrollToData = this.scrollToData.bind(this); this.scrollToData = this.scrollToData.bind(this);
@ -195,6 +196,23 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
start, end 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) { setWindow(element) {
if (this.window) return; if (this.window) return;
@ -205,6 +223,7 @@ export default class VirtualScroller extends PureComponent<VirtualScrollerProps,
element.scrollBy(0, event.deltaY * -1); element.scrollBy(0, event.deltaY * -1);
return false; return false;
}, { passive: false }); }, { passive: false });
window.addEventListener('keydown', this.invertedKeyHandler, { passive: false });
} }
this.resetScroll(); this.resetScroll();
} }