mirror of
https://github.com/urbit/shrub.git
synced 2024-11-28 22:33:06 +03:00
parent
6f5663bcd3
commit
80815b9880
@ -1,6 +1,7 @@
|
||||
import React, { Component, PureComponent } from "react";
|
||||
import moment from "moment";
|
||||
import _ from "lodash";
|
||||
import { Box } from "@tlon/indigo-react";
|
||||
|
||||
import { OverlaySigil } from './overlay-sigil';
|
||||
import { uxToHex, cite, writeText } from '~/logic/lib/util';
|
||||
@ -12,14 +13,10 @@ import RemoteContent from '~/views/components/RemoteContent';
|
||||
|
||||
export const DATESTAMP_FORMAT = '[~]YYYY.M.D';
|
||||
|
||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when, style }, ref) => (
|
||||
<div ref={element => {
|
||||
setTimeout(() => {
|
||||
element.style.opacity = '1';
|
||||
}, 250);
|
||||
}} className="green2 flex items-center f9 absolute w-100" style={{...style, opacity: '0'}}>
|
||||
export const UnreadMarker = React.forwardRef(({ dayBreak, when }, ref) => (
|
||||
<div ref={ref} className="green2 flex items-center f9 absolute w-100 left-0">
|
||||
<hr className="dn-s ma0 w2 b--green2 bt-0" />
|
||||
<p className="mh4">New messages below</p>
|
||||
<p className="mh4" style={{ whiteSpace: 'normal' }}>New messages below</p>
|
||||
<hr className="ma0 flex-grow-1 b--green2 bt-0" />
|
||||
{dayBreak
|
||||
? <p className="gray2 mh4">{moment(when).calendar()}</p>
|
||||
@ -39,18 +36,19 @@ interface ChatMessageProps {
|
||||
msg: Envelope | IMessage;
|
||||
previousMsg: Envelope | IMessage | undefined;
|
||||
nextMsg: Envelope | IMessage | undefined;
|
||||
isFirstUnread: boolean;
|
||||
isLastRead: boolean;
|
||||
group: Group;
|
||||
association: Association;
|
||||
contacts: Contacts;
|
||||
unreadRef: React.RefObject<HTMLDivElement>;
|
||||
hideAvatars: boolean;
|
||||
hideNicknames: boolean;
|
||||
remoteContentPolicy: LocalUpdateRemoteContentPolicy;
|
||||
className: string;
|
||||
className?: string;
|
||||
isPending: boolean;
|
||||
style?: any;
|
||||
scrollWindow: HTMLDivElement;
|
||||
isLastMessage?: boolean;
|
||||
unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
@ -72,11 +70,10 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
msg,
|
||||
previousMsg,
|
||||
nextMsg,
|
||||
isFirstUnread,
|
||||
isLastRead,
|
||||
group,
|
||||
association,
|
||||
contacts,
|
||||
unreadRef,
|
||||
hideAvatars,
|
||||
hideNicknames,
|
||||
remoteContentPolicy,
|
||||
@ -84,7 +81,9 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
isPending,
|
||||
style,
|
||||
measure,
|
||||
scrollWindow
|
||||
scrollWindow,
|
||||
isLastMessage,
|
||||
unreadMarkerRef
|
||||
} = this.props;
|
||||
|
||||
const renderSigil = Boolean((nextMsg && msg.author !== nextMsg.author) || !nextMsg || msg.number === 1);
|
||||
@ -92,7 +91,7 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
|
||||
const containerClass = `${renderSigil
|
||||
? `w-100 flex flex-wrap cf pr3 f7 pt4 pl3 lh-copy`
|
||||
: `w-100 flex flex-wrap cf pr3 hide-child`} ${isPending ? ' o-40' : ''} ${className}`
|
||||
: `w-100 flex flex-wrap cf pr3 hide-child`} ${isPending ? 'o-40' : ''} ${isLastMessage ? 'pb3' : ''} ${className}`
|
||||
|
||||
const timestamp = moment.unix(msg.when / 1000).format(renderSigil ? 'hh:mm a' : 'hh:mm');
|
||||
|
||||
@ -116,15 +115,19 @@ export default class ChatMessage extends Component<ChatMessageProps> {
|
||||
scrollWindow
|
||||
};
|
||||
|
||||
const unreadContainerStyle = {
|
||||
height: isLastRead ? '1.66em' : '0',
|
||||
};
|
||||
|
||||
return (
|
||||
<div ref={this.divRef} className={containerClass} style={style} data-number={msg.number}>
|
||||
{dayBreak && !isFirstUnread ? <DayBreak when={msg.when} /> : null}
|
||||
{dayBreak && !isLastRead ? <DayBreak when={msg.when} /> : null}
|
||||
{renderSigil
|
||||
? <MessageWithSigil {...messageProps} />
|
||||
: <MessageWithoutSigil {...messageProps} />}
|
||||
{isFirstUnread
|
||||
? <UnreadMarker ref={unreadRef} dayBreak={dayBreak} when={msg.when} style={{ marginTop: (renderSigil ? "-17px" : "-6px") }} />
|
||||
: null}
|
||||
<Box fontSize='0' position='relative' width='100%' overflow='hidden' style={unreadContainerStyle}>{isLastRead
|
||||
? <UnreadMarker dayBreak={dayBreak} when={msg.when} ref={unreadMarkerRef} />
|
||||
: null}</Box>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -48,10 +48,12 @@ interface ChatWindowState {
|
||||
fetchPending: boolean;
|
||||
idle: boolean;
|
||||
initialized: boolean;
|
||||
lastRead: number;
|
||||
}
|
||||
|
||||
export default class ChatWindow extends Component<ChatWindowProps, ChatWindowState> {
|
||||
private virtualList: VirtualScroller | null;
|
||||
private unreadMarkerRef: React.RefObject<HTMLDivElement>;
|
||||
|
||||
INITIALIZATION_MAX_TIME = 1500;
|
||||
|
||||
@ -61,18 +63,20 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.state = {
|
||||
fetchPending: false,
|
||||
idle: true,
|
||||
initialized: false
|
||||
initialized: false,
|
||||
lastRead: props.unreadCount ? props.mailboxSize - props.unreadCount : Infinity,
|
||||
};
|
||||
|
||||
this.dismissUnread = this.dismissUnread.bind(this);
|
||||
this.initialIndex = this.initialIndex.bind(this);
|
||||
this.scrollToUnread = this.scrollToUnread.bind(this);
|
||||
this.handleWindowBlur = this.handleWindowBlur.bind(this);
|
||||
this.handleWindowFocus = this.handleWindowFocus.bind(this);
|
||||
this.stayLockedIfActive = this.stayLockedIfActive.bind(this);
|
||||
this.firstUnread = this.firstUnread.bind(this);
|
||||
this.dismissIfLineVisible = this.dismissIfLineVisible.bind(this);
|
||||
this.lastRead = this.lastRead.bind(this);
|
||||
|
||||
this.virtualList = null;
|
||||
this.unreadMarkerRef = React.createRef();
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
@ -97,14 +101,6 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.setState({ idle: false });
|
||||
}
|
||||
|
||||
initialIndex() {
|
||||
const { mailboxSize, unreadCount } = this.props;
|
||||
return Math.min(Math.max(mailboxSize - 1 < INITIAL_LOAD
|
||||
? 0
|
||||
: this.firstUnread(),
|
||||
0), mailboxSize);
|
||||
}
|
||||
|
||||
initialFetch() {
|
||||
const { envelopes, mailboxSize, unreadCount } = this.props;
|
||||
if (envelopes.length > 0) {
|
||||
@ -112,17 +108,9 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.stayLockedIfActive();
|
||||
this.fetchMessages(start, start + DEFAULT_BACKLOG_SIZE, true).then(() => {
|
||||
if (!this.virtualList) return;
|
||||
const initialIndex = this.initialIndex();
|
||||
this.virtualList.scrollToData(initialIndex).then(() => {
|
||||
if (
|
||||
initialIndex === mailboxSize
|
||||
|| (this.virtualList && this.virtualList.window && this.virtualList.window.scrollTop === 0)
|
||||
) {
|
||||
this.setState({ idle: false });
|
||||
this.dismissUnread();
|
||||
}
|
||||
this.setState({ initialized: true });
|
||||
});
|
||||
this.setState({ idle: false });
|
||||
this.setState({ initialized: true });
|
||||
this.dismissIfLineVisible();
|
||||
});
|
||||
} else {
|
||||
setTimeout(() => {
|
||||
@ -195,9 +183,31 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
});
|
||||
}
|
||||
|
||||
firstUnread() {
|
||||
lastRead() {
|
||||
const { mailboxSize, unreadCount } = this.props;
|
||||
return mailboxSize - unreadCount + 1;
|
||||
return mailboxSize - unreadCount;
|
||||
}
|
||||
|
||||
onScroll({ scrollTop, scrollHeight, windowHeight }) {
|
||||
if (!this.state.idle && scrollTop > IDLE_THRESHOLD) {
|
||||
this.setState({ idle: true });
|
||||
}
|
||||
|
||||
this.dismissIfLineVisible();
|
||||
}
|
||||
|
||||
dismissIfLineVisible() {
|
||||
if (this.props.unreadCount === 0) return;
|
||||
if (!this.unreadMarkerRef.current || !this.virtualList?.window) return;
|
||||
const parent = this.unreadMarkerRef.current.parentElement?.parentElement;
|
||||
if (!parent) return;
|
||||
const { scrollTop, scrollHeight, offsetHeight } = this.virtualList.window;
|
||||
if (
|
||||
(scrollHeight - parent.offsetTop > scrollTop)
|
||||
&& (scrollHeight - parent.offsetTop < scrollTop + offsetHeight)
|
||||
) {
|
||||
this.dismissUnread();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -220,6 +230,8 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
remoteContentPolicy,
|
||||
} = this.props;
|
||||
|
||||
const unreadMarkerRef = this.unreadMarkerRef;
|
||||
|
||||
const messages = new Map();
|
||||
let lastMessage = 0;
|
||||
|
||||
@ -238,7 +250,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
lastMessage = envelopes.length + index;
|
||||
});
|
||||
|
||||
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy };
|
||||
const messageProps = { association, group, contacts, hideAvatars, hideNicknames, remoteContentPolicy, unreadMarkerRef };
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -258,11 +270,7 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
this.setState({ idle: false });
|
||||
this.dismissUnread();
|
||||
}}
|
||||
onScroll={({ scrollTop }) => {
|
||||
if (!this.state.idle && scrollTop > IDLE_THRESHOLD) {
|
||||
this.setState({ idle: true });
|
||||
}
|
||||
}}
|
||||
onScroll={this.onScroll.bind(this)}
|
||||
data={messages}
|
||||
size={mailboxSize + stationPendingMessages.length}
|
||||
renderer={({ index, measure, scrollWindow }) => {
|
||||
@ -272,15 +280,14 @@ export default class ChatWindow extends Component<ChatWindowProps, ChatWindowSta
|
||||
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 };
|
||||
const isLastRead: boolean = Boolean(!isLastMessage && index === this.state.lastRead);
|
||||
const props = { measure, scrollWindow, isPending, isLastRead, isLastMessage, msg, ...messageProps };
|
||||
return (
|
||||
<ChatMessage
|
||||
key={index}
|
||||
previousMsg={messages.get(index + 1)}
|
||||
nextMsg={messages.get(index - 1)}
|
||||
className={isLastMessage ? 'pb3' : ''}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user