merge ends and elements in scrolling subscription
This commit is contained in:
parent
7e703f84fd
commit
ce9b17535b
@ -20,7 +20,6 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
||||
fetchAboveCallable,
|
||||
fetchBelowCallable,
|
||||
setScrollRatio,
|
||||
ends,
|
||||
messagesResult,
|
||||
messagesFetchError,
|
||||
messagesFetchAboveError,
|
||||
@ -29,10 +28,10 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
||||
|
||||
const messageElements = useMemo(() => {
|
||||
const result = [];
|
||||
if (messagesResult && messagesResult.value) {
|
||||
for (let i = 0; i < messagesResult.value.length; ++i) {
|
||||
const prevMessage = messagesResult.value[i - 1] ?? null;
|
||||
const message = messagesResult.value[i] as Message;
|
||||
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} setOverlay={setOverlay} />);
|
||||
}
|
||||
}
|
||||
@ -46,7 +45,7 @@ const MessageList: FC<MessageListProps> = (props: MessageListProps) => {
|
||||
fetchAboveCallable={fetchAboveCallable}
|
||||
fetchBelowCallable={fetchBelowCallable}
|
||||
setScrollRatio={setScrollRatio}
|
||||
ends={ends}
|
||||
ends={messagesResult?.value.ends ?? null}
|
||||
fetchError={messagesFetchError}
|
||||
fetchAboveError={messagesFetchAboveError}
|
||||
fetchBelowError={messagesFetchBelowError}
|
||||
|
@ -59,6 +59,14 @@ export interface SubscriptionResult<T> {
|
||||
guild: CombinedGuild;
|
||||
}
|
||||
|
||||
export interface ScrollingSubscriptionResult<T> {
|
||||
value: {
|
||||
ends: { hasMoreAbove: boolean, hasMoreBelow: boolean };
|
||||
elements: T[];
|
||||
},
|
||||
guild: CombinedGuild;
|
||||
}
|
||||
|
||||
export function isNonNullAndHasValue<T>(subscriptionResult: SubscriptionResult<T | null> | null): subscriptionResult is SubscriptionResult<T> {
|
||||
return !!(subscriptionResult !== null && subscriptionResult.value !== null);
|
||||
}
|
||||
@ -347,8 +355,7 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
fetchAboveCallable: () => Promise<void>,
|
||||
fetchBelowCallable: () => Promise<void>,
|
||||
setScrollRatio: Dispatch<SetStateAction<number>>,
|
||||
ends: { hasMoreAbove: boolean, hasMoreBelow: boolean } | null,
|
||||
lastResult: SubscriptionResult<T[]> | null,
|
||||
lastResult: ScrollingSubscriptionResult<T> | null,
|
||||
fetchError: unknown | null,
|
||||
fetchAboveError: unknown | null,
|
||||
fetchBelowError: unknown | null,
|
||||
@ -364,14 +371,12 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
const isMounted = useIsMountedRef();
|
||||
|
||||
// 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<{ value: T[], guild: CombinedGuild } | null>(null);
|
||||
const [ lastResult, setLastResult ] = useState<ScrollingSubscriptionResult<T> | null>(null);
|
||||
|
||||
const [ fetchError, setFetchError ] = useState<unknown | null>(null);
|
||||
const [ fetchAboveError, setFetchAboveError ] = useState<unknown | null>(null);
|
||||
const [ fetchBelowError, setFetchBelowError ] = useState<unknown | null>(null);
|
||||
|
||||
const [ ends, setEnds ] = useState<{ hasMoreAbove: boolean, hasMoreBelow: boolean } | null>(null);
|
||||
|
||||
// Percentage of scroll from top. i.e. 300px from top of 1000px scroll = 0.3 scroll ratio
|
||||
const [ scrollRatio, setScrollRatio ] = useState<number>(0.5);
|
||||
|
||||
@ -389,18 +394,18 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
}
|
||||
}, [ scrollRatio ]);
|
||||
|
||||
function removeByCounts<T>(elements: T[], counts: { fromTop: number, fromBottom: number }): T[] {
|
||||
function removeByCounts<T>(elements: T[], counts: { fromTop: number, fromBottom: number }): T[] {
|
||||
const { fromTop, fromBottom } = counts;
|
||||
return elements.slice(fromTop, elements.length - fromBottom);
|
||||
}
|
||||
|
||||
const fetchAboveCallable = useCallback(async (): Promise<void> => {
|
||||
if (!isMounted.current) return;
|
||||
if (!lastResult || !lastResult.value || lastResult.value.length === 0) return;
|
||||
if (!lastResult || lastResult.value.elements.length === 0) return;
|
||||
if (guild !== lastResult.guild) return;
|
||||
|
||||
try {
|
||||
const reference = lastResult.value[0] as T;
|
||||
const reference = lastResult.value.elements[0] as T;
|
||||
const aboveElements = await fetchAboveFunc(reference);
|
||||
const lastResultAfterAwait = getStateAfterAwait(setLastResult);
|
||||
if (!isMounted.current) return;
|
||||
@ -411,16 +416,30 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
let removedFromBottom = false;
|
||||
setLastResult((lastResult) => {
|
||||
if (!lastResult) return null;
|
||||
let newValue = aboveElements.concat(lastResult.value ?? []).sort(sortFunc);
|
||||
if (newValue.length > maxElements) {
|
||||
newValue = newValue.slice(0, maxElements);
|
||||
let newElements = aboveElements.concat(lastResult.value.elements ?? []).sort(sortFunc);
|
||||
if (newElements.length > maxElements) {
|
||||
newElements = newElements.slice(0, maxElements);
|
||||
removedFromBottom = true;
|
||||
}
|
||||
return { value: newValue, guild: lastResult.guild };
|
||||
return {
|
||||
value: {
|
||||
elements: newElements,
|
||||
ends: { hasMoreBelow: removedFromBottom || lastResult.value.ends.hasMoreBelow, hasMoreAbove }
|
||||
},
|
||||
guild: lastResult.guild
|
||||
};
|
||||
});
|
||||
setEnds(prev => (prev && { hasMoreBelow: removedFromBottom || prev.hasMoreBelow, hasMoreAbove: prev.hasMoreAbove }));
|
||||
} else {
|
||||
setEnds(prev => (prev && { hasMoreBelow: prev.hasMoreBelow, hasMoreAbove: false }));
|
||||
setLastResult((lastResult) => {
|
||||
if (!lastResult) return null;
|
||||
return {
|
||||
value: {
|
||||
elements: lastResult.value.elements,
|
||||
ends: { hasMoreBelow: lastResult.value.ends.hasMoreBelow, hasMoreAbove: false }
|
||||
},
|
||||
guild: lastResult.guild
|
||||
};
|
||||
})
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
LOG.error('error fetching above for subscription', e);
|
||||
@ -428,16 +447,25 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
if (!isMounted.current) return;
|
||||
if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return;
|
||||
setFetchAboveError(e);
|
||||
setEnds(prev => (prev && { hasMoreBelow: prev.hasMoreBelow, hasMoreAbove: true }))
|
||||
setLastResult((lastResult) => {
|
||||
if (!lastResult) return null;
|
||||
return {
|
||||
value: {
|
||||
elements: lastResult.value.elements,
|
||||
ends: { hasMoreBelow: lastResult.value.ends.hasMoreBelow, hasMoreAbove: true },
|
||||
},
|
||||
guild: lastResult.guild
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [ guild, lastResult, fetchAboveFunc, maxFetchElements ]);
|
||||
|
||||
const fetchBelowCallable = useCallback(async (): Promise<void> => {
|
||||
if (!isMounted.current) return;
|
||||
if (!lastResult || !lastResult.value || lastResult.value.length === 0) return;
|
||||
if (!lastResult || !lastResult.value || lastResult.value.elements.length === 0) return;
|
||||
if (guild !== lastResult.guild) return;
|
||||
try {
|
||||
const reference = lastResult.value[lastResult.value.length - 1] as T;
|
||||
const reference = lastResult.value.elements[lastResult.value.elements.length - 1] as T;
|
||||
const belowElements = await fetchBelowFunc(reference);
|
||||
const lastResultAfterAwait = getStateAfterAwait(setLastResult);
|
||||
if (!isMounted.current) return;
|
||||
@ -448,16 +476,30 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
let removedFromTop = false;
|
||||
setLastResult((lastResult) => {
|
||||
if (!lastResult) return null;
|
||||
let newValue = (lastResult.value ?? []).concat(belowElements).sort(sortFunc);
|
||||
if (newValue.length > maxElements) {
|
||||
newValue = newValue.slice(Math.max(newValue.length - maxElements, 0));
|
||||
let newElements = (lastResult.value.elements ?? []).concat(belowElements).sort(sortFunc);
|
||||
if (newElements.length > maxElements) {
|
||||
newElements = newElements.slice(Math.max(newElements.length - maxElements, 0));
|
||||
removedFromTop = true;
|
||||
}
|
||||
return { value: newValue, guild: lastResult.guild };
|
||||
return {
|
||||
value: {
|
||||
elements: newElements,
|
||||
ends: { hasMoreBelow, hasMoreAbove: removedFromTop || lastResult.value.ends.hasMoreAbove }
|
||||
},
|
||||
guild: lastResult.guild
|
||||
};
|
||||
});
|
||||
setEnds(prev => (prev && { hasMoreBelow, hasMoreAbove: removedFromTop || prev.hasMoreAbove })); // :)
|
||||
} else {
|
||||
setEnds(prev => (prev && { hasMoreBelow: false, hasMoreAbove: prev.hasMoreAbove }))
|
||||
setLastResult((lastResult) => {
|
||||
if (!lastResult) return null;
|
||||
return {
|
||||
value: {
|
||||
elements: lastResult.value.elements,
|
||||
ends: { hasMoreBelow: false, hasMoreAbove: lastResult.value.ends.hasMoreAbove }
|
||||
},
|
||||
guild: lastResult.guild
|
||||
};
|
||||
});
|
||||
}
|
||||
} catch (e: unknown) {
|
||||
LOG.error('error fetching below for subscription', e);
|
||||
@ -465,7 +507,16 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
if (!isMounted.current) return;
|
||||
if (!lastResultAfterAwait || guild !== lastResultAfterAwait.guild) return;
|
||||
setFetchBelowError(e);
|
||||
setEnds(prev => (prev && { hasMoreBelow: true, hasMoreAbove: prev.hasMoreAbove }));
|
||||
setLastResult((lastResult) => {
|
||||
if (!lastResult) return null;
|
||||
return {
|
||||
value: {
|
||||
elements: lastResult.value.elements,
|
||||
ends: { hasMoreBelow: true, hasMoreAbove: lastResult.value.ends.hasMoreAbove }
|
||||
},
|
||||
guild: lastResult.guild
|
||||
};
|
||||
});
|
||||
}
|
||||
}, [ lastResult, fetchBelowFunc, maxFetchElements ]);
|
||||
|
||||
@ -476,8 +527,13 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
fetchValue = fetchValue.slice(Math.max(fetchValue.length - maxElements)).sort(sortFunc);
|
||||
}
|
||||
//LOG.debug('Got items: ', { fetchValueLength: fetchValue?.length ?? '<empty>' })
|
||||
setEnds({ hasMoreAbove, hasMoreBelow: false });
|
||||
setLastResult({ value: fetchValue, guild: fetchValueGuild });
|
||||
setLastResult({
|
||||
value: {
|
||||
elements: fetchValue,
|
||||
ends: { hasMoreAbove, hasMoreBelow: false }
|
||||
},
|
||||
guild: fetchValueGuild
|
||||
});
|
||||
setFetchError(null);
|
||||
}, [ sortFunc, maxFetchElements, maxElements ]);
|
||||
|
||||
@ -494,13 +550,25 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
const newElements = newEventArgsMap(...args);
|
||||
setLastResult((lastResult) => {
|
||||
if (lastResult === null) return null;
|
||||
if (ends?.hasMoreBelow) return lastResult; // Don't add to bottom if we are not at the bottom
|
||||
let newValue = (lastResult.value ?? []).concat(newElements).sort(sortFunc);
|
||||
if (newValue.length > maxElements) {
|
||||
if (lastResult.value.ends.hasMoreBelow) return lastResult; // Don't add to bottom if we are not at the bottom
|
||||
let newResultElements = (lastResult.value.elements ?? []).concat(newElements).sort(sortFunc);
|
||||
let newEnds = lastResult.value.ends;
|
||||
if (newResultElements.length > maxElements) {
|
||||
// Remove in a way that tries to keep the scrollbar position consistent
|
||||
newValue = removeByCounts(newValue, getRemoveCounts(newValue.length - maxElements));
|
||||
const removeCounts = getRemoveCounts(newResultElements.length - maxElements);
|
||||
newResultElements = removeByCounts(newResultElements, removeCounts);
|
||||
newEnds = {
|
||||
hasMoreBelow: removeCounts.fromBottom > 0 || lastResult.value.ends.hasMoreBelow,
|
||||
hasMoreAbove: removeCounts.fromTop > 0 || lastResult.value.ends.hasMoreAbove
|
||||
};
|
||||
}
|
||||
return { value: newValue, guild: guild };
|
||||
return {
|
||||
value: {
|
||||
elements: newResultElements,
|
||||
ends: newEnds
|
||||
},
|
||||
guild: guild
|
||||
};
|
||||
});
|
||||
}, [ guild, newEventArgsMap ]) as (Connectable & Conflictable)[NE];
|
||||
const boundUpdateFunc = useCallback((...args: Arguments<Connectable[UE]>): void => {
|
||||
@ -509,7 +577,15 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
const updatedElements = updatedEventArgsMap(...args);
|
||||
setLastResult((lastResult) => {
|
||||
if (lastResult === null) return null;
|
||||
return { value: (lastResult.value ?? []).map(element => updatedElements.find(updatedElement => updatedElement.id === element.id) ?? element).sort(sortFunc), guild: guild };
|
||||
return {
|
||||
value: {
|
||||
elements: (lastResult.value.elements ?? [])
|
||||
.map(element => updatedElements.find(updatedElement => updatedElement.id === element.id) ?? element)
|
||||
.sort(sortFunc),
|
||||
ends: lastResult.value.ends
|
||||
},
|
||||
guild: guild
|
||||
};
|
||||
});
|
||||
}, [ guild, updatedEventArgsMap ]) as (Connectable & Conflictable)[UE];
|
||||
const boundRemovedFunc = useCallback((...args: Arguments<Connectable[RE]>): void => {
|
||||
@ -519,7 +595,14 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
setLastResult((lastResult) => {
|
||||
if (lastResult === null) return null;
|
||||
const deletedIds = new Set(removedElements.map(removedElement => removedElement.id));
|
||||
return { value: (lastResult.value ?? []).filter(element => !deletedIds.has(element.id)), guild: guild };
|
||||
return {
|
||||
value: {
|
||||
elements: (lastResult.value.elements ?? [])
|
||||
.filter(element => !deletedIds.has(element.id)),
|
||||
ends: lastResult.value.ends
|
||||
},
|
||||
guild: guild
|
||||
};
|
||||
});
|
||||
}, [ guild, removedEventArgsMap ]) as (Connectable & Conflictable)[RE];
|
||||
const boundConflictFunc = useCallback((...args: Arguments<Conflictable[CE]>): void => {
|
||||
@ -529,15 +612,27 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
setLastResult((lastResult) => {
|
||||
if (lastResult === null) return null;
|
||||
const deletedIds = new Set(changes.deleted.map(deletedElement => deletedElement.id));
|
||||
let newValue = (lastResult.value ?? [])
|
||||
let newResultElements = (lastResult.value.elements ?? [])
|
||||
.concat(changes.added)
|
||||
.map(element => changes.updated.find(change => change.newDataPoint.id === element.id)?.newDataPoint ?? element)
|
||||
.filter(element => !deletedIds.has(element.id))
|
||||
.sort(sortFunc);
|
||||
if (newValue.length > maxElements) {
|
||||
newValue = removeByCounts(newValue, getRemoveCounts(newValue.length - maxElements));
|
||||
let newEnds = lastResult.value.ends;
|
||||
if (newResultElements.length > maxElements) {
|
||||
const removeCounts = getRemoveCounts(newResultElements.length - maxElements);
|
||||
newResultElements = removeByCounts(newResultElements, removeCounts);
|
||||
newEnds = {
|
||||
hasMoreBelow: removeCounts.fromBottom > 0 || lastResult.value.ends.hasMoreBelow,
|
||||
hasMoreAbove: removeCounts.fromTop > 0 || lastResult.value.ends.hasMoreAbove
|
||||
};
|
||||
}
|
||||
return { value: newValue, guild: guild };
|
||||
return {
|
||||
value: {
|
||||
elements: newResultElements,
|
||||
ends: newEnds
|
||||
},
|
||||
guild: guild
|
||||
};
|
||||
});
|
||||
}, [ guild, conflictEventArgsMap ]) as (Connectable & Conflictable)[CE];
|
||||
|
||||
@ -567,7 +662,6 @@ function useMultipleGuildSubscriptionScrolling<
|
||||
fetchAboveCallable,
|
||||
fetchBelowCallable,
|
||||
setScrollRatio,
|
||||
ends,
|
||||
lastResult,
|
||||
fetchError,
|
||||
fetchAboveError,
|
||||
|
Loading…
Reference in New Issue
Block a user