feat(ui): surface live language filters and built-in EPG sources (#23)

Add language filter controls for projected live channels, preserve backend language views in the browser, and separate built-in EPG sources from custom sources.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
blomios
2026-10-06 11:42:23 +02:00
parent 2a1c745a26
commit 638a31d9ef
9 changed files with 425 additions and 25 deletions

View File

@ -8,6 +8,7 @@ class SourceManager {
this.xtreamList = document.getElementById('xtream-list');
this.m3uList = document.getElementById('m3u-list');
this.epgList = document.getElementById('epg-list');
this.builtinEpgList = document.getElementById('builtin-epg-list');
// Content browser state
this.contentType = 'channels'; // 'channels' or 'movies'
@ -114,7 +115,9 @@ class SourceManager {
this.renderSourceList(this.xtreamList, sources.filter(s => s.type === 'xtream'), 'xtream');
this.renderSourceList(this.m3uList, sources.filter(s => s.type === 'm3u'), 'm3u');
this.renderSourceList(this.epgList, sources.filter(s => s.type === 'epg'), 'epg');
const epgSources = sources.filter(s => s.type === 'epg');
this.renderBuiltinEpgSources(epgSources.filter(source => source.builtinKey));
this.renderSourceList(this.epgList, epgSources.filter(source => !source.builtinKey), 'epg');
} catch (err) {
console.error('Error loading sources:', err);
}
@ -162,6 +165,33 @@ class SourceManager {
});
}
renderBuiltinEpgSources(sources) {
if (!this.builtinEpgList) return;
if (!sources.length) {
this.builtinEpgList.innerHTML = '<p class="hint">No built-in EPG sources available</p>';
return;
}
this.builtinEpgList.innerHTML = sources.map(source => {
const coverage = [source.language?.toUpperCase(), source.country].filter(Boolean).join(' · ');
return `<div class="source-item builtin-source ${source.enabled ? '' : 'disabled'}" data-id="${source.id}">
<span class="source-icon">${Icons.series}</span>
<div class="source-info">
<div class="source-name">${source.name} <span class="source-badge">Built-in</span></div>
<div class="source-meta">Free EPG data${coverage ? ` · ${coverage}` : ''}</div>
</div>
<div class="source-actions">
<button class="btn btn-sm btn-secondary" data-action="refresh" title="Refresh Data" aria-label="Refresh ${source.name}">${Icons.refresh}</button>
<button class="btn btn-sm btn-secondary" data-action="toggle" aria-pressed="${source.enabled}" title="${source.enabled ? 'Disable' : 'Enable'}">${source.enabled ? 'Disable' : 'Enable'}</button>
</div>
</div>`;
}).join('');
this.builtinEpgList.querySelectorAll('.source-item').forEach(item => {
const id = Number(item.dataset.id);
item.querySelector('[data-action="refresh"]')?.addEventListener('click', () => this.refreshSource(id, 'epg'));
item.querySelector('[data-action="toggle"]')?.addEventListener('click', () => this.toggleSource(id));
});
}
/**
* Show add source modal
*/