mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
1a3eeea048
Four-cut cluster pulling action bodies out of PlaylistDetail.tsx. 741
→ 635 LOC (−106). Each handler on the page becomes a thin wrapper
around a parameterized utility; pure code move, no behaviour change.
runPlaylistZipDownload — the buildDownloadUrl → invoke('download_zip')
flow with start/complete/fail dispatches to useZipDownloadStore and
the setZipDownloadId hand-off. Takes playlist + id + downloadFolder
+ requestDownloadFolder + the id-setter as deps. PlaylistDetail
loses the invoke / join / buildDownloadUrl / sanitizeFilename imports
that only this handler used.
playlistBulkPlayActions — three exports (playPlaylistAll,
shufflePlaylistAll, enqueuePlaylistAll) with a shared BulkPlayDeps
shape (songsLength + id + tracks + touchPlaylist + playTrack +
enqueue). Same length-guard, same touchPlaylist call, same
shuffle / play / enqueue branches as before.
startPlaylistRowDrag — the threshold-drag dispatch on row mousedown
(5px deadzone, then bulk-songs / playlist-reorder / single-song
payload depending on selection state + filtered-view flag). Takes
the mouse event + idx + songs + selectedIds + isFiltered + startDrag.
runPlaylistReorderDrop — the psy-drop event handler that lives
inside the tracklist useEffect. Parses the custom-event detail,
computes from→to indexes, and updates songs + savePlaylist + clears
dropTargetIdx. The useEffect itself stays in the page because it
wires up the event listener.
PlaylistDetail's import list also drops `invoke`, `join`,
`buildDownloadUrl`, `sanitizeFilename` — they followed the ZIP
helper to its new file.
46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
import type React from 'react';
|
|
import type { SubsonicSong } from '../api/subsonicTypes';
|
|
|
|
export interface RunPlaylistReorderDropDeps {
|
|
e: Event;
|
|
songs: SubsonicSong[];
|
|
savePlaylist: (next: SubsonicSong[], prevCount?: number) => Promise<void>;
|
|
setDropTargetIdx: React.Dispatch<React.SetStateAction<{ idx: number; before: boolean } | null>>;
|
|
setSongs: React.Dispatch<React.SetStateAction<SubsonicSong[]>>;
|
|
}
|
|
|
|
export function runPlaylistReorderDrop(deps: RunPlaylistReorderDropDeps): void {
|
|
const { e, songs, savePlaylist, setDropTargetIdx, setSongs } = deps;
|
|
const detail = (e as CustomEvent).detail;
|
|
if (!detail?.data) return;
|
|
let parsed: any;
|
|
try { parsed = JSON.parse(detail.data); } catch { return; }
|
|
if (parsed.type !== 'playlist_reorder') return;
|
|
|
|
setDropTargetIdx(null);
|
|
|
|
const fromIdx: number = parsed.index;
|
|
|
|
// Determine drop index from the event target row
|
|
const target = (e.target as HTMLElement).closest('[data-track-idx]');
|
|
let toIdx = songs.length;
|
|
if (target) {
|
|
const targetIdx = parseInt(target.getAttribute('data-track-idx') ?? '', 10);
|
|
const rect = target.getBoundingClientRect();
|
|
const cursorY = (e as CustomEvent & { clientY?: number }).clientY ?? (rect.top + rect.height / 2);
|
|
const before = cursorY < rect.top + rect.height / 2;
|
|
toIdx = before ? targetIdx : targetIdx + 1;
|
|
}
|
|
|
|
if (fromIdx === toIdx || fromIdx === toIdx - 1) return;
|
|
|
|
setSongs(prev => {
|
|
const next = [...prev];
|
|
const [moved] = next.splice(fromIdx, 1);
|
|
const insertAt = toIdx > fromIdx ? toIdx - 1 : toIdx;
|
|
next.splice(insertAt, 0, moved);
|
|
savePlaylist(next);
|
|
return next;
|
|
});
|
|
}
|