fix(live): keep channel search responsive (#25)
Filter projected live channels locally in the browser and apply server-side search and language filters before costly enrichment work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -189,13 +189,14 @@ class ChannelList {
|
||||
}
|
||||
|
||||
init() {
|
||||
// Search handler (debounced)
|
||||
// Search locally whenever a catalog is already available.
|
||||
let searchTimeout;
|
||||
this.searchInput.addEventListener('input', () => {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
this.loadChannels({ refreshLanguages: false });
|
||||
}, 300);
|
||||
if (this.projectedLogicalChannels || this.channels.length) this.render();
|
||||
else this.loadChannels({ refreshLanguages: false });
|
||||
}, this.projectedLogicalChannels || this.channels.length ? 75 : 250);
|
||||
});
|
||||
|
||||
// Source filter handler
|
||||
@ -205,7 +206,18 @@ class ChannelList {
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', event => {
|
||||
if (event.key === 'Escape') this.closeLanguagePanel();
|
||||
if (event.key !== 'Escape') return;
|
||||
const panel = this.languageFilter?.querySelector('.language-more-panel');
|
||||
if (panel && !panel.hidden) {
|
||||
this.closeLanguagePanel();
|
||||
return;
|
||||
}
|
||||
if (this.searchInput?.value) {
|
||||
this.searchInput.value = '';
|
||||
clearTimeout(searchTimeout);
|
||||
this.render();
|
||||
this.searchInput.focus();
|
||||
}
|
||||
});
|
||||
|
||||
this.toggleGroupsBtn?.addEventListener('click', () => {
|
||||
@ -496,15 +508,10 @@ class ChannelList {
|
||||
const groupedChannels = {};
|
||||
|
||||
// 1. Filter
|
||||
this.filteredChannels = this.logicalChannels;
|
||||
if (searchTerm && !this.projectedLogicalChannels) {
|
||||
this.filteredChannels = this.logicalChannels.filter(ch =>
|
||||
String(ch.displayName ?? '').toLowerCase().includes(searchTerm) ||
|
||||
ch.variants.some(variant => String(variant.name ?? '').toLowerCase().includes(searchTerm)) ||
|
||||
ch.variants.some(variant => String(variant.groupTitle ?? '').toLowerCase().includes(searchTerm)) ||
|
||||
ch.variants.some(variant => `${variant.language.code} ${variant.language.label}`.toLowerCase().includes(searchTerm))
|
||||
);
|
||||
}
|
||||
this.filteredChannels = this.logicalChannels.filter(channel => {
|
||||
const languageMatches = !this.selectedLanguages.size || this.selectedLanguages.has(channel.language?.code);
|
||||
return languageMatches && LiveChannelProjection.matchesSearch(channel, searchTerm);
|
||||
});
|
||||
|
||||
let filteredChannels = this.filteredChannels;
|
||||
if (this.listStatus) this.listStatus.textContent = `${filteredChannels.length} channel entries shown`;
|
||||
@ -525,7 +532,7 @@ class ChannelList {
|
||||
});
|
||||
|
||||
// 3. Add Favorites
|
||||
const favoritedChannels = this.logicalChannels.filter(ch => ch.isFavorite);
|
||||
const favoritedChannels = this.filteredChannels.filter(ch => ch.isFavorite);
|
||||
if (favoritedChannels.length > 0) {
|
||||
favoritedChannels.sort((a, b) => a.name.localeCompare(b.name));
|
||||
groupedChannels['Favorites'] = favoritedChannels;
|
||||
@ -1057,33 +1064,22 @@ class ChannelList {
|
||||
|
||||
async loadProjectedChannels(sourceId = null) {
|
||||
try {
|
||||
let response = await API.live.getChannels({
|
||||
const response = await API.live.getChannels({
|
||||
sourceId,
|
||||
includeHidden: true,
|
||||
q: this.searchInput?.value?.trim() || '',
|
||||
languages: [...this.selectedLanguages]
|
||||
q: '',
|
||||
languages: []
|
||||
});
|
||||
if (!LiveChannelProjection.canUse(response)) {
|
||||
this.projectedLogicalChannels = null;
|
||||
this.projectionNotice = 'Channel list is updating. Using the standard channel list for now.';
|
||||
return false;
|
||||
}
|
||||
let mapped = LiveChannelProjection.mapResponse(response, this.sources);
|
||||
const mapped = LiveChannelProjection.mapResponse(response, this.sources);
|
||||
this.channels = mapped.channels;
|
||||
this.projectedLogicalChannels = mapped.logicalChannels;
|
||||
this.availableLanguages = mapped.availableLanguages;
|
||||
if (this.reconcileLanguageSelection()) {
|
||||
response = await API.live.getChannels({
|
||||
sourceId,
|
||||
includeHidden: true,
|
||||
q: this.searchInput?.value?.trim() || '',
|
||||
languages: [...this.selectedLanguages]
|
||||
});
|
||||
mapped = LiveChannelProjection.mapResponse(response, this.sources);
|
||||
this.channels = mapped.channels;
|
||||
this.projectedLogicalChannels = mapped.logicalChannels;
|
||||
this.availableLanguages = mapped.availableLanguages;
|
||||
}
|
||||
this.reconcileLanguageSelection();
|
||||
this.renderLanguageFilter();
|
||||
this.visibleFavorites = mapped.favorites;
|
||||
this.hiddenItems = mapped.hiddenItems;
|
||||
@ -1127,7 +1123,8 @@ class ChannelList {
|
||||
sessionStorage.setItem(this.languageStorageKey(), JSON.stringify([...this.selectedLanguages]));
|
||||
} catch {}
|
||||
this.renderLanguageFilter();
|
||||
this.loadChannels({ refreshLanguages: false });
|
||||
if (this.projectedLogicalChannels || this.channels.length) this.render();
|
||||
else this.loadChannels({ refreshLanguages: false });
|
||||
}
|
||||
|
||||
closeLanguagePanel() {
|
||||
|
||||
Reference in New Issue
Block a user