mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-02 19:01:40 +00:00
add multiple genre support for nd albums/tracks
This commit is contained in:
parent
6df270ba34
commit
4a48598260
4 changed files with 77 additions and 20 deletions
|
|
@ -271,6 +271,10 @@ export const NavidromeController: ControllerEndpoint = {
|
||||||
getAlbumList: async (args) => {
|
getAlbumList: async (args) => {
|
||||||
const { apiClientProps, query } = args;
|
const { apiClientProps, query } = args;
|
||||||
|
|
||||||
|
const genres = hasFeature(apiClientProps.server, ServerFeature.BFR)
|
||||||
|
? query.genres
|
||||||
|
: query.genres?.[0];
|
||||||
|
|
||||||
const res = await ndApiClient(apiClientProps).getAlbumList({
|
const res = await ndApiClient(apiClientProps).getAlbumList({
|
||||||
query: {
|
query: {
|
||||||
_end: query.startIndex + (query.limit || 0),
|
_end: query.startIndex + (query.limit || 0),
|
||||||
|
|
@ -279,7 +283,7 @@ export const NavidromeController: ControllerEndpoint = {
|
||||||
_start: query.startIndex,
|
_start: query.startIndex,
|
||||||
artist_id: query.artistIds?.[0],
|
artist_id: query.artistIds?.[0],
|
||||||
compilation: query.compilation,
|
compilation: query.compilation,
|
||||||
genre_id: query.genres?.[0],
|
genre_id: genres,
|
||||||
name: query.searchTerm,
|
name: query.searchTerm,
|
||||||
...query._custom?.navidrome,
|
...query._custom?.navidrome,
|
||||||
starred: query.favorite,
|
starred: query.favorite,
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,21 @@ import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, useMemo } from 'react';
|
import { ChangeEvent, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { useAlbumArtistList } from '/@/renderer/features/artists/queries/album-artist-list-query';
|
||||||
import { useGenreList } from '/@/renderer/features/genres';
|
import { useGenreList } from '/@/renderer/features/genres';
|
||||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
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 { NDSongQueryFields } from '/@/shared/api/navidrome.types';
|
||||||
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||||
|
|
@ -23,6 +32,7 @@ import {
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
interface NavidromeAlbumFiltersProps {
|
interface NavidromeAlbumFiltersProps {
|
||||||
customFilters?: Partial<AlbumListFilter>;
|
customFilters?: Partial<AlbumListFilter>;
|
||||||
|
|
@ -42,6 +52,7 @@ export const NavidromeAlbumFilters = ({
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { filter } = useListStoreByKey<AlbumListQuery>({ key: pageKey });
|
const { filter } = useListStoreByKey<AlbumListQuery>({ key: pageKey });
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
|
const server = getServerById(serverId);
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useGenreList({
|
||||||
options: {
|
options: {
|
||||||
|
|
@ -64,12 +75,14 @@ export const NavidromeAlbumFilters = ({
|
||||||
}));
|
}));
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const handleGenresFilter = debounce((e: null | string) => {
|
const hasBrf = hasFeature(server, ServerFeature.BFR);
|
||||||
|
|
||||||
|
const handleGenresFilter = debounce((e: null | string[]) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
customFilters,
|
customFilters,
|
||||||
data: {
|
data: {
|
||||||
_custom: filter._custom,
|
_custom: filter._custom,
|
||||||
genres: e ? [e] : undefined,
|
genres: e ? e : undefined,
|
||||||
},
|
},
|
||||||
itemType: LibraryItem.ALBUM,
|
itemType: LibraryItem.ALBUM,
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
|
@ -269,15 +282,29 @@ export const NavidromeAlbumFilters = ({
|
||||||
min={0}
|
min={0}
|
||||||
onChange={(e) => handleYearFilter(e)}
|
onChange={(e) => handleYearFilter(e)}
|
||||||
/>
|
/>
|
||||||
<SelectWithInvalidData
|
{!hasBrf && (
|
||||||
clearable
|
<SelectWithInvalidData
|
||||||
data={genreList}
|
clearable
|
||||||
defaultValue={filter.genres && filter.genres[0]}
|
data={genreList}
|
||||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
defaultValue={filter.genres && filter.genres[0]}
|
||||||
onChange={handleGenresFilter}
|
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||||
searchable
|
onChange={(value) => handleGenresFilter(value !== null ? [value] : null)}
|
||||||
/>
|
searchable
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
{hasBrf && (
|
||||||
|
<Group grow>
|
||||||
|
<MultiSelectWithInvalidData
|
||||||
|
clearable
|
||||||
|
data={genreList}
|
||||||
|
defaultValue={filter.genres}
|
||||||
|
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||||
|
onChange={handleGenresFilter}
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
<Group grow>
|
<Group grow>
|
||||||
<SelectWithInvalidData
|
<SelectWithInvalidData
|
||||||
clearable
|
clearable
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,20 @@ import debounce from 'lodash/debounce';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
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 { useGenreList } from '/@/renderer/features/genres';
|
import { useGenreList } from '/@/renderer/features/genres';
|
||||||
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
import { useTagList } from '/@/renderer/features/tag/queries/use-tag-list';
|
||||||
import { SongListFilter, useListFilterByKey, useListStoreActions } from '/@/renderer/store';
|
import {
|
||||||
|
getServerById,
|
||||||
|
SongListFilter,
|
||||||
|
useListFilterByKey,
|
||||||
|
useListStoreActions,
|
||||||
|
} from '/@/renderer/store';
|
||||||
import { NDSongQueryFields } from '/@/shared/api/navidrome.types';
|
import { NDSongQueryFields } from '/@/shared/api/navidrome.types';
|
||||||
|
import { hasFeature } from '/@/shared/api/utils';
|
||||||
import { Divider } from '/@/shared/components/divider/divider';
|
import { Divider } from '/@/shared/components/divider/divider';
|
||||||
import { Group } from '/@/shared/components/group/group';
|
import { Group } from '/@/shared/components/group/group';
|
||||||
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
import { NumberInput } from '/@/shared/components/number-input/number-input';
|
||||||
|
|
@ -14,6 +23,7 @@ import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
|
import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select';
|
||||||
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/shared/types/domain-types';
|
import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/shared/types/domain-types';
|
||||||
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
interface NavidromeSongFiltersProps {
|
interface NavidromeSongFiltersProps {
|
||||||
customFilters?: Partial<SongListFilter>;
|
customFilters?: Partial<SongListFilter>;
|
||||||
|
|
@ -31,6 +41,7 @@ export const NavidromeSongFilters = ({
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
const filter = useListFilterByKey<SongListQuery>({ key: pageKey });
|
const filter = useListFilterByKey<SongListQuery>({ key: pageKey });
|
||||||
|
const server = getServerById(serverId);
|
||||||
|
|
||||||
const isGenrePage = customFilters?.genreIds !== undefined;
|
const isGenrePage = customFilters?.genreIds !== undefined;
|
||||||
|
|
||||||
|
|
@ -58,12 +69,14 @@ export const NavidromeSongFilters = ({
|
||||||
}));
|
}));
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const handleGenresFilter = debounce((e: null | string) => {
|
const hasBrf = hasFeature(server, ServerFeature.BFR);
|
||||||
|
|
||||||
|
const handleGenresFilter = debounce((e: null | string[]) => {
|
||||||
const updatedFilters = setFilter({
|
const updatedFilters = setFilter({
|
||||||
customFilters,
|
customFilters,
|
||||||
data: {
|
data: {
|
||||||
_custom: filter._custom,
|
_custom: filter._custom,
|
||||||
genreIds: e ? [e] : undefined,
|
genreIds: e ? e : undefined,
|
||||||
},
|
},
|
||||||
itemType: LibraryItem.SONG,
|
itemType: LibraryItem.SONG,
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
|
@ -148,18 +161,30 @@ export const NavidromeSongFilters = ({
|
||||||
value={filter._custom?.navidrome?.year}
|
value={filter._custom?.navidrome?.year}
|
||||||
width={50}
|
width={50}
|
||||||
/>
|
/>
|
||||||
{!isGenrePage && (
|
{!isGenrePage && !hasBrf && (
|
||||||
<SelectWithInvalidData
|
<SelectWithInvalidData
|
||||||
clearable
|
clearable
|
||||||
data={genreList}
|
data={genreList}
|
||||||
defaultValue={filter.genreIds ? filter.genreIds[0] : undefined}
|
defaultValue={filter.genreIds ? filter.genreIds[0] : undefined}
|
||||||
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
label={t('entity.genre', { count: 1, postProcess: 'titleCase' })}
|
||||||
onChange={handleGenresFilter}
|
onChange={(value) => handleGenresFilter(value !== null ? [value] : null)}
|
||||||
searchable
|
searchable
|
||||||
width={150}
|
width={150}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
{!isGenrePage && hasBrf && (
|
||||||
|
<Group grow>
|
||||||
|
<MultiSelectWithInvalidData
|
||||||
|
clearable
|
||||||
|
data={genreList}
|
||||||
|
defaultValue={filter.genreIds}
|
||||||
|
label={t('entity.genre', { count: 2, postProcess: 'sentenceCase' })}
|
||||||
|
onChange={handleGenresFilter}
|
||||||
|
searchable
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
)}
|
||||||
{tagsQuery.data?.enumTags?.length &&
|
{tagsQuery.data?.enumTags?.length &&
|
||||||
tagsQuery.data.enumTags.length > 0 &&
|
tagsQuery.data.enumTags.length > 0 &&
|
||||||
tagsQuery.data.enumTags.map((tag) => (
|
tagsQuery.data.enumTags.map((tag) => (
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,8 @@ const albumListParameters = paginationParameters.extend({
|
||||||
album_id: z.string().optional(),
|
album_id: z.string().optional(),
|
||||||
artist_id: z.string().optional(),
|
artist_id: z.string().optional(),
|
||||||
compilation: z.boolean().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(),
|
has_rating: z.boolean().optional(),
|
||||||
id: z.string().optional(),
|
id: z.string().optional(),
|
||||||
name: z.string().optional(),
|
name: z.string().optional(),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue