mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-06 20:10:12 +02:00
add has_rating filter for Navidrome song list
This commit is contained in:
@@ -30,6 +30,7 @@ import { ServerFeature } from '/@/shared/types/features-types';
|
|||||||
const VERSION_INFO: VersionInfo = [
|
const VERSION_INFO: VersionInfo = [
|
||||||
// Why 2? Subsonic controller will return 1 for its own implementation
|
// Why 2? Subsonic controller will return 1 for its own implementation
|
||||||
// Use 2 to denote that Navidrome's own API has a different endpoint
|
// Use 2 to denote that Navidrome's own API has a different endpoint
|
||||||
|
['0.60.4', { [ServerFeature.TRACK_YES_NO_RATING_FILTER]: [1] }],
|
||||||
['0.57.0', { [ServerFeature.SERVER_PLAY_QUEUE]: [2] }],
|
['0.57.0', { [ServerFeature.SERVER_PLAY_QUEUE]: [2] }],
|
||||||
['0.56.0', { [ServerFeature.TRACK_ALBUM_ARTIST_SEARCH]: [1] }],
|
['0.56.0', { [ServerFeature.TRACK_ALBUM_ARTIST_SEARCH]: [1] }],
|
||||||
['0.55.0', { [ServerFeature.BFR]: [1], [ServerFeature.TAGS]: [1] }],
|
['0.55.0', { [ServerFeature.BFR]: [1], [ServerFeature.TAGS]: [1] }],
|
||||||
@@ -669,6 +670,7 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
...subsonicArgs.features,
|
...subsonicArgs.features,
|
||||||
...navidromeFeatures,
|
...navidromeFeatures,
|
||||||
publicPlaylist: [1],
|
publicPlaylist: [1],
|
||||||
|
[ServerFeature.ALBUM_YES_NO_RATING_FILTER]: [1],
|
||||||
[ServerFeature.MUSIC_FOLDER_MULTISELECT]: [1],
|
[ServerFeature.MUSIC_FOLDER_MULTISELECT]: [1],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -741,6 +743,10 @@ export const NavidromeController: InternalControllerEndpoint = {
|
|||||||
album_id: query.albumIds,
|
album_id: query.albumIds,
|
||||||
genre_id: query.genreIds,
|
genre_id: query.genreIds,
|
||||||
[getArtistSongKey(apiClientProps.server)]: query.artistIds ?? query.albumArtistIds,
|
[getArtistSongKey(apiClientProps.server)]: query.artistIds ?? query.albumArtistIds,
|
||||||
|
...(hasFeature(apiClientProps.server, ServerFeature.TRACK_YES_NO_RATING_FILTER) &&
|
||||||
|
query.hasRating !== undefined
|
||||||
|
? { has_rating: query.hasRating }
|
||||||
|
: {}),
|
||||||
library_id: getLibraryId(query.musicFolderId),
|
library_id: getLibraryId(query.musicFolderId),
|
||||||
starred: query.favorite,
|
starred: query.favorite,
|
||||||
title: query.searchTerm,
|
title: query.searchTerm,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { TagFilters } from '/@/renderer/features/shared/components/tag-filter';
|
|||||||
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
|
import { useSongListFilters } from '/@/renderer/features/songs/hooks/use-song-list-filters';
|
||||||
import { useCurrentServer } from '/@/renderer/store';
|
import { useCurrentServer } from '/@/renderer/store';
|
||||||
import { useAppStore, useAppStoreActions } from '/@/renderer/store/app.store';
|
import { useAppStore, useAppStoreActions } from '/@/renderer/store/app.store';
|
||||||
|
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 { VirtualMultiSelect } from '/@/shared/components/multi-select/virtual-multi-select';
|
import { VirtualMultiSelect } from '/@/shared/components/multi-select/virtual-multi-select';
|
||||||
@@ -27,6 +28,7 @@ import {
|
|||||||
LibraryItem,
|
LibraryItem,
|
||||||
SortOrder,
|
SortOrder,
|
||||||
} from '/@/shared/types/domain-types';
|
} from '/@/shared/types/domain-types';
|
||||||
|
import { ServerFeature } from '/@/shared/types/features-types';
|
||||||
|
|
||||||
interface NavidromeSongFiltersProps {
|
interface NavidromeSongFiltersProps {
|
||||||
disableArtistFilter?: boolean;
|
disableArtistFilter?: boolean;
|
||||||
@@ -40,8 +42,18 @@ export const NavidromeSongFilters = ({
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const server = useCurrentServer();
|
const server = useCurrentServer();
|
||||||
const serverId = server.id;
|
const serverId = server.id;
|
||||||
const { query, setArtistIds, setCustom, setFavorite, setGenreId, setMaxYear, setMinYear } =
|
const {
|
||||||
useSongListFilters();
|
query,
|
||||||
|
setArtistIds,
|
||||||
|
setCustom,
|
||||||
|
setFavorite,
|
||||||
|
setGenreId,
|
||||||
|
setHasRating,
|
||||||
|
setMaxYear,
|
||||||
|
setMinYear,
|
||||||
|
} = useSongListFilters();
|
||||||
|
|
||||||
|
const showRatingFilter = hasFeature(server, ServerFeature.TRACK_YES_NO_RATING_FILTER);
|
||||||
|
|
||||||
const genreListQuery = useQuery(
|
const genreListQuery = useQuery(
|
||||||
genresQueries.list({
|
genresQueries.list({
|
||||||
@@ -278,6 +290,25 @@ export const NavidromeSongFilters = ({
|
|||||||
w="100%"
|
w="100%"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
{showRatingFilter && (
|
||||||
|
<>
|
||||||
|
<Divider my="md" />
|
||||||
|
<Stack gap="xs">
|
||||||
|
<Text size="sm" weight={500}>
|
||||||
|
{t('filter.isRated', { postProcess: 'sentenceCase' })}
|
||||||
|
</Text>
|
||||||
|
<SegmentedControl
|
||||||
|
data={segmentedControlData}
|
||||||
|
onChange={(value) => {
|
||||||
|
setHasRating(segmentValueToBoolean(value));
|
||||||
|
}}
|
||||||
|
size="sm"
|
||||||
|
value={booleanToSegmentValue(query.hasRating)}
|
||||||
|
w="100%"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
{!disableArtistFilter && (
|
{!disableArtistFilter && (
|
||||||
<>
|
<>
|
||||||
<Divider my="md" />
|
<Divider my="md" />
|
||||||
|
|||||||
@@ -53,6 +53,11 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
|
|||||||
[searchParams],
|
[searchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const hasRating = useMemo(
|
||||||
|
() => parseBooleanParam(searchParams, FILTER_KEYS.SONG.HAS_RATING),
|
||||||
|
[searchParams],
|
||||||
|
);
|
||||||
|
|
||||||
const custom = useMemo(
|
const custom = useMemo(
|
||||||
() => parseCustomFiltersParam(searchParams, FILTER_KEYS.SONG._CUSTOM),
|
() => parseCustomFiltersParam(searchParams, FILTER_KEYS.SONG._CUSTOM),
|
||||||
[searchParams],
|
[searchParams],
|
||||||
@@ -103,6 +108,15 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
|
|||||||
[setSearchParams],
|
[setSearchParams],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setHasRating = useCallback(
|
||||||
|
(value: boolean | null) => {
|
||||||
|
setSearchParams((prev) => setSearchParam(prev, FILTER_KEYS.SONG.HAS_RATING, value), {
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[setSearchParams],
|
||||||
|
);
|
||||||
|
|
||||||
const setCustom = useCallback(
|
const setCustom = useCallback(
|
||||||
(
|
(
|
||||||
value:
|
value:
|
||||||
@@ -142,6 +156,7 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
|
|||||||
[FILTER_KEYS.SONG.ARTIST_IDS]: null,
|
[FILTER_KEYS.SONG.ARTIST_IDS]: null,
|
||||||
[FILTER_KEYS.SONG.FAVORITE]: null,
|
[FILTER_KEYS.SONG.FAVORITE]: null,
|
||||||
[FILTER_KEYS.SONG.GENRE_ID]: null,
|
[FILTER_KEYS.SONG.GENRE_ID]: null,
|
||||||
|
[FILTER_KEYS.SONG.HAS_RATING]: null,
|
||||||
[FILTER_KEYS.SONG.MAX_YEAR]: null,
|
[FILTER_KEYS.SONG.MAX_YEAR]: null,
|
||||||
[FILTER_KEYS.SONG.MIN_YEAR]: null,
|
[FILTER_KEYS.SONG.MIN_YEAR]: null,
|
||||||
},
|
},
|
||||||
@@ -160,10 +175,22 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
|
|||||||
[FILTER_KEYS.SONG.ARTIST_IDS]: artistIds ?? undefined,
|
[FILTER_KEYS.SONG.ARTIST_IDS]: artistIds ?? undefined,
|
||||||
[FILTER_KEYS.SONG.FAVORITE]: favorite ?? undefined,
|
[FILTER_KEYS.SONG.FAVORITE]: favorite ?? undefined,
|
||||||
[FILTER_KEYS.SONG.GENRE_ID]: genreId ?? undefined,
|
[FILTER_KEYS.SONG.GENRE_ID]: genreId ?? undefined,
|
||||||
|
[FILTER_KEYS.SONG.HAS_RATING]: hasRating ?? undefined,
|
||||||
[FILTER_KEYS.SONG.MAX_YEAR]: maxYear ?? undefined,
|
[FILTER_KEYS.SONG.MAX_YEAR]: maxYear ?? undefined,
|
||||||
[FILTER_KEYS.SONG.MIN_YEAR]: minYear ?? undefined,
|
[FILTER_KEYS.SONG.MIN_YEAR]: minYear ?? undefined,
|
||||||
}),
|
}),
|
||||||
[searchTerm, sortBy, sortOrder, custom, artistIds, favorite, genreId, maxYear, minYear],
|
[
|
||||||
|
searchTerm,
|
||||||
|
sortBy,
|
||||||
|
sortOrder,
|
||||||
|
custom,
|
||||||
|
artistIds,
|
||||||
|
favorite,
|
||||||
|
genreId,
|
||||||
|
hasRating,
|
||||||
|
maxYear,
|
||||||
|
minYear,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -173,6 +200,7 @@ export const useSongListFilters = (listKey?: ItemListKey) => {
|
|||||||
setCustom,
|
setCustom,
|
||||||
setFavorite,
|
setFavorite,
|
||||||
setGenreId,
|
setGenreId,
|
||||||
|
setHasRating,
|
||||||
setMaxYear,
|
setMaxYear,
|
||||||
setMinYear,
|
setMinYear,
|
||||||
setSearchTerm,
|
setSearchTerm,
|
||||||
|
|||||||
@@ -580,6 +580,7 @@ const songListParameters = paginationParameters.extend({
|
|||||||
artist_id: z.array(z.string()).optional(),
|
artist_id: z.array(z.string()).optional(),
|
||||||
artists_id: z.array(z.string()).optional(),
|
artists_id: z.array(z.string()).optional(),
|
||||||
genre_id: z.array(z.string()).optional(),
|
genre_id: z.array(z.string()).optional(),
|
||||||
|
has_rating: z.boolean().optional(),
|
||||||
library_id: z.array(z.string()).optional(),
|
library_id: z.array(z.string()).optional(),
|
||||||
path: z.string().optional(),
|
path: z.string().optional(),
|
||||||
starred: z.boolean().optional(),
|
starred: z.boolean().optional(),
|
||||||
|
|||||||
@@ -623,6 +623,7 @@ export interface SongListQuery extends BaseQuery<SongListSort> {
|
|||||||
artistIds?: string[];
|
artistIds?: string[];
|
||||||
favorite?: boolean;
|
favorite?: boolean;
|
||||||
genreIds?: string[];
|
genreIds?: string[];
|
||||||
|
hasRating?: boolean;
|
||||||
imageSize?: number;
|
imageSize?: number;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
maxYear?: number;
|
maxYear?: number;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Should follow a strict naming convention: "<FEATURE GROUP>_<FEATURE NAME>"
|
// Should follow a strict naming convention: "<FEATURE GROUP>_<FEATURE NAME>"
|
||||||
// For example: <FEATURE GROUP>: "Playlists", <FEATURE NAME>: "Smart" = "PLAYLISTS_SMART"
|
// For example: <FEATURE GROUP>: "Playlists", <FEATURE NAME>: "Smart" = "PLAYLISTS_SMART"
|
||||||
export enum ServerFeature {
|
export enum ServerFeature {
|
||||||
|
ALBUM_YES_NO_RATING_FILTER = 'albumYesNoRatingFilter',
|
||||||
BFR = 'bfr',
|
BFR = 'bfr',
|
||||||
LYRICS_MULTIPLE_STRUCTURED = 'lyricsMultipleStructured',
|
LYRICS_MULTIPLE_STRUCTURED = 'lyricsMultipleStructured',
|
||||||
LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured',
|
LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured',
|
||||||
@@ -13,6 +14,7 @@ export enum ServerFeature {
|
|||||||
SIMILAR_SONGS_MUSIC_FOLDER = 'similarSongsMusicFolder',
|
SIMILAR_SONGS_MUSIC_FOLDER = 'similarSongsMusicFolder',
|
||||||
TAGS = 'tags',
|
TAGS = 'tags',
|
||||||
TRACK_ALBUM_ARTIST_SEARCH = 'trackAlbumArtistSearch',
|
TRACK_ALBUM_ARTIST_SEARCH = 'trackAlbumArtistSearch',
|
||||||
|
TRACK_YES_NO_RATING_FILTER = 'trackYesNoRatingFilter',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ServerFeatures = Partial<Record<ServerFeature, number[]>>;
|
export type ServerFeatures = Partial<Record<ServerFeature, number[]>>;
|
||||||
|
|||||||
Reference in New Issue
Block a user