fix: filter homepage recent sections by hidden status
This commit is contained in:
+75
-9
@@ -980,7 +980,8 @@ video::cue {
|
|||||||
|
|
||||||
.epg-grid {
|
.epg-grid {
|
||||||
height: calc(100vh - var(--navbar-height) - 60px);
|
height: calc(100vh - var(--navbar-height) - 60px);
|
||||||
overflow: auto;
|
overflow: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
padding: var(--space-md);
|
padding: var(--space-md);
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
overscroll-behavior: contain;
|
overscroll-behavior: contain;
|
||||||
@@ -1887,6 +1888,7 @@ kbd {
|
|||||||
--sidebar-width: 320px;
|
--sidebar-width: 320px;
|
||||||
--space-lg: 16px;
|
--space-lg: 16px;
|
||||||
--space-xl: 24px;
|
--space-xl: 24px;
|
||||||
|
--epg-sidebar-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Navigation - Hide text labels, show only icons */
|
/* Navigation - Hide text labels, show only icons */
|
||||||
@@ -1900,7 +1902,8 @@ kbd {
|
|||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-menu span:not(.nav-icon) {
|
/* Hide nav link text on mobile, but keep Now Playing visible */
|
||||||
|
.navbar-menu .nav-link span:not(.nav-icon) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2039,7 +2042,11 @@ kbd {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.epg-channel-info {
|
.epg-channel-info {
|
||||||
width: 180px;
|
width: var(--epg-sidebar-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.epg-time-header {
|
||||||
|
margin-left: var(--epg-sidebar-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
.epg-time-slot {
|
.epg-time-slot {
|
||||||
@@ -4314,7 +4321,7 @@ kbd {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
padding: 6px 14px;
|
padding: 6px 14px;
|
||||||
margin-right: 16px;
|
margin-right: var(--space-md);
|
||||||
background: linear-gradient(135deg, var(--color-accent) 0%, #8b5cf6 100%);
|
background: linear-gradient(135deg, var(--color-accent) 0%, #8b5cf6 100%);
|
||||||
border-radius: var(--radius-full);
|
border-radius: var(--radius-full);
|
||||||
color: white;
|
color: white;
|
||||||
@@ -4360,13 +4367,16 @@ kbd {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile responsive */
|
/* Mobile responsive - show in dropdown menu */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.now-playing-indicator {
|
.now-playing-indicator {
|
||||||
max-width: 140px;
|
width: calc(100% - 32px);
|
||||||
padding: 5px 10px;
|
max-width: none;
|
||||||
font-size: 12px;
|
margin: 0 16px var(--space-sm) 16px;
|
||||||
margin-right: 8px;
|
padding: 10px 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4406,7 +4416,63 @@ kbd {
|
|||||||
color: var(--color-text-bright);
|
color: var(--color-text-bright);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scroll Wrapper with Navigation Arrows */
|
||||||
|
.scroll-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-arrow {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
z-index: 10;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(0, 0, 0, 0.7);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
transition: all var(--transition-fast);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-arrow:hover {
|
||||||
|
background: rgba(0, 0, 0, 0.9);
|
||||||
|
border-color: var(--color-accent);
|
||||||
|
transform: translateY(-50%) scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-arrow svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-arrow.scroll-left {
|
||||||
|
left: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-arrow.scroll-right {
|
||||||
|
right: -8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-arrow.hidden {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide arrows on touch devices (they can swipe) */
|
||||||
|
@media (hover: none) {
|
||||||
|
.scroll-arrow {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.horizontal-scroll {
|
.horizontal-scroll {
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 259 B After Width: | Height: | Size: 23 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
+5
-5
@@ -38,12 +38,12 @@
|
|||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
</button>
|
</button>
|
||||||
<!-- Now Playing indicator for movies/series -->
|
|
||||||
<a href="#" class="now-playing-indicator hidden" id="now-playing-indicator" data-page="watch">
|
|
||||||
<span class="now-playing-icon">▶</span>
|
|
||||||
<span class="now-playing-text" id="now-playing-text">Now Playing</span>
|
|
||||||
</a>
|
|
||||||
<div class="navbar-menu" id="navbar-menu">
|
<div class="navbar-menu" id="navbar-menu">
|
||||||
|
<!-- Now Playing indicator for movies/series -->
|
||||||
|
<a href="#" class="now-playing-indicator hidden" id="now-playing-indicator" data-page="watch">
|
||||||
|
<span class="now-playing-icon">▶</span>
|
||||||
|
<span class="now-playing-text" id="now-playing-text">Now Playing</span>
|
||||||
|
</a>
|
||||||
<a href="#" class="nav-link active" data-page="home">
|
<a href="#" class="nav-link active" data-page="home">
|
||||||
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
<span class="nav-icon"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"
|
||||||
class="icon">
|
class="icon">
|
||||||
|
|||||||
@@ -439,7 +439,7 @@ class EpgGuide {
|
|||||||
</div>
|
</div>
|
||||||
`).join('')}
|
`).join('')}
|
||||||
</div>
|
</div>
|
||||||
<div class="epg-scroll-container" style="overflow-y: auto; max-height: calc(100vh - 200px);">
|
<div class="epg-scroll-container" style="overflow: auto; max-height: calc(100vh - 200px);">
|
||||||
<div class="epg-spacer" style="height: ${totalHeight}px; position: relative;">
|
<div class="epg-spacer" style="height: ${totalHeight}px; position: relative;">
|
||||||
<div class="epg-channel-rows" style="position: absolute; top: 0; left: 0; right: 0;"></div>
|
<div class="epg-channel-rows" style="position: absolute; top: 0; left: 0; right: 0;"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -461,11 +461,25 @@ class EpgGuide {
|
|||||||
if (this._scrollHandler) {
|
if (this._scrollHandler) {
|
||||||
this.scrollContainer?.removeEventListener('scroll', this._scrollHandler);
|
this.scrollContainer?.removeEventListener('scroll', this._scrollHandler);
|
||||||
}
|
}
|
||||||
|
if (this._hScrollHandler) {
|
||||||
|
this.scrollContainer?.removeEventListener('scroll', this._hScrollHandler);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up scroll handler for virtual scrolling
|
// Get reference to time header for horizontal scroll sync
|
||||||
|
this.timeHeader = this.container.querySelector('.epg-time-header');
|
||||||
|
|
||||||
|
// Set up scroll handler for virtual scrolling (debounced for performance)
|
||||||
this._scrollHandler = this.debounce(() => this.updateVisibleRows(), 16); // ~60fps
|
this._scrollHandler = this.debounce(() => this.updateVisibleRows(), 16); // ~60fps
|
||||||
this.scrollContainer.addEventListener('scroll', this._scrollHandler);
|
this.scrollContainer.addEventListener('scroll', this._scrollHandler);
|
||||||
|
|
||||||
|
// Set up horizontal scroll sync (instant, not debounced)
|
||||||
|
this._hScrollHandler = () => {
|
||||||
|
if (this.timeHeader && this.scrollContainer) {
|
||||||
|
this.timeHeader.style.transform = `translateX(-${this.scrollContainer.scrollLeft}px)`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.scrollContainer.addEventListener('scroll', this._hScrollHandler);
|
||||||
|
|
||||||
// Initial render of visible rows
|
// Initial render of visible rows
|
||||||
this.updateVisibleRows();
|
this.updateVisibleRows();
|
||||||
|
|
||||||
|
|||||||
@@ -1251,6 +1251,9 @@ class VideoPlayer {
|
|||||||
* Stop playback
|
* Stop playback
|
||||||
*/
|
*/
|
||||||
stop() {
|
stop() {
|
||||||
|
// Stop any running transcode session first
|
||||||
|
this.stopTranscodeSession();
|
||||||
|
|
||||||
if (this.hls) {
|
if (this.hls) {
|
||||||
this.hls.destroy();
|
this.hls.destroy();
|
||||||
this.hls = null;
|
this.hls = null;
|
||||||
|
|||||||
+105
-11
@@ -35,8 +35,16 @@ class HomePage {
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Favorite Channels</h2>
|
<h2>Favorite Channels</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="horizontal-scroll channel-tiles" id="favorite-channels-list">
|
<div class="scroll-wrapper">
|
||||||
<div class="loading-state">Loading favorites...</div>
|
<button class="scroll-arrow scroll-left" aria-label="Scroll left">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="horizontal-scroll channel-tiles" id="favorite-channels-list">
|
||||||
|
<div class="loading-state">Loading favorites...</div>
|
||||||
|
</div>
|
||||||
|
<button class="scroll-arrow scroll-right" aria-label="Scroll right">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -44,8 +52,16 @@ class HomePage {
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Continue Watching</h2>
|
<h2>Continue Watching</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="horizontal-scroll" id="continue-watching-list">
|
<div class="scroll-wrapper">
|
||||||
<div class="loading-state">Loading history...</div>
|
<button class="scroll-arrow scroll-left" aria-label="Scroll left">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="horizontal-scroll" id="continue-watching-list">
|
||||||
|
<div class="loading-state">Loading history...</div>
|
||||||
|
</div>
|
||||||
|
<button class="scroll-arrow scroll-right" aria-label="Scroll right">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -53,8 +69,16 @@ class HomePage {
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Recently Added Movies</h2>
|
<h2>Recently Added Movies</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="horizontal-scroll" id="recent-movies-list">
|
<div class="scroll-wrapper">
|
||||||
<div class="empty-state">Coming soon</div>
|
<button class="scroll-arrow scroll-left" aria-label="Scroll left">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="horizontal-scroll" id="recent-movies-list">
|
||||||
|
<div class="empty-state">Coming soon</div>
|
||||||
|
</div>
|
||||||
|
<button class="scroll-arrow scroll-right" aria-label="Scroll right">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -62,15 +86,73 @@ class HomePage {
|
|||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h2>Recently Added Series</h2>
|
<h2>Recently Added Series</h2>
|
||||||
</div>
|
</div>
|
||||||
<div class="horizontal-scroll" id="recent-series-list">
|
<div class="scroll-wrapper">
|
||||||
<div class="empty-state">Coming soon</div>
|
<button class="scroll-arrow scroll-left" aria-label="Scroll left">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>
|
||||||
|
</button>
|
||||||
|
<div class="horizontal-scroll" id="recent-series-list">
|
||||||
|
<div class="empty-state">Coming soon</div>
|
||||||
|
</div>
|
||||||
|
<button class="scroll-arrow scroll-right" aria-label="Scroll right">
|
||||||
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
this.container = document.getElementById('home-content');
|
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() {
|
async loadDashboardData() {
|
||||||
if (this.isLoading) return;
|
if (this.isLoading) return;
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
@@ -147,6 +229,9 @@ class HomePage {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update scroll arrows after content renders
|
||||||
|
this.updateScrollArrows();
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Dashboard] Error loading favorite channels:', err);
|
console.error('[Dashboard] Error loading favorite channels:', err);
|
||||||
list.innerHTML = '<div class="empty-state hint">Error loading favorites</div>';
|
list.innerHTML = '<div class="empty-state hint">Error loading favorites</div>';
|
||||||
@@ -161,7 +246,7 @@ class HomePage {
|
|||||||
return `
|
return `
|
||||||
<div class="channel-tile" data-channel-id="${channel.id}" data-source-id="${channel.sourceId}">
|
<div class="channel-tile" data-channel-id="${channel.id}" data-source-id="${channel.sourceId}">
|
||||||
<div class="tile-logo">
|
<div class="tile-logo">
|
||||||
<img src="${logoUrl}" alt="${name}" loading="lazy" onerror="this.src='/img/placeholder.png'">
|
<img src="${logoUrl}" alt="${name}" loading="lazy" onerror="this.onerror=null;this.src='/img/placeholder.png'">
|
||||||
</div>
|
</div>
|
||||||
<div class="tile-name" title="${name}">${name}</div>
|
<div class="tile-name" title="${name}">${name}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -223,6 +308,9 @@ class HomePage {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update scroll arrows after content renders
|
||||||
|
this.updateScrollArrows();
|
||||||
}
|
}
|
||||||
|
|
||||||
navigateToSeries(item) {
|
navigateToSeries(item) {
|
||||||
@@ -268,6 +356,9 @@ class HomePage {
|
|||||||
if (item) this.playItem(item);
|
if (item) this.playItem(item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update scroll arrows after content renders
|
||||||
|
this.updateScrollArrows();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Dashboard] Error loading recent movies:', err);
|
console.error('[Dashboard] Error loading recent movies:', err);
|
||||||
}
|
}
|
||||||
@@ -294,6 +385,9 @@ class HomePage {
|
|||||||
if (item) this.navigateToSeries(item);
|
if (item) this.navigateToSeries(item);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update scroll arrows after content renders
|
||||||
|
this.updateScrollArrows();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[Dashboard] Error loading recent series:', err);
|
console.error('[Dashboard] Error loading recent series:', err);
|
||||||
}
|
}
|
||||||
@@ -311,7 +405,7 @@ class HomePage {
|
|||||||
return `
|
return `
|
||||||
<div class="dashboard-card" data-id="${item_id}" data-type="${type}">
|
<div class="dashboard-card" data-id="${item_id}" data-type="${type}">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<img src="${posterUrl}" alt="${data.title || item.name}" loading="lazy" onerror="this.src='/img/poster-placeholder.jpg'">
|
<img src="${posterUrl}" alt="${data.title || item.name}" loading="lazy" onerror="this.onerror=null;this.src='/img/poster-placeholder.jpg'">
|
||||||
<div class="progress-bar-container">
|
<div class="progress-bar-container">
|
||||||
<div class="progress-bar" style="width: ${percent}%"></div>
|
<div class="progress-bar" style="width: ${percent}%"></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -336,7 +430,7 @@ class HomePage {
|
|||||||
return `
|
return `
|
||||||
<div class="dashboard-card" data-id="${item_id}" data-type="${type}">
|
<div class="dashboard-card" data-id="${item_id}" data-type="${type}">
|
||||||
<div class="card-image">
|
<div class="card-image">
|
||||||
<img src="${posterUrl}" alt="${item.name}" loading="lazy" onerror="this.src='/img/poster-placeholder.jpg'">
|
<img src="${posterUrl}" alt="${item.name}" loading="lazy" onerror="this.onerror=null;this.src='/img/poster-placeholder.jpg'">
|
||||||
<div class="play-icon-overlay">
|
<div class="play-icon-overlay">
|
||||||
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
|
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -888,7 +888,14 @@ class WatchPage {
|
|||||||
renderDetails() {
|
renderDetails() {
|
||||||
if (!this.content) return;
|
if (!this.content) return;
|
||||||
|
|
||||||
this.posterEl.src = this.content.poster || '/img/placeholder.png';
|
const isChannel = this.content.type === 'channel' || !this.content.type; // Default to channel if unknown
|
||||||
|
const fallback = isChannel ? '/img/placeholder.png' : '/img/poster-placeholder.jpg';
|
||||||
|
|
||||||
|
this.posterEl.onerror = () => {
|
||||||
|
this.posterEl.onerror = null;
|
||||||
|
this.posterEl.src = fallback;
|
||||||
|
};
|
||||||
|
this.posterEl.src = this.content.poster || fallback;
|
||||||
this.posterEl.alt = this.content.title || '';
|
this.posterEl.alt = this.content.title || '';
|
||||||
this.contentTitleEl.textContent = this.content.title || '';
|
this.contentTitleEl.textContent = this.content.title || '';
|
||||||
this.yearEl.textContent = this.content.year || '';
|
this.yearEl.textContent = this.content.year || '';
|
||||||
@@ -984,7 +991,7 @@ class WatchPage {
|
|||||||
<div class="watch-recommended-card" data-id="${movie.stream_id}" data-source="${sourceId}">
|
<div class="watch-recommended-card" data-id="${movie.stream_id}" data-source="${sourceId}">
|
||||||
<img src="${movie.stream_icon || movie.cover || '/img/placeholder.png'}"
|
<img src="${movie.stream_icon || movie.cover || '/img/placeholder.png'}"
|
||||||
alt="${movie.name}"
|
alt="${movie.name}"
|
||||||
onerror="this.src='/img/placeholder.png'" loading="lazy">
|
onerror="this.onerror=null;this.src='/img/placeholder.png'" loading="lazy">
|
||||||
<p>${movie.name}</p>
|
<p>${movie.name}</p>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`).join('');
|
||||||
|
|||||||
@@ -298,9 +298,17 @@ router.get('/recent', async (req, res) => {
|
|||||||
|
|
||||||
const db = getDb();
|
const db = getDb();
|
||||||
const recentItems = db.prepare(`
|
const recentItems = db.prepare(`
|
||||||
SELECT * FROM playlist_items
|
SELECT * FROM playlist_items p
|
||||||
WHERE type = ? AND is_hidden = 0
|
WHERE p.type = ?
|
||||||
ORDER BY added_at DESC
|
AND p.is_hidden = 0
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM categories c
|
||||||
|
WHERE c.source_id = p.source_id
|
||||||
|
AND c.category_id = p.category_id
|
||||||
|
AND c.type = p.type
|
||||||
|
AND c.is_hidden = 1
|
||||||
|
)
|
||||||
|
ORDER BY p.added_at DESC
|
||||||
LIMIT ?
|
LIMIT ?
|
||||||
`).all(type, parseInt(limit));
|
`).all(type, parseInt(limit));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user