mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
feat: playlist management enhancements & UX improvements (#168)
- Multi-selection for Albums, Artists, Playlists with context menu bulk-add - Collapsible playlist section in sidebar - Infinite scroll on Artists page (IntersectionObserver) - Submenu flip-up on viewport overflow - Remove from Playlist in context menu - All 8 locales synced Fixes applied: - title= → data-tooltip on playlist toggle button (CLAUDE.md) - Hardcoded Spanish aria-label → i18n (sidebar.expandPlaylists/collapsePlaylists) - AddToPlaylistSubmenu fetches playlists on first open if store empty Co-Authored-By: kveld9 <kveld9@users.noreply.github.com> Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -174,10 +174,12 @@ interface PlayerState {
|
||||
x: number;
|
||||
y: number;
|
||||
item: any;
|
||||
type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song' | null;
|
||||
type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song' | 'playlist' | 'multi-album' | 'multi-artist' | 'multi-playlist' | null;
|
||||
queueIndex?: number;
|
||||
playlistId?: string;
|
||||
playlistSongIndex?: number;
|
||||
};
|
||||
openContextMenu: (x: number, y: number, item: any, type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song', queueIndex?: number) => void;
|
||||
openContextMenu: (x: number, y: number, item: any, type: 'song' | 'album' | 'artist' | 'queue-item' | 'album-song' | 'playlist' | 'multi-album' | 'multi-artist' | 'multi-playlist', queueIndex?: number, playlistId?: string, playlistSongIndex?: number) => void;
|
||||
closeContextMenu: () => void;
|
||||
|
||||
songInfoModal: { isOpen: boolean; songId: string | null };
|
||||
@@ -729,8 +731,8 @@ export const usePlayerStore = create<PlayerState>()(
|
||||
repeatMode: 'off',
|
||||
contextMenu: { isOpen: false, x: 0, y: 0, item: null, type: null },
|
||||
|
||||
openContextMenu: (x, y, item, type, queueIndex) => set({
|
||||
contextMenu: { isOpen: true, x, y, item, type, queueIndex },
|
||||
openContextMenu: (x, y, item, type, queueIndex, playlistId, playlistSongIndex) => set({
|
||||
contextMenu: { isOpen: true, x, y, item, type, queueIndex, playlistId, playlistSongIndex },
|
||||
}),
|
||||
closeContextMenu: () => set(state => ({
|
||||
contextMenu: { ...state.contextMenu, isOpen: false },
|
||||
|
||||
@@ -1,22 +1,59 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { getPlaylists, createPlaylist as apiCreatePlaylist, SubsonicPlaylist } from '../api/subsonic';
|
||||
|
||||
interface PlaylistStore {
|
||||
recentIds: string[];
|
||||
playlists: SubsonicPlaylist[];
|
||||
playlistsLoading: boolean;
|
||||
lastModified: Record<string, number>;
|
||||
touchPlaylist: (id: string) => void;
|
||||
removeId: (id: string) => void;
|
||||
fetchPlaylists: () => Promise<void>;
|
||||
createPlaylist: (name: string, songIds?: string[]) => Promise<SubsonicPlaylist | null>;
|
||||
addPlaylist: (playlist: SubsonicPlaylist) => void;
|
||||
}
|
||||
|
||||
export const usePlaylistStore = create<PlaylistStore>()(
|
||||
persist(
|
||||
(set) => ({
|
||||
(set, get) => ({
|
||||
recentIds: [],
|
||||
playlists: [],
|
||||
playlistsLoading: false,
|
||||
lastModified: {},
|
||||
touchPlaylist: (id) =>
|
||||
set((s) => ({
|
||||
recentIds: [id, ...s.recentIds.filter((x) => x !== id)].slice(0, 50),
|
||||
lastModified: { ...s.lastModified, [id]: Date.now() },
|
||||
})),
|
||||
removeId: (id) =>
|
||||
set((s) => ({ recentIds: s.recentIds.filter((x) => x !== id) })),
|
||||
fetchPlaylists: async () => {
|
||||
set({ playlistsLoading: true });
|
||||
try {
|
||||
const playlists = await getPlaylists();
|
||||
set({ playlists, playlistsLoading: false });
|
||||
} catch {
|
||||
set({ playlistsLoading: false });
|
||||
}
|
||||
},
|
||||
createPlaylist: async (name: string, songIds?: string[]) => {
|
||||
try {
|
||||
const playlist = await apiCreatePlaylist(name, songIds);
|
||||
set((s) => ({
|
||||
playlists: [...s.playlists, playlist],
|
||||
recentIds: [playlist.id, ...s.recentIds.filter((x) => x !== playlist.id)].slice(0, 50),
|
||||
}));
|
||||
return playlist;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
addPlaylist: (playlist) => {
|
||||
set((s) => ({
|
||||
playlists: [...s.playlists, playlist],
|
||||
}));
|
||||
},
|
||||
}),
|
||||
{ name: 'psysonic_playlists_recent' }
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user