Change grid size to items per row

This commit is contained in:
jeffvli 2023-03-28 15:37:50 -07:00
parent e47fcfc62e
commit 219a9ed613
8 changed files with 125 additions and 97 deletions

View file

@ -1,4 +1,4 @@
import { generatePath, Link } from 'react-router-dom'; import { generatePath, useNavigate } from 'react-router-dom';
import { ListChildComponentProps } from 'react-window'; import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { Album, AlbumArtist, Artist, LibraryItem } from '/@/renderer/api/types'; import { Album, AlbumArtist, Artist, LibraryItem } from '/@/renderer/api/types';
@ -31,6 +31,7 @@ const DefaultCardContainer = styled.div<{ $isHidden?: boolean }>`
overflow: hidden; overflow: hidden;
background: var(--card-default-bg); background: var(--card-default-bg);
border-radius: var(--card-default-radius); border-radius: var(--card-default-radius);
cursor: pointer;
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)}; opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
pointer-events: auto; pointer-events: auto;
@ -106,42 +107,44 @@ export const DefaultCard = ({
controls, controls,
isHidden, isHidden,
}: BaseGridCardProps) => { }: BaseGridCardProps) => {
const navigate = useNavigate();
if (data) { if (data) {
const path = generatePath(
controls.route.route,
controls.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
);
return ( return (
<DefaultCardContainer> <DefaultCardContainer
<Link key={`card-${columnIndex}-${listChildProps.index}`}
tabIndex={0} onClick={() => navigate(path)}
to={generatePath( >
controls.route.route, <InnerCardContainer>
controls.route.slugs?.reduce((acc, slug) => { <ImageContainer>
return { <Image
...acc, placeholder={data?.imagePlaceholderUrl || 'var(--placeholder-bg)'}
[slug.slugProperty]: data[slug.idProperty], src={data?.imageUrl}
}; />
}, {}), <GridCardControls
)} handleFavorite={controls.handleFavorite}
> handlePlayQueueAdd={controls.handlePlayQueueAdd}
<InnerCardContainer key={`card-${columnIndex}-${listChildProps.index}`}> itemData={data}
<ImageContainer> itemType={controls.itemType}
<Image />
placeholder={data?.imagePlaceholderUrl || 'var(--placeholder-bg)'} </ImageContainer>
src={data?.imageUrl} <DetailContainer>
/> <CardRows
<GridCardControls data={data}
handleFavorite={controls.handleFavorite} rows={controls.cardRows}
handlePlayQueueAdd={controls.handlePlayQueueAdd} />
itemData={data} </DetailContainer>
itemType={controls.itemType} </InnerCardContainer>
/>
</ImageContainer>
<DetailContainer>
<CardRows
data={data}
rows={controls.cardRows}
/>
</DetailContainer>
</InnerCardContainer>
</Link>
</DefaultCardContainer> </DefaultCardContainer>
); );
} }

View file

@ -1,4 +1,5 @@
import { generatePath, Link } from 'react-router-dom'; import { Stack } from '@mantine/core';
import { generatePath, useNavigate } from 'react-router-dom';
import { ListChildComponentProps } from 'react-window'; import { ListChildComponentProps } from 'react-window';
import styled from 'styled-components'; import styled from 'styled-components';
import { Album, AlbumArtist, Artist, LibraryItem } from '/@/renderer/api/types'; import { Album, AlbumArtist, Artist, LibraryItem } from '/@/renderer/api/types';
@ -27,7 +28,7 @@ const PosterCardContainer = styled.div<{ $isHidden?: boolean }>`
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
height: 100%; height: 100%;
margin: 0.5rem; margin: 1rem;
overflow: hidden; overflow: hidden;
opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)}; opacity: ${({ $isHidden }) => ($isHidden ? 0 : 1)};
pointer-events: auto; pointer-events: auto;
@ -35,16 +36,10 @@ const PosterCardContainer = styled.div<{ $isHidden?: boolean }>`
.card-controls { .card-controls {
opacity: 0; opacity: 0;
} }
`;
&:hover .card-controls { const LinkContainer = styled.div`
opacity: 1; cursor: pointer;
}
&:hover * {
&::before {
opacity: 0.5;
}
}
`; `;
const ImageContainer = styled.div` const ImageContainer = styled.div`
@ -70,6 +65,16 @@ const ImageContainer = styled.div`
content: ''; content: '';
user-select: none; user-select: none;
} }
&:hover {
&::before {
opacity: 0.5;
}
}
&:hover .card-controls {
opacity: 1;
}
`; `;
const Image = styled.img` const Image = styled.img`
@ -91,21 +96,22 @@ export const PosterCard = ({
controls, controls,
isHidden, isHidden,
}: BaseGridCardProps) => { }: BaseGridCardProps) => {
const navigate = useNavigate();
if (data) { if (data) {
const path = generatePath(
controls.route.route,
controls.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
);
return ( return (
<PosterCardContainer key={`card-${columnIndex}-${listChildProps.index}`}> <PosterCardContainer key={`card-${columnIndex}-${listChildProps.index}`}>
<Link <LinkContainer onClick={() => navigate(path)}>
tabIndex={0}
to={generatePath(
controls.route.route,
controls.route.slugs?.reduce((acc, slug) => {
return {
...acc,
[slug.slugProperty]: data[slug.idProperty],
};
}, {}),
)}
>
<ImageContainer> <ImageContainer>
<Image <Image
placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'} placeholder={data?.imagePlaceholderUrl || 'var(--card-default-bg)'}
@ -118,7 +124,7 @@ export const PosterCard = ({
itemType={controls.itemType} itemType={controls.itemType}
/> />
</ImageContainer> </ImageContainer>
</Link> </LinkContainer>
<DetailContainer> <DetailContainer>
<CardRows <CardRows
data={data} data={data}
@ -134,13 +140,24 @@ export const PosterCard = ({
key={`card-${columnIndex}-${listChildProps.index}`} key={`card-${columnIndex}-${listChildProps.index}`}
$isHidden={isHidden} $isHidden={isHidden}
> >
<ImageContainer> <Skeleton
<Skeleton visible
visible radius="sm"
radius="sm" >
/> <ImageContainer />
</ImageContainer> </Skeleton>
<DetailContainer /> <DetailContainer>
<Stack spacing="sm">
{controls.cardRows.map((row) => (
<Skeleton
key={row.arrayProperty}
visible
height={14}
radius="sm"
/>
))}
</Stack>
</DetailContainer>
</PosterCardContainer> </PosterCardContainer>
); );
}; };

View file

@ -56,20 +56,17 @@ export const VirtualInfiniteGrid = forwardRef(
const listRef = useRef<any>(null); const listRef = useRef<any>(null);
const loader = useRef<InfiniteLoader>(null); const loader = useRef<InfiniteLoader>(null);
const sz = itemSize / 2;
const { itemHeight, rowCount, columnCount } = useMemo(() => { const { itemHeight, rowCount, columnCount } = useMemo(() => {
const itemsPerRow = Math.floor(Number(width) / sz!) - 1; const itemsPerRow = itemSize;
const widthPerItem = Number(width) / itemsPerRow - 10; const widthPerItem = Number(width) / itemsPerRow;
const itemHeight = widthPerItem + cardRows.length * 26; const itemHeight = widthPerItem + cardRows.length * 26;
return { return {
columnCount: itemsPerRow, columnCount: itemsPerRow,
itemHeight, itemHeight,
itemWidth: sz,
rowCount: Math.ceil(itemCount / itemsPerRow), rowCount: Math.ceil(itemCount / itemsPerRow),
}; };
}, [cardRows.length, itemCount, sz, width]); }, [cardRows.length, itemCount, itemSize, width]);
const isItemLoaded = useCallback( const isItemLoaded = useCallback(
(index: number) => { (index: number) => {

View file

@ -318,7 +318,7 @@ export const AlbumListContent = ({ itemCount, gridRef, tableRef }: AlbumListCont
itemCount={itemCount || 0} itemCount={itemCount || 0}
itemData={itemData} itemData={itemData}
itemGap={20} itemGap={20}
itemSize={150 + (grid?.size || 0)} itemSize={grid?.itemsPerRow || 5}
itemType={LibraryItem.ALBUM} itemType={LibraryItem.ALBUM}
loading={itemCount === undefined || itemCount === null} loading={itemCount === undefined || itemCount === null}
minimumBatchSize={40} minimumBatchSize={40}

View file

@ -331,7 +331,7 @@ export const AlbumListHeaderFilters = ({
if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) { if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
setTable({ data: { rowHeight: e }, key: 'album' }); setTable({ data: { rowHeight: e }, key: 'album' });
} else { } else {
setGrid({ data: { size: e }, key: 'album' }); setGrid({ data: { itemsPerRow: e }, key: 'album' });
} }
}; };
@ -564,17 +564,21 @@ export const AlbumListHeaderFilters = ({
Table (paginated) Table (paginated)
</DropdownMenu.Item> </DropdownMenu.Item>
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Label>Item size</DropdownMenu.Label> <DropdownMenu.Label>
{display === ListDisplayType.CARD || display === ListDisplayType.POSTER
? 'Items per row'
: 'Item size'}
</DropdownMenu.Label>
<DropdownMenu.Item closeMenuOnClick={false}> <DropdownMenu.Item closeMenuOnClick={false}>
<Slider <Slider
defaultValue={ defaultValue={
display === ListDisplayType.CARD || display === ListDisplayType.POSTER display === ListDisplayType.CARD || display === ListDisplayType.POSTER
? grid?.size || 0 ? grid?.itemsPerRow || 0
: table.rowHeight : table.rowHeight
} }
label={null} label={null}
max={400} max={14}
min={-25} min={2}
onChange={debouncedHandleItemSize} onChange={debouncedHandleItemSize}
/> />
</DropdownMenu.Item> </DropdownMenu.Item>

View file

@ -276,7 +276,7 @@ export const AlbumArtistListContent = ({ gridRef, tableRef }: AlbumArtistListCon
itemCount={checkAlbumArtistList?.data?.totalRecordCount || 0} itemCount={checkAlbumArtistList?.data?.totalRecordCount || 0}
itemData={itemData} itemData={itemData}
itemGap={20} itemGap={20}
itemSize={150 + (grid?.size || 0)} itemSize={grid?.itemsPerRow || 5}
itemType={LibraryItem.ALBUM_ARTIST} itemType={LibraryItem.ALBUM_ARTIST}
loading={checkAlbumArtistList.isLoading} loading={checkAlbumArtistList.isLoading}
minimumBatchSize={40} minimumBatchSize={40}

View file

@ -96,7 +96,7 @@ export const AlbumArtistListHeaderFilters = ({
if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) { if (display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) {
setTable({ data: { rowHeight: e }, key: pageKey }); setTable({ data: { rowHeight: e }, key: pageKey });
} else { } else {
setGrid({ data: { size: e }, key: pageKey }); setGrid({ data: { itemsPerRow: e }, key: pageKey });
} }
}; };
@ -432,17 +432,23 @@ export const AlbumArtistListHeaderFilters = ({
<DropdownMenu.Divider /> <DropdownMenu.Divider />
<DropdownMenu.Label>Item size</DropdownMenu.Label> <DropdownMenu.Label>Item size</DropdownMenu.Label>
<DropdownMenu.Item closeMenuOnClick={false}> <DropdownMenu.Item closeMenuOnClick={false}>
<Slider {display === ListDisplayType.CARD || display === ListDisplayType.POSTER ? (
defaultValue={ <Slider
display === ListDisplayType.CARD || display === ListDisplayType.POSTER defaultValue={grid?.itemsPerRow}
? grid?.size label={null}
: table.rowHeight max={10}
} min={2}
label={null} onChange={debouncedHandleItemSize}
max={400} />
min={-50} ) : (
onChange={debouncedHandleItemSize} <Slider
/> defaultValue={table.rowHeight}
label={null}
max={100}
min={30}
onChange={debouncedHandleItemSize}
/>
)}
</DropdownMenu.Item> </DropdownMenu.Item>
{(display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) && ( {(display === ListDisplayType.TABLE || display === ListDisplayType.TABLE_PAGINATED) && (
<> <>

View file

@ -34,8 +34,8 @@ type ListTableProps = {
} & DataTableProps; } & DataTableProps;
type ListGridProps = { type ListGridProps = {
itemsPerRow?: number;
scrollOffset?: number; scrollOffset?: number;
size?: number;
}; };
type ItemProps<TFilter = any> = { type ItemProps<TFilter = any> = {
@ -165,8 +165,9 @@ export const useListStore = create<ListSlice>()(
state.detail[args.key] = { state.detail[args.key] = {
filter: {} as FilterType, filter: {} as FilterType,
grid: { grid: {
itemsPerRow:
state.item[page as keyof ListState['item']].grid?.itemsPerRow || 5,
scrollOffset: 0, scrollOffset: 0,
size: state.item[page as keyof ListState['item']].grid?.size || 200,
}, },
table: { table: {
pagination: { pagination: {
@ -282,7 +283,7 @@ export const useListStore = create<ListSlice>()(
sortBy: AlbumListSort.RECENTLY_ADDED, sortBy: AlbumListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
}, },
grid: { scrollOffset: 0, size: 200 }, grid: { itemsPerRow: 5, scrollOffset: 0 },
table: { table: {
autoFit: true, autoFit: true,
columns: [ columns: [
@ -323,7 +324,7 @@ export const useListStore = create<ListSlice>()(
sortBy: AlbumArtistListSort.NAME, sortBy: AlbumArtistListSort.NAME,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
}, },
grid: { scrollOffset: 0, size: 200 }, grid: { itemsPerRow: 5, scrollOffset: 0 },
table: { table: {
autoFit: true, autoFit: true,
columns: [ columns: [
@ -409,7 +410,7 @@ export const useListStore = create<ListSlice>()(
sortBy: SongListSort.RECENTLY_ADDED, sortBy: SongListSort.RECENTLY_ADDED,
sortOrder: SortOrder.DESC, sortOrder: SortOrder.DESC,
}, },
grid: { scrollOffset: 0, size: 0 }, grid: { itemsPerRow: 5, scrollOffset: 0 },
table: { table: {
autoFit: true, autoFit: true,
columns: [ columns: [
@ -470,7 +471,7 @@ export const useListStore = create<ListSlice>()(
Object.entries(state).filter(([key]) => !['detail'].includes(key)), Object.entries(state).filter(([key]) => !['detail'].includes(key)),
); );
}, },
version: 1, version: 2,
}, },
), ),
); );