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
@@ -9,6 +9,7 @@ import {
getAlbumSongsById,
getGenreSongsById,
getPlaylistSongsById,
getSongsByFolder,
} from '/@/renderer/features/player/utils';
import { playlistsQueries } from '/@/renderer/features/playlists/api/playlists-api';
import { useRecentPlaylists } from '/@/renderer/features/playlists/hooks/use-recent-playlists';
@@ -97,49 +98,64 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
const getSongsByAlbum = useCallback(
async (albumId: string) => {
if (!server) return null;
return getAlbumSongsById({
id: [albumId],
queryClient,
server,
serverId,
});
},
[queryClient, server],
[queryClient, serverId],
);
const getSongsByArtist = useCallback(
async (artistId: string) => {
if (!server) return null;
return getAlbumArtistSongsById({
id: [artistId],
queryClient,
server,
serverId,
});
},
[queryClient, server],
[queryClient, serverId],
);
const getSongsByGenre = useCallback(
async (genreIds: string[]) => {
if (!server) return null;
return getGenreSongsById({
id: genreIds,
queryClient,
server,
serverId,
});
},
[queryClient, server],
[queryClient, serverId],
);
const getSongsByPlaylist = useCallback(
async (playlistId: string) => {
if (!server) return null;
return getPlaylistSongsById({
id: playlistId,
queryClient,
server,
serverId,
});
},
[queryClient, serverId],
);
const getSongsByFolderLocal = useCallback(
async (folderId: string) => {
if (!server) return null;
const songsResponse = await getSongsByFolder({
id: [folderId],
queryClient,
serverId: server.id,
});
return {
items: songsResponse.items.map((song) => song.id),
startIndex: 0,
totalRecordCount: songsResponse.items.length,
};
},
[queryClient, server],
);
@@ -173,6 +189,11 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
const songs = await getSongsByPlaylist(id);
allSongIds.push(...(songs?.items?.map((song) => song.id) || []));
}
} else if (itemType === LibraryItem.FOLDER) {
for (const id of items) {
const songs = await getSongsByFolderLocal(id);
allSongIds.push(...(songs?.items || []));
}
}
if (allSongIds.length === 0) {
@@ -213,6 +234,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
addToPlaylistMutation,
getSongsByAlbum,
getSongsByArtist,
getSongsByFolderLocal,
getSongsByGenre,
getSongsByPlaylist,
itemType,
@@ -226,6 +248,7 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
const modalProps: {
albumId?: string[];
artistId?: string[];
folderId?: string[];
genreId?: string[];
initialSelectedIds?: string[];
playlistId?: string[];
@@ -240,6 +263,9 @@ export const AddToPlaylistAction = ({ items, itemType }: AddToPlaylistActionProp
case LibraryItem.ARTIST:
modalProps.artistId = items;
break;
case LibraryItem.FOLDER:
modalProps.folderId = items;
break;
case LibraryItem.GENRE:
modalProps.genreId = items;
break;
@@ -19,6 +19,8 @@ export const ShareAction = ({ ids, itemType }: ShareActionProps) => {
return 'album';
case LibraryItem.ALBUM_ARTIST:
return 'albumArtist';
case LibraryItem.FOLDER:
return 'folder';
case LibraryItem.PLAYLIST:
return 'playlist';
case LibraryItem.SONG:
@@ -6,6 +6,7 @@ import { useParams } from 'react-router';
import { AlbumArtistContextMenu } from '/@/renderer/features/context-menu/menus/album-artist-context-menu';
import { AlbumContextMenu } from '/@/renderer/features/context-menu/menus/album-context-menu';
import { ArtistContextMenu } from '/@/renderer/features/context-menu/menus/artist-context-menu';
import { FolderContextMenu } from '/@/renderer/features/context-menu/menus/folder-context-menu';
import { GenreContextMenu } from '/@/renderer/features/context-menu/menus/genre-context-menu';
import { PlaylistContextMenu } from '/@/renderer/features/context-menu/menus/playlist-context-menu';
import { PlaylistSongContextMenu } from '/@/renderer/features/context-menu/menus/playlist-song-context-menu';
@@ -16,6 +17,7 @@ import {
Album,
AlbumArtist,
Artist,
Folder,
Genre,
LibraryItem,
Playlist,
@@ -82,6 +84,7 @@ export const ContextMenuController = createCallable<ContextMenuControllerProps,
{cmd.type === LibraryItem.ALBUM && <AlbumContextMenu {...cmd} />}
{cmd.type === LibraryItem.ALBUM_ARTIST && <AlbumArtistContextMenu {...cmd} />}
{cmd.type === LibraryItem.ARTIST && <ArtistContextMenu {...cmd} />}
{cmd.type === LibraryItem.FOLDER && <FolderContextMenu {...cmd} />}
{cmd.type === LibraryItem.GENRE && <GenreContextMenu {...cmd} />}
{cmd.type === LibraryItem.PLAYLIST && <PlaylistContextMenu {...cmd} />}
{cmd.type === LibraryItem.PLAYLIST_SONG && <PlaylistSongContextMenu {...cmd} />}
@@ -95,6 +98,7 @@ export type ContextMenuCommand =
| AlbumArtistContextMenuProps
| AlbumContextMenuProps
| ArtistContextMenuProps
| FolderContextMenuProps
| GenreContextMenuProps
| PlaylistContextMenuProps
| PlaylistSongContextMenuProps
@@ -116,6 +120,11 @@ type ArtistContextMenuProps = {
type: LibraryItem.ARTIST;
};
type FolderContextMenuProps = {
items: Folder[];
type: LibraryItem.FOLDER;
};
type GenreContextMenuProps = {
items: Genre[];
type: LibraryItem.GENRE;
@@ -0,0 +1,34 @@
import { useMemo } from 'react';
import { AddToPlaylistAction } from '/@/renderer/features/context-menu/actions/add-to-playlist-action';
import { DownloadAction } from '/@/renderer/features/context-menu/actions/download-action';
import { PlayAction } from '/@/renderer/features/context-menu/actions/play-action';
import { ShareAction } from '/@/renderer/features/context-menu/actions/share-action';
import { ContextMenu } from '/@/shared/components/context-menu/context-menu';
import { ContextMenuPreview } from '/@/shared/components/context-menu/context-menu-preview';
import { Folder, LibraryItem } from '/@/shared/types/domain-types';
interface FolderContextMenuProps {
items: Folder[];
type: LibraryItem.FOLDER;
}
export const FolderContextMenu = ({ items, type }: FolderContextMenuProps) => {
const { ids } = useMemo(() => {
const ids = items.map((item) => item.id);
return { ids };
}, [items]);
return (
<ContextMenu.Content
bottomStickyContent={<ContextMenuPreview items={items} itemType={type} />}
>
<PlayAction ids={ids} itemType={LibraryItem.FOLDER} />
<ContextMenu.Divider />
<AddToPlaylistAction items={ids} itemType={LibraryItem.FOLDER} />
<ContextMenu.Divider />
<DownloadAction ids={ids} />
<ShareAction ids={ids} itemType={LibraryItem.FOLDER} />
</ContextMenu.Content>
);
};