diff --git a/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx b/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx index 90f3b409..3ccaed3e 100644 --- a/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx +++ b/src/renderer/components/virtual-grid/virtual-infinite-grid.tsx @@ -1,4 +1,12 @@ -import { useState, useEffect, useRef, useMemo, useCallback } from 'react'; +import { + useState, + useRef, + useMemo, + useCallback, + forwardRef, + Ref, + useImperativeHandle, +} from 'react'; import debounce from 'lodash/debounce'; import type { FixedSizeListProps } from 'react-window'; import InfiniteLoader from 'react-window-infinite-loader'; @@ -6,6 +14,12 @@ import { VirtualGridWrapper } from '/@/renderer/components/virtual-grid/virtual- import type { CardRoute, CardRow, LibraryItem, PlayQueueAddOptions } from '/@/renderer/types'; import { CardDisplayType } from '/@/renderer/types'; +export type VirtualInfiniteGridRef = { + resetLoadMoreItemsCache: () => void; + scrollTo: (index: number) => void; + setItemData: (data: any[]) => void; +}; + interface VirtualGridProps extends Omit { cardRows: CardRow[]; display?: CardDisplayType; @@ -15,7 +29,6 @@ interface VirtualGridProps extends Omit { return 1920; }; -export const VirtualInfiniteGrid = ({ - itemCount, - itemGap, - itemSize, - itemType, - cardRows, - route, - onScroll, - display, - handlePlayQueueAdd, - minimumBatchSize, - fetchFn, - initialScrollOffset, - height, - width, - refresh, -}: VirtualGridProps) => { - const [itemData, setItemData] = useState([]); - const listRef = useRef(null); - const loader = useRef(null); +export const VirtualInfiniteGrid = forwardRef( + ( + { + itemCount, + itemGap, + itemSize, + itemType, + cardRows, + route, + onScroll, + display, + handlePlayQueueAdd, + minimumBatchSize, + fetchFn, + initialScrollOffset, + height, + width, + }: VirtualGridProps, + ref: Ref, + ) => { + const [itemData, setItemData] = useState([]); + const listRef = useRef(null); + const loader = useRef(null); - const { itemHeight, rowCount, columnCount } = useMemo(() => { - const itemsPerRow = Math.floor( - (constrainWidth(Number(width)) - itemGap + 3) / (itemSize! + itemGap + 2), + const { itemHeight, rowCount, columnCount } = useMemo(() => { + const itemsPerRow = Math.floor( + (constrainWidth(Number(width)) - itemGap + 3) / (itemSize! + itemGap + 2), + ); + + return { + columnCount: itemsPerRow, + itemHeight: itemSize! + cardRows.length * 22 + itemGap, + itemWidth: itemSize! + itemGap, + rowCount: Math.ceil(itemCount / itemsPerRow), + }; + }, [cardRows.length, itemCount, itemGap, itemSize, width]); + + const isItemLoaded = useCallback( + (index: number) => { + const itemIndex = index * columnCount; + + return itemData[itemIndex] !== undefined; + }, + [columnCount, itemData], ); - return { - columnCount: itemsPerRow, - itemHeight: itemSize! + cardRows.length * 22 + itemGap, - itemWidth: itemSize! + itemGap, - rowCount: Math.ceil(itemCount / itemsPerRow), - }; - }, [cardRows.length, itemCount, itemGap, itemSize, width]); + const loadMoreItems = useCallback( + async (startIndex: number, stopIndex: number) => { + // Fixes a caching bug(?) when switching between filters and the itemCount increases + if (startIndex === 1) return; - const isItemLoaded = useCallback( - (index: number) => { - const itemIndex = index * columnCount; + // Need to multiply by columnCount due to the grid layout + const start = startIndex * columnCount; + const end = stopIndex * columnCount + columnCount; - return itemData[itemIndex] !== undefined; - }, - [columnCount, itemData], - ); + const data = await fetchFn({ + columnCount, + skip: start, + take: end - start, + }); - const loadMoreItems = useCallback( - async (startIndex: number, stopIndex: number) => { - // Fixes a caching bug(?) when switching between filters and the itemCount increases - if (startIndex === 1) return; + const newData: any[] = [...itemData]; - // Need to multiply by columnCount due to the grid layout - const start = startIndex * columnCount; - const end = stopIndex * columnCount + columnCount; + let itemIndex = 0; + for (let rowIndex = start; rowIndex < end; rowIndex += 1) { + newData[rowIndex] = data.items[itemIndex]; + itemIndex += 1; + } - const data = await fetchFn({ - columnCount, - skip: start, - take: end - start, - }); + setItemData(newData); + }, + [columnCount, fetchFn, itemData], + ); - const newData: any[] = [...itemData]; + const debouncedLoadMoreItems = debounce(loadMoreItems, 500); - let itemIndex = 0; - for (let rowIndex = start; rowIndex < end; rowIndex += 1) { - newData[rowIndex] = data.items[itemIndex]; - itemIndex += 1; - } + useImperativeHandle(ref, () => ({ + resetLoadMoreItemsCache: () => { + if (loader.current) { + loader.current.resetloadMoreItemsCache(false); + setItemData(() => []); + } + }, + scrollTo: (index: number) => { + listRef.current.scrollToItem(index); + }, + setItemData: (data: any[]) => { + setItemData(data); + }, + })); - setItemData(newData); - }, - [columnCount, fetchFn, itemData], - ); - - const debouncedLoadMoreItems = debounce(loadMoreItems, 500); - - useEffect(() => { - if (loader.current) { - listRef.current.scrollTo(0); - loader.current.resetloadMoreItemsCache(false); - setItemData(() => []); - - loadMoreItems(0, minimumBatchSize! * 2); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [minimumBatchSize, fetchFn, refresh]); - - return ( - isItemLoaded(index)} - itemCount={itemCount || 0} - loadMoreItems={debouncedLoadMoreItems} - minimumBatchSize={minimumBatchSize} - threshold={30} - > - {({ onItemsRendered, ref: infiniteLoaderRef }) => ( - { - infiniteLoaderRef(list); - listRef.current = list; - }} - route={route} - rowCount={rowCount} - width={width} - onItemsRendered={onItemsRendered} - onScroll={onScroll} - /> - )} - - ); -}; + return ( + isItemLoaded(index)} + itemCount={itemCount || 0} + loadMoreItems={debouncedLoadMoreItems} + minimumBatchSize={minimumBatchSize} + threshold={30} + > + {({ onItemsRendered, ref: infiniteLoaderRef }) => ( + { + infiniteLoaderRef(list); + listRef.current = list; + }} + route={route} + rowCount={rowCount} + width={width} + onItemsRendered={onItemsRendered} + onScroll={onScroll} + /> + )} + + ); + }, +); VirtualInfiniteGrid.defaultProps = { display: CardDisplayType.CARD, minimumBatchSize: 20, - refresh: undefined, route: undefined, };