add folder browsing support (#315)

This commit is contained in:
jeffvli
2025-12-02 21:30:44 -08:00
parent 355257104d
commit 917bf91583
53 changed files with 2382 additions and 299 deletions
+16 -11
View File
@@ -4,6 +4,7 @@ import { jfType } from '/@/shared/api/jellyfin/jellyfin-types';
import {
Album,
AlbumArtist,
Folder,
Genre,
LibraryItem,
MusicFolder,
@@ -496,17 +497,20 @@ const normalizeGenre = (
};
};
// const normalizeFolder = (item: any) => {
// return {
// created: item.DateCreated,
// id: item.Id,
// image: getCoverArtUrl(item, 150),
// isDir: true,
// title: item.Name,
// type: Item.Folder,
// uniqueId: nanoid(),
// };
// };
const normalizeFolder = (
item: z.infer<typeof jfType._response.folder>,
server: null | ServerListItem,
): Folder => {
return {
_itemType: LibraryItem.FOLDER,
_serverId: server?.id || 'unknown',
_serverType: ServerType.JELLYFIN,
children: undefined,
id: item.Id,
name: item.Name || 'Unknown folder',
parentId: item.ParentId,
};
};
// const normalizeScanStatus = () => {
// return {
@@ -518,6 +522,7 @@ const normalizeGenre = (
export const jfNormalize = {
album: normalizeAlbum,
albumArtist: normalizeAlbumArtist,
folder: normalizeFolder,
genre: normalizeGenre,
musicFolder: normalizeMusicFolder,
playlist: normalizePlaylist,
+33 -1
View File
@@ -487,6 +487,7 @@ const song = z.object({
MediaType: z.string(),
Name: z.string(),
NormalizationGain: z.number().optional(),
ParentId: z.string().optional(),
ParentIndexNumber: z.number(),
People: participant.array().optional(),
PlaylistItemId: z.string().optional(),
@@ -495,7 +496,7 @@ const song = z.object({
ProviderIds: providerIds.optional(),
RunTimeTicks: z.number(),
ServerId: z.string(),
SortName: z.string(),
SortName: z.string().optional(),
Tags: z.string().array().optional(),
Type: z.string(),
UserData: userData.optional(),
@@ -772,6 +773,34 @@ const filters = z.object({
Years: z.number().array().optional(),
});
const folder = z.object({
BackdropImageTags: z.array(z.string()),
ChannelId: z.null(),
CollectionType: z.string(),
Id: z.string(),
ImageBlurHashes: imageBlurHashes,
ImageTags: imageTags,
IsFolder: z.boolean(),
LocationType: z.string(),
MediaType: z.string(),
Name: z.string(),
ParentId: z.string().optional(),
ServerId: z.string(),
Type: z.string(),
UserData: userData.optional(),
});
const folderList = pagination.extend({
Items: z.array(folder),
});
const folderParameters = z.object({
Fields: z.string().optional(),
ParentId: z.string().optional(),
SortBy: z.string().optional(),
SortOrder: z.enum(sortOrderValues).optional(),
});
export const jfType = {
_enum: {
albumArtistList: albumArtistListSort,
@@ -794,6 +823,7 @@ export const jfType = {
deletePlaylist: deletePlaylistParameters,
favorite: favoriteParameters,
filterList: filterListParameters,
folder: folderParameters,
genreList: genreListParameters,
musicFolderList: musicFolderListParameters,
playlistDetail: playlistDetailParameters,
@@ -819,6 +849,8 @@ export const jfType = {
error,
favorite,
filters,
folder,
folderList,
genre,
genreList,
lyrics,
@@ -5,6 +5,7 @@ import {
Album,
AlbumArtist,
ExplicitStatus,
Folder,
Genre,
LibraryItem,
Playlist,
@@ -342,9 +343,48 @@ const normalizeGenre = (
};
};
const normalizeFolder = (
item: z.infer<typeof ssType._response.directory>,
server?: null | ServerListItemWithCredential,
): Folder => {
const results = item.child?.reduce(
(acc: { folders: Folder[]; songs: Song[] }, item) => {
const isDirectory = item.isDir === true;
if (isDirectory) {
const folder = normalizeFolder(item, server);
acc.folders.push(folder);
} else {
const song = normalizeSong(item, server);
acc.songs.push(song);
}
return acc;
},
{
folders: [],
songs: [],
},
);
return {
_itemType: LibraryItem.FOLDER,
_serverId: server?.id || 'unknown',
_serverType: ServerType.SUBSONIC,
children: {
folders: results?.folders || [],
songs: results?.songs || [],
},
id: item.id.toString(),
name: item.title,
parentId: item.parent,
};
};
export const ssNormalize = {
album: normalizeAlbum,
albumArtist: normalizeAlbumArtist,
folder: normalizeFolder,
genre: normalizeGenre,
playlist: normalizePlaylist,
song: normalizeSong,
+49
View File
@@ -548,6 +548,50 @@ const albumInfo = z.object({
}),
});
const getMusicDirectoryParameters = z.object({
id: z.string(),
});
const directory = z.object({
artist: z.string().optional(),
child: z.array(song).optional(),
coverArt: z.string().optional(),
id,
isDir: z.boolean(),
parent: z.string().optional(),
title: z.string(),
});
const getMusicDirectory = z.object({
directory,
});
const getIndexes = z.object({
indexes: z.object({
child: z.array(song),
index: z
.object({
artist: z
.object({
id: z.string(),
name: z.string(),
})
.array(),
})
.array(),
shortcut: z
.object({
id: z.string(),
name: z.string(),
})
.array(),
}),
});
const getIndexesParameters = z.object({
musicFolderId: z.string().optional(),
});
export const ssType = {
_parameters: {
albumInfo: albumInfoParameters,
@@ -563,6 +607,8 @@ export const ssType = {
getArtists: getArtistsParameters,
getGenre: getGenresParameters,
getGenres: getGenresParameters,
getIndexes: getIndexesParameters,
getMusicDirectory: getMusicDirectoryParameters,
getPlaylist: getPlaylistParameters,
getPlaylists: getPlaylistsParameters,
getSong: getSongParameters,
@@ -591,12 +637,15 @@ export const ssType = {
baseResponse,
createFavorite,
createPlaylist,
directory,
genre,
getAlbum,
getAlbumList2,
getArtist,
getArtists,
getGenres,
getIndexes,
getMusicDirectory,
getPlaylist,
getPlaylists,
getSong,
+5
View File
@@ -245,6 +245,11 @@ export const sortSongsByFetchedOrder = (
fetchedIds: string[],
itemType: LibraryItem,
): Song[] => {
// For folders, songs are already in the correct order
if (itemType === LibraryItem.FOLDER) {
return songs;
}
// Group songs by the fetched ID they belong to
const songsByFetchedId = new Map<string, Song[]>();