mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 18:33:33 +00:00
[enhancement]: Show item details (#573)
* start * More details, don't show manage server when other modal
This commit is contained in:
parent
7bebe286d5
commit
197497df05
6 changed files with 365 additions and 82 deletions
|
|
@ -26,6 +26,8 @@
|
||||||
"action_one": "action",
|
"action_one": "action",
|
||||||
"action_other": "actions",
|
"action_other": "actions",
|
||||||
"add": "add",
|
"add": "add",
|
||||||
|
"albumGain": "album gain",
|
||||||
|
"albumPeak": "album peak",
|
||||||
"areYouSure": "are you sure?",
|
"areYouSure": "are you sure?",
|
||||||
"ascending": "ascending",
|
"ascending": "ascending",
|
||||||
"backward": "backward",
|
"backward": "backward",
|
||||||
|
|
@ -72,6 +74,7 @@
|
||||||
"menu": "menu",
|
"menu": "menu",
|
||||||
"minimize": "minimize",
|
"minimize": "minimize",
|
||||||
"modified": "modified",
|
"modified": "modified",
|
||||||
|
"mbid": "MusicBrainz ID",
|
||||||
"name": "name",
|
"name": "name",
|
||||||
"no": "no",
|
"no": "no",
|
||||||
"none": "none",
|
"none": "none",
|
||||||
|
|
@ -102,6 +105,8 @@
|
||||||
"sortOrder": "order",
|
"sortOrder": "order",
|
||||||
"title": "title",
|
"title": "title",
|
||||||
"trackNumber": "track",
|
"trackNumber": "track",
|
||||||
|
"trackGain": "track gain",
|
||||||
|
"trackPeak": "track peak",
|
||||||
"unknown": "unknown",
|
"unknown": "unknown",
|
||||||
"version": "version",
|
"version": "version",
|
||||||
"year": "year",
|
"year": "year",
|
||||||
|
|
@ -306,7 +311,8 @@
|
||||||
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
"removeFromFavorites": "$t(action.removeFromFavorites)",
|
||||||
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
"removeFromPlaylist": "$t(action.removeFromPlaylist)",
|
||||||
"removeFromQueue": "$t(action.removeFromQueue)",
|
"removeFromQueue": "$t(action.removeFromQueue)",
|
||||||
"setRating": "$t(action.setRating)"
|
"setRating": "$t(action.setRating)",
|
||||||
|
"showDetails": "get info"
|
||||||
},
|
},
|
||||||
"fullscreenPlayer": {
|
"fullscreenPlayer": {
|
||||||
"config": {
|
"config": {
|
||||||
|
|
|
||||||
|
|
@ -80,19 +80,23 @@ const ActionRequiredRoute = () => {
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Group
|
{!displayedCheck && (
|
||||||
noWrap
|
<Group
|
||||||
position="center"
|
noWrap
|
||||||
>
|
position="center"
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
leftIcon={<RiEdit2Line />}
|
|
||||||
variant="filled"
|
|
||||||
onClick={handleManageServersModal}
|
|
||||||
>
|
>
|
||||||
{t('page.appMenu.manageServers', { postProcess: 'sentenceCase' })}
|
<Button
|
||||||
</Button>
|
fullWidth
|
||||||
</Group>
|
leftIcon={<RiEdit2Line />}
|
||||||
|
variant="filled"
|
||||||
|
onClick={handleManageServersModal}
|
||||||
|
>
|
||||||
|
{t('page.appMenu.manageServers', {
|
||||||
|
postProcess: 'sentenceCase',
|
||||||
|
})}
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Center>
|
</Center>
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ export const QUEUE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
{ divider: true, id: 'removeFromFavorites' },
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
{ children: true, disabled: false, id: 'setRating' },
|
||||||
{ disabled: false, id: 'deselectAll' },
|
{ disabled: false, id: 'deselectAll' },
|
||||||
|
{ divider: true, id: 'showDetails' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
|
|
@ -19,6 +20,7 @@ export const SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ id: 'addToFavorites' },
|
{ id: 'addToFavorites' },
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
{ divider: true, id: 'removeFromFavorites' },
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
{ children: true, disabled: false, id: 'setRating' },
|
||||||
|
{ divider: true, id: 'showDetails' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
|
|
@ -30,6 +32,7 @@ export const PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ id: 'addToFavorites' },
|
{ id: 'addToFavorites' },
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
{ divider: true, id: 'removeFromFavorites' },
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
{ children: true, disabled: false, id: 'setRating' },
|
||||||
|
{ divider: true, id: 'showDetails' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
|
|
@ -40,6 +43,7 @@ export const SMART_PLAYLIST_SONG_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ id: 'addToFavorites' },
|
{ id: 'addToFavorites' },
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
{ divider: true, id: 'removeFromFavorites' },
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
{ children: true, disabled: false, id: 'setRating' },
|
||||||
|
{ divider: true, id: 'showDetails' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
|
|
@ -50,6 +54,7 @@ export const ALBUM_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ id: 'addToFavorites' },
|
{ id: 'addToFavorites' },
|
||||||
{ id: 'removeFromFavorites' },
|
{ id: 'removeFromFavorites' },
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
{ children: true, disabled: false, id: 'setRating' },
|
||||||
|
{ divider: true, id: 'showDetails' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GENRE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const GENRE_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
|
|
@ -67,6 +72,7 @@ export const ARTIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
{ id: 'addToFavorites' },
|
{ id: 'addToFavorites' },
|
||||||
{ divider: true, id: 'removeFromFavorites' },
|
{ divider: true, id: 'removeFromFavorites' },
|
||||||
{ children: true, disabled: false, id: 'setRating' },
|
{ children: true, disabled: false, id: 'setRating' },
|
||||||
|
{ divider: true, id: 'showDetails' },
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PLAYLIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
export const PLAYLIST_CONTEXT_MENU_ITEMS: SetContextMenuItems = [
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ import {
|
||||||
RiPlayListAddFill,
|
RiPlayListAddFill,
|
||||||
RiStarFill,
|
RiStarFill,
|
||||||
RiCloseCircleLine,
|
RiCloseCircleLine,
|
||||||
|
RiInformationFill,
|
||||||
} from 'react-icons/ri';
|
} from 'react-icons/ri';
|
||||||
import { AnyLibraryItems, LibraryItem, ServerType, AnyLibraryItem } from '/@/renderer/api/types';
|
import { AnyLibraryItems, LibraryItem, ServerType, AnyLibraryItem } from '/@/renderer/api/types';
|
||||||
import {
|
import {
|
||||||
|
|
@ -53,6 +54,7 @@ import {
|
||||||
} from '/@/renderer/store';
|
} from '/@/renderer/store';
|
||||||
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
import { usePlaybackType } from '/@/renderer/store/settings.store';
|
||||||
import { Play, PlaybackType } from '/@/renderer/types';
|
import { Play, PlaybackType } from '/@/renderer/types';
|
||||||
|
import { ItemDetailsModal } from '/@/renderer/features/item-details/components/item-details-modal';
|
||||||
|
|
||||||
type ContextMenuContextProps = {
|
type ContextMenuContextProps = {
|
||||||
closeContextMenu: () => void;
|
closeContextMenu: () => void;
|
||||||
|
|
@ -627,6 +629,16 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
ctx.tableApi?.deselectAll();
|
ctx.tableApi?.deselectAll();
|
||||||
}, [ctx.tableApi]);
|
}, [ctx.tableApi]);
|
||||||
|
|
||||||
|
const handleOpenItemDetails = useCallback(() => {
|
||||||
|
const item = ctx.data[0];
|
||||||
|
|
||||||
|
openModal({
|
||||||
|
children: <ItemDetailsModal item={item} />,
|
||||||
|
size: 'xl',
|
||||||
|
title: t('page.contextMenu.showDetails', { postProcess: 'titleCase' }),
|
||||||
|
});
|
||||||
|
}, [ctx.data, t]);
|
||||||
|
|
||||||
const contextMenuItems: Record<ContextMenuItemType, ContextMenuItem> = useMemo(() => {
|
const contextMenuItems: Record<ContextMenuItemType, ContextMenuItem> = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
addToFavorites: {
|
addToFavorites: {
|
||||||
|
|
@ -775,20 +787,29 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
onClick: () => {},
|
onClick: () => {},
|
||||||
rightIcon: <RiArrowRightSFill size="1.2rem" />,
|
rightIcon: <RiArrowRightSFill size="1.2rem" />,
|
||||||
},
|
},
|
||||||
|
showDetails: {
|
||||||
|
disabled: ctx.data?.length !== 1 || !ctx.data[0].itemType,
|
||||||
|
id: 'showDetails',
|
||||||
|
label: t('page.contextMenu.showDetails', { postProcess: 'sentenceCase' }),
|
||||||
|
leftIcon: <RiInformationFill />,
|
||||||
|
onClick: handleOpenItemDetails,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}, [
|
}, [
|
||||||
|
t,
|
||||||
handleAddToFavorites,
|
handleAddToFavorites,
|
||||||
handleAddToPlaylist,
|
handleAddToPlaylist,
|
||||||
|
openDeletePlaylistModal,
|
||||||
handleDeselectAll,
|
handleDeselectAll,
|
||||||
handleMoveToBottom,
|
handleMoveToBottom,
|
||||||
handleMoveToTop,
|
handleMoveToTop,
|
||||||
handlePlay,
|
|
||||||
handleRemoveFromFavorites,
|
handleRemoveFromFavorites,
|
||||||
handleRemoveFromPlaylist,
|
handleRemoveFromPlaylist,
|
||||||
handleRemoveSelected,
|
handleRemoveSelected,
|
||||||
|
ctx.data,
|
||||||
|
handleOpenItemDetails,
|
||||||
|
handlePlay,
|
||||||
handleUpdateRating,
|
handleUpdateRating,
|
||||||
openDeletePlaylistModal,
|
|
||||||
t,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const mergedRef = useMergedRef(ref, clickOutsideRef);
|
const mergedRef = useMergedRef(ref, clickOutsideRef);
|
||||||
|
|
@ -819,72 +840,80 @@ export const ContextMenuProvider = ({ children }: ContextMenuProviderProps) => {
|
||||||
>
|
>
|
||||||
{ctx.menuItems?.map((item) => {
|
{ctx.menuItems?.map((item) => {
|
||||||
return (
|
return (
|
||||||
<Fragment key={`context-menu-${item.id}`}>
|
!contextMenuItems[item.id].disabled && (
|
||||||
{item.children ? (
|
<Fragment key={`context-menu-${item.id}`}>
|
||||||
<HoverCard
|
{item.children ? (
|
||||||
offset={5}
|
<HoverCard
|
||||||
position="right"
|
offset={5}
|
||||||
>
|
position="right"
|
||||||
<HoverCard.Target>
|
>
|
||||||
<ContextMenuButton
|
<HoverCard.Target>
|
||||||
disabled={item.disabled}
|
<ContextMenuButton
|
||||||
leftIcon={
|
leftIcon={
|
||||||
contextMenuItems[item.id]
|
contextMenuItems[item.id]
|
||||||
.leftIcon
|
.leftIcon
|
||||||
}
|
}
|
||||||
rightIcon={
|
rightIcon={
|
||||||
contextMenuItems[item.id]
|
contextMenuItems[item.id]
|
||||||
.rightIcon
|
.rightIcon
|
||||||
}
|
}
|
||||||
onClick={
|
onClick={
|
||||||
contextMenuItems[item.id]
|
contextMenuItems[item.id]
|
||||||
.onClick
|
.onClick
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{contextMenuItems[item.id].label}
|
{
|
||||||
</ContextMenuButton>
|
contextMenuItems[item.id]
|
||||||
</HoverCard.Target>
|
.label
|
||||||
<HoverCard.Dropdown>
|
}
|
||||||
<Stack spacing={0}>
|
</ContextMenuButton>
|
||||||
{contextMenuItems[
|
</HoverCard.Target>
|
||||||
item.id
|
<HoverCard.Dropdown>
|
||||||
].children?.map((child) => (
|
<Stack spacing={0}>
|
||||||
<ContextMenuButton
|
{contextMenuItems[
|
||||||
key={`sub-${child.id}`}
|
item.id
|
||||||
disabled={child.disabled}
|
].children?.map((child) => (
|
||||||
leftIcon={child.leftIcon}
|
<ContextMenuButton
|
||||||
rightIcon={child.rightIcon}
|
key={`sub-${child.id}`}
|
||||||
onClick={child.onClick}
|
leftIcon={
|
||||||
>
|
child.leftIcon
|
||||||
{child.label}
|
}
|
||||||
</ContextMenuButton>
|
rightIcon={
|
||||||
))}
|
child.rightIcon
|
||||||
</Stack>
|
}
|
||||||
</HoverCard.Dropdown>
|
onClick={child.onClick}
|
||||||
</HoverCard>
|
>
|
||||||
) : (
|
{child.label}
|
||||||
<ContextMenuButton
|
</ContextMenuButton>
|
||||||
disabled={item.disabled}
|
))}
|
||||||
leftIcon={
|
</Stack>
|
||||||
contextMenuItems[item.id].leftIcon
|
</HoverCard.Dropdown>
|
||||||
}
|
</HoverCard>
|
||||||
rightIcon={
|
) : (
|
||||||
contextMenuItems[item.id].rightIcon
|
<ContextMenuButton
|
||||||
}
|
leftIcon={
|
||||||
onClick={contextMenuItems[item.id].onClick}
|
contextMenuItems[item.id].leftIcon
|
||||||
>
|
}
|
||||||
{contextMenuItems[item.id].label}
|
rightIcon={
|
||||||
</ContextMenuButton>
|
contextMenuItems[item.id].rightIcon
|
||||||
)}
|
}
|
||||||
|
onClick={
|
||||||
|
contextMenuItems[item.id].onClick
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{contextMenuItems[item.id].label}
|
||||||
|
</ContextMenuButton>
|
||||||
|
)}
|
||||||
|
|
||||||
{item.divider && (
|
{item.divider && (
|
||||||
<Divider
|
<Divider
|
||||||
key={`context-menu-divider-${item.id}`}
|
key={`context-menu-divider-${item.id}`}
|
||||||
color="rgb(62, 62, 62)"
|
color="rgb(62, 62, 62)"
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,8 @@ export type ContextMenuItemType =
|
||||||
| 'moveToBottomOfQueue'
|
| 'moveToBottomOfQueue'
|
||||||
| 'moveToTopOfQueue'
|
| 'moveToTopOfQueue'
|
||||||
| 'removeFromQueue'
|
| 'removeFromQueue'
|
||||||
| 'deselectAll';
|
| 'deselectAll'
|
||||||
|
| 'showDetails';
|
||||||
|
|
||||||
export type SetContextMenuItems = {
|
export type SetContextMenuItems = {
|
||||||
children?: boolean;
|
children?: boolean;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,237 @@
|
||||||
|
import { Group, Table } from '@mantine/core';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { RiCheckFill, RiCloseFill } from 'react-icons/ri';
|
||||||
|
import { TFunction, useTranslation } from 'react-i18next';
|
||||||
|
import { ReactNode } from 'react';
|
||||||
|
import { Album, AlbumArtist, AnyLibraryItem, LibraryItem, Song } from '/@/renderer/api/types';
|
||||||
|
import { formatDurationString } from '/@/renderer/utils';
|
||||||
|
import { formatSizeString } from '/@/renderer/utils/format-size-string';
|
||||||
|
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
|
||||||
|
import { Rating, Spoiler } from '/@/renderer/components';
|
||||||
|
import { sanitize } from '/@/renderer/utils/sanitize';
|
||||||
|
|
||||||
|
export type ItemDetailsModalProps = {
|
||||||
|
item: Album | AlbumArtist | Song;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ItemDetailRow<T> = {
|
||||||
|
key?: keyof T;
|
||||||
|
label: string;
|
||||||
|
postprocess?: string[];
|
||||||
|
render?: (item: T) => ReactNode;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRow = <T extends AnyLibraryItem>(t: TFunction, item: T, rule: ItemDetailRow<T>) => {
|
||||||
|
let value: ReactNode;
|
||||||
|
|
||||||
|
if (rule.render) {
|
||||||
|
value = rule.render(item);
|
||||||
|
} else {
|
||||||
|
const prop = item[rule.key!];
|
||||||
|
value = prop !== undefined && prop !== null ? String(prop) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!value) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<tr key={rule.label}>
|
||||||
|
<td>{t(rule.label, { postProcess: rule.postprocess || 'sentenceCase' })}</td>
|
||||||
|
<td>{value}</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatArtists = (item: Album | Song) =>
|
||||||
|
item.albumArtists?.map((artist) => artist.name).join(' · ');
|
||||||
|
|
||||||
|
const formatComment = (item: Album | Song) =>
|
||||||
|
item.comment ? <Spoiler maxHeight={50}>{replaceURLWithHTMLLinks(item.comment)}</Spoiler> : null;
|
||||||
|
|
||||||
|
const formatDate = (key: string | null) => (key ? dayjs(key).fromNow() : '');
|
||||||
|
|
||||||
|
const formatGenre = (item: Album | AlbumArtist | Song) =>
|
||||||
|
item.genres?.map((genre) => genre.name).join(' · ');
|
||||||
|
|
||||||
|
const formatRating = (item: Album | AlbumArtist | Song) =>
|
||||||
|
item.userRating !== null ? (
|
||||||
|
<Rating
|
||||||
|
readOnly
|
||||||
|
value={item.userRating}
|
||||||
|
/>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const BoolField = (key: boolean) =>
|
||||||
|
key ? <RiCheckFill size="1.1rem" /> : <RiCloseFill size="1.1rem" />;
|
||||||
|
|
||||||
|
const AlbumPropertyMapping: ItemDetailRow<Album>[] = [
|
||||||
|
{ key: 'name', label: 'common.title' },
|
||||||
|
{ label: 'entity.albumArtist_one', render: formatArtists },
|
||||||
|
{ label: 'entity.genre_other', render: formatGenre },
|
||||||
|
{
|
||||||
|
label: 'common.duration',
|
||||||
|
render: (album) => album.duration && formatDurationString(album.duration),
|
||||||
|
},
|
||||||
|
{ key: 'releaseYear', label: 'filter.releaseYear' },
|
||||||
|
{ key: 'songCount', label: 'filter.songCount' },
|
||||||
|
{ label: 'filter.isCompilation', render: (album) => BoolField(album.isCompilation || false) },
|
||||||
|
{
|
||||||
|
key: 'size',
|
||||||
|
label: 'common.size',
|
||||||
|
render: (album) => album.size && formatSizeString(album.size),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.favorite',
|
||||||
|
render: (album) => BoolField(album.userFavorite),
|
||||||
|
},
|
||||||
|
{ label: 'common.rating', render: formatRating },
|
||||||
|
{ key: 'playCount', label: 'filter.playCount' },
|
||||||
|
{
|
||||||
|
label: 'filter.lastPlayed',
|
||||||
|
render: (song) => formatDate(song.lastPlayedAt),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.modified',
|
||||||
|
render: (song) => formatDate(song.updatedAt),
|
||||||
|
},
|
||||||
|
{ label: 'filter.comment', render: formatComment },
|
||||||
|
{
|
||||||
|
label: 'common.mbid',
|
||||||
|
postprocess: [],
|
||||||
|
render: (album) =>
|
||||||
|
album.mbzId ? (
|
||||||
|
<a
|
||||||
|
href={`https://musicbrainz.org/release/${album.mbzId}`}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{album.mbzId}
|
||||||
|
</a>
|
||||||
|
) : null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const AlbumArtistPropertyMapping: ItemDetailRow<AlbumArtist>[] = [
|
||||||
|
{ key: 'name', label: 'common.name' },
|
||||||
|
{ label: 'entity.genre_other', render: formatGenre },
|
||||||
|
{
|
||||||
|
label: 'common.duration',
|
||||||
|
render: (artist) => artist.duration && formatDurationString(artist.duration),
|
||||||
|
},
|
||||||
|
{ key: 'songCount', label: 'filter.songCount' },
|
||||||
|
{
|
||||||
|
label: 'common.favorite',
|
||||||
|
render: (artist) => BoolField(artist.userFavorite),
|
||||||
|
},
|
||||||
|
{ label: 'common.rating', render: formatRating },
|
||||||
|
{ key: 'playCount', label: 'filter.playCount' },
|
||||||
|
{
|
||||||
|
label: 'filter.lastPlayed',
|
||||||
|
render: (song) => formatDate(song.lastPlayedAt),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.mbid',
|
||||||
|
postprocess: [],
|
||||||
|
render: (artist) =>
|
||||||
|
artist.mbz ? (
|
||||||
|
<a
|
||||||
|
href={`https://musicbrainz.org/artist/${artist.mbz}`}
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
{artist.mbz}
|
||||||
|
</a>
|
||||||
|
) : null,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.biography',
|
||||||
|
render: (artist) =>
|
||||||
|
artist.biography ? (
|
||||||
|
<Spoiler
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(artist.biography) }}
|
||||||
|
maxHeight={50}
|
||||||
|
/>
|
||||||
|
) : null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const SongPropertyMapping: ItemDetailRow<Song>[] = [
|
||||||
|
{ key: 'name', label: 'common.title' },
|
||||||
|
{ key: 'path', label: 'common.path' },
|
||||||
|
{ label: 'entity.albumArtist_one', render: formatArtists },
|
||||||
|
{ key: 'album', label: 'entity.album_one' },
|
||||||
|
{ key: 'discNumber', label: 'common.disc' },
|
||||||
|
{ key: 'trackNumber', label: 'common.trackNumber' },
|
||||||
|
{ key: 'releaseYear', label: 'filter.releaseYear' },
|
||||||
|
{ label: 'entity.genre_other', render: formatGenre },
|
||||||
|
{
|
||||||
|
label: 'common.duration',
|
||||||
|
render: (song) => formatDurationString(song.duration),
|
||||||
|
},
|
||||||
|
{ label: 'filter.isCompilation', render: (song) => BoolField(song.compilation || false) },
|
||||||
|
{ key: 'container', label: 'common.codec' },
|
||||||
|
{ key: 'bitRate', label: 'common.bitrate', render: (song) => `${song.bitRate} kbps` },
|
||||||
|
{ key: 'channels', label: 'common.channel_other' },
|
||||||
|
{ key: 'size', label: 'common.size', render: (song) => formatSizeString(song.size) },
|
||||||
|
{
|
||||||
|
label: 'common.favorite',
|
||||||
|
render: (song) => BoolField(song.userFavorite),
|
||||||
|
},
|
||||||
|
{ label: 'common.rating', render: formatRating },
|
||||||
|
{ key: 'playCount', label: 'filter.playCount' },
|
||||||
|
{
|
||||||
|
label: 'filter.lastPlayed',
|
||||||
|
render: (song) => formatDate(song.lastPlayedAt),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.modified',
|
||||||
|
render: (song) => formatDate(song.updatedAt),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.albumGain',
|
||||||
|
render: (song) => (song.gain?.album !== undefined ? `${song.gain.album} dB` : null),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.trackGain',
|
||||||
|
render: (song) => (song.gain?.track !== undefined ? `${song.gain.track} dB` : null),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.albumPeak',
|
||||||
|
render: (song) => (song.peak?.album !== undefined ? `${song.peak.album}` : null),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'common.trackPeak',
|
||||||
|
render: (song) => (song.peak?.track !== undefined ? `${song.peak.track}` : null),
|
||||||
|
},
|
||||||
|
{ label: 'filter.comment', render: formatComment },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const ItemDetailsModal = ({ item }: ItemDetailsModalProps) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
let body: ReactNode;
|
||||||
|
|
||||||
|
switch (item.itemType) {
|
||||||
|
case LibraryItem.ALBUM:
|
||||||
|
body = AlbumPropertyMapping.map((rule) => handleRow(t, item, rule));
|
||||||
|
break;
|
||||||
|
case LibraryItem.ALBUM_ARTIST:
|
||||||
|
body = AlbumArtistPropertyMapping.map((rule) => handleRow(t, item, rule));
|
||||||
|
break;
|
||||||
|
case LibraryItem.SONG:
|
||||||
|
body = SongPropertyMapping.map((rule) => handleRow(t, item, rule));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
body = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group>
|
||||||
|
<Table
|
||||||
|
highlightOnHover
|
||||||
|
horizontalSpacing="sm"
|
||||||
|
verticalSpacing="sm"
|
||||||
|
>
|
||||||
|
<tbody>{body}</tbody>
|
||||||
|
</Table>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
};
|
||||||
Loading…
Add table
Add a link
Reference in a new issue