reorder album artist page

This commit is contained in:
Kendall Garner 2024-09-01 16:48:43 -07:00
parent eb50c69a35
commit f7dd634f67
No known key found for this signature in database
GPG key ID: 18D2767419676C87
5 changed files with 201 additions and 98 deletions

View file

@ -457,6 +457,8 @@
"albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image", "albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image",
"applicationHotkeys": "application hotkeys", "applicationHotkeys": "application hotkeys",
"applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)", "applicationHotkeys_description": "configure application hotkeys. toggle the checkbox to set as a global hotkey (desktop only)",
"artistConfiguration": "album artist page configuration",
"artistConfiguration_description": "configure what items are shown, and in what order, on the album artist page",
"audioDevice": "audio device", "audioDevice": "audio device",
"audioDevice_description": "select the audio device to use for playback (web player only)", "audioDevice_description": "select the audio device to use for playback (web player only)",
"audioExclusiveMode": "audio exclusive mode", "audioExclusiveMode": "audio exclusive mode",

View file

@ -1,6 +1,6 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core'; import { ColDef, RowDoubleClickedEvent } from '@ag-grid-community/core';
import { Box, Group, Stack } from '@mantine/core'; import { Box, Grid, Group, Stack } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaLastfmSquare } from 'react-icons/fa'; import { FaLastfmSquare } from 'react-icons/fa';
import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri'; import { RiHeartFill, RiHeartLine, RiMoreFill } from 'react-icons/ri';
@ -36,7 +36,7 @@ import { PlayButton, useCreateFavorite, useDeleteFavorite } from '/@/renderer/fe
import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay'; import { LibraryBackgroundOverlay } from '/@/renderer/features/shared/components/library-background-overlay';
import { useContainerQuery } from '/@/renderer/hooks'; import { useContainerQuery } from '/@/renderer/hooks';
import { AppRoute } from '/@/renderer/router/routes'; import { AppRoute } from '/@/renderer/router/routes';
import { useCurrentServer } from '/@/renderer/store'; import { ArtistItem, useCurrentServer } from '/@/renderer/store';
import { useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store/settings.store'; import { useGeneralSettings, usePlayButtonBehavior } from '/@/renderer/store/settings.store';
import { CardRow, Play, TableColumn } from '/@/renderer/types'; import { CardRow, Play, TableColumn } from '/@/renderer/types';
import { sanitize } from '/@/renderer/utils/sanitize'; import { sanitize } from '/@/renderer/utils/sanitize';
@ -65,13 +65,25 @@ interface AlbumArtistDetailContentProps {
export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailContentProps) => { export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailContentProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { externalLinks } = useGeneralSettings(); const { artistItems, externalLinks } = useGeneralSettings();
const { albumArtistId } = useParams() as { albumArtistId: string }; const { albumArtistId } = useParams() as { albumArtistId: string };
const cq = useContainerQuery(); const cq = useContainerQuery();
const handlePlayQueueAdd = usePlayQueueAdd(); const handlePlayQueueAdd = usePlayQueueAdd();
const server = useCurrentServer(); const server = useCurrentServer();
const genrePath = useGenreRoute(); const genrePath = useGenreRoute();
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 = useAlbumArtistDetail({ const detailQuery = useAlbumArtistDetail({
query: { id: albumArtistId }, query: { id: albumArtistId },
serverId: server?.id, serverId: server?.id,
@ -95,6 +107,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
})}`; })}`;
const recentAlbumsQuery = useAlbumList({ const recentAlbumsQuery = useAlbumList({
options: {
enabled: enabledItem.recentAlbums,
},
query: { query: {
_custom: { _custom: {
jellyfin: { jellyfin: {
@ -117,6 +132,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
}); });
const compilationAlbumsQuery = useAlbumList({ const compilationAlbumsQuery = useAlbumList({
options: {
enabled: enabledItem.compilations,
},
query: { query: {
_custom: { _custom: {
jellyfin: { jellyfin: {
@ -140,7 +158,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
const topSongsQuery = useTopSongsList({ const topSongsQuery = useTopSongsList({
options: { options: {
enabled: !!detailQuery?.data?.name, enabled: !!detailQuery?.data?.name && enabledItem.topSongs,
}, },
query: { query: {
artist: detailQuery?.data?.name || '', artist: detailQuery?.data?.name || '',
@ -207,9 +225,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
return [ return [
{ {
data: recentAlbumsQuery?.data?.items, data: recentAlbumsQuery?.data?.items,
isHidden: !recentAlbumsQuery?.data?.items?.length, isHidden: !recentAlbumsQuery?.data?.items?.length || !enabledItem.recentAlbums,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
loading: recentAlbumsQuery?.isLoading || recentAlbumsQuery.isFetching, loading: recentAlbumsQuery?.isLoading || recentAlbumsQuery.isFetching,
order: itemOrder.recentAlbums,
title: ( title: (
<Group align="flex-end"> <Group align="flex-end">
<TextTitle <TextTitle
@ -235,9 +254,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
}, },
{ {
data: compilationAlbumsQuery?.data?.items, data: compilationAlbumsQuery?.data?.items,
isHidden: !compilationAlbumsQuery?.data?.items?.length, isHidden: !compilationAlbumsQuery?.data?.items?.length || !enabledItem.compilations,
itemType: LibraryItem.ALBUM, itemType: LibraryItem.ALBUM,
loading: compilationAlbumsQuery?.isLoading || compilationAlbumsQuery.isFetching, loading: compilationAlbumsQuery?.isLoading || compilationAlbumsQuery.isFetching,
order: itemOrder.compilations,
title: ( title: (
<TextTitle <TextTitle
order={2} order={2}
@ -250,8 +270,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
}, },
{ {
data: detailQuery?.data?.similarArtists || [], data: detailQuery?.data?.similarArtists || [],
isHidden: !detailQuery?.data?.similarArtists, isHidden: !detailQuery?.data?.similarArtists || !enabledItem.similarArtists,
itemType: LibraryItem.ALBUM_ARTIST, itemType: LibraryItem.ALBUM_ARTIST,
order: itemOrder.similarArtists,
title: ( title: (
<TextTitle <TextTitle
order={2} order={2}
@ -271,6 +292,12 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
compilationAlbumsQuery.isFetching, compilationAlbumsQuery.isFetching,
compilationAlbumsQuery?.isLoading, compilationAlbumsQuery?.isLoading,
detailQuery?.data?.similarArtists, detailQuery?.data?.similarArtists,
enabledItem.compilations,
enabledItem.recentAlbums,
enabledItem.similarArtists,
itemOrder.compilations,
itemOrder.recentAlbums,
itemOrder.similarArtists,
recentAlbumsQuery?.data?.items, recentAlbumsQuery?.data?.items,
recentAlbumsQuery.isFetching, recentAlbumsQuery.isFetching,
recentAlbumsQuery?.isLoading, recentAlbumsQuery?.isLoading,
@ -336,11 +363,11 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
const biography = useMemo(() => { const biography = useMemo(() => {
const bio = detailQuery?.data?.biography; const bio = detailQuery?.data?.biography;
if (!bio) return null; if (!bio || !enabledItem.biography) return null;
return sanitize(bio); return sanitize(bio);
}, [detailQuery?.data?.biography]); }, [detailQuery?.data?.biography, enabledItem.biography]);
const showTopSongs = topSongsQuery?.data?.items?.length; const showTopSongs = topSongsQuery?.data?.items?.length && enabledItem.topSongs;
const showGenres = detailQuery?.data?.genres ? detailQuery?.data?.genres.length !== 0 : false; const showGenres = detailQuery?.data?.genres ? detailQuery?.data?.genres.length !== 0 : false;
const mbzId = detailQuery?.data?.mbz; const mbzId = detailQuery?.data?.mbz;
@ -467,7 +494,12 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</Group> </Group>
</Box> </Box>
) : null} ) : null}
<Grid>
{biography ? ( {biography ? (
<Grid.Col
order={itemOrder.biography}
span={12}
>
<Box <Box
component="section" component="section"
maw="1280px" maw="1280px"
@ -482,8 +514,13 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
</TextTitle> </TextTitle>
<Spoiler dangerouslySetInnerHTML={{ __html: biography }} /> <Spoiler dangerouslySetInnerHTML={{ __html: biography }} />
</Box> </Box>
</Grid.Col>
) : null} ) : null}
{showTopSongs ? ( {showTopSongs ? (
<Grid.Col
order={itemOrder.topSongs}
span={12}
>
<Box component="section"> <Box component="section">
<Group <Group
noWrap noWrap
@ -538,19 +575,31 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
onRowDoubleClicked={handleRowDoubleClick} onRowDoubleClicked={handleRowDoubleClick}
/> />
</Box> </Box>
</Grid.Col>
) : null} ) : null}
<Box component="section">
<Stack spacing="xl">
{carousels {carousels
.filter((c) => !c.isHidden) .filter((c) => !c.isHidden)
.map((carousel) => ( .map((carousel) => (
<MemoizedSwiperGridCarousel <Grid.Col
key={`carousel-${carousel.uniqueId}`} key={`carousel-${carousel.uniqueId}`}
cardRows={cardRows[carousel.itemType as keyof typeof cardRows]} order={carousel.order}
span={12}
>
<Box component="section">
<Stack spacing="xl">
<MemoizedSwiperGridCarousel
cardRows={
cardRows[carousel.itemType as keyof typeof cardRows]
}
data={carousel.data} data={carousel.data}
isLoading={carousel.loading} isLoading={carousel.loading}
itemType={carousel.itemType} itemType={carousel.itemType}
route={cardRoutes[carousel.itemType as keyof typeof cardRoutes]} route={
cardRoutes[
carousel.itemType as keyof typeof cardRoutes
]
}
swiperProps={{ swiperProps={{
grid: { grid: {
rows: 2, rows: 2,
@ -561,9 +610,11 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten
}} }}
uniqueId={carousel.uniqueId} uniqueId={carousel.uniqueId}
/> />
))}
</Stack> </Stack>
</Box> </Box>
</Grid.Col>
))}
</Grid>
</DetailContainer> </DetailContainer>
</ContentContainer> </ContentContainer>
); );

View file

@ -0,0 +1,25 @@
import { DraggableItems } from '/@/renderer/features/settings/components/general/draggable-items';
import { ArtistItem, useGeneralSettings, useSettingsStoreActions } from '/@/renderer/store';
const ARTIST_ITEMS: Array<[ArtistItem, string]> = [
[ArtistItem.BIOGRAPHY, 'table.column.biography'],
[ArtistItem.TOP_SONGS, 'page.albumArtistDetail.topSongs'],
[ArtistItem.RECENT_ALBUMS, 'page.albumArtistDetail.recentReleases'],
[ArtistItem.COMPILATIONS, 'page.albumArtistDetail.appearsOn'],
[ArtistItem.SIMILAR_ARTISTS, 'page.albumArtistDetail.relatedArtists'],
];
export const ArtistSettings = () => {
const { artistItems } = useGeneralSettings();
const { setArtistItems } = useSettingsStoreActions();
return (
<DraggableItems
description="setting.artistConfiguration"
itemLabels={ARTIST_ITEMS}
setItems={setArtistItems}
settings={artistItems}
title="setting.artistConfiguration"
/>
);
};

View file

@ -9,6 +9,7 @@ import isElectron from 'is-electron';
import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings'; import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings';
import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder'; import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder';
import { ContextMenuSettings } from '/@/renderer/features/settings/components/general/context-menu-settings'; import { ContextMenuSettings } from '/@/renderer/features/settings/components/general/context-menu-settings';
import { ArtistSettings } from '/@/renderer/features/settings/components/general/artist-settings';
export const GeneralTab = () => { export const GeneralTab = () => {
return ( return (
@ -17,6 +18,7 @@ export const GeneralTab = () => {
<ThemeSettings /> <ThemeSettings />
<ControlSettings /> <ControlSettings />
<HomeSettings /> <HomeSettings />
<ArtistSettings />
<SidebarReorder /> <SidebarReorder />
<SidebarSettings /> <SidebarSettings />
<ContextMenuSettings /> <ContextMenuSettings />

View file

@ -105,7 +105,22 @@ export enum HomeItem {
RECENTLY_PLAYED = 'recentlyPlayed', RECENTLY_PLAYED = 'recentlyPlayed',
} }
export const homeItems = Object.values(HomeItem).map((item) => ({ const homeItems = Object.values(HomeItem).map((item) => ({
disabled: false,
id: item,
}));
/* eslint-disable typescript-sort-keys/string-enum */
export enum ArtistItem {
BIOGRAPHY = 'biography',
TOP_SONGS = 'topSongs',
RECENT_ALBUMS = 'recentAlbums',
COMPILATIONS = 'compilations',
SIMILAR_ARTISTS = 'similarArtists',
}
/* eslint-enable typescript-sort-keys/string-enum */
const artistItems = Object.values(ArtistItem).map((item) => ({
disabled: false, disabled: false,
id: item, id: item,
})); }));
@ -207,6 +222,7 @@ export interface SettingsState {
albumArtRes?: number | null; albumArtRes?: number | null;
albumBackground: boolean; albumBackground: boolean;
albumBackgroundBlur: number; albumBackgroundBlur: number;
artistItems: SortableItem<ArtistItem>[];
buttonSize: number; buttonSize: number;
defaultFullPlaylist: boolean; defaultFullPlaylist: boolean;
disabledContextMenu: { [k in ContextMenuItemType]?: boolean }; disabledContextMenu: { [k in ContextMenuItemType]?: boolean };
@ -305,6 +321,7 @@ export interface SettingsSlice extends SettingsState {
actions: { actions: {
reset: () => void; reset: () => void;
resetSampleRate: () => void; resetSampleRate: () => void;
setArtistItems: (item: SortableItem<ArtistItem>[]) => void;
setGenreBehavior: (target: GenreTarget) => void; setGenreBehavior: (target: GenreTarget) => void;
setHomeItems: (item: SortableItem<HomeItem>[]) => void; setHomeItems: (item: SortableItem<HomeItem>[]) => void;
setSettings: (data: Partial<SettingsState>) => void; setSettings: (data: Partial<SettingsState>) => void;
@ -347,6 +364,7 @@ const initialState: SettingsState = {
albumArtRes: undefined, albumArtRes: undefined,
albumBackground: false, albumBackground: false,
albumBackgroundBlur: 6, albumBackgroundBlur: 6,
artistItems,
buttonSize: 20, buttonSize: 20,
defaultFullPlaylist: true, defaultFullPlaylist: true,
disabledContextMenu: {}, disabledContextMenu: {},
@ -656,6 +674,11 @@ export const useSettingsStore = create<SettingsSlice>()(
state.playback.mpvProperties.audioSampleRateHz = 0; state.playback.mpvProperties.audioSampleRateHz = 0;
}); });
}, },
setArtistItems: (items) => {
set((state) => {
state.general.artistItems = items;
});
},
setGenreBehavior: (target: GenreTarget) => { setGenreBehavior: (target: GenreTarget) => {
set((state) => { set((state) => {
state.general.genreTarget = target; state.general.genreTarget = target;