From 0b228c698a5cd5d9c55763c78afd09a766f74fba Mon Sep 17 00:00:00 2001 From: Jordan Fearnley Date: Fri, 31 May 2024 08:29:12 +0100 Subject: [PATCH] Feat: Virtualize series list in tag modal (#923) --- .../Collection/Tags/TagDetailsModal.tsx | 104 ++++++++++++------ 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/src/components/Collection/Tags/TagDetailsModal.tsx b/src/components/Collection/Tags/TagDetailsModal.tsx index 7b78fec74..92a53b441 100644 --- a/src/components/Collection/Tags/TagDetailsModal.tsx +++ b/src/components/Collection/Tags/TagDetailsModal.tsx @@ -1,20 +1,78 @@ -import React from 'react'; +import React, { useMemo, useRef } from 'react'; import { Link } from 'react-router-dom'; -import { mdiOpenInNew } from '@mdi/js'; +import { mdiLoading, mdiOpenInNew } from '@mdi/js'; import Icon from '@mdi/react'; +import { useVirtualizer } from '@tanstack/react-virtual'; import cx from 'classnames'; +import { debounce } from 'lodash'; import AnidbDescription from '@/components/Collection/AnidbDescription'; import ModalPanel from '@/components/Panels/ModalPanel'; import { useFilteredSeriesInfiniteQuery } from '@/core/react-query/filter/queries'; import useFlattenListResult from '@/hooks/useFlattenListResult'; +import type { SeriesType } from '@/core/types/api/series'; import type { TagType } from '@/core/types/api/tags'; -const TagDetailsModal = React.memo(({ onClose, show, tag }: { show: boolean, tag?: TagType, onClose: () => void }) => { +const SeriesLink = React.memo(({ extraPadding, series }: { series: SeriesType, extraPadding: boolean }) => ( + + + {series.Name} + + + +)); + +const SeriesVirtualizer = ( + { data, dataSize, fetchNext }: { data: SeriesType[], dataSize: number, fetchNext: () => void }, +) => { + const scrollRef = useRef(null); + const virtualizer = useVirtualizer({ + count: dataSize, + getScrollElement: () => scrollRef.current, + estimateSize: () => (25.6), // Standard line height + gap: 8, + overscan: 12, + }); + + return ( +
+
+ {virtualizer.getVirtualItems().map(({ index, key, size, start }) => { + const series = data[index]; + if (!series) fetchNext(); + return ( +
+ {series + ? 6} /> + : } +
+ ); + })} +
+
+ ); +}; + +const TagDetailsModal = ({ onClose, show, tag }: { show: boolean, tag?: TagType, onClose: () => void }) => { const { data: seriesDataList, fetchNextPage, isFetchingNextPage, isSuccess } = useFilteredSeriesInfiniteQuery( { - pageSize: 50, + pageSize: 25, filterCriteria: { ApplyAtSeriesLevel: true, Expression: { @@ -27,11 +85,12 @@ const TagDetailsModal = React.memo(({ onClose, show, tag }: { show: boolean, tag ); const [seriesData, seriesCount] = useFlattenListResult(seriesDataList); - if (!isFetchingNextPage && seriesData.length !== seriesCount) { - fetchNextPage().catch(() => {}); - } - - // TODO: Virtualize rows + const fetchNextDebounced = useMemo(() => + debounce(() => { + if (!isFetchingNextPage && seriesData.length !== seriesCount) { + fetchNextPage().catch(() => {}); + } + }, 50), [fetchNextPage, isFetchingNextPage, seriesCount, seriesData.length]); const header = (
@@ -71,34 +130,13 @@ const TagDetailsModal = React.memo(({ onClose, show, tag }: { show: boolean, tag  Series
-
-
- {seriesData?.map(series => ( - 6 && ('pr-4'), - )} - > - - {series.Name} - - - - ))} -
+
+
)} ); -}); +}; export default TagDetailsModal;