diff --git a/webui/static/video/video-get-modal.js b/webui/static/video/video-get-modal.js
index 38dbf04b..8b00c307 100644
--- a/webui/static/video/video-get-modal.js
+++ b/webui/static/video/video-get-modal.js
@@ -136,7 +136,14 @@
// Collapse/expand a season (ignore clicks on its checkbox).
var sh = e.target.closest('[data-vgm-season-toggle]');
if (sh && !e.target.closest('.vgm-season-check')) {
- sh.parentNode.classList.toggle('vgm-season--open');
+ var seasonEl = sh.parentNode;
+ var opening = !seasonEl.classList.contains('vgm-season--open');
+ seasonEl.classList.toggle('vgm-season--open');
+ // First expand of an un-owned (tmdb) season → fetch its episodes.
+ if (opening && seasonEl.getAttribute('data-vgm-lazy') === '1' &&
+ !seasonEl.getAttribute('data-vgm-loaded')) {
+ loadSeason(seasonEl);
+ }
return;
}
if (e.target.closest('[data-vgm-wishlist]')) {
@@ -256,17 +263,36 @@
badge + '';
}
- function renderEpisodes(d) {
+ function renderEpisodes(d, o) {
var wrap = modalEl.querySelector('[data-vgm-eps]'); if (!wrap) return;
if (!d.seasons || !d.seasons.length) { wrap.hidden = true; return; }
var today = isoToday();
- modalState = { kind: 'show', sel: new Set() };
- var totalMissing = 0;
+ // For an un-owned (tmdb) show the episodes aren't shipped with the detail —
+ // they load per-season on expand (like the full detail page). Stash the tv
+ // id so the lazy loader can fetch them.
+ var tvId = (o && o.source === 'tmdb') ? parseInt(o.id, 10) : null;
+ modalState = { kind: 'show', sel: new Set(), tvId: tvId, today: today };
+ var totalMissing = 0, anyLazy = false;
var html = '
Episodes' +
'
';
d.seasons.forEach(function (s) {
var eps = s.episodes || [];
+ // Lazy: a tmdb season with a known count but no episodes shipped yet.
+ if (!eps.length && (s.episode_total || 0) > 0 && tvId) {
+ anyLazy = true;
+ totalMissing += s.episode_total; // all wanted → never "all owned"
+ html += '
';
// When you own everything, the missing-only view would be blank — say so.
- if (totalMissing === 0) {
+ // (Skipped for lazy/un-owned shows: nothing is owned, episodes load on expand.)
+ if (totalMissing === 0 && !anyLazy) {
html += '
✓ You have every episode. Turn off “Missing only” to re-download.
';
}
wrap.innerHTML = html;
@@ -294,6 +321,38 @@
d.seasons.forEach(function (s) { syncSeasonCheck(modalEl, s.season_number); });
}
+ // Fetch a tmdb season's episodes the first time it's expanded, render them,
+ // and pre-select the missing-aired ones (everything un-owned that has aired).
+ function loadSeason(seasonEl) {
+ if (!modalState || !modalState.tvId || !seasonEl) return;
+ var sn = parseInt(seasonEl.getAttribute('data-vgm-season'), 10);
+ seasonEl.setAttribute('data-vgm-loaded', '1'); // guard double-fetch
+ var body = seasonEl.querySelector('.vgm-season-eps');
+ if (body) body.innerHTML = '
Loading episodes…
';
+ fetch('/api/video/tmdb/show/' + modalState.tvId + '/season/' + sn, { headers: { Accept: 'application/json' } })
+ .then(function (r) { return r.ok ? r.json() : null; })
+ .then(function (data) {
+ if (!modalEl || !seasonEl.parentNode) return;
+ var eps = (data && data.episodes) || [];
+ var b = seasonEl.querySelector('.vgm-season-eps');
+ if (!eps.length) { if (b) b.innerHTML = '
No episode info available.
'; return; }
+ var today = modalState.today;
+ if (b) b.innerHTML = eps.map(function (e) { return epRow(sn, e, today); }).join('');
+ eps.forEach(function (e) {
+ if (epState(e, today) === 'missing') modalState.sel.add(sn + '_' + e.episode_number);
+ });
+ var all = seasonEl.querySelector('[data-vgm-season-all]');
+ if (all) all.disabled = false;
+ syncSeasonCheck(modalEl, sn);
+ updateFooter();
+ })
+ .catch(function () {
+ var b = seasonEl.querySelector('.vgm-season-eps');
+ if (b) b.innerHTML = '