Files
Psychotoxical-psysonic/src/utils/runPlaylistLoad.ts
T
Frank Stellmacher 16ee1ea373 refactor(playlist): G.69 — extract runPlaylistLoad + usePlaylistPreview + usePlaylistBulkPlayCallbacks + usePlaylistDerived (cluster) (#636)
Four-cut cluster pulling the remaining handler bodies + memo island
out of PlaylistDetail.tsx. 507 → 437 LOC (−70).

runPlaylistLoad — async fetch + populate flow that the load
useEffect drives: getPlaylist → filterSongsToActiveLibrary →
setPlaylist + setSongs + setCustomCoverId from playlist.coverArt +
rebuild ratings + starredSongs from each song's stored userRating /
starred flag. Takes the six setters as deps. Page keeps the
useEffect that calls it on id / lastModified change.

usePlaylistPreview — startPreview callback (dispatches into
previewStore with the 'suggestions' source) plus the unmount-time
stopPreview cleanup. Returns just { startPreview }. No props
needed.

usePlaylistBulkPlayCallbacks — wraps playPlaylistAll /
shufflePlaylistAll / enqueuePlaylistAll utilities behind three
useCallback handles with the right deps array. Takes
{ songsLength, id, tracks, touchPlaylist, playTrack, enqueue }.

usePlaylistDerived — existingIds, tracks (songToTrack), sorted +
filtered displayedSongs via getDisplayedSongs, displayedTracks (with
the cheap aliasing optimization when displayedSongs === songs),
isFiltered. Subscribes to playerStore's userRatingOverrides +
starredOverrides directly so the page no longer threads them through
the memo deps.

PlaylistDetail drops these direct imports — they followed the new
modules: getPlaylist, filterSongsToActiveLibrary, songToTrack,
useMemo, getDisplayedSongs (now type-only),
playPlaylistAll/shuffle/enqueue from playlistBulkPlayActions.
Pure code move otherwise.
2026-05-13 13:54:26 +02:00

39 lines
1.5 KiB
TypeScript

import type React from 'react';
import { getPlaylist } from '../api/subsonicPlaylists';
import { filterSongsToActiveLibrary } from '../api/subsonicLibrary';
import type { SubsonicPlaylist, SubsonicSong } from '../api/subsonicTypes';
export interface RunPlaylistLoadDeps {
id: string;
setLoading: (v: boolean) => void;
setPlaylist: React.Dispatch<React.SetStateAction<SubsonicPlaylist | null>>;
setSongs: React.Dispatch<React.SetStateAction<SubsonicSong[]>>;
setCustomCoverId: React.Dispatch<React.SetStateAction<string | null>>;
setRatings: React.Dispatch<React.SetStateAction<Record<string, number>>>;
setStarredSongs: React.Dispatch<React.SetStateAction<Set<string>>>;
}
export async function runPlaylistLoad(deps: RunPlaylistLoadDeps): Promise<void> {
const { id, setLoading, setPlaylist, setSongs, setCustomCoverId, setRatings, setStarredSongs } = deps;
setLoading(true);
try {
const { playlist, songs } = await getPlaylist(id);
const filteredSongs = await filterSongsToActiveLibrary(songs);
setPlaylist(playlist);
setSongs(filteredSongs);
if (playlist.coverArt) setCustomCoverId(playlist.coverArt);
const init: Record<string, number> = {};
const starred = new Set<string>();
filteredSongs.forEach(s => {
if (s.userRating) init[s.id] = s.userRating;
if (s.starred) starred.add(s.id);
});
setRatings(init);
setStarredSongs(starred);
} catch {
// intentional swallow; load failure leaves loading false + playlist null
} finally {
setLoading(false);
}
}