From 6fec48e084046fa59950735f1b725cde5b8d2661 Mon Sep 17 00:00:00 2001 From: Thales <> Date: Sun, 4 Oct 2026 10:50:07 +0100 Subject: [PATCH] feat(trash): hold a row for a second to restore it, pick several, and a count on the icon Restoring from the Trash used to mean dragging one row at a time onto the Library button, which nothing on screen explained, and a click on a trashed row opened it in the studio as if it were not trashed. Hovering a Trash row now turns its second line into "Hold left click for 1 second to restore". A click selects a row, Ctrl+click adds or removes one, Shift+click takes a range. Holding the mouse button, or Enter, for a second restores the row, or the whole selection when the row is part of it; letting go early does nothing. Every row the hold will restore fills while it is held. Dragging onto Library still restores, the selection too. Restore never queues a song again (#748). The Trash icon carries the same count badge as the Queue, and its label says how many songs are in it, so a full Trash is visible from anywhere. Closes #749 Closes #755 --- static/css/daw.css | 31 +++- static/index.html | 3 +- static/js/catalog.js | 161 +++++++++++++++++++-- static/js/i18n.js | 20 +++ tests/e2e/trash-restore.spec.mjs | 240 +++++++++++++++++++++++++++++++ 5 files changed, 441 insertions(+), 14 deletions(-) create mode 100644 tests/e2e/trash-restore.spec.mjs diff --git a/static/css/daw.css b/static/css/daw.css index db6cde71..cb2090e9 100644 --- a/static/css/daw.css +++ b/static/css/daw.css @@ -578,8 +578,9 @@ input, textarea { font-family: inherit; } .rail-btn > span { white-space: nowrap; max-width: 56px; overflow: hidden; text-overflow: ellipsis; } /* Queue rail button. Only present while something is importing, so the rail - stays as it is today for anyone not using the queue. */ -.rail-queue { position: relative; } + stays as it is today for anyone not using the queue. The Trash button wears + the same badge, with how many songs are in it (#749). */ +.rail-queue, .rail-trash { position: relative; } .rail-badge { position: absolute; top: 2px; right: 2px; min-width: 15px; height: 15px; padding: 0 4px; @@ -931,6 +932,32 @@ input, textarea { font-family: inherit; } .cat-fav.active, .cat-fav.active:hover { color: #e54e4e; } /* A stopped song (#748) offers Extract at rest: it is the row's only way on. */ .cat-item.is-stopped .cat-actions { opacity: 1; pointer-events: auto; } + +/* Trash rows look like library rows. On hover the second line becomes the + hint, and a hold fills the row from the left over --hold-ms (set from + TRASH_HOLD_MS in catalog.js), then restores. */ +.cat-item.in-trash { user-select: none; -webkit-user-select: none; overflow: hidden; } +.cat-item.in-trash .trash-hint { display: none; color: var(--fg-2); } +.cat-item.in-trash:hover .trash-hint, +.cat-item.in-trash:focus-visible .trash-hint, +.cat-item.in-trash.holding .trash-hint { display: block; overflow: hidden; text-overflow: ellipsis; } +.cat-item.in-trash:hover .cat-sub:not(.trash-hint), +.cat-item.in-trash:focus-visible .cat-sub:not(.trash-hint), +.cat-item.in-trash.holding .cat-sub:not(.trash-hint) { display: none; } +.trash-hold { position: absolute; inset: 0; pointer-events: none; } +.trash-hold::before { + content: ""; position: absolute; inset: 0; + background: color-mix(in srgb, var(--accent) 18%, transparent); + transform: scaleX(0); transform-origin: left center; +} +.cat-item.in-trash.holding .trash-hold::before { + transform: scaleX(1); + transition: transform var(--hold-ms, 1000ms) linear; +} +.cat-item.in-trash.selected { + background: color-mix(in srgb, var(--accent) 10%, transparent); + border-color: color-mix(in srgb, var(--accent) 45%, transparent); +} .cat-extract { height: 20px; padding: 0 8px; border: 1px solid var(--accent); border-radius: 5px; background: transparent; color: var(--accent); cursor: pointer; diff --git a/static/index.html b/static/index.html index 7baf25c0..56490e23 100644 --- a/static/index.html +++ b/static/index.html @@ -323,11 +323,12 @@ Queue -