From f09ad1da89f02b160b96f7be6219f23d06e7d694 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 7 Aug 2023 14:42:47 -0700 Subject: [PATCH] Add dynamic grid sizing --- .../virtual-grid/grid-card/default-card.tsx | 7 +++-- .../grid-card/grid-card-controls.tsx | 2 +- .../virtual-grid/grid-card/index.tsx | 2 ++ .../virtual-grid/grid-card/poster-card.tsx | 11 +++++-- .../virtual-grid/virtual-infinite-grid.tsx | 2 +- .../components/album-list-grid-view.tsx | 4 +-- .../components/album-list-header-filters.tsx | 29 ++++++++++++++----- .../album-artist-list-grid-view.tsx | 4 +-- .../album-artist-list-header-filters.tsx | 27 +++++++++++++---- .../hooks/use-handle-context-menu.ts | 2 +- .../components/genre-list-grid-view.tsx | 4 +-- .../components/genre-list-header-filters.tsx | 29 ++++++++++++++----- .../components/playlist-list-grid-view.tsx | 4 +-- .../playlist-list-header-filters.tsx | 29 ++++++++++++++----- src/renderer/store/list.store.ts | 23 +++++++++------ 15 files changed, 127 insertions(+), 52 deletions(-) diff --git a/src/renderer/components/virtual-grid/grid-card/default-card.tsx b/src/renderer/components/virtual-grid/grid-card/default-card.tsx index 21648eb2..7b1dca3c 100644 --- a/src/renderer/components/virtual-grid/grid-card/default-card.tsx +++ b/src/renderer/components/virtual-grid/grid-card/default-card.tsx @@ -20,6 +20,7 @@ interface BaseGridCardProps { itemType: LibraryItem; }) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; + itemGap: number; itemType: LibraryItem; playButtonBehavior: Play; resetInfiniteLoaderCache: () => void; @@ -30,12 +31,12 @@ interface BaseGridCardProps { listChildProps: Omit; } -const DefaultCardContainer = styled.div<{ $isHidden?: boolean }>` +const DefaultCardContainer = styled.div<{ $isHidden?: boolean; $itemGap: number }>` display: flex; flex-direction: column; width: 100%; height: calc(100% - 2rem); - margin: 0.5rem; + margin: ${({ $itemGap }) => $itemGap}px; overflow: hidden; background: var(--card-default-bg); border-radius: var(--card-default-radius); @@ -172,6 +173,7 @@ export const DefaultCard = ({ return ( navigate(path)} > @@ -221,6 +223,7 @@ export const DefaultCard = ({ diff --git a/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx b/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx index 543bb666..aeef92c2 100644 --- a/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx +++ b/src/renderer/components/virtual-grid/grid-card/grid-card-controls.tsx @@ -123,7 +123,7 @@ export const GridCardControls = ({ handlePlayQueueAdd?: (options: PlayQueueAddOptions) => void; itemData: any; itemType: LibraryItem; - resetInfiniteLoaderCache: () => void; + resetInfiniteLoaderCache?: () => void; }) => { const [isFavorite, setIsFavorite] = useState(itemData?.userFavorite); const playButtonBehavior = usePlayButtonBehavior(); diff --git a/src/renderer/components/virtual-grid/grid-card/index.tsx b/src/renderer/components/virtual-grid/grid-card/index.tsx index 2fa29e79..b9abfa5d 100644 --- a/src/renderer/components/virtual-grid/grid-card/index.tsx +++ b/src/renderer/components/virtual-grid/grid-card/index.tsx @@ -12,6 +12,7 @@ export const GridCard = memo(({ data, index, style }: ListChildComponentProps) = cardRows, itemData, itemType, + itemGap, playButtonBehavior, handlePlayQueueAdd, handleFavorite, @@ -40,6 +41,7 @@ export const GridCard = memo(({ data, index, style }: ListChildComponentProps) = cardRows, handleFavorite, handlePlayQueueAdd, + itemGap, itemType, playButtonBehavior, resetInfiniteLoaderCache, diff --git a/src/renderer/components/virtual-grid/grid-card/poster-card.tsx b/src/renderer/components/virtual-grid/grid-card/poster-card.tsx index fc8021cc..f11985f5 100644 --- a/src/renderer/components/virtual-grid/grid-card/poster-card.tsx +++ b/src/renderer/components/virtual-grid/grid-card/poster-card.tsx @@ -20,6 +20,7 @@ interface BaseGridCardProps { itemType: LibraryItem; }) => void; handlePlayQueueAdd: (options: PlayQueueAddOptions) => void; + itemGap: number; itemType: LibraryItem; playButtonBehavior: Play; resetInfiniteLoaderCache: () => void; @@ -30,12 +31,12 @@ interface BaseGridCardProps { listChildProps: Omit; } -const PosterCardContainer = styled.div<{ $isHidden?: boolean }>` +const PosterCardContainer = styled.div<{ $isHidden?: boolean; $itemGap: number }>` display: flex; flex-direction: column; width: 100%; height: 100%; - margin: 1rem; + margin: ${({ $itemGap }) => $itemGap}px; overflow: hidden; opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)}; pointer-events: auto; @@ -158,7 +159,10 @@ export const PosterCard = ({ } return ( - + navigate(path)}> {data?.imageUrl ? ( @@ -205,6 +209,7 @@ export const PosterCard = ({ (fetchInitialData?.() || []); const { itemHeight, rowCount, columnCount } = useMemo(() => { - const itemsPerRow = itemSize; + const itemsPerRow = width ? Math.floor(width / itemSize) : 5; const widthPerItem = Number(width) / itemsPerRow; const itemHeight = widthPerItem + cardRows.length * 26; diff --git a/src/renderer/features/albums/components/album-list-grid-view.tsx b/src/renderer/features/albums/components/album-list-grid-view.tsx index 03cec70c..a9d9c097 100644 --- a/src/renderer/features/albums/components/album-list-grid-view.tsx +++ b/src/renderer/features/albums/components/album-list-grid-view.tsx @@ -224,8 +224,8 @@ export const AlbumListGridView = ({ gridRef, itemCount }: any) => { height={height} initialScrollOffset={initialScrollOffset} itemCount={itemCount || 0} - itemGap={20} - itemSize={grid?.itemsPerRow || 5} + itemGap={grid?.itemGap ?? 10} + itemSize={grid?.itemSize || 200} itemType={LibraryItem.ALBUM} loading={itemCount === undefined || itemCount === null} minimumBatchSize={40} diff --git a/src/renderer/features/albums/components/album-list-header-filters.tsx b/src/renderer/features/albums/components/album-list-header-filters.tsx index 076aa0e8..8051ddf3 100644 --- a/src/renderer/features/albums/components/album-list-header-filters.tsx +++ b/src/renderer/features/albums/components/album-list-header-filters.tsx @@ -207,12 +207,16 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil const handleItemSize = (e: number) => { if (isGrid) { - setGrid({ data: { itemsPerRow: e }, key: pageKey }); + setGrid({ data: { itemSize: e }, key: pageKey }); } else { setTable({ data: { rowHeight: e }, key: pageKey }); } }; + const handleItemGap = (e: number) => { + setGrid({ data: { itemGap: e }, key: pageKey }); + }; + const handleSetViewType = useCallback( (e: MouseEvent) => { if (!e.currentTarget?.value) return; @@ -449,17 +453,28 @@ export const AlbumListHeaderFilters = ({ gridRef, tableRef }: AlbumListHeaderFil Table (paginated) */} - - {isGrid ? 'Items per row' : 'Item size'} - + Item size + {isGrid && ( + <> + Item gap + + + + + )} {(display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) && ( <> diff --git a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx index 174ccb00..4fa26025 100644 --- a/src/renderer/features/artists/components/album-artist-list-grid-view.tsx +++ b/src/renderer/features/artists/components/album-artist-list-grid-view.tsx @@ -158,8 +158,8 @@ export const AlbumArtistListGridView = ({ itemCount, gridRef }: AlbumArtistListG height={height} initialScrollOffset={grid?.scrollOffset || 0} itemCount={itemCount || 0} - itemGap={20} - itemSize={grid?.itemsPerRow || 5} + itemGap={grid?.itemGap ?? 10} + itemSize={grid?.itemSize || 200} itemType={LibraryItem.ALBUM_ARTIST} loading={itemCount === undefined || itemCount === null} minimumBatchSize={40} diff --git a/src/renderer/features/artists/components/album-artist-list-header-filters.tsx b/src/renderer/features/artists/components/album-artist-list-header-filters.tsx index 51fc548e..54e337d7 100644 --- a/src/renderer/features/artists/components/album-artist-list-header-filters.tsx +++ b/src/renderer/features/artists/components/album-artist-list-header-filters.tsx @@ -83,10 +83,14 @@ export const AlbumArtistListHeaderFilters = ({ if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) { setTable({ data: { rowHeight: e }, key: pageKey }); } else { - setGrid({ data: { itemsPerRow: e }, key: pageKey }); + setGrid({ data: { itemSize: e }, key: pageKey }); } }; + const handleItemGap = (e: number) => { + setGrid({ data: { itemGap: e }, key: pageKey }); + }; + const debouncedHandleItemSize = debounce(handleItemSize, 20); const fetch = useCallback( @@ -422,22 +426,33 @@ export const AlbumArtistListHeaderFilters = ({ {display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? ( ) : ( )} + {isGrid && ( + <> + Item gap + + + + + )} {!isGrid && ( <> Table Columns diff --git a/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts b/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts index e4ae1d95..5606d24e 100644 --- a/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts +++ b/src/renderer/features/context-menu/hooks/use-handle-context-menu.ts @@ -87,7 +87,7 @@ export const useHandleGeneralContextMenu = ( export const useHandleGridContextMenu = ( itemType: LibraryItem, contextMenuItems: SetContextMenuItems, - resetGridCache: () => void, + resetGridCache?: () => void, context?: any, ) => { const handleContextMenu = ( diff --git a/src/renderer/features/genres/components/genre-list-grid-view.tsx b/src/renderer/features/genres/components/genre-list-grid-view.tsx index 4a70aac4..e3dc5f43 100644 --- a/src/renderer/features/genres/components/genre-list-grid-view.tsx +++ b/src/renderer/features/genres/components/genre-list-grid-view.tsx @@ -97,8 +97,8 @@ export const GenreListGridView = ({ gridRef, itemCount }: any) => { height={height} initialScrollOffset={initialScrollOffset} itemCount={itemCount || 0} - itemGap={20} - itemSize={grid?.itemsPerRow || 5} + itemGap={grid?.itemGap ?? 10} + itemSize={grid?.itemSize || 200} itemType={LibraryItem.GENRE} loading={itemCount === undefined || itemCount === null} minimumBatchSize={40} diff --git a/src/renderer/features/genres/components/genre-list-header-filters.tsx b/src/renderer/features/genres/components/genre-list-header-filters.tsx index 1458ed05..3ef00d97 100644 --- a/src/renderer/features/genres/components/genre-list-header-filters.tsx +++ b/src/renderer/features/genres/components/genre-list-header-filters.tsx @@ -109,12 +109,16 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil const handleItemSize = (e: number) => { if (isGrid) { - setGrid({ data: { itemsPerRow: e }, key: pageKey }); + setGrid({ data: { itemSize: e }, key: pageKey }); } else { setTable({ data: { rowHeight: e }, key: pageKey }); } }; + const handleItemGap = (e: number) => { + setGrid({ data: { itemGap: e }, key: pageKey }); + }; + const handleSetViewType = useCallback( (e: MouseEvent) => { if (!e.currentTarget?.value) return; @@ -256,17 +260,28 @@ export const GenreListHeaderFilters = ({ gridRef, tableRef }: GenreListHeaderFil Table - - {isGrid ? 'Items per row' : 'Item size'} - + Item size + {isGrid && ( + <> + Item gap + + + + + )} {(display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) && ( <> diff --git a/src/renderer/features/playlists/components/playlist-list-grid-view.tsx b/src/renderer/features/playlists/components/playlist-list-grid-view.tsx index 5cb75f75..7f478a4d 100644 --- a/src/renderer/features/playlists/components/playlist-list-grid-view.tsx +++ b/src/renderer/features/playlists/components/playlist-list-grid-view.tsx @@ -141,8 +141,8 @@ export const PlaylistListGridView = ({ gridRef, itemCount }: PlaylistListGridVie height={height} initialScrollOffset={grid?.scrollOffset || 0} itemCount={itemCount || 0} - itemGap={20} - itemSize={grid?.itemsPerRow || 5} + itemGap={grid?.itemGap ?? 10} + itemSize={grid?.itemSize || 200} itemType={LibraryItem.PLAYLIST} loading={itemCount === undefined || itemCount === null} minimumBatchSize={40} diff --git a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx index 6b72ab7b..60c2e216 100644 --- a/src/renderer/features/playlists/components/playlist-list-header-filters.tsx +++ b/src/renderer/features/playlists/components/playlist-list-header-filters.tsx @@ -230,12 +230,16 @@ export const PlaylistListHeaderFilters = ({ const handleItemSize = (e: number) => { if (isGrid) { - setGrid({ data: { itemsPerRow: e }, key: pageKey }); + setGrid({ data: { itemSize: e }, key: pageKey }); } else { setTable({ data: { rowHeight: e }, key: pageKey }); } }; + const handleItemGap = (e: number) => { + setGrid({ data: { itemGap: e }, key: pageKey }); + }; + const handleRefresh = () => { queryClient.invalidateQueries(queryKeys.playlists.list(server?.id || '', filter)); handleFilterChange(filter); @@ -344,16 +348,27 @@ export const PlaylistListHeaderFilters = ({ Table (paginated) */} - - {isGrid ? 'Items per row' : 'Item size'} - + Item size + {isGrid && ( + <> + Item gap + + + + + )} {!isGrid && ( <> diff --git a/src/renderer/store/list.store.ts b/src/renderer/store/list.store.ts index c3f64c2e..c4bd24d3 100644 --- a/src/renderer/store/list.store.ts +++ b/src/renderer/store/list.store.ts @@ -43,6 +43,8 @@ export type ListTableProps = { } & DataTableProps; export type ListGridProps = { + itemGap?: number; + itemSize?: number; itemsPerRow?: number; scrollOffset?: number; }; @@ -222,9 +224,12 @@ export const useListStore = create()( state.detail[args.key] = { filter: {} as FilterType, grid: { - itemsPerRow: + itemGap: state.item[page as keyof ListState['item']].grid - ?.itemsPerRow || 5, + ?.itemGap || 10, + itemSize: + state.item[page as keyof ListState['item']].grid + ?.itemSize || 5, scrollOffset: 0, }, table: { @@ -342,7 +347,7 @@ export const useListStore = create()( sortBy: AlbumListSort.RECENTLY_ADDED, sortOrder: SortOrder.DESC, }, - grid: { itemsPerRow: 5, scrollOffset: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [ @@ -383,7 +388,7 @@ export const useListStore = create()( sortBy: AlbumArtistListSort.NAME, sortOrder: SortOrder.DESC, }, - grid: { itemsPerRow: 5, scrollOffset: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [ @@ -412,7 +417,7 @@ export const useListStore = create()( sortBy: AlbumListSort.RECENTLY_ADDED, sortOrder: SortOrder.DESC, }, - grid: { itemsPerRow: 5, scrollOffset: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [ @@ -453,7 +458,7 @@ export const useListStore = create()( sortBy: SongListSort.RECENTLY_ADDED, sortOrder: SortOrder.DESC, }, - grid: { itemsPerRow: 5, scrollOffset: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [ @@ -510,7 +515,7 @@ export const useListStore = create()( sortBy: GenreListSort.NAME, sortOrder: SortOrder.ASC, }, - grid: { itemsPerRow: 5, scrollOffset: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [ @@ -539,7 +544,7 @@ export const useListStore = create()( sortBy: PlaylistListSort.NAME, sortOrder: SortOrder.DESC, }, - grid: { scrollOffset: 0, size: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [ @@ -572,7 +577,7 @@ export const useListStore = create()( sortBy: SongListSort.RECENTLY_ADDED, sortOrder: SortOrder.DESC, }, - grid: { itemsPerRow: 5, scrollOffset: 0 }, + grid: { itemGap: 10, itemSize: 200, scrollOffset: 0 }, table: { autoFit: true, columns: [