refactor item list to use accessor function

This commit is contained in:
jeffvli
2026-01-16 16:34:31 -08:00
parent e2b20eb89b
commit d1aed5007f
39 changed files with 528 additions and 324 deletions
@@ -32,6 +32,13 @@ export const getListQueryKeyName = (itemType: LibraryItem): string => {
} }
}; };
type InfiniteLoaderCacheData = {
dataMap: Map<number, unknown>;
idToIndexMap: Map<string, number>;
pagesLoaded: Record<string, boolean>;
version: number;
};
interface UseItemListInfiniteLoaderProps { interface UseItemListInfiniteLoaderProps {
eventKey: string; eventKey: string;
fetchThreshold?: number; fetchThreshold?: number;
@@ -43,10 +50,12 @@ interface UseItemListInfiniteLoaderProps {
serverId: string; serverId: string;
} }
function getInitialData(itemCount: number) { function getInitialData(): InfiniteLoaderCacheData {
return { return {
data: Array.from({ length: itemCount }, () => undefined), dataMap: new Map(),
idToIndexMap: new Map(),
pagesLoaded: {}, pagesLoaded: {},
version: 0,
}; };
} }
@@ -118,28 +127,27 @@ export const useItemListInfiniteLoader = ({
queryKey: queryKeys[getListQueryKeyName(itemType)].list(serverId, queryParams), queryKey: queryKeys[getListQueryKeyName(itemType)].list(serverId, queryParams),
}); });
const endIndex = startIndex + itemsPerPage;
// Update the query data with the fetched page // Update the query data with the fetched page
queryClient.setQueryData( queryClient.setQueryData(dataQueryKey, (oldData: InfiniteLoaderCacheData) => {
dataQueryKey, const nextDataMap = new Map(oldData.dataMap);
(oldData: { data: unknown[]; pagesLoaded: Record<string, boolean> }) => { const nextIdToIndexMap = new Map(oldData.idToIndexMap);
const newData = [
...oldData.data.slice(0, startIndex), result.items.forEach((item, offset) => {
...result.items, const index = startIndex + offset;
...oldData.data.slice(endIndex), nextDataMap.set(index, item);
]; if (item && typeof item === 'object' && 'id' in (item as any)) {
const newPagesLoaded = { const id = String((item as any).id);
...oldData.pagesLoaded, nextIdToIndexMap.set(id, index);
[pageNumber]: true, }
}; });
return { return {
data: newData, dataMap: nextDataMap,
pagesLoaded: newPagesLoaded, idToIndexMap: nextIdToIndexMap,
pagesLoaded: { ...oldData.pagesLoaded, [pageNumber]: true },
version: oldData.version + 1,
}; };
}, });
);
// Track the last fetched page // Track the last fetched page
lastFetchedPageRef.current = Math.max(lastFetchedPageRef.current, pageNumber); lastFetchedPageRef.current = Math.max(lastFetchedPageRef.current, pageNumber);
@@ -179,7 +187,10 @@ export const useItemListInfiniteLoader = ({
if (!oldData) return oldData; if (!oldData) return oldData;
return { return {
...oldData, ...oldData,
dataMap: new Map(),
idToIndexMap: new Map(),
pagesLoaded: {}, pagesLoaded: {},
version: (oldData?.version ?? 0) + 1,
}; };
}); });
@@ -211,11 +222,11 @@ export const useItemListInfiniteLoader = ({
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [dataQueryKey, queryClient, fetchPage, itemsPerPage]); }, [dataQueryKey, queryClient, fetchPage, itemsPerPage]);
const { data } = useQuery<{ data: unknown[]; pagesLoaded: Record<string, boolean> }>({ const { data } = useQuery<InfiniteLoaderCacheData>({
enabled: false, enabled: false,
initialData: getInitialData(totalItemCount), initialData: getInitialData(),
queryFn: () => { queryFn: () => {
return getInitialData(totalItemCount); return getInitialData();
}, },
queryKey: dataQueryKey, queryKey: dataQueryKey,
}); });
@@ -233,7 +244,7 @@ export const useItemListInfiniteLoader = ({
const pageNumber = Math.floor(range.startIndex / itemsPerPage); const pageNumber = Math.floor(range.startIndex / itemsPerPage);
const currentData = queryClient.getQueryData<{ const currentData = queryClient.getQueryData<{
data: unknown[]; dataMap: Map<number, unknown>;
pagesLoaded: Record<string, boolean>; pagesLoaded: Record<string, boolean>;
}>(dataQueryKey); }>(dataQueryKey);
@@ -289,18 +300,20 @@ export const useItemListInfiniteLoader = ({
// Reset the infinite list data // Reset the infinite list data
const currentData = queryClient.getQueryData<{ const currentData = queryClient.getQueryData<{
data: unknown[]; dataMap: Map<number, unknown>;
pagesLoaded: Record<string, boolean>; pagesLoaded: Record<string, boolean>;
}>(dataQueryKey); }>(dataQueryKey);
if (force || currentData) { if (force || currentData) {
// Reset data to initial state and clear all loaded pages // Reset data to initial state and clear all loaded pages
await queryClient.setQueryData(dataQueryKey, (oldData: any) => { await queryClient.setQueryData(dataQueryKey, (oldData: any) => {
if (!oldData) return getInitialData(totalItemCount); if (!oldData) return getInitialData();
return { return {
...oldData, ...oldData,
data: Array.from({ length: totalItemCount }, () => undefined), dataMap: new Map(),
idToIndexMap: new Map(),
pagesLoaded: {}, pagesLoaded: {},
version: (oldData?.version ?? 0) + 1,
}; };
}); });
lastFetchedPageRef.current = -1; lastFetchedPageRef.current = -1;
@@ -336,28 +349,23 @@ export const useItemListInfiniteLoader = ({
const updateItems = useCallback( const updateItems = useCallback(
(indexes: number[], value: object) => { (indexes: number[], value: object) => {
queryClient.setQueryData( queryClient.setQueryData(dataQueryKey, (prev: InfiniteLoaderCacheData) => {
dataQueryKey, const nextDataMap = new Map(prev.dataMap);
(prev: { data: unknown[]; pagesLoaded: Record<string, boolean> }) => {
indexes.forEach((index) => {
const existing = nextDataMap.get(index);
if (!existing || typeof existing !== 'object') {
return;
}
nextDataMap.set(index, { ...(existing as any), ...(value as any) });
});
return { return {
...prev, ...prev,
data: prev.data.map((item: any, index) => { dataMap: nextDataMap,
if (!item) { version: prev.version + 1,
return item;
}
if (!indexes.includes(index)) {
return item;
}
return {
...item,
...value,
}; };
}), });
};
},
);
}, },
[queryClient, dataQueryKey], [queryClient, dataQueryKey],
); );
@@ -384,16 +392,9 @@ export const useItemListInfiniteLoader = ({
return; return;
} }
const idToIndexMap = data.data
.filter(Boolean)
.reduce((acc: Record<string, number>, item: any, index: number) => {
acc[item.id] = index;
return acc;
}, {});
const dataIndexes = payload.id const dataIndexes = payload.id
.map((id: string) => idToIndexMap[id]) .map((id: string) => (data as any).idToIndexMap?.get(id))
.filter((idx) => idx !== undefined); .filter((idx): idx is number => typeof idx === 'number');
if (dataIndexes.length === 0) { if (dataIndexes.length === 0) {
return; return;
@@ -407,16 +408,9 @@ export const useItemListInfiniteLoader = ({
return; return;
} }
const idToIndexMap = data.data
.filter(Boolean)
.reduce((acc: Record<string, number>, item: any, index: number) => {
acc[item.id] = index;
return acc;
}, {});
const dataIndexes = payload.id const dataIndexes = payload.id
.map((id: string) => idToIndexMap[id]) .map((id: string) => (data as any).idToIndexMap?.get(id))
.filter((idx) => idx !== undefined); .filter((idx): idx is number => typeof idx === 'number');
if (dataIndexes.length === 0) { if (dataIndexes.length === 0) {
return; return;
@@ -434,7 +428,40 @@ export const useItemListInfiniteLoader = ({
}; };
}, [data, eventKey, itemType, serverId, updateItems]); }, [data, eventKey, itemType, serverId, updateItems]);
return { data: data.data, onRangeChanged, refresh, updateItems }; const itemCount = totalItemCount ?? 0;
const getItem = useCallback(
(index: number) => {
return (data as any).dataMap?.get(index);
},
[data],
);
const getItemIndex = useCallback(
(id: string) => {
return (data as any).idToIndexMap?.get(id);
},
[data],
);
const loadedItems = useMemo(() => {
const map: Map<number, unknown> | undefined = (data as any).dataMap;
if (!map || map.size === 0) return [];
return Array.from(map.entries())
.sort(([a], [b]) => a - b)
.map(([, v]) => v);
}, [data]);
return {
dataVersion: (data as any).version ?? 0,
getItem,
getItemIndex,
itemCount,
loadedItems,
onRangeChanged,
refresh,
updateItems,
};
}; };
export const parseListCountQuery = (query: any) => { export const parseListCountQuery = (query: any) => {
@@ -53,14 +53,16 @@ interface VirtualizedGridListProps {
_tableMetaVersion: number; // Used to trigger rerenders via React.memo comparison _tableMetaVersion: number; // Used to trigger rerenders via React.memo comparison
controls: ItemControls; controls: ItemControls;
currentPage?: number; currentPage?: number;
data: unknown[]; dataVersion?: number;
enableDrag?: boolean; enableDrag?: boolean;
enableExpansion: boolean; enableExpansion: boolean;
enableSelection: boolean; enableSelection: boolean;
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
getItem?: (index: number) => ItemCardProps['data'];
height: number; height: number;
initialTop?: ItemGridListProps['initialTop']; initialTop?: ItemGridListProps['initialTop'];
internalState: ItemListStateActions; internalState: ItemListStateActions;
itemCount: number;
itemType: LibraryItem; itemType: LibraryItem;
onRangeChanged?: ItemGridListProps['onRangeChanged']; onRangeChanged?: ItemGridListProps['onRangeChanged'];
onScroll?: ItemGridListProps['onScroll']; onScroll?: ItemGridListProps['onScroll'];
@@ -81,14 +83,16 @@ const VirtualizedGridList = React.memo(
({ ({
controls, controls,
currentPage, currentPage,
data, dataVersion,
enableDrag, enableDrag,
enableExpansion, enableExpansion,
enableSelection, enableSelection,
gap, gap,
getItem,
height, height,
initialTop, initialTop,
internalState, internalState,
itemCount,
itemType, itemType,
onRangeChanged, onRangeChanged,
onScroll, onScroll,
@@ -107,12 +111,14 @@ const VirtualizedGridList = React.memo(
return { return {
columns: tableMeta?.columnCount || 0, columns: tableMeta?.columnCount || 0,
controls, controls,
data, dataVersion,
enableDrag, enableDrag,
enableExpansion, enableExpansion,
enableSelection, enableSelection,
gap, gap,
getItem,
internalState, internalState,
itemCount,
itemType, itemType,
rows, rows,
size, size,
@@ -122,7 +128,9 @@ const VirtualizedGridList = React.memo(
tableMeta, tableMeta,
controls, controls,
rows, rows,
data, getItem,
itemCount,
dataVersion,
enableDrag, enableDrag,
enableExpansion, enableExpansion,
enableSelection, enableSelection,
@@ -285,12 +293,14 @@ const createThrottledSetTableMeta = (
export interface GridItemProps { export interface GridItemProps {
columns: number; columns: number;
controls: ItemCardProps['controls']; controls: ItemCardProps['controls'];
data: any[]; dataVersion?: number;
enableDrag?: boolean; enableDrag?: boolean;
enableExpansion?: boolean; enableExpansion?: boolean;
enableSelection?: boolean; enableSelection?: boolean;
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
getItem?: (index: number) => ItemCardProps['data'];
internalState: ItemListStateActions; internalState: ItemListStateActions;
itemCount: number;
itemType: LibraryItem; itemType: LibraryItem;
rows?: ItemCardProps['rows']; rows?: ItemCardProps['rows'];
size?: 'compact' | 'default' | 'large'; size?: 'compact' | 'default' | 'large';
@@ -304,17 +314,21 @@ export interface GridItemProps {
export interface ItemGridListProps { export interface ItemGridListProps {
currentPage?: number; currentPage?: number;
data: unknown[]; data: unknown[];
dataVersion?: number;
enableDrag?: boolean; enableDrag?: boolean;
enableEntranceAnimation?: boolean; enableEntranceAnimation?: boolean;
enableExpansion?: boolean; enableExpansion?: boolean;
enableSelection?: boolean; enableSelection?: boolean;
enableSelectionDialog?: boolean; enableSelectionDialog?: boolean;
gap?: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; gap?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
getItem?: (index: number) => ItemCardProps['data'];
getItemIndex?: (rowId: string) => number | undefined;
getRowId?: ((item: unknown) => string) | string; getRowId?: ((item: unknown) => string) | string;
initialTop?: { initialTop?: {
to: number; to: number;
type: 'index' | 'offset'; type: 'index' | 'offset';
}; };
itemCount?: number;
itemsPerRow?: number; itemsPerRow?: number;
itemType: LibraryItem; itemType: LibraryItem;
onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => void; onRangeChanged?: (range: { startIndex: number; stopIndex: number }) => void;
@@ -329,13 +343,17 @@ export interface ItemGridListProps {
const BaseItemGridList = ({ const BaseItemGridList = ({
currentPage, currentPage,
data, data,
dataVersion,
enableDrag = true, enableDrag = true,
enableEntranceAnimation = true, enableEntranceAnimation = true,
enableExpansion = false, enableExpansion = false,
enableSelection = true, enableSelection = true,
gap = 'sm', gap = 'sm',
getItem,
getItemIndex,
getRowId, getRowId,
initialTop, initialTop,
itemCount,
itemsPerRow, itemsPerRow,
itemType, itemType,
onRangeChanged, onRangeChanged,
@@ -354,6 +372,14 @@ const BaseItemGridList = ({
const handleRef = useRef<ItemListHandle | null>(null); const handleRef = useRef<ItemListHandle | null>(null);
const mergedContainerRef = useMergedRef(containerRef, rootRef, containerFocusRef); const mergedContainerRef = useMergedRef(containerRef, rootRef, containerFocusRef);
const resolvedItemCount = itemCount ?? data.length;
const resolvedGetItem = useCallback<(index: number) => ItemCardProps['data']>(
(index: number) => {
return (getItem ? getItem(index) : (data as any[])[index]) as ItemCardProps['data'];
},
[data, getItem],
);
const getDataFn = useCallback(() => { const getDataFn = useCallback(() => {
return data; return data;
}, [data]); }, [data]);
@@ -442,7 +468,7 @@ const BaseItemGridList = ({
const { current: container } = containerRef; const { current: container } = containerRef;
if (!container) return; if (!container) return;
throttledSetTableMeta(containerWidth, data.length, (meta) => { throttledSetTableMeta(containerWidth, resolvedItemCount, (meta) => {
if (!meta) return; if (!meta) return;
const current = tableMetaRef.current; const current = tableMetaRef.current;
@@ -459,7 +485,7 @@ const BaseItemGridList = ({
setTableMetaVersion((v) => v + 1); setTableMetaVersion((v) => v + 1);
} }
}); });
}, [containerWidth, data.length, throttledSetTableMeta, containerRef]); }, [containerWidth, resolvedItemCount, throttledSetTableMeta, containerRef]);
const controls = useDefaultItemListControls({ overrides: overrideControls }); const controls = useDefaultItemListControls({ overrides: overrideControls });
@@ -512,7 +538,9 @@ const BaseItemGridList = ({
const lastSelected = selected[selected.length - 1]; const lastSelected = selected[selected.length - 1];
const lastRowId = internalState.extractRowId(lastSelected); const lastRowId = internalState.extractRowId(lastSelected);
if (lastRowId) { if (lastRowId) {
currentIndex = data.findIndex((d: any) => { currentIndex =
getItemIndex?.(lastRowId) ??
data.findIndex((d: any) => {
const rowId = internalState.extractRowId(d); const rowId = internalState.extractRowId(d);
return rowId === lastRowId; return rowId === lastRowId;
}); });
@@ -526,7 +554,7 @@ const BaseItemGridList = ({
: 0; : 0;
const currentCol = const currentCol =
currentIndex !== -1 ? currentIndex % tableMetaRef.current.columnCount : 0; currentIndex !== -1 ? currentIndex % tableMetaRef.current.columnCount : 0;
const totalRows = Math.ceil(data.length / tableMetaRef.current.columnCount); const totalRows = Math.ceil(resolvedItemCount / tableMetaRef.current.columnCount);
let newIndex = 0; let newIndex = 0;
if (currentIndex !== -1) { if (currentIndex !== -1) {
@@ -538,7 +566,7 @@ const BaseItemGridList = ({
const nextRowStart = nextRow * tableMetaRef.current.columnCount; const nextRowStart = nextRow * tableMetaRef.current.columnCount;
const nextRowEnd = Math.min( const nextRowEnd = Math.min(
nextRowStart + tableMetaRef.current.columnCount - 1, nextRowStart + tableMetaRef.current.columnCount - 1,
data.length - 1, resolvedItemCount - 1,
); );
// Keep same column position, or use last item in row if column doesn't exist // Keep same column position, or use last item in row if column doesn't exist
newIndex = Math.min(nextRowStart + currentCol, nextRowEnd); newIndex = Math.min(nextRowStart + currentCol, nextRowEnd);
@@ -559,7 +587,7 @@ const BaseItemGridList = ({
1, 1,
0, 0,
); );
newIndex = Math.min(newIndex, data.length - 1); newIndex = Math.min(newIndex, resolvedItemCount - 1);
} else { } else {
newIndex = currentIndex; newIndex = currentIndex;
} }
@@ -569,14 +597,14 @@ const BaseItemGridList = ({
// Move right, wrap to next row if at end of row // Move right, wrap to next row if at end of row
if ( if (
currentCol < tableMetaRef.current.columnCount - 1 && currentCol < tableMetaRef.current.columnCount - 1 &&
currentIndex < data.length - 1 currentIndex < resolvedItemCount - 1
) { ) {
newIndex = currentIndex + 1; newIndex = currentIndex + 1;
} else if (currentRow < totalRows - 1) { } else if (currentRow < totalRows - 1) {
// Wrap to start of next row // Wrap to start of next row
newIndex = Math.min( newIndex = Math.min(
(currentRow + 1) * tableMetaRef.current.columnCount, (currentRow + 1) * tableMetaRef.current.columnCount,
data.length - 1, resolvedItemCount - 1,
); );
} else { } else {
newIndex = currentIndex; newIndex = currentIndex;
@@ -590,7 +618,7 @@ const BaseItemGridList = ({
const prevRowStart = prevRow * tableMetaRef.current.columnCount; const prevRowStart = prevRow * tableMetaRef.current.columnCount;
const prevRowEnd = Math.min( const prevRowEnd = Math.min(
prevRowStart + tableMetaRef.current.columnCount - 1, prevRowStart + tableMetaRef.current.columnCount - 1,
data.length - 1, resolvedItemCount - 1,
); );
// Keep same column position, or use last item in row if column doesn't exist // Keep same column position, or use last item in row if column doesn't exist
newIndex = Math.min(prevRowStart + currentCol, prevRowEnd); newIndex = Math.min(prevRowStart + currentCol, prevRowEnd);
@@ -605,7 +633,7 @@ const BaseItemGridList = ({
newIndex = 0; newIndex = 0;
} }
const newItem: any = data[newIndex]; const newItem: any = resolvedGetItem(newIndex);
if (!newItem) return; if (!newItem) return;
// Handle Shift + Arrow for incremental range selection (matches shift+click behavior) // Handle Shift + Arrow for incremental range selection (matches shift+click behavior)
@@ -618,7 +646,9 @@ const BaseItemGridList = ({
const lastRowId = internalState.extractRowId(lastSelectedItem); const lastRowId = internalState.extractRowId(lastSelectedItem);
if (!lastRowId) return; if (!lastRowId) return;
const lastIndex = data.findIndex((d: any) => { const lastIndex =
getItemIndex?.(lastRowId) ??
data.findIndex((d: any) => {
const rowId = internalState.extractRowId(d); const rowId = internalState.extractRowId(d);
return rowId === lastRowId; return rowId === lastRowId;
}); });
@@ -630,7 +660,7 @@ const BaseItemGridList = ({
const rangeItems: ItemListStateItemWithRequiredProperties[] = []; const rangeItems: ItemListStateItemWithRequiredProperties[] = [];
for (let i = startIndex; i <= stopIndex; i++) { for (let i = startIndex; i <= stopIndex; i++) {
const rangeItem = data[i]; const rangeItem = resolvedGetItem(i);
if ( if (
rangeItem && rangeItem &&
typeof rangeItem === 'object' && typeof rangeItem === 'object' &&
@@ -695,7 +725,15 @@ const BaseItemGridList = ({
scrollToIndex(newIndex); scrollToIndex(newIndex);
}, },
[data, enableSelection, internalState, scrollToIndex], [
data,
enableSelection,
getItemIndex,
internalState,
resolvedGetItem,
resolvedItemCount,
scrollToIndex,
],
); );
const imperativeHandle: ItemListHandle = useMemo(() => { const imperativeHandle: ItemListHandle = useMemo(() => {
@@ -740,14 +778,16 @@ const BaseItemGridList = ({
_tableMetaVersion={tableMetaVersion} _tableMetaVersion={tableMetaVersion}
controls={controls} controls={controls}
currentPage={currentPage} currentPage={currentPage}
data={data} dataVersion={dataVersion}
enableDrag={enableDrag} enableDrag={enableDrag}
enableExpansion={enableExpansion} enableExpansion={enableExpansion}
enableSelection={enableSelection} enableSelection={enableSelection}
gap={gap} gap={gap}
getItem={resolvedGetItem}
height={height} height={height}
initialTop={initialTop} initialTop={initialTop}
internalState={internalState} internalState={internalState}
itemCount={resolvedItemCount}
itemType={itemType} itemType={itemType}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
onScroll={onScroll ?? (() => {})} onScroll={onScroll ?? (() => {})}
@@ -771,10 +811,10 @@ const BaseItemGridList = ({
const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => { const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
const { index, style } = props; const { index, style } = props;
const { columns, controls, data, enableDrag, gap, itemType, rows, size } = props.data; const { columns, controls, enableDrag, gap, getItem, itemCount, itemType, rows, size } =
props.data;
const items: ReactNode[] = []; const items: ReactNode[] = [];
const itemCount = data.length;
const startIndex = index * columns; const startIndex = index * columns;
const stopIndex = Math.min(itemCount - 1, startIndex + columns - 1); const stopIndex = Math.min(itemCount - 1, startIndex + columns - 1);
@@ -787,7 +827,8 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
} }
for (let i = startIndex; i <= stopIndex + columnCountToAdd; i += 1) { for (let i = startIndex; i <= stopIndex + columnCountToAdd; i += 1) {
if (i < data.length) { if (i < itemCount) {
const item = getItem ? getItem(i) : undefined;
items.push( items.push(
<div <div
className={clsx(styles.itemRow, styles[`gap-${gap}`])} className={clsx(styles.itemRow, styles[`gap-${gap}`])}
@@ -796,7 +837,7 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
> >
<ItemCard <ItemCard
controls={controls} controls={controls}
data={data[i]} data={item}
enableDrag={enableDrag} enableDrag={enableDrag}
enableExpansion={props.data.enableExpansion} enableExpansion={props.data.enableExpansion}
internalState={props.data.internalState} internalState={props.data.internalState}
@@ -6,7 +6,7 @@ import { ItemListItem } from '/@/renderer/components/item-list/types';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
export const ActionsColumn = (props: ItemTableListInnerColumn) => { export const ActionsColumn = (props: ItemTableListInnerColumn) => {
const row: any = (props.data as (any | undefined)[])[props.rowIndex]; const row: any = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const handleActionClick = (event: React.MouseEvent<HTMLButtonElement>) => { const handleActionClick = (event: React.MouseEvent<HTMLButtonElement>) => {
event.stopPropagation(); event.stopPropagation();
@@ -13,11 +13,10 @@ import { JoinedArtists } from '/@/renderer/features/albums/components/joined-art
import { Album, RelatedAlbumArtist, Song } from '/@/shared/types/domain-types'; import { Album, RelatedAlbumArtist, Song } from '/@/shared/types/domain-types';
const AlbumArtistsColumn = (props: ItemTableListInnerColumn) => { const AlbumArtistsColumn = (props: ItemTableListInnerColumn) => {
const row: RelatedAlbumArtist[] | undefined = ( const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.data as (RelatedAlbumArtist[] | undefined)[] const row: RelatedAlbumArtist[] | undefined = rowItem?.[props.columns[props.columnIndex].id];
)[props.rowIndex]?.[props.columns[props.columnIndex].id];
const item = props.data[props.rowIndex] as Album | Song | undefined; const item = rowItem as Album | Song | undefined;
const albumArtistString = item && 'albumArtistName' in item ? item.albumArtistName : ''; const albumArtistString = item && 'albumArtistName' in item ? item.albumArtistName : '';
if (Array.isArray(row)) { if (Array.isArray(row)) {
@@ -15,11 +15,10 @@ import { Text } from '/@/shared/components/text/text';
import { Song } from '/@/shared/types/domain-types'; import { Song } from '/@/shared/types/domain-types';
const AlbumColumn = (props: ItemTableListInnerColumn) => { const AlbumColumn = (props: ItemTableListInnerColumn) => {
const row: null | string | undefined = (props.data as (null | string | undefined)[])[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.rowIndex const row: null | string | undefined = rowItem?.[props.columns[props.columnIndex].id];
]?.[props.columns[props.columnIndex].id];
const song = props.data[props.rowIndex] as Song | undefined; const song = rowItem as Song | undefined;
const albumId = song?.albumId; const albumId = song?.albumId;
const albumPath = useMemo(() => { const albumPath = useMemo(() => {
@@ -16,9 +16,8 @@ import { Text } from '/@/shared/components/text/text';
import { LibraryItem, RelatedAlbumArtist, Song } from '/@/shared/types/domain-types'; import { LibraryItem, RelatedAlbumArtist, Song } from '/@/shared/types/domain-types';
const AlbumArtistsColumn = (props: ItemTableListInnerColumn) => { const AlbumArtistsColumn = (props: ItemTableListInnerColumn) => {
const row: RelatedAlbumArtist[] | undefined = ( const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.data as (RelatedAlbumArtist[] | undefined)[] const row: RelatedAlbumArtist[] | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
)[props.rowIndex]?.[props.columns[props.columnIndex].id];
const artists = useMemo(() => { const artists = useMemo(() => {
if (!row) return []; if (!row) return [];
@@ -67,7 +66,8 @@ const AlbumArtistsColumn = (props: ItemTableListInnerColumn) => {
}; };
const SongArtistsColumn = (props: ItemTableListInnerColumn) => { const SongArtistsColumn = (props: ItemTableListInnerColumn) => {
const row: Song | undefined = (props.data as (Song | undefined)[])[props.rowIndex]; const row: Song | undefined =
(props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex]) as Song | undefined;
if (row) { if (row) {
return ( return (
@@ -6,9 +6,8 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const CountColumn = (props: ItemTableListInnerColumn) => { export const CountColumn = (props: ItemTableListInnerColumn) => {
const row: number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: number | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'number') { if (typeof row === 'number') {
return ( return (
@@ -30,9 +30,8 @@ const getDateTooltipLabel = (utcString: string) => {
}; };
export const DateColumn = (props: ItemTableListInnerColumn) => { export const DateColumn = (props: ItemTableListInnerColumn) => {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'string' && row) { if (typeof row === 'string' && row) {
return ( return (
@@ -52,12 +51,11 @@ export const DateColumn = (props: ItemTableListInnerColumn) => {
}; };
export const AbsoluteDateColumn = (props: ItemTableListInnerColumn) => { export const AbsoluteDateColumn = (props: ItemTableListInnerColumn) => {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (props.type === TableColumn.RELEASE_DATE) { if (props.type === TableColumn.RELEASE_DATE) {
const item = (props.data as (any | undefined)[])[props.rowIndex]; const item = rowItem as any;
if (item && 'releaseDate' in item && item.releaseDate) { if (item && 'releaseDate' in item && item.releaseDate) {
const releaseDate = item.releaseDate; const releaseDate = item.releaseDate;
const originalDate = const originalDate =
@@ -115,9 +113,8 @@ export const AbsoluteDateColumn = (props: ItemTableListInnerColumn) => {
}; };
export const RelativeDateColumn = (props: ItemTableListInnerColumn) => { export const RelativeDateColumn = (props: ItemTableListInnerColumn) => {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'string') { if (typeof row === 'string') {
return ( return (
@@ -6,9 +6,8 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const DefaultColumn = (props: ItemTableListInnerColumn) => { export const DefaultColumn = (props: ItemTableListInnerColumn) => {
const row: any | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: any | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'string') { if (typeof row === 'string') {
return <TableColumnTextContainer {...props}>{row}</TableColumnTextContainer>; return <TableColumnTextContainer {...props}>{row}</TableColumnTextContainer>;
@@ -8,9 +8,8 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const DurationColumn = (props: ItemTableListInnerColumn) => { export const DurationColumn = (props: ItemTableListInnerColumn) => {
const row: number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: number | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'number') { if (typeof row === 'number') {
return ( return (
@@ -8,9 +8,8 @@ import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutatio
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
export const FavoriteColumn = (props: ItemTableListInnerColumn) => { export const FavoriteColumn = (props: ItemTableListInnerColumn) => {
const row: boolean | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: boolean | undefined = rowItem?.[props.columns[props.columnIndex].id];
];
const isMutatingCreateFavorite = useIsMutatingCreateFavorite(); const isMutatingCreateFavorite = useIsMutatingCreateFavorite();
const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite(); const isMutatingDeleteFavorite = useIsMutatingDeleteFavorite();
@@ -31,7 +30,7 @@ export const FavoriteColumn = (props: ItemTableListInnerColumn) => {
onClick={(event) => { onClick={(event) => {
event.stopPropagation(); event.stopPropagation();
event.preventDefault(); event.preventDefault();
const item = props.data[props.rowIndex] as ItemListItem; const item = rowItem as ItemListItem;
const rowId = props.internalState.extractRowId(item); const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1; const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onFavorite?.({ props.controls.onFavorite?.({
@@ -18,9 +18,8 @@ import { stringToColor } from '/@/shared/utils/string-to-color';
const MAX_GENRES = 4; const MAX_GENRES = 4;
const GenreBadgeColumn = (props: ItemTableListInnerColumn) => { const GenreBadgeColumn = (props: ItemTableListInnerColumn) => {
const row: Genre[] | undefined = (props.data as (Genre[] | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
'genres' const row: Genre[] | undefined = (rowItem as any)?.genres;
];
const genres = useMemo(() => { const genres = useMemo(() => {
if (!row) return []; if (!row) return [];
@@ -15,9 +15,8 @@ import { Text } from '/@/shared/components/text/text';
import { Genre } from '/@/shared/types/domain-types'; import { Genre } from '/@/shared/types/domain-types';
const GenreColumn = (props: ItemTableListInnerColumn) => { const GenreColumn = (props: ItemTableListInnerColumn) => {
const row: Genre[] | undefined = (props.data as (Genre[] | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: Genre[] | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
const genres = useMemo(() => { const genres = useMemo(() => {
if (!row) return []; if (!row) return [];
@@ -20,8 +20,9 @@ import { Folder, LibraryItem } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
export const ImageColumn = (props: ItemTableListInnerColumn) => { export const ImageColumn = (props: ItemTableListInnerColumn) => {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.id; const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const item = props.data[props.rowIndex] as any; const row: string | undefined = rowItem?.id;
const item = rowItem as any;
const playButtonBehavior = usePlayButtonBehavior(); const playButtonBehavior = usePlayButtonBehavior();
const internalState = (props as any).internalState; const internalState = (props as any).internalState;
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
@@ -113,7 +114,7 @@ export const ImageColumn = (props: ItemTableListInnerColumn) => {
); );
} }
if ((props.data[props.rowIndex] as unknown as Folder)?._itemType === LibraryItem.FOLDER) { if ((rowItem as unknown as Folder)?._itemType === LibraryItem.FOLDER) {
return ( return (
<TableColumnContainer {...props}> <TableColumnContainer {...props}>
<Icon className={styles.folderIcon} icon="folder" size="2xl" /> <Icon className={styles.folderIcon} icon="folder" size="2xl" />
@@ -6,9 +6,8 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const NumericColumn = (props: ItemTableListInnerColumn) => { export const NumericColumn = (props: ItemTableListInnerColumn) => {
const row: number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: number | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'number') { if (typeof row === 'number') {
return <TableColumnTextContainer {...props}>{row}</TableColumnTextContainer>; return <TableColumnTextContainer {...props}>{row}</TableColumnTextContainer>;
@@ -6,9 +6,8 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const PathColumn = (props: ItemTableListInnerColumn) => { export const PathColumn = (props: ItemTableListInnerColumn) => {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'string' && row) { if (typeof row === 'string' && row) {
return ( return (
@@ -24,7 +24,9 @@ export const PlaylistReorderColumn = (props: ItemTableListInnerColumn) => {
const { playlistId } = useParams() as { playlistId?: string }; const { playlistId } = useParams() as { playlistId?: string };
const isHeaderEnabled = !!props.enableHeader; const isHeaderEnabled = !!props.enableHeader;
const isDataRow = isHeaderEnabled ? props.rowIndex > 0 : true; const isDataRow = isHeaderEnabled ? props.rowIndex > 0 : true;
const item = isDataRow ? props.data[props.rowIndex] : null; const item = isDataRow
? (props.getRowItem?.(props.rowIndex) ?? props.data[props.rowIndex])
: null;
const isPlaylistSong = props.itemType === LibraryItem.PLAYLIST_SONG; const isPlaylistSong = props.itemType === LibraryItem.PLAYLIST_SONG;
@@ -153,8 +155,8 @@ export const PlaylistReorderColumn = (props: ItemTableListInnerColumn) => {
const isDragging = props.internalState ? isDraggingState : isDraggingLocal; const isDragging = props.internalState ? isDraggingState : isDraggingLocal;
const getValidDataItems = useCallback(() => { const getValidDataItems = useCallback(() => {
return props.data.filter((d) => d !== null && (d as any).id); return props.internalState.getData().filter((d) => d !== null && (d as any).id);
}, [props.data]); }, [props.internalState]);
const handleMoveUp = useCallback(() => { const handleMoveUp = useCallback(() => {
if (!item || !isDataRow || !isPlaylistSong || !playlistId) { if (!item || !isDataRow || !isPlaylistSong || !playlistId) {
@@ -7,9 +7,8 @@ import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-r
import { Rating } from '/@/shared/components/rating/rating'; import { Rating } from '/@/shared/components/rating/rating';
export const RatingColumn = (props: ItemTableListInnerColumn) => { export const RatingColumn = (props: ItemTableListInnerColumn) => {
const row: null | number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: null | number | undefined = rowItem?.[props.columns[props.columnIndex].id];
];
const isMutatingRating = useIsMutatingRating(); const isMutatingRating = useIsMutatingRating();
@@ -19,7 +18,7 @@ export const RatingColumn = (props: ItemTableListInnerColumn) => {
<Rating <Rating
className={row ? undefined : 'hover-only-flex'} className={row ? undefined : 'hover-only-flex'}
onChange={(rating) => { onChange={(rating) => {
const item = props.data[props.rowIndex] as ItemListItem; const item = rowItem as ItemListItem;
const rowId = props.internalState.extractRowId(item); const rowId = props.internalState.extractRowId(item);
const index = rowId ? props.internalState.findItemIndex(rowId) : -1; const index = rowId ? props.internalState.findItemIndex(rowId) : -1;
props.controls.onRating?.({ props.controls.onRating?.({
@@ -61,7 +61,7 @@ const DefaultRowIndexColumn = (props: ItemTableListInnerColumn) => {
icon="arrowDownS" icon="arrowDownS"
iconProps={{ color: 'muted', size: 'md' }} iconProps={{ color: 'muted', size: 'md' }}
onClick={(e) => { onClick={(e) => {
const item = data[rowIndex] as ItemListItem; const item = (props.getRowItem?.(rowIndex) ?? data[rowIndex]) as ItemListItem;
const rowId = internalState.extractRowId(item); const rowId = internalState.extractRowId(item);
const index = rowId ? internalState.findItemIndex(rowId) : -1; const index = rowId ? internalState.findItemIndex(rowId) : -1;
controls.onExpand?.({ controls.onExpand?.({
@@ -87,7 +87,7 @@ const DefaultRowIndexColumn = (props: ItemTableListInnerColumn) => {
const QueueSongRowIndexColumn = (props: ItemTableListInnerColumn) => { const QueueSongRowIndexColumn = (props: ItemTableListInnerColumn) => {
const status = usePlayerStatus(); const status = usePlayerStatus();
const song = props.data[props.rowIndex] as QueueSong; const song = (props.getRowItem?.(props.rowIndex) ?? props.data[props.rowIndex]) as QueueSong;
const isActive = useIsActiveRow(song?.id, song?._uniqueId); const isActive = useIsActiveRow(song?.id, song?._uniqueId);
const isActiveAndPlaying = isActive && status === PlayerStatus.PLAYING; const isActiveAndPlaying = isActive && status === PlayerStatus.PLAYING;
@@ -7,9 +7,8 @@ import {
import { formatSizeString } from '/@/renderer/utils/format'; import { formatSizeString } from '/@/renderer/utils/format';
export const SizeColumn = (props: ItemTableListInnerColumn) => { export const SizeColumn = (props: ItemTableListInnerColumn) => {
const row: number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: number | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'number') { if (typeof row === 'number') {
return ( return (
@@ -10,9 +10,8 @@ import {
} from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
export const TextColumn = (props: ItemTableListInnerColumn) => { export const TextColumn = (props: ItemTableListInnerColumn) => {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'string' && row) { if (typeof row === 'string' && row) {
return ( return (
@@ -29,13 +29,12 @@ export const TitleColumn = (props: ItemTableListInnerColumn) => {
}; };
function DefaultTitleColumn(props: ItemTableListInnerColumn) { function DefaultTitleColumn(props: ItemTableListInnerColumn) {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = rowItem?.[props.columns[props.columnIndex].id];
];
if (typeof row === 'string') { if (typeof row === 'string') {
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string); const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = props.data[props.rowIndex] as any; const item = rowItem as any;
const titleLinkProps = path const titleLinkProps = path
? { ? {
@@ -71,16 +70,15 @@ function DefaultTitleColumn(props: ItemTableListInnerColumn) {
} }
function QueueSongTitleColumn(props: ItemTableListInnerColumn) { function QueueSongTitleColumn(props: ItemTableListInnerColumn) {
const row: string | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
props.columns[props.columnIndex].id const row: string | undefined = rowItem?.[props.columns[props.columnIndex].id];
];
const song = props.data[props.rowIndex] as QueueSong; const song = rowItem as QueueSong;
const isActive = useIsActiveRow(song?.id, song?._uniqueId); const isActive = useIsActiveRow(song?.id, song?._uniqueId);
if (typeof row === 'string') { if (typeof row === 'string') {
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string); const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = props.data[props.rowIndex] as any; const item = rowItem as any;
const titleLinkProps = path const titleLinkProps = path
? { ? {
@@ -26,8 +26,9 @@ import { Folder, LibraryItem, QueueSong } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => { export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.id; const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const item = props.data[props.rowIndex] as any; const row: object | undefined = (rowItem as any)?.id;
const item = rowItem as any;
const internalState = (props as any).internalState; const internalState = (props as any).internalState;
const playButtonBehavior = usePlayButtonBehavior(); const playButtonBehavior = usePlayButtonBehavior();
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
@@ -76,9 +77,9 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
if (item && 'name' in item && 'imageUrl' in item && 'artists' in item) { if (item && 'name' in item && 'imageUrl' in item && 'artists' in item) {
const rowHeight = props.getRowHeight(props.rowIndex, props); const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string); const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = props.data[props.rowIndex] as any; const item = rowItem as any;
const titleLinkProps = path const titleLinkProps = path
? { ? {
component: Link, component: Link,
@@ -156,10 +157,11 @@ export const DefaultTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
}; };
export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) => { export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) => {
const row: object | undefined = (props.data as (any | undefined)[])[props.rowIndex]; const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const row: object | undefined = rowItem as any;
const song = props.data[props.rowIndex] as QueueSong; const song = rowItem as QueueSong;
const item = props.data[props.rowIndex] as any; const item = rowItem as any;
const internalState = (props as any).internalState; const internalState = (props as any).internalState;
const playButtonBehavior = usePlayButtonBehavior(); const playButtonBehavior = usePlayButtonBehavior();
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
@@ -209,9 +211,9 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
if (row && 'name' in row && 'imageUrl' in row && 'artists' in row) { if (row && 'name' in row && 'imageUrl' in row && 'artists' in row) {
const rowHeight = props.getRowHeight(props.rowIndex, props); const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string); const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = props.data[props.rowIndex] as any; const item = rowItem as any;
const titleLinkProps = path const titleLinkProps = path
? { ? {
@@ -306,11 +308,11 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
); );
} }
if ((props.data[props.rowIndex] as unknown as Folder)?._itemType === LibraryItem.FOLDER) { if ((rowItem as unknown as Folder)?._itemType === LibraryItem.FOLDER) {
const rowHeight = props.getRowHeight(props.rowIndex, props); const rowHeight = props.getRowHeight(props.rowIndex, props);
const path = getTitlePath(props.itemType, (props.data[props.rowIndex] as any).id as string); const path = getTitlePath(props.itemType, (rowItem as any).id as string);
const item = props.data[props.rowIndex] as any; const item = rowItem as any;
const textStyles = isActive ? { color: 'var(--theme-colors-primary)' } : {}; const textStyles = isActive ? { color: 'var(--theme-colors-primary)' } : {};
const titleLinkProps = path const titleLinkProps = path
@@ -322,7 +324,7 @@ export const QueueSongTitleCombinedColumn = (props: ItemTableListInnerColumn) =>
} }
: {}; : {};
const title = (props.data[props.rowIndex] as unknown as Folder)?.name; const title = (rowItem as unknown as Folder)?.name;
return ( return (
<TableColumnContainer <TableColumnContainer
@@ -7,7 +7,8 @@ import {
import { SEPARATOR_STRING } from '/@/shared/api/utils'; import { SEPARATOR_STRING } from '/@/shared/api/utils';
export const YearColumn = (props: ItemTableListInnerColumn) => { export const YearColumn = (props: ItemTableListInnerColumn) => {
const item = (props.data as (any | undefined)[])[props.rowIndex]; const rowItem = props.getRowItem?.(props.rowIndex) ?? (props.data as any[])[props.rowIndex];
const item = rowItem as any;
if (item && 'releaseYear' in item && item.releaseYear !== null) { if (item && 'releaseYear' in item && item.releaseYear !== null) {
const releaseYear = item.releaseYear; const releaseYear = item.releaseYear;
@@ -29,9 +30,7 @@ export const YearColumn = (props: ItemTableListInnerColumn) => {
} }
} }
const row: number | undefined = (props.data as (any | undefined)[])[props.rowIndex]?.[ const row: number | undefined = (rowItem as any)?.[props.columns[props.columnIndex].id];
props.columns[props.columnIndex].id
];
if (row === null) { if (row === null) {
return <ColumnNullFallback {...props} />; return <ColumnNullFallback {...props} />;
@@ -17,11 +17,14 @@ interface UseTableKeyboardNavigationProps {
enableHeader: boolean; enableHeader: boolean;
enableSelection: boolean; enableSelection: boolean;
extractRowId: (item: unknown) => string | undefined; extractRowId: (item: unknown) => string | undefined;
getItem?: (index: number) => undefined | unknown;
getItemIndex?: (rowId: string) => number | undefined;
getStateItem: (item: any) => ItemListStateItemWithRequiredProperties | null; getStateItem: (item: any) => ItemListStateItemWithRequiredProperties | null;
hasRequiredStateItemProperties: ( hasRequiredStateItemProperties: (
item: unknown, item: unknown,
) => item is ItemListStateItemWithRequiredProperties; ) => item is ItemListStateItemWithRequiredProperties;
internalState: ItemListStateActions; internalState: ItemListStateActions;
itemCount?: number;
itemType: LibraryItem; itemType: LibraryItem;
parsedColumns: TableItemProps['columns']; parsedColumns: TableItemProps['columns'];
pinnedRightColumnCount: number; pinnedRightColumnCount: number;
@@ -45,9 +48,12 @@ export const useTableKeyboardNavigation = ({
enableHeader, enableHeader,
enableSelection, enableSelection,
extractRowId, extractRowId,
getItem,
getItemIndex,
getStateItem, getStateItem,
hasRequiredStateItemProperties, hasRequiredStateItemProperties,
internalState, internalState,
itemCount,
itemType, itemType,
parsedColumns, parsedColumns,
pinnedRightColumnCount, pinnedRightColumnCount,
@@ -69,23 +75,26 @@ export const useTableKeyboardNavigation = ({
const selected = internalState.getSelected(); const selected = internalState.getSelected();
const validSelected = selected.filter(hasRequiredStateItemProperties); const validSelected = selected.filter(hasRequiredStateItemProperties);
let currentIndex = -1; let currentIndex = -1;
const totalCount = itemCount ?? data.length;
if (validSelected.length > 0) { if (validSelected.length > 0) {
const lastSelected = validSelected[validSelected.length - 1]; const lastSelected = validSelected[validSelected.length - 1];
currentIndex = data.findIndex( const rowId = extractRowId(lastSelected);
(d) => extractRowId(d) === extractRowId(lastSelected), if (rowId) {
); currentIndex =
getItemIndex?.(rowId) ?? data.findIndex((d) => extractRowId(d) === rowId);
}
} }
let newIndex = 0; let newIndex = 0;
if (currentIndex !== -1) { if (currentIndex !== -1) {
newIndex = newIndex =
e.key === 'ArrowDown' e.key === 'ArrowDown'
? Math.min(currentIndex + 1, data.length - 1) ? Math.min(currentIndex + 1, totalCount - 1)
: Math.max(currentIndex - 1, 0); : Math.max(currentIndex - 1, 0);
} }
const newItem: any = data[newIndex]; const newItem: any = getItem ? getItem(newIndex) : data[newIndex];
if (!newItem) return; if (!newItem) return;
const newItemListItem = getStateItem(newItem); const newItemListItem = getStateItem(newItem);
@@ -118,7 +127,7 @@ export const useTableKeyboardNavigation = ({
cellPadding, cellPadding,
columns: parsedColumns, columns: parsedColumns,
controls: {} as ItemControls, controls: {} as ItemControls,
data: enableHeader ? [null, ...data] : data, data: enableHeader ? [null] : [],
enableAlternateRowColors: false, enableAlternateRowColors: false,
enableExpansion: false, enableExpansion: false,
enableHeader, enableHeader,
@@ -127,6 +136,12 @@ export const useTableKeyboardNavigation = ({
enableSelection, enableSelection,
enableVerticalBorders: false, enableVerticalBorders: false,
getRowHeight: () => DEFAULT_ROW_HEIGHT, getRowHeight: () => DEFAULT_ROW_HEIGHT,
getRowItem: (rowIndex: number) => {
if (!getItem) return undefined;
if (enableHeader && rowIndex === 0) return null;
const dataIndex = enableHeader ? rowIndex - 1 : rowIndex;
return getItem(dataIndex);
},
internalState: {} as ItemListStateActions, internalState: {} as ItemListStateActions,
itemType, itemType,
playerContext, playerContext,
@@ -174,6 +189,8 @@ export const useTableKeyboardNavigation = ({
calculateScrollTopForIndex, calculateScrollTopForIndex,
cellPadding, cellPadding,
data, data,
getItem,
getItemIndex,
DEFAULT_ROW_HEIGHT, DEFAULT_ROW_HEIGHT,
enableHeader, enableHeader,
enableSelection, enableSelection,
@@ -181,6 +198,7 @@ export const useTableKeyboardNavigation = ({
getStateItem, getStateItem,
hasRequiredStateItemProperties, hasRequiredStateItemProperties,
internalState, internalState,
itemCount,
itemType, itemType,
parsedColumns, parsedColumns,
pinnedRightColumnCount, pinnedRightColumnCount,
@@ -84,7 +84,9 @@ export const ItemTableListColumn = (props: ItemTableListColumn) => {
const isHeaderEnabled = !!props.enableHeader; const isHeaderEnabled = !!props.enableHeader;
const isDataRow = isHeaderEnabled ? props.rowIndex > 0 : true; const isDataRow = isHeaderEnabled ? props.rowIndex > 0 : true;
const item = isDataRow ? props.data[props.rowIndex] : null; const item = isDataRow
? (props.getRowItem?.(props.rowIndex) ?? props.data[props.rowIndex])
: null;
const shouldEnableDrag = !!props.enableDrag && isDataRow && !!item; const shouldEnableDrag = !!props.enableDrag && isDataRow && !!item;
const itemType = (item as unknown as { _itemType?: LibraryItem })?._itemType || props.itemType; const itemType = (item as unknown as { _itemType?: LibraryItem })?._itemType || props.itemType;
@@ -585,7 +587,9 @@ export const TableColumnTextContainer = (
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true; const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex; const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex;
const item = isDataRow ? props.data[props.rowIndex] : null; const item = isDataRow
? (props.getRowItem?.(props.rowIndex) ?? props.data[props.rowIndex])
: null;
const itemRowId = const itemRowId =
item && typeof item === 'object' && 'id' in item item && typeof item === 'object' && 'id' in item
? props.internalState.extractRowId(item) ? props.internalState.extractRowId(item)
@@ -736,7 +740,9 @@ export const TableColumnContainer = (
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true; const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex; const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex;
const item = isDataRow ? props.data[props.rowIndex] : null; const item = isDataRow
? (props.getRowItem?.(props.rowIndex) ?? props.data[props.rowIndex])
: null;
const itemRowId = const itemRowId =
item && typeof item === 'object' && 'id' in item item && typeof item === 'object' && 'id' in item
? props.internalState.extractRowId(item) ? props.internalState.extractRowId(item)
@@ -116,6 +116,7 @@ interface VirtualizedTableGridProps {
enableScrollShadow: boolean; enableScrollShadow: boolean;
enableSelection: boolean; enableSelection: boolean;
enableVerticalBorders: boolean; enableVerticalBorders: boolean;
getItem?: (index: number) => undefined | unknown;
getRowHeight: (index: number, cellProps: TableItemProps) => number; getRowHeight: (index: number, cellProps: TableItemProps) => number;
groups?: TableGroupHeader[]; groups?: TableGroupHeader[];
headerHeight: number; headerHeight: number;
@@ -159,6 +160,7 @@ const VirtualizedTableGrid = ({
enableScrollShadow, enableScrollShadow,
enableSelection, enableSelection,
enableVerticalBorders, enableVerticalBorders,
getItem,
getRowHeight, getRowHeight,
groups, groups,
headerHeight, headerHeight,
@@ -284,6 +286,44 @@ const VirtualizedTableGrid = ({
[enableHeader, groupHeaderInfoByRowIndex, groupHeaderRowIndexes, groups], [enableHeader, groupHeaderInfoByRowIndex, groupHeaderRowIndexes, groups],
); );
const getRowItem = useCallback(
(rowIndex: number): null | undefined | unknown => {
// Header row
if (enableHeader && rowIndex === 0) return null;
// Group header rows are represented as null in the row model
if (groupHeaderInfoByRowIndex?.has(rowIndex)) return null;
if (!groups || groups.length === 0) {
const dataIndex = enableHeader ? rowIndex - 1 : rowIndex;
return getItem ? getItem(dataIndex) : dataWithGroups[rowIndex];
}
const headerOffset = enableHeader ? 1 : 0;
// Count group header rows strictly before this rowIndex (upperBound on groupHeaderRowIndexes)
let lo = 0;
let hi = groupHeaderRowIndexes.length;
const target = rowIndex - 1;
while (lo < hi) {
const mid = (lo + hi) >>> 1;
if (groupHeaderRowIndexes[mid] <= target) lo = mid + 1;
else hi = mid;
}
const groupHeadersBefore = lo;
const dataIndex = rowIndex - headerOffset - groupHeadersBefore;
return getItem ? getItem(dataIndex) : undefined;
},
[
dataWithGroups,
enableHeader,
getItem,
groupHeaderInfoByRowIndex,
groupHeaderRowIndexes,
groups,
],
);
const stableConfigProps = useMemo( const stableConfigProps = useMemo(
() => ({ () => ({
cellPadding, cellPadding,
@@ -317,6 +357,7 @@ const VirtualizedTableGrid = ({
data: dataWithGroups, data: dataWithGroups,
getAdjustedRowIndex, getAdjustedRowIndex,
getGroupRenderData, getGroupRenderData,
getRowItem,
groupHeaderInfoByRowIndex, groupHeaderInfoByRowIndex,
pinnedLeftColumnCount, pinnedLeftColumnCount,
pinnedLeftColumnWidths, pinnedLeftColumnWidths,
@@ -327,6 +368,7 @@ const VirtualizedTableGrid = ({
[ [
calculatedColumnWidths, calculatedColumnWidths,
dataWithGroups, dataWithGroups,
getRowItem,
getAdjustedRowIndex, getAdjustedRowIndex,
getGroupRenderData, getGroupRenderData,
groupHeaderInfoByRowIndex, groupHeaderInfoByRowIndex,
@@ -724,6 +766,7 @@ export interface TableItemProps {
getAdjustedRowIndex?: (rowIndex: number) => number; getAdjustedRowIndex?: (rowIndex: number) => number;
getGroupRenderData?: () => unknown[]; getGroupRenderData?: () => unknown[];
getRowHeight: (index: number, cellProps: TableItemProps) => number; getRowHeight: (index: number, cellProps: TableItemProps) => number;
getRowItem?: (rowIndex: number) => null | undefined | unknown;
groupHeaderInfoByRowIndex?: Map<number, { groupIndex: number; startDataIndex: number }>; groupHeaderInfoByRowIndex?: Map<number, { groupIndex: number; startDataIndex: number }>;
groups?: TableGroupHeader[]; groups?: TableGroupHeader[];
internalState: ItemListStateActions; internalState: ItemListStateActions;
@@ -759,6 +802,8 @@ interface ItemTableListProps {
enableStickyGroupRows?: boolean; enableStickyGroupRows?: boolean;
enableStickyHeader?: boolean; enableStickyHeader?: boolean;
enableVerticalBorders?: boolean; enableVerticalBorders?: boolean;
getItem?: (index: number) => undefined | unknown;
getItemIndex?: (rowId: string) => number | undefined;
getRowId?: ((item: unknown) => string) | string; getRowId?: ((item: unknown) => string) | string;
groups?: TableGroupHeader[]; groups?: TableGroupHeader[];
headerHeight?: number; headerHeight?: number;
@@ -767,6 +812,7 @@ interface ItemTableListProps {
to: number; to: number;
type: 'index' | 'offset'; type: 'index' | 'offset';
}; };
itemCount?: number;
itemType: LibraryItem; itemType: LibraryItem;
onColumnReordered?: ( onColumnReordered?: (
columnIdFrom: TableColumn, columnIdFrom: TableColumn,
@@ -802,10 +848,13 @@ const BaseItemTableList = ({
enableStickyGroupRows = false, enableStickyGroupRows = false,
enableStickyHeader = false, enableStickyHeader = false,
enableVerticalBorders = false, enableVerticalBorders = false,
getItem,
getItemIndex,
getRowId, getRowId,
groups, groups,
headerHeight = 40, headerHeight = 40,
initialTop, initialTop,
itemCount,
itemType, itemType,
onColumnReordered, onColumnReordered,
onColumnResized, onColumnResized,
@@ -818,7 +867,8 @@ const BaseItemTableList = ({
startRowIndex, startRowIndex,
}: ItemTableListProps) => { }: ItemTableListProps) => {
const tableId = useId(); const tableId = useId();
const totalItemCount = enableHeader ? data.length + 1 : data.length; const baseItemCount = itemCount ?? data.length;
const totalItemCount = enableHeader ? baseItemCount + 1 : baseItemCount;
const [centerContainerWidth, setCenterContainerWidth] = useState(0); const [centerContainerWidth, setCenterContainerWidth] = useState(0);
const [totalContainerWidth, setTotalContainerWidth] = useState(0); const [totalContainerWidth, setTotalContainerWidth] = useState(0);
@@ -836,12 +886,29 @@ const BaseItemTableList = ({
}); });
const playerContext = usePlayer(); const playerContext = usePlayer();
const { dataWithGroups, groupHeaderRowCount } = useTableRowModel({ const {
dataWithGroups: dataWithGroupsFromModel,
groupHeaderRowCount: groupHeaderRowCountFromModel,
} = useTableRowModel({
data, data,
enableHeader, enableHeader,
groups, groups,
}); });
const shouldUseAccessor = typeof getItem === 'function' && typeof itemCount === 'number';
// Avoid constructing a massive row-model array for infinite lists.
// Cell renderers use `getRowItem` accessor when provided.
const dataWithGroups = useMemo<(null | unknown)[]>(() => {
if (!shouldUseAccessor) return dataWithGroupsFromModel;
return enableHeader ? [null] : [];
}, [dataWithGroupsFromModel, enableHeader, shouldUseAccessor]);
const groupHeaderRowCount = useMemo(() => {
if (!shouldUseAccessor) return groupHeaderRowCountFromModel;
return groups?.length ? groups.length : 0;
}, [groupHeaderRowCountFromModel, groups, shouldUseAccessor]);
const pinnedRowCount = enableHeader ? 1 : 0; const pinnedRowCount = enableHeader ? 1 : 0;
// Group headers are inserted at specific indexes, so they add to the total row count // Group headers are inserted at specific indexes, so they add to the total row count
@@ -1007,8 +1074,9 @@ const BaseItemTableList = ({
}); });
const getDataFn = useCallback(() => { const getDataFn = useCallback(() => {
return dataWithGroups; // For infinite lists, callers should pass `data` as the currently loaded items only.
}, [dataWithGroups]); return data;
}, [data]);
const extractRowId = useMemo(() => createExtractRowId(getRowId), [getRowId]); const extractRowId = useMemo(() => createExtractRowId(getRowId), [getRowId]);
@@ -1041,9 +1109,12 @@ const BaseItemTableList = ({
enableHeader, enableHeader,
enableSelection, enableSelection,
extractRowId, extractRowId,
getItem,
getItemIndex,
getStateItem, getStateItem,
hasRequiredStateItemProperties, hasRequiredStateItemProperties,
internalState, internalState,
itemCount: baseItemCount,
itemType, itemType,
parsedColumns, parsedColumns,
pinnedRightColumnCount, pinnedRightColumnCount,
@@ -1499,6 +1570,7 @@ const BaseItemTableList = ({
enableScrollShadow={enableScrollShadow} enableScrollShadow={enableScrollShadow}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getRowHeight={getRowHeight} getRowHeight={getRowHeight}
groups={groups} groups={groups}
headerHeight={headerHeight} headerHeight={headerHeight}
@@ -36,7 +36,8 @@ export const AlbumListInfiniteGrid = ({
const listQueryFn = api.controller.getAlbumList; const listQueryFn = api.controller.getAlbumList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM, eventKey: ItemListKey.ALBUM,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
@@ -54,13 +55,17 @@ export const AlbumListInfiniteGrid = ({
return ( return (
<ItemGridList <ItemGridList
data={data} data={loadedItems}
dataVersion={dataVersion}
enableExpansion enableExpansion
gap={gap} gap={gap}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -43,7 +43,8 @@ export const AlbumListInfiniteTable = ({
const listQueryFn = api.controller.getAlbumList; const listQueryFn = api.controller.getAlbumList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM, eventKey: ItemListKey.ALBUM,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
@@ -70,16 +71,19 @@ export const AlbumListInfiniteTable = ({
autoFitColumns={autoFitColumns} autoFitColumns={autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
data={data} data={loadedItems}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}
enableRowHoverHighlight={enableRowHoverHighlight} enableRowHoverHighlight={enableRowHoverHighlight}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}
@@ -37,7 +37,8 @@ export const AlbumArtistListInfiniteGrid = ({
const listQueryFn = api.controller.getAlbumArtistList; const listQueryFn = api.controller.getAlbumArtistList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM_ARTIST, eventKey: ItemListKey.ALBUM_ARTIST,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
@@ -55,12 +56,16 @@ export const AlbumArtistListInfiniteGrid = ({
return ( return (
<ItemGridList <ItemGridList
data={data} data={loadedItems}
dataVersion={dataVersion}
gap={gap} gap={gap}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.ALBUM_ARTIST} itemType={LibraryItem.ALBUM_ARTIST}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -44,7 +44,8 @@ export const AlbumArtistListInfiniteTable = ({
const listQueryFn = api.controller.getAlbumArtistList; const listQueryFn = api.controller.getAlbumArtistList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ALBUM_ARTIST, eventKey: ItemListKey.ALBUM_ARTIST,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
@@ -71,17 +72,20 @@ export const AlbumArtistListInfiniteTable = ({
autoFitColumns={autoFitColumns} autoFitColumns={autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
data={data} data={loadedItems}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableExpansion={false} enableExpansion={false}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}
enableRowHoverHighlight={enableRowHoverHighlight} enableRowHoverHighlight={enableRowHoverHighlight}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemType={LibraryItem.ALBUM_ARTIST} itemType={LibraryItem.ALBUM_ARTIST}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}
@@ -36,7 +36,8 @@ export const ArtistListInfiniteGrid = ({
const listQueryFn = api.controller.getArtistList; const listQueryFn = api.controller.getArtistList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ARTIST, eventKey: ItemListKey.ARTIST,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.ARTIST, itemType: LibraryItem.ARTIST,
@@ -54,12 +55,16 @@ export const ArtistListInfiniteGrid = ({
return ( return (
<ItemGridList <ItemGridList
data={data} data={loadedItems}
dataVersion={dataVersion}
gap={gap} gap={gap}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.ARTIST} itemType={LibraryItem.ARTIST}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -43,7 +43,8 @@ export const ArtistListInfiniteTable = ({
const listQueryFn = api.controller.getArtistList; const listQueryFn = api.controller.getArtistList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.ARTIST, eventKey: ItemListKey.ARTIST,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.ARTIST, itemType: LibraryItem.ARTIST,
@@ -70,17 +71,20 @@ export const ArtistListInfiniteTable = ({
autoFitColumns={autoFitColumns} autoFitColumns={autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
data={data} data={loadedItems}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableExpansion={false} enableExpansion={false}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}
enableRowHoverHighlight={enableRowHoverHighlight} enableRowHoverHighlight={enableRowHoverHighlight}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemType={LibraryItem.ARTIST} itemType={LibraryItem.ARTIST}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}
@@ -36,7 +36,8 @@ export const GenreListInfiniteGrid = ({
const listQueryFn = api.controller.getGenreList; const listQueryFn = api.controller.getGenreList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.GENRE, eventKey: ItemListKey.GENRE,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.GENRE, itemType: LibraryItem.GENRE,
@@ -54,12 +55,16 @@ export const GenreListInfiniteGrid = ({
return ( return (
<ItemGridList <ItemGridList
data={data} data={loadedItems}
dataVersion={dataVersion}
gap={gap} gap={gap}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.GENRE} itemType={LibraryItem.GENRE}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -43,7 +43,8 @@ export const GenreListInfiniteTable = ({
const listQueryFn = api.controller.getGenreList; const listQueryFn = api.controller.getGenreList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.GENRE, eventKey: ItemListKey.GENRE,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.GENRE, itemType: LibraryItem.GENRE,
@@ -70,17 +71,20 @@ export const GenreListInfiniteTable = ({
autoFitColumns={autoFitColumns} autoFitColumns={autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
data={data} data={loadedItems}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableExpansion={false} enableExpansion={false}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}
enableRowHoverHighlight={enableRowHoverHighlight} enableRowHoverHighlight={enableRowHoverHighlight}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemType={LibraryItem.GENRE} itemType={LibraryItem.GENRE}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}
@@ -36,7 +36,8 @@ export const PlaylistListInfiniteGrid = ({
const listQueryFn = api.controller.getPlaylistList; const listQueryFn = api.controller.getPlaylistList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.PLAYLIST, eventKey: ItemListKey.PLAYLIST,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.PLAYLIST, itemType: LibraryItem.PLAYLIST,
@@ -54,12 +55,16 @@ export const PlaylistListInfiniteGrid = ({
return ( return (
<ItemGridList <ItemGridList
data={data} data={loadedItems}
dataVersion={dataVersion}
gap={gap} gap={gap}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.PLAYLIST} itemType={LibraryItem.PLAYLIST}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -43,7 +43,8 @@ export const PlaylistListInfiniteTable = ({
const listQueryFn = api.controller.getPlaylistList; const listQueryFn = api.controller.getPlaylistList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.PLAYLIST, eventKey: ItemListKey.PLAYLIST,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.PLAYLIST, itemType: LibraryItem.PLAYLIST,
@@ -70,16 +71,19 @@ export const PlaylistListInfiniteTable = ({
autoFitColumns={autoFitColumns} autoFitColumns={autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
data={data} data={loadedItems}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}
enableRowHoverHighlight={enableRowHoverHighlight} enableRowHoverHighlight={enableRowHoverHighlight}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemType={LibraryItem.PLAYLIST} itemType={LibraryItem.PLAYLIST}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}
@@ -31,7 +31,8 @@ export const SongListInfiniteGrid = ({
const listQueryFn = api.controller.getSongList; const listQueryFn = api.controller.getSongList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { dataVersion, getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.SONG, eventKey: ItemListKey.SONG,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
@@ -49,12 +50,16 @@ export const SongListInfiniteGrid = ({
return ( return (
<ItemGridList <ItemGridList
data={data} data={loadedItems}
dataVersion={dataVersion}
gap={gap} gap={gap}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemsPerRow={itemsPerRow} itemsPerRow={itemsPerRow}
itemType={LibraryItem.SONG} itemType={LibraryItem.SONG}
onRangeChanged={onRangeChanged} onRangeChanged={onRangeChanged}
@@ -39,7 +39,8 @@ export const SongListInfiniteTable = ({
const listQueryFn = api.controller.getSongList; const listQueryFn = api.controller.getSongList;
const { data, onRangeChanged } = useItemListInfiniteLoader({ const { getItem, getItemIndex, itemCount, loadedItems, onRangeChanged } =
useItemListInfiniteLoader({
eventKey: ItemListKey.SONG, eventKey: ItemListKey.SONG,
itemsPerPage, itemsPerPage,
itemType: LibraryItem.SONG, itemType: LibraryItem.SONG,
@@ -69,17 +70,20 @@ export const SongListInfiniteTable = ({
autoFitColumns={autoFitColumns} autoFitColumns={autoFitColumns}
CellComponent={ItemTableListColumn} CellComponent={ItemTableListColumn}
columns={columns} columns={columns}
data={data} data={loadedItems}
enableAlternateRowColors={enableAlternateRowColors} enableAlternateRowColors={enableAlternateRowColors}
enableExpansion={false} enableExpansion={false}
enableHorizontalBorders={enableHorizontalBorders} enableHorizontalBorders={enableHorizontalBorders}
enableRowHoverHighlight={enableRowHoverHighlight} enableRowHoverHighlight={enableRowHoverHighlight}
enableSelection={enableSelection} enableSelection={enableSelection}
enableVerticalBorders={enableVerticalBorders} enableVerticalBorders={enableVerticalBorders}
getItem={getItem}
getItemIndex={getItemIndex}
initialTop={{ initialTop={{
to: scrollOffset ?? 0, to: scrollOffset ?? 0,
type: 'offset', type: 'offset',
}} }}
itemCount={itemCount}
itemType={LibraryItem.SONG} itemType={LibraryItem.SONG}
onColumnReordered={handleColumnReordered} onColumnReordered={handleColumnReordered}
onColumnResized={handleColumnResized} onColumnResized={handleColumnResized}