Load Live channels from a filtered SQLite cursor API, keep navigation and guide consumers working across pages, and retain the QA reproduction coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
3.1 KiB
JavaScript
82 lines
3.1 KiB
JavaScript
/**
|
|
* Guide Page Controller
|
|
*/
|
|
|
|
class GuidePage {
|
|
constructor(app) {
|
|
this.app = app;
|
|
}
|
|
|
|
async init() {
|
|
// EPG guide will lazy load when shown
|
|
}
|
|
|
|
async loadCatalog() {
|
|
const channelList = this.app.channelList;
|
|
const revision = API.liveCatalogRevision || 0;
|
|
if (channelList.guideCatalog && channelList.guideCatalogRevision === revision) return;
|
|
if (this.catalogLoad?.revision === revision) return this.catalogLoad.promise;
|
|
const promise = (async () => {
|
|
const channels = [];
|
|
let cursor;
|
|
do {
|
|
// Guide ignores group visibility; EpgGuide filters individually hidden channels.
|
|
const result = await API.channels.liveCatalog({ limit: 500, cursor, includeHidden: true });
|
|
if (revision !== (API.liveCatalogRevision || 0)) throw new Error('Catalogue changed');
|
|
channels.push(...result.items);
|
|
const next = result.page.hasMore ? result.page.nextCursor : null;
|
|
if (result.page.hasMore && (!next || next === cursor)) throw new Error('Invalid catalogue cursor');
|
|
cursor = next;
|
|
} while (cursor);
|
|
await channelList.loadHiddenItems();
|
|
if (revision !== (API.liveCatalogRevision || 0)) throw new Error('Catalogue changed');
|
|
channelList.guideCatalog = channels;
|
|
channelList.guideCatalogRevision = revision;
|
|
})();
|
|
this.catalogLoad = { revision, promise };
|
|
try { await promise; }
|
|
finally {
|
|
if (this.catalogLoad?.promise === promise) this.catalogLoad = null;
|
|
}
|
|
}
|
|
|
|
async show() {
|
|
const visit = this.visit = (this.visit || 0) + 1;
|
|
const container = this.app.epgGuide.container;
|
|
const channelList = this.app.channelList;
|
|
const needsCatalog = !channelList.guideCatalog || channelList.guideCatalogRevision !== (API.liveCatalogRevision || 0);
|
|
if (needsCatalog) {
|
|
container.textContent = 'Loading…';
|
|
container.setAttribute('aria-busy', 'true');
|
|
}
|
|
try {
|
|
await this.loadCatalog();
|
|
if (visit !== this.visit) return;
|
|
if (!this.app.epgGuide.programmes?.length) {
|
|
await this.app.epgGuide.loadEpg();
|
|
} else {
|
|
this.app.epgGuide.render();
|
|
}
|
|
} catch (err) {
|
|
if (visit !== this.visit) return;
|
|
container.textContent = 'Error loading channels';
|
|
const retry = document.createElement('button');
|
|
retry.type = 'button';
|
|
retry.className = 'btn btn-sm';
|
|
retry.textContent = 'Retry';
|
|
retry.addEventListener('click', () => this.show());
|
|
container.appendChild(retry);
|
|
} finally {
|
|
if (visit === this.visit) container.setAttribute('aria-busy', 'false');
|
|
}
|
|
}
|
|
|
|
hide() {
|
|
this.visit = (this.visit || 0) + 1;
|
|
this.app.epgGuide.container.setAttribute('aria-busy', 'false');
|
|
}
|
|
|
|
}
|
|
|
|
window.GuidePage = GuidePage;
|