more grid list optimizations

This commit is contained in:
jeffvli
2025-10-24 00:05:10 -07:00
parent 62b0ea6616
commit 62127df4f4
2 changed files with 117 additions and 139 deletions
+18 -28
View File
@@ -1,6 +1,6 @@
import clsx from 'clsx'; import clsx from 'clsx';
import { AnimatePresence } from 'motion/react'; import { AnimatePresence } from 'motion/react';
import { Dispatch, Fragment, memo, ReactNode, SetStateAction, useState } from 'react'; import { Fragment, memo, ReactNode, useState } from 'react';
import { generatePath, Link } from 'react-router'; import { generatePath, Link } from 'react-router';
import styles from './item-card.module.css'; import styles from './item-card.module.css';
@@ -44,8 +44,6 @@ export const ItemCard = ({
type = 'poster', type = 'poster',
withControls, withControls,
}: ItemCardProps) => { }: ItemCardProps) => {
const [showControls, setShowControls] = useState(false);
const imageUrl = getImageUrl(data); const imageUrl = getImageUrl(data);
const rows = getDataRows(itemType); const rows = getDataRows(itemType);
@@ -59,8 +57,6 @@ export const ItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
rows={rows} rows={rows}
setShowControls={setShowControls}
showControls={showControls}
withControls={withControls} withControls={withControls}
/> />
); );
@@ -73,8 +69,6 @@ export const ItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
rows={rows} rows={rows}
setShowControls={setShowControls}
showControls={showControls}
withControls={withControls} withControls={withControls}
/> />
); );
@@ -88,8 +82,6 @@ export const ItemCard = ({
isRound={isRound} isRound={isRound}
itemType={itemType} itemType={itemType}
rows={rows} rows={rows}
setShowControls={setShowControls}
showControls={showControls}
withControls={withControls} withControls={withControls}
/> />
); );
@@ -100,8 +92,6 @@ export interface ItemCardDerivativeProps extends Omit<ItemCardProps, 'type'> {
controls: ItemControls; controls: ItemControls;
imageUrl: string | undefined; imageUrl: string | undefined;
rows: DataRow[]; rows: DataRow[];
setShowControls: Dispatch<SetStateAction<boolean>>;
showControls: boolean;
} }
const CompactItemCard = ({ const CompactItemCard = ({
@@ -111,10 +101,10 @@ const CompactItemCard = ({
isRound, isRound,
itemType, itemType,
rows, rows,
setShowControls,
showControls,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
if (data) { if (data) {
return ( return (
<div className={clsx(styles.container, styles.compact)}> <div className={clsx(styles.container, styles.compact)}>
@@ -170,10 +160,10 @@ const DefaultItemCard = ({
isRound, isRound,
itemType, itemType,
rows, rows,
setShowControls,
showControls,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
if (data) { if (data) {
return ( return (
<div className={clsx(styles.container)}> <div className={clsx(styles.container)}>
@@ -229,10 +219,10 @@ const PosterItemCard = ({
isRound, isRound,
itemType, itemType,
rows, rows,
setShowControls,
showControls,
withControls, withControls,
}: ItemCardDerivativeProps) => { }: ItemCardDerivativeProps) => {
const [showControls, setShowControls] = useState(false);
if (data) { if (data) {
return ( return (
<div className={clsx(styles.container, styles.poster)}> <div className={clsx(styles.container, styles.poster)}>
@@ -246,16 +236,14 @@ const PosterItemCard = ({
className={clsx(styles.image, { [styles.isRound]: isRound })} className={clsx(styles.image, { [styles.isRound]: isRound })}
src={imageUrl} src={imageUrl}
/> />
<AnimatePresence> {withControls && showControls && (
{withControls && showControls && ( <ItemCardControls
<ItemCardControls controls={controls}
controls={controls} item={data}
item={data} itemType={itemType}
itemType={itemType} type="poster"
type="poster" />
/> )}
)}
</AnimatePresence>
</div> </div>
<div className={styles.detailContainer}> <div className={styles.detailContainer}>
{rows.map((row) => ( {rows.map((row) => (
@@ -273,7 +261,9 @@ const PosterItemCard = ({
</div> </div>
<div className={styles.detailContainer}> <div className={styles.detailContainer}>
{rows.map((row) => ( {rows.map((row) => (
<ItemCardRow data={undefined} key={row.id} row={row} type="poster" /> <div className={styles.row} key={row.id}>
&nbsp;
</div>
))} ))}
</div> </div>
</div> </div>
@@ -6,6 +6,8 @@ import { AnimatePresence } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react'; import { useOverlayScrollbars } from 'overlayscrollbars-react';
import React, { import React, {
CSSProperties, CSSProperties,
memo,
ReactNode,
Ref, Ref,
UIEvent, UIEvent,
useCallback, useCallback,
@@ -28,7 +30,7 @@ import {
ItemListStateActions, ItemListStateActions,
useItemListState, useItemListState,
} from '/@/renderer/components/item-list/helpers/item-list-state'; } from '/@/renderer/components/item-list/helpers/item-list-state';
import { ItemListHandle } from '/@/renderer/components/item-list/types'; import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
import { LibraryItem } from '/@/shared/types/domain-types'; import { LibraryItem } from '/@/shared/types/domain-types';
interface VirtualizedGridListProps { interface VirtualizedGridListProps {
@@ -61,40 +63,69 @@ const VirtualizedGridList = React.memo(
onScroll, onScroll,
tableMeta, tableMeta,
}: VirtualizedGridListProps) => { }: VirtualizedGridListProps) => {
const elements = useMemo(() => { const itemProps: GridItemProps = useMemo(() => {
if (!tableMeta) { return {
return []; columns: tableMeta?.columnCount || 0,
} controls: {
onClick: enableSelection
return data ? (item, itemType) => {
.map((d, i) => { return itemListControls.handleItemClick(
return { item,
data: d, itemType,
index: i, internalState,
}; );
}) }
.reduce( : undefined,
(acc, d) => { onDoubleClick: (item, itemType) => {
if (d.index % (tableMeta?.columnCount || 0) === 0) { return itemListControls.handleItemDoubleClick(
acc.push([]); item,
} itemType,
const prev = acc[acc.length - 1]; internalState,
prev.push(d); );
return acc;
}, },
[] as { data: any; index: number }[][], onFavorite: (item, itemType) => {
); return itemListControls.handleItemFavorite(item, itemType, internalState);
}, [tableMeta, data]); },
onItemExpand: enableExpansion
const itemProps: GridItemProps = { ? (item, itemType) => {
columns: tableMeta?.columnCount || 0, return itemListControls.handleItemExpand(
data: elements, item,
itemType,
internalState,
);
}
: undefined,
onMore: (item, itemType) => {
return itemListControls.handleItemMore(item, itemType, internalState);
},
onPlay: (item, itemType, playType) => {
return itemListControls.handleItemPlay(
item,
itemType,
playType,
internalState,
);
},
onRating: (item, itemType) => {
return itemListControls.handleItemRating(item, itemType, internalState);
},
},
data,
enableExpansion,
enableSelection,
gap,
internalState,
itemType,
};
}, [
data,
tableMeta?.columnCount,
enableExpansion, enableExpansion,
enableSelection, enableSelection,
gap, gap,
internalState, internalState,
itemType, itemType,
}; ]);
return ( return (
<List <List
@@ -112,7 +143,6 @@ const VirtualizedGridList = React.memo(
VirtualizedGridList.displayName = 'VirtualizedGridList'; VirtualizedGridList.displayName = 'VirtualizedGridList';
// Throttled function moved outside component for better performance
const createThrottledSetTableMeta = (itemsPerRow?: number) => { const createThrottledSetTableMeta = (itemsPerRow?: number) => {
return throttle( return throttle(
( (
@@ -170,6 +200,7 @@ const createThrottledSetTableMeta = (itemsPerRow?: number) => {
export interface GridItemProps { export interface GridItemProps {
columns: number; columns: number;
controls: ItemControls;
data: any[]; data: any[];
enableExpansion?: boolean; enableExpansion?: boolean;
enableSelection?: boolean; enableSelection?: boolean;
@@ -427,88 +458,45 @@ export const ItemGridList = ({
); );
}; };
const ListComponent = ({ const ListComponent = memo(
columns, ({
data, columns,
enableExpansion, controls,
enableSelection, data,
gap, gap,
index, index,
internalState, itemType,
itemType, style,
style, }: RowComponentProps<GridItemProps>) => {
}: RowComponentProps<GridItemProps>) => { const items: ReactNode[] = [];
return ( const itemCount = data.length;
<div className={styles.itemList} style={style}> const startIndex = index * columns;
{data[index].map((d) => ( const stopIndex = Math.min(itemCount - 1, startIndex + columns - 1);
const columnCountInRow = stopIndex - startIndex + 1;
let columnCountToAdd = 0;
if (columnCountInRow !== columns) {
columnCountToAdd = columns - columnCountInRow;
}
for (let i = startIndex; i <= stopIndex + columnCountToAdd; i += 1) {
items.push(
<div <div
className={clsx(styles.itemRow, styles[`gap-${gap}`])} className={clsx(styles.itemRow, styles[`gap-${gap}`])}
key={d.index} key={`card-${i}-${index}`}
style={{ '--columns': columns } as CSSProperties} style={{ '--columns': columns } as CSSProperties}
> >
<ItemCard <ItemCard controls={controls} data={data[i]} itemType={itemType} withControls />
controls={{ </div>,
onClick: enableSelection );
? (item, itemType) => { }
return itemListControls.handleItemClick(
item, return (
itemType, <div className={styles.itemList} style={style}>
internalState, {items}
); </div>
} );
: undefined, },
onDoubleClick: (item, itemType) => { );
return itemListControls.handleItemDoubleClick(
item,
itemType,
internalState,
);
},
onFavorite: (item, itemType) => {
return itemListControls.handleItemFavorite(
item,
itemType,
internalState,
);
},
onItemExpand: enableExpansion
? (item, itemType) => {
return itemListControls.handleItemExpand(
item,
itemType,
internalState,
);
}
: undefined,
onMore: (item, itemType) => {
return itemListControls.handleItemMore(
item,
itemType,
internalState,
);
},
onPlay: (item, itemType, playType) => {
return itemListControls.handleItemPlay(
item,
itemType,
playType,
internalState,
);
},
onRating: (item, itemType) => {
return itemListControls.handleItemRating(
item,
itemType,
internalState,
);
},
}}
data={d.data}
itemType={itemType}
withControls
/>
</div>
))}
</div>
);
};