mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 10:23:33 +00:00
Compilation support for Jellyfin artist albums, misc other album filter fixes
- Jellyfin will use `ContributingArtistsId` (compilation), `AlbumArtistIds` (compilation is false), or `ArtistIds` (unspecified; all) - Jellyfin can filter by compilation _only_ on the artist discography page - Navidrome album filter fix for `defaultValue` display and prevent showing `tagQuery` 0 when querying - Subsonic can filter by one or more artists in the album page. Sort is also applied on these items - Bump genre/tag cache/stale time to 2/1 minutes - Fix various cases where the album filter would display as active when it wasn't
This commit is contained in:
parent
6f5dd4881a
commit
176a95a946
7 changed files with 167 additions and 47 deletions
|
|
@ -290,19 +290,32 @@ export const JellyfinController: ControllerEndpoint = {
|
||||||
|
|
||||||
const yearsFilter = yearsGroup.length ? yearsGroup.join(',') : undefined;
|
const yearsFilter = yearsGroup.length ? yearsGroup.join(',') : undefined;
|
||||||
|
|
||||||
|
let artistQuery:
|
||||||
|
| Omit<z.infer<typeof jfType._parameters.albumList>, 'IncludeItemTypes'>
|
||||||
|
| undefined;
|
||||||
|
|
||||||
|
if (query.artistIds) {
|
||||||
|
// Based mostly off of observation, this is the behavior I've seen:
|
||||||
|
// ContributingArtistIds is the _closest_ to where the album is a compilation and the artist is involved
|
||||||
|
// AlbumArtistIds is where the artist is an album artist
|
||||||
|
// ArtistIds is all credits
|
||||||
|
if (query.compilation) {
|
||||||
|
artistQuery = {
|
||||||
|
ContributingArtistIds: formatCommaDelimitedString(query.artistIds),
|
||||||
|
};
|
||||||
|
} else if (query.compilation === false) {
|
||||||
|
artistQuery = { AlbumArtistIds: formatCommaDelimitedString(query.artistIds) };
|
||||||
|
} else {
|
||||||
|
artistQuery = { ArtistIds: formatCommaDelimitedString(query.artistIds) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const res = await jfApiClient(apiClientProps).getAlbumList({
|
const res = await jfApiClient(apiClientProps).getAlbumList({
|
||||||
params: {
|
params: {
|
||||||
userId: apiClientProps.server?.userId,
|
userId: apiClientProps.server?.userId,
|
||||||
},
|
},
|
||||||
query: {
|
query: {
|
||||||
...(!query.compilation &&
|
...artistQuery,
|
||||||
query.artistIds && {
|
|
||||||
AlbumArtistIds: formatCommaDelimitedString(query.artistIds),
|
|
||||||
}),
|
|
||||||
...(query.compilation &&
|
|
||||||
query.artistIds && {
|
|
||||||
ContributingArtistIds: query.artistIds[0],
|
|
||||||
}),
|
|
||||||
Fields: 'People, Tags',
|
Fields: 'People, Tags',
|
||||||
GenreIds: query.genres ? query.genres.join(',') : undefined,
|
GenreIds: query.genres ? query.genres.join(',') : undefined,
|
||||||
IncludeItemTypes: 'MusicAlbum',
|
IncludeItemTypes: 'MusicAlbum',
|
||||||
|
|
|
||||||
|
|
@ -316,8 +316,10 @@ export const SubsonicController: ControllerEndpoint = {
|
||||||
return artist.body.artist.album ?? [];
|
return artist.body.artist.album ?? [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const items = albums.map((album) => ssNormalize.album(album, apiClientProps.server));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
items: albums.map((album) => ssNormalize.album(album, apiClientProps.server)),
|
items: sortAlbumList(items, query.sortBy, query.sortOrder),
|
||||||
startIndex: 0,
|
startIndex: 0,
|
||||||
totalRecordCount: albums.length,
|
totalRecordCount: albums.length,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -405,31 +405,35 @@ export const AlbumListHeaderFilters = ({
|
||||||
const isFilterApplied = useMemo(() => {
|
const isFilterApplied = useMemo(() => {
|
||||||
const isNavidromeFilterApplied =
|
const isNavidromeFilterApplied =
|
||||||
server?.type === ServerType.NAVIDROME &&
|
server?.type === ServerType.NAVIDROME &&
|
||||||
filter?._custom?.navidrome &&
|
((filter?._custom?.navidrome &&
|
||||||
Object.values(filter?._custom?.navidrome).some((value) => value !== undefined);
|
Object.values(filter?._custom?.navidrome).some((value) => value !== undefined)) ||
|
||||||
|
// Compilation is always valid
|
||||||
|
filter.compilation !== undefined);
|
||||||
|
|
||||||
const isJellyfinFilterApplied =
|
const isJellyfinFilterApplied =
|
||||||
server?.type === ServerType.JELLYFIN &&
|
server?.type === ServerType.JELLYFIN &&
|
||||||
filter?._custom?.jellyfin &&
|
((filter?._custom?.jellyfin &&
|
||||||
Object.values(filter?._custom?.jellyfin).some((value) => value !== undefined);
|
Object.values(filter?._custom?.jellyfin).some((value) => value !== undefined)) ||
|
||||||
|
// Compilation filter is only valid when on the artist page
|
||||||
|
(filter.compilation !== undefined && customFilters?.artistIds));
|
||||||
|
|
||||||
const isSubsonicFilterApplied =
|
const isSubsonicFilterApplied =
|
||||||
server?.type === ServerType.SUBSONIC && (filter.maxYear || filter.minYear);
|
server?.type === ServerType.SUBSONIC && (filter.maxYear || filter.minYear);
|
||||||
|
|
||||||
const isCompilationFilterApplied =
|
|
||||||
server?.type === ServerType.NAVIDROME && filter.compilation !== undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
isNavidromeFilterApplied ||
|
isNavidromeFilterApplied ||
|
||||||
isJellyfinFilterApplied ||
|
isJellyfinFilterApplied ||
|
||||||
isSubsonicFilterApplied ||
|
isSubsonicFilterApplied ||
|
||||||
filter.genres?.length ||
|
filter.genres?.length ||
|
||||||
filter.favorite !== undefined ||
|
filter.favorite !== undefined ||
|
||||||
isCompilationFilterApplied
|
// If we are on the artist page, the artist id filter should not be active
|
||||||
|
(filter.artistIds?.length && !(customFilters?.artistIds as any | undefined)?.length)
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
|
customFilters?.artistIds,
|
||||||
filter?._custom?.jellyfin,
|
filter?._custom?.jellyfin,
|
||||||
filter?._custom?.navidrome,
|
filter?._custom?.navidrome,
|
||||||
|
filter.artistIds?.length,
|
||||||
filter.compilation,
|
filter.compilation,
|
||||||
filter.favorite,
|
filter.favorite,
|
||||||
filter.genres?.length,
|
filter.genres?.length,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||||
|
|
@ -43,6 +43,10 @@ export const JellyfinAlbumFilters = ({
|
||||||
|
|
||||||
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
// TODO - eventually replace with /items/filters endpoint to fetch genres and tags specific to the selected library
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useGenreList({
|
||||||
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
musicFolderId: filter?.musicFolderId,
|
musicFolderId: filter?.musicFolderId,
|
||||||
sortBy: GenreListSort.NAME,
|
sortBy: GenreListSort.NAME,
|
||||||
|
|
@ -61,6 +65,10 @@ export const JellyfinAlbumFilters = ({
|
||||||
}, [genreListQuery.data]);
|
}, [genreListQuery.data]);
|
||||||
|
|
||||||
const tagsQuery = useTagList({
|
const tagsQuery = useTagList({
|
||||||
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
folder: filter?.musicFolderId,
|
folder: filter?.musicFolderId,
|
||||||
type: LibraryItem.ALBUM,
|
type: LibraryItem.ALBUM,
|
||||||
|
|
@ -72,24 +80,55 @@ export const JellyfinAlbumFilters = ({
|
||||||
return filter?._custom?.jellyfin?.Tags?.split('|');
|
return filter?._custom?.jellyfin?.Tags?.split('|');
|
||||||
}, [filter?._custom?.jellyfin?.Tags]);
|
}, [filter?._custom?.jellyfin?.Tags]);
|
||||||
|
|
||||||
const yesNoFilter = [
|
const yesNoFilter = useMemo(() => {
|
||||||
{
|
const filters = [
|
||||||
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }),
|
{
|
||||||
onChange: (favorite?: boolean) => {
|
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }),
|
||||||
const updatedFilters = setFilter({
|
onChange: (favorite?: boolean) => {
|
||||||
customFilters,
|
const updatedFilters = setFilter({
|
||||||
data: {
|
customFilters,
|
||||||
_custom: filter?._custom,
|
data: {
|
||||||
favorite,
|
_custom: filter?._custom,
|
||||||
},
|
favorite,
|
||||||
itemType: LibraryItem.ALBUM,
|
},
|
||||||
key: pageKey,
|
itemType: LibraryItem.ALBUM,
|
||||||
}) as AlbumListFilter;
|
key: pageKey,
|
||||||
onFilterChange(updatedFilters);
|
}) as AlbumListFilter;
|
||||||
|
onFilterChange(updatedFilters);
|
||||||
|
},
|
||||||
|
value: filter?.favorite,
|
||||||
},
|
},
|
||||||
value: filter?.favorite,
|
];
|
||||||
},
|
|
||||||
];
|
if (customFilters?.artistIds) {
|
||||||
|
filters.push({
|
||||||
|
label: t('filter.isCompilation', { postProcess: 'sentenceCase' }),
|
||||||
|
onChange: (compilation?: boolean) => {
|
||||||
|
const updatedFilters = setFilter({
|
||||||
|
customFilters,
|
||||||
|
data: {
|
||||||
|
_custom: filter._custom,
|
||||||
|
compilation,
|
||||||
|
},
|
||||||
|
itemType: LibraryItem.ALBUM,
|
||||||
|
key: pageKey,
|
||||||
|
}) as AlbumListFilter;
|
||||||
|
onFilterChange(updatedFilters);
|
||||||
|
},
|
||||||
|
value: filter.compilation,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return filters;
|
||||||
|
}, [
|
||||||
|
customFilters,
|
||||||
|
filter._custom,
|
||||||
|
filter.compilation,
|
||||||
|
filter?.favorite,
|
||||||
|
onFilterChange,
|
||||||
|
pageKey,
|
||||||
|
setFilter,
|
||||||
|
t,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleMinYearFilter = debounce((e: number | string) => {
|
const handleMinYearFilter = debounce((e: number | string) => {
|
||||||
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
if (typeof e === 'number' && (e < 1700 || e > 2300)) return;
|
||||||
|
|
@ -132,8 +171,6 @@ export const JellyfinAlbumFilters = ({
|
||||||
onFilterChange(updatedFilters);
|
onFilterChange(updatedFilters);
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList({
|
const albumArtistListQuery = useAlbumArtistList({
|
||||||
options: {
|
options: {
|
||||||
cacheTime: 1000 * 60 * 2,
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
|
@ -161,7 +198,7 @@ export const JellyfinAlbumFilters = ({
|
||||||
customFilters,
|
customFilters,
|
||||||
data: {
|
data: {
|
||||||
_custom: filter?._custom,
|
_custom: filter?._custom,
|
||||||
artistIds: e || undefined,
|
artistIds: e?.length ? e : undefined,
|
||||||
},
|
},
|
||||||
itemType: LibraryItem.ALBUM,
|
itemType: LibraryItem.ALBUM,
|
||||||
key: pageKey,
|
key: pageKey,
|
||||||
|
|
@ -238,16 +275,14 @@ export const JellyfinAlbumFilters = ({
|
||||||
<MultiSelectWithInvalidData
|
<MultiSelectWithInvalidData
|
||||||
clearable
|
clearable
|
||||||
data={selectableAlbumArtists}
|
data={selectableAlbumArtists}
|
||||||
defaultValue={filter?._custom?.jellyfin?.AlbumArtistIds?.split(',')}
|
defaultValue={filter?.artistIds}
|
||||||
disabled={disableArtistFilter}
|
disabled={disableArtistFilter}
|
||||||
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
||||||
limit={300}
|
limit={300}
|
||||||
onChange={handleAlbumArtistFilter}
|
onChange={handleAlbumArtistFilter}
|
||||||
onSearchChange={setAlbumArtistSearchTerm}
|
|
||||||
placeholder="Type to search for an artist"
|
placeholder="Type to search for an artist"
|
||||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||||
searchable
|
searchable
|
||||||
searchValue={albumArtistSearchTerm}
|
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
{tagsQuery.data?.boolTags && tagsQuery.data.boolTags.length > 0 && (
|
{tagsQuery.data?.boolTags && tagsQuery.data.boolTags.length > 0 && (
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, useMemo, useState } 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 { SelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data';
|
||||||
|
|
@ -43,6 +43,10 @@ export const NavidromeAlbumFilters = ({
|
||||||
const { setFilter } = useListStoreActions();
|
const { setFilter } = useListStoreActions();
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useGenreList({
|
||||||
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
sortBy: GenreListSort.NAME,
|
sortBy: GenreListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
|
|
@ -73,6 +77,10 @@ export const NavidromeAlbumFilters = ({
|
||||||
}, 250);
|
}, 250);
|
||||||
|
|
||||||
const tagsQuery = useTagList({
|
const tagsQuery = useTagList({
|
||||||
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
type: LibraryItem.ALBUM,
|
type: LibraryItem.ALBUM,
|
||||||
},
|
},
|
||||||
|
|
@ -177,8 +185,6 @@ export const NavidromeAlbumFilters = ({
|
||||||
onFilterChange(updatedFilters);
|
onFilterChange(updatedFilters);
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
const [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
|
||||||
|
|
||||||
const albumArtistListQuery = useAlbumArtistList({
|
const albumArtistListQuery = useAlbumArtistList({
|
||||||
options: {
|
options: {
|
||||||
cacheTime: 1000 * 60 * 2,
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
|
@ -293,13 +299,12 @@ export const NavidromeAlbumFilters = ({
|
||||||
label={t('entity.artist', { count: 1, postProcess: 'titleCase' })}
|
label={t('entity.artist', { count: 1, postProcess: 'titleCase' })}
|
||||||
limit={300}
|
limit={300}
|
||||||
onChange={handleAlbumArtistFilter}
|
onChange={handleAlbumArtistFilter}
|
||||||
onSearchChange={setAlbumArtistSearchTerm}
|
|
||||||
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||||
searchable
|
searchable
|
||||||
searchValue={albumArtistSearchTerm}
|
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
{tagsQuery.data?.enumTags?.length &&
|
{tagsQuery.data?.enumTags?.length &&
|
||||||
|
tagsQuery.data.enumTags.length > 0 &&
|
||||||
tagsQuery.data.enumTags.map((tag) => (
|
tagsQuery.data.enumTags.map((tag) => (
|
||||||
<Group
|
<Group
|
||||||
grow
|
grow
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import { ChangeEvent, useMemo } from 'react';
|
import { ChangeEvent, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { MultiSelectWithInvalidData } 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 { useGenreList } from '/@/renderer/features/genres';
|
||||||
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
import { AlbumListFilter, useListStoreActions, useListStoreByKey } from '/@/renderer/store';
|
||||||
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';
|
||||||
import { Select } from '/@/shared/components/select/select';
|
import { Select } from '/@/shared/components/select/select';
|
||||||
|
import { SpinnerIcon } from '/@/shared/components/spinner/spinner';
|
||||||
import { Stack } from '/@/shared/components/stack/stack';
|
import { Stack } from '/@/shared/components/stack/stack';
|
||||||
import { Switch } from '/@/shared/components/switch/switch';
|
import { Switch } from '/@/shared/components/switch/switch';
|
||||||
import { Text } from '/@/shared/components/text/text';
|
import { Text } from '/@/shared/components/text/text';
|
||||||
import {
|
import {
|
||||||
|
AlbumArtistListSort,
|
||||||
AlbumListQuery,
|
AlbumListQuery,
|
||||||
GenreListSort,
|
GenreListSort,
|
||||||
LibraryItem,
|
LibraryItem,
|
||||||
|
|
@ -19,12 +23,14 @@ import {
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface SubsonicAlbumFiltersProps {
|
interface SubsonicAlbumFiltersProps {
|
||||||
|
disableArtistFilter?: boolean;
|
||||||
onFilterChange: (filters: AlbumListFilter) => void;
|
onFilterChange: (filters: AlbumListFilter) => void;
|
||||||
pageKey: string;
|
pageKey: string;
|
||||||
serverId?: string;
|
serverId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SubsonicAlbumFilters = ({
|
export const SubsonicAlbumFilters = ({
|
||||||
|
disableArtistFilter,
|
||||||
onFilterChange,
|
onFilterChange,
|
||||||
pageKey,
|
pageKey,
|
||||||
serverId,
|
serverId,
|
||||||
|
|
@ -32,8 +38,46 @@ export const SubsonicAlbumFilters = ({
|
||||||
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 [albumArtistSearchTerm, setAlbumArtistSearchTerm] = useState<string>('');
|
||||||
|
|
||||||
|
const albumArtistListQuery = useAlbumArtistList({
|
||||||
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
|
query: {
|
||||||
|
sortBy: AlbumArtistListSort.NAME,
|
||||||
|
sortOrder: SortOrder.ASC,
|
||||||
|
startIndex: 0,
|
||||||
|
},
|
||||||
|
serverId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectableAlbumArtists = useMemo(() => {
|
||||||
|
if (!albumArtistListQuery?.data?.items) return [];
|
||||||
|
|
||||||
|
return albumArtistListQuery?.data?.items?.map((artist) => ({
|
||||||
|
label: artist.name,
|
||||||
|
value: artist.id,
|
||||||
|
}));
|
||||||
|
}, [albumArtistListQuery?.data?.items]);
|
||||||
|
|
||||||
|
const handleAlbumArtistFilter = (e: null | string[]) => {
|
||||||
|
const updatedFilters = setFilter({
|
||||||
|
data: {
|
||||||
|
artistIds: e?.length ? e : undefined,
|
||||||
|
},
|
||||||
|
itemType: LibraryItem.ALBUM,
|
||||||
|
key: pageKey,
|
||||||
|
}) as AlbumListFilter;
|
||||||
|
onFilterChange(updatedFilters);
|
||||||
|
};
|
||||||
|
|
||||||
const genreListQuery = useGenreList({
|
const genreListQuery = useGenreList({
|
||||||
|
options: {
|
||||||
|
cacheTime: 1000 * 60 * 2,
|
||||||
|
staleTime: 1000 * 60 * 1,
|
||||||
|
},
|
||||||
query: {
|
query: {
|
||||||
sortBy: GenreListSort.NAME,
|
sortBy: GenreListSort.NAME,
|
||||||
sortOrder: SortOrder.ASC,
|
sortOrder: SortOrder.ASC,
|
||||||
|
|
@ -147,6 +191,22 @@ export const SubsonicAlbumFilters = ({
|
||||||
searchable
|
searchable
|
||||||
/>
|
/>
|
||||||
</Group>
|
</Group>
|
||||||
|
<Group grow>
|
||||||
|
<MultiSelectWithInvalidData
|
||||||
|
clearable
|
||||||
|
data={selectableAlbumArtists}
|
||||||
|
defaultValue={filter?.artistIds}
|
||||||
|
disabled={disableArtistFilter}
|
||||||
|
label={t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
|
||||||
|
limit={300}
|
||||||
|
onChange={handleAlbumArtistFilter}
|
||||||
|
onSearchChange={setAlbumArtistSearchTerm}
|
||||||
|
placeholder="Type to search for an artist"
|
||||||
|
rightSection={albumArtistListQuery.isFetching ? <SpinnerIcon /> : undefined}
|
||||||
|
searchable
|
||||||
|
searchValue={albumArtistSearchTerm}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -167,6 +167,7 @@ export const NavidromeSongFilters = ({
|
||||||
)}
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
{tagsQuery.data?.enumTags?.length &&
|
{tagsQuery.data?.enumTags?.length &&
|
||||||
|
tagsQuery.data.enumTags.length > 0 &&
|
||||||
tagsQuery.data.enumTags.map((tag) => (
|
tagsQuery.data.enumTags.map((tag) => (
|
||||||
<Group
|
<Group
|
||||||
grow
|
grow
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue