@@ -242,13 +224,11 @@ const DefaultItemCard = ({
};
const PosterItemCard = ({
+ controls,
data,
imageUrl,
isRound,
itemType,
- onClick,
- onItemExpand,
- onItemSelect,
rows,
setShowControls,
showControls,
@@ -259,7 +239,7 @@ const PosterItemCard = ({
onClick?.(e, data, itemType)}
+ onClick={(e) => controls?.onClick?.(data, itemType, e)}
onMouseEnter={() => withControls && setShowControls(true)}
onMouseLeave={() => withControls && setShowControls(false)}
>
@@ -268,7 +248,14 @@ const PosterItemCard = ({
src={imageUrl}
/>
- {withControls && showControls && }
+ {withControls && showControls && (
+
+ )}
diff --git a/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts b/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts
index 9e156e48f..392761700 100644
--- a/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts
+++ b/src/renderer/components/item-list/helpers/item-list-infinite-loader.ts
@@ -6,6 +6,7 @@ import { queryKeys } from '/@/renderer/api/query-keys';
import { getServerById } from '/@/renderer/store';
export interface InfiniteListProps
{
+ itemsPerPage?: number;
query: Omit;
serverId: string;
}
diff --git a/src/renderer/components/item-list/item-grid-list/item-grid-list.module.css b/src/renderer/components/item-list/item-grid-list/item-grid-list.module.css
index 83e204f5e..05e88f296 100644
--- a/src/renderer/components/item-list/item-grid-list/item-grid-list.module.css
+++ b/src/renderer/components/item-list/item-grid-list/item-grid-list.module.css
@@ -6,17 +6,6 @@
padding: 0 var(--theme-spacing-md);
}
-.auto-sizer {
- width: 100% !important;
- height: 100% !important;
-}
-
-.list-container {
- display: flex;
- width: 100%;
- height: 100%;
-}
-
.grid-list-container {
width: 100%;
padding: 0 var(--theme-spacing-md);
@@ -35,11 +24,6 @@
overflow: hidden;
}
-.full-width-content {
- grid-column: 1 / -1;
- box-shadow: 0 4px 6px -1px rgb(0 0 0 / 10%);
-}
-
.list-expanded-container {
width: 100%;
height: 100%;
diff --git a/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx b/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx
index 9ddde325b..3a46eea71 100644
--- a/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx
+++ b/src/renderer/components/item-list/item-grid-list/item-grid-list.tsx
@@ -4,7 +4,6 @@ import { AnimatePresence, motion, Variants } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import {
CSSProperties,
- MouseEvent,
Ref,
UIEvent,
useCallback,
@@ -21,46 +20,42 @@ import styles from './item-grid-list.module.css';
import { getDataRowsCount, ItemCard } from '/@/renderer/components/item-card/item-card';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
import {
+ ItemListItem,
ItemListStateActions,
useItemListState,
} from '/@/renderer/components/item-list/helpers/item-list-state';
+import { ItemListHandle } from '/@/renderer/components/item-list/types';
import { LibraryItem } from '/@/shared/types/domain-types';
+import { Play } from '/@/shared/types/types';
+
+export interface GridItemProps {
+ columns: number;
+ data: any[];
+ enableExpansion?: boolean;
+ enableSelection?: boolean;
+ internalState: ItemListStateActions;
+ itemType: LibraryItem;
+}
export interface ItemGridListProps {
data: unknown[];
enableExpansion?: boolean;
enableSelection?: boolean;
- initialTopMostItemIndex?:
- | number
- | {
- align: 'center' | 'end' | 'start';
- behavior: 'auto' | 'smooth';
- index: number;
- offset?: number;
- };
+ initialTop?: {
+ behavior?: 'auto' | 'smooth';
+ to: number;
+ type: 'index' | 'offset';
+ };
itemType: LibraryItem;
- onEndReached?: (index: number) => void;
- onItemClick?: (item: unknown, index: number) => void;
- onItemContextMenu?: (item: unknown, index: number) => void;
- onItemDoubleClick?: (item: unknown, index: number) => void;
+ onEndReached?: (index: number, handle: ItemListHandle) => void;
onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void;
onScroll?: (e: UIEvent) => void;
- onScrollEnd?: () => void;
- onStartReached?: (index: number) => void;
+ onScrollEnd?: (offset: number, handle: ItemListHandle) => void;
+ onStartReached?: (index: number, handle: ItemListHandle) => void;
ref: Ref;
totalItemCount?: number;
}
-interface ItemContext {
- enableExpansion?: boolean;
- enableSelection?: boolean;
- internalState: ItemListStateActions;
- itemType: LibraryItem;
- onItemClick?: (item: unknown, index: number) => void;
- onItemContextMenu?: (item: unknown, index: number) => void;
- onItemDoubleClick?: (item: unknown, index: number) => void;
-}
-
const expandedAnimationVariants: Variants = {
hidden: {
height: 0,
@@ -79,15 +74,10 @@ export const ItemGridList = ({
data,
enableExpansion = false,
enableSelection = false,
- initialTopMostItemIndex = 0,
itemType,
onEndReached,
- onItemClick,
- onItemContextMenu,
- onItemDoubleClick,
onRangeChanged,
onScroll,
- onScrollEnd,
onStartReached,
totalItemCount = 0,
}: ItemGridListProps) => {
@@ -133,19 +123,6 @@ export const ItemGridList = ({
const hasExpanded = internalState.hasExpanded();
- const handleExpand = useCallback(
- (_e: MouseEvent, item: unknown, itemType: LibraryItem) => {
- if (item && typeof item === 'object' && 'id' in item && 'serverId' in item) {
- internalState.toggleExpanded({
- id: item.id as string,
- itemType: itemType,
- serverId: item.serverId as string,
- });
- }
- },
- [internalState],
- );
-
const handleScroll = useCallback(
(e: UIEvent) => {
onScroll?.(e);
@@ -218,14 +195,21 @@ export const ItemGridList = ({
const endRow = visibleRows.stopIndex;
if (startRow === 0) {
- onStartReached?.(startRow);
+ onStartReached?.(startRow, itemGridRef.current ?? (undefined as any));
}
if (endRow >= totalRows) {
- onEndReached?.(endRow);
+ onEndReached?.(endRow, itemGridRef.current ?? (undefined as any));
}
}
},
- [onEndReached, onRangeChanged, onStartReached, totalItemCount, tableMeta?.columnCount],
+ [
+ onRangeChanged,
+ tableMeta?.columnCount,
+ onStartReached,
+ onEndReached,
+ totalItemCount,
+ itemGridRef,
+ ],
);
const elements = useMemo(() => {
@@ -253,6 +237,15 @@ export const ItemGridList = ({
);
}, [tableMeta, data]);
+ const itemProps: GridItemProps = {
+ columns: tableMeta?.columnCount || 0,
+ data: elements,
+ enableExpansion,
+ enableSelection,
+ internalState,
+ itemType,
+ };
+
return (
{hasExpanded && (
@@ -300,19 +288,16 @@ export const ItemGridList = ({
);
};
-function RowComponent({
+const ListComponent = ({
columns,
data,
- handleExpand,
+ enableExpansion,
+ enableSelection,
index,
+ internalState,
itemType,
style,
-}: RowComponentProps<{
- columns: number;
- data: any[];
- handleExpand: (e: MouseEvent, item: unknown, itemType: LibraryItem) => void;
- itemType: LibraryItem;
-}>) {
+}: RowComponentProps) => {
return (
{data[index].map((d) => (
@@ -322,14 +307,104 @@ function RowComponent({
style={{ '--columns': columns } as CSSProperties}
>
{
+ return handleItemClick(item, itemType, internalState);
+ }
+ : undefined,
+ onDoubleClick: (item, itemType) => {
+ return handleItemDoubleClick(item, itemType, internalState);
+ },
+ onFavorite: (item, itemType) => {
+ return handleItemFavorite(item, itemType, internalState);
+ },
+ onItemExpand: enableExpansion
+ ? (item, itemType) => {
+ return handleItemExpand(item, itemType, internalState);
+ }
+ : undefined,
+ onMore: (item, itemType) => {
+ return handleItemMore(item, itemType, internalState);
+ },
+ onPlay: (item, itemType, playType) => {
+ return handleItemPlay(item, itemType, playType, internalState);
+ },
+ onRating: (item, itemType) => {
+ return handleItemRating(item, itemType, internalState);
+ },
+ }}
data={d.data}
itemType={itemType}
- onClick={(e, item, itemType) => handleExpand(e, item, itemType)}
- type="poster"
withControls
/>
))}
);
-}
+};
+
+const handleItemClick = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemClick', item, itemType, internalState);
+};
+
+const handleItemDoubleClick = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemDoubleClick', item, itemType, internalState);
+};
+
+const handleItemExpand = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ if (!item) {
+ return;
+ }
+
+ return internalState.toggleExpanded({
+ id: item.id,
+ itemType,
+ serverId: item.serverId,
+ });
+};
+
+const handleItemFavorite = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemFavorite', item, itemType, internalState);
+};
+
+const handleItemRating = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemRating', item, itemType, internalState);
+};
+
+const handleItemMore = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemMore', item, itemType, internalState);
+};
+
+const handleItemPlay = (
+ item: (ItemListItem & object) | undefined,
+ itemType: LibraryItem,
+ playType: Play,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemPlay', item, itemType, playType, internalState);
+};
diff --git a/src/renderer/components/item-list/item-list-pagination/use-item-list-pagination.ts b/src/renderer/components/item-list/item-list-pagination/use-item-list-pagination.ts
index d7e1852c2..fa0518c6f 100644
--- a/src/renderer/components/item-list/item-list-pagination/use-item-list-pagination.ts
+++ b/src/renderer/components/item-list/item-list-pagination/use-item-list-pagination.ts
@@ -1,13 +1,9 @@
import { useSearchParams } from 'react-router-dom';
-interface UseItemListPaginationProps {
- initialPage?: number;
-}
-
-export const useItemListPagination = ({ initialPage }: UseItemListPaginationProps) => {
+export const useItemListPagination = () => {
const [searchParams, setSearchParams] = useSearchParams();
- const currentPage = initialPage || Number(searchParams.get('currentPage')) || 0;
+ const currentPage = Number(searchParams.get('currentPage')) || 0;
const onChange = (index: number) => {
setSearchParams(
diff --git a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
index a72eb1a32..da3cd0c80 100644
--- a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
+++ b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx
@@ -5,6 +5,7 @@ import { CellComponentProps } from 'react-window-v2';
import styles from './item-table-list-column.module.css';
import i18n from '/@/i18n/i18n';
+import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
import { ActionsColumn } from '/@/renderer/components/item-list/item-table-list/columns/actions-column';
import { AlbumArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-artists-column';
import { CountColumn } from '/@/renderer/components/item-list/item-table-list/columns/count-column';
@@ -23,13 +24,13 @@ import { RatingColumn } from '/@/renderer/components/item-list/item-table-list/c
import { RowIndexColumn } from '/@/renderer/components/item-list/item-table-list/columns/row-index-column';
import { SizeColumn } from '/@/renderer/components/item-list/item-table-list/columns/size-column';
import { TextColumn } from '/@/renderer/components/item-list/item-table-list/columns/text-column';
-import { CellProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
+import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
import { Icon } from '/@/shared/components/icon/icon';
import { Text } from '/@/shared/components/text/text';
-import { TableColumn } from '/@/shared/types/types';
-import { createDoubleClickHandler } from '/@/shared/utils/double-click-handler';
+import { LibraryItem } from '/@/shared/types/domain-types';
+import { Play, TableColumn } from '/@/shared/types/types';
-export interface ItemTableListColumn extends CellComponentProps
{}
+export interface ItemTableListColumn extends CellComponentProps {}
export interface ItemTableListInnerColumn extends ItemTableListColumn {
type: TableColumn;
@@ -159,18 +160,6 @@ export const TableColumnTextContainer = (
props.enableRowBorders && props.enableHeader && props.rowIndex > 0,
})}
data-row-index={isDataRow ? props.rowIndex : undefined}
- onClick={createDoubleClickHandler({
- onDoubleClick: (e) => {
- props.onItemDoubleClick?.(props.data[props.rowIndex], dataIndex, e);
- },
- onSingleClick: (e) => {
- props.onItemClick?.(props.data[props.rowIndex], dataIndex, e);
- props.handleExpand(e, props.data[props.rowIndex], props.itemType);
- },
- })}
- onContextMenu={(e) => {
- props.onItemContextMenu?.(props.data[props.rowIndex], dataIndex, e);
- }}
ref={containerRef}
style={props.style}
>
@@ -237,18 +226,6 @@ export const TableColumnContainer = (
props.enableRowBorders && props.enableHeader && props.rowIndex > 0,
})}
data-row-index={isDataRow ? props.rowIndex : undefined}
- onClick={createDoubleClickHandler({
- onDoubleClick: (e) => {
- props.onItemDoubleClick?.(props.data[props.rowIndex], dataIndex, e);
- },
- onSingleClick: (e) => {
- props.onItemClick?.(props.data[props.rowIndex], dataIndex, e);
- props.handleExpand(e, props.data[props.rowIndex], props.itemType);
- },
- })}
- onContextMenu={(e) => {
- props.onItemContextMenu?.(props.data[props.rowIndex], dataIndex, e);
- }}
ref={containerRef}
style={props.style}
>
@@ -283,8 +260,73 @@ export const TableColumnHeaderContainer = (
);
};
+const handleItemClick = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemClick', item, itemType, internalState);
+};
+
+const handleItemExpand = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemExpand', item, itemType, internalState);
+};
+
+const handleItemSelect = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemSelect', item, itemType, internalState);
+};
+
+const handleItemDoubleClick = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemDoubleClick', item, itemType, internalState);
+};
+
+const handleItemFavorite = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemFavorite', item, itemType, internalState);
+};
+
+const handleItemRating = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemRating', item, itemType, internalState);
+};
+
+const handleItemMore = (
+ item: unknown,
+ itemType: LibraryItem,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemMore', item, itemType, internalState);
+};
+
+const handleItemPlay = (
+ item: unknown,
+ itemType: LibraryItem,
+ playType: Play,
+ internalState: ItemListStateActions,
+) => {
+ console.log('handleItemPlay', item, itemType, playType, internalState);
+};
+
const columnLabelMap: Record = {
- [TableColumn.ACTIONS]: '',
+ [TableColumn.ACTIONS]: ,
[TableColumn.ALBUM]: i18n.t('table.column.album', { postProcess: 'upperCase' }) as string,
[TableColumn.ALBUM_ARTIST]: i18n.t('table.column.albumArtist', {
postProcess: 'upperCase',
@@ -335,9 +377,7 @@ const columnLabelMap: Record = {
[TableColumn.TRACK_NUMBER]: i18n.t('table.column.trackNumber', {
postProcess: 'upperCase',
}) as string,
- [TableColumn.USER_FAVORITE]: i18n.t('table.column.favorite', {
- postProcess: 'upperCase',
- }) as string,
+ [TableColumn.USER_FAVORITE]: ,
[TableColumn.USER_RATING]: i18n.t('table.column.rating', {
postProcess: 'upperCase',
}) as string,
diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx
index 92ff22616..9ecfc09ed 100644
--- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx
+++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx
@@ -6,38 +6,28 @@ import { AnimatePresence, motion, Variants } from 'motion/react';
import { useOverlayScrollbars } from 'overlayscrollbars-react';
import {
type JSXElementConstructor,
- MouseEvent,
Ref,
useCallback,
useEffect,
+ useImperativeHandle,
useMemo,
useRef,
useState,
} from 'react';
-import { type CellComponentProps, Grid, GridImperativeAPI, type GridProps } from 'react-window-v2';
+import { type CellComponentProps, Grid, type GridProps } from 'react-window-v2';
import styles from './item-table-list.module.css';
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
-import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state';
+import {
+ ItemListStateActions,
+ useItemListState,
+} from '/@/renderer/components/item-list/helpers/item-list-state';
import { sortTableColumns } from '/@/renderer/components/item-list/helpers/sort-table-columns';
+import { ItemListHandle } from '/@/renderer/components/item-list/types';
import { LibraryItem } from '/@/shared/types/domain-types';
import { TableColumn } from '/@/shared/types/types';
-export interface CellProps {
- columns: ItemTableListColumnConfig[];
- data: unknown[];
- enableHeader?: boolean;
- enableRowBorders?: boolean;
- enableRowHover?: boolean;
- handleExpand: (e: MouseEvent, item: unknown, itemType: LibraryItem) => void;
- itemType: LibraryItem;
- onItemClick?: (item: unknown, index: number, event: MouseEvent) => void;
- onItemContextMenu?: (item: unknown, index: number, event: MouseEvent) => void;
- onItemDoubleClick?: (item: unknown, index: number, event: MouseEvent) => void;
- size?: 'compact' | 'default';
-}
-
export interface ItemTableListColumnConfig {
align: 'center' | 'end' | 'start';
id: TableColumn;
@@ -45,8 +35,21 @@ export interface ItemTableListColumnConfig {
width: number;
}
+export interface TableItemProps {
+ columns: ItemTableListColumnConfig[];
+ data: unknown[];
+ enableExpansion?: boolean;
+ enableHeader?: boolean;
+ enableRowBorders?: boolean;
+ enableRowHover?: boolean;
+ enableSelection?: boolean;
+ internalState: ItemListStateActions;
+ itemType: LibraryItem;
+ size?: 'compact' | 'default';
+}
+
interface ItemTableListProps {
- CellComponent: JSXElementConstructor>;
+ CellComponent: JSXElementConstructor>;
columns: ItemTableListColumnConfig[];
data: unknown[];
enableExpansion?: boolean;
@@ -61,18 +64,17 @@ interface ItemTableListProps {
type: 'index' | 'offset';
};
itemType: LibraryItem;
- onCellsRendered?: GridProps['onCellsRendered'];
- onEndReached?: (index: number) => void;
- onItemClick?: (item: unknown, index: number, event: MouseEvent) => void;
- onItemContextMenu?: (item: unknown, index: number, event: MouseEvent) => void;
- onItemDoubleClick?: (item: unknown, index: number, event: MouseEvent) => void;
- onRangeChanged?: (range: { endIndex: number; startIndex: number }) => void;
- onScrollEnd?: (offset: number) => void;
- onStartReached?: (index: number) => void;
- ref?: Ref;
- rowHeight: ((index: number, cellProps: CellProps) => number) | number;
+ onCellsRendered?: GridProps['onCellsRendered'];
+ onEndReached?: (index: number, internalState: ItemListStateActions) => void;
+ onRangeChanged?: (
+ range: { endIndex: number; startIndex: number },
+ internalState: ItemListStateActions,
+ ) => void;
+ onScrollEnd?: (offset: number, internalState: ItemListStateActions) => void;
+ onStartReached?: (index: number, internalState: ItemListStateActions) => void;
+ ref?: Ref;
+ rowHeight?: ((index: number, cellProps: TableItemProps) => number) | number;
size?: 'compact' | 'default';
- totalItemCount: number;
}
const expandedAnimationVariants: Variants = {
@@ -97,26 +99,24 @@ export const ItemTableList = ({
enableHeader = true,
enableRowBorders = false,
enableRowHover = false,
+ enableSelection = false,
headerHeight = 40,
initialTop,
itemType,
onCellsRendered,
onEndReached,
- onItemClick,
- onItemContextMenu,
- onItemDoubleClick,
onRangeChanged,
onScrollEnd,
onStartReached,
ref,
rowHeight,
size = 'default',
- totalItemCount,
}: ItemTableListProps) => {
+ const totalItemCount = data.length;
const sortedColumns = useMemo(() => sortTableColumns(columns), [columns]);
const columnCount = sortedColumns.length;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- const columnWidth = (index: number, _cellProps: CellProps) => sortedColumns[index].width;
+ const columnWidth = (index: number, _cellProps: TableItemProps) => sortedColumns[index].width;
const pinnedLeftColumnCount = sortedColumns.filter((col) => col.pinned === 'left').length;
const pinnedRightColumnCount = sortedColumns.filter((col) => col.pinned === 'right').length;
@@ -131,8 +131,53 @@ export const ItemTableList = ({
const mergedRowRef = useMergedRef(rowRef, scrollContainerRef);
const [showLeftShadow, setShowLeftShadow] = useState(false);
const [showRightShadow, setShowRightShadow] = useState(false);
+ const handleRef = useRef(null);
const onScrollEndRef = useRef(onScrollEnd);
+ useEffect(() => {
+ onScrollEndRef.current = onScrollEnd;
+ }, [onScrollEnd]);
+
+ const scrollToTableOffset = useCallback((offset: number) => {
+ const mainContainer = rowRef.current?.childNodes[0] as HTMLDivElement | undefined;
+ const pinnedLeftContainer = pinnedLeftColumnRef.current?.childNodes[0] as
+ | HTMLDivElement
+ | undefined;
+ const pinnedRightContainer = pinnedRightColumnRef.current?.childNodes[0] as
+ | HTMLDivElement
+ | undefined;
+
+ if (mainContainer) {
+ mainContainer.scrollTo({ behavior: 'instant', top: offset });
+ }
+ if (pinnedLeftContainer) {
+ pinnedLeftContainer.scrollTo({ behavior: 'instant', top: offset });
+ }
+ if (pinnedRightContainer) {
+ pinnedRightContainer.scrollTo({ behavior: 'instant', top: offset });
+ }
+ }, []);
+
+ const calculateScrollTopForIndex = useCallback(
+ (index: number) => {
+ const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
+ let scrollTop = 0;
+ for (let i = 0; i < adjustedIndex; i++) {
+ const height = rowHeight as number;
+ scrollTop += height;
+ }
+ return scrollTop;
+ },
+ [enableHeader, rowHeight],
+ );
+
+ const scrollToTableIndex = useCallback(
+ (index: number) => {
+ const offset = calculateScrollTopForIndex(index);
+ scrollToTableOffset(offset);
+ },
+ [calculateScrollTopForIndex, scrollToTableOffset],
+ );
const [initialize] = useOverlayScrollbars({
defer: true,
@@ -232,7 +277,10 @@ export const ItemTableList = ({
scrollTimeouts.delete(element);
if (element === row && onScrollEndRef.current) {
- onScrollEndRef.current(row.scrollTop);
+ onScrollEndRef.current(
+ row.scrollTop,
+ handleRef.current ?? (undefined as any),
+ );
}
}, 150);
@@ -430,9 +478,11 @@ export const ItemTableList = ({
}, [pinnedLeftColumnCount, pinnedRightColumnCount]);
const getRowHeight = useCallback(
- (index: number, cellProps: CellProps) => {
+ (index: number, cellProps: TableItemProps) => {
+ const height = size === 'compact' ? 40 : 68;
+
const baseHeight =
- typeof rowHeight === 'number' ? rowHeight : rowHeight(index, cellProps);
+ typeof rowHeight === 'number' ? rowHeight : rowHeight?.(index, cellProps) || height;
// If enableHeader is true and this is the first sticky row, use fixed header height
if (enableHeader && index === 0 && pinnedRowCount > 0) {
@@ -441,30 +491,13 @@ export const ItemTableList = ({
return baseHeight;
},
- [enableHeader, headerHeight, rowHeight, pinnedRowCount],
+ [enableHeader, headerHeight, rowHeight, pinnedRowCount, size],
);
const internalState = useItemListState();
const hasExpanded = internalState.hasExpanded();
- const handleExpand = useCallback(
- (_e: MouseEvent, item: unknown, itemType: LibraryItem) => {
- if (!enableExpansion) {
- return;
- }
-
- if (item && typeof item === 'object' && 'id' in item && 'serverId' in item) {
- internalState.toggleExpanded({
- id: item.id as string,
- itemType: itemType,
- serverId: item.serverId as string,
- });
- }
- },
- [enableExpansion, internalState],
- );
-
const handleOnCellsRendered = useCallback(
(cells: {
columnStartIndex: number;
@@ -472,18 +505,21 @@ export const ItemTableList = ({
rowStartIndex: number;
rowStopIndex: number;
}) => {
- onRangeChanged?.({
- endIndex: cells.rowStopIndex,
- startIndex: cells.rowStartIndex,
- });
+ onRangeChanged?.(
+ {
+ endIndex: cells.rowStopIndex,
+ startIndex: cells.rowStartIndex,
+ },
+ internalState,
+ );
if (onStartReached || onEndReached) {
if (cells.rowStartIndex === 0) {
- onStartReached?.(0);
+ onStartReached?.(0, handleRef.current ?? (undefined as any));
}
if (cells.rowStopIndex + 10 >= totalItemCount) {
- onEndReached?.(totalItemCount);
+ onEndReached?.(totalItemCount, handleRef.current ?? (undefined as any));
}
}
@@ -511,11 +547,12 @@ export const ItemTableList = ({
pinnedLeftColumnCount,
pinnedRowCount,
totalItemCount,
+ internalState,
],
);
const PinnedRowCell = useCallback(
- (cellProps: CellComponentProps & CellProps) => {
+ (cellProps: CellComponentProps & TableItemProps) => {
return (
{
+ (cellProps: CellComponentProps & TableItemProps) => {
return ;
},
[pinnedRowCount, CellComponent],
);
const PinnedRightColumnCell = useCallback(
- (cellProps: CellComponentProps & CellProps) => {
+ (cellProps: CellComponentProps & TableItemProps) => {
return (
{
+ (cellProps: CellComponentProps & TableItemProps) => {
return (
) => {
+ (cellProps: CellComponentProps) => {
return (
{
if (!initialTop || isInitialScrollPositionSet.current) return;
+ isInitialScrollPositionSet.current = true;
- const scrollToIndex = (index: number, behavior: 'auto' | 'smooth' = 'auto') => {
- isInitialScrollPositionSet.current = true;
- const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
-
- // Calculate scroll position based on row heights
- const calculateScrollTop = (rowIndex: number) => {
- let scrollTop = 0;
- for (let i = 0; i < rowIndex; i++) {
- const height = rowHeight as number;
- scrollTop += height;
- }
- return scrollTop;
- };
-
- const scrollTop = calculateScrollTop(adjustedIndex);
-
- // Get the scroll containers and scroll them directly
- const mainContainer = rowRef.current?.childNodes[0] as HTMLDivElement;
- const pinnedLeftContainer = pinnedLeftColumnRef.current
- ?.childNodes[0] as HTMLDivElement;
- const pinnedRightContainer = pinnedRightColumnRef.current
- ?.childNodes[0] as HTMLDivElement;
-
- if (initialTop.type === 'offset') {
- if (mainContainer) {
- mainContainer.scrollTo({
- behavior,
- top: initialTop.to,
- });
- }
-
- if (pinnedLeftContainer) {
- pinnedLeftContainer.scrollTo({
- behavior,
- top: initialTop.to,
- });
- }
-
- if (pinnedRightContainer) {
- pinnedRightContainer.scrollTo({
- behavior,
- top: initialTop.to,
- });
- }
- } else {
- if (mainContainer) {
- mainContainer.scrollTo({
- behavior,
- top: scrollTop,
- });
- }
-
- if (pinnedLeftContainer) {
- pinnedLeftContainer.scrollTo({
- behavior,
- top: scrollTop,
- });
- }
-
- if (pinnedRightContainer) {
- pinnedRightContainer.scrollTo({
- behavior,
- top: scrollTop,
- });
- }
- }
- };
-
- scrollToIndex(initialTop.to, initialTop.behavior);
- }, [initialTop, enableHeader, pinnedLeftColumnCount, pinnedRightColumnCount, rowHeight]);
-
- // Expose grid refs to parent component
- useEffect(() => {
- if (ref && typeof ref === 'object') {
- // Create a simple API that exposes the main container
- const combinedAPI: GridImperativeAPI = {
- // We'll create a minimal API that can be extended later
- // For now, we'll just expose the main container ref
- } as GridImperativeAPI;
-
- if ('current' in ref) {
- (ref as React.MutableRefObject).current = combinedAPI;
- }
+ if (initialTop.type === 'offset') {
+ scrollToTableOffset(initialTop.to);
+ } else {
+ scrollToTableIndex(initialTop.to);
}
- }, [ref]);
+ }, [initialTop, scrollToTableIndex, scrollToTableOffset]);
+
+ const imperativeHandle: ItemListHandle = useMemo(() => {
+ return {
+ clearExpanded: () => {
+ internalState.clearExpanded();
+ },
+ clearSelected: () => {
+ internalState.clearSelected();
+ },
+ getItem: (index: number) => (enableHeader ? data[index + 1] : data[index]),
+ getItemCount: () => (enableHeader ? data.length - 1 : data.length),
+ getItems: () => data,
+ internalState,
+ scrollToIndex: (index: number) => {
+ scrollToTableIndex(enableHeader ? index + 1 : index);
+ },
+ scrollToOffset: (offset: number) => {
+ scrollToTableOffset(enableHeader ? offset + headerHeight : offset);
+ },
+ };
+ }, [data, enableHeader, headerHeight, internalState, scrollToTableIndex, scrollToTableOffset]);
+
+ useImperativeHandle(ref, () => imperativeHandle);
+
+ useEffect(() => {
+ handleRef.current = imperativeHandle;
+ }, [imperativeHandle]);
return (
0).reduce(
- (a, _, i) => a + columnWidth(i, cellProps),
+ (a, _, i) => a + columnWidth(i, itemProps),
0,
)}px`,
}}
@@ -708,12 +693,12 @@ export const ItemTableList = ({
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
- ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`,
+ ).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
}}
>
0,
- ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`,
+ ).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
} as React.CSSProperties
}
>
{
@@ -776,7 +761,7 @@ export const ItemTableList = ({
{
@@ -808,7 +793,7 @@ export const ItemTableList = ({
a +
columnWidth(
i + pinnedLeftColumnCount + totalColumnCount,
- cellProps,
+ itemProps,
),
0,
)}px`,
@@ -823,12 +808,12 @@ export const ItemTableList = ({
minHeight: `${Array.from(
{ length: pinnedRowCount },
() => 0,
- ).reduce((a, _, i) => a + getRowHeight(i, cellProps), 0)}px`,
+ ).reduce((a, _, i) => a + getRowHeight(i, itemProps), 0)}px`,
}}
>
{
@@ -851,7 +836,7 @@ export const ItemTableList = ({
>
{
diff --git a/src/renderer/components/item-list/types.tsx b/src/renderer/components/item-list/types.tsx
new file mode 100644
index 000000000..ea97a4f07
--- /dev/null
+++ b/src/renderer/components/item-list/types.tsx
@@ -0,0 +1,68 @@
+import { MouseEvent } from 'react';
+
+import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
+import {
+ Album,
+ AlbumArtist,
+ Artist,
+ LibraryItem,
+ Playlist,
+ Song,
+} from '/@/shared/types/domain-types';
+import { Play } from '/@/shared/types/types';
+
+export interface ItemControls {
+ onClick?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ e: MouseEvent,
+ ) => void;
+ onDoubleClick?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ e: MouseEvent,
+ ) => void;
+ onFavorite?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ e: MouseEvent,
+ ) => void;
+ onItemExpand?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ e: MouseEvent,
+ ) => void;
+ onMore?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ e: MouseEvent,
+ ) => void;
+ onPlay?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ playType: Play,
+ e: MouseEvent,
+ ) => void;
+ onRating?: (
+ item: Album | AlbumArtist | Artist | Playlist | Song | undefined,
+ itemType: LibraryItem,
+ e: MouseEvent,
+ ) => void;
+}
+
+export interface ItemListComponentProps {
+ itemsPerPage?: number;
+ query: Omit;
+ serverId: string;
+}
+
+export interface ItemListHandle {
+ clearExpanded: () => void;
+ clearSelected: () => void;
+ getItem: (index: number) => unknown;
+ getItemCount: () => number;
+ getItems: () => unknown[];
+ internalState: ItemListStateActions;
+ scrollToIndex: (index: number, options?: { behavior?: 'auto' | 'smooth' }) => void;
+ scrollToOffset: (offset: number, options?: { behavior?: 'auto' | 'smooth' }) => void;
+}