mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
d0a270d90a
Three-cut cluster pulling tightly-scoped state + memo islands out of PlaylistDetail.tsx as custom hooks. 659 → 565 LOC (−94). Each hook returns the same names the page already used, so call sites stay identical. usePlaylistCovers(songs, customCoverId) — the 2×2 cover quad memo, the four cover-quad URLs (with their stable cache keys to avoid the buildCoverArtUrl-salt re-render loop documented in the comment), the customCover fetch URL + cache key, and the blurred background URL going through useCachedUrl. Returns coverQuadUrls / customCoverFetchUrl / customCoverCacheKey / resolvedBgUrl. The page no longer imports buildCoverArtUrl / coverArtCacheKey / useCachedUrl directly. usePlaylistSelection(songs, setSongs, savePlaylist) — selectedIds / lastSelectedIdx state + toggleSelect (with shift-range support) + allSelected + toggleAll + bulkRemove (savePlaylist + setSongs + clears selection). Returns the same shape the tracklist props already destructured. savePlaylist moves a few lines up in PlaylistDetail so the hook can be called with it. usePlaylistSuggestions(songs, playlist.id) — suggestions state + loadSuggestions (genre-weighted random pull, top 10) + the auto-load useEffect that fires on playlist change. Returns setSuggestions too so addSong on the page can still filter the just-added song out of the suggestions strip. The page drops the getRandomSongs import. Pure code move otherwise — no behaviour change.
51 lines
1.8 KiB
TypeScript
51 lines
1.8 KiB
TypeScript
import { useState } from 'react';
|
|
import type React from 'react';
|
|
import type { SubsonicSong } from '../api/subsonicTypes';
|
|
|
|
export interface PlaylistSelection {
|
|
selectedIds: Set<string>;
|
|
setSelectedIds: React.Dispatch<React.SetStateAction<Set<string>>>;
|
|
lastSelectedIdx: number | null;
|
|
allSelected: boolean;
|
|
toggleAll: () => void;
|
|
toggleSelect: (id: string, idx: number, shift: boolean) => void;
|
|
bulkRemove: () => void;
|
|
}
|
|
|
|
export function usePlaylistSelection(
|
|
songs: SubsonicSong[],
|
|
setSongs: React.Dispatch<React.SetStateAction<SubsonicSong[]>>,
|
|
savePlaylist: (updatedSongs: SubsonicSong[], prevCount?: number) => Promise<void>,
|
|
): PlaylistSelection {
|
|
const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());
|
|
const [lastSelectedIdx, setLastSelectedIdx] = useState<number | null>(null);
|
|
|
|
const toggleSelect = (id: string, idx: number, shift: boolean) => {
|
|
setSelectedIds(prev => {
|
|
const next = new Set(prev);
|
|
if (shift && lastSelectedIdx !== null) {
|
|
const from = Math.min(lastSelectedIdx, idx);
|
|
const to = Math.max(lastSelectedIdx, idx);
|
|
songs.slice(from, to + 1).forEach(s => next.add(s.id));
|
|
} else {
|
|
next.has(id) ? next.delete(id) : next.add(id);
|
|
}
|
|
return next;
|
|
});
|
|
setLastSelectedIdx(idx);
|
|
};
|
|
|
|
const allSelected = selectedIds.size === songs.length && songs.length > 0;
|
|
const toggleAll = () => setSelectedIds(allSelected ? new Set() : new Set(songs.map(s => s.id)));
|
|
|
|
const bulkRemove = () => {
|
|
const prevCount = songs.length;
|
|
const next = songs.filter(s => !selectedIds.has(s.id));
|
|
setSongs(next);
|
|
savePlaylist(next, prevCount);
|
|
setSelectedIds(new Set());
|
|
};
|
|
|
|
return { selectedIds, setSelectedIds, lastSelectedIdx, allSelected, toggleAll, toggleSelect, bulkRemove };
|
|
}
|