From b6c32004198f9ae4a232223b0441ceba4b523539 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Fri, 14 Nov 2025 15:18:25 -0800 Subject: [PATCH] implement item list grid card row customization --- src/i18n/locales/en.json | 2 + .../components/item-card/item-card.module.css | 12 + .../components/item-card/item-card.tsx | 426 ++++++++++++++---- .../item-list/helpers/use-grid-rows.ts | 111 +++++ .../item-grid-list/item-grid-list.tsx | 105 ++--- .../components/album-list-infinite-grid.tsx | 4 + .../components/album-list-paginated-grid.tsx | 5 + .../album-artist-list-infinite-grid.tsx | 7 +- .../album-artist-list-paginated-grid.tsx | 5 + .../components/artist-list-infinite-grid.tsx | 4 + .../components/artist-list-paginated-grid.tsx | 5 + .../components/genre-list-infinite-grid.tsx | 4 + .../components/genre-list-paginated-grid.tsx | 5 + .../shared/components/grid-config.tsx | 3 - .../components/song-list-infinite-grid.tsx | 4 + .../components/song-list-paginated-grid.tsx | 5 + src/shared/types/drag-and-drop.ts | 1 + 17 files changed, 559 insertions(+), 149 deletions(-) create mode 100644 src/renderer/components/item-list/helpers/use-grid-rows.ts diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index c9ba70183..d7e5ac17e 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -128,6 +128,7 @@ "yes": "yes", "explicit": "explicit", "clean": "clean", + "gridRows": "grid rows", "tableColumns": "table columns" }, "entity": { @@ -858,6 +859,7 @@ "label": { "actions": "$t(common.action_other)", "album": "$t(entity.album_one)", + "albumCount": "$t(entity.album_other)", "albumArtist": "$t(entity.albumArtist_one)", "artist": "$t(entity.artist_one)", "biography": "$t(common.biography)", diff --git a/src/renderer/components/item-card/item-card.module.css b/src/renderer/components/item-card/item-card.module.css index a956771b8..42949a30c 100644 --- a/src/renderer/components/item-card/item-card.module.css +++ b/src/renderer/components/item-card/item-card.module.css @@ -96,6 +96,18 @@ color: var(--theme-colors-foreground-muted); } +.row.align-start { + text-align: left; +} + +.row.align-center { + text-align: center; +} + +.row.align-end { + text-align: right; +} + .container.poster { padding: 0; background-color: inherit; diff --git a/src/renderer/components/item-card/item-card.tsx b/src/renderer/components/item-card/item-card.tsx index f1509a53c..8e1099268 100644 --- a/src/renderer/components/item-card/item-card.tsx +++ b/src/renderer/components/item-card/item-card.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import formatDuration from 'format-duration'; import { AnimatePresence } from 'motion/react'; import { Fragment, memo, ReactNode, useState } from 'react'; import { generatePath, Link } from 'react-router'; @@ -11,6 +12,7 @@ import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/i import { ItemControls } from '/@/renderer/components/item-list/types'; import { useDragDrop } from '/@/renderer/hooks/use-drag-drop'; import { AppRoute } from '/@/renderer/router/routes'; +import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format'; import { Image } from '/@/shared/components/image/image'; import { Separator } from '/@/shared/components/separator/separator'; import { Skeleton } from '/@/shared/components/skeleton/skeleton'; @@ -26,6 +28,13 @@ import { } from '/@/shared/types/domain-types'; import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop'; +export type DataRow = { + align?: 'center' | 'end' | 'start'; + format: (data: Album | AlbumArtist | Artist | Playlist | Song) => null | ReactNode | string; + id: string; + isMuted?: boolean; +}; + export interface ItemCardProps { controls?: ItemControls; data: Album | AlbumArtist | Artist | Playlist | Song | undefined; @@ -33,16 +42,11 @@ export interface ItemCardProps { internalState?: ItemListStateActions; isRound?: boolean; itemType: LibraryItem; + rows?: DataRow[]; type?: 'compact' | 'default' | 'poster'; withControls?: boolean; } -type DataRow = { - format: (data: Album | AlbumArtist | Artist | Playlist | Song) => ReactNode | string; - id: string; - isMuted?: boolean; -}; - export const ItemCard = ({ controls, data, @@ -50,11 +54,13 @@ export const ItemCard = ({ internalState, isRound, itemType, + rows: providedRows, type = 'poster', withControls, }: ItemCardProps) => { const imageUrl = getImageUrl(data); - const rows = getDataRows(itemType); + const defaultRows = getDataRows(); + const rows = providedRows && providedRows.length > 0 ? providedRows : defaultRows; switch (type) { case 'compact': @@ -218,9 +224,20 @@ const CompactItemCard = ({ )}
- {rows.map((row) => ( - - ))} + {rows + .filter( + (row): row is NonNullable => + row !== null && row !== undefined, + ) + .map((row, index) => ( + + ))}
@@ -232,11 +249,21 @@ const CompactItemCard = ({
- {rows.map((row) => ( -
-   -
- ))} + {rows + .filter( + (row): row is NonNullable => + row !== null && row !== undefined, + ) + .map((row, index) => ( +
0, + })} + key={row.id} + > +   +
+ ))}
@@ -352,9 +379,20 @@ const DefaultItemCard = ({
- {rows.map((row) => ( - - ))} + {rows + .filter( + (row): row is NonNullable => + row !== null && row !== undefined, + ) + .map((row, index) => ( + + ))}
); @@ -366,11 +404,20 @@ const DefaultItemCard = ({
- {rows.map((row) => ( -
-   -
- ))} + {rows + .filter( + (row): row is NonNullable => row !== null && row !== undefined, + ) + .map((row, index) => ( +
0, + })} + key={row.id} + > +   +
+ ))}
); @@ -531,9 +578,20 @@ const PosterItemCard = ({ {data && (
- {rows.map((row) => ( - - ))} + {rows + .filter( + (row): row is NonNullable => + row !== null && row !== undefined, + ) + .map((row, index) => ( + + ))}
)} @@ -546,83 +604,253 @@ const PosterItemCard = ({
- {rows.map((row) => ( -
-   -
- ))} + {rows + .filter( + (row): row is NonNullable => row !== null && row !== undefined, + ) + .map((row, index) => ( +
0, + })} + key={row.id} + > +   +
+ ))}
); }; -const getDataRows = (itemType: LibraryItem): DataRow[] => { - switch (itemType) { - case LibraryItem.ALBUM: - return [ - { - format: (data) => { - const album = data as Album; +export const getDataRows = (): DataRow[] => { + return [ + { + format: (data) => { + if ('name' in data && data.name) { + if ('id' in data && data.id) { + if ('_itemType' in data) { + switch (data._itemType) { + case LibraryItem.ALBUM: + return ( + + {data.name} + + ); + case LibraryItem.ALBUM_ARTIST: + return ( + + {data.name} + + ); + case LibraryItem.PLAYLIST: + return ( + + {data.name} + + ); + default: + return data.name; + } + } + } + return data.name; + } + return ''; + }, + id: 'name', + }, + { + format: (data) => { + if ('albumArtists' in data && Array.isArray(data.albumArtists)) { + return (data as Album | Song).albumArtists.map((artist, index) => ( + + + {artist.name} + + {index < (data as Album | Song).albumArtists.length - 1 && ( + + )} + + )); + } + return ''; + }, + id: 'albumArtists', + isMuted: true, + }, + { + format: (data) => { + if ('artists' in data && Array.isArray(data.artists)) { + return (data as Album | Song).artists.map((artist, index) => ( + + + {artist.name} + + {index < (data as Album | Song).artists.length - 1 && } + + )); + } + return ''; + }, + id: 'artists', + isMuted: true, + }, + { + format: (data) => { + if ('duration' in data && data.duration !== null) { + return formatDuration(data.duration * 1000); + } + return ''; + }, + id: 'duration', + }, + { + format: (data) => { + if ('releaseYear' in data && data.releaseYear !== null) { + return String(data.releaseYear); + } + return ''; + }, + id: 'releaseYear', + }, + { + format: (data) => { + if ('releaseDate' in data && data.releaseDate) { + return data.releaseDate; + } + return ''; + }, + id: 'releaseDate', + }, + { + format: (data) => { + if ('createdAt' in data && data.createdAt) { + return formatDateAbsolute(data.createdAt); + } + return ''; + }, + id: 'createdAt', + }, + { + format: (data) => { + if ('lastPlayedAt' in data && data.lastPlayedAt) { + return formatDateRelative(data.lastPlayedAt); + } + return ''; + }, + id: 'lastPlayedAt', + }, + { + format: (data) => { + if ('playCount' in data && data.playCount !== null) { + return String(data.playCount); + } + return ''; + }, + id: 'playCount', + }, + { + format: (data) => { + if ('genres' in data && Array.isArray(data.genres)) { + return (data as Album | AlbumArtist | Song).genres + .map((genre) => genre.name) + .join(', '); + } + return ''; + }, + id: 'genres', + isMuted: true, + }, + { + format: (data) => { + if ('album' in data && data.album) { + const song = data as Song; + if ('albumId' in song && song.albumId) { return ( - {album.name} + {song.album} ); - }, - id: 'name', - }, - { - format: (data) => { - const album = data as Album; - return album.albumArtists.map((artist, index) => ( - - - {artist.name} - - {index < album.albumArtists.length - 1 && } - - )); - }, - id: 'albumArtists', - isMuted: true, - }, - ]; - case LibraryItem.ALBUM_ARTIST: - return [{ format: (data) => (data as AlbumArtist).name, id: 'name' }]; - case LibraryItem.ARTIST: - return [{ format: (data) => (data as Artist).name, id: 'name' }]; - case LibraryItem.PLAYLIST: - return [{ format: (data) => (data as Playlist).name, id: 'name' }]; - case LibraryItem.SONG: - return [{ format: (data) => (data as Song).name, id: 'name' }]; - default: - return []; - } + } + return song.album; + } + return ''; + }, + id: 'album', + isMuted: true, + }, + { + format: (data) => { + if ('songCount' in data && data.songCount !== null) { + return String(data.songCount); + } + return ''; + }, + id: 'songCount', + }, + { + format: (data) => { + if ('albumCount' in data && data.albumCount !== null) { + return String(data.albumCount); + } + return ''; + }, + id: 'albumCount', + }, + { + format: (data) => { + if ( + 'userRating' in data && + (data as Album | AlbumArtist | Song).userRating !== null + ) { + return formatRating(data as Album | AlbumArtist | Song); + } + return null; + }, + id: 'rating', + }, + { + format: (data) => { + if ('userFavorite' in data) { + return (data as Album | AlbumArtist | Song).userFavorite ? '★' : ''; + } + return ''; + }, + id: 'userFavorite', + }, + ]; }; -export const getDataRowsCount = (itemType: LibraryItem) => { - switch (itemType) { - case LibraryItem.ALBUM: - return 2; - case LibraryItem.ALBUM_ARTIST: - return 1; - case LibraryItem.ARTIST: - return 1; - case LibraryItem.PLAYLIST: - return 2; - case LibraryItem.SONG: - return 2; - default: - return 1; - } +export const getDataRowsCount = () => { + return getDataRows().length; }; const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => { @@ -635,20 +863,32 @@ const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | unde const ItemCardRow = ({ data, + index, row, type, }: { data: Album | AlbumArtist | Artist | Playlist | Song | undefined; + index: number; row: DataRow; type?: 'compact' | 'default' | 'poster'; }) => { + const alignmentClass = + row.align === 'center' + ? styles['align-center'] + : row.align === 'end' + ? styles['align-end'] + : styles['align-start']; + + // All rows except the first one (index 0) should be muted + const isMuted = index > 0 || row.isMuted; + if (!data) { return (
@@ -659,10 +899,10 @@ const ItemCardRow = ({ return ( diff --git a/src/renderer/components/item-list/helpers/use-grid-rows.ts b/src/renderer/components/item-list/helpers/use-grid-rows.ts new file mode 100644 index 000000000..19af7a3d8 --- /dev/null +++ b/src/renderer/components/item-list/helpers/use-grid-rows.ts @@ -0,0 +1,111 @@ +import { useMemo } from 'react'; + +import { type DataRow, getDataRows } from '/@/renderer/components/item-card/item-card'; +import { useSettingsStore } from '/@/renderer/store'; +import { LibraryItem } from '/@/shared/types/domain-types'; +import { TableColumn } from '/@/shared/types/types'; +import { ItemListKey } from '/@/shared/types/types'; + +const getDefaultRowsForItemType = (itemType: LibraryItem): DataRow[] => { + const allRows = getDataRows(); + const rowMap = new Map(allRows.map((row) => [row.id, row])); + + switch (itemType) { + case LibraryItem.ALBUM: + return [rowMap.get('name'), rowMap.get('albumArtists')].filter( + (row): row is NonNullable => row !== undefined, + ); + case LibraryItem.ALBUM_ARTIST: + return [rowMap.get('name')].filter( + (row): row is NonNullable => row !== undefined, + ); + case LibraryItem.ARTIST: + return [rowMap.get('name')].filter( + (row): row is NonNullable => row !== undefined, + ); + case LibraryItem.PLAYLIST: + return [rowMap.get('name')].filter( + (row): row is NonNullable => row !== undefined, + ); + case LibraryItem.SONG: + return [rowMap.get('name')].filter( + (row): row is NonNullable => row !== undefined, + ); + default: + return []; + } +}; + +// Map TableColumn enum values to row IDs used in getDataRows +const getRowIdFromTableColumn = (tableColumn: TableColumn): null | string => { + // Map TableColumn enum values to the row IDs used in getDataRows + const columnToRowIdMap: Record = { + [TableColumn.ACTIONS]: null, + [TableColumn.ALBUM]: 'album', + [TableColumn.ALBUM_ARTIST]: 'albumArtists', + [TableColumn.ALBUM_COUNT]: 'albumCount', + [TableColumn.ARTIST]: 'artists', + [TableColumn.BIOGRAPHY]: null, + [TableColumn.BIT_RATE]: null, + [TableColumn.BPM]: null, + [TableColumn.CHANNELS]: null, + [TableColumn.CODEC]: null, + [TableColumn.COMMENT]: null, + [TableColumn.DATE_ADDED]: 'createdAt', + [TableColumn.DISC_NUMBER]: null, + [TableColumn.DURATION]: 'duration', + [TableColumn.GENRE]: 'genres', + [TableColumn.GENRE_BADGE]: null, + [TableColumn.ID]: null, + [TableColumn.IMAGE]: null, + [TableColumn.LAST_PLAYED]: 'lastPlayedAt', + [TableColumn.OWNER]: null, + [TableColumn.PATH]: null, + [TableColumn.PLAY_COUNT]: 'playCount', + [TableColumn.RELEASE_DATE]: 'releaseDate', + [TableColumn.ROW_INDEX]: null, + [TableColumn.SIZE]: null, + [TableColumn.SKIP]: null, + [TableColumn.SONG_COUNT]: 'songCount', + [TableColumn.TITLE]: 'name', + [TableColumn.TITLE_COMBINED]: null, + [TableColumn.TRACK_NUMBER]: null, + [TableColumn.USER_FAVORITE]: 'userFavorite', + [TableColumn.USER_RATING]: 'rating', + [TableColumn.YEAR]: 'releaseYear', + }; + return columnToRowIdMap[tableColumn] || null; +}; + +export const useGridRows = (itemType: LibraryItem, listKey?: ItemListKey) => { + const gridRowsConfig = useSettingsStore((state) => + listKey ? state.lists[listKey]?.grid?.rows : undefined, + ); + + return useMemo(() => { + const allRows = getDataRows(); + + if (!listKey || !gridRowsConfig || gridRowsConfig.length === 0) { + const defaultRows = getDefaultRowsForItemType(itemType); + return defaultRows.length > 0 ? defaultRows : allRows; + } + + const rowMap = new Map(allRows.map((row) => [row.id, row])); + + const configuredRows = gridRowsConfig + .filter((config) => config.isEnabled) + .map((config) => { + const rowId = getRowIdFromTableColumn(config.id); + const baseRow = rowId ? rowMap.get(rowId) : null; + if (!baseRow) return null; + + return { + ...baseRow, + align: config.align, + }; + }) + .filter((row): row is NonNullable => row !== null && row !== undefined); + + return configuredRows.length > 0 ? configuredRows : allRows; + }, [itemType, listKey, gridRowsConfig]); +}; 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 b76b5411c..63fbebfcb 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 @@ -60,6 +60,7 @@ interface VirtualizedGridListProps { onScrollEnd?: ItemGridListProps['onScrollEnd']; outerRef: RefObject; ref: RefObject>; + rows?: ItemCardProps['rows']; tableMeta: null | { columnCount: number; itemHeight: number; @@ -85,6 +86,7 @@ const VirtualizedGridList = React.memo( onScrollEnd, outerRef, ref, + rows, tableMeta, width, }: VirtualizedGridListProps) => { @@ -99,11 +101,13 @@ const VirtualizedGridList = React.memo( gap, internalState, itemType, + rows, tableMeta, }; }, [ tableMeta, controls, + rows, data, enableDrag, enableExpansion, @@ -167,59 +171,51 @@ const VirtualizedGridList = React.memo( VirtualizedGridList.displayName = 'VirtualizedGridList'; -const createThrottledSetTableMeta = (itemsPerRow?: number) => { - return throttle( - ( - width: number, - dataLength: number, - type: LibraryItem, - setTableMeta: (meta: any) => void, - ) => { - const isSm = width >= 600; - const isMd = width >= 768; - const isLg = width >= 960; - const isXl = width >= 1200; - const is2xl = width >= 1440; - const is3xl = width >= 1920; - const is4xl = width >= 2560; +const createThrottledSetTableMeta = (itemsPerRow?: number, rowsCount?: number) => { + return throttle((width: number, dataLength: number, setTableMeta: (meta: any) => void) => { + const isSm = width >= 600; + const isMd = width >= 768; + const isLg = width >= 960; + const isXl = width >= 1200; + const is2xl = width >= 1440; + const is3xl = width >= 1920; + const is4xl = width >= 2560; - let dynamicItemsPerRow = 2; + let dynamicItemsPerRow = 2; - if (is4xl) { - dynamicItemsPerRow = 12; - } else if (is3xl) { - dynamicItemsPerRow = 10; - } else if (is2xl) { - dynamicItemsPerRow = 8; - } else if (isXl) { - dynamicItemsPerRow = 6; - } else if (isLg) { - dynamicItemsPerRow = 5; - } else if (isMd) { - dynamicItemsPerRow = 4; - } else if (isSm) { - dynamicItemsPerRow = 3; - } else { - dynamicItemsPerRow = 2; - } + if (is4xl) { + dynamicItemsPerRow = 12; + } else if (is3xl) { + dynamicItemsPerRow = 10; + } else if (is2xl) { + dynamicItemsPerRow = 8; + } else if (isXl) { + dynamicItemsPerRow = 6; + } else if (isLg) { + dynamicItemsPerRow = 5; + } else if (isMd) { + dynamicItemsPerRow = 4; + } else if (isSm) { + dynamicItemsPerRow = 3; + } else { + dynamicItemsPerRow = 2; + } - const setItemsPerRow = itemsPerRow || dynamicItemsPerRow; + const setItemsPerRow = itemsPerRow || dynamicItemsPerRow; - const widthPerItem = Number(width) / setItemsPerRow; - const itemHeight = widthPerItem + getDataRowsCount(type) * 26; + const widthPerItem = Number(width) / setItemsPerRow; + const itemHeight = widthPerItem + (rowsCount || getDataRowsCount()) * 26; - if (widthPerItem === 0) { - return; - } + if (widthPerItem === 0) { + return; + } - setTableMeta({ - columnCount: setItemsPerRow, - itemHeight, - rowCount: Math.ceil(dataLength / setItemsPerRow), - }); - }, - 200, - ); + setTableMeta({ + columnCount: setItemsPerRow, + itemHeight, + rowCount: Math.ceil(dataLength / setItemsPerRow), + }); + }, 200); }; export interface GridItemProps { @@ -232,6 +228,7 @@ export interface GridItemProps { gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs'; internalState: ItemListStateActions; itemType: LibraryItem; + rows?: ItemCardProps['rows']; tableMeta: null | { columnCount: number; itemHeight: number; @@ -257,6 +254,7 @@ export interface ItemGridListProps { onScroll?: (offset: number, direction: 'down' | 'up') => void; onScrollEnd?: (offset: number, direction: 'down' | 'up') => void; ref?: Ref; + rows?: ItemCardProps['rows']; } export const ItemGridList = ({ @@ -273,6 +271,7 @@ export const ItemGridList = ({ onScroll, onScrollEnd, ref, + rows, }: ItemGridListProps) => { const rootRef = useRef(null); const outerRef = useRef(null); @@ -334,12 +333,12 @@ export const ItemGridList = ({ }, [initialize, tableMeta]); const throttledSetTableMeta = useMemo(() => { - return createThrottledSetTableMeta(itemsPerRow); - }, [itemsPerRow]); + return createThrottledSetTableMeta(itemsPerRow, rows?.length); + }, [itemsPerRow, rows?.length]); useLayoutEffect(() => { - throttledSetTableMeta(containerWidth, data.length, itemType, setTableMeta); - }, [containerWidth, data.length, itemType, throttledSetTableMeta]); + throttledSetTableMeta(containerWidth, data.length, setTableMeta); + }, [containerWidth, data.length, throttledSetTableMeta]); const controls = useDefaultItemListControls(); @@ -620,6 +619,7 @@ export const ItemGridList = ({ onScrollEnd={onScrollEnd ?? (() => {})} outerRef={outerRef} ref={listRef} + rows={rows} tableMeta={tableMeta} width={width} /> @@ -638,7 +638,7 @@ export const ItemGridList = ({ const ListComponent = memo((props: ListChildComponentProps) => { const { index, style } = props; - const { columns, controls, data, enableDrag, gap, itemType } = props.data; + const { columns, controls, data, enableDrag, gap, itemType, rows } = props.data; const items: ReactNode[] = []; const itemCount = data.length; @@ -667,6 +667,7 @@ const ListComponent = memo((props: ListChildComponentProps) => { enableDrag={enableDrag} internalState={props.data.internalState} itemType={itemType} + rows={rows} withControls />
, diff --git a/src/renderer/features/albums/components/album-list-infinite-grid.tsx b/src/renderer/features/albums/components/album-list-infinite-grid.tsx index 4a63fc015..9bed75515 100644 --- a/src/renderer/features/albums/components/album-list-infinite-grid.tsx +++ b/src/renderer/features/albums/components/album-list-infinite-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types'; @@ -52,6 +53,8 @@ export const AlbumListInfiniteGrid = forwardRef enabled: saveScrollOffset, }); + const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM); + return ( itemType={LibraryItem.ALBUM} onRangeChanged={onRangeChanged} onScrollEnd={handleOnScrollEnd} + rows={rows} /> ); }, diff --git a/src/renderer/features/albums/components/album-list-paginated-grid.tsx b/src/renderer/features/albums/components/album-list-paginated-grid.tsx index 48af9cf34..6cb7b2e55 100644 --- a/src/renderer/features/albums/components/album-list-paginated-grid.tsx +++ b/src/renderer/features/albums/components/album-list-paginated-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination'; @@ -15,6 +16,7 @@ import { LibraryItem, SortOrder, } from '/@/shared/types/domain-types'; +import { ItemListKey } from '/@/shared/types/types'; interface AlbumListPaginatedGridProps extends ItemListGridComponentProps {} @@ -55,6 +57,8 @@ export const AlbumListPaginatedGrid = forwardRef ); diff --git a/src/renderer/features/artists/components/album-artist-list-infinite-grid.tsx b/src/renderer/features/artists/components/album-artist-list-infinite-grid.tsx index 6cde6e78f..47710b3f3 100644 --- a/src/renderer/features/artists/components/album-artist-list-infinite-grid.tsx +++ b/src/renderer/features/artists/components/album-artist-list-infinite-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types'; @@ -15,7 +16,8 @@ import { } from '/@/shared/types/domain-types'; import { ItemListKey } from '/@/shared/types/types'; -interface AlbumArtistListInfiniteGridProps extends ItemListGridComponentProps {} +interface AlbumArtistListInfiniteGridProps + extends ItemListGridComponentProps {} export const AlbumArtistListInfiniteGrid = forwardRef( ( @@ -53,6 +55,8 @@ export const AlbumArtistListInfiniteGrid = forwardRef ); }, diff --git a/src/renderer/features/artists/components/album-artist-list-paginated-grid.tsx b/src/renderer/features/artists/components/album-artist-list-paginated-grid.tsx index 504c2630c..6cb00e0ff 100644 --- a/src/renderer/features/artists/components/album-artist-list-paginated-grid.tsx +++ b/src/renderer/features/artists/components/album-artist-list-paginated-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination'; @@ -15,6 +16,7 @@ import { LibraryItem, SortOrder, } from '/@/shared/types/domain-types'; +import { ItemListKey } from '/@/shared/types/types'; interface AlbumArtistListPaginatedGridProps extends ItemListGridComponentProps {} @@ -57,6 +59,8 @@ export const AlbumArtistListPaginatedGrid = forwardRef ); diff --git a/src/renderer/features/artists/components/artist-list-infinite-grid.tsx b/src/renderer/features/artists/components/artist-list-infinite-grid.tsx index 817c68c6f..99bcab1e1 100644 --- a/src/renderer/features/artists/components/artist-list-infinite-grid.tsx +++ b/src/renderer/features/artists/components/artist-list-infinite-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types'; @@ -53,6 +54,8 @@ export const ArtistListInfiniteGrid = forwardRef ); }, diff --git a/src/renderer/features/artists/components/artist-list-paginated-grid.tsx b/src/renderer/features/artists/components/artist-list-paginated-grid.tsx index 4471051dc..04957e19c 100644 --- a/src/renderer/features/artists/components/artist-list-paginated-grid.tsx +++ b/src/renderer/features/artists/components/artist-list-paginated-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination'; @@ -15,6 +16,7 @@ import { LibraryItem, SortOrder, } from '/@/shared/types/domain-types'; +import { ItemListKey } from '/@/shared/types/types'; interface ArtistListPaginatedGridProps extends ItemListGridComponentProps {} @@ -56,6 +58,8 @@ export const ArtistListPaginatedGrid = forwardRef ); diff --git a/src/renderer/features/genres/components/genre-list-infinite-grid.tsx b/src/renderer/features/genres/components/genre-list-infinite-grid.tsx index 667e9a82c..8924f2cf1 100644 --- a/src/renderer/features/genres/components/genre-list-infinite-grid.tsx +++ b/src/renderer/features/genres/components/genre-list-infinite-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types'; @@ -53,6 +54,8 @@ export const GenreListInfiniteGrid = forwardRef enabled: saveScrollOffset, }); + const rows = useGridRows(LibraryItem.GENRE, ItemListKey.GENRE); + return ( itemType={LibraryItem.GENRE} onRangeChanged={onRangeChanged} onScrollEnd={handleOnScrollEnd} + rows={rows} /> ); }, diff --git a/src/renderer/features/genres/components/genre-list-paginated-grid.tsx b/src/renderer/features/genres/components/genre-list-paginated-grid.tsx index b334c4edc..cbf646723 100644 --- a/src/renderer/features/genres/components/genre-list-paginated-grid.tsx +++ b/src/renderer/features/genres/components/genre-list-paginated-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination'; @@ -15,6 +16,7 @@ import { LibraryItem, SortOrder, } from '/@/shared/types/domain-types'; +import { ItemListKey } from '/@/shared/types/types'; interface GenreListPaginatedGridProps extends ItemListGridComponentProps {} @@ -56,6 +58,8 @@ export const GenreListPaginatedGrid = forwardRef ); diff --git a/src/renderer/features/shared/components/grid-config.tsx b/src/renderer/features/shared/components/grid-config.tsx index 004367022..c668907a5 100644 --- a/src/renderer/features/shared/components/grid-config.tsx +++ b/src/renderer/features/shared/components/grid-config.tsx @@ -253,9 +253,6 @@ const GridRowConfig = ({ ); }, [data]); - console.log('data', data); - console.log(labelMap); - const handleChangeEnabled = useCallback( (item: ItemGridListRowConfig, checked: boolean) => { const value = useSettingsStore.getState().lists[listKey]?.grid.rows; diff --git a/src/renderer/features/songs/components/song-list-infinite-grid.tsx b/src/renderer/features/songs/components/song-list-infinite-grid.tsx index 88f4d016f..4d6eb045f 100644 --- a/src/renderer/features/songs/components/song-list-infinite-grid.tsx +++ b/src/renderer/features/songs/components/song-list-infinite-grid.tsx @@ -3,6 +3,7 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { useItemListScrollPersist } from '/@/renderer/components/item-list/helpers/use-item-list-scroll-persist'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types'; @@ -48,6 +49,8 @@ export const SongListInfiniteGrid = forwardRef( enabled: saveScrollOffset, }); + const rows = useGridRows(LibraryItem.SONG, ItemListKey.SONG); + return ( ( itemType={LibraryItem.SONG} onRangeChanged={onRangeChanged} onScrollEnd={handleOnScrollEnd} + rows={rows} /> ); }, diff --git a/src/renderer/features/songs/components/song-list-paginated-grid.tsx b/src/renderer/features/songs/components/song-list-paginated-grid.tsx index 986d9932e..7397c16da 100644 --- a/src/renderer/features/songs/components/song-list-paginated-grid.tsx +++ b/src/renderer/features/songs/components/song-list-paginated-grid.tsx @@ -3,12 +3,14 @@ import { forwardRef } from 'react'; import { api } from '/@/renderer/api'; import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader'; +import { useGridRows } from '/@/renderer/components/item-list/helpers/use-grid-rows'; import { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list'; import { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination'; import { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination'; import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types'; import { songsQueries } from '/@/renderer/features/songs/api/songs-api'; import { LibraryItem, SongListQuery, SongListSort, SortOrder } from '/@/shared/types/domain-types'; +import { ItemListKey } from '/@/shared/types/types'; interface SongListPaginatedGridProps extends ItemListGridComponentProps {} @@ -44,6 +46,8 @@ export const SongListPaginatedGrid = forwardRef serverId, }); + const rows = useGridRows(LibraryItem.SONG, ItemListKey.SONG); + return ( gap={gap} itemType={LibraryItem.SONG} ref={ref} + rows={rows} /> ); diff --git a/src/shared/types/drag-and-drop.ts b/src/shared/types/drag-and-drop.ts index d2e71b548..66a4ab521 100644 --- a/src/shared/types/drag-and-drop.ts +++ b/src/shared/types/drag-and-drop.ts @@ -8,6 +8,7 @@ export enum DragTarget { ARTIST = LibraryItem.ARTIST, GENERIC = 'generic', GENRE = LibraryItem.GENRE, + GRID_ROW = 'gridRow', PLAYLIST = LibraryItem.PLAYLIST, QUEUE_SONG = LibraryItem.QUEUE_SONG, SONG = LibraryItem.SONG,