OMDb now has the same setup as TMDB/TVDB: a yellow dashboard orb (★ glyph) that spins/idles in the worker-orb animation, an entry in Manage Workers (Ratings coverage cards, pause/resume, retry, search), and a BACKGROUND ratings pass. - Worker 'ratings mode' (is_ratings): instead of a match queue it pulls ratings_next() (library items with an imdb_id and ratings_synced=0), fetches IMDb/RT/Metacritic, applies + marks synced. So the whole library gets ratings, not just titles you open (schema v7: ratings_synced). - enrichment_breakdown/unmatched/retry get an 'omdb' branch (coverage = ratings-filled, not matched). build_clients includes omdb; the lazy on-view backfill uses the omdb worker's client. - Dashboard orb + Manage Workers entry (★ glyph fallback where there's no logo), yellow accent. Seam tests: omdb worker rates the queue (ratings mode), ratings breakdown.
105 lines
4.1 KiB
JavaScript
105 lines
4.1 KiB
JavaScript
/*
|
|
* SoulSync — Video enrichment dashboard buttons (isolated).
|
|
*
|
|
* Polls /api/video/enrichment/<svc>/status and reflects it on the dashboard
|
|
* TMDB/TVDB buttons (spin while running, tooltip status). Click toggles
|
|
* pause/resume. The Manage Workers button fires 'soulsync:video-open-workers'
|
|
* for the (isolated) video enrichment modal. Music code is never touched.
|
|
*
|
|
* Self-contained IIFE, no globals, no inline handlers. Only polls on the video
|
|
* side so the video engine isn't spun up while the user is on the music side.
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
|
|
var SERVICES = ['tmdb', 'tvdb', 'omdb'];
|
|
|
|
function onVideoSide() {
|
|
return document.body.getAttribute('data-side') === 'video';
|
|
}
|
|
|
|
function btn(svc) { return document.querySelector('[data-video-enrich="' + svc + '"]'); }
|
|
|
|
function setText(root, sel, text) {
|
|
var n = root.querySelector(sel);
|
|
if (n) n.textContent = text;
|
|
}
|
|
|
|
function reflect(svc, d) {
|
|
var b = btn(svc);
|
|
if (!b) return;
|
|
b.classList.remove('active', 'paused', 'complete', 'disabled');
|
|
if (!d.enabled) b.classList.add('disabled');
|
|
else if (d.idle) b.classList.add('complete');
|
|
else if (d.running && !d.paused) b.classList.add('active');
|
|
else if (d.paused) b.classList.add('paused');
|
|
|
|
var tip = document.querySelector('[data-video-enrich-tooltip="' + svc + '"]');
|
|
if (!tip) return;
|
|
var status = !d.enabled ? 'Not configured'
|
|
: d.idle ? 'Complete'
|
|
: (d.running && !d.paused) ? 'Running'
|
|
: d.paused ? 'Paused' : 'Idle';
|
|
setText(tip, '[data-video-enrich-status]', status);
|
|
var cur = (d.current_item && d.current_item.name)
|
|
? ((d.current_item.type || 'item') + ': ' + d.current_item.name)
|
|
: (d.idle ? 'All matched' : 'No active matches');
|
|
setText(tip, '[data-video-enrich-current]', cur);
|
|
var matched = 0, total = 0, prog = d.progress || {};
|
|
for (var k in prog) {
|
|
if (Object.prototype.hasOwnProperty.call(prog, k)) {
|
|
matched += prog[k].matched || 0;
|
|
total += prog[k].total || 0;
|
|
}
|
|
}
|
|
setText(tip, '[data-video-enrich-progress]', 'Progress: ' + matched + ' / ' + total);
|
|
|
|
// Feed the idle worker-orbs animation real telemetry (isolated; no-op if
|
|
// the orbs script isn't present). Drives the inbound pulses to the hub.
|
|
if (window.videoWorkerOrbs && typeof window.videoWorkerOrbs.onStatus === 'function') {
|
|
window.videoWorkerOrbs.onStatus(svc, d);
|
|
}
|
|
}
|
|
|
|
function pollOne(svc) {
|
|
fetch('/api/video/enrichment/' + svc + '/status', { headers: { 'Accept': 'application/json' } })
|
|
.then(function (r) { return r.ok ? r.json() : null; })
|
|
.then(function (d) { if (d && !d.error) reflect(svc, d); })
|
|
.catch(function () { /* ignore */ });
|
|
}
|
|
|
|
function poll() {
|
|
if (onVideoSide() && !document.hidden) SERVICES.forEach(pollOne);
|
|
}
|
|
|
|
function toggle(svc) {
|
|
var b = btn(svc);
|
|
if (!b || b.classList.contains('disabled')) return;
|
|
var action = b.classList.contains('paused') ? 'resume' : 'pause';
|
|
fetch('/api/video/enrichment/' + svc + '/' + action,
|
|
{ method: 'POST', headers: { 'Accept': 'application/json' } })
|
|
.then(function () { setTimeout(function () { pollOne(svc); }, 200); })
|
|
.catch(function () { /* ignore */ });
|
|
}
|
|
|
|
function init() {
|
|
SERVICES.forEach(function (svc) {
|
|
var b = btn(svc);
|
|
if (b) b.addEventListener('click', function () { toggle(svc); });
|
|
});
|
|
var manage = document.querySelector('[data-video-manage-workers]');
|
|
if (manage) {
|
|
manage.addEventListener('click', function () {
|
|
document.dispatchEvent(new CustomEvent('soulsync:video-open-workers'));
|
|
});
|
|
}
|
|
poll();
|
|
setInterval(poll, 2500);
|
|
}
|
|
|
|
if (document.readyState === 'loading') {
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
} else {
|
|
init();
|
|
}
|
|
})();
|