diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index 5fa7ba3e..19d3cda8 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -457,6 +457,8 @@ "albumBackgroundBlur_description": "adjusts the amount of blur applied to the album background image", "applicationHotkeys": "application hotkeys", "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_description": "select the audio device to use for playback (web player only)", "audioExclusiveMode": "audio exclusive mode", diff --git a/src/renderer/features/artists/components/album-artist-detail-content.tsx b/src/renderer/features/artists/components/album-artist-detail-content.tsx index 7224ce13..025d877d 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; 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 { FaLastfmSquare } from 'react-icons/fa'; 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 { useContainerQuery } from '/@/renderer/hooks'; 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 { CardRow, Play, TableColumn } from '/@/renderer/types'; import { sanitize } from '/@/renderer/utils/sanitize'; @@ -65,13 +65,25 @@ interface AlbumArtistDetailContentProps { export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailContentProps) => { const { t } = useTranslation(); - const { externalLinks } = useGeneralSettings(); + const { artistItems, externalLinks } = useGeneralSettings(); const { albumArtistId } = useParams() as { albumArtistId: string }; const cq = useContainerQuery(); const handlePlayQueueAdd = usePlayQueueAdd(); const server = useCurrentServer(); 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({ query: { id: albumArtistId }, serverId: server?.id, @@ -95,6 +107,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten })}`; const recentAlbumsQuery = useAlbumList({ + options: { + enabled: enabledItem.recentAlbums, + }, query: { _custom: { jellyfin: { @@ -117,6 +132,9 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten }); const compilationAlbumsQuery = useAlbumList({ + options: { + enabled: enabledItem.compilations, + }, query: { _custom: { jellyfin: { @@ -140,7 +158,7 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten const topSongsQuery = useTopSongsList({ options: { - enabled: !!detailQuery?.data?.name, + enabled: !!detailQuery?.data?.name && enabledItem.topSongs, }, query: { artist: detailQuery?.data?.name || '', @@ -207,9 +225,10 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten return [ { data: recentAlbumsQuery?.data?.items, - isHidden: !recentAlbumsQuery?.data?.items?.length, + isHidden: !recentAlbumsQuery?.data?.items?.length || !enabledItem.recentAlbums, itemType: LibraryItem.ALBUM, loading: recentAlbumsQuery?.isLoading || recentAlbumsQuery.isFetching, + order: itemOrder.recentAlbums, title: ( { const bio = detailQuery?.data?.biography; - if (!bio) return null; + if (!bio || !enabledItem.biography) return null; 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 mbzId = detailQuery?.data?.mbz; @@ -467,103 +494,127 @@ export const AlbumArtistDetailContent = ({ background }: AlbumArtistDetailConten ) : null} - {biography ? ( - - + {biography ? ( + - {t('page.albumArtistDetail.about', { - artist: detailQuery?.data?.name, - })} - - - - ) : null} - {showTopSongs ? ( - - - - {t('page.albumArtistDetail.topSongs', { - postProcess: 'sentenceCase', + {t('page.albumArtistDetail.about', { + artist: detailQuery?.data?.name, })} - - - - data.data.uniqueId} - rowData={topSongs} - rowHeight={60} - rowSelection="multiple" - onCellContextMenu={handleContextMenu} - onRowDoubleClicked={handleRowDoubleClick} - /> - - ) : null} - - - {carousels - .filter((c) => !c.isHidden) - .map((carousel) => ( - + + {t('page.albumArtistDetail.topSongs', { + postProcess: 'sentenceCase', + })} + + + + + data.data.uniqueId} + rowData={topSongs} + rowHeight={60} + rowSelection="multiple" + onCellContextMenu={handleContextMenu} + onRowDoubleClicked={handleRowDoubleClick} /> - ))} - - + + + ) : null} + + {carousels + .filter((c) => !c.isHidden) + .map((carousel) => ( + + + + + + + + ))} + ); diff --git a/src/renderer/features/settings/components/general/artist-settings.tsx b/src/renderer/features/settings/components/general/artist-settings.tsx new file mode 100644 index 00000000..9a22ca7d --- /dev/null +++ b/src/renderer/features/settings/components/general/artist-settings.tsx @@ -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 ( + + ); +}; diff --git a/src/renderer/features/settings/components/general/general-tab.tsx b/src/renderer/features/settings/components/general/general-tab.tsx index adceee4b..f8f4c983 100644 --- a/src/renderer/features/settings/components/general/general-tab.tsx +++ b/src/renderer/features/settings/components/general/general-tab.tsx @@ -9,6 +9,7 @@ import isElectron from 'is-electron'; import { HomeSettings } from '/@/renderer/features/settings/components/general/home-settings'; import { SidebarReorder } from '/@/renderer/features/settings/components/general/sidebar-reorder'; import { ContextMenuSettings } from '/@/renderer/features/settings/components/general/context-menu-settings'; +import { ArtistSettings } from '/@/renderer/features/settings/components/general/artist-settings'; export const GeneralTab = () => { return ( @@ -17,6 +18,7 @@ export const GeneralTab = () => { + diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 85330efd..99a3ac21 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -105,7 +105,22 @@ export enum HomeItem { 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, id: item, })); @@ -207,6 +222,7 @@ export interface SettingsState { albumArtRes?: number | null; albumBackground: boolean; albumBackgroundBlur: number; + artistItems: SortableItem[]; buttonSize: number; defaultFullPlaylist: boolean; disabledContextMenu: { [k in ContextMenuItemType]?: boolean }; @@ -305,6 +321,7 @@ export interface SettingsSlice extends SettingsState { actions: { reset: () => void; resetSampleRate: () => void; + setArtistItems: (item: SortableItem[]) => void; setGenreBehavior: (target: GenreTarget) => void; setHomeItems: (item: SortableItem[]) => void; setSettings: (data: Partial) => void; @@ -347,6 +364,7 @@ const initialState: SettingsState = { albumArtRes: undefined, albumBackground: false, albumBackgroundBlur: 6, + artistItems, buttonSize: 20, defaultFullPlaylist: true, disabledContextMenu: {}, @@ -656,6 +674,11 @@ export const useSettingsStore = create()( state.playback.mpvProperties.audioSampleRateHz = 0; }); }, + setArtistItems: (items) => { + set((state) => { + state.general.artistItems = items; + }); + }, setGenreBehavior: (target: GenreTarget) => { set((state) => { state.general.genreTarget = target;