mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-08-11 19:29:33 +00:00
feat(v3): add "Add to playlist" to a song's ⋮ More menu (#625)
* feat(v3): add "Add to playlist" to a song's ⋮ More menu You could only add a song to a playlist via select-mode (checkbox → batch bar). Add an "Add to playlist" row to each song card's ⋮ overflow menu that targets that one song, reusing the same picker (pick a listed number or type a new name to create the playlist). The select-mode batch flow and the single-song menu now share one extracted `addFilenamesToPlaylist(filenames)` helper; the menu is `openCardMenu`, shared by grid cards and tree rows, so both views get it. Tests: tests/js/v3_add_to_playlist_menu.test.js. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QbexxfTt8q2tAn436MqGWF * fix(v3): don't clear the batch selection when the playlist picker is cancelled The extract-helper refactor made batchAddToPlaylist() call finishBatch() unconditionally, so cancelling (or a failed create) cleared the multi-select and reloaded the grid — a regression from the original early-return-on-cancel behaviour. addFilenamesToPlaylist() already returns null on cancel/failure; gate finishBatch() on a truthy playlist id so the selection is preserved for a retry. Adds a regression assertion. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: byrongamatos <xasiklas@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
byrongamatos
parent
b103a722ce
commit
3d97c07b2b
+24
-6
@@ -495,6 +495,7 @@
|
||||
menu.className = 'v3-card-menu absolute top-10 right-2 z-30 min-w-[10rem] bg-fb-card border border-fb-border/60 rounded-lg shadow-xl py-1 text-sm';
|
||||
const rows = [
|
||||
{ id: '__play', label: 'Play', run: () => { _saveLibraryScrollSnapshot(); window.playSong && window.playSong(enc(song.filename)); } },
|
||||
{ id: '__playlist', label: 'Add to playlist' },
|
||||
...items.map((a) => ({ id: a.id, label: a.label, destructive: a.destructive, enabled: a.enabled, plugin: a.pluginId })),
|
||||
];
|
||||
menu.innerHTML = rows.map((r) =>
|
||||
@@ -514,6 +515,7 @@
|
||||
const id = b.getAttribute('data-act');
|
||||
closeMenu();
|
||||
if (id === '__play') { playCard(song); return; }
|
||||
if (id === '__playlist') { await addFilenamesToPlaylist([song.filename]); return; }
|
||||
if (reg) await reg.run(id, song, { source: 'v3-songs' });
|
||||
}));
|
||||
setTimeout(() => document.addEventListener('click', closer), 0);
|
||||
@@ -612,26 +614,42 @@
|
||||
finishBatch();
|
||||
}
|
||||
|
||||
async function batchAddToPlaylist() {
|
||||
// Prompt for a target playlist (pick a listed number, or type a new name to
|
||||
// create it) and add the given song filenames to it. Shared by the
|
||||
// select-mode batch bar and the per-card ⋮ menu's single-song add. Returns
|
||||
// the playlist id (or null if cancelled).
|
||||
async function addFilenamesToPlaylist(filenames) {
|
||||
const fns = Array.from(filenames || []);
|
||||
if (!fns.length) return null;
|
||||
const lists = (await jget('/api/playlists')) || [];
|
||||
const choices = lists.filter((p) => !p.system_key);
|
||||
const labels = choices.map((p, i) => (i + 1) + '. ' + p.name).join(' ');
|
||||
const ans = ((await window.uiPrompt({
|
||||
title: 'Add ' + state.selected.size + ' song(s) to a playlist',
|
||||
title: 'Add ' + fns.length + ' song' + (fns.length === 1 ? '' : 's') + ' to a playlist',
|
||||
label: (labels ? labels + ' ' : '') + 'Type a number above, or a new playlist name:',
|
||||
okLabel: 'Add',
|
||||
placeholder: 'Number or new playlist name',
|
||||
})) || '').trim();
|
||||
if (!ans) return;
|
||||
if (!ans) return null;
|
||||
let pid = null;
|
||||
const num = parseInt(ans, 10);
|
||||
if (!isNaN(num) && choices[num - 1]) pid = choices[num - 1].id;
|
||||
else { const created = await jsend('POST', '/api/playlists', { name: ans }); pid = created && created.id; }
|
||||
if (!pid) return;
|
||||
for (const fn of state.selected) {
|
||||
if (!pid) return null;
|
||||
for (const fn of fns) {
|
||||
try { await fetch('/api/playlists/' + pid + '/songs', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: fn }) }); } catch (e) { /* */ }
|
||||
}
|
||||
finishBatch();
|
||||
if (window.v3Playlists) { try { window.v3Playlists.refresh(); } catch (e) { /* */ } }
|
||||
return pid;
|
||||
}
|
||||
|
||||
async function batchAddToPlaylist() {
|
||||
const pid = await addFilenamesToPlaylist(state.selected);
|
||||
// Only tear down the multi-select when the add actually happened. A
|
||||
// cancelled or failed picker returns null — preserve the selection (and
|
||||
// skip the reload) so the user can retry, matching the pre-refactor
|
||||
// behaviour where !ans / !pid returned early before finishBatch().
|
||||
if (pid) finishBatch();
|
||||
}
|
||||
|
||||
function finishBatch() {
|
||||
|
||||
Reference in New Issue
Block a user