add more metadata to album header / side

This commit is contained in:
jeffvli
2025-12-30 02:33:23 -08:00
parent 62ab4b7a00
commit aa75aaaffb
7 changed files with 449 additions and 254 deletions
+1
View File
@@ -102,6 +102,7 @@
"minimize": "minimize", "minimize": "minimize",
"modified": "modified", "modified": "modified",
"mbid": "MusicBrainz ID", "mbid": "MusicBrainz ID",
"mood": "mood",
"name": "name", "name": "name",
"no": "no", "no": "no",
"none": "none", "none": "none",
@@ -27,7 +27,11 @@
.metadata-column { .metadata-column {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
flex-wrap: wrap;
grid-area: metadata; grid-area: metadata;
gap: var(--theme-spacing-xl);
align-items: center;
text-align: center;
} }
.songs-column { .songs-column {
@@ -53,6 +57,47 @@
} }
} }
.external-links-group {
justify-content: center;
}
.metadata-pill-group {
align-items: center;
}
.pill-group-wrapper {
display: flex;
width: 100%;
& > div {
justify-content: center;
}
}
@container (min-width: $mantine-breakpoint-sm) {
.metadata-column {
flex-direction: row;
justify-content: flex-start;
text-align: left;
}
.external-links-group {
justify-content: flex-start;
}
.metadata-pill-group {
align-items: flex-start;
}
.pill-group-wrapper {
justify-content: flex-start;
& > div {
justify-content: flex-start;
}
}
}
@container (min-width: $mantine-breakpoint-lg) { @container (min-width: $mantine-breakpoint-lg) {
.content-layout { .content-layout {
grid-template-areas: 'songs metadata'; grid-template-areas: 'songs metadata';
@@ -22,18 +22,12 @@ import { useContainerQuery } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, usePlayerSong } from '/@/renderer/store'; import { useCurrentServer, usePlayerSong } from '/@/renderer/store';
import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store'; import { useGeneralSettings, useSettingsStore } from '/@/renderer/store/settings.store';
import { import { titleCase } from '/@/renderer/utils';
formatDateAbsoluteUTC,
formatDurationString,
formatSizeString,
titleCase,
} from '/@/renderer/utils';
import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify'; import { replaceURLWithHTMLLinks } from '/@/renderer/utils/linkify';
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types'; import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
import { sortSongList } from '/@/shared/api/utils'; import { sortSongList } from '/@/shared/api/utils';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Checkbox } from '/@/shared/components/checkbox/checkbox'; import { Checkbox } from '/@/shared/components/checkbox/checkbox';
import { Flex } from '/@/shared/components/flex/flex';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Icon } from '/@/shared/components/icon/icon'; import { Icon } from '/@/shared/components/icon/icon';
import { Pill, PillLink } from '/@/shared/components/pill/pill'; import { Pill, PillLink } from '/@/shared/components/pill/pill';
@@ -55,89 +49,85 @@ import {
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types'; import { ItemListKey, ListDisplayType, Play } from '/@/shared/types/types';
const MetadataPillGroup = ({
items,
title,
}: {
items: undefined | { id: string; value: ReactNode | string | undefined }[];
title: string;
}) => {
if (!items || items.length === 0) return null;
return (
<Stack align="center" className={styles.metadataPillGroup} gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase">
{title}
</Text>
<div className={styles['pill-group-wrapper']}>
<Pill.Group>
{items.map((tag, index) => (
<Pill key={`item-${tag.id}-${index}`} size="md">
{tag.value}
</Pill>
))}
</Pill.Group>
</div>
</Stack>
);
};
interface AlbumMetadataTagsProps { interface AlbumMetadataTagsProps {
album: Album | undefined; album: Album | undefined;
} }
const MOOD_TAG = 'mood';
const RELEASE_COUNTRY_TAG = 'releasecountry';
const RELEASE_STATUS_TAG = 'releasestatus';
const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => { const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const metadataItems = useMemo(() => { const defaultTagItems = useMemo(() => {
if (!album) return []; if (!album) return [];
const originalDifferentFromRelease =
album.originalDate && album.originalDate !== album.releaseDate;
const releasePrefix = originalDifferentFromRelease
? t('page.albumDetail.released', { postProcess: 'sentenceCase' })
: '♫';
const releaseTypes = normalizeReleaseTypes(album.releaseTypes ?? [], t).map((type) => ({ const releaseTypes = normalizeReleaseTypes(album.releaseTypes ?? [], t).map((type) => ({
id: type, id: type,
value: titleCase(type), value: titleCase(type),
})); }));
const releaseCountries =
album.tags?.[RELEASE_COUNTRY_TAG]?.map((country) => ({
id: country,
value: country,
})) || [];
const releaseStatuses =
album.tags?.[RELEASE_STATUS_TAG]?.map((status) => ({
id: status,
value: status,
})) || [];
const recordLabels =
album.recordLabels?.map((label) => ({
id: label,
value: label,
})) || [];
console.log('album', album);
const items: Array<{ id: string; value: ReactNode | string | undefined }> = []; const items: Array<{ id: string; value: ReactNode | string | undefined }> = [];
if (originalDifferentFromRelease && album.originalDate) {
items.push({
id: 'originalDate',
value: `${formatDateAbsoluteUTC(album.originalDate)}`,
});
}
items.push(...releaseTypes);
items.push( items.push(
...releaseTypes,
{ {
id: 'isCompilation', id: 'isCompilation',
value: album?.isCompilation value: album?.isCompilation
? t('filter.isCompilation', { postProcess: 'sentenceCase' }) ? t('filter.isCompilation', { postProcess: 'sentenceCase' })
: undefined, : undefined,
}, },
{ ...releaseCountries,
id: 'releaseDate', ...releaseStatuses,
value: album.releaseDate ...recordLabels,
? `${releasePrefix} ${formatDateAbsoluteUTC(album.releaseDate)}`
: undefined,
},
{
id: 'releaseYear',
value: album.releaseDate
? undefined
: album.releaseYear
? album.releaseYear.toString()
: undefined,
},
{
id: 'songCount',
value: album.songCount
? t('entity.trackWithCount', {
count: album.songCount,
})
: undefined,
},
{
id: 'duration',
value: album.duration ? (
<Flex align="center" gap="xs">
<Icon icon="duration" size="md" /> {formatDurationString(album.duration)}
</Flex>
) : undefined,
},
{
id: 'size',
value: album.size ? formatSizeString(album.size) : undefined,
},
{
id: 'playCount',
value:
typeof album.playCount === 'number'
? t('entity.play', {
count: album.playCount,
})
: undefined,
},
{ {
id: 'explicitStatus', id: 'explicitStatus',
value: value:
@@ -147,31 +137,32 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
? t('common.clean', { postProcess: 'sentenceCase' }) ? t('common.clean', { postProcess: 'sentenceCase' })
: undefined, : undefined,
}, },
{
id: 'version',
value: album.version || undefined,
},
); );
return items.filter((item) => item.value); return items.filter((item) => item.value);
}, [album, t]); }, [album, t]);
if (metadataItems.length === 0) return null; const moodTagItems = useMemo(() => {
if (!album) return [];
return album.tags?.[MOOD_TAG]?.map((tag) => ({
id: tag,
value: tag,
}));
}, [album]);
return ( return (
<Stack gap="xs"> <>
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <MetadataPillGroup
{t('common.tags', { postProcess: 'sentenceCase' })} items={defaultTagItems}
</Text> title={t('common.tags', { postProcess: 'sentenceCase' })}
<Pill.Group> />
{metadataItems.map((item, index) => (
<Pill key={`item-${item.id}-${index}`} size="md"> <MetadataPillGroup
{item.value} items={moodTagItems}
</Pill> title={t('common.mood', { postProcess: 'sentenceCase' })}
))} />
</Pill.Group> </>
</Stack>
); );
}; };
@@ -208,38 +199,38 @@ const AlbumMetadataGenres = ({ genres }: AlbumMetadataGenresProps) => {
); );
}; };
interface AlbumMetadataArtistsProps { // interface AlbumMetadataArtistsProps {
artists?: Array<{ id: string; name: string }>; // artists?: Array<{ id: string; name: string }>;
} // }
const AlbumMetadataArtists = ({ artists }: AlbumMetadataArtistsProps) => { // const AlbumMetadataArtists = ({ artists }: AlbumMetadataArtistsProps) => {
const { t } = useTranslation(); // const { t } = useTranslation();
if (!artists || artists.length === 0) return null; // if (!artists || artists.length === 0) return null;
return ( // return (
<Stack gap="xs"> // <Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> // <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('entity.albumArtist', { // {t('entity.albumArtist', {
count: artists.length, // count: artists.length,
})} // })}
</Text> // </Text>
<Pill.Group> // <Pill.Group>
{artists.map((artist) => ( // {artists.map((artist) => (
<PillLink // <PillLink
key={`artist-${artist.id}`} // key={`artist-${artist.id}`}
size="md" // size="md"
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, { // to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: artist.id, // albumArtistId: artist.id,
})} // })}
> // >
{artist.name} // {artist.name}
</PillLink> // </PillLink>
))} // ))}
</Pill.Group> // </Pill.Group>
</Stack> // </Stack>
); // );
}; // };
interface AlbumMetadataExternalLinksProps { interface AlbumMetadataExternalLinksProps {
albumArtist?: string; albumArtist?: string;
@@ -269,7 +260,7 @@ const AlbumMetadataExternalLinks = ({
postProcess: 'sentenceCase', postProcess: 'sentenceCase',
})} })}
</Text> </Text>
<Group gap="sm"> <Group className={styles.externalLinksGroup} gap="sm">
{lastFM && ( {lastFM && (
<ActionIcon <ActionIcon
component="a" component="a"
@@ -382,8 +373,7 @@ export const AlbumDetailContent = () => {
)} )}
</div> </div>
<div className={styles.metadataColumn}> <div className={styles.metadataColumn}>
<Stack gap="2xl"> {/* <AlbumMetadataArtists artists={detailQuery?.data?.albumArtists} /> */}
<AlbumMetadataArtists artists={detailQuery?.data?.albumArtists} />
<AlbumMetadataGenres genres={detailQuery?.data?.genres} /> <AlbumMetadataGenres genres={detailQuery?.data?.genres} />
<AlbumMetadataTags album={detailQuery?.data} /> <AlbumMetadataTags album={detailQuery?.data} />
<AlbumMetadataExternalLinks <AlbumMetadataExternalLinks
@@ -394,7 +384,6 @@ export const AlbumDetailContent = () => {
mbzId={mbzId || undefined} mbzId={mbzId || undefined}
musicBrainz={musicBrainz} musicBrainz={musicBrainz}
/> />
</Stack>
</div> </div>
</div> </div>
{labels && ( {labels && (
@@ -1,11 +1,13 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { forwardRef } from 'react'; import { forwardRef, Fragment, useMemo } from 'react';
import { generatePath, Link, useParams } from 'react-router'; import { useTranslation } from 'react-i18next';
import { Link, useParams } from 'react-router';
import styles from './album-detail-header.module.css'; import styles from './album-detail-header.module.css';
import { useItemImageUrl } from '/@/renderer/components/item-image/item-image'; import { useItemImageUrl } from '/@/renderer/components/item-image/item-image';
import { albumQueries } from '/@/renderer/features/albums/api/album-api'; import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { JoinedAlbumArtist } from '/@/renderer/features/albums/components/joined-album-artist';
import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller'; import { ContextMenuController } from '/@/renderer/features/context-menu/context-menu-controller';
import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { usePlayer } from '/@/renderer/features/player/context/player-context';
import { import {
@@ -15,7 +17,10 @@ import {
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer, useGeneralSettings } from '/@/renderer/store'; import { useCurrentServer, useGeneralSettings } from '/@/renderer/store';
import { usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { formatDateAbsoluteUTC, formatDurationString } from '/@/renderer/utils';
import { normalizeReleaseTypes } from '/@/renderer/utils/normalize-release-types';
import { Group } from '/@/shared/components/group/group'; import { Group } from '/@/shared/components/group/group';
import { Separator } from '/@/shared/components/separator/separator';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { Text } from '/@/shared/components/text/text'; import { Text } from '/@/shared/components/text/text';
import { LibraryItem, ServerType } from '/@/shared/types/domain-types'; import { LibraryItem, ServerType } from '/@/shared/types/domain-types';
@@ -23,6 +28,7 @@ import { Play } from '/@/shared/types/types';
export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => { export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
const { albumId } = useParams() as { albumId: string }; const { albumId } = useParams() as { albumId: string };
const { t } = useTranslation();
const server = useCurrentServer(); const server = useCurrentServer();
const { showRatings } = useGeneralSettings(); const { showRatings } = useGeneralSettings();
const detailQuery = useQuery( const detailQuery = useQuery(
@@ -82,8 +88,8 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
}); });
}; };
const firstAlbumArtist = detailQuery?.data?.albumArtists?.[0];
const releaseYear = detailQuery?.data?.releaseYear; const releaseYear = detailQuery?.data?.releaseYear;
const releaseDate = detailQuery?.data?.releaseDate;
const imageUrl = useItemImageUrl({ const imageUrl = useItemImageUrl({
id: detailQuery?.data?.imageId || undefined, id: detailQuery?.data?.imageId || undefined,
@@ -91,43 +97,128 @@ export const AlbumDetailHeader = forwardRef<HTMLDivElement>((_props, ref) => {
type: 'header', type: 'header',
}); });
const releaseType = detailQuery?.data?.releaseType || undefined; const metadataItems = useMemo(() => {
const items: Array<{ id: string; value: React.ReactNode | string | undefined }> = [];
const album = detailQuery?.data;
if (!album) return [];
const originalDifferentFromRelease =
album?.originalDate && album?.originalDate !== album?.releaseDate;
const playCount = album?.playCount;
const releasePrefix = originalDifferentFromRelease
? t('page.albumDetail.released', { postProcess: 'sentenceCase' })
: '♫';
if (originalDifferentFromRelease && album.originalDate) {
items.push({
id: 'originalDate',
value: `${formatDateAbsoluteUTC(album.originalDate)}`,
});
}
items.push(
...[
{
id: 'releaseDate',
value: releaseDate
? `${releasePrefix} ${formatDateAbsoluteUTC(releaseDate)}`
: releaseYear,
},
{
id: 'songCount',
value: t('entity.trackWithCount', { count: detailQuery?.data?.songCount || 0 }),
},
{
id: 'duration',
value: formatDurationString(detailQuery?.data?.duration || 0),
},
{
id: 'explicitStatus',
value: detailQuery?.data?.explicitStatus,
},
{
id: 'playCount',
value: playCount ? t('entity.play', { count: playCount }) : undefined,
},
],
);
return items.filter((item) => !!item.value);
}, [detailQuery?.data, releaseDate, releaseYear, t]);
const headerItem = useMemo(() => {
const album = detailQuery?.data;
if (!album) return null;
const releaseTypes = album.releaseType
? normalizeReleaseTypes([album.releaseType], t)
: null;
const releaseTypeText = releaseTypes?.length ? releaseTypes[0] : null;
if (releaseTypeText) {
return (
<Group gap="sm">
<Text
component={Link}
fw={600}
isLink
size="md"
to={AppRoute.LIBRARY_ALBUMS}
tt="uppercase"
>
{releaseTypeText}
</Text>
{album.version && (
<>
<Text fw={600} isMuted>
<Separator />
</Text>
<Text>{album.version}</Text>
</>
)}
</Group>
);
}
return null;
}, [detailQuery?.data, t]);
return ( return (
<Stack ref={ref}> <Stack ref={ref}>
<LibraryHeader <LibraryHeader
imageUrl={imageUrl} imageUrl={imageUrl}
item={{ releaseType, route: AppRoute.LIBRARY_ALBUMS, type: LibraryItem.ALBUM }} item={{
children: headerItem,
route: AppRoute.LIBRARY_ALBUMS,
type: LibraryItem.ALBUM,
}}
title={detailQuery?.data?.name || ''} title={detailQuery?.data?.name || ''}
> >
<Stack gap="md" w="100%"> <Stack gap="md" w="100%">
{(firstAlbumArtist || releaseYear) && ( <Group className={styles.metadataGroup} gap="xs">
<Group className={styles.metadataGroup}> {metadataItems.map((item, index) => (
{firstAlbumArtist && ( <Fragment key={item.id}>
<Text {index > 0 && (
component={Link} <Text fw={400} isMuted isNoSelect>
fw={600}
isLink
isNoSelect
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: firstAlbumArtist.id,
})}
>
{firstAlbumArtist.name}
</Text>
)}
{firstAlbumArtist && releaseYear && (
<Text fw={600} isNoSelect>
</Text> </Text>
)} )}
{releaseYear && ( <Text fw={400}>{item.value}</Text>
<Text fw={600} isMuted isNoSelect> </Fragment>
{releaseYear} ))}
</Text> </Group>
)} <Group className={styles.metadataGroup}>
<JoinedAlbumArtist
albumArtist={detailQuery?.data?.albumArtist || ''}
albumArtists={detailQuery?.data?.albumArtists || []}
/>
</Group> </Group>
)}
<LibraryHeaderMenu <LibraryHeaderMenu
favorite={detailQuery?.data?.userFavorite} favorite={detailQuery?.data?.userFavorite}
onFavorite={handleFavorite} onFavorite={handleFavorite}
@@ -0,0 +1,145 @@
import { Fragment } from 'react';
import { generatePath, Link } from 'react-router';
import { AppRoute } from '/@/renderer/router/routes';
import { Group } from '/@/shared/components/group/group';
import { Separator } from '/@/shared/components/separator/separator';
import { Text } from '/@/shared/components/text/text';
import { AlbumArtist, RelatedArtist } from '/@/shared/types/domain-types';
interface JoinedAlbumArtistProps {
albumArtist: string;
albumArtists: AlbumArtist[] | RelatedArtist[];
}
export const JoinedAlbumArtist = ({ albumArtist, albumArtists }: JoinedAlbumArtistProps) => {
const parts: (
| string
| { artist: AlbumArtist | RelatedArtist; end: number; start: number; text: string }
)[] = [];
const matches: Array<{
artist: AlbumArtist | RelatedArtist;
end: number;
name: string;
start: number;
}> = [];
for (const artist of albumArtists) {
const name = artist.name;
const regex = new RegExp(escapeRegex(name), 'gi');
let match: null | RegExpExecArray = null;
while ((match = regex.exec(albumArtist)) !== null) {
matches.push({
artist,
end: match.index + match[0].length,
name: match[0],
start: match.index,
});
}
}
matches.sort((a, b) => {
const lengthDiff = b.end - b.start - (a.end - a.start);
if (lengthDiff !== 0) return lengthDiff;
return a.start - b.start;
});
const nonOverlappingMatches: typeof matches = [];
for (const match of matches) {
const overlaps = nonOverlappingMatches.some(
(existing) =>
(match.start >= existing.start && match.start < existing.end) ||
(match.end > existing.start && match.end <= existing.end) ||
(match.start <= existing.start && match.end >= existing.end),
);
if (!overlaps) {
nonOverlappingMatches.push(match);
}
}
nonOverlappingMatches.sort((a, b) => a.start - b.start);
let lastIndex = 0;
for (const match of nonOverlappingMatches) {
if (match.start > lastIndex) {
parts.push(albumArtist.substring(lastIndex, match.start));
}
parts.push({
artist: match.artist,
end: match.end,
start: match.start,
text: match.name,
});
lastIndex = match.end;
}
if (lastIndex < albumArtist.length) {
parts.push(albumArtist.substring(lastIndex));
}
const hasArtistMatches = parts.some((part) => typeof part !== 'string');
// If no matches found and there are album artists, return the album artists
if (!hasArtistMatches && albumArtists.length > 0) {
return (
<Group gap="xs">
{albumArtists.map((artist, index) => (
<Fragment key={artist.id}>
{index > 0 && <Separator />}
<Text
component={Link}
fw={600}
isLink
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: artist.id,
})}
>
{artist.name}
</Text>
</Fragment>
))}
</Group>
);
}
// If no matches found and no albumArtists, return the original string
if (!hasArtistMatches) {
return (
<Text fw={400} isNoSelect>
{albumArtist}
</Text>
);
}
return (
<Text component="span" fw={400}>
{parts.map((part, index) => {
if (typeof part === 'string') {
return <Fragment key={index}>{part}</Fragment>;
}
const { artist, text } = part;
return (
<Text
component={Link}
fw={600}
isLink
key={`${artist.id}-${index}`}
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL, {
albumArtistId: artist.id,
})}
>
{text}
</Text>
);
})}
</Text>
);
};
function escapeRegex(str: string): string {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
@@ -97,7 +97,7 @@
.title { .title {
display: flex; display: flex;
margin: var(--theme-spacing-sm) 0; margin: 0;
font-size: clamp(1.75rem, 3dvw, 2.75rem); font-size: clamp(1.75rem, 3dvw, 2.75rem);
line-height: 1.2; line-height: 1.2;
} }
@@ -17,7 +17,6 @@ import { usePlayButtonClick } from '/@/renderer/features/shared/hooks/use-play-b
import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation'; import { useIsMutatingCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation'; import { useIsMutatingDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation'; import { useIsMutatingRating } from '/@/renderer/features/shared/mutations/set-rating-mutation';
import { titleCase } from '/@/renderer/utils';
import { ActionIcon } from '/@/shared/components/action-icon/action-icon'; import { ActionIcon } from '/@/shared/components/action-icon/action-icon';
import { Button } from '/@/shared/components/button/button'; import { Button } from '/@/shared/components/button/button';
import { Center } from '/@/shared/components/center/center'; import { Center } from '/@/shared/components/center/center';
@@ -35,7 +34,7 @@ interface LibraryHeaderProps {
containerClassName?: string; containerClassName?: string;
imagePlaceholderUrl?: null | string; imagePlaceholderUrl?: null | string;
imageUrl?: null | string; imageUrl?: null | string;
item: { releaseType?: string; route: string; type?: LibraryItem }; item: { children?: ReactNode; route: string; type?: LibraryItem };
loading?: boolean; loading?: boolean;
title: string; title: string;
} }
@@ -53,85 +52,6 @@ export const LibraryHeader = forwardRef(
}; };
const itemTypeString = (): string => { const itemTypeString = (): string => {
if (item.releaseType) {
switch (item.releaseType) {
case 'album':
return t('releaseType.primary.album', {
postProcess: 'sentenceCase',
});
case 'appears-on':
return t('page.albumArtistDetail.appearsOn', {
postProcess: 'sentenceCase',
});
case 'audiobook':
return t('releaseType.secondary.audiobook', {
postProcess: 'sentenceCase',
});
case 'audio drama':
return t('releaseType.secondary.audioDrama', {
postProcess: 'sentenceCase',
});
case 'broadcast':
return t('releaseType.primary.broadcast', {
postProcess: 'sentenceCase',
});
case 'compilation':
return t('releaseType.secondary.compilation', {
postProcess: 'sentenceCase',
});
case 'demo':
return t('releaseType.secondary.demo', {
postProcess: 'sentenceCase',
});
case 'dj-mix':
return t('releaseType.secondary.djMix', {
postProcess: 'sentenceCase',
});
case 'ep':
return t('releaseType.primary.ep', {
postProcess: 'sentenceCase',
});
case 'field recording':
return t('releaseType.secondary.fieldRecording', {
postProcess: 'sentenceCase',
});
case 'interview':
return t('releaseType.secondary.interview', {
postProcess: 'sentenceCase',
});
case 'live':
return t('releaseType.secondary.live', {
postProcess: 'sentenceCase',
});
case 'mixtape/street':
return t('releaseType.secondary.mixtape', {
postProcess: 'sentenceCase',
});
case 'other':
return t('releaseType.primary.other', {
postProcess: 'sentenceCase',
});
case 'remix':
return t('releaseType.secondary.remix', {
postProcess: 'sentenceCase',
});
case 'single':
return t('releaseType.primary.single', {
postProcess: 'sentenceCase',
});
case 'soundtrack':
return t('releaseType.secondary.soundtrack', {
postProcess: 'sentenceCase',
});
case 'spokenword':
return t('releaseType.secondary.spokenWord', {
postProcess: 'sentenceCase',
});
default:
return titleCase(item.releaseType);
}
}
switch (item.type) { switch (item.type) {
case LibraryItem.ALBUM: case LibraryItem.ALBUM:
return t('entity.album', { count: 1 }); return t('entity.album', { count: 1 });
@@ -203,18 +123,22 @@ export const LibraryHeader = forwardRef(
</div> </div>
{title && ( {title && (
<div className={styles.metadataSection}> <div className={styles.metadataSection}>
{item.children ? (
<div className={styles.itemType}>{item.children}</div>
) : (
<Text <Text
className={styles.itemType} className={styles.itemType}
component={Link} component={Link}
fw={600} fw={600}
isLink isLink
size="md" size="md"
style={{}}
to={item.route} to={item.route}
tt="uppercase" tt="uppercase"
> >
{itemTypeString()} {itemTypeString()}
</Text> </Text>
)}
<h1 <h1
className={styles.title} className={styles.title}
style={{ style={{