mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-18 16:36:29 +02:00
add additional album group metadata rows
This commit is contained in:
@@ -1231,6 +1231,8 @@
|
|||||||
"general": {
|
"general": {
|
||||||
"advancedSettings": "Advanced settings",
|
"advancedSettings": "Advanced settings",
|
||||||
"albumGroupConfig": "Album Group configuration",
|
"albumGroupConfig": "Album Group configuration",
|
||||||
|
"albumGroupMetadata": "Metadata fields",
|
||||||
|
"albumGroupShowFavoriteRating": "Show favorites and ratings",
|
||||||
"albumImageSize": "Album image size",
|
"albumImageSize": "Album image size",
|
||||||
"autoFitColumns": "Auto fit columns",
|
"autoFitColumns": "Auto fit columns",
|
||||||
"autosize": "Autosize",
|
"autosize": "Autosize",
|
||||||
|
|||||||
@@ -43,8 +43,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-rows: auto auto auto;
|
flex-direction: column;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
@@ -71,7 +71,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.artist-name {
|
.metadata-row {
|
||||||
font-size: var(--theme-font-size-xs);
|
font-size: var(--theme-font-size-xs);
|
||||||
line-height: 1.25;
|
line-height: 1.25;
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
|
|||||||
@@ -1,26 +1,33 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { ReactElement, useLayoutEffect, useRef, useState } from 'react';
|
import { ReactElement, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, Link } from 'react-router';
|
import { generatePath, Link } from 'react-router';
|
||||||
|
|
||||||
import imageColumnStyles from '../item-detail-list/columns/image-column.module.css';
|
import imageColumnStyles from '../item-detail-list/columns/image-column.module.css';
|
||||||
import { AlbumGroupControls } from './album-group-controls';
|
import { AlbumGroupControls } from './album-group-controls';
|
||||||
import styles from './album-group-header.module.css';
|
import styles from './album-group-header.module.css';
|
||||||
|
import { AlbumGroupMetadata, renderAlbumGroupMetadataItem } from './album-group-metadata';
|
||||||
import { TableItemSize } from './item-table-list';
|
import { TableItemSize } from './item-table-list';
|
||||||
|
|
||||||
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
import { ItemImage } from '/@/renderer/components/item-image/item-image';
|
||||||
import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists';
|
|
||||||
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
|
import { PlayButton } from '/@/renderer/features/shared/components/play-button';
|
||||||
import {
|
import {
|
||||||
LONG_PRESS_PLAY_BEHAVIOR,
|
LONG_PRESS_PLAY_BEHAVIOR,
|
||||||
PlayTooltip,
|
PlayTooltip,
|
||||||
} from '/@/renderer/features/shared/components/play-button-group';
|
} from '/@/renderer/features/shared/components/play-button-group';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
import { useAlbumGroupImageSize, usePlayButtonBehavior } from '/@/renderer/store';
|
import {
|
||||||
|
useAlbumGroupImageSize,
|
||||||
|
useAlbumGroupItems,
|
||||||
|
useAlbumGroupShowFavoriteRating,
|
||||||
|
usePlayButtonBehavior,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
import { LibraryItem, Song } from '/@/shared/types/domain-types';
|
||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
interface AlbumGroupHeaderProps {
|
interface AlbumGroupHeaderProps {
|
||||||
groupRowCount?: number;
|
groupRowCount?: number;
|
||||||
|
metadata: AlbumGroupMetadata;
|
||||||
onPlay?: (playType: Play) => void;
|
onPlay?: (playType: Play) => void;
|
||||||
rowIndex?: number;
|
rowIndex?: number;
|
||||||
setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void;
|
setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void;
|
||||||
@@ -30,12 +37,16 @@ interface AlbumGroupHeaderProps {
|
|||||||
|
|
||||||
export const AlbumGroupHeader = ({
|
export const AlbumGroupHeader = ({
|
||||||
groupRowCount,
|
groupRowCount,
|
||||||
|
metadata,
|
||||||
onPlay,
|
onPlay,
|
||||||
rowIndex,
|
rowIndex,
|
||||||
setAlbumGroupContentHeight,
|
setAlbumGroupContentHeight,
|
||||||
size = 'normal',
|
size = 'normal',
|
||||||
song,
|
song,
|
||||||
}: AlbumGroupHeaderProps): ReactElement => {
|
}: AlbumGroupHeaderProps): ReactElement => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const albumGroupItems = useAlbumGroupItems();
|
||||||
|
const showFavoriteRating = useAlbumGroupShowFavoriteRating();
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [resolvedInfoHeight, setResolvedInfoHeight] = useState<number | undefined>();
|
const [resolvedInfoHeight, setResolvedInfoHeight] = useState<number | undefined>();
|
||||||
const playButtonBehavior = usePlayButtonBehavior();
|
const playButtonBehavior = usePlayButtonBehavior();
|
||||||
@@ -50,6 +61,16 @@ export const AlbumGroupHeader = ({
|
|||||||
? generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: song.albumId })
|
? generatePath(AppRoute.LIBRARY_ALBUMS_DETAIL, { albumId: song.albumId })
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
|
const metadataRows = useMemo(() => {
|
||||||
|
return albumGroupItems
|
||||||
|
.filter((item) => !item.disabled)
|
||||||
|
.map((item) => ({
|
||||||
|
content: renderAlbumGroupMetadataItem(item.id, song, metadata, t),
|
||||||
|
id: item.id,
|
||||||
|
}))
|
||||||
|
.filter((item) => item.content != null);
|
||||||
|
}, [albumGroupItems, metadata, song, t]);
|
||||||
|
|
||||||
// The album group spans the combined row height, but when the image is
|
// The album group spans the combined row height, but when the image is
|
||||||
// enlarged the group's last row is grown so the total reaches the img size.
|
// enlarged the group's last row is grown so the total reaches the img size.
|
||||||
const infoHeight =
|
const infoHeight =
|
||||||
@@ -145,14 +166,12 @@ export const AlbumGroupHeader = ({
|
|||||||
(song?.album ?? '')
|
(song?.album ?? '')
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.artistName}>
|
{metadataRows.map((row) => (
|
||||||
<JoinedArtists
|
<div className={styles.metadataRow} key={row.id}>
|
||||||
artistName={song?.albumArtistName ?? ''}
|
{row.content}
|
||||||
artists={song?.albumArtists ?? []}
|
|
||||||
linkProps={{ fw: 400 }}
|
|
||||||
rootTextProps={{ fw: 400, size: 'xs' }}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
|
{showFavoriteRating && (
|
||||||
<div className={styles.controlsRow}>
|
<div className={styles.controlsRow}>
|
||||||
<AlbumGroupControls
|
<AlbumGroupControls
|
||||||
albumId={song?.albumId}
|
albumId={song?.albumId}
|
||||||
@@ -161,6 +180,7 @@ export const AlbumGroupHeader = ({
|
|||||||
serverType={song?._serverType}
|
serverType={song?._serverType}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,147 @@
|
|||||||
|
import { TFunction } from 'i18next';
|
||||||
|
import { Fragment, ReactNode } from 'react';
|
||||||
|
import { generatePath, Link } from 'react-router';
|
||||||
|
|
||||||
|
import { JoinedArtists } from '/@/renderer/features/albums/components/joined-artists';
|
||||||
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
|
import { AlbumGroupItem } from '/@/renderer/store';
|
||||||
|
import { formatDurationString, formatPartialIsoDateUTC, formatSizeString } from '/@/renderer/utils';
|
||||||
|
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
import { Genre, Song } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
|
export type AlbumGroupMetadata = {
|
||||||
|
duration: number;
|
||||||
|
genres: Genre[];
|
||||||
|
releaseDate: Song['releaseDate'];
|
||||||
|
releaseType: null | string;
|
||||||
|
releaseYear: Song['releaseYear'];
|
||||||
|
size: number;
|
||||||
|
songCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const computeAlbumGroupMetadata = (
|
||||||
|
songs: Song[],
|
||||||
|
songCount: number,
|
||||||
|
t: TFunction,
|
||||||
|
): AlbumGroupMetadata => {
|
||||||
|
const genreMap = new Map<string, Genre>();
|
||||||
|
let duration = 0;
|
||||||
|
let size = 0;
|
||||||
|
|
||||||
|
for (const song of songs) {
|
||||||
|
duration += song.duration ?? 0;
|
||||||
|
size += song.size ?? 0;
|
||||||
|
|
||||||
|
for (const genre of song.genres ?? []) {
|
||||||
|
if (!genreMap.has(genre.id)) {
|
||||||
|
genreMap.set(genre.id, genre);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstSong = songs[0];
|
||||||
|
const releaseTypes = firstSong?.tags?.releasetype;
|
||||||
|
const releaseType = releaseTypes?.length
|
||||||
|
? (normalizeReleaseTypes(releaseTypes, t)[0] ?? null)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
duration,
|
||||||
|
genres: Array.from(genreMap.values()),
|
||||||
|
releaseDate: firstSong?.releaseDate ?? null,
|
||||||
|
releaseType,
|
||||||
|
releaseYear: firstSong?.releaseYear ?? null,
|
||||||
|
size,
|
||||||
|
songCount,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatReleaseDate = (metadata: AlbumGroupMetadata): null | string => {
|
||||||
|
if (metadata.releaseDate) {
|
||||||
|
return formatPartialIsoDateUTC(metadata.releaseDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const renderAlbumGroupMetadataItem = (
|
||||||
|
itemId: AlbumGroupItem,
|
||||||
|
song: Song | undefined,
|
||||||
|
metadata: AlbumGroupMetadata,
|
||||||
|
t: TFunction,
|
||||||
|
): null | ReactNode => {
|
||||||
|
switch (itemId) {
|
||||||
|
case AlbumGroupItem.ALBUM_ARTISTS:
|
||||||
|
if (!song?.albumArtistName && !(song?.albumArtists?.length ?? 0)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JoinedArtists
|
||||||
|
artistName={song?.albumArtistName ?? ''}
|
||||||
|
artists={song?.albumArtists ?? []}
|
||||||
|
linkProps={{ fw: 400 }}
|
||||||
|
rootTextProps={{ fw: 400, size: 'xs' }}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
case AlbumGroupItem.DURATION:
|
||||||
|
return metadata.duration > 0 ? (
|
||||||
|
<Text size="xs">{formatDurationString(metadata.duration)}</Text>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
case AlbumGroupItem.GENRES:
|
||||||
|
if (metadata.genres.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{metadata.genres.map((genre, index) => (
|
||||||
|
<Fragment key={genre.id}>
|
||||||
|
<Text
|
||||||
|
component={Link}
|
||||||
|
fw={400}
|
||||||
|
isLink
|
||||||
|
size="xs"
|
||||||
|
state={{ item: genre }}
|
||||||
|
to={generatePath(AppRoute.LIBRARY_GENRES_DETAIL, {
|
||||||
|
genreId: genre.id,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{genre.name}
|
||||||
|
</Text>
|
||||||
|
{index < metadata.genres.length - 1 && ', '}
|
||||||
|
</Fragment>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
|
case AlbumGroupItem.RELEASE_DATE: {
|
||||||
|
const releaseDate = formatReleaseDate(metadata);
|
||||||
|
return releaseDate ? <Text size="xs">{releaseDate}</Text> : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
case AlbumGroupItem.RELEASE_TYPE:
|
||||||
|
return metadata.releaseType ? <Text size="xs">{metadata.releaseType}</Text> : null;
|
||||||
|
|
||||||
|
case AlbumGroupItem.RELEASE_YEAR:
|
||||||
|
return metadata.releaseYear != null && metadata.releaseYear > 0 ? (
|
||||||
|
<Text size="xs">{metadata.releaseYear}</Text>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
case AlbumGroupItem.SIZE:
|
||||||
|
return metadata.size > 0 ? (
|
||||||
|
<Text size="xs">{formatSizeString(metadata.size)}</Text>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
case AlbumGroupItem.SONG_COUNT:
|
||||||
|
return metadata.songCount > 0 ? (
|
||||||
|
<Text size="xs">{t('entity.trackWithCount', { count: metadata.songCount })}</Text>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { AlbumGroupHeader } from '/@/renderer/components/item-list/item-table-list/album-group-header';
|
import { AlbumGroupHeader } from '/@/renderer/components/item-list/item-table-list/album-group-header';
|
||||||
|
import { computeAlbumGroupMetadata } from '/@/renderer/components/item-list/item-table-list/album-group-metadata';
|
||||||
import {
|
import {
|
||||||
isLastInAlbumGroup,
|
isLastInAlbumGroup,
|
||||||
ItemTableListInnerColumn,
|
ItemTableListInnerColumn,
|
||||||
@@ -10,6 +12,7 @@ import { Song } from '/@/shared/types/domain-types';
|
|||||||
import { Play } from '/@/shared/types/types';
|
import { Play } from '/@/shared/types/types';
|
||||||
|
|
||||||
export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const firstDataRow = props.enableHeader ? 1 : 0;
|
const firstDataRow = props.enableHeader ? 1 : 0;
|
||||||
const item = props.getRowItem?.(props.rowIndex) as null | Song | undefined;
|
const item = props.getRowItem?.(props.rowIndex) as null | Song | undefined;
|
||||||
|
|
||||||
@@ -76,13 +79,17 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let groupRowCount = 1;
|
let groupRowCount = 1;
|
||||||
|
const groupSongs: Song[] = [item];
|
||||||
const totalDataRows = props.data.length + firstDataRow;
|
const totalDataRows = props.data.length + firstDataRow;
|
||||||
for (let idx = props.rowIndex + 1; idx < totalDataRows; idx++) {
|
for (let idx = props.rowIndex + 1; idx < totalDataRows; idx++) {
|
||||||
const nextItem = props.getRowItem?.(idx) as null | Song | undefined;
|
const nextItem = props.getRowItem?.(idx) as null | Song | undefined;
|
||||||
if (!nextItem || nextItem.album !== item.album) break;
|
if (!nextItem || nextItem.album !== item.album) break;
|
||||||
groupRowCount++;
|
groupRowCount++;
|
||||||
|
groupSongs.push(nextItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const metadata = computeAlbumGroupMetadata(groupSongs, groupRowCount, t);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableColumnContainer
|
<TableColumnContainer
|
||||||
{...props}
|
{...props}
|
||||||
@@ -92,6 +99,7 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => {
|
|||||||
>
|
>
|
||||||
<AlbumGroupHeader
|
<AlbumGroupHeader
|
||||||
groupRowCount={groupRowCount}
|
groupRowCount={groupRowCount}
|
||||||
|
metadata={metadata}
|
||||||
onPlay={handlePlay}
|
onPlay={handlePlay}
|
||||||
rowIndex={props.rowIndex}
|
rowIndex={props.rowIndex}
|
||||||
setAlbumGroupContentHeight={props.setAlbumGroupContentHeight}
|
setAlbumGroupContentHeight={props.setAlbumGroupContentHeight}
|
||||||
|
|||||||
@@ -0,0 +1,116 @@
|
|||||||
|
import { Reorder } from 'motion/react';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { DraggableItem } from '/@/renderer/features/settings/components/general/draggable-item';
|
||||||
|
import {
|
||||||
|
AlbumGroupItem,
|
||||||
|
SortableItem,
|
||||||
|
useAlbumGroupItems,
|
||||||
|
useSettingsStoreActions,
|
||||||
|
} from '/@/renderer/store';
|
||||||
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
|
import { Text } from '/@/shared/components/text/text';
|
||||||
|
|
||||||
|
const ALBUM_GROUP_ITEM_LABELS: Array<[AlbumGroupItem, string]> = [
|
||||||
|
[AlbumGroupItem.ALBUM_ARTISTS, 'table.column.albumArtist'],
|
||||||
|
[AlbumGroupItem.RELEASE_DATE, 'table.column.releaseDate'],
|
||||||
|
[AlbumGroupItem.RELEASE_YEAR, 'table.column.releaseYear'],
|
||||||
|
[AlbumGroupItem.SONG_COUNT, 'table.column.songCount'],
|
||||||
|
[AlbumGroupItem.DURATION, 'common.duration'],
|
||||||
|
[AlbumGroupItem.RELEASE_TYPE, 'common.releaseType'],
|
||||||
|
[AlbumGroupItem.GENRES, 'table.column.genre'],
|
||||||
|
[AlbumGroupItem.SIZE, 'table.column.size'],
|
||||||
|
];
|
||||||
|
|
||||||
|
const mergeItems = (
|
||||||
|
items: SortableItem<AlbumGroupItem>[],
|
||||||
|
itemLabels: Array<[string, string]>,
|
||||||
|
): SortableItem<AlbumGroupItem>[] => {
|
||||||
|
const allItemIds = itemLabels.map(([key]) => key);
|
||||||
|
|
||||||
|
const missingItemIds = allItemIds.filter((id) => !items.some((item) => item.id === id));
|
||||||
|
|
||||||
|
const merged = [
|
||||||
|
...items,
|
||||||
|
...(missingItemIds.map((id) => ({
|
||||||
|
disabled: true,
|
||||||
|
id,
|
||||||
|
})) as SortableItem<AlbumGroupItem>[]),
|
||||||
|
];
|
||||||
|
|
||||||
|
const uniqueMerged = merged.filter(
|
||||||
|
(item, index, self) => index === self.findIndex((t) => t.id === item.id),
|
||||||
|
);
|
||||||
|
|
||||||
|
return uniqueMerged.filter((item) => itemLabels.some(([key]) => key === item.id));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const AlbumGroupMetadataConfig = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const albumGroupItems = useAlbumGroupItems();
|
||||||
|
const { setAlbumGroupItems } = useSettingsStoreActions();
|
||||||
|
|
||||||
|
const items = useMemo(
|
||||||
|
() => mergeItems(albumGroupItems, ALBUM_GROUP_ITEM_LABELS),
|
||||||
|
[albumGroupItems],
|
||||||
|
);
|
||||||
|
|
||||||
|
const translatedItemMap = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.fromEntries(
|
||||||
|
ALBUM_GROUP_ITEM_LABELS.map(([key, value]) => [key, t(value)]),
|
||||||
|
) as Record<AlbumGroupItem, string>,
|
||||||
|
[t],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleChangeDisabled = useCallback(
|
||||||
|
(id: string, enabled: boolean) => {
|
||||||
|
setAlbumGroupItems(
|
||||||
|
items.map((item) => {
|
||||||
|
if (item.id === id) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
disabled: !enabled,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
[items, setAlbumGroupItems],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleReorder = useCallback(
|
||||||
|
(reorderedItems: SortableItem<AlbumGroupItem>[]) => {
|
||||||
|
setAlbumGroupItems(reorderedItems);
|
||||||
|
},
|
||||||
|
[setAlbumGroupItems],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack gap="sm" pb="md" pl="md" pr="md">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text fw={500} size="sm">
|
||||||
|
{t('table.config.general.albumGroupMetadata')}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<Reorder.Group
|
||||||
|
axis="y"
|
||||||
|
onReorder={handleReorder}
|
||||||
|
style={{ userSelect: 'none' }}
|
||||||
|
values={items}
|
||||||
|
>
|
||||||
|
{items.map((item) => (
|
||||||
|
<DraggableItem
|
||||||
|
handleChangeDisabled={handleChangeDisabled}
|
||||||
|
item={item}
|
||||||
|
key={item.id}
|
||||||
|
value={translatedItemMap[item.id]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Reorder.Group>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
});
|
||||||
@@ -17,6 +17,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import styles from './table-config.module.css';
|
import styles from './table-config.module.css';
|
||||||
|
|
||||||
import { ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
import { ItemTableListColumnConfig } from '/@/renderer/components/item-list/types';
|
||||||
|
import { AlbumGroupMetadataConfig } from '/@/renderer/features/shared/components/album-group-metadata-config';
|
||||||
import {
|
import {
|
||||||
ListConfigBooleanControl,
|
ListConfigBooleanControl,
|
||||||
ListConfigTable,
|
ListConfigTable,
|
||||||
@@ -74,6 +75,9 @@ export const TableConfig = ({
|
|||||||
|
|
||||||
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
|
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
|
||||||
const albumGroupImageSize = useSettingsStore((state) => state.general.albumGroupImageSize);
|
const albumGroupImageSize = useSettingsStore((state) => state.general.albumGroupImageSize);
|
||||||
|
const albumGroupShowFavoriteRating = useSettingsStore(
|
||||||
|
(state) => state.general.albumGroupShowFavoriteRating,
|
||||||
|
);
|
||||||
const imageResTable = useSettingsStore((state) => state.general.imageRes.table);
|
const imageResTable = useSettingsStore((state) => state.general.imageRes.table);
|
||||||
const { setList, setSettings } = useSettingsStoreActions();
|
const { setList, setSettings } = useSettingsStoreActions();
|
||||||
const [albumGroupOpen, setAlbumGroupOpen] = useState(false);
|
const [albumGroupOpen, setAlbumGroupOpen] = useState(false);
|
||||||
@@ -156,11 +160,31 @@ export const TableConfig = ({
|
|||||||
),
|
),
|
||||||
id: 'albumImageSize',
|
id: 'albumImageSize',
|
||||||
label: (
|
label: (
|
||||||
<Text pl="md">
|
<Text fw={500} pl="md" size="sm">
|
||||||
{t('table.config.general.albumImageSize')}
|
{t('table.config.general.albumImageSize')}
|
||||||
</Text>
|
</Text>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
component: (
|
||||||
|
<ListConfigBooleanControl
|
||||||
|
onChange={(value) =>
|
||||||
|
setSettings({
|
||||||
|
general: {
|
||||||
|
albumGroupShowFavoriteRating: value,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
value={albumGroupShowFavoriteRating}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
id: 'albumGroupShowFavoriteRating',
|
||||||
|
label: (
|
||||||
|
<Text fw={500} pl="md" size="sm">
|
||||||
|
{t('table.config.general.albumGroupShowFavoriteRating')}
|
||||||
|
</Text>
|
||||||
|
),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
]
|
]
|
||||||
@@ -342,6 +366,7 @@ export const TableConfig = ({
|
|||||||
hasAlbumGroupColumn,
|
hasAlbumGroupColumn,
|
||||||
albumGroupOpen,
|
albumGroupOpen,
|
||||||
albumGroupImageSize,
|
albumGroupImageSize,
|
||||||
|
albumGroupShowFavoriteRating,
|
||||||
imageResTable,
|
imageResTable,
|
||||||
setSettings,
|
setSettings,
|
||||||
]);
|
]);
|
||||||
@@ -349,6 +374,9 @@ export const TableConfig = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListConfigTable options={advancedSettings} />
|
<ListConfigTable options={advancedSettings} />
|
||||||
|
{hasAlbumGroupColumn && tableKey === 'main' && albumGroupOpen && (
|
||||||
|
<AlbumGroupMetadataConfig />
|
||||||
|
)}
|
||||||
<Divider />
|
<Divider />
|
||||||
<TableColumnConfig
|
<TableColumnConfig
|
||||||
data={tableColumnsData}
|
data={tableColumnsData}
|
||||||
|
|||||||
@@ -76,6 +76,17 @@ const HomeItemSchema = z.enum([
|
|||||||
'recentlyReleased',
|
'recentlyReleased',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const AlbumGroupItemSchema = z.enum([
|
||||||
|
'albumArtists',
|
||||||
|
'duration',
|
||||||
|
'genres',
|
||||||
|
'releaseDate',
|
||||||
|
'releaseYear',
|
||||||
|
'releaseType',
|
||||||
|
'size',
|
||||||
|
'songCount',
|
||||||
|
]);
|
||||||
|
|
||||||
const PlayerItemSchema = z.enum([
|
const PlayerItemSchema = z.enum([
|
||||||
'bit_depth',
|
'bit_depth',
|
||||||
'bit_rate',
|
'bit_rate',
|
||||||
@@ -475,6 +486,8 @@ export const GeneralSettingsSchema = z.object({
|
|||||||
albumBackground: z.boolean(),
|
albumBackground: z.boolean(),
|
||||||
albumBackgroundBlur: z.number(),
|
albumBackgroundBlur: z.number(),
|
||||||
albumGroupImageSize: z.number(),
|
albumGroupImageSize: z.number(),
|
||||||
|
albumGroupItems: z.array(SortableItemSchema(AlbumGroupItemSchema)),
|
||||||
|
albumGroupShowFavoriteRating: z.boolean(),
|
||||||
artistBackground: z.boolean(),
|
artistBackground: z.boolean(),
|
||||||
artistBackgroundBlur: z.number(),
|
artistBackgroundBlur: z.number(),
|
||||||
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
artistItems: z.array(SortableItemSchema(ArtistItemSchema)),
|
||||||
@@ -767,6 +780,17 @@ export const SettingsStateSchema = ValidationSettingsStateSchema.merge(
|
|||||||
NonValidatedSettingsStateSchema,
|
NonValidatedSettingsStateSchema,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export enum AlbumGroupItem {
|
||||||
|
ALBUM_ARTISTS = 'albumArtists',
|
||||||
|
DURATION = 'duration',
|
||||||
|
GENRES = 'genres',
|
||||||
|
RELEASE_DATE = 'releaseDate',
|
||||||
|
RELEASE_TYPE = 'releaseType',
|
||||||
|
RELEASE_YEAR = 'releaseYear',
|
||||||
|
SIZE = 'size',
|
||||||
|
SONG_COUNT = 'songCount',
|
||||||
|
}
|
||||||
|
|
||||||
export enum ArtistItem {
|
export enum ArtistItem {
|
||||||
BIOGRAPHY = 'biography',
|
BIOGRAPHY = 'biography',
|
||||||
FAVORITE_SONGS = 'favoriteSongs',
|
FAVORITE_SONGS = 'favoriteSongs',
|
||||||
@@ -944,6 +968,7 @@ export interface SettingsSlice extends z.infer<typeof SettingsStateSchema> {
|
|||||||
removeCollection: (id: string) => void;
|
removeCollection: (id: string) => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
resetSampleRate: () => void;
|
resetSampleRate: () => void;
|
||||||
|
setAlbumGroupItems: (items: SortableItem<AlbumGroupItem>[]) => void;
|
||||||
setArtistItems: (item: SortableItem<ArtistItem>[]) => void;
|
setArtistItems: (item: SortableItem<ArtistItem>[]) => void;
|
||||||
setArtistReleaseTypeItems: (item: SortableItem<ArtistReleaseTypeItem>[]) => void;
|
setArtistReleaseTypeItems: (item: SortableItem<ArtistReleaseTypeItem>[]) => void;
|
||||||
setGenreBehavior: (target: GenreTarget) => void;
|
setGenreBehavior: (target: GenreTarget) => void;
|
||||||
@@ -1120,6 +1145,17 @@ const artistReleaseTypeItems = Object.values(ArtistReleaseTypeItem).map((item) =
|
|||||||
id: item,
|
id: item,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const albumGroupItems: SortableItem<AlbumGroupItem>[] = [
|
||||||
|
{ disabled: false, id: AlbumGroupItem.ALBUM_ARTISTS },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.RELEASE_DATE },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.RELEASE_YEAR },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.SONG_COUNT },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.DURATION },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.RELEASE_TYPE },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.GENRES },
|
||||||
|
{ disabled: true, id: AlbumGroupItem.SIZE },
|
||||||
|
];
|
||||||
|
|
||||||
// Determines the default/initial windowBarStyle value based on the current platform.
|
// Determines the default/initial windowBarStyle value based on the current platform.
|
||||||
const getPlatformDefaultWindowBarStyle = (): Platform => {
|
const getPlatformDefaultWindowBarStyle = (): Platform => {
|
||||||
if (utils?.isWindows()) {
|
if (utils?.isWindows()) {
|
||||||
@@ -1173,6 +1209,8 @@ const initialState: SettingsState = {
|
|||||||
albumBackground: false,
|
albumBackground: false,
|
||||||
albumBackgroundBlur: 3,
|
albumBackgroundBlur: 3,
|
||||||
albumGroupImageSize: 0,
|
albumGroupImageSize: 0,
|
||||||
|
albumGroupItems,
|
||||||
|
albumGroupShowFavoriteRating: true,
|
||||||
artistBackground: true,
|
artistBackground: true,
|
||||||
artistBackgroundBlur: 3,
|
artistBackgroundBlur: 3,
|
||||||
artistItems,
|
artistItems,
|
||||||
@@ -2063,6 +2101,11 @@ export const useSettingsStore = createWithEqualityFn<SettingsSlice>()(
|
|||||||
state.playback.mpvProperties.audioSampleRateHz = 0;
|
state.playback.mpvProperties.audioSampleRateHz = 0;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
setAlbumGroupItems: (items: SortableItem<AlbumGroupItem>[]) => {
|
||||||
|
set((state) => {
|
||||||
|
state.general.albumGroupItems = items;
|
||||||
|
});
|
||||||
|
},
|
||||||
setArtistItems: (items) => {
|
setArtistItems: (items) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.general.artistItems = items;
|
state.general.artistItems = items;
|
||||||
@@ -2681,6 +2724,9 @@ export const useImageRes = () => useSettingsStore((state) => state.general.image
|
|||||||
export const useAlbumGroupImageSize = () =>
|
export const useAlbumGroupImageSize = () =>
|
||||||
useSettingsStore((state) => state.general.albumGroupImageSize);
|
useSettingsStore((state) => state.general.albumGroupImageSize);
|
||||||
|
|
||||||
|
export const useAlbumGroupShowFavoriteRating = () =>
|
||||||
|
useSettingsStore((state) => state.general.albumGroupShowFavoriteRating);
|
||||||
|
|
||||||
export const useVolumeWidth = () => useSettingsStore((state) => state.general.volumeWidth, shallow);
|
export const useVolumeWidth = () => useSettingsStore((state) => state.general.volumeWidth, shallow);
|
||||||
|
|
||||||
export const useFollowCurrentSong = () =>
|
export const useFollowCurrentSong = () =>
|
||||||
@@ -2800,6 +2846,9 @@ export const useHomeFeatureStyle = () =>
|
|||||||
|
|
||||||
export const useHomeItems = () => useSettingsStore((state) => state.general.homeItems, shallow);
|
export const useHomeItems = () => useSettingsStore((state) => state.general.homeItems, shallow);
|
||||||
|
|
||||||
|
export const useAlbumGroupItems = () =>
|
||||||
|
useSettingsStore((state) => state.general.albumGroupItems, shallow);
|
||||||
|
|
||||||
export const useArtistItems = () => useSettingsStore((state) => state.general.artistItems, shallow);
|
export const useArtistItems = () => useSettingsStore((state) => state.general.artistItems, shallow);
|
||||||
|
|
||||||
export const useArtistReleaseTypeItems = () =>
|
export const useArtistReleaseTypeItems = () =>
|
||||||
|
|||||||
Reference in New Issue
Block a user