From 4a4859826087cd2246b18d0bad5b688f935b6caa Mon Sep 17 00:00:00 2001 From: Kendall Garner <17521368+kgarner7@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:59:20 -0700 Subject: [PATCH] add multiple genre support for nd albums/tracks --- .../api/navidrome/navidrome-controller.ts | 6 ++- .../components/navidrome-album-filters.tsx | 51 ++++++++++++++----- .../components/navidrome-song-filters.tsx | 37 +++++++++++--- src/shared/api/navidrome/navidrome-types.ts | 3 +- 4 files changed, 77 insertions(+), 20 deletions(-) diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 0d95199f..10b6e675 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -271,6 +271,10 @@ export const NavidromeController: ControllerEndpoint = { getAlbumList: async (args) => { const { apiClientProps, query } = args; + const genres = hasFeature(apiClientProps.server, ServerFeature.BFR) + ? query.genres + : query.genres?.[0]; + const res = await ndApiClient(apiClientProps).getAlbumList({ query: { _end: query.startIndex + (query.limit || 0), @@ -279,7 +283,7 @@ export const NavidromeController: ControllerEndpoint = { _start: query.startIndex, artist_id: query.artistIds?.[0], compilation: query.compilation, - genre_id: query.genres?.[0], + genre_id: genres, name: query.searchTerm, ...query._custom?.navidrome, starred: query.favorite, diff --git a/src/renderer/features/albums/components/navidrome-album-filters.tsx b/src/renderer/features/albums/components/navidrome-album-filters.tsx index 672c6caf..4335e55e 100644 --- a/src/renderer/features/albums/components/navidrome-album-filters.tsx +++ b/src/renderer/features/albums/components/navidrome-album-filters.tsx @@ -2,12 +2,21 @@ import debounce from 'lodash/debounce'; import { ChangeEvent, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data'; +import { + MultiSelectWithInvalidData, + SelectWithInvalidData, +} from '/@/renderer/components/select-with-invalid-data'; import { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query'; import { useGenreList } from '/@/renderer/features/genres'; import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list'; -import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store'; +import { + AlbumListFilter, + getServerById, + useListStoreActions, + useListStoreByKey, +} from '/@/renderer/store'; import { NDSongQueryFields } from '/@/shared/api/navidrome.types'; +import { hasFeature } from '/@/shared/api/utils'; import { Divider } from '/@/shared/components/divider/divider'; import { Group } from '/@/shared/components/group/group'; import { NumberInput } from '/@/shared/components/number-input/number-input'; @@ -23,6 +32,7 @@ import { LibraryItem, SortOrder, } from '/@/shared/types/domain-types'; +import { ServerFeature } from '/@/shared/types/features-types'; interface NavidromeAlbumFiltersProps { customFilters?: Partial; @@ -42,6 +52,7 @@ export const NavidromeAlbumFilters = ({ const { t } = useTranslation(); const { filter } = useListStoreByKey({ key: pageKey }); const { setFilter } = useListStoreActions(); + const server = getServerById(serverId); const genreListQuery = useGenreList({ options: { @@ -64,12 +75,14 @@ export const NavidromeAlbumFilters = ({ })); }, [genreListQuery.data]); - const handleGenresFilter = debounce((e: null | string) => { + const hasBrf = hasFeature(server, ServerFeature.BFR); + + const handleGenresFilter = debounce((e: null | string[]) => { const updatedFilters = setFilter({ customFilters, data: { _custom: filter._custom, - genres: e ? [e] : undefined, + genres: e ? e : undefined, }, itemType: LibraryItem.ALBUM, key: pageKey, @@ -269,15 +282,29 @@ export const NavidromeAlbumFilters = ({ min={0} onChange={(e) => handleYearFilter(e)} /> - + {!hasBrf && ( + handleGenresFilter(value !== null ? [value] : null)} + searchable + /> + )} + {hasBrf && ( + + + + )} ; @@ -31,6 +41,7 @@ export const NavidromeSongFilters = ({ const { t } = useTranslation(); const { setFilter } = useListStoreActions(); const filter = useListFilterByKey({ key: pageKey }); + const server = getServerById(serverId); const isGenrePage = customFilters?.genreIds !== undefined; @@ -58,12 +69,14 @@ export const NavidromeSongFilters = ({ })); }, [genreListQuery.data]); - const handleGenresFilter = debounce((e: null | string) => { + const hasBrf = hasFeature(server, ServerFeature.BFR); + + const handleGenresFilter = debounce((e: null | string[]) => { const updatedFilters = setFilter({ customFilters, data: { _custom: filter._custom, - genreIds: e ? [e] : undefined, + genreIds: e ? e : undefined, }, itemType: LibraryItem.SONG, key: pageKey, @@ -148,18 +161,30 @@ export const NavidromeSongFilters = ({ value={filter._custom?.navidrome?.year} width={50} /> - {!isGenrePage && ( + {!isGenrePage && !hasBrf && ( handleGenresFilter(value !== null ? [value] : null)} searchable width={150} /> )} + {!isGenrePage && hasBrf && ( + + + + )} {tagsQuery.data?.enumTags?.length && tagsQuery.data.enumTags.length > 0 && tagsQuery.data.enumTags.map((tag) => ( diff --git a/src/shared/api/navidrome/navidrome-types.ts b/src/shared/api/navidrome/navidrome-types.ts index 9a1e02b4..495dc8de 100644 --- a/src/shared/api/navidrome/navidrome-types.ts +++ b/src/shared/api/navidrome/navidrome-types.ts @@ -169,7 +169,8 @@ const albumListParameters = paginationParameters.extend({ album_id: z.string().optional(), artist_id: z.string().optional(), compilation: z.boolean().optional(), - genre_id: z.string().optional(), + // in older versions, this was a single string. post BFR, you can repeat it multiple times + genre_id: z.union([z.string(), z.string().array()]).optional(), has_rating: z.boolean().optional(), id: z.string().optional(), name: z.string().optional(),