diff --git a/public/css/main.css b/public/css/main.css index c133a42..573a399 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -1163,6 +1163,74 @@ kbd { border-top: 1px solid var(--color-border); } +/* EPG Info Modal */ +.epg-info-modal .channel-details { + display: flex; + gap: var(--space-md); + margin-bottom: var(--space-md); + padding-bottom: var(--space-md); + border-bottom: 1px solid var(--color-border); +} + +.epg-info-modal .channel-logo { + width: 64px; + height: 64px; + object-fit: contain; + background: var(--color-bg-tertiary); + border-radius: var(--radius-md); +} + +.epg-info-modal .channel-meta p { + margin: var(--space-xs) 0; + font-size: 0.875rem; + color: var(--color-text-secondary); +} + +.epg-info-modal h4 { + margin: var(--space-md) 0 var(--space-sm); + font-size: 1rem; + color: var(--color-text-primary); +} + +.epg-program-list { + max-height: 300px; + overflow-y: auto; +} + +.epg-program { + padding: var(--space-sm); + margin-bottom: var(--space-xs); + background: var(--color-bg-tertiary); + border-radius: var(--radius-sm); +} + +.epg-program.current { + background: var(--color-primary-bg); + border-left: 3px solid var(--color-primary); +} + +.epg-program-time { + font-size: 0.75rem; + color: var(--color-text-muted); +} + +.epg-program-title { + font-weight: 500; + color: var(--color-text-primary); +} + +.epg-program-desc { + font-size: 0.8rem; + color: var(--color-text-secondary); + margin-top: var(--space-xs); +} + +.no-programs { + color: var(--color-text-muted); + text-align: center; + padding: var(--space-lg); +} + /* Form Elements */ .form-group { margin-bottom: var(--space-md); diff --git a/public/index.html b/public/index.html index fad8a46..8b412b8 100644 --- a/public/index.html +++ b/public/index.html @@ -381,7 +381,6 @@
diff --git a/public/js/components/ChannelList.js b/public/js/components/ChannelList.js index 143af70..1c8f853 100644 --- a/public/js/components/ChannelList.js +++ b/public/js/components/ChannelList.js @@ -116,10 +116,18 @@ class ChannelList { } // Context menu handlers - document.addEventListener('click', () => this.hideContextMenu()); + document.addEventListener('click', (e) => { + // Don't close if clicking inside context menu + if (!this.contextMenu.contains(e.target)) { + this.hideContextMenu(); + } + }); this.contextMenu.querySelectorAll('.context-item').forEach(item => { - item.addEventListener('click', (e) => this.handleContextAction(e)); + item.addEventListener('click', (e) => { + e.stopPropagation(); // Prevent document click from firing + this.handleContextAction(e); + }); }); // Intersection Observer for lazy loading @@ -852,6 +860,7 @@ class ChannelList { this.contextMenu.dataset.type = type; this.contextMenu.dataset.sourceId = data.sourceId; this.contextMenu.dataset.itemId = type === 'group' ? data.group : data.channelId; + this.contextMenu.dataset.streamId = data.streamId || ''; this.contextMenu.style.left = `${e.clientX}px`; this.contextMenu.style.top = `${e.clientY}px`; @@ -870,7 +879,7 @@ class ChannelList { */ async handleContextAction(e) { const action = e.target.dataset.action; - const { type, sourceId, itemId } = this.contextMenu.dataset; + const { type, sourceId, itemId, streamId } = this.contextMenu.dataset; switch (action) { case 'play': @@ -882,19 +891,104 @@ class ChannelList { } break; case 'hide': - await API.channels.hide(parseInt(sourceId), type, itemId); - this.hiddenItems.add(`${type}:${sourceId}:${itemId}`); + // Use streamId for hiding Xtream channels (raw ID, not composite) + // Server expects 'channel' type, not 'live' + const hideId = streamId || itemId; + await API.channels.hide(parseInt(sourceId), 'channel', hideId); + this.hiddenItems.add(`channel:${sourceId}:${hideId}`); this.render(); break; case 'epg': // Show EPG info modal - this.showEpgInfo(itemId); + this.showEpgInfo(sourceId, itemId, streamId); break; } this.hideContextMenu(); } + /** + * Show EPG info for a channel + */ + showEpgInfo(sourceId, channelId, streamId) { + const channel = this.channels.find(c => c.id === channelId); + if (!channel) { + alert('Channel not found'); + return; + } + + const modal = document.getElementById('modal'); + const modalTitle = document.getElementById('modal-title'); + const modalBody = document.getElementById('modal-body'); + + if (!modal || !modalTitle || !modalBody) return; + + modalTitle.textContent = `📋 ${channel.name} - EPG Info`; + + // Get current and upcoming programs + let programsHtml = 'No EPG data available for this channel.
'; + + if (window.app?.epgGuide) { + const tvgKey = channel.tvgId || channel.name; + const currentProgram = window.app.epgGuide.getCurrentProgram(channel.tvgId, channel.name); + const programs = window.app.epgGuide.getChannelPrograms?.(tvgKey) || []; + + if (currentProgram || programs.length > 0) { + programsHtml = '
+
+