diff --git a/src/renderer/features/albums/components/album-list-header-filters.tsx b/src/renderer/features/albums/components/album-list-header-filters.tsx index 0883b6d2..d51cd639 100644 --- a/src/renderer/features/albums/components/album-list-header-filters.tsx +++ b/src/renderer/features/albums/components/album-list-header-filters.tsx @@ -414,18 +414,23 @@ export const AlbumListHeaderFilters = ({ Object.values(filter?._custom?.jellyfin).some((value) => value !== undefined); const isSubsonicFilterApplied = - server?.type === ServerType.SUBSONIC && - (filter.maxYear || filter.minYear || filter.favorite); + server?.type === ServerType.SUBSONIC && (filter.maxYear || filter.minYear); + + const isCompilationFilterApplied = + server?.type === ServerType.NAVIDROME && filter.compilation !== undefined; return ( isNavidromeFilterApplied || isJellyfinFilterApplied || isSubsonicFilterApplied || - filter.genres?.length + filter.genres?.length || + filter.favorite !== undefined || + isCompilationFilterApplied ); }, [ filter?._custom?.jellyfin, filter?._custom?.navidrome, + filter.compilation, filter.favorite, filter.genres?.length, filter.maxYear, diff --git a/src/renderer/features/albums/components/jellyfin-album-filters.tsx b/src/renderer/features/albums/components/jellyfin-album-filters.tsx index a86fbc81..9de6972c 100644 --- a/src/renderer/features/albums/components/jellyfin-album-filters.tsx +++ b/src/renderer/features/albums/components/jellyfin-album-filters.tsx @@ -1,5 +1,5 @@ import debounce from 'lodash/debounce'; -import { ChangeEvent, useMemo, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data'; @@ -12,8 +12,8 @@ import { Group } from '/@/shared/components/group/group'; import { NumberInput } from '/@/shared/components/number-input/number-input'; import { SpinnerIcon } from '/@/shared/components/spinner/spinner'; import { Stack } from '/@/shared/components/stack/stack'; -import { Switch } from '/@/shared/components/switch/switch'; import { Text } from '/@/shared/components/text/text'; +import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select'; import { AlbumArtistListSort, AlbumListQuery, @@ -72,15 +72,15 @@ export const JellyfinAlbumFilters = ({ return filter?._custom?.jellyfin?.Tags?.split('|'); }, [filter?._custom?.jellyfin?.Tags]); - const toggleFilters = [ + const yesNoFilter = [ { label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), - onChange: (e: ChangeEvent) => { + onChange: (favorite?: boolean) => { const updatedFilters = setFilter({ customFilters, data: { _custom: filter?._custom, - favorite: e.currentTarget.checked ? true : undefined, + favorite, }, itemType: LibraryItem.ALBUM, key: pageKey, @@ -189,16 +189,16 @@ export const JellyfinAlbumFilters = ({ return ( - {toggleFilters.map((filter) => ( + {yesNoFilter.map((filter) => ( {filter.label} - ))} @@ -250,7 +250,7 @@ export const JellyfinAlbumFilters = ({ searchValue={albumArtistSearchTerm} /> - {tagsQuery.data?.boolTags?.length && ( + {tagsQuery.data?.boolTags && tagsQuery.data.boolTags.length > 0 && ( { + const updatedFilters = setFilter({ + customFilters, + data: { + _custom: filter._custom, + favorite, + }, + itemType: LibraryItem.ALBUM, + key: pageKey, + }) as AlbumListFilter; + onFilterChange(updatedFilters); + }, + value: filter.favorite, + }, + { + 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, + }, + ]; + const toggleFilters = [ { label: t('filter.isRated', { postProcess: 'sentenceCase' }), @@ -100,38 +136,6 @@ export const NavidromeAlbumFilters = ({ }, value: filter._custom?.navidrome?.has_rating, }, - { - label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), - onChange: (e: ChangeEvent) => { - const updatedFilters = setFilter({ - customFilters, - data: { - _custom: filter._custom, - favorite: e.currentTarget.checked ? true : undefined, - }, - itemType: LibraryItem.ALBUM, - key: pageKey, - }) as AlbumListFilter; - onFilterChange(updatedFilters); - }, - value: filter.favorite, - }, - { - label: t('filter.isCompilation', { postProcess: 'sentenceCase' }), - onChange: (e: ChangeEvent) => { - const updatedFilters = setFilter({ - customFilters, - data: { - _custom: filter._custom, - compilation: e.currentTarget.checked ? true : undefined, - }, - itemType: LibraryItem.ALBUM, - key: pageKey, - }) as AlbumListFilter; - onFilterChange(updatedFilters); - }, - value: filter.compilation, - }, { label: t('filter.isRecentlyPlayed', { postProcess: 'sentenceCase' }), onChange: (e: ChangeEvent) => { @@ -236,6 +240,19 @@ export const NavidromeAlbumFilters = ({ return ( + {yesNoUndefinedFilters.map((filter) => ( + + {filter.label} + + + ))} {toggleFilters.map((filter) => ( { ...props.iconProps, }} tooltip={{ - label: t('entity.folder', { postProcess: 'sentenceCase' }), + label: t('entity.folder', { count: 1, postProcess: 'sentenceCase' }), ...props.tooltip, }} variant="subtle" diff --git a/src/renderer/features/songs/components/jellyfin-song-filters.tsx b/src/renderer/features/songs/components/jellyfin-song-filters.tsx index f664ee8d..a516f13a 100644 --- a/src/renderer/features/songs/components/jellyfin-song-filters.tsx +++ b/src/renderer/features/songs/components/jellyfin-song-filters.tsx @@ -1,5 +1,5 @@ import debounce from 'lodash/debounce'; -import { ChangeEvent, useMemo } from 'react'; +import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { MultiSelectWithInvalidData } from '/@/renderer/components/select-with-invalid-data'; @@ -10,8 +10,8 @@ import { Divider } from '/@/shared/components/divider/divider'; import { Group } from '/@/shared/components/group/group'; import { NumberInput } from '/@/shared/components/number-input/number-input'; import { Stack } from '/@/shared/components/stack/stack'; -import { Switch } from '/@/shared/components/switch/switch'; import { Text } from '/@/shared/components/text/text'; +import { YesNoSelect } from '/@/shared/components/yes-no-select/yes-no-select'; import { GenreListSort, LibraryItem, SongListQuery, SortOrder } from '/@/shared/types/domain-types'; interface JellyfinSongFiltersProps { @@ -69,10 +69,10 @@ export const JellyfinSongFilters = ({ return filter?._custom?.jellyfin?.Tags?.split('|'); }, [filter?._custom?.jellyfin?.Tags]); - const toggleFilters = [ + const yesNoFilters = [ { label: t('filter.isFavorited', { postProcess: 'sentenceCase' }), - onChange: (e: ChangeEvent) => { + onChange: (favorite?: boolean) => { const updatedFilters = setFilter({ customFilters, data: { @@ -83,7 +83,7 @@ export const JellyfinSongFilters = ({ IncludeItemTypes: 'Audio', }, }, - favorite: e.currentTarget.checked ? true : undefined, + favorite, }, itemType: LibraryItem.SONG, key: pageKey, @@ -174,15 +174,16 @@ export const JellyfinSongFilters = ({ return ( - {toggleFilters.map((filter) => ( + {yesNoFilters.map((filter) => ( {filter.label} - ))} @@ -218,7 +219,7 @@ export const JellyfinSongFilters = ({ /> )} - {tagsQuery.data?.boolTags?.length && ( + {tagsQuery.data?.boolTags && tagsQuery.data.boolTags.length > 0 && ( ) => { + onChange: (favorite: boolean | undefined) => { const updatedFilters = setFilter({ customFilters, data: { _custom: filter._custom, - favorite: e.currentTarget.checked ? true : undefined, + favorite, }, itemType: LibraryItem.SONG, key: pageKey, @@ -137,10 +137,10 @@ export const NavidromeSongFilters = ({ key={`nd-filter-${filter.label}`} > {filter.label} - ))} diff --git a/src/renderer/features/songs/components/song-list-header-filters.tsx b/src/renderer/features/songs/components/song-list-header-filters.tsx index f3145221..4afe9281 100644 --- a/src/renderer/features/songs/components/song-list-header-filters.tsx +++ b/src/renderer/features/songs/components/song-list-header-filters.tsx @@ -467,7 +467,7 @@ export const SongListHeaderFilters = ({ .filter((value) => value !== 'Audio') // Don't account for includeItemTypes: Audio .some((value) => value !== undefined); - const isGenericFilterApplied = filter?.favorite || filter?.genreIds?.length; + const isGenericFilterApplied = filter?.favorite !== undefined || filter?.genreIds?.length; return isNavidromeFilterApplied || isJellyfinFilterApplied || isGenericFilterApplied; }, [ diff --git a/src/renderer/features/songs/components/subsonic-song-filter.tsx b/src/renderer/features/songs/components/subsonic-song-filter.tsx index 202a2075..1b508d4c 100644 --- a/src/renderer/features/songs/components/subsonic-song-filter.tsx +++ b/src/renderer/features/songs/components/subsonic-song-filter.tsx @@ -69,7 +69,7 @@ export const SubsonicSongFilters = ({ const updatedFilters = setFilter({ customFilters, data: { - favorite: e.target.checked, + favorite: e.target.checked ? true : undefined, }, itemType: LibraryItem.SONG, key: pageKey, diff --git a/src/shared/components/yes-no-select/yes-no-select.tsx b/src/shared/components/yes-no-select/yes-no-select.tsx new file mode 100644 index 00000000..0609ec65 --- /dev/null +++ b/src/shared/components/yes-no-select/yes-no-select.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from 'react-i18next'; + +import { Select, SelectProps } from '/@/shared/components/select/select'; + +export interface YesNoSelectProps extends Omit { + onChange: (e?: boolean) => void; + value?: boolean; +} + +export const YesNoSelect = ({ onChange, value, ...props }: YesNoSelectProps) => { + const { t } = useTranslation(); + + return ( +