conditionally disable Subsonic list filters based on availability (#1567)

This commit is contained in:
jeffvli
2026-01-17 18:20:40 -08:00
parent 9f9d685353
commit 27f82aef94
7 changed files with 275 additions and 38 deletions
@@ -1,10 +1,17 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import { ChangeEvent, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { getItemImageUrl } from '/@/renderer/components/item-image/item-image';
import { useListContext } from '/@/renderer/context/list-context';
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
import { useGenreList } from '/@/renderer/features/genres/api/genres-api';
import { GenreMultiSelectRow } from '/@/renderer/features/shared/components/multi-select-rows';
import {
ArtistMultiSelectRow,
GenreMultiSelectRow,
} from '/@/renderer/features/shared/components/multi-select-rows';
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
import { useCurrentServerId } from '/@/renderer/store';
import { Button } from '/@/shared/components/button/button';
import { Divider } from '/@/shared/components/divider/divider';
import { Group } from '/@/shared/components/group/group';
@@ -12,10 +19,12 @@ import { VirtualMultiSelect } from '/@/shared/components/multi-select/virtual-mu
import { Stack } from '/@/shared/components/stack/stack';
import { Switch } from '/@/shared/components/switch/switch';
import { Text } from '/@/shared/components/text/text';
import { AlbumArtistListSort, LibraryItem, SortOrder } from '/@/shared/types/domain-types';
export const SubsonicSongFilters = () => {
const { t } = useTranslation();
const { clear, query, setFavorite, setGenreId } = useSongListFilters();
const serverId = useCurrentServerId();
const { clear, query, setArtistIds, setFavorite, setGenreId } = useSongListFilters();
const { customFilters } = useListContext();
@@ -35,15 +44,75 @@ export const SubsonicSongFilters = () => {
const selectedGenreIds = useMemo(() => query.genreIds || [], [query.genreIds]);
const albumArtistListQuery = useSuspenseQuery(
artistsQueries.albumArtistList({
options: {
gcTime: 1000 * 60 * 2,
staleTime: 1000 * 60 * 1,
},
query: {
sortBy: AlbumArtistListSort.NAME,
sortOrder: SortOrder.ASC,
startIndex: 0,
},
serverId,
}),
);
const items = albumArtistListQuery?.data?.items;
const selectableAlbumArtists = useMemo(() => {
if (!items) return [];
return items.map((artist) => ({
albumCount: artist.albumCount,
imageUrl: getItemImageUrl({
id: artist.id,
itemType: LibraryItem.ARTIST,
type: 'table',
}),
label: artist.name,
songCount: artist.songCount,
value: artist.id,
}));
}, [items]);
const selectedArtistIds = useMemo(() => query.artistIds || [], [query.artistIds]);
const hasFavorite = query.favorite === true;
const hasArtist = query.artistIds && query.artistIds.length > 0;
const hasGenre = query.genreIds && query.genreIds.length > 0;
const isFavoriteDisabled = hasArtist || hasGenre;
const isArtistDisabled = hasFavorite || hasGenre;
const isGenreDisabled = hasFavorite || hasArtist;
const handleArtistFilter = useCallback(
(e: null | string[]) => {
if (isArtistDisabled && e !== null) return;
setArtistIds(e ?? null);
},
[isArtistDisabled, setArtistIds],
);
const artistFilterLabel = useMemo(() => {
return (
<Text fw={500} size="sm">
{t('entity.artist', { count: 2, postProcess: 'sentenceCase' })}
</Text>
);
}, [t]);
const handleGenresFilter = useCallback(
(e: null | string[]) => {
if (isGenreDisabled && e !== null && e.length > 0) return;
if (e && e.length > 0) {
setGenreId([e[0]]);
} else {
setGenreId(null);
}
},
[setGenreId],
[isGenreDisabled, setGenreId],
);
const genreFilterLabel = useMemo(() => {
@@ -59,13 +128,14 @@ export const SubsonicSongFilters = () => {
{
label: t('filter.isFavorited', { postProcess: 'sentenceCase' }),
onChange: (e: ChangeEvent<HTMLInputElement>) => {
if (isFavoriteDisabled && e.target.checked) return;
const favoriteValue = e.target.checked ? true : undefined;
setFavorite(favoriteValue ?? null);
},
value: query.favorite,
},
],
[t, query.favorite, setFavorite],
[isFavoriteDisabled, query.favorite, setFavorite, t],
);
return (
@@ -73,13 +143,30 @@ export const SubsonicSongFilters = () => {
{toggleFilters.map((filter) => (
<Group justify="space-between" key={`ss-filter-${filter.label}`}>
<Text>{filter.label}</Text>
<Switch checked={filter.value ?? false} onChange={filter.onChange} />
<Switch
checked={filter.value ?? false}
disabled={isFavoriteDisabled}
onChange={filter.onChange}
/>
</Group>
))}
<Divider my="md" />
<VirtualMultiSelect
disabled={isArtistDisabled}
displayCountType="song"
height={300}
label={artistFilterLabel}
onChange={handleArtistFilter}
options={selectableAlbumArtists}
RowComponent={ArtistMultiSelectRow}
singleSelect={true}
value={selectedArtistIds}
/>
{!isGenrePage && (
<>
<Divider my="md" />
<VirtualMultiSelect
disabled={isGenreDisabled}
displayCountType="song"
height={220}
label={genreFilterLabel}