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