mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
support multiple items in item details modal
This commit is contained in:
@@ -11,25 +11,35 @@ import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
|
|||||||
|
|
||||||
interface GetInfoActionProps {
|
interface GetInfoActionProps {
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
item: ItemDetailsModalProps['item'];
|
items: ItemDetailsModalProps['item'][];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const GetInfoAction = ({ disabled, item }: GetInfoActionProps) => {
|
export const GetInfoAction = ({ disabled, items }: GetInfoActionProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
|
|
||||||
const onSelect = useCallback(async () => {
|
const onSelect = useCallback(async () => {
|
||||||
if (!server) return;
|
if (!server || items.length === 0) return;
|
||||||
|
|
||||||
|
const filteredItems = items.filter(
|
||||||
|
(item): item is NonNullable<typeof item> => item !== undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (filteredItems.length === 0) return;
|
||||||
|
|
||||||
openModal({
|
openModal({
|
||||||
children: <ItemDetailsModal item={item} />,
|
children: <ItemDetailsModal items={filteredItems} />,
|
||||||
size: 'lg',
|
size: 'lg',
|
||||||
styles: {
|
styles: {
|
||||||
body: { paddingBottom: 'var(--theme-spacing-xl)' },
|
body: { paddingBottom: 'var(--theme-spacing-xl)' },
|
||||||
},
|
},
|
||||||
title: item.name || t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }),
|
title:
|
||||||
|
filteredItems.length === 1
|
||||||
|
? filteredItems[0]?.name ||
|
||||||
|
t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' })
|
||||||
|
: t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }),
|
||||||
});
|
});
|
||||||
}, [item, server, t]);
|
}, [items, server, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContextMenu.Item disabled={disabled} leftIcon="info" onSelect={onSelect}>
|
<ContextMenu.Item disabled={disabled} leftIcon="info" onSelect={onSelect}>
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const AlbumArtistContextMenu = ({ items, type }: AlbumArtistContextMenuPr
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
<GoToAction items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const AlbumContextMenu = ({ items, type }: AlbumContextMenuProps) => {
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
<GoToAction items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const ArtistContextMenu = ({ items, type }: ArtistContextMenuProps) => {
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
<GoToAction items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export const PlaylistContextMenu = ({ items, type }: PlaylistContextMenuProps) =
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
<AddToPlaylistAction items={ids} itemType={LibraryItem.ALBUM} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<EditPlaylistAction items={items} />
|
<EditPlaylistAction items={items} />
|
||||||
<DeletePlaylistAction items={items} />
|
<DeletePlaylistAction items={items} />
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ export const PlaylistSongContextMenu = ({ items, type }: PlaylistSongContextMenu
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
<GoToAction items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export const QueueContextMenu = ({ items }: QueueContextMenuProps) => {
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
<GoToAction items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export const SongContextMenu = ({ items, type }: SongContextMenuProps) => {
|
|||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GoToAction items={items} />
|
<GoToAction items={items} />
|
||||||
<ContextMenu.Divider />
|
<ContextMenu.Divider />
|
||||||
<GetInfoAction disabled={items.length === 0} item={items[0]} />
|
<GetInfoAction disabled={items.length === 0} items={items} />
|
||||||
</ContextMenu.Content>
|
</ContextMenu.Content>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { TFunction } from 'i18next';
|
import { TFunction } from 'i18next';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { generatePath, Link } from 'react-router';
|
import { generatePath, Link } from 'react-router';
|
||||||
|
|
||||||
@@ -12,8 +12,10 @@ import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types
|
|||||||
import { sanitize } from '/@/renderer/utils/sanitize';
|
import { sanitize } from '/@/renderer/utils/sanitize';
|
||||||
import { SEPARATOR_STRING } from '/@/shared/api/utils';
|
import { SEPARATOR_STRING } from '/@/shared/api/utils';
|
||||||
import { Icon } from '/@/shared/components/icon/icon';
|
import { Icon } from '/@/shared/components/icon/icon';
|
||||||
|
import { Select } from '/@/shared/components/select/select';
|
||||||
import { Separator } from '/@/shared/components/separator/separator';
|
import { Separator } from '/@/shared/components/separator/separator';
|
||||||
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
import { Spoiler } from '/@/shared/components/spoiler/spoiler';
|
||||||
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Table } from '/@/shared/components/table/table';
|
import { Table } from '/@/shared/components/table/table';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import {
|
import {
|
||||||
@@ -29,7 +31,8 @@ import {
|
|||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
export type ItemDetailsModalProps = {
|
export type ItemDetailsModalProps = {
|
||||||
item: Album | AlbumArtist | Artist | Playlist | Song;
|
item?: Album | AlbumArtist | Artist | Playlist | Song;
|
||||||
|
items?: (Album | AlbumArtist | Artist | Playlist | Song)[];
|
||||||
};
|
};
|
||||||
|
|
||||||
type ItemDetailRow<T> = {
|
type ItemDetailRow<T> = {
|
||||||
@@ -404,48 +407,81 @@ const handleParticipants = (item: Album | Song, t: TFunction) => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ItemDetailsModal = ({ item }: ItemDetailsModalProps) => {
|
export const ItemDetailsModal = ({ item, items }: ItemDetailsModalProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const allItems = useMemo(() => items || (item ? [item] : []), [item, items]);
|
||||||
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
||||||
|
|
||||||
|
const selectedItem = useMemo(() => {
|
||||||
|
return allItems[selectedIndex] || null;
|
||||||
|
}, [allItems, selectedIndex]);
|
||||||
|
|
||||||
|
const selectData = useMemo(() => {
|
||||||
|
return allItems.map((it, index) => ({
|
||||||
|
label:
|
||||||
|
it.name ||
|
||||||
|
`${t('common.item', { defaultValue: 'Item', postProcess: 'sentenceCase' })} ${index + 1}`,
|
||||||
|
value: String(index),
|
||||||
|
}));
|
||||||
|
}, [allItems, t]);
|
||||||
|
|
||||||
|
if (!selectedItem) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
let body: ReactNode[] = [];
|
let body: ReactNode[] = [];
|
||||||
|
|
||||||
switch (item._itemType) {
|
switch (selectedItem._itemType) {
|
||||||
case LibraryItem.ALBUM:
|
case LibraryItem.ALBUM:
|
||||||
body = AlbumPropertyMapping.map((rule) => handleRow(t, item, rule));
|
body = AlbumPropertyMapping.map((rule) => handleRow(t, selectedItem, rule));
|
||||||
body.push(...handleParticipants(item, t));
|
body.push(...handleParticipants(selectedItem, t));
|
||||||
body.push(...handleTags(item, t));
|
body.push(...handleTags(selectedItem, t));
|
||||||
break;
|
break;
|
||||||
case LibraryItem.ALBUM_ARTIST:
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
body = AlbumArtistPropertyMapping.map((rule) => handleRow(t, item, rule));
|
body = AlbumArtistPropertyMapping.map((rule) => handleRow(t, selectedItem, rule));
|
||||||
break;
|
break;
|
||||||
case LibraryItem.PLAYLIST:
|
case LibraryItem.PLAYLIST:
|
||||||
body = PlaylistPropertyMapping.map((rule) => handleRow(t, item, rule));
|
body = PlaylistPropertyMapping.map((rule) => handleRow(t, selectedItem, rule));
|
||||||
break;
|
break;
|
||||||
case LibraryItem.SONG:
|
case LibraryItem.SONG:
|
||||||
body = SongPropertyMapping.map((rule) => handleRow(t, item, rule));
|
body = SongPropertyMapping.map((rule) => handleRow(t, selectedItem, rule));
|
||||||
body.push(...handleParticipants(item, t));
|
body.push(...handleParticipants(selectedItem, t));
|
||||||
body.push(...handleTags(item, t));
|
body.push(...handleTags(selectedItem, t));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
body = [];
|
body = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Table
|
<Stack gap="md">
|
||||||
highlightOnHover={false}
|
{allItems.length > 1 && (
|
||||||
styles={{
|
<Select
|
||||||
th: {
|
data={selectData}
|
||||||
color: 'var(--theme-colors-foreground-muted)',
|
onChange={(value) => {
|
||||||
fontWeight: 500,
|
if (value) {
|
||||||
padding: 'var(--theme-spacing-sm)',
|
setSelectedIndex(Number(value));
|
||||||
},
|
}
|
||||||
tr: {
|
}}
|
||||||
color: 'var(--theme-colors-foreground-muted)',
|
value={String(selectedIndex)}
|
||||||
padding: 'var(--theme-spacing-xl)',
|
/>
|
||||||
},
|
)}
|
||||||
}}
|
<Table
|
||||||
withRowBorders={true}
|
highlightOnHover={false}
|
||||||
>
|
styles={{
|
||||||
<Table.Tbody>{body}</Table.Tbody>
|
th: {
|
||||||
</Table>
|
color: 'var(--theme-colors-foreground-muted)',
|
||||||
|
fontWeight: 500,
|
||||||
|
padding: 'var(--theme-spacing-sm)',
|
||||||
|
},
|
||||||
|
tr: {
|
||||||
|
color: 'var(--theme-colors-foreground-muted)',
|
||||||
|
padding: 'var(--theme-spacing-xl)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
withRowBorders={true}
|
||||||
|
>
|
||||||
|
<Table.Tbody>{body}</Table.Tbody>
|
||||||
|
</Table>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user