From ba8e55bd97c9c32620edf82d05b5c83a1accf45f Mon Sep 17 00:00:00 2001 From: Dymas Date: Tue, 18 Aug 2026 08:48:13 +0200 Subject: [PATCH] Show watchlist refresh progress as a single bar --- CHANGELOG.md | 4 +++ README.md | 2 +- VERSION | 2 +- test_app.py | 9 ++++++ watchlist_page.py | 79 ++++++++++++++++++++++++++++++++++++++++++++--- 5 files changed, 89 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ebcbd4..7fbef99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.52.15 - 2026-08-18 + +- Replaced duplicated Watchlist refresh-all progress text with a single progress bar and current-anime label. + ## 0.52.14 - 2026-08-18 - Moved each completed episode from staging into the configured downloads library as soon as the downloader reports that episode finished. diff --git a/README.md b/README.md index bcb94ef..ae69f6e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Kaizoku is a local web app for searching, tracking, and downloading anime from A - Save files with Jellyfin-friendly layout: `TV/Series Name/Season 01/Series Name - S01E01.mp4`. - Save English external subtitles when the provider exposes usable subtitle tracks. - Manage focused watchlists for `Watching`, `Planned`, `Finished`, and `Dropped`, with provider source tags and title links back to the original provider page. -- Periodically refresh selected watchlists and auto-download newly available episodes. +- Periodically refresh selected watchlists with a single progress bar and auto-download newly available episodes. - Sync watchlist download flags from the filesystem. - Hand completed libraries to Jellyfin TV or movie folders. - Export/import watchlist backups. diff --git a/VERSION b/VERSION index 07bd608..cac7789 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.52.14 +0.52.15 diff --git a/test_app.py b/test_app.py index 6911b7f..ff830b1 100644 --- a/test_app.py +++ b/test_app.py @@ -4301,6 +4301,15 @@ class TemplateHelperTests(unittest.TestCase): self.assertIn("else if (state.queuedCount > 0)", APP.WATCHLIST_HTML) self.assertIn("fetchWatchlist(state.watchlistPage, { silent: true })", APP.WATCHLIST_HTML) + def test_watchlist_page_renders_single_refresh_progress_bar(self): + self.assertIn('class="refresh-progress" id="refreshJobStatus"', APP.WATCHLIST_HTML) + self.assertIn('class="refresh-progress-fill" id="refreshJobFill"', APP.WATCHLIST_HTML) + self.assertIn("function refreshJobPercent(job)", APP.WATCHLIST_HTML) + self.assertIn('$("refreshJobFill").style.width = `${percent}%`;', APP.WATCHLIST_HTML) + self.assertIn('`Refreshing ${job.current_title}`', APP.WATCHLIST_HTML) + self.assertEqual(APP.WATCHLIST_HTML.count('id="refreshJobStatus"'), 1) + self.assertNotIn("refreshed${job.current_title", APP.WATCHLIST_HTML) + def test_watchlist_page_highlights_fully_ready_cards(self): self.assertIn(".anime-card.fully-ready", APP.WATCHLIST_HTML) self.assertIn("const isFullyReady = item.sub_complete && item.dub_complete;", APP.WATCHLIST_HTML) diff --git a/watchlist_page.py b/watchlist_page.py index 641d33f..415bbbe 100644 --- a/watchlist_page.py +++ b/watchlist_page.py @@ -254,6 +254,44 @@ WATCHLIST_HTML = r""" color: #ddd0ff; font-size: 13px; } + .refresh-progress { + display: none; + gap: 7px; + min-width: 0; + } + .refresh-progress.visible { + display: grid; + } + .refresh-progress-header { + align-items: center; + color: var(--muted); + display: flex; + font-size: 12px; + font-weight: 700; + justify-content: space-between; + gap: 10px; + text-transform: uppercase; + } + .refresh-progress-track { + background: rgba(255, 255, 255, 0.08); + border: 1px solid var(--line); + border-radius: 999px; + height: 11px; + overflow: hidden; + } + .refresh-progress-fill { + background: linear-gradient(90deg, var(--accent), var(--accent-2)); + border-radius: inherit; + height: 100%; + transition: width 0.2s ease; + width: 0%; + } + .refresh-progress-current { + color: #ddd0ff; + font-size: 13px; + min-height: 20px; + overflow-wrap: anywhere; + } .field-hint { color: var(--muted); font-size: 12px; @@ -716,6 +754,16 @@ WATCHLIST_HTML = r"""
+
+
+ Refresh progress + 0 / 0 +
+ +
+
0 @@ -737,7 +785,6 @@ WATCHLIST_HTML = r"""

Watchlist library

Compact poster grid for faster scanning.

-

@@ -867,18 +914,40 @@ WATCHLIST_HTML = r""" button.textContent = running ? "Refreshing..." : "Refresh All"; } + function refreshJobPercent(job) { + const total = Number((job || {}).total || 0); + if (total <= 0) return 0; + const completed = Math.max(0, Math.min(total, Number((job || {}).completed || 0))); + return Math.round((completed / total) * 100); + } + function setRefreshJobStatus(job) { const el = $("refreshJobStatus"); if (!el) return; if (!job) { - el.textContent = ""; + el.classList.remove("visible"); + $("refreshJobFill").style.width = "0%"; + $("refreshJobCount").textContent = "0 / 0"; + $("refreshJobCurrent").textContent = ""; + return; + } + const completed = Number(job.completed || 0); + const total = Number(job.total || 0); + const percent = refreshJobPercent(job); + el.classList.toggle("visible", job.status === "running" || job.status === "pending"); + $("refreshJobFill").style.width = `${percent}%`; + $("refreshJobCount").textContent = `${completed} / ${total}`; + if (job.status === "pending") { + $("refreshJobCurrent").textContent = job.message || "Waiting to start refresh."; return; } if (job.status === "running") { - el.textContent = `${job.completed || 0}/${job.total || 0} refreshed${job.current_title ? ` ยท ${job.current_title}` : ""}`; + $("refreshJobCurrent").textContent = job.current_title + ? `Refreshing ${job.current_title}` + : "Refreshing watchlist entries"; return; } - el.textContent = job.message || ""; + $("refreshJobCurrent").textContent = ""; } function coverInitials(title) { @@ -1497,7 +1566,7 @@ WATCHLIST_HTML = r""" setRefreshAllButton(running); setRefreshJobStatus(job); if (running) { - setNotice(job.message || "Refreshing all watchlist entries..."); + setNotice(""); } else if (job && wasRunning && refreshOnComplete) { const message = job.message || "Watchlist refreshed."; await fetchWatchlist();