feishin/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx

183 lines
5.1 KiB
TypeScript
Raw Normal View History

2023-04-03 03:42:51 -07:00
import {
useState,
useRef,
useMemo,
useCallback,
forwardRef,
Ref,
useImperativeHandle,
} from 'react';
2022-12-19 15:59:14 -08:00
import debounce from 'lodash/debounce';
import type { FixedSizeListProps } from 'react-window';
import InfiniteLoader from 'react-window-infinite-loader';
import { VirtualGridWrapper } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper';
2023-01-05 21:59:07 -08:00
import type { CardRoute, CardRow, PlayQueueAddOptions } from '/@/renderer/types';
2022-12-26 04:47:40 -08:00
import { ListDisplayType } from '/@/renderer/types';
2023-01-05 21:59:07 -08:00
import { LibraryItem } from '/@/renderer/api/types';
2022-12-19 15:59:14 -08:00
2022-12-24 20:19:56 -08:00
export type VirtualInfiniteGridRef = {
resetLoadMoreItemsCache: () => void;
scrollTo: (index: number) => void;
setItemData: (data: any[]) => void;
};
2023-05-10 20:00:39 -07:00
interface VirtualGridProps
extends Omit<FixedSizeListProps, 'children' | 'itemSize' | 'height' | 'width'> {
cardRows: CardRow<any>[];
2022-12-26 04:47:40 -08:00
display?: ListDisplayType;
2022-12-19 15:59:14 -08:00
fetchFn: (options: { columnCount: number; skip: number; take: number }) => Promise<any>;
2023-01-08 00:52:53 -08:00
handleFavorite?: (options: { id: string[]; isFavorite: boolean; itemType: LibraryItem }) => void;
2022-12-20 04:11:06 -08:00
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
2023-05-10 20:00:39 -07:00
height?: number;
2022-12-19 15:59:14 -08:00
itemGap: number;
itemSize: number;
itemType: LibraryItem;
loading?: boolean;
2022-12-19 15:59:14 -08:00
minimumBatchSize?: number;
route?: CardRoute;
2023-05-10 20:00:39 -07:00
width?: number;
2022-12-19 15:59:14 -08:00
}
2022-12-24 20:19:56 -08:00
export const VirtualInfiniteGrid = forwardRef(
(
{
itemCount,
itemGap,
itemSize,
itemType,
cardRows,
route,
onScroll,
display,
handlePlayQueueAdd,
minimumBatchSize,
fetchFn,
loading,
2022-12-24 20:19:56 -08:00
initialScrollOffset,
2023-01-08 00:52:53 -08:00
handleFavorite,
2022-12-24 20:19:56 -08:00
height,
width,
}: VirtualGridProps,
ref: Ref<VirtualInfiniteGridRef>,
) => {
const listRef = useRef<any>(null);
const loader = useRef<InfiniteLoader>(null);
2023-04-03 03:42:51 -07:00
const [itemData, setItemData] = useState<any[]>([]);
2022-12-24 20:19:56 -08:00
const { itemHeight, rowCount, columnCount } = useMemo(() => {
2023-03-28 15:37:50 -07:00
const itemsPerRow = itemSize;
const widthPerItem = Number(width) / itemsPerRow;
2023-03-09 02:26:09 -08:00
const itemHeight = widthPerItem + cardRows.length * 26;
2022-12-24 20:19:56 -08:00
return {
columnCount: itemsPerRow,
2023-03-09 02:26:09 -08:00
itemHeight,
2022-12-24 20:19:56 -08:00
rowCount: Math.ceil(itemCount / itemsPerRow),
};
2023-03-28 15:37:50 -07:00
}, [cardRows.length, itemCount, itemSize, width]);
2022-12-24 20:19:56 -08:00
const isItemLoaded = useCallback(
(index: number) => {
const itemIndex = index * columnCount;
return itemData[itemIndex] !== undefined;
},
[columnCount, itemData],
2022-12-19 15:59:14 -08:00
);
2022-12-24 20:19:56 -08:00
const loadMoreItems = useCallback(
async (startIndex: number, stopIndex: number) => {
// Fixes a caching bug(?) when switching between filters and the itemCount increases
if (startIndex === 1) return;
// Need to multiply by columnCount due to the grid layout
const start = startIndex * columnCount;
const end = stopIndex * columnCount + columnCount;
const data = await fetchFn({
columnCount,
skip: start,
take: end - start,
});
const newData: any[] = [...itemData];
let itemIndex = 0;
for (let rowIndex = start; rowIndex < end; rowIndex += 1) {
newData[rowIndex] = data.items[itemIndex];
itemIndex += 1;
}
setItemData(newData);
},
[columnCount, fetchFn, itemData, setItemData],
2022-12-24 20:19:56 -08:00
);
const debouncedLoadMoreItems = debounce(loadMoreItems, 500);
useImperativeHandle(ref, () => ({
resetLoadMoreItemsCache: () => {
if (loader.current) {
loader.current.resetloadMoreItemsCache(false);
setItemData([]);
2022-12-24 20:19:56 -08:00
}
},
scrollTo: (index: number) => {
2023-01-07 03:28:28 -08:00
listRef?.current?.scrollToItem(index);
2022-12-24 20:19:56 -08:00
},
setItemData: (data: any[]) => {
setItemData(data);
},
}));
if (loading) return null;
2022-12-24 20:19:56 -08:00
return (
<>
<InfiniteLoader
ref={loader}
isItemLoaded={(index) => isItemLoaded(index)}
itemCount={itemCount || 0}
loadMoreItems={debouncedLoadMoreItems}
minimumBatchSize={minimumBatchSize}
threshold={30}
>
{({ onItemsRendered, ref: infiniteLoaderRef }) => (
<VirtualGridWrapper
cardRows={cardRows}
columnCount={columnCount}
display={display || ListDisplayType.CARD}
2023-01-08 00:52:53 -08:00
handleFavorite={handleFavorite}
handlePlayQueueAdd={handlePlayQueueAdd}
height={height}
initialScrollOffset={initialScrollOffset}
itemCount={itemCount || 0}
itemData={itemData}
itemGap={itemGap}
2023-03-09 02:26:09 -08:00
itemHeight={itemHeight}
itemType={itemType}
itemWidth={itemSize}
refInstance={(list) => {
infiniteLoaderRef(list);
listRef.current = list;
}}
route={route}
rowCount={rowCount}
width={width}
onItemsRendered={onItemsRendered}
onScroll={onScroll}
/>
)}
</InfiniteLoader>
</>
2022-12-24 20:19:56 -08:00
);
},
);
2022-12-19 15:59:14 -08:00
VirtualInfiniteGrid.defaultProps = {
2022-12-26 04:47:40 -08:00
display: ListDisplayType.CARD,
2022-12-19 15:59:14 -08:00
minimumBatchSize: 20,
route: undefined,
};