fix(playlists): what the first review of bulk actions found

An Esc during a pointer drag is spent on the drag and leaves the selection.
A wheel over the rail re-reads the target each frame, so a drop lands on the
row under the pointer. After a bulk removal the focus goes to the next row
left, and to the list's menu when the list is emptied.

remove-bulk names the videos that went, and its Undo brings back only those.
An Undo of a move keeps each row (and when it was saved), leaves out a video
that moved on from the target, closes up its place, and says how many came
back. Delete with every tick hidden removes nothing and says why. A list gone
before the lock answers 404, and a body without its videos 422.

One removal path now: the single row goes through the bulk one, the same
toast and undo, which also retires the add-at-position route. railDrop reuses
the store and scrollParent, the drag image the overlay and the z-index token,
and the sensors keep their number. The count is read out from a region that
outlives the band. Tests fill the gaps the review named, each seen failing.
This commit is contained in:
2026-11-02 03:41:08 +01:00
parent d09b5d6abb
commit 1a1e9d9542
24 changed files with 1168 additions and 379 deletions
+3 -3
View File
@@ -70,10 +70,10 @@ export const playlistsApi = {
req(`/api/playlists/${id}${onYoutube ? "?on_youtube=true" : ""}`, { method: "DELETE" }),
// `position` (an index in the stored order) puts it back where a removal took it from — the
// list page's Undo; omitted, it is appended.
addToPlaylist: (id: number, videoId: string, position?: number) =>
addToPlaylist: (id: number, videoId: string) =>
req(`/api/playlists/${id}/items`, {
method: "POST",
body: JSON.stringify({ video_id: videoId, ...(position != null && { position }) }),
body: JSON.stringify({ video_id: videoId }),
}),
removeFromPlaylist: (id: number, videoId: string) =>
req(`/api/playlists/${id}/items/${videoId}`, { method: "DELETE" }),
@@ -115,7 +115,7 @@ export const playlistsApi = {
removeManyFromPlaylist: (
id: number,
videoIds: string[],
): Promise<{ ok: true; playlist_id: number; removed: number }> =>
): Promise<{ ok: true; playlist_id: number; removed: string[] }> =>
req(`/api/playlists/${id}/items/remove-bulk`, {
method: "POST",
body: JSON.stringify({ video_ids: videoIds }),