From 98c3c01fd3881da33e136d476f2f1548437320e8 Mon Sep 17 00:00:00 2001 From: Sucukdeluxe <259325684+Sucukdeluxe@users.noreply.github.com> Date: Sun, 6 Sep 2026 12:44:12 +0200 Subject: [PATCH] feat: animate queue detail expansion and collapse --- CHANGELOG.md | 1 + PROJECT_MEMORY.md | 1 + scripts/smoke-test-queue-cards.js | 30 +++++++++++++++++++++++ src/renderer-queue.ts | 10 ++++++-- src/style-modules.production-path.test.ts | 2 +- src/styles-workflows.css | 30 +++++++++++++++++------ src/workspace.css | 5 ++++ 7 files changed, 69 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c5ab8e8..e1dd8a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Animate queue details expanding and collapsing with a matching chevron rotation; keep collapsed file actions out of keyboard navigation. - Keep the waiting queue status unboxed with a transparent background and its yellow status dot. - Rework queue cards with two-line titles, clearer status badges, larger dates below the progress bar on the right, and 32-pixel remove and retry buttons. Keep paused progress visible and wrap long download errors. - Expand or collapse queue details by double-clicking the card surface or using the dedicated keyboard-accessible toggle. Add isolated queue-card checks for both languages and themes. diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md index 55add0e..243e725 100644 --- a/PROJECT_MEMORY.md +++ b/PROJECT_MEMORY.md @@ -18,6 +18,7 @@ Am 6. September 2026 nach einem Festplatten-Reset aus den vorhandenen Remote-Rep ## Letzte Änderungen +- Queue-Details fahren seit dem 6. September 2026 beim Auf-/Zuklappen animiert ein und aus: Grid-Höhe und Deckkraft über 220/180 ms, Pfeilrotation über 220 ms. Bei reduzierten Windows-Animationen bleibt eine kurze 160-ms-Transition wie bei den bestehenden Workspace-Steuerelementen erhalten. Eingeklappte Details sind `inert`, damit unsichtbare Dateiaktionen nicht per Tastatur erreichbar sind. Build, 19 gezielte Tests und isolierter Queue-UI-Test einschließlich gemessener Zwischenhöhen in beiden Bewegungsmodi erfolgreich. - Queue-Card-Nachbesserung am 6. September 2026: Wartestatus ohne sichtbare Umrandung und ohne gelblichen Hintergrund; gelber Statuspunkt und bisherige Abstände bleiben erhalten. In Hell/Dunkel geprüft, Queue-UI- und Stylesheet-Tests erfolgreich. - 6. September 2026: Queue-Cards überarbeitet. Titel mit bis zu zwei Zeilen in 13 px; Datum von 10 auf 12 px vergrößert und rechts unter den Fortschrittsbalken gesetzt. Status als beschriftetes Badge, Entfernen/Wiederholen mit 32 × 32 px Klickfläche, Details und Fortschritt in 12 px, keine Kartenschrift unter 10 px. Wartende Einträge ohne doppelte Statuszeile, pausierte Downloads mit erhaltenem Prozentstand, längere Fehler umbrechend. - Details lassen sich per Doppelklick auf die freie Kartenfläche einschließlich Titel, Datum und Balken umschalten. Ein eigener Pfeil unterstützt Einzelklick, Enter und Leertaste; Schaltflächen lösen keine zusätzliche Kartenaktion aus. Aufklappen aktualisiert die vorhandenen Elemente und erhält den Tastaturfokus. diff --git a/scripts/smoke-test-queue-cards.js b/scripts/smoke-test-queue-cards.js index 4b210b5..f7aaf6a 100644 --- a/scripts/smoke-test-queue-cards.js +++ b/scripts/smoke-test-queue-cards.js @@ -64,6 +64,36 @@ async function main() { } const card = win.locator('.queue-item[data-id="pending"]'); const toggle = card.locator('[data-queue-action="details"]'); + for (const reducedMotion of ['no-preference', 'reduce']) { + await win.emulateMedia({ reducedMotion }); + const motion = await card.evaluate(async (item) => { + const details = item.querySelector('.queue-details'); + const frame = () => new Promise((resolve) => requestAnimationFrame(resolve)); + const sample = async () => { + await frame(); + await frame(); + const animations = details.getAnimations(); + const slide = animations.find((animation) => animation.transitionProperty === 'grid-template-rows'); + if (!slide) throw new Error('Queue details have no height transition'); + for (const animation of animations) animation.currentTime = Number(animation.effect.getTiming().duration) / 2; + const middle = details.getBoundingClientRect().height; + for (const animation of animations) animation.finish(); + await frame(); + return { middle, end: details.getBoundingClientRect().height }; + }; + const collapsed = details.getBoundingClientRect().height; + toggleQueueDetails(item.dataset.id); + const opening = await sample(); + toggleQueueDetails(item.dataset.id); + const closing = await sample(); + return { collapsed, opening, closing, inert: details.inert }; + }); + assert.equal(motion.collapsed, 0); + assert(motion.opening.middle > 0 && motion.opening.middle < motion.opening.end, 'Details slide open'); + assert(motion.closing.middle > 0 && motion.closing.middle < motion.opening.end, 'Details slide closed'); + assert.equal(motion.closing.end, 0); + assert(motion.inert, 'Collapsed details cannot receive keyboard input'); + } for (const selector of ['.title', '.queue-date', '.queue-progress-wrap', '.queue-status-label']) { await card.locator(selector).dblclick(); assert.equal(await toggle.getAttribute('aria-expanded'), 'true', `${selector} expands`); diff --git a/src/renderer-queue.ts b/src/renderer-queue.ts index 248865d..217a0e6 100644 --- a/src/renderer-queue.ts +++ b/src/renderer-queue.ts @@ -575,7 +575,11 @@ function toggleQueueDetails(id: string): void { } const item = Array.from(byId('queueList').querySelectorAll('.queue-item')) .find((candidate) => candidate.dataset.id === id); - item?.querySelector('.queue-details')?.classList.toggle('expanded', expandedQueueIds.has(id)); + const details = item?.querySelector('.queue-details'); + if (details) { + details.classList.toggle('expanded', expandedQueueIds.has(id)); + details.inert = !expandedQueueIds.has(id); + } item?.querySelector('[data-queue-action="details"]')?.setAttribute('aria-expanded', String(expandedQueueIds.has(id))); } @@ -723,12 +727,14 @@ function renderQueue(): void { ${safeProgressStatus} ${safeProgressMetrics} -
+
+
URL: ${escapeHtml(item.url)}
${escapeHtml(UI_TEXT.queue.detailStreamer)} ${escapeHtml(item.streamer)}
${escapeHtml(UI_TEXT.queue.detailDuration)} ${escapeHtml(item.duration_str)}
${escapeHtml(UI_TEXT.queue.detailDate)} ${escapeHtml(formatUiDateTime(item.date))}
${renderQueueItemFileActions(item)} +
diff --git a/src/style-modules.production-path.test.ts b/src/style-modules.production-path.test.ts index 58d8075..8fb558e 100644 --- a/src/style-modules.production-path.test.ts +++ b/src/style-modules.production-path.test.ts @@ -20,7 +20,7 @@ describe('production style modules', () => { .replace(/\r\n/g, '\n')); const digest = createHash('sha256').update(content).digest('hex'); - expect(digest).toBe('2121672b9cf4534a7e15be2cf88ab1919fe54fe8f3ca314e6658ed6633436019'); + expect(digest).toBe('b937d229d00f4a35098f288399331025580a1448f7c7aab7908f12ec826628e9'); }); test('derives the Windows hot-development executable version from package metadata', () => { diff --git a/src/styles-workflows.css b/src/styles-workflows.css index c92300a..b4b0a26 100644 --- a/src/styles-workflows.css +++ b/src/styles-workflows.css @@ -341,6 +341,10 @@ background: color-mix(in srgb, var(--text-secondary) 12%, transparent); } +.queue-details-toggle svg { + transition: transform 220ms ease; +} + .queue-details-toggle[aria-expanded="true"] svg { transform: rotate(180deg); } @@ -363,21 +367,33 @@ } .queue-details { - display: none; + display: grid; + grid-template-rows: 0fr; + opacity: 0; + transition: grid-template-rows 220ms ease, opacity 180ms ease; font-size: 12px; line-height: 18px; color: var(--text-secondary); - margin-top: 8px; - padding: 8px 0 2px; - border-top: 1px solid var(--border-soft); overflow-wrap: anywhere; } -.queue-details.expanded { - display: block; +.queue-details-clip { + min-height: 0; + overflow: hidden; } -.queue-details div { +.queue-details-content { + margin-top: 8px; + padding: 8px 0 2px; + border-top: 1px solid var(--border-soft); +} + +.queue-details.expanded { + grid-template-rows: 1fr; + opacity: 1; +} + +.queue-details-content > div { margin-bottom: 2px; } diff --git a/src/workspace.css b/src/workspace.css index 79a316c..a4f2680 100644 --- a/src/workspace.css +++ b/src/workspace.css @@ -2662,6 +2662,11 @@ input[type="checkbox"].vod-select-checkbox:focus-visible { } @media (prefers-reduced-motion: reduce) { + .queue-details, + .queue-details-toggle svg { + transition-duration: 160ms !important; + } + .vod-bulk-bar { transition: transform 280ms cubic-bezier(0.22, 0.76, 0.22, 1), opacity 220ms ease, visibility 0s linear 280ms !important; }