This commit is contained in:
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
## Unreleased
|
## 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Am 6. September 2026 nach einem Festplatten-Reset aus den vorhandenen Remote-Rep
|
|||||||
|
|
||||||
## Letzte Änderungen
|
## 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.
|
- 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.
|
- 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.
|
- 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.
|
||||||
|
|||||||
@@ -64,6 +64,36 @@ async function main() {
|
|||||||
}
|
}
|
||||||
const card = win.locator('.queue-item[data-id="pending"]');
|
const card = win.locator('.queue-item[data-id="pending"]');
|
||||||
const toggle = card.locator('[data-queue-action="details"]');
|
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']) {
|
for (const selector of ['.title', '.queue-date', '.queue-progress-wrap', '.queue-status-label']) {
|
||||||
await card.locator(selector).dblclick();
|
await card.locator(selector).dblclick();
|
||||||
assert.equal(await toggle.getAttribute('aria-expanded'), 'true', `${selector} expands`);
|
assert.equal(await toggle.getAttribute('aria-expanded'), 'true', `${selector} expands`);
|
||||||
|
|||||||
@@ -575,7 +575,11 @@ function toggleQueueDetails(id: string): void {
|
|||||||
}
|
}
|
||||||
const item = Array.from(byId<HTMLElement>('queueList').querySelectorAll<HTMLElement>('.queue-item'))
|
const item = Array.from(byId<HTMLElement>('queueList').querySelectorAll<HTMLElement>('.queue-item'))
|
||||||
.find((candidate) => candidate.dataset.id === id);
|
.find((candidate) => candidate.dataset.id === id);
|
||||||
item?.querySelector('.queue-details')?.classList.toggle('expanded', expandedQueueIds.has(id));
|
const details = item?.querySelector<HTMLElement>('.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)));
|
item?.querySelector('[data-queue-action="details"]')?.setAttribute('aria-expanded', String(expandedQueueIds.has(id)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -723,12 +727,14 @@ function renderQueue(): void {
|
|||||||
<span class="queue-progress-status${progressStatusClass}${item.status === 'paused' ? ' is-hidden' : ''}">${safeProgressStatus}</span>
|
<span class="queue-progress-status${progressStatusClass}${item.status === 'paused' ? ' is-hidden' : ''}">${safeProgressStatus}</span>
|
||||||
<span class="queue-progress-metrics">${safeProgressMetrics}</span>
|
<span class="queue-progress-metrics">${safeProgressMetrics}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="queue-details${expandedQueueIds.has(item.id) ? ' expanded' : ''}" id="${detailsId}">
|
<div class="queue-details${expandedQueueIds.has(item.id) ? ' expanded' : ''}" id="${detailsId}"${expandedQueueIds.has(item.id) ? '' : ' inert'}>
|
||||||
|
<div class="queue-details-clip"><div class="queue-details-content">
|
||||||
<div><span class="queue-detail-label">URL:</span> ${escapeHtml(item.url)}</div>
|
<div><span class="queue-detail-label">URL:</span> ${escapeHtml(item.url)}</div>
|
||||||
<div><span class="queue-detail-label">${escapeHtml(UI_TEXT.queue.detailStreamer)}</span> ${escapeHtml(item.streamer)}</div>
|
<div><span class="queue-detail-label">${escapeHtml(UI_TEXT.queue.detailStreamer)}</span> ${escapeHtml(item.streamer)}</div>
|
||||||
<div><span class="queue-detail-label">${escapeHtml(UI_TEXT.queue.detailDuration)}</span> ${escapeHtml(item.duration_str)}</div>
|
<div><span class="queue-detail-label">${escapeHtml(UI_TEXT.queue.detailDuration)}</span> ${escapeHtml(item.duration_str)}</div>
|
||||||
<div><span class="queue-detail-label">${escapeHtml(UI_TEXT.queue.detailDate)}</span> ${escapeHtml(formatUiDateTime(item.date))}</div>
|
<div><span class="queue-detail-label">${escapeHtml(UI_TEXT.queue.detailDate)}</span> ${escapeHtml(formatUiDateTime(item.date))}</div>
|
||||||
${renderQueueItemFileActions(item)}
|
${renderQueueItemFileActions(item)}
|
||||||
|
</div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ describe('production style modules', () => {
|
|||||||
.replace(/\r\n/g, '\n'));
|
.replace(/\r\n/g, '\n'));
|
||||||
const digest = createHash('sha256').update(content).digest('hex');
|
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', () => {
|
test('derives the Windows hot-development executable version from package metadata', () => {
|
||||||
|
|||||||
@@ -341,6 +341,10 @@
|
|||||||
background: color-mix(in srgb, var(--text-secondary) 12%, transparent);
|
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 {
|
.queue-details-toggle[aria-expanded="true"] svg {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
@@ -363,21 +367,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.queue-details {
|
.queue-details {
|
||||||
display: none;
|
display: grid;
|
||||||
|
grid-template-rows: 0fr;
|
||||||
|
opacity: 0;
|
||||||
|
transition: grid-template-rows 220ms ease, opacity 180ms ease;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
margin-top: 8px;
|
|
||||||
padding: 8px 0 2px;
|
|
||||||
border-top: 1px solid var(--border-soft);
|
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
}
|
}
|
||||||
|
|
||||||
.queue-details.expanded {
|
.queue-details-clip {
|
||||||
display: block;
|
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;
|
margin-bottom: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2662,6 +2662,11 @@ input[type="checkbox"].vod-select-checkbox:focus-visible {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (prefers-reduced-motion: reduce) {
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.queue-details,
|
||||||
|
.queue-details-toggle svg {
|
||||||
|
transition-duration: 160ms !important;
|
||||||
|
}
|
||||||
|
|
||||||
.vod-bulk-bar {
|
.vod-bulk-bar {
|
||||||
transition: transform 280ms cubic-bezier(0.22, 0.76, 0.22, 1), opacity 220ms ease, visibility 0s linear 280ms !important;
|
transition: transform 280ms cubic-bezier(0.22, 0.76, 0.22, 1), opacity 220ms ease, visibility 0s linear 280ms !important;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user