/** * 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;