mirror of
https://github.com/ilyakooo0/urbit.git
synced 2024-12-15 01:52:42 +03:00
commit
fd49748c9f
@ -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<{
|
||||||
|
@ -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;
|
@ -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>;
|
||||||
|
|
@ -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) {
|
@ -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' : ''}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
@ -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>
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user