refactor grid columns internally into album artist sections to handle null cases

This commit is contained in:
jeffvli
2026-03-08 22:15:54 -07:00
parent 17deac8d65
commit bc6cd5b014
@@ -159,15 +159,17 @@ const AlbumArtistActionButtons = ({
interface AlbumArtistMetadataGenresProps { interface AlbumArtistMetadataGenresProps {
genres?: Array<{ id: string; name: string }>; genres?: Array<{ id: string; name: string }>;
order?: number;
} }
const AlbumArtistMetadataGenres = ({ genres }: AlbumArtistMetadataGenresProps) => { const AlbumArtistMetadataGenres = ({ genres, order }: AlbumArtistMetadataGenresProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const genrePath = useGenreRoute(); const genrePath = useGenreRoute();
if (!genres || genres.length === 0) return null; if (!genres || genres.length === 0) return null;
return ( return (
<Grid.Col order={order} span={12}>
<Stack gap="xs"> <Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('entity.genre', { {t('entity.genre', {
@@ -196,16 +198,19 @@ const AlbumArtistMetadataGenres = ({ genres }: AlbumArtistMetadataGenresProps) =
))} ))}
</Group> </Group>
</Stack> </Stack>
</Grid.Col>
); );
}; };
interface AlbumArtistMetadataBiographyProps { interface AlbumArtistMetadataBiographyProps {
artistName?: string; artistName?: string;
order?: number;
routeId: string; routeId: string;
} }
const AlbumArtistMetadataBiography = ({ const AlbumArtistMetadataBiography = ({
artistName, artistName,
order,
routeId, routeId,
}: AlbumArtistMetadataBiographyProps) => { }: AlbumArtistMetadataBiographyProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -234,6 +239,7 @@ const AlbumArtistMetadataBiography = ({
if (isLoading) { if (isLoading) {
return ( return (
<Grid.Col order={order} span={12}>
<section style={{ maxWidth: '1280px' }}> <section style={{ maxWidth: '1280px' }}>
<TextTitle fw={700} order={3}> <TextTitle fw={700} order={3}>
{t('page.albumArtistDetail.about', { {t('page.albumArtistDetail.about', {
@@ -246,6 +252,7 @@ const AlbumArtistMetadataBiography = ({
<Skeleton enableAnimation height="1rem" width="60%" /> <Skeleton enableAnimation height="1rem" width="60%" />
</Stack> </Stack>
</section> </section>
</Grid.Col>
); );
} }
@@ -254,6 +261,7 @@ const AlbumArtistMetadataBiography = ({
} }
return ( return (
<Grid.Col order={order} span={12}>
<section style={{ maxWidth: '1280px' }}> <section style={{ maxWidth: '1280px' }}>
<TextTitle fw={700} order={3}> <TextTitle fw={700} order={3}>
{t('page.albumArtistDetail.about', { {t('page.albumArtistDetail.about', {
@@ -264,6 +272,7 @@ const AlbumArtistMetadataBiography = ({
<Text dangerouslySetInnerHTML={{ __html: sanitizedBiography }} /> <Text dangerouslySetInnerHTML={{ __html: sanitizedBiography }} />
</Spoiler> </Spoiler>
</section> </section>
</Grid.Col>
); );
}; };
@@ -302,11 +311,13 @@ const SongTableListContainer = ({
interface AlbumArtistMetadataTopSongsProps { interface AlbumArtistMetadataTopSongsProps {
detailQuery: ReturnType<typeof useSuspenseQuery<AlbumArtistDetailResponse>>; detailQuery: ReturnType<typeof useSuspenseQuery<AlbumArtistDetailResponse>>;
order?: number;
routeId: string; routeId: string;
} }
const AlbumArtistMetadataTopSongsContent = ({ const AlbumArtistMetadataTopSongsContent = ({
detailQuery, detailQuery,
order,
routeId, routeId,
}: AlbumArtistMetadataTopSongsProps) => { }: AlbumArtistMetadataTopSongsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -395,10 +406,12 @@ const AlbumArtistMetadataTopSongsContent = ({
const isLoading = topSongsQuery.isLoading || !topSongsQuery.data; const isLoading = topSongsQuery.isLoading || !topSongsQuery.data;
if (!isLoading && !tableConfig) return null; if (!isLoading && !tableConfig) return null;
if (!isLoading && songs.length === 0) return null;
const currentSongId = currentSong?.id; const currentSongId = currentSong?.id;
return ( return (
<Grid.Col order={order} span={12}>
<section> <section>
<Stack gap="md"> <Stack gap="md">
<div className={styles.albumSectionTitle}> <div className={styles.albumSectionTitle}>
@@ -475,7 +488,9 @@ const AlbumArtistMetadataTopSongsContent = ({
flex={1} flex={1}
leftSection={<Icon icon="search" />} leftSection={<Icon icon="search" />}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('common.search', { postProcess: 'sentenceCase' })} placeholder={t('common.search', {
postProcess: 'sentenceCase',
})}
radius="xl" radius="xl"
rightSection={ rightSection={
searchTerm ? ( searchTerm ? (
@@ -564,11 +579,13 @@ const AlbumArtistMetadataTopSongsContent = ({
) : null} ) : null}
</Stack> </Stack>
</section> </section>
</Grid.Col>
); );
}; };
const AlbumArtistMetadataTopSongs = ({ const AlbumArtistMetadataTopSongs = ({
detailQuery, detailQuery,
order,
routeId, routeId,
}: AlbumArtistMetadataTopSongsProps) => { }: AlbumArtistMetadataTopSongsProps) => {
const server = useCurrentServer(); const server = useCurrentServer();
@@ -581,17 +598,25 @@ const AlbumArtistMetadataTopSongs = ({
return ( return (
<Suspense fallback={null}> <Suspense fallback={null}>
{canStartQuery ? ( {canStartQuery ? (
<AlbumArtistMetadataTopSongsContent detailQuery={detailQuery} routeId={routeId} /> <AlbumArtistMetadataTopSongsContent
detailQuery={detailQuery}
order={order}
routeId={routeId}
/>
) : null} ) : null}
</Suspense> </Suspense>
); );
}; };
interface AlbumArtistMetadataFavoriteSongsProps { interface AlbumArtistMetadataFavoriteSongsProps {
order?: number;
routeId: string; routeId: string;
} }
const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavoriteSongsProps) => { const AlbumArtistMetadataFavoriteSongs = ({
order,
routeId,
}: AlbumArtistMetadataFavoriteSongsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300); const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300);
@@ -671,10 +696,12 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
const isLoading = favoriteSongsQuery.isLoading || !favoriteSongsQuery.data; const isLoading = favoriteSongsQuery.isLoading || !favoriteSongsQuery.data;
if (!isLoading && !tableConfig) return null; if (!isLoading && !tableConfig) return null;
if (!isLoading && songs.length === 0) return null;
const currentSongId = currentSong?.id; const currentSongId = currentSong?.id;
return ( return (
<Grid.Col order={order} span={12}>
<section> <section>
<Stack gap="md"> <Stack gap="md">
<div className={styles.albumSectionTitle}> <div className={styles.albumSectionTitle}>
@@ -691,9 +718,12 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
<Button <Button
component={Link} component={Link}
size="compact-md" size="compact-md"
to={generatePath(AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_FAVORITE_SONGS, { to={generatePath(
AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_FAVORITE_SONGS,
{
albumArtistId: routeId, albumArtistId: routeId,
})} },
)}
uppercase uppercase
variant="subtle" variant="subtle"
> >
@@ -751,7 +781,9 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
flex={1} flex={1}
leftSection={<Icon icon="search" />} leftSection={<Icon icon="search" />}
onChange={(e) => setSearchTerm(e.target.value)} onChange={(e) => setSearchTerm(e.target.value)}
placeholder={t('common.search', { postProcess: 'sentenceCase' })} placeholder={t('common.search', {
postProcess: 'sentenceCase',
})}
radius="xl" radius="xl"
rightSection={ rightSection={
searchTerm ? ( searchTerm ? (
@@ -819,6 +851,7 @@ const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavori
) : null} ) : null}
</Stack> </Stack>
</section> </section>
</Grid.Col>
); );
}; };
@@ -828,6 +861,7 @@ interface AlbumArtistMetadataExternalLinksProps {
lastFM: boolean; lastFM: boolean;
mbzId?: null | string; mbzId?: null | string;
musicBrainz: boolean; musicBrainz: boolean;
order?: number;
} }
const AlbumArtistMetadataExternalLinks = ({ const AlbumArtistMetadataExternalLinks = ({
@@ -836,12 +870,14 @@ const AlbumArtistMetadataExternalLinks = ({
lastFM, lastFM,
mbzId, mbzId,
musicBrainz, musicBrainz,
order,
}: AlbumArtistMetadataExternalLinksProps) => { }: AlbumArtistMetadataExternalLinksProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
if (!externalLinks || (!lastFM && !musicBrainz)) return null; if (!externalLinks || (!lastFM && !musicBrainz)) return null;
return ( return (
<Grid.Col order={order} span={12}>
<Stack gap="xs"> <Stack gap="xs">
<Text fw={600} isNoSelect size="sm" tt="uppercase"> <Text fw={600} isNoSelect size="sm" tt="uppercase">
{t('common.externalLinks', { {t('common.externalLinks', {
@@ -885,14 +921,19 @@ const AlbumArtistMetadataExternalLinks = ({
) : null} ) : null}
</Group> </Group>
</Stack> </Stack>
</Grid.Col>
); );
}; };
interface AlbumArtistMetadataSimilarArtistsProps { interface AlbumArtistMetadataSimilarArtistsProps {
order?: number;
routeId: string; routeId: string;
} }
const AlbumArtistMetadataSimilarArtists = ({ routeId }: AlbumArtistMetadataSimilarArtistsProps) => { const AlbumArtistMetadataSimilarArtists = ({
order,
routeId,
}: AlbumArtistMetadataSimilarArtistsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const server = useCurrentServer(); const server = useCurrentServer();
const serverId = useCurrentServerId(); const serverId = useCurrentServerId();
@@ -957,6 +998,7 @@ const AlbumArtistMetadataSimilarArtists = ({ routeId }: AlbumArtistMetadataSimil
} }
return ( return (
<Grid.Col order={order} span={12}>
<AlbumArtistGridCarousel <AlbumArtistGridCarousel
data={similarArtists} data={similarArtists}
excludeIds={[routeId]} excludeIds={[routeId]}
@@ -964,6 +1006,7 @@ const AlbumArtistMetadataSimilarArtists = ({ routeId }: AlbumArtistMetadataSimil
rowCount={1} rowCount={1}
title={carouselTitle} title={carouselTitle}
/> />
</Grid.Col>
); );
}; };
@@ -1022,7 +1065,6 @@ export const AlbumArtistDetailContent = ({
[routeId, detailQuery.data?.name], [routeId, detailQuery.data?.name],
); );
const showGenres = detailQuery.data?.genres ? detailQuery.data.genres.length !== 0 : false;
const mbzId = detailQuery.data?.mbz; const mbzId = detailQuery.data?.mbz;
const handleArtistRadio = useCallback(async () => { const handleArtistRadio = useCallback(async () => {
@@ -1061,50 +1103,46 @@ export const AlbumArtistDetailContent = ({
onArtistRadio={handleArtistRadio} onArtistRadio={handleArtistRadio}
/> />
<Grid gutter="2xl"> <Grid gutter="2xl">
{showGenres && ( <AlbumArtistMetadataGenres
<Grid.Col order={genresOrder} span={12}> genres={detailQuery.data?.genres}
<AlbumArtistMetadataGenres genres={detailQuery.data?.genres} /> order={genresOrder}
</Grid.Col> />
)}
{externalLinks && (lastFM || musicBrainz) && ( {externalLinks && (lastFM || musicBrainz) && (
<Grid.Col order={externalLinksOrder} span={12}>
<AlbumArtistMetadataExternalLinks <AlbumArtistMetadataExternalLinks
artistName={detailQuery.data?.name} artistName={detailQuery.data?.name}
externalLinks={externalLinks} externalLinks={externalLinks}
lastFM={lastFM} lastFM={lastFM}
mbzId={mbzId} mbzId={mbzId}
musicBrainz={musicBrainz} musicBrainz={musicBrainz}
order={externalLinksOrder}
/> />
</Grid.Col>
)} )}
{enabledItem.biography && ( {enabledItem.biography && (
<Grid.Col order={itemOrder.biography} span={12}>
<AlbumArtistMetadataBiography <AlbumArtistMetadataBiography
artistName={detailQuery.data?.name} artistName={detailQuery.data?.name}
order={itemOrder.biography}
routeId={routeId} routeId={routeId}
/> />
</Grid.Col>
)} )}
<Grid.Col order={itemOrder.recentAlbums} span={12}> <ArtistAlbums albumsQuery={albumsQuery} order={itemOrder.recentAlbums} />
<ArtistAlbums albumsQuery={albumsQuery} />
</Grid.Col>
{enabledItem.similarArtists && ( {enabledItem.similarArtists && (
<Grid.Col order={itemOrder.similarArtists} span={12}> <AlbumArtistMetadataSimilarArtists
<AlbumArtistMetadataSimilarArtists routeId={routeId} /> order={itemOrder.similarArtists}
</Grid.Col> routeId={routeId}
/>
)} )}
{enabledItem.topSongs && ( {enabledItem.topSongs && (
<Grid.Col order={itemOrder.topSongs} span={12}>
<AlbumArtistMetadataTopSongs <AlbumArtistMetadataTopSongs
detailQuery={detailQuery} detailQuery={detailQuery}
order={itemOrder.topSongs}
routeId={routeId} routeId={routeId}
/> />
</Grid.Col>
)} )}
{enabledItem.favoriteSongs && ( {enabledItem.favoriteSongs && (
<Grid.Col order={itemOrder.favoriteSongs} span={12}> <AlbumArtistMetadataFavoriteSongs
<AlbumArtistMetadataFavoriteSongs routeId={routeId} /> order={itemOrder.favoriteSongs}
</Grid.Col> routeId={routeId}
/>
)} )}
</Grid> </Grid>
</div> </div>
@@ -1427,9 +1465,10 @@ const releaseTypeToEnumMap: Record<string, ArtistReleaseTypeItem> = {
interface ArtistAlbumsProps { interface ArtistAlbumsProps {
albumsQuery: UseSuspenseQueryResult<AlbumListResponse, Error>; albumsQuery: UseSuspenseQueryResult<AlbumListResponse, Error>;
order?: number;
} }
const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => { const ArtistAlbums = ({ albumsQuery, order }: ArtistAlbumsProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const artistReleaseTypeItems = useArtistReleaseTypeItems(); const artistReleaseTypeItems = useArtistReleaseTypeItems();
const [searchTerm, setSearchTerm] = useState(''); const [searchTerm, setSearchTerm] = useState('');
@@ -1674,6 +1713,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
]); ]);
return ( return (
<Grid.Col order={order} span={12}>
<Stack gap="md"> <Stack gap="md">
<Group gap="sm" w="100%"> <Group gap="sm" w="100%">
<TextInput <TextInput
@@ -1710,7 +1750,9 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
sortBy={sortBy} sortBy={sortBy}
/> />
<ListSortOrderToggleButtonControlled <ListSortOrderToggleButtonControlled
setSortOrder={(value) => setAlbumArtistDetailSort(sortBy, value as SortOrder)} setSortOrder={(value) =>
setAlbumArtistDetailSort(sortBy, value as SortOrder)
}
sortOrder={sortOrder} sortOrder={sortOrder}
/> />
<GroupingTypeSelector /> <GroupingTypeSelector />
@@ -1736,6 +1778,7 @@ const ArtistAlbums = ({ albumsQuery }: ArtistAlbumsProps) => {
</div> </div>
)} )}
</Stack> </Stack>
</Grid.Col>
); );
}; };