remove deprecated virtual grid

This commit is contained in:
jeffvli
2025-10-10 18:17:14 -07:00
parent 804d8c766f
commit ac625a9032
10 changed files with 0 additions and 1070 deletions
@@ -1,98 +0,0 @@
.container {
display: flex;
flex-direction: column;
width: 100%;
height: calc(100% - 2rem);
overflow: hidden;
pointer-events: auto;
cursor: pointer;
border-radius: var(--theme-radius-md);
&:hover {
background: var(--theme-colors-surface-hover);
}
}
.container.is-hidden {
opacity: 0;
}
.inner-container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
padding: 1rem;
overflow: hidden;
background: lighten(var(--theme-colors-surface), 3%);
.card-controls {
opacity: 0;
}
&:hover .card-controls {
opacity: 1;
}
&:hover * {
&::before {
opacity: 0.5;
}
}
}
.image-container {
position: relative;
display: flex;
align-items: center;
height: 100%;
aspect-ratio: 1/1;
overflow: hidden;
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
user-select: none;
content: '';
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
}
.image-container.is-favorite {
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--theme-colors-primary-filled);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
}
}
.image {
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
border-radius: var(--theme-radius-md);
img {
height: 100%;
object-fit: var(--theme-image-fit);
}
}
.detail-container {
margin-top: 0.5rem;
}
@@ -1,124 +0,0 @@
import clsx from 'clsx';
import { useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { ListChildComponentProps } from 'react-window';
import styles from './default-card.module.css';
import { CardRows } from '/@/renderer/components/card/card-rows';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack';
import {
Album,
AlbumArtist,
Artist,
LibraryItem,
Playlist,
Song,
} from '/@/shared/types/domain-types';
import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types';
interface BaseGridCardProps {
columnIndex: number;
controls: {
cardRows: CardRow<Album | AlbumArtist | Artist | Playlist | Song>[];
handleFavorite: (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
}) => void;
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemGap: number;
itemType: LibraryItem;
playButtonBehavior: Play;
resetInfiniteLoaderCache: () => void;
route: CardRoute;
};
data: any;
isHidden?: boolean;
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
}
export const DefaultCard = ({
columnIndex,
controls,
data,
isHidden,
listChildProps,
}: BaseGridCardProps) => {
const navigate = useNavigate();
const [isHovered, setIsHovered] = useState(false);
if (data) {
const path = generatePath(
controls.route.route as string,
controls.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
);
return (
<div
className={clsx(styles.container, isHidden && styles.isHidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
onClick={() => navigate(path)}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
margin: controls.itemGap,
}}
>
<div className={styles.innerContainer}>
<div
className={clsx(
styles.imageContainer,
data?.userFavorite && styles.isFavorite,
)}
>
<Image className={styles.image} src={data?.imageUrl} />
<GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={controls.handlePlayQueueAdd}
isHovered={isHovered}
itemData={data}
itemType={controls.itemType}
resetInfiniteLoaderCache={controls.resetInfiniteLoaderCache}
/>
</div>
<div className={styles.detailContainer}>
<CardRows data={data} rows={controls.cardRows} />
</div>
</div>
</div>
);
}
return (
<div
className={clsx(styles.container, isHidden && styles.isHidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
style={{
margin: controls.itemGap,
}}
>
<div className={styles.innerContainer}>
<div className={styles.imageContainer}>
<Skeleton className={styles.image} />
</div>
<div className={styles.detailContainer}>
<Stack gap="xs">
{(controls?.cardRows || []).map((row, index) => (
<Skeleton key={`${index}-${columnIndex}-${row.arrayProperty}`} />
))}
</Stack>
</div>
</div>
</div>
);
};
@@ -1,84 +0,0 @@
.play-button {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
background-color: var(--theme-colors-white);
border: none;
border-radius: 50%;
opacity: 0.8;
transition: opacity 0.2s ease-in-out;
transition: scale 0.1s ease-in-out;
&:hover {
background-color: var(--theme-colors-white);
opacity: 1;
}
&:active {
opacity: 1;
}
svg {
fill: var(--theme-colors-black);
stroke: var(--theme-colors-black);
}
}
.secondary-button {
opacity: 0.8;
transition: opacity 0.2s ease-in-out;
transition: scale 0.2s linear;
&:hover {
opacity: 1;
}
&:active {
opacity: 1;
}
}
.grid-card-controls-container {
position: absolute;
z-index: 100;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
.favorite-banner {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--theme-colors-primary-filled);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
}
.favorite-wrapper {
svg {
fill: var(--theme-colors-primary-filled);
}
}
.bottom-controls {
position: absolute;
bottom: 0;
display: flex;
gap: var(--theme-spacing-md);
align-items: flex-end;
justify-content: flex-end;
width: 100%;
height: calc(100% / 3);
padding: 1rem 0.5rem;
}
@@ -1,120 +0,0 @@
import clsx from 'clsx';
import { MouseEvent, useState } from 'react';
import styles from './grid-card-controls.module.css';
import {
ALBUM_CONTEXT_MENU_ITEMS,
ARTIST_CONTEXT_MENU_ITEMS,
PLAYLIST_CONTEXT_MENU_ITEMS,
} from '/@/renderer/features/context-menu/context-menu-items';
import { useHandleGridContextMenu } from '/@/renderer/features/context-menu/hooks/use-handle-context-menu';
import { usePlayerContext } from '/@/renderer/features/player/context/player-context';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Button } from '/@/shared/components/button/button';
import { Icon } from '/@/shared/components/icon/icon';
import { LibraryItem } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types';
export const GridCardControls = ({
handleFavorite,
isHovered,
itemData,
itemType,
resetInfiniteLoaderCache,
}: {
handleFavorite: (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
serverId: string;
}) => void;
isHovered?: boolean;
itemData: any;
itemType: LibraryItem;
resetInfiniteLoaderCache?: () => void;
}) => {
const [isFavorite, setIsFavorite] = useState(itemData?.userFavorite);
const playButtonBehavior = usePlayButtonBehavior();
const player = usePlayerContext();
const handlePlay = async (e: MouseEvent<HTMLButtonElement>, playType?: Play) => {
e.preventDefault();
e.stopPropagation();
player.addToQueueByFetch(
itemData._serverId,
[itemData.id],
itemType,
playType || playButtonBehavior,
);
};
const handleFavorites = async (e: MouseEvent<HTMLButtonElement>, serverId: string) => {
e.preventDefault();
e.stopPropagation();
handleFavorite?.({
id: [itemData.id],
isFavorite: itemData.userFavorite,
itemType,
serverId,
});
setIsFavorite(!isFavorite);
};
const handleContextMenu = useHandleGridContextMenu(
itemType,
itemType === LibraryItem.ALBUM
? ALBUM_CONTEXT_MENU_ITEMS
: itemType === LibraryItem.PLAYLIST
? PLAYLIST_CONTEXT_MENU_ITEMS
: ARTIST_CONTEXT_MENU_ITEMS,
resetInfiniteLoaderCache,
);
return (
<>
{isFavorite ? <div className={styles.favoriteBanner} /> : null}
{isHovered && (
<div className={clsx(styles.gridCardControlsContainer)}>
<Button
classNames={{ root: styles.playButton }}
onClick={handlePlay}
variant="filled"
>
<Icon icon="mediaPlay" size="xl" />
</Button>
<div className={styles.bottomControls}>
{itemType !== LibraryItem.PLAYLIST && (
<ActionIcon
classNames={{ root: styles.secondaryButton }}
icon={isFavorite ? 'favorite' : 'favorite'}
iconProps={{
fill: isFavorite ? 'primary' : undefined,
}}
onClick={(e) => handleFavorites(e, itemData?.serverId)}
size="sm"
variant="transparent"
/>
)}
<ActionIcon
classNames={{ root: styles.secondaryButton }}
icon="ellipsisHorizontal"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleContextMenu(e, [itemData]);
}}
size="sm"
variant="transparent"
/>
</div>
</div>
)}
</>
);
};
@@ -1,69 +0,0 @@
import type { ListChildComponentProps } from 'react-window';
import { memo } from 'react';
import { areEqual } from 'react-window';
import { DefaultCard } from '/@/renderer/components/virtual-grid/grid-card/default-card';
import { PosterCard } from '/@/renderer/components/virtual-grid/grid-card/poster-card';
import { CardRow, GridCardData, ListDisplayType } from '/@/shared/types/types';
export const GridCard = memo(({ data, index, style }: ListChildComponentProps) => {
const {
cardRows,
columnCount,
display,
handleFavorite,
handlePlayQueueAdd,
itemCount,
itemData,
itemGap,
itemType,
playButtonBehavior,
resetInfiniteLoaderCache,
route,
} = data as GridCardData;
const cards: React.ReactNode[] = [];
const startIndex = index * columnCount;
const stopIndex = Math.min(itemCount - 1, startIndex + columnCount - 1);
const columnCountInRow = stopIndex - startIndex + 1;
let columnCountToAdd = 0;
if (columnCountInRow !== columnCount) {
columnCountToAdd = columnCount - columnCountInRow;
}
const View = display === ListDisplayType.CARD ? DefaultCard : PosterCard;
for (let i = startIndex; i <= stopIndex + columnCountToAdd; i += 1) {
cards.push(
<View
columnIndex={i}
controls={{
cardRows: cardRows as CardRow<any>[],
handleFavorite,
handlePlayQueueAdd,
itemGap,
itemType,
playButtonBehavior,
resetInfiniteLoaderCache,
route,
}}
data={itemData[i]}
isHidden={i > stopIndex}
key={`card-${i}-${index}`}
listChildProps={{ index }}
/>,
);
}
return (
<div
style={{
...style,
display: 'flex',
}}
>
{cards}
</div>
);
}, areEqual);
@@ -1,90 +0,0 @@
.container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: auto;
&:global(.card-controls) {
opacity: 0;
}
}
.container.hidden {
opacity: 0;
}
.link-container {
cursor: pointer;
}
.image-container {
position: relative;
display: flex;
align-items: center;
aspect-ratio: 1/1;
overflow: hidden;
&::before {
position: absolute;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
user-select: none;
content: '';
background: linear-gradient(0deg, rgb(0 0 0 / 100%) 35%, rgb(0 0 0 / 0%) 100%);
opacity: 0;
transition: all 0.2s ease-in-out;
}
&:hover {
&::before {
opacity: 0.5;
}
}
&:hover .card-controls {
opacity: 1;
}
}
.image-container.is-favorite {
&::after {
position: absolute;
top: -50px;
left: -50px;
width: 80px;
height: 80px;
pointer-events: none;
content: '';
background-color: var(--theme-colors-primary-filled);
box-shadow: 0 0 10px 8px rgb(0 0 0 / 80%);
transform: rotate(-45deg);
}
}
.image {
width: 100%;
max-width: 100%;
height: 100% !important;
max-height: 100%;
border: 0;
border-radius: var(--theme-radius-md);
img {
height: 100%;
object-fit: var(--theme-image-fit);
}
}
.detail-container {
margin-top: 0.5rem;
}
.placeholder-wrapper {
width: 100%;
height: 100%;
}
@@ -1,121 +0,0 @@
import clsx from 'clsx';
import { useState } from 'react';
import { generatePath, useNavigate } from 'react-router-dom';
import { ListChildComponentProps } from 'react-window';
import styles from './poster-card.module.css';
import { CardRows } from '/@/renderer/components/card/card-rows';
import { GridCardControls } from '/@/renderer/components/virtual-grid/grid-card/grid-card-controls';
import { Image } from '/@/shared/components/image/image';
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
import { Stack } from '/@/shared/components/stack/stack';
import {
Album,
AlbumArtist,
Artist,
LibraryItem,
Playlist,
Song,
} from '/@/shared/types/domain-types';
import { CardRoute, CardRow, Play, PlayQueueAddOptions } from '/@/shared/types/types';
interface BaseGridCardProps {
columnIndex: number;
controls: {
cardRows: CardRow<Album | AlbumArtist | Artist | Playlist | Song>[];
handleFavorite: (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
}) => void;
handlePlayQueueAdd: (options: PlayQueueAddOptions) => void;
itemGap: number;
itemType: LibraryItem;
playButtonBehavior: Play;
resetInfiniteLoaderCache: () => void;
route: CardRoute;
};
data: any;
isHidden?: boolean;
listChildProps: Omit<ListChildComponentProps, 'data' | 'style'>;
}
export const PosterCard = ({
columnIndex,
controls,
data,
isHidden,
listChildProps,
}: BaseGridCardProps) => {
const navigate = useNavigate();
const [isHovered, setIsHovered] = useState(false);
if (data) {
const path = generatePath(
controls.route.route as string,
controls.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
);
return (
<div
className={styles.container}
key={`card-${columnIndex}-${listChildProps.index}`}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
style={{
margin: controls.itemGap,
}}
>
<div className={styles.linkContainer} onClick={() => navigate(path)}>
<div
className={`${styles.imageContainer} ${data?.userFavorite ? styles.isFavorite : ''}`}
>
<Image className={styles.image} src={data?.imageUrl} />
<GridCardControls
handleFavorite={controls.handleFavorite}
handlePlayQueueAdd={controls.handlePlayQueueAdd}
isHovered={isHovered}
itemData={data}
itemType={controls.itemType}
resetInfiniteLoaderCache={controls.resetInfiniteLoaderCache}
/>
</div>
</div>
<div className={styles.detailContainer}>
<CardRows data={data} rows={controls.cardRows} />
</div>
</div>
);
}
return (
<div
className={clsx(styles.container, isHidden && styles.hidden)}
key={`card-${columnIndex}-${listChildProps.index}`}
style={{
margin: controls.itemGap,
}}
>
<div className={styles.imageContainer}>
<Skeleton className={styles.image} />
</div>
<div className={styles.detailContainer}>
<Stack gap="xs">
{(controls?.cardRows || []).map((row, index) => (
<Skeleton
className={styles.row}
key={`${index}-${columnIndex}-${row.arrayProperty}`}
/>
))}
</Stack>
</div>
</div>
);
};
@@ -1,9 +0,0 @@
.virtual-grid-container {
display: flex;
flex-direction: column;
height: 100%;
}
.virtual-grid-auto-sizer-container {
flex: 1;
}
@@ -1,142 +0,0 @@
import type {
CardRoute,
CardRow,
ListDisplayType,
PlayQueueAddOptions,
} from '/@/shared/types/types';
import type { Ref } from 'react';
import type { FixedSizeListProps } from 'react-window';
import debounce from 'lodash/debounce';
import memoize from 'memoize-one';
import { FixedSizeList } from 'react-window';
import styles from './virtual-grid-wrapper.module.css';
import { GridCard } from '/@/renderer/components/virtual-grid/grid-card';
import { Album, AlbumArtist, Artist, LibraryItem } from '/@/shared/types/domain-types';
const createItemData = memoize(
(
cardRows,
columnCount,
display,
itemCount,
itemData,
itemGap,
itemHeight,
itemType,
itemWidth,
route,
handlePlayQueueAdd,
handleFavorite,
resetInfiniteLoaderCache,
) => ({
cardRows,
columnCount,
display,
handleFavorite,
handlePlayQueueAdd,
itemCount,
itemData,
itemGap,
itemHeight,
itemType,
itemWidth,
resetInfiniteLoaderCache,
route,
}),
);
const createScrollHandler = memoize((onScroll) => debounce(onScroll, 250));
export const VirtualGridWrapper = ({
cardRows,
columnCount,
display,
handleFavorite,
handlePlayQueueAdd,
height,
initialScrollOffset,
itemCount,
itemData,
itemGap,
itemHeight,
itemType,
itemWidth,
onScroll,
refInstance,
resetInfiniteLoaderCache,
route,
rowCount,
width,
...rest
}: Omit<FixedSizeListProps, 'children' | 'height' | 'itemSize' | 'ref' | 'width'> & {
cardRows: CardRow<Album | AlbumArtist | Artist>[];
columnCount: number;
display: ListDisplayType;
handleFavorite?: (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
}) => void;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
height?: number;
itemData: any[];
itemGap: number;
itemHeight: number;
itemType: LibraryItem;
itemWidth: number;
refInstance: Ref<any>;
resetInfiniteLoaderCache: () => void;
route?: CardRoute;
rowCount: number;
width?: number;
}) => {
const memoizedItemData = createItemData(
cardRows,
columnCount,
display,
itemCount,
itemData,
itemGap,
itemHeight,
itemType,
itemWidth,
route,
handlePlayQueueAdd,
handleFavorite,
resetInfiniteLoaderCache,
);
const memoizedOnScroll = createScrollHandler(onScroll);
return (
<FixedSizeList
ref={refInstance}
{...rest}
height={(height && Number(height)) || 0}
initialScrollOffset={initialScrollOffset}
itemCount={rowCount}
itemData={memoizedItemData}
itemSize={itemHeight}
onScroll={memoizedOnScroll}
overscanCount={5}
width={(width && Number(width)) || 0}
>
{GridCard}
</FixedSizeList>
);
};
interface VirtualGridContainerProps {
children: React.ReactNode;
}
export const VirtualGridContainer = ({ children }: VirtualGridContainerProps) => {
return <div className={styles.virtualGridContainer}>{children}</div>;
};
export const VirtualGridAutoSizerContainer = ({ children }: VirtualGridContainerProps) => {
return <div className={styles.virtualGridAutoSizerContainer}>{children}</div>;
};
@@ -1,213 +0,0 @@
import type { CardRoute, CardRow, PlayQueueAddOptions } from '/@/shared/types/types';
import type { FixedSizeListProps } from 'react-window';
import debounce from 'lodash/debounce';
import {
forwardRef,
Ref,
useCallback,
useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
import InfiniteLoader from 'react-window-infinite-loader';
import { VirtualGridWrapper } from '/@/renderer/components/virtual-grid/virtual-grid-wrapper';
import { AnyLibraryItem, Genre, LibraryItem } from '/@/shared/types/domain-types';
import { ListDisplayType } from '/@/shared/types/types';
export type VirtualInfiniteGridRef = {
resetLoadMoreItemsCache: () => void;
scrollTo: (index: number) => void;
setItemData: (data: LibraryItemOrGenre[]) => void;
updateItemData: (rule: (item: LibraryItemOrGenre) => LibraryItemOrGenre) => void;
};
type LibraryItemOrGenre = AnyLibraryItem | Genre;
interface VirtualGridProps
extends Omit<FixedSizeListProps, 'children' | 'height' | 'itemSize' | 'width'> {
cardRows: CardRow<any>[];
display?: ListDisplayType;
fetchFn: (options: { columnCount: number; skip: number; take: number }) => Promise<any>;
fetchInitialData?: () => LibraryItemOrGenre[];
handleFavorite?: (options: {
id: string[];
isFavorite: boolean;
itemType: LibraryItem;
}) => void;
handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void;
height?: number;
itemGap: number;
itemSize: number;
itemType: LibraryItem;
loading?: boolean;
minimumBatchSize?: number;
route?: CardRoute;
width?: number;
}
export const VirtualInfiniteGrid = forwardRef(
(
{
cardRows,
display,
fetchFn,
fetchInitialData,
handleFavorite,
handlePlayQueueAdd,
height,
initialScrollOffset,
itemCount,
itemGap,
itemSize,
itemType,
loading,
minimumBatchSize,
onScroll,
route,
width,
}: VirtualGridProps,
ref: Ref<VirtualInfiniteGridRef>,
) => {
const listRef = useRef<any>(null);
const loader = useRef<InfiniteLoader>(null);
const minItemCount = useRef(0);
// itemData can be a sparse array. Treat the intermediate elements as being undefined
const [itemData, setItemData] = useState<Array<LibraryItemOrGenre | undefined>>(
fetchInitialData?.() || [],
);
const { columnCount, itemHeight, rowCount } = useMemo(() => {
const itemsPerRow = width ? Math.floor(width / (itemSize + itemGap * 2)) : 5;
const widthPerItem = Number(width) / itemsPerRow;
const itemHeight = widthPerItem + cardRows.length * 26;
return {
columnCount: itemsPerRow,
itemHeight,
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],
);
const loadMoreItems = useCallback(
async (startIndex: number, stopIndex: number) => {
if (
// Fixes a caching bug(?) when switching between filters and the itemCount increases
startIndex === 1 ||
// Fixes a caching bug when refreshing items. Prevents a second
// refetch from happening if:
// 1: we are already in a refresh (-1)
// 2: we just had a refresh, and we are index 0
minItemCount.current === -1 ||
(minItemCount.current > 0 && startIndex === 0)
)
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,
});
setItemData((itemData) => {
const newData = [...itemData];
let itemIndex = 0;
for (let rowIndex = start; rowIndex < itemCount; rowIndex += 1) {
newData[rowIndex] = data.items[itemIndex];
itemIndex += 1;
}
return newData;
});
},
[columnCount, fetchFn, itemCount],
);
const debouncedLoadMoreItems = debounce(loadMoreItems, 500);
useImperativeHandle(ref, () => ({
resetLoadMoreItemsCache: () => {
if (loader.current) {
loader.current.resetloadMoreItemsCache(false);
minItemCount.current = -1;
setItemData([]);
}
},
scrollTo: (index: number) => {
listRef?.current?.scrollToItem(index);
},
setItemData: (data: LibraryItemOrGenre[]) => {
setItemData(data);
minItemCount.current = data.length;
},
updateItemData: (rule) => {
setItemData((data) => data.map((item) => item && rule(item)));
},
}));
if (loading) return null;
return (
<>
<InfiniteLoader
isItemLoaded={(index) => isItemLoaded(index)}
itemCount={itemCount || 0}
loadMoreItems={debouncedLoadMoreItems}
minimumBatchSize={minimumBatchSize}
ref={loader}
threshold={30}
>
{({ onItemsRendered, ref: infiniteLoaderRef }) => (
<VirtualGridWrapper
cardRows={cardRows}
columnCount={columnCount}
display={display || ListDisplayType.CARD}
handleFavorite={handleFavorite}
handlePlayQueueAdd={handlePlayQueueAdd}
height={height}
initialScrollOffset={initialScrollOffset}
itemCount={itemCount || 0}
itemData={itemData}
itemGap={itemGap}
itemHeight={itemHeight}
itemType={itemType}
itemWidth={itemSize}
onItemsRendered={onItemsRendered}
onScroll={onScroll}
refInstance={(list) => {
infiniteLoaderRef(list);
listRef.current = list;
}}
resetInfiniteLoaderCache={() => {
if (loader.current) {
loader.current.resetloadMoreItemsCache(false);
setItemData([]);
}
}}
route={route}
rowCount={rowCount}
width={width}
/>
)}
</InfiniteLoader>
</>
);
},
);