diff --git a/web/css/queue_style.css b/web/css/queue_style.css index df48b4f..afe7e72 100644 --- a/web/css/queue_style.css +++ b/web/css/queue_style.css @@ -132,7 +132,7 @@ .pqueue-thumb-placeholder--failed{color:var(--danger-300,#fb7185)} .pqueue-thumb-placeholder--interrupted{color:var(--warning-300,#facc15)} .pqueue-thumb-wrap-placeholder{cursor:default;border-style:dashed} -.pqueue-gallery-overlay{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(15,23,42,.75);backdrop-filter:blur(6px);z-index:100000} +.pqueue-gallery-overlay{position:fixed;inset:0;display:none;align-items:center;justify-content:center;background:rgba(26, 26, 26, 0.65);backdrop-filter:blur(10px);z-index:100000} .pqueue-gallery{position:relative;max-width:92vw;max-height:92vh;display:flex;align-items:center;justify-content:center} .pqueue-gallery__img{max-width:90vw;max-height:90vh;border-radius:12px;box-shadow:0 20px 45px rgba(0,0,0,.6)} .pqueue-gallery__counter{position:absolute;left:50%;bottom:-32px;transform:translateX(-50%);color:#fff;font-size:.85rem} diff --git a/web/js/pqueue.state.js b/web/js/pqueue.state.js index c2b22c8..efdd4e0 100644 --- a/web/js/pqueue.state.js +++ b/web/js/pqueue.state.js @@ -54,6 +54,8 @@ container: null, dom: {}, uiMetricsCollapsed: false, + uiQueueCollapsed: false, + uiHistoryCollapsed: false, }; PQ.state = state; diff --git a/web/js/pqueue.ui.history.js b/web/js/pqueue.ui.history.js index 0ce5811..1f2166b 100644 --- a/web/js/pqueue.ui.history.js +++ b/web/js/pqueue.ui.history.js @@ -175,16 +175,76 @@ const base = (count != null) ? `${count} entries` : null; const range = UI.currentHistoryRangeLabel(); const subtitle = [base, range].filter(Boolean).join(' • '); - const card = UI.card("History", { - icon: "ti ti-clock-bolt", - subtitle, - actions: UI.historyFilters(), - content: grid, + + // Collapsible card structure + const bodyInner = grid; + const body = UI.el("div", { class: "pqueue-card__body" }, [bodyInner]); + try { + body.style.overflow = 'hidden'; + body.style.transition = 'height 120ms ease, opacity 120ms ease'; + } catch (err) { /* noop */ } + + const titleWrap = UI.el("div", { class: "pqueue-card__title-wrap" }); + titleWrap.appendChild(UI.icon("ti ti-clock-bolt", { size: "md" })); + titleWrap.appendChild(UI.el("h3", { class: "pqueue-card__title", text: "History" })); + if (subtitle) titleWrap.appendChild(UI.el("span", { class: "pqueue-card__subtitle", text: subtitle })); + + const toggle = UI.button({ icon: state.uiHistoryCollapsed ? "ti ti-chevron-down" : "ti ti-chevron-up", variant: "ghost", subtle: true, title: state.uiHistoryCollapsed ? "Expand" : "Collapse" }); + toggle.addEventListener("click", () => { + try { + const isCollapsed = !!state.uiHistoryCollapsed; + const setIcon = (name) => { try { toggle.innerHTML = ''; toggle.appendChild(UI.icon(name)); } catch (err) { /* noop */ } }; + if (!isCollapsed) { + const start = body.scrollHeight; + body.style.height = `${start}px`; + body.style.opacity = '1'; + requestAnimationFrame(() => { + body.style.height = '0px'; + body.style.opacity = '0'; + }); + const onEnd = () => { + body.removeEventListener('transitionend', onEnd); + setIcon('ti ti-chevron-down'); + state.uiHistoryCollapsed = true; + try { wrap.setAttribute('data-collapsed', 'true'); } catch (err) { /* noop */ } + }; + body.addEventListener('transitionend', onEnd); + } else { + const target = bodyInner.scrollHeight; + body.style.height = '0px'; + body.style.opacity = '0'; + requestAnimationFrame(() => { + const h = bodyInner.scrollHeight || target; + body.style.height = `${h}px`; + body.style.opacity = '1'; + }); + const onEnd = () => { + body.removeEventListener('transitionend', onEnd); + try { body.style.height = ''; } catch (err) { /* noop */ } + setIcon('ti ti-chevron-up'); + state.uiHistoryCollapsed = false; + try { wrap.setAttribute('data-collapsed', 'false'); } catch (err) { /* noop */ } + }; + body.addEventListener('transitionend', onEnd); + } + } catch (err) { /* noop */ } }); - state.dom.historyCard = card; - state.dom.historySubtitle = card.querySelector('.pqueue-card__subtitle'); + + const actions = UI.el("div", { class: "pqueue-card__actions" }, [...UI.historyFilters(), toggle]); + const header = UI.el("header", { class: "pqueue-card__header" }, [titleWrap, actions]); + const wrap = UI.el("section", { class: ["pqueue-card"] }, [header]); + try { wrap.setAttribute('data-collapsed', state.uiHistoryCollapsed ? 'true' : 'false'); } catch (err) { /* noop */ } + if (state.uiHistoryCollapsed) { + try { body.style.height = '0px'; body.style.opacity = '0'; } catch (err) { /* noop */ } + } else { + try { body.style.height = ''; body.style.opacity = '1'; } catch (err) { /* noop */ } + } + + wrap.appendChild(body); + state.dom.historyCard = wrap; + state.dom.historySubtitle = wrap.querySelector('.pqueue-card__subtitle'); try { UI.ensureThumbObserver(); } catch (err) { /* noop */ } - return card; + return wrap; }; UI.ensureHistoryObserver = function ensureHistoryObserver() { diff --git a/web/js/pqueue.ui.queue.js b/web/js/pqueue.ui.queue.js index 52df678..7cdd706 100644 --- a/web/js/pqueue.ui.queue.js +++ b/web/js/pqueue.ui.queue.js @@ -190,14 +190,73 @@ UI.el("div", { class: "pqueue-table__footer-right" }, [deleteSelected]), ]); - const card = UI.card("Queue", { - icon: "ti ti-stack-front", - subtitle: state.metrics.queueCount ? `${state.metrics.queueCount} pending` : null, - actions: [filter], - content: [wrapper, footer], + // Build collapsible card (mirrors metrics behavior) + const bodyInner = UI.el("div", { class: "pqueue-pending" }, [wrapper, footer]); + const body = UI.el("div", { class: "pqueue-card__body" }, [bodyInner]); + try { + body.style.overflow = 'hidden'; + body.style.transition = 'height 120ms ease, opacity 120ms ease'; + } catch (err) { /* noop */ } + + const titleWrap = UI.el("div", { class: "pqueue-card__title-wrap" }); + titleWrap.appendChild(UI.icon("ti ti-stack-front", { size: "md" })); + titleWrap.appendChild(UI.el("h3", { class: "pqueue-card__title", text: "Queue" })); + const subtitleText = state.metrics.queueCount ? `${state.metrics.queueCount} pending` : null; + if (subtitleText) titleWrap.appendChild(UI.el("span", { class: "pqueue-card__subtitle", text: subtitleText })); + + const toggle = UI.button({ icon: state.uiQueueCollapsed ? "ti ti-chevron-down" : "ti ti-chevron-up", variant: "ghost", subtle: true, title: state.uiQueueCollapsed ? "Expand" : "Collapse" }); + toggle.addEventListener("click", () => { + try { + const isCollapsed = !!state.uiQueueCollapsed; + const setIcon = (name) => { try { toggle.innerHTML = ''; toggle.appendChild(UI.icon(name)); } catch (err) { /* noop */ } }; + if (!isCollapsed) { + const start = body.scrollHeight; + body.style.height = `${start}px`; + body.style.opacity = '1'; + requestAnimationFrame(() => { + body.style.height = '0px'; + body.style.opacity = '0'; + }); + const onEnd = () => { + body.removeEventListener('transitionend', onEnd); + setIcon('ti ti-chevron-down'); + state.uiQueueCollapsed = true; + try { wrap.setAttribute('data-collapsed', 'true'); } catch (err) { /* noop */ } + }; + body.addEventListener('transitionend', onEnd); + } else { + const target = bodyInner.scrollHeight; + body.style.height = '0px'; + body.style.opacity = '0'; + requestAnimationFrame(() => { + const h = bodyInner.scrollHeight || target; + body.style.height = `${h}px`; + body.style.opacity = '1'; + }); + const onEnd = () => { + body.removeEventListener('transitionend', onEnd); + try { body.style.height = ''; } catch (err) { /* noop */ } + setIcon('ti ti-chevron-up'); + state.uiQueueCollapsed = false; + try { wrap.setAttribute('data-collapsed', 'false'); } catch (err) { /* noop */ } + }; + body.addEventListener('transitionend', onEnd); + } + } catch (err) { /* noop */ } }); - state.dom.pendingCard = card; - return card; + + const actions = UI.el("div", { class: "pqueue-card__actions" }, [filter, toggle]); + const header = UI.el("header", { class: "pqueue-card__header" }, [titleWrap, actions]); + const wrap = UI.el("section", { class: ["pqueue-card"] }, [header]); + try { wrap.setAttribute('data-collapsed', state.uiQueueCollapsed ? 'true' : 'false'); } catch (err) { /* noop */ } + if (state.uiQueueCollapsed) { + try { body.style.height = '0px'; body.style.opacity = '0'; } catch (err) { /* noop */ } + } else { + try { body.style.height = ''; body.style.opacity = '1'; } catch (err) { /* noop */ } + } + wrap.appendChild(body); + state.dom.pendingCard = wrap; + return wrap; }; UI.pendingRow = function pendingRow(item) {