mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-21 23:05:46 +00:00
refactor(playlist): G.68 — extract runPlaylistSaveMeta + 3 hooks (song search / mutations / star+rating) (#635)
Four-cut cluster. 572 → 471 LOC (−101). Each handler on the page
collapses to a one-line delegate; pure code move otherwise.
runPlaylistSaveMeta — the meta save flow (updatePlaylistMeta then
optional uploadPlaylistCoverArt + getPlaylist refresh + cover toast,
or coverRemoved → null, then metaSaved toast + closes the modal).
Takes the deps separately from the opts object so the call-site
just passes through the modal's payload.
usePlaylistSongSearch — searchOpen + searchQuery driven debounced
search against subsonic. Owns the 350 ms timeout, the filter-out
of songs already in the playlist, and the searching state. Returns
{ searchResults, setSearchResults, searching } so addSong on the
page can still drop a just-added song out of the result list.
usePlaylistSongMutations — addSong / removeSong. removeSong is
trivial (filter + setSongs + savePlaylist with prevCount).
addSong preserves the .main-content scrollTop save / requestAnimationFrame
restore trick + drops the song out of both suggestions and search
results + fires the add toast with playlist name interpolation.
usePlaylistStarRating — handleRate (local override + playerStore
userRatingOverride + setRating API) + handleToggleStar (e.stopPropagation,
local set + playerStore starredOverride + star/unstar API). Reads
starredOverrides / setStarredOverride from playerStore directly so
the page doesn't have to thread them through.
PlaylistDetail drops these direct imports (now consumed in the
hooks/utility): updatePlaylistMeta, uploadPlaylistCoverArt, search,
setRating, star, unstar, showToast, useRef. Pure code move.
This commit is contained in:
committed by
GitHub
parent
d0a270d90a
commit
d08875dc70
@@ -0,0 +1,46 @@
|
||||
import type { TFunction } from 'i18next';
|
||||
import { getPlaylist, updatePlaylistMeta, uploadPlaylistCoverArt } from '../api/subsonicPlaylists';
|
||||
import type { SubsonicPlaylist } from '../api/subsonicTypes';
|
||||
import { showToast } from './toast';
|
||||
|
||||
export interface RunPlaylistSaveMetaDeps {
|
||||
id: string;
|
||||
playlist: SubsonicPlaylist;
|
||||
t: TFunction;
|
||||
setPlaylist: (updater: (p: SubsonicPlaylist | null) => SubsonicPlaylist | null) => void;
|
||||
setCustomCoverId: (id: string | null) => void;
|
||||
setEditingMeta: (v: boolean) => void;
|
||||
}
|
||||
|
||||
export async function runPlaylistSaveMeta(
|
||||
deps: RunPlaylistSaveMetaDeps,
|
||||
opts: {
|
||||
name: string;
|
||||
comment: string;
|
||||
isPublic: boolean;
|
||||
coverFile: File | null;
|
||||
coverRemoved: boolean;
|
||||
},
|
||||
): Promise<void> {
|
||||
const { id, playlist, t, setPlaylist, setCustomCoverId, setEditingMeta } = deps;
|
||||
await updatePlaylistMeta(id, opts.name.trim() || playlist.name, opts.comment, opts.isPublic);
|
||||
setPlaylist(p => p
|
||||
? { ...p, name: opts.name.trim() || p.name, comment: opts.comment, public: opts.isPublic }
|
||||
: p
|
||||
);
|
||||
if (opts.coverFile) {
|
||||
try {
|
||||
await uploadPlaylistCoverArt(id, opts.coverFile);
|
||||
const { playlist: refreshed } = await getPlaylist(id);
|
||||
setPlaylist(prev => prev ? { ...prev, coverArt: refreshed.coverArt } : prev);
|
||||
if (refreshed.coverArt) setCustomCoverId(refreshed.coverArt);
|
||||
showToast(t('playlists.coverUpdated'));
|
||||
} catch (err) {
|
||||
showToast(err instanceof Error ? err.message : t('playlists.coverUpdated'), 3000, 'error');
|
||||
}
|
||||
} else if (opts.coverRemoved) {
|
||||
setCustomCoverId(null);
|
||||
}
|
||||
showToast(t('playlists.metaSaved'));
|
||||
setEditingMeta(false);
|
||||
}
|
||||
Reference in New Issue
Block a user