infinite scroll with recoil works!
This commit is contained in:
parent
00c99cefc8
commit
fb0981e92b
0
archive/react-helper-partial.ts
Normal file
0
archive/react-helper-partial.ts
Normal file
@ -48,17 +48,16 @@ interface PreviewImageElementProps {
|
||||
resourcePreviewId: string;
|
||||
resourceId: string;
|
||||
resourceName: string;
|
||||
resourceIdGuild: CombinedGuild; // TODO: Remove in favor of just one guild
|
||||
}
|
||||
|
||||
const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageElementProps) => {
|
||||
const { guild, previewWidth, previewHeight, resourcePreviewId, resourceId, resourceName, resourceIdGuild } = props;
|
||||
const { guild, previewWidth, previewHeight, resourcePreviewId, resourceId, resourceName } = props;
|
||||
|
||||
const setOverlay = useSetRecoilState<ReactNode>(overlayState);
|
||||
|
||||
// TODO: Handle resourceError
|
||||
const previewResource = useRecoilValue(guildResourceState({ guildId: resourceIdGuild.id, resourceId: resourcePreviewId }));
|
||||
const previewImgSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: resourceIdGuild.id, resourceId: resourcePreviewId }));
|
||||
const previewResource = useRecoilValue(guildResourceState({ guildId: guild.id, resourceId: resourcePreviewId }));
|
||||
const previewImgSrc = useRecoilValueSoftImgSrc(guildResourceSoftImgSrcState({ guildId: guild.id, resourceId: resourcePreviewId }));
|
||||
|
||||
const [ contextMenu, onContextMenu ] = useContextClickContextMenu((_alignment: IAlignment, relativeToPos: { x: number, y: number }, close: () => void) => {
|
||||
if (!isLoaded(previewResource)) return null;
|
||||
@ -71,7 +70,7 @@ const PreviewImageElement: FC<PreviewImageElementProps> = (props: PreviewImageEl
|
||||
}, [ previewResource, resourceName ]);
|
||||
|
||||
const openImageOverlay = useCallback(() => {
|
||||
setOverlay(<ImageOverlay guild={resourceIdGuild} resourceId={resourceId} resourceName={resourceName} />);
|
||||
setOverlay(<ImageOverlay guild={guild} resourceId={resourceId} resourceName={resourceName} />);
|
||||
}, [ guild, resourceId, resourceName ]);
|
||||
|
||||
return (
|
||||
@ -89,11 +88,10 @@ export interface MessageElementProps {
|
||||
guild: CombinedGuild;
|
||||
message: Message;
|
||||
prevMessage: Message | null;
|
||||
messageGuild: CombinedGuild;
|
||||
}
|
||||
|
||||
const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) => {
|
||||
const { guild, message, prevMessage, messageGuild } = props;
|
||||
const { guild, message, prevMessage } = props;
|
||||
|
||||
const className = useMemo(() => {
|
||||
return message.isContinued(prevMessage) ? 'message-react continued' : 'message-react';
|
||||
@ -147,7 +145,6 @@ const MessageElement: FC<MessageElementProps> = (props: MessageElementProps) =>
|
||||
previewWidth={message.previewWidth} previewHeight={message.previewHeight}
|
||||
resourcePreviewId={message.resourcePreviewId}
|
||||
resourceId={message.resourceId}
|
||||
resourceIdGuild={messageGuild}
|
||||
resourceName={message.resourceName ?? 'unknown.unk'}
|
||||
/>
|
||||
);
|
||||
|
@ -3,94 +3,56 @@ const electronConsole = electronRemote.getGlobal('console') as Console;
|
||||
import Logger from '../../../../logger/logger';
|
||||
const LOG = Logger.create(__filename, electronConsole);
|
||||
|
||||
import React, { Dispatch, FC, SetStateAction, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { Channel, Message } from '../../data-types';
|
||||
import CombinedGuild from '../../guild-combined';
|
||||
import React, { FC, useEffect, useMemo } from 'react';
|
||||
import { Message } from '../../data-types';
|
||||
import MessageElement from './components/message-element';
|
||||
import InfiniteScroll from '../components/infinite-scroll';
|
||||
import { useMessagesScrollingSubscription } from '../require/guild-subscriptions';
|
||||
import { currGuildActiveChannelMessagesState } from '../require/atoms';
|
||||
import { currGuildActiveChannelMessagesState, currGuildState } from '../require/atoms';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { isFailed, isLoaded, isPended, isUnload } from '../require/loadables';
|
||||
import InfiniteScrollRecoil from '../components/infinite-scroll-recoil';
|
||||
|
||||
interface MessageListProps {
|
||||
guild: CombinedGuild;
|
||||
channel: Channel;
|
||||
channelGuild: CombinedGuild;
|
||||
setFetchRetryCallable: Dispatch<SetStateAction<(() => Promise<void>) | null>>;
|
||||
}
|
||||
const MessageList: FC = () => {
|
||||
// const scrollToBottomFunc = useCallback(() => {
|
||||
// if (!infiniteScrollElementRef.current) return;
|
||||
// infiniteScrollElementRef.current.scrollTop = 0; // Keep in mind that this is reversed since we are in flex-flow: column-reverse
|
||||
// }, []);
|
||||
|
||||
const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
||||
const { guild, channel, channelGuild, setFetchRetryCallable } = props;
|
||||
|
||||
const infiniteScrollElementRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const scrollToBottomFunc = useCallback(() => {
|
||||
if (!infiniteScrollElementRef.current) return;
|
||||
infiniteScrollElementRef.current.scrollTop = 0; // Keep in mind that this is reversed since we are in flex-flow: column-reverse
|
||||
}, []);
|
||||
|
||||
const recoilMessages = useRecoilValue(currGuildActiveChannelMessagesState);
|
||||
const guild = useRecoilValue(currGuildState);
|
||||
const messages = useRecoilValue(currGuildActiveChannelMessagesState);
|
||||
useEffect(() => {
|
||||
if (isUnload(recoilMessages)) {
|
||||
if (isUnload(messages)) {
|
||||
LOG.debug('recoilMessages unloaded');
|
||||
} else if (isPended(recoilMessages)) {
|
||||
} else if (isPended(messages)) {
|
||||
LOG.debug('recoilMessages pended');
|
||||
} else if (isLoaded(recoilMessages)) {
|
||||
LOG.debug('recoilMessages loaded. length: ' + recoilMessages.value.length);
|
||||
} else if (isFailed(recoilMessages)) {
|
||||
} else if (isLoaded(messages)) {
|
||||
LOG.debug('recoilMessages loaded. length: ' + messages.value.length);
|
||||
} else if (isFailed(messages)) {
|
||||
LOG.debug('recoilMessages failed');
|
||||
}
|
||||
// LOG.debug('recoilMessages update', { recoilMessagesLength: recoilMessages.value?.length ?? 'unloaded' });
|
||||
}, [ recoilMessages ]);
|
||||
|
||||
// TODO: Store state higher up
|
||||
const [
|
||||
fetchRetryCallable,
|
||||
fetchAboveCallable,
|
||||
fetchBelowCallable,
|
||||
setScrollRatio,
|
||||
messagesResult,
|
||||
messagesFetchError,
|
||||
messagesFetchAboveError,
|
||||
messagesFetchBelowError
|
||||
] = 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 ]);
|
||||
}, [ messages ]);
|
||||
|
||||
const messageElements = useMemo(() => {
|
||||
if (guild === null) return [];
|
||||
if (!isLoaded(messages)) return [];
|
||||
const result = [];
|
||||
if (messagesResult) {
|
||||
for (let i = 0; i < messagesResult.value.elements.length; ++i) {
|
||||
const prevMessage = messagesResult.value.elements[i - 1] ?? null;
|
||||
const message = messagesResult.value.elements[i] as Message;
|
||||
result.push(<MessageElement key={guild.id + message.id} guild={guild} message={message} prevMessage={prevMessage} messageGuild={messagesResult.guild} />);
|
||||
}
|
||||
for (let i = 0; i < messages.value.length; ++i) {
|
||||
const prevMessage = messages.value[i - 1] ?? null;
|
||||
const message = messages.value[i] as Message;
|
||||
result.push(<MessageElement key={guild.id + 'm#' + message.id} guild={guild} message={message} prevMessage={prevMessage} />);
|
||||
}
|
||||
return result;
|
||||
}, [ messagesResult ]);
|
||||
}, [ messages ]);
|
||||
|
||||
return (
|
||||
<div className="message-list">
|
||||
<InfiniteScroll
|
||||
infiniteScrollElementRef={infiniteScrollElementRef}
|
||||
fetchRetryCallable={fetchRetryCallable}
|
||||
fetchAboveCallable={fetchAboveCallable}
|
||||
fetchBelowCallable={fetchBelowCallable}
|
||||
setScrollRatio={setScrollRatio}
|
||||
ends={messagesResult?.value.ends ?? null}
|
||||
fetchError={messagesFetchError}
|
||||
fetchAboveError={messagesFetchAboveError}
|
||||
fetchBelowError={messagesFetchBelowError}
|
||||
fetchErrorMessage="Unable to retrieve recent messages"
|
||||
fetchAboveErrorMessage="Unable to retrieve messages above"
|
||||
fetchBelowErrorMessage="Unable to retrieve messages below"
|
||||
>{messageElements}</InfiniteScroll>
|
||||
<InfiniteScrollRecoil
|
||||
scrollable={messages}
|
||||
initialErrorMessage="Unable to retrieve recent messages"
|
||||
aboveErrorMessage="Unable to retrieve messages above"
|
||||
belowErrorMessage="Unable to retrieve messages below"
|
||||
>{messageElements}</InfiniteScrollRecoil>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ const LOG = Logger.create(__filename, electronConsole);
|
||||
import { AtomEffect, Loadable, RecoilValue, useRecoilValueLoadable } from "recoil";
|
||||
import CombinedGuild from "../../guild-combined";
|
||||
import { Conflictable, Connectable } from "../../guild-types";
|
||||
import { createFailedScrollingEnd, createFailedValue, createFailedValueScrolling, createLoadedScrollingEnd, createLoadedValue, createLoadedValueScrolling, createLoadingScrollingEnd, Defined, DEF_PENDED_SCROLLING_VALUE, DEF_PENDED_VALUE, DEF_UNLOADED_SCROLL_END, isEndPended, isLoaded, isPended, LoadableScrollingEnd, LoadableValue, LoadableValueScrolling, LoadedScrollingEnd, LoadedValueScrolling } from "./loadables";
|
||||
import { createCancelledScrollingEnd, createFailedScrollingEnd, createFailedValue, createFailedValueScrolling, createLoadedScrollingEnd, createLoadedValue, createLoadedValueScrolling, createLoadingScrollingEnd, Defined, DEF_PENDED_SCROLLING_VALUE, DEF_PENDED_VALUE, isEndPended, isLoaded, isPended, LoadableScrollingEnd, LoadableValue, LoadableValueScrolling, LoadedScrollingEnd, LoadedValueScrolling } from "./loadables";
|
||||
import { guildState } from './atoms';
|
||||
import { Changes } from '../../data-types';
|
||||
|
||||
@ -109,15 +109,16 @@ export function createFetchValueScrollingReferenceFunc<T>(
|
||||
const selfState = await getPromise(node);
|
||||
if (!isLoaded(selfState)) return; // Don't send a request if the base LoadableValueScrolling isn't loaded yet
|
||||
|
||||
const selfEnd = getFunc(selfState);
|
||||
let selfEnd = getFunc(selfState);
|
||||
if (isEndPended(selfEnd)) return; // Don't send a request if we're already loading
|
||||
|
||||
canceled = false;
|
||||
setSelf(applyEndToSelf(selfState, createLoadingScrollingEnd(fetchValueReferenceFunc, cancel)));
|
||||
selfEnd = createLoadingScrollingEnd(fetchValueReferenceFunc, cancel);
|
||||
setSelf(applyEndToSelf(selfState, selfEnd));
|
||||
try {
|
||||
const result = await fetchReferenceFunc(guild, reference, count);
|
||||
if (canceled) {
|
||||
setSelf(applyEndToSelf(selfState, DEF_UNLOADED_SCROLL_END));
|
||||
setSelf(applyEndToSelf(selfState, createCancelledScrollingEnd(selfEnd)));
|
||||
} else {
|
||||
const hasMore = result.length >= count;
|
||||
setSelf(applyResultToSelf(selfState, createLoadedScrollingEnd(hasMore, fetchValueReferenceFunc, cancel), result));
|
||||
@ -125,7 +126,7 @@ export function createFetchValueScrollingReferenceFunc<T>(
|
||||
} catch (e: unknown) {
|
||||
if (canceled) {
|
||||
LOG.error('unable to fetch value based on reference (but we were canceled)', e);
|
||||
setSelf(applyEndToSelf(selfState, DEF_UNLOADED_SCROLL_END));
|
||||
setSelf(applyEndToSelf(selfState, createCancelledScrollingEnd(selfEnd)));
|
||||
} else {
|
||||
LOG.error('unable to fetch value based on reference', e);
|
||||
setSelf(applyEndToSelf(selfState, createFailedScrollingEnd(e, fetchValueReferenceFunc, cancel)));
|
||||
|
@ -333,12 +333,12 @@ export function useScrollableCallables<T>(scrollable: LoadableValueScrolling<T[]
|
||||
|
||||
const fetchAboveCallable = useCallback(async () => {
|
||||
if (loadingAbove) return;
|
||||
setLoadingAbove(true);
|
||||
if (!isLoaded(scrollable)) return;
|
||||
if (scrollable.above.hasMore !== true) return; // Don't load unless we know there could be more
|
||||
if (scrollable.value.length === 0) return; // There's no references available. In this case, hasMore should already have been false/undefined anyway
|
||||
const topReference = scrollable.value[0] as T;
|
||||
try {
|
||||
setLoadingAbove(true);
|
||||
await scrollable.above.fetch(topReference);
|
||||
} finally {
|
||||
if (isMounted.current) {
|
||||
@ -348,12 +348,12 @@ export function useScrollableCallables<T>(scrollable: LoadableValueScrolling<T[]
|
||||
}, [ scrollable ]);
|
||||
const fetchBelowCallable = useCallback(async () => {
|
||||
if (loadingBelow) return;
|
||||
setLoadingBelow(true);
|
||||
if (!isLoaded(scrollable)) return;
|
||||
if (scrollable.below.hasMore !== true) return; // Don't load unless we know there could be more
|
||||
if (scrollable.value.length === 0) return; // There's no references available. In this case, hasMore should already have been false/undefined anyway
|
||||
const bottomReference = scrollable.value[scrollable.value.length - 1] as T;
|
||||
try {
|
||||
setLoadingBelow(true);
|
||||
await scrollable.below.fetch(bottomReference);
|
||||
} finally {
|
||||
if (isMounted.current) {
|
||||
@ -378,7 +378,7 @@ export function useScrollableCallables<T>(scrollable: LoadableValueScrolling<T[]
|
||||
const distToTop = -(clientHeight - scrollHeight - scrollTop); // keep in mind scrollTop is negative >:]
|
||||
const distToBottom = -scrollTop;
|
||||
|
||||
//LOG.debug(`scroll callable update. to top: ${distToTop}, to bottom: ${distToBottom}`)
|
||||
LOG.debug(`scroll callable update. to top: ${distToTop}, to bottom: ${distToBottom}`)
|
||||
|
||||
if (distToTop < threshold) {
|
||||
await fetchAboveCallable();
|
||||
|
@ -74,7 +74,7 @@ const GuildElement: FC = () => {
|
||||
<ChannelTitle channel={isLoaded(activeChannel) ? activeChannel.value : null} />
|
||||
<div className="guild-channel-content">
|
||||
<div className="guild-channel-feed-wrapper">
|
||||
{guild && isLoaded(activeChannel) && <MessageList guild={guild} channel={activeChannel.value} channelGuild={guild} setFetchRetryCallable={setFetchMessagesRetryCallable} />}
|
||||
{guild && isLoaded(activeChannel) && <MessageList />}
|
||||
{guild && isLoaded(activeChannel) && <SendMessage guild={guild} channel={activeChannel.value} />}
|
||||
</div>
|
||||
<div className="member-list-wrapper">
|
||||
|
Loading…
Reference in New Issue
Block a user