Skip to content

Commit

Permalink
Feat: Virtualize series list in tag modal (#923)
Browse files Browse the repository at this point in the history
  • Loading branch information
fearnlj01 authored May 31, 2024
1 parent c8abbef commit 0b228c6
Showing 1 changed file with 71 additions and 33 deletions.
104 changes: 71 additions & 33 deletions src/components/Collection/Tags/TagDetailsModal.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<Link
to={`/webui/collection/series/${series.IDs.ID}`}
className={cx(
'flex justify-between align-middle hover:text-panel-text-primary',
extraPadding && ('pr-4'),
)}
>
<span
className="line-clamp-1"
data-tooltip-id="tooltip"
data-tooltip-content={series.Name}
data-tooltip-delay-show={500}
>
{series.Name}
</span>
<Icon path={mdiOpenInNew} size={1} className="shrink-0" />
</Link>
));

const SeriesVirtualizer = (
{ data, dataSize, fetchNext }: { data: SeriesType[], dataSize: number, fetchNext: () => void },
) => {
const scrollRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: dataSize,
getScrollElement: () => scrollRef.current,
estimateSize: () => (25.6), // Standard line height
gap: 8,
overscan: 12,
});

return (
<div className="shoko-scrollbar max-h-[12.5rem] overflow-y-auto" ref={scrollRef}>
<div className="relative" style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map(({ index, key, size, start }) => {
const series = data[index];
if (!series) fetchNext();
return (
<div
key={key}
className="absolute left-0 top-0 w-full"
style={{ height: size, transform: `translateY(${start}px)` }}
>
{series
? <SeriesLink series={series} extraPadding={data.length > 6} />
: <Icon path={mdiLoading} spin className="mx-auto" size={`${size}px`} />}
</div>
);
})}
</div>
</div>
);
};

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: {
Expand All @@ -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 = (
<div className="flex w-full justify-between capitalize">
Expand Down Expand Up @@ -71,34 +130,13 @@ const TagDetailsModal = React.memo(({ onClose, show, tag }: { show: boolean, tag
</span>
&nbsp;Series
</div>
<div className="w-full rounded-lg bg-panel-input p-6">
<div className="shoko-scrollbar flex max-h-[12.5rem] flex-col gap-y-2 overflow-y-auto bg-panel-input">
{seriesData?.map(series => (
<Link
to={`/webui/collection/series/${series.IDs.ID}`}
key={series.IDs.ID}
className={cx(
'flex justify-between align-middle hover:text-panel-text-primary',
seriesData.length > 6 && ('pr-4'),
)}
>
<span
className="line-clamp-1"
data-tooltip-id="tooltip"
data-tooltip-content={series.Name}
data-tooltip-delay-show={500}
>
{series.Name}
</span>
<Icon path={mdiOpenInNew} size={1} className="shrink-0" />
</Link>
))}
</div>
<div className="grow rounded-lg bg-panel-input p-6">
<SeriesVirtualizer data={seriesData} fetchNext={fetchNextDebounced} dataSize={seriesCount} />
</div>
</div>
)}
</ModalPanel>
);
});
};

export default TagDetailsModal;

0 comments on commit 0b228c6

Please sign in to comment.