/** * Home Dashboard Page * Features "Continue Watching" and "Recently Added" content */ class HomePage { constructor(app) { this.app = app; this.container = null; // Will be set in renderLayout this.isLoading = false; } async init() { // Initialization if needed } async show() { this.renderLayout(); await this.loadDashboardData(); } hide() { // Cleanup if needed if (this.container) { this.container.innerHTML = ''; } } renderLayout() { const pageHome = document.getElementById('page-home'); if (!pageHome) return; pageHome.innerHTML = `

Favorite Channels

Loading favorites...

Continue Watching

Loading history...

Recently Added Movies

Loading recently added...

Recently Added Series

Loading recently added...
`; this.container = document.getElementById('home-content'); // Attach scroll arrow handlers this.initScrollArrows(); } initScrollArrows() { this.container.querySelectorAll('.scroll-wrapper').forEach(wrapper => { const scrollContainer = wrapper.querySelector('.horizontal-scroll'); const leftBtn = wrapper.querySelector('.scroll-left'); const rightBtn = wrapper.querySelector('.scroll-right'); if (!scrollContainer || !leftBtn || !rightBtn) return; const scrollAmount = 300; // pixels to scroll per click leftBtn.addEventListener('click', () => { scrollContainer.scrollBy({ left: -scrollAmount, behavior: 'smooth' }); }); rightBtn.addEventListener('click', () => { scrollContainer.scrollBy({ left: scrollAmount, behavior: 'smooth' }); }); // Update arrow visibility based on scroll position const updateArrows = () => { const { scrollLeft, scrollWidth, clientWidth } = scrollContainer; leftBtn.classList.toggle('hidden', scrollLeft <= 0); rightBtn.classList.toggle('hidden', scrollLeft + clientWidth >= scrollWidth - 5); }; // Store reference for later updates wrapper._updateArrows = updateArrows; scrollContainer.addEventListener('scroll', updateArrows); // Initial check after content loads setTimeout(updateArrows, 100); }); } /** * Re-check scroll arrow visibility for all sections * Call this after dynamically loading content */ updateScrollArrows() { this.container?.querySelectorAll('.scroll-wrapper').forEach(wrapper => { if (wrapper._updateArrows) { wrapper._updateArrows(); } }); } async loadDashboardData() { if (this.isLoading) return; this.isLoading = true; try { // 0. Load Favorite Channels (first section) await this.renderFavoriteChannels(); // 1. Load Watch History const history = await window.API.request('GET', '/history?limit=12'); if (history && Array.isArray(history)) { this.renderHistory(history); } // 2. Load Recent Items this.renderRecentMovies(); this.renderRecentSeries(); } catch (err) { console.error('[Dashboard] Error loading data:', err); } finally { this.isLoading = false; } } async renderFavoriteChannels() { const list = document.getElementById('favorite-channels-list'); const section = document.getElementById('favorite-channels-section'); if (!list || !section) return; try { // Fetch favorite channels for current user const favorites = await window.API.request('GET', '/favorites?itemType=channel'); if (!favorites || favorites.length === 0) { list.innerHTML = '
Add channels to favorites from Live TV
'; return; } // Ensure channel list is loaded to resolve channel details const channelList = this.app.channelList; if (!channelList.channels || channelList.channels.length === 0) { await channelList.loadSources(); await channelList.loadChannels(); } // Match favorites to channel data const channels = []; for (const fav of favorites) { // Find channel in loaded channel list const channel = channelList.channels.find(ch => String(ch.sourceId) === String(fav.source_id) && (String(ch.id) === String(fav.item_id) || String(ch.streamId) === String(fav.item_id)) ); if (channel) { channels.push({ ...channel, favoriteId: fav.id }); } } if (channels.length === 0) { list.innerHTML = '
Add channels to favorites from Live TV
'; return; } // Render channel tiles list.innerHTML = channels.map(ch => this.createChannelTile(ch)).join(''); // Attach click handlers list.querySelectorAll('.channel-tile').forEach(tile => { tile.addEventListener('click', () => { const channelId = tile.dataset.channelId; const sourceId = tile.dataset.sourceId; this.playChannel(channelId, sourceId); }); }); // Update scroll arrows after content renders this.updateScrollArrows(); } catch (err) { console.error('[Dashboard] Error loading favorite channels:', err); list.innerHTML = '
Error loading favorites
'; } } createChannelTile(channel) { const logo = channel.tvgLogo || '/img/placeholder.png'; const logoUrl = logo.startsWith('http') ? `/api/proxy/image?url=${encodeURIComponent(logo)}` : logo; const name = channel.name || 'Unknown'; return `
${name}
`; } playChannel(channelId, sourceId) { // Navigate to Live TV and select the channel this.app.navigateTo('live'); // Small delay to ensure page is ready setTimeout(() => { const channelList = this.app.channelList; if (channelList) { // Find and select the channel const channel = channelList.channels.find(ch => String(ch.id) === String(channelId) && String(ch.sourceId) === String(sourceId) ); if (channel) { channelList.selectChannel({ channelId: channel.id, sourceId: channel.sourceId, sourceType: channel.sourceType, streamId: channel.streamId || '', url: channel.url || '' }); } } }, 100); } renderHistory(items) { const list = document.getElementById('continue-watching-list'); const section = document.getElementById('continue-watching-section'); if (!list || !section) return; if (items.length === 0) { section.classList.add('hidden'); return; } section.classList.remove('hidden'); list.innerHTML = items.map(item => this.createCard(item)).join(''); // Attach click listeners list.querySelectorAll('.dashboard-card').forEach(card => { card.addEventListener('click', () => { const id = card.dataset.id; const item = items.find(i => i.item_id === id); if (item) { const type = item.item_type || item.type; // IF it's a series, checking details is better than blind resume // BUT for "Continue Watching", we ideally want to resume // Prioritize playing directly for resume tiles this.playItem(item, true); // true for resume } }); }); // Update scroll arrows after content renders this.updateScrollArrows(); } navigateToSeries(item) { if (!this.app.pages.series) return; // Prepare the series object as expected by SeriesPage.showSeriesDetails const series = { series_id: item.item_id, sourceId: item.source_id, name: item.name || (item.data ? item.data.title : 'Series'), cover: item.stream_icon || (item.data ? item.data.poster : null), plot: item.data ? item.data.description : '', year: item.data ? item.data.year : '' }; // Switch page this.app.navigateTo('series'); // Show details (delay slightly to ensure page is visible) setTimeout(() => { this.app.pages.series.showSeriesDetails(series); }, 100); } async renderRecentMovies() { const list = document.getElementById('recent-movies-list'); if (!list) return; try { const movies = await window.API.request('GET', '/channels/recent?type=movie&limit=12'); if (!movies || movies.length === 0) { list.innerHTML = '
No recently added movies found
'; return; } list.innerHTML = movies.map(item => this.createRecentCard(item)).join(''); // Attach listeners list.querySelectorAll('.dashboard-card').forEach(card => { card.addEventListener('click', () => { const id = card.dataset.id; const item = movies.find(m => m.item_id === id); if (item) this.playItem(item); }); }); // Update scroll arrows after content renders this.updateScrollArrows(); } catch (err) { console.error('[Dashboard] Error loading recent movies:', err); } } async renderRecentSeries() { const list = document.getElementById('recent-series-list'); if (!list) return; try { const series = await window.API.request('GET', '/channels/recent?type=series&limit=12'); if (!series || series.length === 0) { list.innerHTML = '
No recently added series found
'; return; } list.innerHTML = series.map(item => this.createRecentCard(item)).join(''); // Attach listeners list.querySelectorAll('.dashboard-card').forEach(card => { card.addEventListener('click', () => { const id = card.dataset.id; const item = series.find(s => s.item_id === id); if (item) this.navigateToSeries(item); }); }); // Update scroll arrows after content renders this.updateScrollArrows(); } catch (err) { console.error('[Dashboard] Error loading recent series:', err); } } createCard(item) { const { data, progress, duration, item_id } = item; const type = item.item_type || item.type; const percent = Math.min(100, Math.round((progress / duration) * 100)); // Proxy the poster if it's an external URL const poster = data.poster || '/img/poster-placeholder.jpg'; const posterUrl = poster.startsWith('http') ? `/api/proxy/image?url=${encodeURIComponent(poster)}` : poster; return `
${data.title || item.name}
${item.name || data.title || 'Unknown Title'}
${data.subtitle || (type === 'movie' ? 'Movie' : 'Series')}
`; } createRecentCard(item) { const { data, item_id } = item; const type = item.type || item.item_type; const poster = item.stream_icon || data.poster || '/img/poster-placeholder.jpg'; const posterUrl = poster.startsWith('http') ? `/api/proxy/image?url=${encodeURIComponent(poster)}` : poster; return `
${item.name}
${item.name || (data && data.title) || 'Unknown Title'}
${(data && data.subtitle) || (type === 'movie' ? 'Movie' : 'Series')}
`; } async playItem(item, isResume = false) { if (!this.app.pages.watch) return; try { const type = item.item_type || item.type; const streamType = type === 'movie' ? 'movie' : 'series'; const sourceId = item.source_id || (item.data && item.data.sourceId); const streamId = item.item_id; const container = item.container_extension || (item.data && item.data.containerExtension) || 'mp4'; const result = await window.API.request('GET', `/proxy/xtream/${sourceId}/stream/${streamId}/${streamType}?container=${container}`); if (result && result.url) { const content = { id: item.item_id, type: type, title: item.name || item.data.title, subtitle: item.data.subtitle || (type === 'movie' ? 'Movie' : 'Series'), poster: item.stream_icon || item.data.poster, sourceId: sourceId, resumeTime: isResume ? item.progress : 0, containerExtension: container }; // Switch to watch page this.app.navigateTo('watch'); this.app.pages.watch.play(content, result.url); } } catch (err) { console.error('[Dashboard] Playback failed:', err); } } } window.HomePage = HomePage;