video dashboard: Library card is a live scan tool (parity with music)

The Refresh/Deep Scan buttons already fired a scan, but the card gave no
feedback so it looked dead. Now it mirrors music's dashboard library card:
- a progress section (phase + bar + detail) appears during a scan, driven by
  the shared scan events (real percent);
- buttons disable while scanning;
- the card hydrates on load/return — if a scan is already running, video-scan.js
  re-emits progress and the card shows it;
- stats refresh when the scan finishes.
Reuses music's .library-status-progress classes. 84 tests green.
This commit is contained in:
BoulderBadgeDad 2026-06-14 09:28:14 -07:00
parent 68582af374
commit be141d0073
3 changed files with 45 additions and 3 deletions

View file

@ -184,6 +184,8 @@ def test_dashboard_library_card_has_refresh_and_deep_buttons():
assert 'data-video-scan-mode="full"' in block # Refresh
assert 'data-video-scan-mode="deep"' in block # Deep Scan
assert "library-status-actions" in block
# The card shows live scan progress (parity with the music dashboard).
assert "data-video-dash-progress" in block and "data-video-dash-bar" in block
def test_scan_module_referenced_and_isolated():

View file

@ -608,6 +608,11 @@
</div>
</div>
</div>
<div class="library-status-progress hidden" data-video-dash-progress>
<div class="library-status-phase" data-video-dash-phase>Scanning&hellip;</div>
<div class="library-status-bar"><div class="library-status-bar-fill" data-video-dash-bar style="width: 0%;"></div></div>
<div class="library-status-progress-detail" data-video-dash-detail>0 movies, 0 shows</div>
</div>
</div>
</div>
</article>

View file

@ -117,8 +117,43 @@
loadStats();
}
// ── Library card: live scan progress (parity with the music dashboard) ──
// The scan buttons (data-video-scan-mode) are wired by video-scan.js; here
// we reflect progress on the card and hydrate if a scan is already running
// (video-scan.js re-emits the progress event on load).
function dashButtons() {
return document.querySelectorAll(
'.video-subpage[data-video-subpage="video-dashboard"] [data-video-scan-mode]');
}
function onDashScanProgress(e) {
var s = e.detail || {};
if (s.state !== 'scanning') return;
var prog = document.querySelector('[data-video-dash-progress]');
if (prog) prog.classList.remove('hidden');
var phase = (s.phase || 'scanning');
var phaseEl = document.querySelector('[data-video-dash-phase]');
if (phaseEl) phaseEl.textContent = phase.charAt(0).toUpperCase() + phase.slice(1);
var bar = document.querySelector('[data-video-dash-bar]');
if (bar) bar.style.width = (s.percent != null ? s.percent : 100) + '%';
var detail = document.querySelector('[data-video-dash-detail]');
if (detail) {
detail.textContent = (s.movies || 0) + ' movies, ' + (s.shows || 0) + ' shows'
+ (s.percent != null ? ' · ' + s.percent + '%' : '');
}
var btns = dashButtons();
for (var i = 0; i < btns.length; i++) btns[i].disabled = true;
}
function onDashScanDone() {
var prog = document.querySelector('[data-video-dash-progress]');
if (prog) prog.classList.add('hidden');
var btns = dashButtons();
for (var i = 0; i < btns.length; i++) btns[i].disabled = false;
loadStats();
}
document.addEventListener('soulsync:video-page-shown', onPageShown);
// Dashboard scan buttons (data-video-scan-mode) are wired by video-scan.js;
// refresh the live counts whenever any scan finishes.
document.addEventListener('soulsync:video-scan-done', function () { loadStats(); });
document.addEventListener('soulsync:video-scan-progress', onDashScanProgress);
document.addEventListener('soulsync:video-scan-done', onDashScanDone);
})();