diff --git a/src/client/webapp/elements/components/infinite-scroll.tsx b/src/client/webapp/elements/components/infinite-scroll.tsx index 7461fa2..d3cb5f9 100644 --- a/src/client/webapp/elements/components/infinite-scroll.tsx +++ b/src/client/webapp/elements/components/infinite-scroll.tsx @@ -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; fetchRetryCallable: () => Promise; fetchAboveCallable: () => Promise; fetchBelowCallable: () => Promise; @@ -20,6 +21,7 @@ export interface InfiniteScrollProps { // Implements convenient features such as 'try again' components const InfiniteScroll: FC = (props: InfiniteScrollProps) => { const { + infiniteScrollElementRef, fetchRetryCallable, fetchAboveCallable, fetchBelowCallable, @@ -47,7 +49,7 @@ const InfiniteScroll: FC = (props: InfiniteScrollProps) => ); return ( -
+
{children} diff --git a/src/client/webapp/elements/lists/message-list.tsx b/src/client/webapp/elements/lists/message-list.tsx index 4c7a809..5f4a4ad 100644 --- a/src/client/webapp/elements/lists/message-list.tsx +++ b/src/client/webapp/elements/lists/message-list.tsx @@ -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 Promise) | null>>; setOverlay: Dispatch>; } const MessageList: FC = (props: MessageListProps) => { - const { guild, channel, channelGuild, setOverlay } = props; + const { guild, channel, channelGuild, setFetchRetryCallable, setOverlay } = props; + const infiniteScrollElementRef = useRef(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 = (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 = (props: MessageListProps) => { return (
Promise, fetchAboveFunc: ((reference: T) => Promise), fetchBelowFunc: ((reference: T) => Promise), + scrollToBottomFunc: () => void, ): [ fetchRetryCallable: () => Promise, fetchAboveCallable: () => Promise, @@ -369,6 +370,8 @@ function useMultipleGuildSubscriptionScrolling< } = eventMappingParams; const isMounted = useIsMountedRef(); + const guildRef = useRef(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 | 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 + ); } diff --git a/src/client/webapp/elements/sections/guild.tsx b/src/client/webapp/elements/sections/guild.tsx index 3a8cc68..523d5d4 100644 --- a/src/client/webapp/elements/sections/guild.tsx +++ b/src/client/webapp/elements/sections/guild.tsx @@ -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 = (props: GuildElementProps) => { const [ activeChannel, setActiveChannel ] = useState(null); const [ activeChannelGuild, setActiveChannelGuild ] = useState(null); + const [ fetchMessagesRetryCallable, setFetchMessagesRetryCallable ] = useState<(() => Promise) | null>(null); + useEffect(() => { setActiveChannel(null); }, [ guild ]); @@ -57,6 +59,28 @@ const GuildElement: FC = (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 (
@@ -73,7 +97,7 @@ const GuildElement: FC = (props: GuildElementProps) => {
- {activeChannel && activeChannelGuild && } + {activeChannel && activeChannelGuild && } {activeChannel && activeChannelGuild && }
diff --git a/src/client/webapp/elements/sections/send-message.tsx b/src/client/webapp/elements/sections/send-message.tsx index d7b801b..bbd08f8 100644 --- a/src/client/webapp/elements/sections/send-message.tsx +++ b/src/client/webapp/elements/sections/send-message.tsx @@ -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 = (props: SendMessageProps) => { if (!isMounted.current) return; } + setText(''); if (contentEditableRef.current) contentEditableRef.current.innerText = ''; setEnabled(true); diff --git a/src/client/webapp/preload.tsx b/src/client/webapp/preload.tsx index 87265cc..7447a82 100644 --- a/src/client/webapp/preload.tsx +++ b/src/client/webapp/preload.tsx @@ -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(, document.getElementById('react-root')); })();