Jump messages to bottom when current user sent the message
This commit is contained in:
parent
ce9b17535b
commit
b97db81fa2
@ -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 { useColumnReverseInfiniteScroll } from '../require/react-helper';
|
||||||
import Retry from './retry';
|
import Retry from './retry';
|
||||||
|
|
||||||
export interface InfiniteScrollProps {
|
export interface InfiniteScrollProps {
|
||||||
|
infiniteScrollElementRef: RefObject<HTMLDivElement>;
|
||||||
fetchRetryCallable: () => Promise<void>;
|
fetchRetryCallable: () => Promise<void>;
|
||||||
fetchAboveCallable: () => Promise<void>;
|
fetchAboveCallable: () => Promise<void>;
|
||||||
fetchBelowCallable: () => Promise<void>;
|
fetchBelowCallable: () => Promise<void>;
|
||||||
@ -20,6 +21,7 @@ export interface InfiniteScrollProps {
|
|||||||
// Implements convenient features such as 'try again' components
|
// Implements convenient features such as 'try again' components
|
||||||
const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) => {
|
const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) => {
|
||||||
const {
|
const {
|
||||||
|
infiniteScrollElementRef,
|
||||||
fetchRetryCallable,
|
fetchRetryCallable,
|
||||||
fetchAboveCallable,
|
fetchAboveCallable,
|
||||||
fetchBelowCallable,
|
fetchBelowCallable,
|
||||||
@ -47,7 +49,7 @@ const InfiniteScroll: FC<InfiniteScrollProps> = (props: InfiniteScrollProps) =>
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="infinite-scroll-scroll-base" onScroll={updateScrollCallable}>
|
<div className="infinite-scroll-scroll-base" ref={infiniteScrollElementRef} onScroll={updateScrollCallable}>
|
||||||
<div className="infinite-scroll-elements">
|
<div className="infinite-scroll-elements">
|
||||||
<Retry error={fetchAboveError} text={fetchAboveErrorMessage} retryFunc={fetchAboveRetry} />
|
<Retry error={fetchAboveError} text={fetchAboveErrorMessage} retryFunc={fetchAboveRetry} />
|
||||||
{children}
|
{children}
|
||||||
|
@ -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 { Channel, Message } from '../../data-types';
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import MessageElement from './components/message-element';
|
import MessageElement from './components/message-element';
|
||||||
@ -9,12 +9,22 @@ interface MessageListProps {
|
|||||||
guild: CombinedGuild;
|
guild: CombinedGuild;
|
||||||
channel: Channel;
|
channel: Channel;
|
||||||
channelGuild: CombinedGuild;
|
channelGuild: CombinedGuild;
|
||||||
|
setFetchRetryCallable: Dispatch<SetStateAction<(() => Promise<void>) | null>>;
|
||||||
setOverlay: Dispatch<SetStateAction<ReactNode>>;
|
setOverlay: Dispatch<SetStateAction<ReactNode>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
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 [
|
const [
|
||||||
fetchRetryCallable,
|
fetchRetryCallable,
|
||||||
fetchAboveCallable,
|
fetchAboveCallable,
|
||||||
@ -24,7 +34,12 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
|||||||
messagesFetchError,
|
messagesFetchError,
|
||||||
messagesFetchAboveError,
|
messagesFetchAboveError,
|
||||||
messagesFetchBelowError
|
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 messageElements = useMemo(() => {
|
||||||
const result = [];
|
const result = [];
|
||||||
@ -41,6 +56,7 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
|||||||
return (
|
return (
|
||||||
<div className="message-list">
|
<div className="message-list">
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
|
infiniteScrollElementRef={infiniteScrollElementRef}
|
||||||
fetchRetryCallable={fetchRetryCallable}
|
fetchRetryCallable={fetchRetryCallable}
|
||||||
fetchAboveCallable={fetchAboveCallable}
|
fetchAboveCallable={fetchAboveCallable}
|
||||||
fetchBelowCallable={fetchBelowCallable}
|
fetchBelowCallable={fetchBelowCallable}
|
||||||
|
@ -350,6 +350,7 @@ function useMultipleGuildSubscriptionScrolling<
|
|||||||
fetchFunc: () => Promise<T[] | null>,
|
fetchFunc: () => Promise<T[] | null>,
|
||||||
fetchAboveFunc: ((reference: T) => Promise<T[] | null>),
|
fetchAboveFunc: ((reference: T) => Promise<T[] | null>),
|
||||||
fetchBelowFunc: ((reference: T) => Promise<T[] | null>),
|
fetchBelowFunc: ((reference: T) => Promise<T[] | null>),
|
||||||
|
scrollToBottomFunc: () => void,
|
||||||
): [
|
): [
|
||||||
fetchRetryCallable: () => Promise<void>,
|
fetchRetryCallable: () => Promise<void>,
|
||||||
fetchAboveCallable: () => Promise<void>,
|
fetchAboveCallable: () => Promise<void>,
|
||||||
@ -369,6 +370,8 @@ function useMultipleGuildSubscriptionScrolling<
|
|||||||
} = eventMappingParams;
|
} = eventMappingParams;
|
||||||
|
|
||||||
const isMounted = useIsMountedRef();
|
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
|
// 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);
|
const [ lastResult, setLastResult ] = useState<ScrollingSubscriptionResult<T> | null>(null);
|
||||||
@ -535,6 +538,7 @@ function useMultipleGuildSubscriptionScrolling<
|
|||||||
guild: fetchValueGuild
|
guild: fetchValueGuild
|
||||||
});
|
});
|
||||||
setFetchError(null);
|
setFetchError(null);
|
||||||
|
scrollToBottomFunc(); // Make sure that we scroll back to the bottom
|
||||||
}, [ sortFunc, maxFetchElements, maxElements ]);
|
}, [ sortFunc, maxFetchElements, maxElements ]);
|
||||||
|
|
||||||
const onFetchError = useCallback((e: unknown) => {
|
const onFetchError = useCallback((e: unknown) => {
|
||||||
@ -799,7 +803,7 @@ export function useTokensSubscription(guild: CombinedGuild) {
|
|||||||
}, fetchTokensFunc);
|
}, 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 maxFetchElements = Globals.MESSAGES_PER_REQUEST;
|
||||||
const maxElements = Globals.MAX_CURRENT_MESSAGES;
|
const maxElements = Globals.MAX_CURRENT_MESSAGES;
|
||||||
const fetchMessagesFunc = useCallback(async () => {
|
const fetchMessagesFunc = useCallback(async () => {
|
||||||
@ -829,7 +833,8 @@ export function useMessagesScrollingSubscription(guild: CombinedGuild, channel:
|
|||||||
sortFunc: Message.sortOrder
|
sortFunc: Message.sortOrder
|
||||||
},
|
},
|
||||||
maxElements, maxFetchElements,
|
maxElements, maxFetchElements,
|
||||||
fetchMessagesFunc, fetchAboveFunc, fetchBelowFunc
|
fetchMessagesFunc, fetchAboveFunc, fetchBelowFunc,
|
||||||
)
|
scrollToBottomFunc
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,8 +3,8 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
|||||||
import Logger from '../../../../logger/logger';
|
import Logger from '../../../../logger/logger';
|
||||||
const LOG = Logger.create(__filename, electronConsole);
|
const LOG = Logger.create(__filename, electronConsole);
|
||||||
|
|
||||||
import React, { Dispatch, FC, ReactNode, SetStateAction, useEffect, useState } from 'react';
|
import React, { Dispatch, FC, ReactNode, SetStateAction, useCallback, useEffect, useState } from 'react';
|
||||||
import { Channel } from '../../data-types';
|
import { Channel, Message } from '../../data-types';
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
import ChannelList from '../lists/channel-list';
|
import ChannelList from '../lists/channel-list';
|
||||||
import MemberList from '../lists/member-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 [ activeChannel, setActiveChannel ] = useState<Channel | null>(null);
|
||||||
const [ activeChannelGuild, setActiveChannelGuild ] = useState<CombinedGuild | null>(null);
|
const [ activeChannelGuild, setActiveChannelGuild ] = useState<CombinedGuild | null>(null);
|
||||||
|
|
||||||
|
const [ fetchMessagesRetryCallable, setFetchMessagesRetryCallable ] = useState<(() => Promise<void>) | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActiveChannel(null);
|
setActiveChannel(null);
|
||||||
}, [ guild ]);
|
}, [ guild ]);
|
||||||
@ -57,6 +59,28 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
|
|||||||
}
|
}
|
||||||
}, [ channelsResult, activeChannel ]);
|
}, [ 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 (
|
return (
|
||||||
<div className="guild-react">
|
<div className="guild-react">
|
||||||
<div className="guild-sidebar">
|
<div className="guild-sidebar">
|
||||||
@ -73,7 +97,7 @@ const GuildElement: FC<GuildElementProps> = (props: GuildElementProps) => {
|
|||||||
<ChannelTitle channel={activeChannel} />
|
<ChannelTitle channel={activeChannel} />
|
||||||
<div className="guild-channel-content">
|
<div className="guild-channel-content">
|
||||||
<div className="guild-channel-feed-wrapper">
|
<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} />}
|
{activeChannel && activeChannelGuild && <SendMessage guild={guild} channel={activeChannel} />}
|
||||||
</div>
|
</div>
|
||||||
<div className="member-list-wrapper">
|
<div className="member-list-wrapper">
|
||||||
|
@ -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 React, { ClipboardEvent, FC, FormEvent, KeyboardEvent, RefObject, useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { Channel } from '../../data-types';
|
import { Channel } from '../../data-types';
|
||||||
import CombinedGuild from '../../guild-combined';
|
import CombinedGuild from '../../guild-combined';
|
||||||
@ -79,6 +74,7 @@ const SendMessage: FC<SendMessageProps> = (props: SendMessageProps) => {
|
|||||||
if (!isMounted.current) return;
|
if (!isMounted.current) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
setText('');
|
setText('');
|
||||||
if (contentEditableRef.current) contentEditableRef.current.innerText = '';
|
if (contentEditableRef.current) contentEditableRef.current.innerText = '';
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
|
@ -56,7 +56,7 @@ window.addEventListener('DOMContentLoaded', () => {
|
|||||||
const guildsManager = new GuildsManager(messageRAMCache, resourceRAMCache, personalDB);
|
const guildsManager = new GuildsManager(messageRAMCache, resourceRAMCache, personalDB);
|
||||||
await guildsManager.init();
|
await guildsManager.init();
|
||||||
|
|
||||||
LOG.silly('events bound');
|
LOG.silly('guildsManager initialized');
|
||||||
|
|
||||||
ReactDOM.render(<RootElement guildsManager={guildsManager} />, document.getElementById('react-root'));
|
ReactDOM.render(<RootElement guildsManager={guildsManager} />, document.getElementById('react-root'));
|
||||||
})();
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user