From 5bec8713a5e91c1de44759b19e1e8225b2d18b0b Mon Sep 17 00:00:00 2001 From: Dymas Date: Sat, 23 May 2026 13:59:46 +0200 Subject: [PATCH] Add collapsible queue job cards --- CHANGELOG.md | 4 +++ README.md | 3 ++- VERSION | 2 +- queue_page.py | 67 +++++++++++++++++++++++++++++++++++++++++++++------ 4 files changed, 67 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3df1510..30df754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.35.2 - 2026-05-23 + +- Made queue job cards collapsible and defaulted finished jobs to collapsed so long queue histories take less space while running and pending jobs stay expanded. + ## 0.35.1 - 2026-05-23 - Changed the shared sidebar navigation to a vertical one-link-per-line stack so the menu stays balanced after adding the dedicated Queue page. diff --git a/README.md b/README.md index 24ebecc..35380ea 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Small local web UI for a system-wide `ani-cli` install. -Current version: `0.35.1` +Current version: `0.35.2` ## Project Layout @@ -166,6 +166,7 @@ Highlights: - Automatic queue polling every 2.5 seconds. - Dedicated queue pagination and cleanup controls. - Search and queue workflows are now separated into distinct pages. +- Each queue job card can be collapsed, and finished jobs start collapsed by default to keep the page compact. ### Config Page diff --git a/VERSION b/VERSION index 731b95d..abc2aab 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.35.1 +0.35.2 diff --git a/queue_page.py b/queue_page.py index f7e2d39..d471536 100644 --- a/queue_page.py +++ b/queue_page.py @@ -209,21 +209,48 @@ QUEUE_HTML = r""" gap: 14px; transition: border-color 0.16s ease, transform 0.16s ease; } + .job.collapsed { + gap: 0; + } .job:hover { border-color: var(--line-strong); transform: translateY(-1px); } .job-head { display: grid; - grid-template-columns: 1fr auto; + grid-template-columns: auto 1fr auto; gap: 12px; align-items: start; min-width: 0; } + .job-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + width: 40px; + min-width: 40px; + padding: 0; + border-radius: 12px; + font-size: 18px; + line-height: 1; + } + .job-toggle::before { + content: "▾"; + } + .job.collapsed .job-toggle::before { + content: "▸"; + } .job-title { font-weight: 800; overflow-wrap: anywhere; } + .job-body { + display: grid; + gap: 14px; + } + .job.collapsed .job-body { + display: none; + } .status { border-radius: 999px; padding: 6px 10px; @@ -271,7 +298,8 @@ QUEUE_HTML = r""" .app { grid-template-columns: 1fr; } aside { border-right: 0; border-bottom: 1px solid var(--line); } .toolbar, .row { align-items: stretch; flex-direction: column; } - .job-head { grid-template-columns: 1fr; } + .job-head { grid-template-columns: auto 1fr; } + .job-head .status { grid-column: 2; justify-self: start; } } @@ -322,7 +350,8 @@ QUEUE_HTML = r""" queuePages: 1, queueTotal: 0, queueRequestId: 0, - queueTimer: null + queueTimer: null, + collapsedJobs: {} }; const $ = (id) => document.getElementById(id); @@ -342,6 +371,17 @@ QUEUE_HTML = r""" return btn; } + function isCollapsed(job) { + if (Object.prototype.hasOwnProperty.call(state.collapsedJobs, job.id)) { + return state.collapsedJobs[job.id]; + } + return job.status === "done"; + } + + function setCollapsed(jobId, collapsed) { + state.collapsedJobs[jobId] = collapsed; + } + function renderQueue(data) { const jobs = data.jobs || []; state.queuePage = data.page || 1; @@ -357,21 +397,34 @@ QUEUE_HTML = r""" for (const job of jobs) { const item = document.createElement("article"); item.className = "job"; + const collapsed = isCollapsed(job); + if (collapsed) item.classList.add("collapsed"); const log = [...(job.log || [])].slice(-28).reverse().join("\n"); item.innerHTML = `
+

-

-          
- -
+
+

+            
+ +
+
`; + const toggle = item.querySelector(".job-toggle"); + toggle.setAttribute("aria-expanded", collapsed ? "false" : "true"); + toggle.addEventListener("click", () => { + const nextCollapsed = !item.classList.contains("collapsed"); + item.classList.toggle("collapsed", nextCollapsed); + toggle.setAttribute("aria-expanded", nextCollapsed ? "false" : "true"); + setCollapsed(job.id, nextCollapsed); + }); item.querySelector(".job-title").textContent = job.title; const libraryName = job.anime_name || job.title; const season = String(job.season || "1").padStart(2, "0");