diff --git a/public/js/components/ChannelList.js b/public/js/components/ChannelList.js index 1256b7c..b52a99a 100644 --- a/public/js/components/ChannelList.js +++ b/public/js/components/ChannelList.js @@ -16,6 +16,7 @@ class ChannelList { this.groups = []; this.hiddenItems = new Set(); // Set<"type:sourceId:itemId"> this.collapsedGroups = new Set(); // Track collapsed groups + this._userExpandedGroups = new Set(); // Track groups user has explicitly expanded this.favorites = []; // Array of favorite objects this.visibleFavorites = new Set(); // Set<"sourceId:channelId"> this.currentChannel = null; @@ -48,9 +49,13 @@ class ChannelList { const saved = localStorage.getItem('nodecast_tv_collapsed_groups'); if (saved) { this.collapsedGroups = new Set(JSON.parse(saved)); + this._hasCollapsedState = true; + } else { + this._hasCollapsedState = false; // First load - will collapse all by default } } catch (err) { console.error('Error loading collapsed state:', err); + this._hasCollapsedState = false; } } @@ -71,8 +76,12 @@ class ChannelList { toggleGroup(groupName) { if (this.collapsedGroups.has(groupName)) { this.collapsedGroups.delete(groupName); + // Track that user explicitly expanded this group + this._userExpandedGroups.add(groupName); } else { this.collapsedGroups.add(groupName); + // User collapsed it, remove from expanded tracking + this._userExpandedGroups.delete(groupName); } this.saveCollapsedState(); } @@ -83,7 +92,17 @@ class ChannelList { expandAll() { this.collapsedGroups.clear(); this.saveCollapsedState(); - this.container.querySelectorAll('.group-header.collapsed').forEach(h => h.classList.remove('collapsed')); + + // Expand all and render channels for empty containers + this.container.querySelectorAll('.group-header.collapsed').forEach(h => { + h.classList.remove('collapsed'); + const groupName = h.dataset.group; + const groupEl = h.closest('.channel-group'); + const channelsContainer = groupEl?.querySelector('.group-channels'); + if (channelsContainer && channelsContainer.children.length === 0) { + this.renderGroupChannels(groupName, channelsContainer); + } + }); // Update toggle button if (this.toggleGroupsBtn) { @@ -334,6 +353,18 @@ class ChannelList { this.groupedChannels = groupedChannels; this.showHidden = showHidden; + // Collapse all groups by default on first load (for large playlists) + // This prevents rendering 100K+ channel items on initial load + if (!this._hasCollapsedState && this.sortedGroups.length > 0) { + this.sortedGroups.forEach(groupName => { + if (groupName !== 'Favorites') { + this.collapsedGroups.add(groupName); + } + }); + this._hasCollapsedState = true; + this.saveCollapsedState(); + } + // Build rendered channel list for navigation (matches visual order) this.renderedChannels = []; this.sortedGroups.forEach(groupName => { @@ -431,6 +462,12 @@ class ChannelList { // Skip group if no visible channels (derived visibility) if (visibleChannels.length === 0) continue; + // Default new groups to collapsed (except Favorites) + // This handles groups loaded via scroll that weren't in the initial collapse + if (!isFavoritesGroup && !this.collapsedGroups.has(groupName) && !this._userExpandedGroups?.has(groupName)) { + this.collapsedGroups.add(groupName); + } + html += `