feat(playlists): act on the selected rows from the toolbar, and undo either

While rows are selected the toolbar's second row is the band: the count,
the last target as one click, Move to, Remove, Clear selection. Both write
in one request and undo back to the places; a video the target held before
stays there. Delete takes the selection when there is one. A failure puts
the rows back ticked.

A row's own move goes through the same write, so it undoes too, and the
single-row move route and service function are gone. A playlist row drops
its grip below a 24rem table: the row menu keeps the moves.
This commit is contained in:
2026-11-01 22:07:09 +01:00
parent 3b85881af4
commit efbbb23525
24 changed files with 1114 additions and 236 deletions
+46 -5
View File
@@ -59,11 +59,52 @@ export function removePlaylistItem(qc: QueryClient, list: ListRef, id: string) {
});
}
/** Move a video from one playlist to another, in one request (see `api.moveToPlaylist`). */
export function movePlaylistItem(qc: QueryClient, from: ListRef, id: string, to: ListRef) {
return api.moveToPlaylist(from.id, id, to.id).then((r) => {
markIfWatchLater(qc, from, id, false);
markIfWatchLater(qc, to, id, true);
/** Take videos out of a playlist, in one request. */
export function removePlaylistItems(qc: QueryClient, list: ListRef, ids: string[]) {
return api.removeManyFromPlaylist(list.id, ids).then((r) => {
for (const id of ids) markIfWatchLater(qc, list, id, false);
return r;
});
}
/** Put removed videos back at their places, in one request. */
export function restorePlaylistItems(
qc: QueryClient,
list: ListRef,
ids: string[],
positions: number[],
) {
return api.restoreToPlaylist(list.id, ids, positions).then((r) => {
for (const id of ids) markIfWatchLater(qc, list, id, true);
return r;
});
}
/** Move videos from one playlist to another, in one request (see `api.moveManyToPlaylist`). Marks
* what the server says it moved: a video a concurrent edit already took out is in neither answer. */
export function movePlaylistItems(qc: QueryClient, from: ListRef, ids: string[], to: ListRef) {
return api.moveManyToPlaylist(from.id, ids, to.id).then((r) => {
for (const id of [...r.moved, ...r.already_there]) {
markIfWatchLater(qc, from, id, false);
markIfWatchLater(qc, to, id, true);
}
return r;
});
}
/** Take a move back: every video returns to `from`, and only the ones that landed in `to` leave it;
* it held the others before the move. */
export function undoPlaylistMove(
qc: QueryClient,
from: ListRef,
to: ListRef,
back: { moved: string[]; already_there: string[]; positions: number[] },
) {
return api
.undoPlaylistMove(from.id, to.id, back.moved, back.already_there, back.positions)
.then((r) => {
for (const id of back.moved) markIfWatchLater(qc, to, id, false);
for (const id of [...back.moved, ...back.already_there]) markIfWatchLater(qc, from, id, true);
return r;
});
}