update album artist page

This commit is contained in:
jeffvli
2025-11-28 22:59:40 -08:00
parent 663fdd426f
commit a18d2ee305
5 changed files with 345 additions and 222 deletions
@@ -1,12 +1,12 @@
.content-container { .content-container {
position: relative; position: relative;
z-index: 0; z-index: 0;
container-type: inline-size;
} }
.detail-container { .detail-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--theme-spacing-lg); gap: var(--theme-spacing-2xl);
padding: 1rem 2rem 5rem; padding: 1rem 2rem 5rem;
overflow: hidden;
} }
@@ -1,4 +1,4 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery, useSuspenseQuery } from '@tanstack/react-query';
import { Suspense, useMemo } from 'react'; import { Suspense, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { createSearchParams, generatePath, Link, useParams } from 'react-router'; import { createSearchParams, generatePath, Link, useParams } from 'react-router';
@@ -25,225 +25,55 @@ import { Spinner } from '/@/shared/components/spinner/spinner';
import { Spoiler } from '/@/shared/components/spoiler/spoiler'; import { Spoiler } from '/@/shared/components/spoiler/spoiler';
import { Stack } from '/@/shared/components/stack/stack'; import { Stack } from '/@/shared/components/stack/stack';
import { TextTitle } from '/@/shared/components/text-title/text-title'; import { TextTitle } from '/@/shared/components/text-title/text-title';
import { Text } from '/@/shared/components/text/text';
import { import {
AlbumArtist, AlbumArtist,
AlbumListSort, AlbumListSort,
LibraryItem, LibraryItem,
ServerType, ServerType,
SortOrder, SortOrder,
TopSongListResponse,
} from '/@/shared/types/domain-types'; } from '/@/shared/types/domain-types';
import { Play } from '/@/shared/types/types'; import { Play } from '/@/shared/types/types';
export const AlbumArtistDetailContent = () => { interface AlbumArtistActionButtonsProps {
const { t } = useTranslation(); albumCount: null | number | undefined;
const { artistItems, externalLinks, lastFM, musicBrainz } = useGeneralSettings(); artistDiscographyLink: string;
const { albumArtistId, artistId } = useParams() as { artistSongsLink: string;
albumArtistId?: string; onFavorite: () => void;
artistId?: string; onMoreOptions: (e: React.MouseEvent<HTMLButtonElement>) => void;
}; onPlay: () => void;
const routeId = (artistId || albumArtistId) as string; userFavorite?: boolean;
const { ref } = useContainerQuery(); }
const { addToQueueByFetch, setFavorite } = usePlayer();
const server = useCurrentServer();
const genrePath = useGenreRoute();
const [enabledItem, itemOrder] = useMemo(() => { const AlbumArtistActionButtons = ({
const enabled: { [key in ArtistItem]?: boolean } = {}; albumCount,
const order: { [key in ArtistItem]?: number } = {};
for (const [idx, item] of artistItems.entries()) {
enabled[item.id] = !item.disabled;
order[item.id] = idx + 1;
}
return [enabled, order];
}, [artistItems]);
const detailQuery = useQuery(
artistsQueries.albumArtistDetail({
query: { id: routeId },
serverId: server?.id,
}),
);
const artistDiscographyLink = `${generatePath(
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY,
{
albumArtistId: routeId,
},
)}?${createSearchParams({
artistId: routeId,
artistName: detailQuery?.data?.name || '',
})}`;
const artistSongsLink = `${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_SONGS, {
albumArtistId: routeId,
})}?${createSearchParams({
artistId: routeId,
artistName: detailQuery?.data?.name || '',
})}`;
const topSongsQuery = useQuery(
artistsQueries.topSongs({
options: {
enabled: !!detailQuery?.data?.name && enabledItem.topSongs,
},
query: {
artist: detailQuery?.data?.name || '',
artistId: routeId,
},
serverId: server?.id,
}),
);
const carousels = useMemo(() => {
return [
{
isHidden: !enabledItem.recentAlbums || !routeId,
itemType: LibraryItem.ALBUM,
order: itemOrder.recentAlbums,
query: {
artistIds: routeId ? [routeId] : undefined,
compilation: false,
},
sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC,
title: (
<Group align="flex-end">
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.recentReleases', {
postProcess: 'sentenceCase',
})}
</TextTitle>
<Button
component={Link}
size="compact-md"
to={artistDiscographyLink}
variant="subtle"
>
{String(t('page.albumArtistDetail.viewDiscography')).toUpperCase()}
</Button>
</Group>
),
uniqueId: 'recentReleases',
},
{
isHidden:
!enabledItem.compilations || server?.type === ServerType.SUBSONIC || !routeId,
itemType: LibraryItem.ALBUM,
order: itemOrder.compilations,
query: {
artistIds: routeId ? [routeId] : undefined,
compilation: true,
},
sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC,
title: (
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.appearsOn', { postProcess: 'sentenceCase' })}
</TextTitle>
),
uniqueId: 'compilationAlbums',
},
{
data: (detailQuery?.data?.similarArtists || []) as AlbumArtist[],
isHidden: !detailQuery?.data?.similarArtists || !enabledItem.similarArtists,
itemType: LibraryItem.ALBUM_ARTIST,
order: itemOrder.similarArtists,
title: (
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.relatedArtists', {
postProcess: 'sentenceCase',
})}
</TextTitle>
),
uniqueId: 'similarArtists',
},
];
}, [
artistDiscographyLink, artistDiscographyLink,
detailQuery?.data?.similarArtists, artistSongsLink,
enabledItem.compilations, onFavorite,
enabledItem.recentAlbums, onMoreOptions,
enabledItem.similarArtists, onPlay,
itemOrder.compilations, userFavorite,
itemOrder.recentAlbums, }: AlbumArtistActionButtonsProps) => {
itemOrder.similarArtists, const { t } = useTranslation();
routeId,
server?.type,
t,
]);
const playButtonBehavior = usePlayButtonBehavior();
const handlePlay = async (playType?: Play) => {
if (!server?.id) return;
addToQueueByFetch(
server.id,
[routeId],
albumArtistId ? LibraryItem.ALBUM_ARTIST : LibraryItem.ARTIST,
playType || playButtonBehavior,
);
};
const handleFavorite = () => {
if (!detailQuery?.data) return;
setFavorite(
detailQuery.data._serverId,
[detailQuery.data.id],
LibraryItem.ALBUM_ARTIST,
!detailQuery.data.userFavorite,
);
};
const handleMoreOptions = (e: React.MouseEvent<HTMLButtonElement>) => {
if (!detailQuery?.data) return;
ContextMenuController.call({
cmd: { items: [detailQuery.data], type: LibraryItem.ALBUM_ARTIST },
event: e,
});
};
const albumCount = detailQuery?.data?.albumCount;
const biography = useMemo(() => {
const bio = detailQuery?.data?.biography;
if (!bio || !enabledItem.biography) return null;
return sanitize(bio);
}, [detailQuery?.data?.biography, enabledItem.biography]);
const showTopSongs = topSongsQuery?.data?.items?.length && enabledItem.topSongs;
const showGenres = detailQuery?.data?.genres ? detailQuery?.data?.genres.length !== 0 : false;
const mbzId = detailQuery?.data?.mbz;
const isLoading =
detailQuery?.isLoading ||
(server?.type === ServerType.NAVIDROME && enabledItem.topSongs && topSongsQuery?.isLoading);
if (isLoading) return <div className={styles.contentContainer} ref={ref} />;
return ( return (
<div className={styles.contentContainer} ref={ref}> <>
<div className={styles.detailContainer}>
<Group gap="md"> <Group gap="md">
<DefaultPlayButton <DefaultPlayButton disabled={albumCount === 0} onClick={onPlay} />
disabled={albumCount === 0}
onClick={() => handlePlay(playButtonBehavior)}
/>
<Group gap="xs"> <Group gap="xs">
<ActionIcon <ActionIcon
icon="favorite" icon="favorite"
iconProps={{ iconProps={{
fill: detailQuery?.data?.userFavorite ? 'primary' : undefined, fill: userFavorite ? 'primary' : undefined,
}} }}
onClick={handleFavorite} onClick={onFavorite}
size="lg" size="lg"
variant="transparent" variant="transparent"
/> />
<ActionIcon <ActionIcon
icon="ellipsisHorizontal" icon="ellipsisHorizontal"
onClick={handleMoreOptions} onClick={onMoreOptions}
size="lg" size="lg"
variant="transparent" variant="transparent"
/> />
@@ -258,26 +88,45 @@ export const AlbumArtistDetailContent = () => {
> >
{String(t('page.albumArtistDetail.viewDiscography')).toUpperCase()} {String(t('page.albumArtistDetail.viewDiscography')).toUpperCase()}
</Button> </Button>
<Button <Button component={Link} size="compact-md" to={artistSongsLink} variant="subtle">
component={Link}
size="compact-md"
to={artistSongsLink}
variant="subtle"
>
{String(t('page.albumArtistDetail.viewAllTracks')).toUpperCase()} {String(t('page.albumArtistDetail.viewAllTracks')).toUpperCase()}
</Button> </Button>
</Group> </Group>
{showGenres ? ( </>
<section> );
};
interface AlbumArtistMetadataGenresProps {
genres?: Array<{ id: string; name: string }>;
}
const AlbumArtistMetadataGenres = ({ genres }: AlbumArtistMetadataGenresProps) => {
const { t } = useTranslation();
const genrePath = useGenreRoute();
if (!genres || genres.length === 0) return null;
return (
<Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('entity.genre', {
count: genres.length,
})}
</Text>
<Group gap="sm"> <Group gap="sm">
{detailQuery?.data?.genres?.map((genre) => ( {genres.map((genre) => (
<Button <Button
component={Link} component={Link}
key={`genre-${genre.id}`} key={`genre-${genre.id}`}
radius="md" radius="md"
size="compact-md" size="compact-md"
to={generatePath(genrePath, { to={generatePath(genrePath, {
albumArtistId: null,
albumId: null,
artistId: null,
genreId: genre.id, genreId: genre.id,
itemType: null,
playlistId: null,
})} })}
variant="outline" variant="outline"
> >
@@ -285,17 +134,109 @@ export const AlbumArtistDetailContent = () => {
</Button> </Button>
))} ))}
</Group> </Group>
</Stack>
);
};
interface AlbumArtistMetadataBiographyProps {
artistName?: string;
biography: null | string | undefined;
}
const AlbumArtistMetadataBiography = ({
artistName,
biography,
}: AlbumArtistMetadataBiographyProps) => {
const { t } = useTranslation();
if (!biography) return null;
const sanitizedBiography = sanitize(biography);
return (
<section style={{ maxWidth: '1280px' }}>
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.about', {
artist: artistName,
})}
</TextTitle>
<Spoiler dangerouslySetInnerHTML={{ __html: sanitizedBiography }} />
</section> </section>
) : null} );
{externalLinks && (lastFM || musicBrainz) ? ( };
interface AlbumArtistMetadataTopSongsProps {
routeId: string;
topSongsQuery: ReturnType<typeof useQuery<TopSongListResponse>>;
}
const AlbumArtistMetadataTopSongs = ({
routeId,
topSongsQuery,
}: AlbumArtistMetadataTopSongsProps) => {
const { t } = useTranslation();
if (!topSongsQuery?.data?.items?.length) return null;
return (
<section> <section>
<Group justify="space-between" wrap="nowrap">
<Group align="flex-end" wrap="nowrap">
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.topSongs', {
postProcess: 'sentenceCase',
})}
</TextTitle>
<Button
component={Link}
size="compact-md"
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS, {
albumArtistId: routeId,
})}
uppercase
variant="subtle"
>
{t('page.albumArtistDetail.viewAll', {
postProcess: 'sentenceCase',
})}
</Button>
</Group>
</Group>
</section>
);
};
interface AlbumArtistMetadataExternalLinksProps {
artistName?: string;
externalLinks: boolean;
lastFM: boolean;
mbzId?: null | string;
musicBrainz: boolean;
}
const AlbumArtistMetadataExternalLinks = ({
artistName,
externalLinks,
lastFM,
mbzId,
musicBrainz,
}: AlbumArtistMetadataExternalLinksProps) => {
const { t } = useTranslation();
if (!externalLinks || (!lastFM && !musicBrainz)) return null;
return (
<Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('common.externalLinks', {
postProcess: 'sentenceCase',
})}
</Text>
<Group gap="sm"> <Group gap="sm">
{lastFM && ( {lastFM && (
<ActionIcon <ActionIcon
component="a" component="a"
href={`https://www.last.fm/music/${encodeURIComponent( href={`https://www.last.fm/music/${encodeURIComponent(artistName || '')}`}
detailQuery?.data?.name || '',
)}`}
icon="brandLastfm" icon="brandLastfm"
iconProps={{ iconProps={{
fill: 'default', fill: 'default',
@@ -327,54 +268,240 @@ export const AlbumArtistDetailContent = () => {
/> />
) : null} ) : null}
</Group> </Group>
</section> </Stack>
) : null} );
<Grid gutter="xl"> };
{biography ? (
<Grid.Col order={itemOrder.biography} span={12}> export const AlbumArtistDetailContent = () => {
<section style={{ maxWidth: '1280px' }}> const { t } = useTranslation();
const { artistItems, externalLinks, lastFM, musicBrainz } = useGeneralSettings();
const { albumArtistId, artistId } = useParams() as {
albumArtistId?: string;
artistId?: string;
};
const routeId = (artistId || albumArtistId) as string;
const { ref, ...cq } = useContainerQuery();
const { addToQueueByFetch, setFavorite } = usePlayer();
const server = useCurrentServer();
const [enabledItem, itemOrder] = useMemo(() => {
const enabled: { [key in ArtistItem]?: boolean } = {};
const order: { [key in ArtistItem]?: number } = {};
for (const [idx, item] of artistItems.entries()) {
enabled[item.id] = !item.disabled;
order[item.id] = idx + 1;
}
return [enabled, order];
}, [artistItems]);
const detailQuery = useSuspenseQuery(
artistsQueries.albumArtistDetail({
query: { id: routeId },
serverId: server?.id,
}),
);
const artistDiscographyLink = `${generatePath(
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY,
{
albumArtistId: routeId,
},
)}?${createSearchParams({
artistId: routeId,
artistName: detailQuery.data?.name || '',
})}`;
const artistSongsLink = `${generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_SONGS, {
albumArtistId: routeId,
})}?${createSearchParams({
artistId: routeId,
artistName: detailQuery.data?.name || '',
})}`;
const topSongsQuery = useQuery(
artistsQueries.topSongs({
options: {
enabled: !!detailQuery.data?.name && enabledItem.topSongs,
},
query: {
artist: detailQuery.data?.name || '',
artistId: routeId,
},
serverId: server?.id,
}),
);
const carousels = useMemo(() => {
return [
{
isHidden: !enabledItem.recentAlbums || !routeId,
itemType: LibraryItem.ALBUM,
order: itemOrder.recentAlbums,
query: {
artistIds: routeId ? [routeId] : undefined,
compilation: false,
},
rowCount: 2,
sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC,
title: (
<Group align="flex-end">
<TextTitle fw={700} order={2}> <TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.about', { {t('page.albumArtistDetail.recentReleases', {
artist: detailQuery?.data?.name,
})}
</TextTitle>
<Spoiler dangerouslySetInnerHTML={{ __html: biography }} />
</section>
</Grid.Col>
) : null}
{showTopSongs ? (
<Grid.Col order={itemOrder.topSongs} span={12}>
<section>
<Group justify="space-between" wrap="nowrap">
<Group align="flex-end" wrap="nowrap">
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.topSongs', {
postProcess: 'sentenceCase', postProcess: 'sentenceCase',
})} })}
</TextTitle> </TextTitle>
<Button <Button
component={Link} component={Link}
size="compact-md" size="compact-md"
to={generatePath( to={artistDiscographyLink}
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS,
{
albumArtistId: routeId,
},
)}
uppercase
variant="subtle" variant="subtle"
> >
{t('page.albumArtistDetail.viewAll', { {String(t('page.albumArtistDetail.viewDiscography')).toUpperCase()}
postProcess: 'sentenceCase',
})}
</Button> </Button>
</Group> </Group>
</Group> ),
</section> uniqueId: 'recentReleases',
</Grid.Col> },
) : null} {
isHidden:
!enabledItem.compilations || server?.type === ServerType.SUBSONIC || !routeId,
itemType: LibraryItem.ALBUM,
order: itemOrder.compilations,
query: {
artistIds: routeId ? [routeId] : undefined,
compilation: true,
},
rowCount: 1,
sortBy: AlbumListSort.RELEASE_DATE,
sortOrder: SortOrder.DESC,
title: (
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.appearsOn', { postProcess: 'sentenceCase' })}
</TextTitle>
),
uniqueId: 'compilationAlbums',
},
{
data: (detailQuery.data?.similarArtists || []) as AlbumArtist[],
isHidden: !detailQuery.data?.similarArtists || !enabledItem.similarArtists,
itemType: LibraryItem.ALBUM_ARTIST,
order: itemOrder.similarArtists,
rowCount: 1,
title: (
<TextTitle fw={700} order={2}>
{t('page.albumArtistDetail.relatedArtists', {
postProcess: 'sentenceCase',
})}
</TextTitle>
),
uniqueId: 'similarArtists',
},
];
}, [
artistDiscographyLink,
detailQuery.data?.similarArtists,
enabledItem.compilations,
enabledItem.recentAlbums,
enabledItem.similarArtists,
itemOrder.compilations,
itemOrder.recentAlbums,
itemOrder.similarArtists,
routeId,
server?.type,
t,
]);
{carousels const playButtonBehavior = usePlayButtonBehavior();
const handlePlay = async (playType?: Play) => {
if (!server?.id) return;
addToQueueByFetch(
server.id,
[routeId],
albumArtistId ? LibraryItem.ALBUM_ARTIST : LibraryItem.ARTIST,
playType || playButtonBehavior,
);
};
const handleFavorite = () => {
if (!detailQuery.data) return;
setFavorite(
detailQuery.data._serverId,
[detailQuery.data.id],
LibraryItem.ALBUM_ARTIST,
!detailQuery.data.userFavorite,
);
};
const handleMoreOptions = (e: React.MouseEvent<HTMLButtonElement>) => {
if (!detailQuery.data) return;
ContextMenuController.call({
cmd: { items: [detailQuery.data], type: LibraryItem.ALBUM_ARTIST },
event: e,
});
};
const albumCount = detailQuery.data?.albumCount;
const biography =
detailQuery.data?.biography && enabledItem.biography ? detailQuery.data.biography : null;
const showGenres = detailQuery.data?.genres ? detailQuery.data.genres.length !== 0 : false;
const mbzId = detailQuery.data?.mbz;
// Calculate order for genres and external links (show before other sections)
// Use a very low order number to ensure they appear first
const genresOrder = 0;
const externalLinksOrder = 0.5;
return (
<div className={styles.contentContainer} ref={ref}>
<div className={styles.detailContainer}>
<AlbumArtistActionButtons
albumCount={albumCount}
artistDiscographyLink={artistDiscographyLink}
artistSongsLink={artistSongsLink}
onFavorite={handleFavorite}
onMoreOptions={handleMoreOptions}
onPlay={() => handlePlay(playButtonBehavior)}
userFavorite={detailQuery.data?.userFavorite}
/>
<Grid gutter="xl">
{showGenres && (
<Grid.Col order={genresOrder} span={12}>
<AlbumArtistMetadataGenres genres={detailQuery.data?.genres} />
</Grid.Col>
)}
{externalLinks && (lastFM || musicBrainz) && (
<Grid.Col order={externalLinksOrder} span={12}>
<AlbumArtistMetadataExternalLinks
artistName={detailQuery.data?.name}
externalLinks={externalLinks}
lastFM={lastFM}
mbzId={mbzId}
musicBrainz={musicBrainz}
/>
</Grid.Col>
)}
{biography && (
<Grid.Col order={itemOrder.biography} span={12}>
<AlbumArtistMetadataBiography
artistName={detailQuery.data?.name}
biography={biography}
/>
</Grid.Col>
)}
{Boolean(topSongsQuery?.data?.items?.length) && enabledItem.topSongs && (
<Grid.Col order={itemOrder.topSongs} span={12}>
<AlbumArtistMetadataTopSongs
routeId={routeId}
topSongsQuery={topSongsQuery}
/>
</Grid.Col>
)}
{cq.height || cq.width
? carousels
.filter((c) => !c.isHidden) .filter((c) => !c.isHidden)
.map((carousel) => ( .map((carousel) => (
<Grid.Col <Grid.Col
@@ -382,36 +509,33 @@ export const AlbumArtistDetailContent = () => {
order={carousel.order} order={carousel.order}
span={12} span={12}
> >
<section> <Suspense fallback={<Spinner container />}>
<Stack gap="xl">
{carousel.itemType === LibraryItem.ALBUM ? ( {carousel.itemType === LibraryItem.ALBUM ? (
'query' in carousel && 'query' in carousel &&
carousel.query && carousel.query &&
carousel.sortBy && carousel.sortBy &&
carousel.sortOrder ? ( carousel.sortOrder ? (
<Suspense fallback={<Spinner container />}>
<AlbumInfiniteCarousel <AlbumInfiniteCarousel
query={carousel.query} query={carousel.query}
rowCount={1} rowCount={carousel.rowCount}
sortBy={carousel.sortBy} sortBy={carousel.sortBy}
sortOrder={carousel.sortOrder} sortOrder={carousel.sortOrder}
title={carousel.title} title={carousel.title}
/> />
</Suspense>
) : null ) : null
) : carousel.itemType === LibraryItem.ALBUM_ARTIST ? ( ) : carousel.itemType === LibraryItem.ALBUM_ARTIST ? (
'data' in carousel && carousel.data ? ( 'data' in carousel && carousel.data ? (
<AlbumArtistGridCarousel <AlbumArtistGridCarousel
data={carousel.data} data={carousel.data}
rowCount={1} rowCount={carousel.rowCount}
title={carousel.title} title={carousel.title}
/> />
) : null ) : null
) : null} ) : null}
</Stack> </Suspense>
</section>
</Grid.Col> </Grid.Col>
))} ))
: null}
</Grid> </Grid>
</div> </div>
</div> </div>
@@ -1,4 +1,4 @@
import { useQuery } from '@tanstack/react-query'; import { useSuspenseQuery } from '@tanstack/react-query';
import { useRef } from 'react'; import { useRef } from 'react';
import { useLocation, useParams } from 'react-router'; import { useLocation, useParams } from 'react-router';
@@ -33,11 +33,8 @@ const AlbumArtistDetailRoute = () => {
const location = useLocation(); const location = useLocation();
const detailQuery = useQuery({ const detailQuery = useSuspenseQuery({
...artistsQueries.albumArtistDetail({ ...artistsQueries.albumArtistDetail({ query: { id: routeId }, serverId: server?.id }),
query: { id: routeId },
serverId: server?.id,
}),
initialData: location.state?.item, initialData: location.state?.item,
staleTime: 0, staleTime: 0,
}); });
@@ -49,6 +46,7 @@ const AlbumArtistDetailRoute = () => {
}); });
const background = backgroundColor; const background = backgroundColor;
const showBlurredImage = Boolean(detailQuery.data?.imageUrl) && artistBackground; const showBlurredImage = Boolean(detailQuery.data?.imageUrl) && artistBackground;
return ( return (
@@ -61,6 +59,7 @@ const AlbumArtistDetailRoute = () => {
<LibraryHeaderBar.PlayButton <LibraryHeaderBar.PlayButton
ids={[routeId]} ids={[routeId]}
itemType={LibraryItem.ALBUM_ARTIST} itemType={LibraryItem.ALBUM_ARTIST}
variant="default"
/> />
<LibraryHeaderBar.Title> <LibraryHeaderBar.Title>
{detailQuery?.data?.name} {detailQuery?.data?.name}
@@ -72,11 +71,11 @@ const AlbumArtistDetailRoute = () => {
}} }}
ref={scrollAreaRef} ref={scrollAreaRef}
> >
{showBlurredImage && detailQuery.data?.imageUrl ? ( {showBlurredImage ? (
<LibraryBackgroundImage <LibraryBackgroundImage
blur={artistBackgroundBlur} blur={artistBackgroundBlur}
headerRef={headerRef} headerRef={headerRef}
imageUrl={detailQuery.data.imageUrl} imageUrl={detailQuery.data?.imageUrl || ''}
/> />
) : ( ) : (
<LibraryBackgroundOverlay backgroundColor={background} headerRef={headerRef} /> <LibraryBackgroundOverlay backgroundColor={background} headerRef={headerRef} />
@@ -10,7 +10,6 @@
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
height: auto; height: auto;
min-height: 340px;
padding: 2rem 1rem; padding: 2rem 1rem;
:global(.item-image-placeholder) { :global(.item-image-placeholder) {
@@ -101,6 +101,7 @@ export const LibraryHeader = forwardRef(
alt="cover" alt="cover"
className={styles.image} className={styles.image}
containerClassName={styles.image} containerClassName={styles.image}
key={imageUrl}
loading="eager" loading="eager"
onError={onImageError} onError={onImageError}
src={imageUrl || ''} src={imageUrl || ''}