diff --git a/src/renderer/api/jellyfin/jellyfin-api.ts b/src/renderer/api/jellyfin/jellyfin-api.ts index 6de03ccd6..449a2d61e 100644 --- a/src/renderer/api/jellyfin/jellyfin-api.ts +++ b/src/renderer/api/jellyfin/jellyfin-api.ts @@ -248,6 +248,15 @@ export const contract = c.router({ 404: jfType._response.error, }, }, + getStudioList: { + method: 'GET', + path: 'studios', + query: jfType._parameters.studioList, + responses: { + 200: jfType._response.studioList, + 400: jfType._response.error, + }, + }, getTopSongsList: { method: 'GET', path: 'users/:userId/items', diff --git a/src/renderer/api/jellyfin/jellyfin-controller.ts b/src/renderer/api/jellyfin/jellyfin-controller.ts index cc43cbe20..f4066bea9 100644 --- a/src/renderer/api/jellyfin/jellyfin-controller.ts +++ b/src/renderer/api/jellyfin/jellyfin-controller.ts @@ -25,6 +25,7 @@ import { songListSortMap, SortOrder, sortOrderMap, + Tag, } from '/@/shared/types/domain-types'; import { ServerFeature } from '/@/shared/types/features-types'; @@ -1233,12 +1234,38 @@ export const JellyfinController: InternalControllerEndpoint = { throw new Error('failed to get tags'); } - return { - boolTags: res.body.Tags?.sort((a, b) => - a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()), - ), - excluded: { album: [], song: [] }, - }; + const studioRes = await jfApiClient(apiClientProps).getStudioList({ + query: { + EnableTotalRecordCount: true, + IncludeItemTypes: query.type === LibraryItem.SONG ? 'Audio' : 'MusicAlbum', + ParentId: query.folder, + }, + }); + + if (studioRes.status !== 200) { + throw new Error('failed to get studios'); + } + + const tags: Tag[] = []; + if (res.body.Tags?.length) { + tags.push({ + name: 'Tags', + options: res.body.Tags.sort((a, b) => + a.toLocaleLowerCase().localeCompare(b.toLocaleLowerCase()), + ).map((tag) => ({ id: tag, name: tag })), + }); + } + + if (studioRes.body.Items.length) { + tags.push({ + name: 'Studios', + options: studioRes.body.Items.sort((a, b) => + a.Name.toLocaleLowerCase().localeCompare(b.Name.toLocaleLowerCase()), + ).map((option) => ({ id: option.Name, name: option.Name })), + }); + } + + return { excluded: { album: [], song: [] }, tags }; }, getTopSongs: async (args) => { const { apiClientProps, query } = args; diff --git a/src/renderer/api/navidrome/navidrome-controller.ts b/src/renderer/api/navidrome/navidrome-controller.ts index 0fb63c70c..b51da1596 100644 --- a/src/renderer/api/navidrome/navidrome-controller.ts +++ b/src/renderer/api/navidrome/navidrome-controller.ts @@ -778,7 +778,7 @@ export const NavidromeController: InternalControllerEndpoint = { } } - const enumTags = Array.from(tagsToValues) + const tags = Array.from(tagsToValues) .map((data) => ({ name: data[0], options: data[1] @@ -793,12 +793,11 @@ export const NavidromeController: InternalControllerEndpoint = { const excludedSongTags = Array.from(EXCLUDED_SONG_TAGS.values()); return { - boolTags: undefined, - enumTags, excluded: { album: excludedAlbumTags, song: excludedSongTags, }, + tags, }; }, getTopSongs: SubsonicController.getTopSongs, diff --git a/src/renderer/features/albums/components/album-detail-content.tsx b/src/renderer/features/albums/components/album-detail-content.tsx index 2fad55b76..ae101d8eb 100644 --- a/src/renderer/features/albums/components/album-detail-content.tsx +++ b/src/renderer/features/albums/components/album-detail-content.tsx @@ -49,6 +49,7 @@ import { AlbumListSort, ExplicitStatus, LibraryItem, + ServerType, Song, SongListSort, SortOrder, @@ -152,8 +153,15 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => { if (!album?.recordLabels || album.recordLabels.length === 0) return []; return album.recordLabels.map((label) => { + if (album._serverType === ServerType.SUBSONIC) { + return { id: label, label: label, url: null }; + } + const searchParams = new URLSearchParams(); - const customFilters = { recordlabel: [label] }; + const customFilters = + album._serverType === ServerType.JELLYFIN + ? { Studios: [label] } + : { recordlabel: [label] }; const paramsWithCustom = setJsonSearchParam( searchParams, FILTER_KEYS.ALBUM._CUSTOM, @@ -183,15 +191,21 @@ const AlbumMetadataTags = ({ album }: AlbumMetadataTagsProps) => {
- {recordLabels.map((recordLabel) => ( - - {recordLabel.label} - - ))} + {recordLabels.map((recordLabel) => + recordLabel.url ? ( + + {recordLabel.label} + + ) : ( + + {recordLabel.label} + + ), + )}
diff --git a/src/renderer/features/albums/components/jellyfin-album-filters.tsx b/src/renderer/features/albums/components/jellyfin-album-filters.tsx index 5597d81b4..f21ae6b4e 100644 --- a/src/renderer/features/albums/components/jellyfin-album-filters.tsx +++ b/src/renderer/features/albums/components/jellyfin-album-filters.tsx @@ -3,16 +3,15 @@ import { useCallback, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { getItemImageUrl } from '/@/renderer/components/item-image/item-image'; -import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data'; import { useListContext } from '/@/renderer/context/list-context'; import { useAlbumListFilters } from '/@/renderer/features/albums/hooks/use-album-list-filters'; import { artistsQueries } from '/@/renderer/features/artists/api/artists-api'; import { genresQueries } from '/@/renderer/features/genres/api/genres-api'; -import { sharedQueries } from '/@/renderer/features/shared/api/shared-api'; import { ArtistMultiSelectRow, GenreMultiSelectRow, } from '/@/renderer/features/shared/components/multi-select-rows'; +import { TagFilters } from '/@/renderer/features/shared/components/tag-filter'; import { useCurrentServerId } from '/@/renderer/store'; import { useAppStore, useAppStoreActions } from '/@/renderer/store/app.store'; import { Button } from '/@/shared/components/button/button'; @@ -78,19 +77,6 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte })); }, [genreListQuery.data]); - const tagsQuery = useQuery( - sharedQueries.tagList({ - options: { - gcTime: 1000 * 60 * 2, - staleTime: 1000 * 60 * 1, - }, - query: { - type: LibraryItem.ALBUM, - }, - serverId, - }), - ); - const yesNoFilter = useMemo(() => { const filters = [ { @@ -204,13 +190,6 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte [setAlbumArtist], ); - const handleTagFilter = useCallback( - (e: null | string[]) => { - setCustom({ Tags: e && e.length > 0 ? e.join('|') : null }); - }, - [setCustom], - ); - const debouncedHandleMinYearFilter = useDebouncedCallback(handleMinYearFilter, 300); const debouncedHandleMaxYearFilter = useDebouncedCallback(handleMaxYearFilter, 300); @@ -358,17 +337,7 @@ export const JellyfinAlbumFilters = ({ disableArtistFilter }: JellyfinAlbumFilte value={query.maxYear ?? undefined} /> - {tagsQuery.data?.boolTags && tagsQuery.data.boolTags.length > 0 && ( - - )} +