mirror of
https://github.com/antebudimir/feishin.git
synced 2026-01-01 10:23:33 +00:00
Add initial genre list support
This commit is contained in:
parent
4d5085f230
commit
8029712b55
25 changed files with 968 additions and 41 deletions
|
|
@ -15,6 +15,10 @@ export interface JFGenreListResponse extends JFBasePaginatedResponse {
|
|||
|
||||
export type JFGenreList = JFGenreListResponse;
|
||||
|
||||
export enum JFGenreListSort {
|
||||
NAME = 'Name,SortName',
|
||||
}
|
||||
|
||||
export type JFAlbumArtistDetailResponse = JFAlbumArtist;
|
||||
|
||||
export type JFAlbumArtistDetail = JFAlbumArtistDetailResponse;
|
||||
|
|
|
|||
|
|
@ -108,6 +108,7 @@ export const contract = c.router({
|
|||
getGenreList: {
|
||||
method: 'GET',
|
||||
path: 'genres',
|
||||
query: jfType._parameters.genreList,
|
||||
responses: {
|
||||
200: jfType._response.genreList,
|
||||
400: jfType._response.error,
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import {
|
|||
RandomSongListArgs,
|
||||
LyricsArgs,
|
||||
LyricsResponse,
|
||||
genreListSortMap,
|
||||
} from '/@/renderer/api/types';
|
||||
import { jfApiClient } from '/@/renderer/api/jellyfin/jellyfin-api';
|
||||
import { jfNormalize } from './jellyfin-normalize';
|
||||
|
|
@ -116,9 +117,16 @@ const getMusicFolderList = async (args: MusicFolderListArgs): Promise<MusicFolde
|
|||
};
|
||||
|
||||
const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> => {
|
||||
const { apiClientProps } = args;
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
const res = await jfApiClient(apiClientProps).getGenreList();
|
||||
const res = await jfApiClient(apiClientProps).getGenreList({
|
||||
query: {
|
||||
SearchTerm: query?.searchTerm,
|
||||
SortBy: genreListSortMap.jellyfin[query.sortBy] || 'Name,SortName',
|
||||
SortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
StartIndex: query.startIndex,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get genre list');
|
||||
|
|
@ -126,8 +134,8 @@ const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> =>
|
|||
|
||||
return {
|
||||
items: res.body.Items.map(jfNormalize.genre),
|
||||
startIndex: 0,
|
||||
totalRecordCount: res.body?.Items?.length || 0,
|
||||
startIndex: query.startIndex || 0,
|
||||
totalRecordCount: res.body?.TotalRecordCount || 0,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -304,10 +304,21 @@ const genre = z.object({
|
|||
Type: z.string(),
|
||||
});
|
||||
|
||||
const genreList = z.object({
|
||||
const genreList = pagination.extend({
|
||||
Items: z.array(genre),
|
||||
});
|
||||
|
||||
const genreListSort = {
|
||||
NAME: 'Name,SortName',
|
||||
} as const;
|
||||
|
||||
const genreListParameters = paginationParameters.merge(
|
||||
baseParameters.extend({
|
||||
SearchTerm: z.string().optional(),
|
||||
SortBy: z.nativeEnum(genreListSort).optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
const musicFolder = z.object({
|
||||
BackdropImageTags: z.array(z.string()),
|
||||
ChannelId: z.null(),
|
||||
|
|
@ -352,7 +363,7 @@ const playlist = z.object({
|
|||
UserData: userData,
|
||||
});
|
||||
|
||||
const jfPlaylistListSort = {
|
||||
const playlistListSort = {
|
||||
ALBUM_ARTIST: 'AlbumArtist,SortName',
|
||||
DURATION: 'Runtime',
|
||||
NAME: 'SortName',
|
||||
|
|
@ -363,7 +374,7 @@ const jfPlaylistListSort = {
|
|||
const playlistListParameters = paginationParameters.merge(
|
||||
baseParameters.extend({
|
||||
IncludeItemTypes: z.literal('Playlist'),
|
||||
SortBy: z.nativeEnum(jfPlaylistListSort).optional(),
|
||||
SortBy: z.nativeEnum(playlistListSort).optional(),
|
||||
}),
|
||||
);
|
||||
|
||||
|
|
@ -461,7 +472,7 @@ const album = z.object({
|
|||
UserData: userData.optional(),
|
||||
});
|
||||
|
||||
const jfAlbumListSort = {
|
||||
const albumListSort = {
|
||||
ALBUM_ARTIST: 'AlbumArtist,SortName',
|
||||
COMMUNITY_RATING: 'CommunityRating,SortName',
|
||||
CRITIC_RATING: 'CriticRating,SortName',
|
||||
|
|
@ -479,7 +490,7 @@ const albumListParameters = paginationParameters.merge(
|
|||
IncludeItemTypes: z.literal('MusicAlbum'),
|
||||
IsFavorite: z.boolean().optional(),
|
||||
SearchTerm: z.string().optional(),
|
||||
SortBy: z.nativeEnum(jfAlbumListSort).optional(),
|
||||
SortBy: z.nativeEnum(albumListSort).optional(),
|
||||
Tags: z.string().optional(),
|
||||
Years: z.string().optional(),
|
||||
}),
|
||||
|
|
@ -489,7 +500,7 @@ const albumList = pagination.extend({
|
|||
Items: z.array(album),
|
||||
});
|
||||
|
||||
const jfAlbumArtistListSort = {
|
||||
const albumArtistListSort = {
|
||||
ALBUM: 'Album,SortName',
|
||||
DURATION: 'Runtime,AlbumArtist,Album,SortName',
|
||||
NAME: 'Name,SortName',
|
||||
|
|
@ -502,7 +513,7 @@ const albumArtistListParameters = paginationParameters.merge(
|
|||
baseParameters.extend({
|
||||
Filters: z.string().optional(),
|
||||
Genres: z.string().optional(),
|
||||
SortBy: z.nativeEnum(jfAlbumArtistListSort).optional(),
|
||||
SortBy: z.nativeEnum(albumArtistListSort).optional(),
|
||||
Years: z.string().optional(),
|
||||
}),
|
||||
);
|
||||
|
|
@ -515,7 +526,7 @@ const similarArtistListParameters = baseParameters.extend({
|
|||
Limit: z.number().optional(),
|
||||
});
|
||||
|
||||
const jfSongListSort = {
|
||||
const songListSort = {
|
||||
ALBUM: 'Album,SortName',
|
||||
ALBUM_ARTIST: 'AlbumArtist,Album,SortName',
|
||||
ARTIST: 'Artist,Album,SortName',
|
||||
|
|
@ -539,7 +550,7 @@ const songListParameters = paginationParameters.merge(
|
|||
Genres: z.string().optional(),
|
||||
IsFavorite: z.boolean().optional(),
|
||||
SearchTerm: z.string().optional(),
|
||||
SortBy: z.nativeEnum(jfSongListSort).optional(),
|
||||
SortBy: z.nativeEnum(songListSort).optional(),
|
||||
Tags: z.string().optional(),
|
||||
Years: z.string().optional(),
|
||||
}),
|
||||
|
|
@ -642,9 +653,14 @@ const lyrics = z.object({
|
|||
|
||||
export const jfType = {
|
||||
_enum: {
|
||||
albumArtistList: albumArtistListSort,
|
||||
albumList: albumListSort,
|
||||
collection: jfCollection,
|
||||
external: jfExternal,
|
||||
genreList: genreListSort,
|
||||
image: jfImage,
|
||||
playlistList: playlistListSort,
|
||||
songList: songListSort,
|
||||
},
|
||||
_parameters: {
|
||||
addToPlaylist: addToPlaylistParameters,
|
||||
|
|
@ -656,6 +672,7 @@ export const jfType = {
|
|||
createPlaylist: createPlaylistParameters,
|
||||
deletePlaylist: deletePlaylistParameters,
|
||||
favorite: favoriteParameters,
|
||||
genreList: genreListParameters,
|
||||
musicFolderList: musicFolderListParameters,
|
||||
playlistDetail: playlistDetailParameters,
|
||||
playlistList: playlistListParameters,
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ export const contract = c.router({
|
|||
getGenreList: {
|
||||
method: 'GET',
|
||||
path: 'genre',
|
||||
query: ndType._parameters.genreList,
|
||||
responses: {
|
||||
200: resultWithHeaders(ndType._response.genreList),
|
||||
500: resultWithHeaders(ndType._response.error),
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
PlaylistSongListResponse,
|
||||
RemoveFromPlaylistResponse,
|
||||
RemoveFromPlaylistArgs,
|
||||
genreListSortMap,
|
||||
} from '../types';
|
||||
import { ndApiClient } from '/@/renderer/api/navidrome/navidrome-api';
|
||||
import { ndNormalize } from '/@/renderer/api/navidrome/navidrome-normalize';
|
||||
|
|
@ -94,9 +95,17 @@ const getUserList = async (args: UserListArgs): Promise<UserListResponse> => {
|
|||
};
|
||||
|
||||
const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> => {
|
||||
const { apiClientProps } = args;
|
||||
const { query, apiClientProps } = args;
|
||||
|
||||
const res = await ndApiClient(apiClientProps).getGenreList({});
|
||||
const res = await ndApiClient(apiClientProps).getGenreList({
|
||||
query: {
|
||||
_end: query.startIndex + (query.limit || 0),
|
||||
_order: sortOrderMap.navidrome[query.sortOrder],
|
||||
_sort: genreListSortMap.navidrome[query.sortBy],
|
||||
_start: query.startIndex,
|
||||
name: query.searchTerm,
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get genre list');
|
||||
|
|
@ -104,7 +113,7 @@ const getGenreList = async (args: GenreListArgs): Promise<GenreListResponse> =>
|
|||
|
||||
return {
|
||||
items: res.body.data,
|
||||
startIndex: 0,
|
||||
startIndex: query.startIndex || 0,
|
||||
totalRecordCount: Number(res.body.headers.get('x-total-count') || 0),
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -52,6 +52,16 @@ const genre = z.object({
|
|||
name: z.string(),
|
||||
});
|
||||
|
||||
const genreListSort = {
|
||||
NAME: 'name',
|
||||
SONG_COUNT: 'songCount',
|
||||
} as const;
|
||||
|
||||
const genreListParameters = paginationParameters.extend({
|
||||
_sort: z.nativeEnum(genreListSort).optional(),
|
||||
name: z.string().optional(),
|
||||
});
|
||||
|
||||
const genreList = z.array(genre);
|
||||
|
||||
const albumArtist = z.object({
|
||||
|
|
@ -322,6 +332,7 @@ export const ndType = {
|
|||
_enum: {
|
||||
albumArtistList: ndAlbumArtistListSort,
|
||||
albumList: ndAlbumListSort,
|
||||
genreList: genreListSort,
|
||||
playlistList: ndPlaylistListSort,
|
||||
songList: ndSongListSort,
|
||||
userList: ndUserListSort,
|
||||
|
|
@ -332,6 +343,7 @@ export const ndType = {
|
|||
albumList: albumListParameters,
|
||||
authenticate: authenticateParameters,
|
||||
createPlaylist: createPlaylistParameters,
|
||||
genreList: genreListParameters,
|
||||
playlistList: playlistListParameters,
|
||||
removeFromPlaylist: removeFromPlaylistParameters,
|
||||
songList: songListParameters,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import type {
|
|||
RandomSongListQuery,
|
||||
LyricsQuery,
|
||||
LyricSearchQuery,
|
||||
GenreListQuery,
|
||||
} from './types';
|
||||
|
||||
export const splitPaginatedQuery = (key: any) => {
|
||||
|
|
@ -106,7 +107,18 @@ export const queryKeys: Record<
|
|||
root: (serverId: string) => [serverId, 'artists'] as const,
|
||||
},
|
||||
genres: {
|
||||
list: (serverId: string) => [serverId, 'genres', 'list'] as const,
|
||||
list: (serverId: string, query?: GenreListQuery) => {
|
||||
const { pagination, filter } = splitPaginatedQuery(query);
|
||||
if (query && pagination) {
|
||||
return [serverId, 'genres', 'list', filter, pagination] as const;
|
||||
}
|
||||
|
||||
if (query) {
|
||||
return [serverId, 'genres', 'list', filter] as const;
|
||||
}
|
||||
|
||||
return [serverId, 'genres', 'list'] as const;
|
||||
},
|
||||
root: (serverId: string) => [serverId, 'genres'] as const,
|
||||
},
|
||||
musicFolders: {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from 'zod';
|
||||
import { jfType } from './jellyfin/jellyfin-types';
|
||||
import {
|
||||
JFSortOrder,
|
||||
JFAlbumListSort,
|
||||
|
|
@ -6,8 +7,9 @@ import {
|
|||
JFAlbumArtistListSort,
|
||||
JFArtistListSort,
|
||||
JFPlaylistListSort,
|
||||
JFGenreListSort,
|
||||
} from './jellyfin.types';
|
||||
import { jfType } from './jellyfin/jellyfin-types';
|
||||
import { ndType } from './navidrome/navidrome-types';
|
||||
import {
|
||||
NDSortOrder,
|
||||
NDOrder,
|
||||
|
|
@ -16,13 +18,14 @@ import {
|
|||
NDPlaylistListSort,
|
||||
NDSongListSort,
|
||||
NDUserListSort,
|
||||
NDGenreListSort,
|
||||
} from './navidrome.types';
|
||||
import { ndType } from './navidrome/navidrome-types';
|
||||
|
||||
export enum LibraryItem {
|
||||
ALBUM = 'album',
|
||||
ALBUM_ARTIST = 'albumArtist',
|
||||
ARTIST = 'artist',
|
||||
GENRE = 'genre',
|
||||
PLAYLIST = 'playlist',
|
||||
SONG = 'song',
|
||||
}
|
||||
|
|
@ -292,7 +295,40 @@ export type GenreListResponse = BasePaginatedResponse<Genre[]> | null | undefine
|
|||
|
||||
export type GenreListArgs = { query: GenreListQuery } & BaseEndpointArgs;
|
||||
|
||||
export type GenreListQuery = null;
|
||||
export enum GenreListSort {
|
||||
NAME = 'name',
|
||||
}
|
||||
|
||||
export type GenreListQuery = {
|
||||
_custom?: {
|
||||
jellyfin?: null;
|
||||
navidrome?: null;
|
||||
};
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
searchTerm?: string;
|
||||
sortBy: GenreListSort;
|
||||
sortOrder: SortOrder;
|
||||
startIndex: number;
|
||||
};
|
||||
|
||||
type GenreListSortMap = {
|
||||
jellyfin: Record<GenreListSort, JFGenreListSort | undefined>;
|
||||
navidrome: Record<GenreListSort, NDGenreListSort | undefined>;
|
||||
subsonic: Record<UserListSort, undefined>;
|
||||
};
|
||||
|
||||
export const genreListSortMap: GenreListSortMap = {
|
||||
jellyfin: {
|
||||
name: JFGenreListSort.NAME,
|
||||
},
|
||||
navidrome: {
|
||||
name: NDGenreListSort.NAME,
|
||||
},
|
||||
subsonic: {
|
||||
name: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
// Album List
|
||||
export type AlbumListResponse = BasePaginatedResponse<Album[]> | null | undefined;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue