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
@@ -7,6 +7,13 @@ import { useTranslation } from 'react-i18next';
import { queryKeys } from '/@/renderer/api/query-keys';
import { albumQueries } from '/@/renderer/features/albums/api/album-api';
import { artistsQueries } from '/@/renderer/features/artists/api/artists-api';
import {
getAlbumArtistSongsById,
getAlbumSongsById,
getGenreSongsById,
getPlaylistSongsById,
getSongsByFolder,
} from '/@/renderer/features/player/utils';
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
import { useCreateFavorite } from '/@/renderer/features/shared/mutations/create-favorite-mutation';
import { useDeleteFavorite } from '/@/renderer/features/shared/mutations/delete-favorite-mutation';
@@ -28,9 +35,6 @@ import {
PlaylistSongListResponse,
QueueSong,
Song,
SongListResponse,
SongListSort,
SortOrder,
} from '/@/shared/types/domain-types';
import { Play, PlayerRepeat, PlayerShuffle } from '/@/shared/types/types';
@@ -911,88 +915,57 @@ export async function fetchSongsByItemType(
switch (args.itemType) {
case LibraryItem.ALBUM: {
const promises: Promise<SongListResponse>[] = [];
for (const id of args.id) {
promises.push(
queryClient.fetchQuery({
...songsQueries.list({
query: {
albumIds: [id],
sortBy: SongListSort.ID,
sortOrder: SortOrder.ASC,
startIndex: 0,
...args.params,
},
serverId: serverId,
}),
gcTime: 0,
staleTime: 0,
}),
);
}
const results = await Promise.all(promises);
songs.push(...results.flatMap((r) => r.items));
const albumSongsResponse = await getAlbumSongsById({
id: args.id,
query: args.params,
queryClient,
serverId,
});
songs.push(...albumSongsResponse.items);
break;
}
case LibraryItem.ALBUM_ARTIST: {
const albumArtistSongsResponse = await getAlbumArtistSongsById({
id: args.id,
query: args.params,
queryClient,
serverId,
});
songs.push(...albumArtistSongsResponse.items);
break;
}
case LibraryItem.ALBUM_ARTIST:
case LibraryItem.ARTIST: {
const promises: Promise<SongListResponse>[] = [];
for (const id of args.id) {
promises.push(
queryClient.fetchQuery({
...songsQueries.list({
query: {
albumArtistIds: [id],
limit: -1,
sortBy: SongListSort.ID,
sortOrder: SortOrder.ASC,
startIndex: 0,
...args.params,
},
serverId: serverId,
}),
gcTime: 0,
staleTime: 0,
}),
);
}
const results = await Promise.all(promises);
songs.push(...results.flatMap((r) => r.items));
const artistSongsResponse = await getAlbumArtistSongsById({
id: args.id,
query: args.params,
queryClient,
serverId,
});
songs.push(...artistSongsResponse.items);
break;
}
case LibraryItem.FOLDER: {
const folderSongsResponse = await getSongsByFolder({
id: args.id,
query: args.params,
queryClient,
serverId,
});
songs.push(...folderSongsResponse.items);
break;
}
case LibraryItem.GENRE: {
const promises: Promise<SongListResponse>[] = [];
for (const id of args.id) {
promises.push(
queryClient.fetchQuery({
...songsQueries.list({
query: {
genreIds: [id],
limit: -1,
sortBy: SongListSort.ID,
sortOrder: SortOrder.ASC,
startIndex: 0,
...args.params,
},
serverId: serverId,
}),
gcTime: 0,
staleTime: 0,
}),
);
}
const results = await Promise.all(promises);
songs.push(...results.flatMap((r) => r.items));
const genreSongsResponse = await getGenreSongsById({
id: args.id,
query: args.params,
queryClient,
serverId,
});
songs.push(...genreSongsResponse.items);
break;
}
@@ -1001,22 +974,16 @@ export async function fetchSongsByItemType(
for (const id of args.id) {
promises.push(
queryClient.fetchQuery({
...playlistsQueries.songList({
query: {
id: id,
...args.params,
},
serverId: serverId,
}),
gcTime: 0,
staleTime: 0,
getPlaylistSongsById({
id,
query: args.params,
queryClient,
serverId,
}),
);
}
const results = await Promise.all(promises);
songs.push(...results.flatMap((r) => r.items));
break;
}
+84 -29
View File
@@ -2,11 +2,12 @@ import { QueryClient } from '@tanstack/react-query';
import { api } from '/@/renderer/api';
import { queryKeys } from '/@/renderer/api/query-keys';
import { folderQueries } from '/@/renderer/features/folders/api/folder-api';
import { sortSongList } from '/@/shared/api/utils';
import {
PlaylistSongListQuery,
PlaylistSongListQueryClientSide,
ServerListItem,
Song,
SongDetailQuery,
SongListQuery,
SongListResponse,
@@ -18,22 +19,22 @@ export const getPlaylistSongsById = async (args: {
id: string;
query?: Partial<PlaylistSongListQueryClientSide>;
queryClient: QueryClient;
server: ServerListItem;
serverId: string;
}) => {
const { id, query, queryClient, server } = args;
const { id, query, queryClient, serverId } = args;
const queryFilter: PlaylistSongListQuery = {
id,
};
const queryKey = queryKeys.playlists.songList(server?.id, id);
const queryKey = queryKeys.playlists.songList(serverId, id);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) =>
api.controller.getPlaylistSongList({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,
@@ -58,9 +59,9 @@ export const getAlbumSongsById = async (args: {
orderByIds?: boolean;
query?: Partial<SongListQuery>;
queryClient: QueryClient;
server: ServerListItem;
serverId: string;
}) => {
const { id, query, queryClient, server } = args;
const { id, query, queryClient, serverId } = args;
const queryFilter: SongListQuery = {
albumIds: id,
@@ -70,14 +71,14 @@ export const getAlbumSongsById = async (args: {
...query,
};
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
const queryKey = queryKeys.songs.list(serverId, queryFilter);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) =>
api.controller.getSongList({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,
@@ -94,9 +95,9 @@ export const getGenreSongsById = async (args: {
orderByIds?: boolean;
query?: Partial<SongListQuery>;
queryClient: QueryClient;
server: null | ServerListItem;
serverId: string;
}) => {
const { id, query, queryClient, server } = args;
const { id, query, queryClient, serverId } = args;
const data: SongListResponse = {
items: [],
@@ -112,14 +113,14 @@ export const getGenreSongsById = async (args: {
...query,
};
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
const queryKey = queryKeys.songs.list(serverId, queryFilter);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) =>
api.controller.getSongList({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,
@@ -142,9 +143,9 @@ export const getAlbumArtistSongsById = async (args: {
orderByIds?: boolean;
query?: Partial<SongListQuery>;
queryClient: QueryClient;
server: ServerListItem;
serverId: string;
}) => {
const { id, query, queryClient, server } = args;
const { id, query, queryClient, serverId } = args;
const queryFilter: SongListQuery = {
albumArtistIds: id || [],
@@ -154,14 +155,14 @@ export const getAlbumArtistSongsById = async (args: {
...query,
};
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
const queryKey = queryKeys.songs.list(serverId, queryFilter);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) =>
api.controller.getSongList({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,
@@ -177,9 +178,9 @@ export const getArtistSongsById = async (args: {
id: string[];
query?: Partial<SongListQuery>;
queryClient: QueryClient;
server: ServerListItem;
serverId: string;
}) => {
const { id, query, queryClient, server } = args;
const { id, query, queryClient, serverId } = args;
const queryFilter: SongListQuery = {
artistIds: id,
@@ -189,14 +190,14 @@ export const getArtistSongsById = async (args: {
...query,
};
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
const queryKey = queryKeys.songs.list(serverId, queryFilter);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) =>
api.controller.getSongList({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,
@@ -211,9 +212,9 @@ export const getArtistSongsById = async (args: {
export const getSongsByQuery = async (args: {
query?: Partial<SongListQuery>;
queryClient: QueryClient;
server: ServerListItem;
serverId: string;
}) => {
const { query, queryClient, server } = args;
const { query, queryClient, serverId } = args;
const queryFilter: SongListQuery = {
sortBy: SongListSort.ALBUM,
@@ -222,14 +223,14 @@ export const getSongsByQuery = async (args: {
...query,
};
const queryKey = queryKeys.songs.list(server?.id, queryFilter);
const queryKey = queryKeys.songs.list(serverId, queryFilter);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) => {
return api.controller.getSongList({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,
@@ -242,23 +243,77 @@ export const getSongsByQuery = async (args: {
return res;
};
export const getSongsByFolder = async (args: {
id: string[];
orderByIds?: boolean;
query?: Partial<SongListQuery>;
queryClient: QueryClient;
serverId: string;
}) => {
const { id, queryClient, serverId } = args;
const collectSongsFromFolder = async (folderId: string): Promise<Song[]> => {
const folderSongs: Song[] = [];
const folder = await queryClient.fetchQuery({
...folderQueries.folder({
query: {
id: folderId,
sortBy: SongListSort.ID,
sortOrder: SortOrder.ASC,
},
serverId,
}),
gcTime: 0,
staleTime: 0,
});
if (folder.children?.songs) {
folderSongs.push(...folder.children.songs);
}
if (folder.children?.folders) {
for (const subFolder of folder.children.folders) {
const subFolderSongs = await collectSongsFromFolder(subFolder.id);
folderSongs.push(...subFolderSongs);
}
}
return folderSongs;
};
const data: SongListResponse = {
items: [],
startIndex: 0,
totalRecordCount: 0,
};
// Process folders sequentially to maintain order
for (const folderId of id) {
const folderSongs = await collectSongsFromFolder(folderId);
data.items.push(...folderSongs);
data.totalRecordCount = (data.totalRecordCount || 0) + folderSongs.length;
}
return data;
};
export const getSongById = async (args: {
id: string;
queryClient: QueryClient;
server: ServerListItem;
serverId: string;
}): Promise<SongListResponse> => {
const { id, queryClient, server } = args;
const { id, queryClient, serverId } = args;
const queryFilter: SongDetailQuery = { id };
const queryKey = queryKeys.songs.detail(server?.id, queryFilter);
const queryKey = queryKeys.songs.detail(serverId, queryFilter);
const res = await queryClient.fetchQuery({
gcTime: 1000 * 60,
queryFn: async ({ signal }) =>
api.controller.getSongDetail({
apiClientProps: {
serverId: server?.id || '',
serverId,
signal,
},
query: queryFilter,