diff --git a/pkg/interface/src/views/apps/chat/components/chat.tsx b/pkg/interface/src/views/apps/chat/components/chat.tsx index bf4c2f0e3..56fab6c72 100644 --- a/pkg/interface/src/views/apps/chat/components/chat.tsx +++ b/pkg/interface/src/views/apps/chat/components/chat.tsx @@ -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<{ diff --git a/pkg/interface/src/views/apps/chat/components/lib/chat-header.js b/pkg/interface/src/views/apps/chat/components/lib/ChatHeader.js similarity index 96% rename from pkg/interface/src/views/apps/chat/components/lib/chat-header.js rename to pkg/interface/src/views/apps/chat/components/lib/ChatHeader.js index ab63978f2..5cc2c0ee0 100644 --- a/pkg/interface/src/views/apps/chat/components/lib/chat-header.js +++ b/pkg/interface/src/views/apps/chat/components/lib/ChatHeader.js @@ -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) => { ); }; + +export default ChatHeader; \ No newline at end of file diff --git a/pkg/interface/src/views/apps/chat/components/lib/chat-input.tsx b/pkg/interface/src/views/apps/chat/components/lib/ChatInput.tsx similarity index 98% rename from pkg/interface/src/views/apps/chat/components/lib/chat-input.tsx rename to pkg/interface/src/views/apps/chat/components/lib/ChatInput.tsx index ba7671463..200e02817 100644 --- a/pkg/interface/src/views/apps/chat/components/lib/chat-input.tsx +++ b/pkg/interface/src/views/apps/chat/components/lib/ChatInput.tsx @@ -33,7 +33,7 @@ interface ChatInputState { } -export class ChatInput extends Component { +export default class ChatInput extends Component { public s3Uploader: React.RefObject; private chatEditor: React.RefObject; diff --git a/pkg/interface/src/views/apps/chat/components/lib/chat-message.tsx b/pkg/interface/src/views/apps/chat/components/lib/ChatMessage.tsx similarity index 96% rename from pkg/interface/src/views/apps/chat/components/lib/chat-message.tsx rename to pkg/interface/src/views/apps/chat/components/lib/ChatMessage.tsx index 16b074383..30ab082f9 100644 --- a/pkg/interface/src/views/apps/chat/components/lib/chat-message.tsx +++ b/pkg/interface/src/views/apps/chat/components/lib/ChatMessage.tsx @@ -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) => ( -
+
{ + setTimeout(() => { + element.style.opacity = '1'; + }, 250); + }} className="green2 flex items-center f9 absolute w-100" style={{...style, opacity: '0'}}>

New messages below


@@ -49,7 +53,7 @@ interface ChatMessageProps { scrollWindow: HTMLDivElement; } -export default class ChatMessage extends PureComponent { +export default class ChatMessage extends Component { private divRef: React.RefObject; constructor(props) { diff --git a/pkg/interface/src/views/apps/chat/components/lib/chat-window.tsx b/pkg/interface/src/views/apps/chat/components/lib/ChatWindow.tsx similarity index 74% rename from pkg/interface/src/views/apps/chat/components/lib/chat-window.tsx rename to pkg/interface/src/views/apps/chat/components/lib/ChatWindow.tsx index a89c10518..4724a94bf 100644 --- a/pkg/interface/src/views/apps/chat/components/lib/chat-window.tsx +++ b/pkg/interface/src/views/apps/chat/components/lib/ChatWindow.tsx @@ -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; } -export class ChatWindow extends Component { - private unreadReference: React.RefObject; +export default class ChatWindow extends Component { private virtualList: VirtualScroller | null; INITIALIZATION_MAX_TIME = 1500; @@ -64,9 +61,7 @@ export class ChatWindow extends Component { 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 { 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 { } 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 { 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 { } } - assembleMessages() { - const { envelopes, stationPendingMessages } = this.props; - const messages: Map = 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 { 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 { 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 { 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 ; } + 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 ( ); }} diff --git a/pkg/interface/src/views/apps/chat/components/lib/group-item.js b/pkg/interface/src/views/apps/chat/components/lib/group-item.js index fdc405c4a..79d8a8cb4 100644 --- a/pkg/interface/src/views/apps/chat/components/lib/group-item.js +++ b/pkg/interface/src/views/apps/chat/components/lib/group-item.js @@ -79,6 +79,7 @@ export class GroupItem extends Component { if (props.index === 'dm') { dmLink = -

{title}

+

{title}

{dmLink} {channelItems}
diff --git a/pkg/interface/src/views/apps/chat/components/settings.js b/pkg/interface/src/views/apps/chat/components/settings.js index a79011ded..4e219350d 100644 --- a/pkg/interface/src/views/apps/chat/components/settings.js +++ b/pkg/interface/src/views/apps/chat/components/settings.js @@ -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) { diff --git a/pkg/interface/src/views/components/VirtualScroller.tsx b/pkg/interface/src/views/components/VirtualScroller.tsx index ca8903e5f..c5b98816c 100644 --- a/pkg/interface/src/views/components/VirtualScroller.tsx +++ b/pkg/interface/src/views/components/VirtualScroller.tsx @@ -53,6 +53,7 @@ export default class VirtualScroller extends PureComponent