From 034ecad644e1690fe0caff2bec80c7d87520ed89 Mon Sep 17 00:00:00 2001 From: Steppenstreuner Date: Fri, 28 Aug 2026 18:11:15 +0200 Subject: [PATCH] add(ui/ux): bulk delete --- static/app.js | 77 +++++++++++++++++++++++++++++++---------------- static/index.html | 13 ++++++++ static/style.css | 16 ++++++++++ 3 files changed, 80 insertions(+), 26 deletions(-) diff --git a/static/app.js b/static/app.js index 69ab746..4022f6f 100644 --- a/static/app.js +++ b/static/app.js @@ -261,6 +261,26 @@ $("#manual-form").addEventListener("submit", async (e) => { // ---- missing / imported lists ---- let currentRequests = { missing: [], imported: [] }; +// view mode (Liste / kleine / große Symbole), shared by both lists +function getViewMode() { + try { return localStorage.getItem("wordarr.viewMode") || "small"; } catch { return "small"; } +} +function applyViewMode(mode) { + try { localStorage.setItem("wordarr.viewMode", mode); } catch {} + ["#missing-list", "#imported-list"].forEach((sel) => { + const box = $(sel); + box.classList.remove("view-list", "view-small", "view-large"); + box.classList.add("view-" + mode); + }); + $$(".view-toggle button").forEach((b) => + b.classList.toggle("active", b.dataset.viewmode === mode) + ); +} +$$(".view-toggle button").forEach((b) => + b.addEventListener("click", () => applyViewMode(b.dataset.viewmode)) +); +applyViewMode(getViewMode()); + function requestMatchesFilter(r, text) { if (!text) return true; const hay = `${r.title} ${r.authors} ${r.series} ${r.narrator}`.toLowerCase(); @@ -284,9 +304,10 @@ function renderRequests(status) { const text = status === "missing" ? $("#missing-search").value : $("#imported-search").value; const reqs = currentRequests[status].filter((r) => requestMatchesFilter(r, text)); const box = status === "missing" ? $("#missing-list") : $("#imported-list"); + const bulkBtn = status === "missing" ? $("#missing-bulk-delete") : $("#imported-bulk-delete"); if (!reqs.length) { box.innerHTML = "

Nichts hier.

"; - if (status === "missing") $("#missing-bulk-delete").hidden = true; + bulkBtn.hidden = true; return; } box.innerHTML = reqs @@ -300,7 +321,7 @@ function renderRequests(status) { ${esc(r.media_type)} → ${esc(r.library_name)} ${status === "imported" ? `${esc(r.imported_path)}` : ""} - ${status === "missing" ? `` : ""} + ` ) .join(""); @@ -310,37 +331,41 @@ function renderRequests(status) { openDetail(parseInt(card.dataset.detail), status); }) ); - if (status === "missing") { - const updateBulkBtn = () => { - const n = box.querySelectorAll("[data-sel-req]:checked").length; - const btn = $("#missing-bulk-delete"); - btn.hidden = n === 0; - btn.textContent = `Ausgewählte entfernen (${n})`; - }; - box.querySelectorAll("[data-sel-req]").forEach((c) => - c.addEventListener("change", updateBulkBtn) - ); - updateBulkBtn(); - } + const updateBulkBtn = () => { + const n = box.querySelectorAll("[data-sel-req]:checked").length; + bulkBtn.hidden = n === 0; + bulkBtn.textContent = `Ausgewählte entfernen (${n})`; + }; + box.querySelectorAll("[data-sel-req]").forEach((c) => + c.addEventListener("change", updateBulkBtn) + ); + updateBulkBtn(); } +async function bulkDeleteRequests(status) { + const box = status === "missing" ? $("#missing-list") : $("#imported-list"); + const ids = [...box.querySelectorAll("[data-sel-req]:checked")].map((c) => c.dataset.selReq); + if (!ids.length) return; + const hint = status === "imported" + ? `${ids.length} Eintrag/Einträge entfernen? Die importierten Dateien in der Library bleiben erhalten.` + : `${ids.length} Anfrage(n) entfernen?`; + if (!confirm(hint)) return; + for (const id of ids) { + try { await api("/api/requests/" + id, { method: "DELETE" }); } + catch (err) { toast(err.message, true); } + } + toast(`${ids.length} entfernt`); + loadRequests(status); +} + +$("#imported-bulk-delete").addEventListener("click", () => bulkDeleteRequests("imported")); + $("#missing-filter-type").addEventListener("change", () => loadRequests("missing")); $("#missing-filter-library").addEventListener("change", () => loadRequests("missing")); $("#missing-search").addEventListener("input", () => renderRequests("missing")); $("#imported-search").addEventListener("input", () => renderRequests("imported")); -$("#missing-bulk-delete").addEventListener("click", async () => { - const ids = [...$("#missing-list").querySelectorAll("[data-sel-req]:checked")] - .map((c) => c.dataset.selReq); - if (!ids.length) return; - if (!confirm(`${ids.length} Anfrage(n) entfernen?`)) return; - for (const id of ids) { - try { await api("/api/requests/" + id, { method: "DELETE" }); } - catch (err) { toast(err.message, true); } - } - toast(`${ids.length} Anfrage(n) entfernt`); - loadRequests("missing"); -}); +$("#missing-bulk-delete").addEventListener("click", () => bulkDeleteRequests("missing")); // ---- request detail dialog ---- let detailRequest = null; diff --git a/static/index.html b/static/index.html index 466e6c4..555fe62 100644 --- a/static/index.html +++ b/static/index.html @@ -90,6 +90,11 @@ +
+ + + +
@@ -97,6 +102,14 @@ diff --git a/static/style.css b/static/style.css index 20abb93..4595e90 100644 --- a/static/style.css +++ b/static/style.css @@ -55,6 +55,22 @@ button:disabled { opacity: 0.6; cursor: default; } .mono { font-family: ui-monospace, monospace; font-size: 0.85rem; word-break: break-all; } .subpath { color: var(--muted); font-size: 0.75rem; font-family: ui-monospace, monospace; } +/* view modes (Missing/Importiert) */ +.view-toggle { display: flex; gap: 0; border: 1px solid var(--border); border-radius: 4px; overflow: hidden; margin-left: auto; } +.view-toggle button { background: var(--panel); color: var(--muted); padding: 0.45rem 0.7rem; border-radius: 0; } +.view-toggle button.active { background: var(--accent); color: #fff; } +.cards.view-large { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); } +.cards.view-large .card img, .cards.view-large .nocover { width: 90px; height: 135px; } +.cards.view-small { grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); } +.cards.view-small .card img, .cards.view-small .nocover { width: 45px; height: 68px; } +.cards.view-small .card { padding: 0.5rem; gap: 0.6rem; } +.cards.view-small .card-body { font-size: 0.88rem; gap: 0.15rem; } +.cards.view-list { grid-template-columns: 1fr; gap: 0.25rem; } +.cards.view-list .card { padding: 0.35rem 0.6rem; gap: 0.6rem; align-items: center; border-radius: 4px; } +.cards.view-list .card img, .cards.view-list .nocover { width: 28px; height: 42px; font-size: 0.9rem; } +.cards.view-list .card-body { flex-direction: row; align-items: baseline; gap: 0.7rem; flex-wrap: wrap; } +.cards.view-list .card-check { position: static; margin-left: auto; flex-shrink: 0; } + /* tables */ .table-scroll { overflow: auto; max-height: 70vh; margin-bottom: 1rem; border: 1px solid var(--border); border-radius: 6px; } table { width: 100%; border-collapse: collapse; background: var(--panel); }