mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
implement item list grid card row customization
This commit is contained in:
@@ -128,6 +128,7 @@
|
|||||||
"yes": "yes",
|
"yes": "yes",
|
||||||
"explicit": "explicit",
|
"explicit": "explicit",
|
||||||
"clean": "clean",
|
"clean": "clean",
|
||||||
|
"gridRows": "grid rows",
|
||||||
"tableColumns": "table columns"
|
"tableColumns": "table columns"
|
||||||
},
|
},
|
||||||
"entity": {
|
"entity": {
|
||||||
@@ -858,6 +859,7 @@
|
|||||||
"label": {
|
"label": {
|
||||||
"actions": "$t(common.action_other)",
|
"actions": "$t(common.action_other)",
|
||||||
"album": "$t(entity.album_one)",
|
"album": "$t(entity.album_one)",
|
||||||
|
"albumCount": "$t(entity.album_other)",
|
||||||
"albumArtist": "$t(entity.albumArtist_one)",
|
"albumArtist": "$t(entity.albumArtist_one)",
|
||||||
"artist": "$t(entity.artist_one)",
|
"artist": "$t(entity.artist_one)",
|
||||||
"biography": "$t(common.biography)",
|
"biography": "$t(common.biography)",
|
||||||
|
|||||||
@@ -96,6 +96,18 @@
|
|||||||
color: var(--theme-colors-foreground-muted);
|
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 {
|
.container.poster {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import formatDuration from 'format-duration';
|
||||||
import { AnimatePresence } from 'motion/react';
|
import { AnimatePresence } from 'motion/react';
|
||||||
import { Fragment, memo, ReactNode, useState } from 'react';
|
import { Fragment, memo, ReactNode, useState } from 'react';
|
||||||
import { generatePath, Link } from 'react-router';
|
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 { ItemControls } from '/@/renderer/components/item-list/types';
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { formatDateAbsolute, formatDateRelative, formatRating } from '/@/renderer/utils/format';
|
||||||
import { Image } from '/@/shared/components/image/image';
|
import { Image } from '/@/shared/components/image/image';
|
||||||
import { Separator } from '/@/shared/components/separator/separator';
|
import { Separator } from '/@/shared/components/separator/separator';
|
||||||
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
import { Skeleton } from '/@/shared/components/skeleton/skeleton';
|
||||||
@@ -26,6 +28,13 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
import { DragOperation, DragTarget } from '/@/shared/types/drag-and-drop';
|
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 {
|
export interface ItemCardProps {
|
||||||
controls?: ItemControls;
|
controls?: ItemControls;
|
||||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||||
@@ -33,16 +42,11 @@ export interface ItemCardProps {
|
|||||||
internalState?: ItemListStateActions;
|
internalState?: ItemListStateActions;
|
||||||
isRound?: boolean;
|
isRound?: boolean;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
|
rows?: DataRow[];
|
||||||
type?: 'compact' | 'default' | 'poster';
|
type?: 'compact' | 'default' | 'poster';
|
||||||
withControls?: boolean;
|
withControls?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataRow = {
|
|
||||||
format: (data: Album | AlbumArtist | Artist | Playlist | Song) => ReactNode | string;
|
|
||||||
id: string;
|
|
||||||
isMuted?: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ItemCard = ({
|
export const ItemCard = ({
|
||||||
controls,
|
controls,
|
||||||
data,
|
data,
|
||||||
@@ -50,11 +54,13 @@ export const ItemCard = ({
|
|||||||
internalState,
|
internalState,
|
||||||
isRound,
|
isRound,
|
||||||
itemType,
|
itemType,
|
||||||
|
rows: providedRows,
|
||||||
type = 'poster',
|
type = 'poster',
|
||||||
withControls,
|
withControls,
|
||||||
}: ItemCardProps) => {
|
}: ItemCardProps) => {
|
||||||
const imageUrl = getImageUrl(data);
|
const imageUrl = getImageUrl(data);
|
||||||
const rows = getDataRows(itemType);
|
const defaultRows = getDataRows();
|
||||||
|
const rows = providedRows && providedRows.length > 0 ? providedRows : defaultRows;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'compact':
|
case 'compact':
|
||||||
@@ -218,9 +224,20 @@ const CompactItemCard = ({
|
|||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
<div className={clsx(styles.detailContainer, styles.compact)}>
|
<div className={clsx(styles.detailContainer, styles.compact)}>
|
||||||
{rows.map((row) => (
|
{rows
|
||||||
<ItemCardRow data={data!} key={row.id} row={row} type="compact" />
|
.filter(
|
||||||
))}
|
(row): row is NonNullable<typeof row> =>
|
||||||
|
row !== null && row !== undefined,
|
||||||
|
)
|
||||||
|
.map((row, index) => (
|
||||||
|
<ItemCardRow
|
||||||
|
data={data!}
|
||||||
|
index={index}
|
||||||
|
key={row.id}
|
||||||
|
row={row}
|
||||||
|
type="compact"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -232,11 +249,21 @@ const CompactItemCard = ({
|
|||||||
<div className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}>
|
<div className={clsx(styles.imageContainer, { [styles.isRound]: isRound })}>
|
||||||
<Skeleton className={styles.image} />
|
<Skeleton className={styles.image} />
|
||||||
<div className={clsx(styles.detailContainer, styles.compact)}>
|
<div className={clsx(styles.detailContainer, styles.compact)}>
|
||||||
{rows.map((row) => (
|
{rows
|
||||||
<div className={styles.row} key={row.id}>
|
.filter(
|
||||||
|
(row): row is NonNullable<typeof row> =>
|
||||||
</div>
|
row !== null && row !== undefined,
|
||||||
))}
|
)
|
||||||
|
.map((row, index) => (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.row, {
|
||||||
|
[styles.muted]: index > 0,
|
||||||
|
})}
|
||||||
|
key={row.id}
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -352,9 +379,20 @@ const DefaultItemCard = ({
|
|||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows.map((row) => (
|
{rows
|
||||||
<ItemCardRow data={data!} key={row.id} row={row} type="default" />
|
.filter(
|
||||||
))}
|
(row): row is NonNullable<typeof row> =>
|
||||||
|
row !== null && row !== undefined,
|
||||||
|
)
|
||||||
|
.map((row, index) => (
|
||||||
|
<ItemCardRow
|
||||||
|
data={data!}
|
||||||
|
index={index}
|
||||||
|
key={row.id}
|
||||||
|
row={row}
|
||||||
|
type="default"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -366,11 +404,20 @@ const DefaultItemCard = ({
|
|||||||
<Skeleton className={styles.image} />
|
<Skeleton className={styles.image} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows.map((row) => (
|
{rows
|
||||||
<div className={styles.row} key={row.id}>
|
.filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== null && row !== undefined,
|
||||||
</div>
|
)
|
||||||
))}
|
.map((row, index) => (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.row, {
|
||||||
|
[styles.muted]: index > 0,
|
||||||
|
})}
|
||||||
|
key={row.id}
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -531,9 +578,20 @@ const PosterItemCard = ({
|
|||||||
</div>
|
</div>
|
||||||
{data && (
|
{data && (
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows.map((row) => (
|
{rows
|
||||||
<ItemCardRow data={data} key={row.id} row={row} type="poster" />
|
.filter(
|
||||||
))}
|
(row): row is NonNullable<typeof row> =>
|
||||||
|
row !== null && row !== undefined,
|
||||||
|
)
|
||||||
|
.map((row, index) => (
|
||||||
|
<ItemCardRow
|
||||||
|
data={data}
|
||||||
|
index={index}
|
||||||
|
key={row.id}
|
||||||
|
row={row}
|
||||||
|
type="poster"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -546,83 +604,253 @@ const PosterItemCard = ({
|
|||||||
<Skeleton className={clsx(styles.image, { [styles.isRound]: isRound })} />
|
<Skeleton className={clsx(styles.image, { [styles.isRound]: isRound })} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.detailContainer}>
|
<div className={styles.detailContainer}>
|
||||||
{rows.map((row) => (
|
{rows
|
||||||
<div className={styles.row} key={row.id}>
|
.filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== null && row !== undefined,
|
||||||
</div>
|
)
|
||||||
))}
|
.map((row, index) => (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.row, {
|
||||||
|
[styles.muted]: index > 0,
|
||||||
|
})}
|
||||||
|
key={row.id}
|
||||||
|
>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getDataRows = (itemType: LibraryItem): DataRow[] => {
|
export const getDataRows = (): DataRow[] => {
|
||||||
switch (itemType) {
|
return [
|
||||||
case LibraryItem.ALBUM:
|
{
|
||||||
return [
|
format: (data) => {
|
||||||
{
|
if ('name' in data && data.name) {
|
||||||
format: (data) => {
|
if ('id' in data && data.id) {
|
||||||
const album = data as Album;
|
if ('_itemType' in data) {
|
||||||
|
switch (data._itemType) {
|
||||||
|
case LibraryItem.ALBUM:
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||||
|
albumId: data.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={generatePath(
|
||||||
|
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL,
|
||||||
|
{
|
||||||
|
albumArtistId: data.id,
|
||||||
|
},
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
case LibraryItem.PLAYLIST:
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={generatePath(AppRoute.PLAYLISTS_DETAIL_SONGS, {
|
||||||
|
playlistId: data.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{data.name}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
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) => (
|
||||||
|
<Fragment key={artist.id}>
|
||||||
|
<Link
|
||||||
|
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||||
|
albumArtistId: artist.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{artist.name}
|
||||||
|
</Link>
|
||||||
|
{index < (data as Album | Song).albumArtists.length - 1 && (
|
||||||
|
<Separator />
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
id: 'albumArtists',
|
||||||
|
isMuted: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: (data) => {
|
||||||
|
if ('artists' in data && Array.isArray(data.artists)) {
|
||||||
|
return (data as Album | Song).artists.map((artist, index) => (
|
||||||
|
<Fragment key={artist.id}>
|
||||||
|
<Link
|
||||||
|
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
||||||
|
albumArtistId: artist.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{artist.name}
|
||||||
|
</Link>
|
||||||
|
{index < (data as Album | Song).artists.length - 1 && <Separator />}
|
||||||
|
</Fragment>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
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 (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
to={generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, {
|
||||||
albumId: album.id,
|
albumId: song.albumId,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{album.name}
|
{song.album}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
id: 'name',
|
return song.album;
|
||||||
},
|
}
|
||||||
{
|
return '';
|
||||||
format: (data) => {
|
},
|
||||||
const album = data as Album;
|
id: 'album',
|
||||||
return album.albumArtists.map((artist, index) => (
|
isMuted: true,
|
||||||
<Fragment key={artist.id}>
|
},
|
||||||
<Link
|
{
|
||||||
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
|
format: (data) => {
|
||||||
albumArtistId: artist.id,
|
if ('songCount' in data && data.songCount !== null) {
|
||||||
})}
|
return String(data.songCount);
|
||||||
>
|
}
|
||||||
{artist.name}
|
return '';
|
||||||
</Link>
|
},
|
||||||
{index < album.albumArtists.length - 1 && <Separator />}
|
id: 'songCount',
|
||||||
</Fragment>
|
},
|
||||||
));
|
{
|
||||||
},
|
format: (data) => {
|
||||||
id: 'albumArtists',
|
if ('albumCount' in data && data.albumCount !== null) {
|
||||||
isMuted: true,
|
return String(data.albumCount);
|
||||||
},
|
}
|
||||||
];
|
return '';
|
||||||
case LibraryItem.ALBUM_ARTIST:
|
},
|
||||||
return [{ format: (data) => (data as AlbumArtist).name, id: 'name' }];
|
id: 'albumCount',
|
||||||
case LibraryItem.ARTIST:
|
},
|
||||||
return [{ format: (data) => (data as Artist).name, id: 'name' }];
|
{
|
||||||
case LibraryItem.PLAYLIST:
|
format: (data) => {
|
||||||
return [{ format: (data) => (data as Playlist).name, id: 'name' }];
|
if (
|
||||||
case LibraryItem.SONG:
|
'userRating' in data &&
|
||||||
return [{ format: (data) => (data as Song).name, id: 'name' }];
|
(data as Album | AlbumArtist | Song).userRating !== null
|
||||||
default:
|
) {
|
||||||
return [];
|
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) => {
|
export const getDataRowsCount = () => {
|
||||||
switch (itemType) {
|
return getDataRows().length;
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => {
|
const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | undefined) => {
|
||||||
@@ -635,20 +863,32 @@ const getImageUrl = (data: Album | AlbumArtist | Artist | Playlist | Song | unde
|
|||||||
|
|
||||||
const ItemCardRow = ({
|
const ItemCardRow = ({
|
||||||
data,
|
data,
|
||||||
|
index,
|
||||||
row,
|
row,
|
||||||
type,
|
type,
|
||||||
}: {
|
}: {
|
||||||
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
data: Album | AlbumArtist | Artist | Playlist | Song | undefined;
|
||||||
|
index: number;
|
||||||
row: DataRow;
|
row: DataRow;
|
||||||
type?: 'compact' | 'default' | 'poster';
|
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) {
|
if (!data) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={clsx(styles.row, {
|
className={clsx(styles.row, alignmentClass, {
|
||||||
[styles.compact]: type === 'compact',
|
[styles.compact]: type === 'compact',
|
||||||
[styles.default]: type === 'default',
|
[styles.default]: type === 'default',
|
||||||
[styles.muted]: row.isMuted,
|
[styles.muted]: isMuted,
|
||||||
[styles.poster]: type === 'poster',
|
[styles.poster]: type === 'poster',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
@@ -659,10 +899,10 @@ const ItemCardRow = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Text
|
<Text
|
||||||
className={clsx(styles.row, {
|
className={clsx(styles.row, alignmentClass, {
|
||||||
[styles.compact]: type === 'compact',
|
[styles.compact]: type === 'compact',
|
||||||
[styles.default]: type === 'default',
|
[styles.default]: type === 'default',
|
||||||
[styles.muted]: row.isMuted,
|
[styles.muted]: isMuted,
|
||||||
[styles.poster]: type === 'poster',
|
[styles.poster]: type === 'poster',
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -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<typeof row> => row !== undefined,
|
||||||
|
);
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
return [rowMap.get('name')].filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== undefined,
|
||||||
|
);
|
||||||
|
case LibraryItem.ARTIST:
|
||||||
|
return [rowMap.get('name')].filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== undefined,
|
||||||
|
);
|
||||||
|
case LibraryItem.PLAYLIST:
|
||||||
|
return [rowMap.get('name')].filter(
|
||||||
|
(row): row is NonNullable<typeof row> => row !== undefined,
|
||||||
|
);
|
||||||
|
case LibraryItem.SONG:
|
||||||
|
return [rowMap.get('name')].filter(
|
||||||
|
(row): row is NonNullable<typeof row> => 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, null | string> = {
|
||||||
|
[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<typeof row> => row !== null && row !== undefined);
|
||||||
|
|
||||||
|
return configuredRows.length > 0 ? configuredRows : allRows;
|
||||||
|
}, [itemType, listKey, gridRowsConfig]);
|
||||||
|
};
|
||||||
@@ -60,6 +60,7 @@ interface VirtualizedGridListProps {
|
|||||||
onScrollEnd?: ItemGridListProps['onScrollEnd'];
|
onScrollEnd?: ItemGridListProps['onScrollEnd'];
|
||||||
outerRef: RefObject<any>;
|
outerRef: RefObject<any>;
|
||||||
ref: RefObject<FixedSizeList<GridItemProps>>;
|
ref: RefObject<FixedSizeList<GridItemProps>>;
|
||||||
|
rows?: ItemCardProps['rows'];
|
||||||
tableMeta: null | {
|
tableMeta: null | {
|
||||||
columnCount: number;
|
columnCount: number;
|
||||||
itemHeight: number;
|
itemHeight: number;
|
||||||
@@ -85,6 +86,7 @@ const VirtualizedGridList = React.memo(
|
|||||||
onScrollEnd,
|
onScrollEnd,
|
||||||
outerRef,
|
outerRef,
|
||||||
ref,
|
ref,
|
||||||
|
rows,
|
||||||
tableMeta,
|
tableMeta,
|
||||||
width,
|
width,
|
||||||
}: VirtualizedGridListProps) => {
|
}: VirtualizedGridListProps) => {
|
||||||
@@ -99,11 +101,13 @@ const VirtualizedGridList = React.memo(
|
|||||||
gap,
|
gap,
|
||||||
internalState,
|
internalState,
|
||||||
itemType,
|
itemType,
|
||||||
|
rows,
|
||||||
tableMeta,
|
tableMeta,
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
tableMeta,
|
tableMeta,
|
||||||
controls,
|
controls,
|
||||||
|
rows,
|
||||||
data,
|
data,
|
||||||
enableDrag,
|
enableDrag,
|
||||||
enableExpansion,
|
enableExpansion,
|
||||||
@@ -167,59 +171,51 @@ const VirtualizedGridList = React.memo(
|
|||||||
|
|
||||||
VirtualizedGridList.displayName = 'VirtualizedGridList';
|
VirtualizedGridList.displayName = 'VirtualizedGridList';
|
||||||
|
|
||||||
const createThrottledSetTableMeta = (itemsPerRow?: number) => {
|
const createThrottledSetTableMeta = (itemsPerRow?: number, rowsCount?: number) => {
|
||||||
return throttle(
|
return throttle((width: number, dataLength: number, setTableMeta: (meta: any) => void) => {
|
||||||
(
|
const isSm = width >= 600;
|
||||||
width: number,
|
const isMd = width >= 768;
|
||||||
dataLength: number,
|
const isLg = width >= 960;
|
||||||
type: LibraryItem,
|
const isXl = width >= 1200;
|
||||||
setTableMeta: (meta: any) => void,
|
const is2xl = width >= 1440;
|
||||||
) => {
|
const is3xl = width >= 1920;
|
||||||
const isSm = width >= 600;
|
const is4xl = width >= 2560;
|
||||||
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) {
|
if (is4xl) {
|
||||||
dynamicItemsPerRow = 12;
|
dynamicItemsPerRow = 12;
|
||||||
} else if (is3xl) {
|
} else if (is3xl) {
|
||||||
dynamicItemsPerRow = 10;
|
dynamicItemsPerRow = 10;
|
||||||
} else if (is2xl) {
|
} else if (is2xl) {
|
||||||
dynamicItemsPerRow = 8;
|
dynamicItemsPerRow = 8;
|
||||||
} else if (isXl) {
|
} else if (isXl) {
|
||||||
dynamicItemsPerRow = 6;
|
dynamicItemsPerRow = 6;
|
||||||
} else if (isLg) {
|
} else if (isLg) {
|
||||||
dynamicItemsPerRow = 5;
|
dynamicItemsPerRow = 5;
|
||||||
} else if (isMd) {
|
} else if (isMd) {
|
||||||
dynamicItemsPerRow = 4;
|
dynamicItemsPerRow = 4;
|
||||||
} else if (isSm) {
|
} else if (isSm) {
|
||||||
dynamicItemsPerRow = 3;
|
dynamicItemsPerRow = 3;
|
||||||
} else {
|
} else {
|
||||||
dynamicItemsPerRow = 2;
|
dynamicItemsPerRow = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
|
const setItemsPerRow = itemsPerRow || dynamicItemsPerRow;
|
||||||
|
|
||||||
const widthPerItem = Number(width) / setItemsPerRow;
|
const widthPerItem = Number(width) / setItemsPerRow;
|
||||||
const itemHeight = widthPerItem + getDataRowsCount(type) * 26;
|
const itemHeight = widthPerItem + (rowsCount || getDataRowsCount()) * 26;
|
||||||
|
|
||||||
if (widthPerItem === 0) {
|
if (widthPerItem === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTableMeta({
|
setTableMeta({
|
||||||
columnCount: setItemsPerRow,
|
columnCount: setItemsPerRow,
|
||||||
itemHeight,
|
itemHeight,
|
||||||
rowCount: Math.ceil(dataLength / setItemsPerRow),
|
rowCount: Math.ceil(dataLength / setItemsPerRow),
|
||||||
});
|
});
|
||||||
},
|
}, 200);
|
||||||
200,
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface GridItemProps {
|
export interface GridItemProps {
|
||||||
@@ -232,6 +228,7 @@ export interface GridItemProps {
|
|||||||
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
gap: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
internalState: ItemListStateActions;
|
internalState: ItemListStateActions;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
|
rows?: ItemCardProps['rows'];
|
||||||
tableMeta: null | {
|
tableMeta: null | {
|
||||||
columnCount: number;
|
columnCount: number;
|
||||||
itemHeight: number;
|
itemHeight: number;
|
||||||
@@ -257,6 +254,7 @@ export interface ItemGridListProps {
|
|||||||
onScroll?: (offset: number, direction: 'down' | 'up') => void;
|
onScroll?: (offset: number, direction: 'down' | 'up') => void;
|
||||||
onScrollEnd?: (offset: number, direction: 'down' | 'up') => void;
|
onScrollEnd?: (offset: number, direction: 'down' | 'up') => void;
|
||||||
ref?: Ref<ItemListHandle>;
|
ref?: Ref<ItemListHandle>;
|
||||||
|
rows?: ItemCardProps['rows'];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ItemGridList = ({
|
export const ItemGridList = ({
|
||||||
@@ -273,6 +271,7 @@ export const ItemGridList = ({
|
|||||||
onScroll,
|
onScroll,
|
||||||
onScrollEnd,
|
onScrollEnd,
|
||||||
ref,
|
ref,
|
||||||
|
rows,
|
||||||
}: ItemGridListProps) => {
|
}: ItemGridListProps) => {
|
||||||
const rootRef = useRef(null);
|
const rootRef = useRef(null);
|
||||||
const outerRef = useRef(null);
|
const outerRef = useRef(null);
|
||||||
@@ -334,12 +333,12 @@ export const ItemGridList = ({
|
|||||||
}, [initialize, tableMeta]);
|
}, [initialize, tableMeta]);
|
||||||
|
|
||||||
const throttledSetTableMeta = useMemo(() => {
|
const throttledSetTableMeta = useMemo(() => {
|
||||||
return createThrottledSetTableMeta(itemsPerRow);
|
return createThrottledSetTableMeta(itemsPerRow, rows?.length);
|
||||||
}, [itemsPerRow]);
|
}, [itemsPerRow, rows?.length]);
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
throttledSetTableMeta(containerWidth, data.length, itemType, setTableMeta);
|
throttledSetTableMeta(containerWidth, data.length, setTableMeta);
|
||||||
}, [containerWidth, data.length, itemType, throttledSetTableMeta]);
|
}, [containerWidth, data.length, throttledSetTableMeta]);
|
||||||
|
|
||||||
const controls = useDefaultItemListControls();
|
const controls = useDefaultItemListControls();
|
||||||
|
|
||||||
@@ -620,6 +619,7 @@ export const ItemGridList = ({
|
|||||||
onScrollEnd={onScrollEnd ?? (() => {})}
|
onScrollEnd={onScrollEnd ?? (() => {})}
|
||||||
outerRef={outerRef}
|
outerRef={outerRef}
|
||||||
ref={listRef}
|
ref={listRef}
|
||||||
|
rows={rows}
|
||||||
tableMeta={tableMeta}
|
tableMeta={tableMeta}
|
||||||
width={width}
|
width={width}
|
||||||
/>
|
/>
|
||||||
@@ -638,7 +638,7 @@ export const ItemGridList = ({
|
|||||||
|
|
||||||
const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
||||||
const { index, style } = props;
|
const { index, style } = props;
|
||||||
const { columns, controls, data, enableDrag, gap, itemType } = props.data;
|
const { columns, controls, data, enableDrag, gap, itemType, rows } = props.data;
|
||||||
|
|
||||||
const items: ReactNode[] = [];
|
const items: ReactNode[] = [];
|
||||||
const itemCount = data.length;
|
const itemCount = data.length;
|
||||||
@@ -667,6 +667,7 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
|||||||
enableDrag={enableDrag}
|
enableDrag={enableDrag}
|
||||||
internalState={props.data.internalState}
|
internalState={props.data.internalState}
|
||||||
itemType={itemType}
|
itemType={itemType}
|
||||||
|
rows={rows}
|
||||||
withControls
|
withControls
|
||||||
/>
|
/>
|
||||||
</div>,
|
</div>,
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
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 { 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 { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list';
|
||||||
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
@@ -52,6 +53,8 @@ export const AlbumListInfiniteGrid = forwardRef<any, AlbumListInfiniteGridProps>
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemGridList
|
<ItemGridList
|
||||||
data={data}
|
data={data}
|
||||||
@@ -64,6 +67,7 @@ export const AlbumListInfiniteGrid = forwardRef<any, AlbumListInfiniteGridProps>
|
|||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
onRangeChanged={onRangeChanged}
|
onRangeChanged={onRangeChanged}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
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 { 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 { 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 { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
LibraryItem,
|
LibraryItem,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumListPaginatedGridProps extends ItemListGridComponentProps<AlbumListQuery> {}
|
interface AlbumListPaginatedGridProps extends ItemListGridComponentProps<AlbumListQuery> {}
|
||||||
|
|
||||||
@@ -55,6 +57,8 @@ export const AlbumListPaginatedGrid = forwardRef<any, AlbumListPaginatedGridProp
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.ALBUM, ItemListKey.ALBUM);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemListWithPagination
|
<ItemListWithPagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
@@ -74,6 +78,7 @@ export const AlbumListPaginatedGrid = forwardRef<any, AlbumListPaginatedGridProp
|
|||||||
itemType={LibraryItem.ALBUM}
|
itemType={LibraryItem.ALBUM}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
</ItemListWithPagination>
|
</ItemListWithPagination>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
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 { 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 { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list';
|
||||||
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
@@ -15,7 +16,8 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
import { ItemListKey } from '/@/shared/types/types';
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumArtistListInfiniteGridProps extends ItemListGridComponentProps<AlbumArtistListQuery> {}
|
interface AlbumArtistListInfiniteGridProps
|
||||||
|
extends ItemListGridComponentProps<AlbumArtistListQuery> {}
|
||||||
|
|
||||||
export const AlbumArtistListInfiniteGrid = forwardRef<any, AlbumArtistListInfiniteGridProps>(
|
export const AlbumArtistListInfiniteGrid = forwardRef<any, AlbumArtistListInfiniteGridProps>(
|
||||||
(
|
(
|
||||||
@@ -53,6 +55,8 @@ export const AlbumArtistListInfiniteGrid = forwardRef<any, AlbumArtistListInfini
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.ALBUM_ARTIST, ItemListKey.ALBUM_ARTIST);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemGridList
|
<ItemGridList
|
||||||
data={data}
|
data={data}
|
||||||
@@ -65,6 +69,7 @@ export const AlbumArtistListInfiniteGrid = forwardRef<any, AlbumArtistListInfini
|
|||||||
itemType={LibraryItem.ALBUM_ARTIST}
|
itemType={LibraryItem.ALBUM_ARTIST}
|
||||||
onRangeChanged={onRangeChanged}
|
onRangeChanged={onRangeChanged}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
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 { 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 { 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 { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
LibraryItem,
|
LibraryItem,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumArtistListPaginatedGridProps
|
interface AlbumArtistListPaginatedGridProps
|
||||||
extends ItemListGridComponentProps<AlbumArtistListQuery> {}
|
extends ItemListGridComponentProps<AlbumArtistListQuery> {}
|
||||||
@@ -57,6 +59,8 @@ export const AlbumArtistListPaginatedGrid = forwardRef<any, AlbumArtistListPagin
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.ALBUM_ARTIST, ItemListKey.ALBUM_ARTIST);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemListWithPagination
|
<ItemListWithPagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
@@ -77,6 +81,7 @@ export const AlbumArtistListPaginatedGrid = forwardRef<any, AlbumArtistListPagin
|
|||||||
itemType={LibraryItem.ALBUM_ARTIST}
|
itemType={LibraryItem.ALBUM_ARTIST}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
</ItemListWithPagination>
|
</ItemListWithPagination>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
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 { 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 { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list';
|
||||||
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
@@ -53,6 +54,8 @@ export const ArtistListInfiniteGrid = forwardRef<any, ArtistListInfiniteGridProp
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.ARTIST, ItemListKey.ARTIST);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemGridList
|
<ItemGridList
|
||||||
data={data}
|
data={data}
|
||||||
@@ -65,6 +68,7 @@ export const ArtistListInfiniteGrid = forwardRef<any, ArtistListInfiniteGridProp
|
|||||||
itemType={LibraryItem.ARTIST}
|
itemType={LibraryItem.ARTIST}
|
||||||
onRangeChanged={onRangeChanged}
|
onRangeChanged={onRangeChanged}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
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 { 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 { 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 { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
LibraryItem,
|
LibraryItem,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface ArtistListPaginatedGridProps extends ItemListGridComponentProps<ArtistListQuery> {}
|
interface ArtistListPaginatedGridProps extends ItemListGridComponentProps<ArtistListQuery> {}
|
||||||
|
|
||||||
@@ -56,6 +58,8 @@ export const ArtistListPaginatedGrid = forwardRef<any, ArtistListPaginatedGridPr
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.ARTIST, ItemListKey.ARTIST);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemListWithPagination
|
<ItemListWithPagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
@@ -76,6 +80,7 @@ export const ArtistListPaginatedGrid = forwardRef<any, ArtistListPaginatedGridPr
|
|||||||
itemType={LibraryItem.ARTIST}
|
itemType={LibraryItem.ARTIST}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
</ItemListWithPagination>
|
</ItemListWithPagination>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
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 { 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 { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list';
|
||||||
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
@@ -53,6 +54,8 @@ export const GenreListInfiniteGrid = forwardRef<any, GenreListInfiniteGridProps>
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.GENRE, ItemListKey.GENRE);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemGridList
|
<ItemGridList
|
||||||
data={data}
|
data={data}
|
||||||
@@ -65,6 +68,7 @@ export const GenreListInfiniteGrid = forwardRef<any, GenreListInfiniteGridProps>
|
|||||||
itemType={LibraryItem.GENRE}
|
itemType={LibraryItem.GENRE}
|
||||||
onRangeChanged={onRangeChanged}
|
onRangeChanged={onRangeChanged}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
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 { 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 { 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 { ItemListWithPagination } from '/@/renderer/components/item-list/item-list-pagination/item-list-pagination';
|
||||||
@@ -15,6 +16,7 @@ import {
|
|||||||
LibraryItem,
|
LibraryItem,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface GenreListPaginatedGridProps extends ItemListGridComponentProps<GenreListQuery> {}
|
interface GenreListPaginatedGridProps extends ItemListGridComponentProps<GenreListQuery> {}
|
||||||
|
|
||||||
@@ -56,6 +58,8 @@ export const GenreListPaginatedGrid = forwardRef<any, GenreListPaginatedGridProp
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.GENRE, ItemListKey.GENRE);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemListWithPagination
|
<ItemListWithPagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
@@ -76,6 +80,7 @@ export const GenreListPaginatedGrid = forwardRef<any, GenreListPaginatedGridProp
|
|||||||
itemType={LibraryItem.GENRE}
|
itemType={LibraryItem.GENRE}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
</ItemListWithPagination>
|
</ItemListWithPagination>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -253,9 +253,6 @@ const GridRowConfig = ({
|
|||||||
);
|
);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
console.log('data', data);
|
|
||||||
console.log(labelMap);
|
|
||||||
|
|
||||||
const handleChangeEnabled = useCallback(
|
const handleChangeEnabled = useCallback(
|
||||||
(item: ItemGridListRowConfig, checked: boolean) => {
|
(item: ItemGridListRowConfig, checked: boolean) => {
|
||||||
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
const value = useSettingsStore.getState().lists[listKey]?.grid.rows;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListInfiniteLoader } from '/@/renderer/components/item-list/helpers/item-list-infinite-loader';
|
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 { 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 { ItemGridList } from '/@/renderer/components/item-list/item-grid-list/item-grid-list';
|
||||||
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
@@ -48,6 +49,8 @@ export const SongListInfiniteGrid = forwardRef<any, SongListInfiniteGridProps>(
|
|||||||
enabled: saveScrollOffset,
|
enabled: saveScrollOffset,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.SONG, ItemListKey.SONG);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemGridList
|
<ItemGridList
|
||||||
data={data}
|
data={data}
|
||||||
@@ -60,6 +63,7 @@ export const SongListInfiniteGrid = forwardRef<any, SongListInfiniteGridProps>(
|
|||||||
itemType={LibraryItem.SONG}
|
itemType={LibraryItem.SONG}
|
||||||
onRangeChanged={onRangeChanged}
|
onRangeChanged={onRangeChanged}
|
||||||
onScrollEnd={handleOnScrollEnd}
|
onScrollEnd={handleOnScrollEnd}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import { forwardRef } from 'react';
|
|||||||
|
|
||||||
import { api } from '/@/renderer/api';
|
import { api } from '/@/renderer/api';
|
||||||
import { useItemListPaginatedLoader } from '/@/renderer/components/item-list/helpers/item-list-paginated-loader';
|
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 { 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 { 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 { useItemListPagination } from '/@/renderer/components/item-list/item-list-pagination/use-item-list-pagination';
|
||||||
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
import { ItemListGridComponentProps } from '/@/renderer/components/item-list/types';
|
||||||
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
import { songsQueries } from '/@/renderer/features/songs/api/songs-api';
|
||||||
import { LibraryItem, SongListQuery, SongListSort, SortOrder } from '/@/shared/types/domain-types';
|
import { LibraryItem, SongListQuery, SongListSort, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
import { ItemListKey } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface SongListPaginatedGridProps extends ItemListGridComponentProps<SongListQuery> {}
|
interface SongListPaginatedGridProps extends ItemListGridComponentProps<SongListQuery> {}
|
||||||
|
|
||||||
@@ -44,6 +46,8 @@ export const SongListPaginatedGrid = forwardRef<any, SongListPaginatedGridProps>
|
|||||||
serverId,
|
serverId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const rows = useGridRows(LibraryItem.SONG, ItemListKey.SONG);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ItemListWithPagination
|
<ItemListWithPagination
|
||||||
currentPage={currentPage}
|
currentPage={currentPage}
|
||||||
@@ -58,6 +62,7 @@ export const SongListPaginatedGrid = forwardRef<any, SongListPaginatedGridProps>
|
|||||||
gap={gap}
|
gap={gap}
|
||||||
itemType={LibraryItem.SONG}
|
itemType={LibraryItem.SONG}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
rows={rows}
|
||||||
/>
|
/>
|
||||||
</ItemListWithPagination>
|
</ItemListWithPagination>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ export enum DragTarget {
|
|||||||
ARTIST = LibraryItem.ARTIST,
|
ARTIST = LibraryItem.ARTIST,
|
||||||
GENERIC = 'generic',
|
GENERIC = 'generic',
|
||||||
GENRE = LibraryItem.GENRE,
|
GENRE = LibraryItem.GENRE,
|
||||||
|
GRID_ROW = 'gridRow',
|
||||||
PLAYLIST = LibraryItem.PLAYLIST,
|
PLAYLIST = LibraryItem.PLAYLIST,
|
||||||
QUEUE_SONG = LibraryItem.QUEUE_SONG,
|
QUEUE_SONG = LibraryItem.QUEUE_SONG,
|
||||||
SONG = LibraryItem.SONG,
|
SONG = LibraryItem.SONG,
|
||||||
|
|||||||
Reference in New Issue
Block a user