From ac5611fdca7a6f25492234bf9ba4ea707de3f6f5 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Mon, 2 Feb 2026 22:23:38 -0800 Subject: [PATCH] add favorite songs section to artist page (#1604) --- src/i18n/locales/en.json | 2 + src/renderer/api/query-keys.ts | 7 + .../features/artists/api/artists-api.ts | 20 ++ .../album-artist-detail-content.tsx | 212 ++++++++++++++++++ ...tist-detail-favorite-songs-list-header.tsx | 38 ++++ ...um-artist-detail-top-songs-list-header.tsx | 6 +- ...rtist-detail-favorite-songs-list-route.tsx | 150 +++++++++++++ ...bum-artist-detail-top-songs-list-route.tsx | 67 +++--- .../components/general/artist-settings.tsx | 1 + .../shared/components/library-header-bar.tsx | 5 +- .../mutations/create-favorite-mutation.ts | 12 + .../mutations/delete-favorite-mutation.ts | 12 + .../shared/mutations/set-rating-mutation.ts | 16 +- src/renderer/router/app-router.tsx | 20 ++ src/renderer/router/routes.ts | 2 + src/renderer/store/settings.store.ts | 18 +- 16 files changed, 545 insertions(+), 43 deletions(-) create mode 100644 src/renderer/features/artists/components/album-artist-detail-favorite-songs-list-header.tsx create mode 100644 src/renderer/features/artists/routes/album-artist-detail-favorite-songs-list-route.tsx diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json index dec82402e..f1ed209f3 100644 --- a/src/i18n/locales/en.json +++ b/src/i18n/locales/en.json @@ -418,6 +418,7 @@ "albumArtistDetail": { "about": "About {{artist}}", "appearsOn": "appears on", + "favoriteSongs": "favorite songs", "groupingTypeAll": "all release types", "groupingTypePrimary": "primary release types", "recentReleases": "recent releases", @@ -425,6 +426,7 @@ "relatedArtists": "related $t(entity.artist, {\"count\": 2})", "topSongs": "top songs", "topSongsFrom": "top songs from {{title}}", + "favoriteSongsFrom": "favorite songs from {{title}}", "viewAll": "view all", "viewAllTracks": "view all $t(entity.track, {\"count\": 2})" }, diff --git a/src/renderer/api/query-keys.ts b/src/renderer/api/query-keys.ts index c3767992d..b22b59304 100644 --- a/src/renderer/api/query-keys.ts +++ b/src/renderer/api/query-keys.ts @@ -73,6 +73,13 @@ export const queryKeys: Record< return [serverId, 'albumArtists', 'detail'] as const; }, + favoriteSongs: (serverId: string, artistId?: string) => { + if (artistId) { + return [serverId, 'albumArtists', 'favoriteSongs', artistId] as const; + } + + return [serverId, 'albumArtists', 'favoriteSongs'] as const; + }, infiniteList: (serverId: string, query?: AlbumArtistListQuery) => { const { filter, pagination } = splitPaginatedQuery(query); if (query && pagination) { diff --git a/src/renderer/features/artists/api/artists-api.ts b/src/renderer/features/artists/api/artists-api.ts index 539217bae..a8f4de62b 100644 --- a/src/renderer/features/artists/api/artists-api.ts +++ b/src/renderer/features/artists/api/artists-api.ts @@ -10,6 +10,8 @@ import { AlbumArtistListQuery, ArtistListQuery, ListCountQuery, + SongListSort, + SortOrder, TopSongListQuery, } from '/@/shared/types/domain-types'; @@ -120,6 +122,24 @@ export const artistsQueries = { ...args.options, }); }, + favoriteSongs: (args: QueryHookArgs<{ artistId: string }>) => { + return queryOptions({ + queryFn: ({ signal }) => { + return api.controller.getSongList({ + apiClientProps: { serverId: args.serverId, signal }, + query: { + artistIds: [args.query.artistId], + favorite: true, + limit: -1, + sortBy: SongListSort.RELEASE_DATE, + sortOrder: SortOrder.ASC, + startIndex: 0, + }, + }); + }, + queryKey: queryKeys.albumArtists.favoriteSongs(args.serverId, args.query.artistId), + }); + }, topSongs: (args: QueryHookArgs) => { return queryOptions({ queryFn: ({ signal }) => { 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 dd62508b4..2212d7e13 100644 --- a/src/renderer/features/artists/components/album-artist-detail-content.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-content.tsx @@ -450,6 +450,213 @@ const AlbumArtistMetadataTopSongs = ({ ); }; +interface AlbumArtistMetadataFavoriteSongsProps { + routeId: string; +} + +const AlbumArtistMetadataFavoriteSongs = ({ routeId }: AlbumArtistMetadataFavoriteSongsProps) => { + const { t } = useTranslation(); + const [searchTerm, setSearchTerm] = useState(''); + const [debouncedSearchTerm] = useDebouncedValue(searchTerm, 300); + const [showAll, setShowAll] = useState(false); + const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table); + const currentSong = usePlayerSong(); + const player = usePlayer(); + const serverId = useCurrentServerId(); + + const favoriteSongsQuery = useQuery({ + ...artistsQueries.favoriteSongs({ + query: { + artistId: routeId, + }, + serverId: serverId, + }), + }); + + const songs = useMemo( + () => favoriteSongsQuery.data?.items || [], + [favoriteSongsQuery.data?.items], + ); + + const columns = useMemo(() => { + return tableConfig?.columns || []; + }, [tableConfig?.columns]); + + const filteredSongs = useMemo(() => { + const filtered = searchLibraryItems(songs, debouncedSearchTerm, LibraryItem.SONG); + // When searching, show all results. Otherwise, limit to 5 if not showing all + if (debouncedSearchTerm?.trim() || showAll) { + return filtered; + } + return filtered.slice(0, 5); + }, [songs, debouncedSearchTerm, showAll]); + + const { handleColumnReordered } = useItemListColumnReorder({ + itemListKey: ItemListKey.SONG, + }); + + const { handleColumnResized } = useItemListColumnResize({ + itemListKey: ItemListKey.SONG, + }); + + const overrideControls: Partial = useMemo(() => { + return { + onDoubleClick: ({ index, internalState, item, meta }) => { + if (!item) { + return; + } + + const playType = (meta?.playType as Play) || Play.NOW; + const items = internalState?.getData() as Song[]; + + if (index !== undefined) { + player.addToQueueByData(items, playType, item.id); + } + }, + }; + }, [player]); + + if (favoriteSongsQuery.isLoading || !favoriteSongsQuery.data) { + return null; + } + + if (!favoriteSongsQuery?.data?.items?.length) return null; + + if (!tableConfig || columns.length === 0) { + return ( +
+
+ + + {t('page.albumArtistDetail.favoriteSongs', { + postProcess: 'sentenceCase', + })} + + {favoriteSongsQuery.data?.items?.length} + +
+
+ +
+
+
+ ); + } + + const currentSongId = currentSong?.id; + + return ( +
+ +
+ + + {t('page.albumArtistDetail.favoriteSongs', { + postProcess: 'sentenceCase', + })} + + {favoriteSongsQuery.data?.items?.length} + +
+
+ +
+
+ + } + onChange={(e) => setSearchTerm(e.target.value)} + placeholder={t('common.search', { postProcess: 'sentenceCase' })} + radius="xl" + rightSection={ + searchTerm ? ( + setSearchTerm('')} + size="sm" + variant="transparent" + /> + ) : null + } + styles={{ + input: { + background: 'transparent', + border: '1px solid rgba(255, 255, 255, 0.05)', + }, + }} + value={searchTerm} + /> + + + + {!searchTerm.trim() && songs.length > 5 && !showAll && ( + + + + )} + +
+ ); +}; + interface AlbumArtistMetadataExternalLinksProps { artistName?: string; externalLinks: boolean; @@ -728,6 +935,11 @@ export const AlbumArtistDetailContent = ({ /> )} + {enabledItem.favoriteSongs && ( + + + + )} diff --git a/src/renderer/features/artists/components/album-artist-detail-favorite-songs-list-header.tsx b/src/renderer/features/artists/components/album-artist-detail-favorite-songs-list-header.tsx new file mode 100644 index 000000000..a3fecb349 --- /dev/null +++ b/src/renderer/features/artists/components/album-artist-detail-favorite-songs-list-header.tsx @@ -0,0 +1,38 @@ +import { useTranslation } from 'react-i18next'; + +import { PageHeader } from '/@/renderer/components/page-header/page-header'; +import { LibraryHeaderBar } from '/@/renderer/features/shared/components/library-header-bar'; +import { Badge } from '/@/shared/components/badge/badge'; +import { SpinnerIcon } from '/@/shared/components/spinner/spinner'; +import { LibraryItem, Song } from '/@/shared/types/domain-types'; + +interface AlbumArtistDetailFavoriteSongsListHeaderProps { + data: Song[]; + itemCount?: number; + title: string; +} + +export const AlbumArtistDetailFavoriteSongsListHeader = ({ + data, + itemCount, + title, +}: AlbumArtistDetailFavoriteSongsListHeaderProps) => { + const { t } = useTranslation(); + + return ( + + + + + {t('page.albumArtistDetail.favoriteSongsFrom', { + postProcess: 'titleCase', + title, + })} + + + {itemCount === null || itemCount === undefined ? : itemCount} + + + + ); +}; diff --git a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx index 42f302722..edc73ef4e 100644 --- a/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx +++ b/src/renderer/features/artists/components/album-artist-detail-top-songs-list-header.tsx @@ -20,10 +20,10 @@ export const AlbumArtistDetailTopSongsListHeader = ({ const { t } = useTranslation(); return ( - - + + - + {t('page.albumArtistDetail.topSongsFrom', { postProcess: 'titleCase', title, diff --git a/src/renderer/features/artists/routes/album-artist-detail-favorite-songs-list-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-favorite-songs-list-route.tsx new file mode 100644 index 000000000..d2436c126 --- /dev/null +++ b/src/renderer/features/artists/routes/album-artist-detail-favorite-songs-list-route.tsx @@ -0,0 +1,150 @@ +import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import { useParams } from 'react-router'; + +import { useItemListColumnReorder } from '/@/renderer/components/item-list/helpers/use-item-list-column-reorder'; +import { useItemListColumnResize } from '/@/renderer/components/item-list/helpers/use-item-list-column-resize'; +import { ItemTableList } from '/@/renderer/components/item-list/item-table-list/item-table-list'; +import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column'; +import { ItemControls } from '/@/renderer/components/item-list/types'; +import { ListContext } from '/@/renderer/context/list-context'; +import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; +import { AlbumArtistDetailFavoriteSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-favorite-songs-list-header'; +import { usePlayer } from '/@/renderer/features/player/context/player-context'; +import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; +import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; +import { usePlayerSong } from '/@/renderer/store'; +import { useCurrentServer } from '/@/renderer/store/auth.store'; +import { useSettingsStore } from '/@/renderer/store/settings.store'; +import { LibraryItem, Song } from '/@/shared/types/domain-types'; +import { ItemListKey, Play } from '/@/shared/types/types'; + +const AlbumArtistDetailFavoriteSongsListRoute = () => { + const { albumArtistId, artistId } = useParams() as { + albumArtistId?: string; + artistId?: string; + }; + const routeId = (artistId || albumArtistId) as string; + const server = useCurrentServer(); + const pageKey = LibraryItem.SONG; + + const detailQuery = useQuery( + artistsQueries.albumArtistDetail({ + query: { id: routeId }, + serverId: server?.id, + }), + ); + + const favoriteSongsQuery = useQuery( + artistsQueries.favoriteSongs({ + options: { enabled: !!detailQuery?.data?.name }, + query: { artistId: routeId }, + serverId: server?.id, + }), + ); + + const itemCount = favoriteSongsQuery?.data?.items?.length || 0; + const songs = useMemo( + () => favoriteSongsQuery?.data?.items || [], + [favoriteSongsQuery?.data?.items], + ); + + const tableConfig = useSettingsStore((state) => state.lists[ItemListKey.SONG]?.table); + const currentSong = usePlayerSong(); + const player = usePlayer(); + + const columns = useMemo(() => { + return tableConfig?.columns || []; + }, [tableConfig?.columns]); + + const { handleColumnReordered } = useItemListColumnReorder({ + itemListKey: ItemListKey.SONG, + }); + + const { handleColumnResized } = useItemListColumnResize({ + itemListKey: ItemListKey.SONG, + }); + + const overrideControls: Partial = useMemo(() => { + return { + onDoubleClick: ({ index, internalState, item, meta }) => { + if (!item) { + return; + } + + const playType = (meta?.playType as Play) || Play.NOW; + const items = internalState?.getData() as Song[]; + + if (index !== undefined) { + player.addToQueueByData(items, playType, item.id); + } + }, + }; + }, [player]); + + const providerValue = useMemo(() => { + return { + id: routeId, + pageKey, + }; + }, [routeId, pageKey]); + + const currentSongId = currentSong?.id; + + if (!tableConfig || columns.length === 0) { + return ( + + + + + + ); + } + + return ( + + + + + + + ); +}; + +const AlbumArtistDetailTopSongsListRouteWithBoundary = () => { + return ( + + + + ); +}; + +export default AlbumArtistDetailTopSongsListRouteWithBoundary; diff --git a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx index a9fd3dd79..27b3f9a66 100644 --- a/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx +++ b/src/renderer/features/artists/routes/album-artist-detail-top-songs-list-route.tsx @@ -12,7 +12,6 @@ import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { AlbumArtistDetailTopSongsListHeader } from '/@/renderer/features/artists/components/album-artist-detail-top-songs-list-header'; import { usePlayer } from '/@/renderer/features/player/context/player-context'; import { AnimatedPage } from '/@/renderer/features/shared/components/animated-page'; -import { LibraryContainer } from '/@/renderer/features/shared/components/library-container'; import { PageErrorBoundary } from '/@/renderer/features/shared/components/page-error-boundary'; import { usePlayerSong } from '/@/renderer/store'; import { useCurrentServer } from '/@/renderer/store/auth.store'; @@ -93,13 +92,11 @@ const AlbumArtistDetailTopSongsListRoute = () => { return ( - - - + ); @@ -108,34 +105,32 @@ const AlbumArtistDetailTopSongsListRoute = () => { return ( - - - - + + ); diff --git a/src/renderer/features/settings/components/general/artist-settings.tsx b/src/renderer/features/settings/components/general/artist-settings.tsx index 1d77775a2..c7f0a09a0 100644 --- a/src/renderer/features/settings/components/general/artist-settings.tsx +++ b/src/renderer/features/settings/components/general/artist-settings.tsx @@ -11,6 +11,7 @@ import { const ARTIST_ITEMS: Array<[ArtistItem, string]> = [ [ArtistItem.BIOGRAPHY, 'table.column.biography'], + [ArtistItem.FAVORITE_SONGS, 'page.albumArtistDetail.favoriteSongs'], [ArtistItem.TOP_SONGS, 'page.albumArtistDetail.topSongs'], [ArtistItem.RECENT_ALBUMS, 'page.albumArtistDetail.recentReleases'], [ArtistItem.SIMILAR_ARTISTS, 'page.albumArtistDetail.relatedArtists'], diff --git a/src/renderer/features/shared/components/library-header-bar.tsx b/src/renderer/features/shared/components/library-header-bar.tsx index a73544562..2c270e2e3 100644 --- a/src/renderer/features/shared/components/library-header-bar.tsx +++ b/src/renderer/features/shared/components/library-header-bar.tsx @@ -41,6 +41,7 @@ interface HeaderPlayButtonProps { interface TitleProps { children: ReactNode; + order?: number; } const HeaderPlayButton = ({ @@ -100,9 +101,9 @@ const HeaderPlayButton = ({ ); }; -const Title = ({ children }: TitleProps) => { +const Title = ({ children, order = 1 }: TitleProps) => { return ( - + {children} ); diff --git a/src/renderer/features/shared/mutations/create-favorite-mutation.ts b/src/renderer/features/shared/mutations/create-favorite-mutation.ts index cdea051e7..d414f6da0 100644 --- a/src/renderer/features/shared/mutations/create-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/create-favorite-mutation.ts @@ -4,6 +4,7 @@ import isElectron from 'is-electron'; import { useTranslation } from 'react-i18next'; import { api } from '/@/renderer/api'; +import { queryKeys } from '/@/renderer/api/query-keys'; import { eventEmitter } from '/@/renderer/events/event-emitter'; import { applyFavoriteOptimisticUpdates, @@ -63,6 +64,17 @@ export const useCreateFavorite = (args: MutationHookArgs) => { if (variables.query.type === LibraryItem.SONG) { remote?.updateFavorite(true, variables.apiClientProps.serverId, variables.query.id); } + if ( + variables.query.type === LibraryItem.SONG || + variables.query.type === LibraryItem.PLAYLIST_SONG || + variables.query.type === LibraryItem.QUEUE_SONG + ) { + queryClient.invalidateQueries({ + queryKey: queryKeys.albumArtists.favoriteSongs( + variables.apiClientProps.serverId, + ), + }); + } }, ...options, }); diff --git a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts index 90534c272..909f23ee8 100644 --- a/src/renderer/features/shared/mutations/delete-favorite-mutation.ts +++ b/src/renderer/features/shared/mutations/delete-favorite-mutation.ts @@ -4,6 +4,7 @@ import isElectron from 'is-electron'; import { useTranslation } from 'react-i18next'; import { api } from '/@/renderer/api'; +import { queryKeys } from '/@/renderer/api/query-keys'; import { eventEmitter } from '/@/renderer/events/event-emitter'; import { applyFavoriteOptimisticUpdates, @@ -67,6 +68,17 @@ export const useDeleteFavorite = (args: MutationHookArgs) => { variables.query.id, ); } + if ( + variables.query.type === LibraryItem.SONG || + variables.query.type === LibraryItem.PLAYLIST_SONG || + variables.query.type === LibraryItem.QUEUE_SONG + ) { + queryClient.invalidateQueries({ + queryKey: queryKeys.albumArtists.favoriteSongs( + variables.apiClientProps.serverId, + ), + }); + } }, ...options, }); diff --git a/src/renderer/features/shared/mutations/set-rating-mutation.ts b/src/renderer/features/shared/mutations/set-rating-mutation.ts index 5a9856f5a..6f50e07b6 100644 --- a/src/renderer/features/shared/mutations/set-rating-mutation.ts +++ b/src/renderer/features/shared/mutations/set-rating-mutation.ts @@ -3,6 +3,7 @@ import { AxiosError } from 'axios'; import { useTranslation } from 'react-i18next'; import { api } from '/@/renderer/api'; +import { queryKeys } from '/@/renderer/api/query-keys'; import { eventEmitter } from '/@/renderer/events/event-emitter'; import { PreviousQueryData } from '/@/renderer/features/shared/mutations/favorite-optimistic-updates'; import { @@ -11,7 +12,7 @@ import { } from '/@/renderer/features/shared/mutations/rating-optimistic-updates'; import { MutationHookArgs } from '/@/renderer/lib/react-query'; import { toast } from '/@/shared/components/toast/toast'; -import { RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types'; +import { LibraryItem, RatingResponse, SetRatingArgs } from '/@/shared/types/domain-types'; const setRatingMutationKey = ['set-rating']; @@ -56,6 +57,19 @@ export const useSetRatingMutation = (args: MutationHookArgs) => { return applyRatingOptimisticUpdates(queryClient, variables, variables.query.rating); }, + onSuccess: (_data, variables) => { + if ( + variables.query.type === LibraryItem.SONG || + variables.query.type === LibraryItem.PLAYLIST_SONG || + variables.query.type === LibraryItem.QUEUE_SONG + ) { + queryClient.invalidateQueries({ + queryKey: queryKeys.albumArtists.favoriteSongs( + variables.apiClientProps.serverId, + ), + }); + } + }, ...options, }); }; diff --git a/src/renderer/router/app-router.tsx b/src/renderer/router/app-router.tsx index 1a92cf576..79b0c2e19 100644 --- a/src/renderer/router/app-router.tsx +++ b/src/renderer/router/app-router.tsx @@ -55,6 +55,10 @@ const AlbumArtistDetailTopSongsListRoute = lazy( () => import('../features/artists/routes/album-artist-detail-top-songs-list-route'), ); +const AlbumArtistDetailFavoriteSongsListRoute = lazy( + () => import('../features/artists/routes/album-artist-detail-favorite-songs-list-route'), +); + const AlbumDetailRoute = lazy( () => import('/@/renderer/features/albums/routes/album-detail-route'), ); @@ -251,6 +255,14 @@ export const AppRouter = () => { element={} path={AppRoute.LIBRARY_ARTISTS_DETAIL_TOP_SONGS} /> + + } + path={ + AppRoute.LIBRARY_ARTISTS_DETAIL_FAVORITE_SONGS + } + /> } @@ -295,6 +307,14 @@ export const AppRouter = () => { AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS } /> + + } + path={ + AppRoute.LIBRARY_ALBUM_ARTISTS_DETAIL_FAVORITE_SONGS + } + /> } path="*" /> diff --git a/src/renderer/router/routes.ts b/src/renderer/router/routes.ts index d7b08f2d1..4a1e68c04 100644 --- a/src/renderer/router/routes.ts +++ b/src/renderer/router/routes.ts @@ -7,6 +7,7 @@ export enum AppRoute { LIBRARY_ALBUM_ARTISTS = '/library/album-artists', LIBRARY_ALBUM_ARTISTS_DETAIL = '/library/album-artists/:albumArtistId', LIBRARY_ALBUM_ARTISTS_DETAIL_DISCOGRAPHY = '/library/album-artists/:albumArtistId/discography', + LIBRARY_ALBUM_ARTISTS_DETAIL_FAVORITE_SONGS = '/library/album-artists/:albumArtistId/favorite-songs', LIBRARY_ALBUM_ARTISTS_DETAIL_SONGS = '/library/album-artists/:albumArtistId/songs', LIBRARY_ALBUM_ARTISTS_DETAIL_TOP_SONGS = '/library/album-artists/:albumArtistId/top-songs', LIBRARY_ALBUMS = '/library/albums', @@ -14,6 +15,7 @@ export enum AppRoute { LIBRARY_ARTISTS = '/library/artists', LIBRARY_ARTISTS_DETAIL = '/library/artists/:artistId', LIBRARY_ARTISTS_DETAIL_DISCOGRAPHY = '/library/artists/:artistId/discography', + LIBRARY_ARTISTS_DETAIL_FAVORITE_SONGS = '/library/artists/:artistId/favorite-songs', LIBRARY_ARTISTS_DETAIL_SONGS = '/library/artists/:artistId/songs', LIBRARY_ARTISTS_DETAIL_TOP_SONGS = '/library/artists/:artistId/top-songs', LIBRARY_FOLDERS = '/library/folders', diff --git a/src/renderer/store/settings.store.ts b/src/renderer/store/settings.store.ts index 157a86e66..682aa403e 100644 --- a/src/renderer/store/settings.store.ts +++ b/src/renderer/store/settings.store.ts @@ -77,6 +77,7 @@ const HomeItemSchema = z.enum([ const ArtistItemSchema = z.enum([ 'biography', 'compilations', + 'favoriteSongs', 'recentAlbums', 'similarArtists', 'topSongs', @@ -655,6 +656,7 @@ export const SettingsStateSchema = ValidationSettingsStateSchema.merge( export enum ArtistItem { BIOGRAPHY = 'biography', + FAVORITE_SONGS = 'favoriteSongs', RECENT_ALBUMS = 'recentAlbums', SIMILAR_ARTISTS = 'similarArtists', TOP_SONGS = 'topSongs', @@ -2071,10 +2073,24 @@ export const useSettingsStore = createWithEqualityFn()( }); } + if (version <= 23) { + // Add FAVORITE_SONGS to album artist page configuration + const hasFavoriteSongs = state.general.artistItems?.some( + (item) => item.id === ArtistItem.FAVORITE_SONGS, + ); + + if (!hasFavoriteSongs) { + state.general.artistItems.push({ + disabled: false, + id: ArtistItem.FAVORITE_SONGS, + }); + } + } + return persistedState; }, name: 'store_settings', - version: 23, + version: 24, }, ), );