Jump messages to bottom when current user sent the message

This commit is contained in:
Michael Peters 2022-01-23 16:39:14 -06:00
parent ce9b17535b
commit b97db81fa2
6 changed files with 60 additions and 17 deletions

View File

@ -1,8 +1,9 @@
import React, { Dispatch, FC, ReactNode, SetStateAction } from 'react';
import React, { Dispatch, FC, ReactNode, RefObject, SetStateAction } from 'react';
import { useColumnReverseInfiniteScroll } from '../require/react-helper';
import Retry from './retry';
export interface InfiniteScrollProps {
infiniteScrollElementRef: RefObject<HTMLDivElement>;
fetchRetryCallable: () => Promise<void>;
fetchAboveCallable: () => Promise<void>;
fetchBelowCallable: () => Promise<void>;
@ -20,6 +21,7 @@ export interface InfiniteScrollProps {
// Implements convenient features such as 'try again' components
const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) => {
const {
infiniteScrollElementRef,
fetchRetryCallable,
fetchAboveCallable,
fetchBelowCallable,
@ -47,7 +49,7 @@ const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) =>
);
return (
<div className="infinite-scroll-scroll-base" onScroll={updateScrollCallable}>
<div className="infinite-scroll-scroll-base" ref={infiniteScrollElementRef} onScroll={updateScrollCallable}>
<div className="infinite-scroll-elements">
<Retry error={fetchAboveError} text={fetchAboveErrorMessage} retryFunc={fetchAboveRetry} />
{children}

View File

@ -1,4 +1,4 @@
import React, { Dispatch, FC, ReactNode, SetStateAction, useMemo } from 'react';
import React, { Dispatch, FC, ReactNode, SetStateAction, useCallback, useEffect, useMemo, useRef } from 'react';
import { Channel, Message } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import MessageElement from './components/message-element';
@ -9,12 +9,22 @@ interface MessageListProps {
guild: CombinedGuild;
channel: Channel;
channelGuild: CombinedGuild;
setFetchRetryCallable: Dispatch<SetStateAction<(() => Promise<void>) | null>>;
setOverlay: Dispatch<SetStateAction<ReactNode>>;
}
const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
const { guild, channel, channelGuild, setOverlay } = props;
const { guild, channel, channelGuild, setFetchRetryCallable, setOverlay } = props;
const infiniteScrollElementRef = useRef<HTMLDivElement>(null);
const scrollToBottomFunc = useCallback(() => {
if (!infiniteScrollElementRef.current) return;
console.log('scrolling to bottom');
infiniteScrollElementRef.current.scrollTop = 0; // Keep in mind that this is reversed for flex-flow: column-reverse
}, []);
// TODO: Store state higher up
const [
fetchRetryCallable,
fetchAboveCallable,
@ -24,7 +34,12 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
messagesFetchError,
messagesFetchAboveError,
messagesFetchBelowError
] = useMessagesScrollingSubscription(guild, channel, channelGuild);
] = useMessagesScrollingSubscription(guild, channel, channelGuild, scrollToBottomFunc);
useEffect(() => {
// Note: have to use a function here otherwise React trys to "help out" by running the promise
setFetchRetryCallable(() => { return fetchRetryCallable });
}, [ fetchRetryCallable ]);
const messageElements = useMemo(() => {
const result = [];
@ -41,6 +56,7 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
return (
<div className="message-list">
<InfiniteScroll
infiniteScrollElementRef={infiniteScrollElementRef}
fetchRetryCallable={fetchRetryCallable}
fetchAboveCallable={fetchAboveCallable}
fetchBelowCallable={fetchBelowCallable}

View File

@ -350,6 +350,7 @@ function useMultipleGuildSubscriptionScrolling<
fetchFunc: () => Promise<T[] | null>,
fetchAboveFunc: ((reference: T) => Promise<T[] | null>),
fetchBelowFunc: ((reference: T) => Promise<T[] | null>),
scrollToBottomFunc: () => void,
): [
fetchRetryCallable: () => Promise<void>,
fetchAboveCallable: () => Promise<void>,
@ -369,6 +370,8 @@ function useMultipleGuildSubscriptionScrolling<
} = eventMappingParams;
const isMounted = useIsMountedRef();
const guildRef = useRef<CombinedGuild>(guild);
guildRef.current = guild;
// TODO: lastResult.value should really be only T[] instead of | null since we set it to [] anyway in the onUpdate, etc functions
const [ lastResult, setLastResult ] = useState<ScrollingSubscriptionResult<T> | null>(null);
@ -535,6 +538,7 @@ function useMultipleGuildSubscriptionScrolling<
guild: fetchValueGuild
});
setFetchError(null);
scrollToBottomFunc(); // Make sure that we scroll back to the bottom
}, [ sortFunc, maxFetchElements, maxElements ]);
const onFetchError = useCallback((e: unknown) => {
@ -799,7 +803,7 @@ export function useTokensSubscription(guild: CombinedGuild) {
}, fetchTokensFunc);
}
export function useMessagesScrollingSubscription(guild: CombinedGuild, channel: Channel, channelGuild: CombinedGuild) {
export function useMessagesScrollingSubscription(guild: CombinedGuild, channel: Channel, channelGuild: CombinedGuild, scrollToBottomFunc: () => void) {
const maxFetchElements = Globals.MESSAGES_PER_REQUEST;
const maxElements = Globals.MAX_CURRENT_MESSAGES;
const fetchMessagesFunc = useCallback(async () => {
@ -829,7 +833,8 @@ export function useMessagesScrollingSubscription(guild: CombinedGuild, channel:
sortFunc: Message.sortOrder
},
maxElements, maxFetchElements,
fetchMessagesFunc, fetchAboveFunc, fetchBelowFunc
)
fetchMessagesFunc, fetchAboveFunc, fetchBelowFunc,
scrollToBottomFunc
);
}

View File

@ -3,8 +3,8 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { Dispatch, FC, ReactNode, SetStateAction, useEffect, useState } from 'react';
import { Channel } from '../../data-types';
import React, { Dispatch, FC, ReactNode, SetStateAction, useCallback, useEffect, useState } from 'react';
import { Channel, Message } from '../../data-types';
import CombinedGuild from '../../guild-combined';
import ChannelList from '../lists/channel-list';
import MemberList from '../lists/member-list';
@ -36,6 +36,8 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
const [ activeChannel, setActiveChannel ] = useState<Channel | null>(null);
const [ activeChannelGuild, setActiveChannelGuild ] = useState<CombinedGuild | null>(null);
const [ fetchMessagesRetryCallable, setFetchMessagesRetryCallable ] = useState<(() => Promise<void>) | null>(null);
useEffect(() => {
setActiveChannel(null);
}, [ guild ]);
@ -57,6 +59,28 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
}
}, [ channelsResult, activeChannel ]);
// When the self member sends a message to the active channel and we get it as a new message,
// move the active channel's messages back to the bottom
const onNewMessage = useCallback((messages: Message[]) => {
if (messages.find(message =>
message.member.id === selfMemberResult?.value?.id &&
message.channel.id === activeChannel?.id)
) {
if (fetchMessagesRetryCallable) {
// TODO: Only do the fetch if we're not at the bottom. If we're at the bottom, just
// set the scroll height
fetchMessagesRetryCallable(); // Re-load the messages to move them to the bottom.
}
}
}, [ fetchMessagesRetryCallable/*, Note: Removing these to prevent re-sending fetch. This technique should probably be changed in the future with react/redux selfMemberResult, activeChannel */ ]);
useEffect(() => {
guild.on('new-messages', onNewMessage);
return () => {
guild.off('new-messages', onNewMessage);
}
});
return (
<div className="guild-react">
<div className="guild-sidebar">
@ -73,7 +97,7 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
<ChannelTitle channel={activeChannel} />
<div className="guild-channel-content">
<div className="guild-channel-feed-wrapper">
{activeChannel && activeChannelGuild && <MessageList guild={guild} channel={activeChannel} channelGuild={activeChannelGuild} setOverlay={setOverlay} />}
{activeChannel && activeChannelGuild && <MessageList guild={guild} channel={activeChannel} channelGuild={activeChannelGuild} setFetchRetryCallable={setFetchMessagesRetryCallable} setOverlay={setOverlay} />}
{activeChannel && activeChannelGuild && <SendMessage guild={guild} channel={activeChannel} />}
</div>
<div className="member-list-wrapper">

View File

@ -1,8 +1,3 @@
import * as electronRemote from '@electron/remote';
const electronConsole = electronRemote.getGlobal('console') as Console;
import Logger from '../../../../logger/logger';
const LOG = Logger.create(__filename, electronConsole);
import React, { ClipboardEvent, FC, FormEvent, KeyboardEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react';
import { Channel } from '../../data-types';
import CombinedGuild from '../../guild-combined';
@ -79,6 +74,7 @@ const SendMessage: FC<SendMessageProps> = (props: SendMessageProps) => {
if (!isMounted.current) return;
}
setText('');
if (contentEditableRef.current) contentEditableRef.current.innerText = '';
setEnabled(true);

View File

@ -56,7 +56,7 @@ window.addEventListener('DOMContentLoaded', () => {
const guildsManager = new GuildsManager(messageRAMCache, resourceRAMCache, personalDB);
await guildsManager.init();
LOG.silly('events bound');
LOG.silly('guildsManager initialized');
ReactDOM.render(<RootElement guildsManager={guildsManager} />, document.getElementById('react-root'));
})();