From 1dcdccb282446a04cd8fb63b17e18d31f8ba9d15 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Fri, 10 Apr 2026 23:23:35 -0700 Subject: [PATCH] Fix music video download in global search and improve progress visibility - Moved _downloadMusicVideo to top-level scope so global search can use it (was inside enhanced search conditional that only runs on downloads page) - Global search video cards use base64 data attributes to avoid JSON escaping issues in onclick handlers - Darkened thumbnail overlay during download for better progress visibility - Larger progress ring (52px) with accent-colored glow shadow --- webui/static/script.js | 136 ++++++++++++++++++++--------------------- webui/static/style.css | 14 ++++- 2 files changed, 78 insertions(+), 72 deletions(-) diff --git a/webui/static/script.js b/webui/static/script.js index 053ee607..aa83744b 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -8940,74 +8940,6 @@ function initializeSearchModeToggle() { }).join(''); } - window._downloadMusicVideo = async function(cardEl, video) { - if (cardEl.classList.contains('downloading') || cardEl.classList.contains('completed')) return; - cardEl.classList.add('downloading'); - cardEl.onclick = null; // Disable click - - const playBtn = cardEl.querySelector('.enh-video-play'); - const progressRing = cardEl.querySelector('.enh-video-progress-ring'); - const progressBar = cardEl.querySelector('.enh-video-progress-bar'); - const doneIcon = cardEl.querySelector('.enh-video-done'); - const errorIcon = cardEl.querySelector('.enh-video-error'); - - if (playBtn) playBtn.classList.add('hidden'); - if (progressRing) progressRing.classList.remove('hidden'); - - try { - const res = await fetch('/api/music-video/download', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - video_id: video.video_id, - url: video.url, - title: video.title, - channel: video.channel, - }), - }); - if (!res.ok) throw new Error('Download request failed'); - - // Poll for progress - const circumference = 97.4; // 2 * PI * 15.5 - const pollInterval = setInterval(async () => { - try { - const statusRes = await fetch(`/api/music-video/status/${video.video_id}`); - const status = await statusRes.json(); - - if (progressBar && status.progress > 0) { - const offset = circumference - (status.progress / 100) * circumference; - progressBar.style.strokeDashoffset = offset; - } - - if (status.status === 'completed') { - clearInterval(pollInterval); - cardEl.classList.remove('downloading'); - cardEl.classList.add('completed'); - if (progressRing) progressRing.classList.add('hidden'); - if (doneIcon) doneIcon.classList.remove('hidden'); - } else if (status.status === 'error') { - clearInterval(pollInterval); - cardEl.classList.remove('downloading'); - cardEl.classList.add('errored'); - if (progressRing) progressRing.classList.add('hidden'); - if (errorIcon) errorIcon.classList.remove('hidden'); - // Re-enable click for retry - cardEl.onclick = () => window._downloadMusicVideo(cardEl, video); - } - } catch (e) { - // Polling error — keep trying - } - }, 500); - - } catch (e) { - cardEl.classList.remove('downloading'); - if (progressRing) progressRing.classList.add('hidden'); - if (playBtn) playBtn.classList.remove('hidden'); - if (errorIcon) errorIcon.classList.remove('hidden'); - cardEl.onclick = () => window._downloadMusicVideo(cardEl, video); - } - }; - function _formatViewCount(count) { if (count >= 1000000000) return `${(count / 1000000000).toFixed(1)}B`; if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`; @@ -17516,6 +17448,71 @@ function _notifTimeAgo(ts) { } // ================================================================================== +// Music video download handler — defined at top level so both enhanced and global search can use it +function _downloadMusicVideo(cardEl, video) { + if (cardEl.classList.contains('downloading') || cardEl.classList.contains('completed')) return; + cardEl.classList.add('downloading'); + cardEl.onclick = null; + + const playBtn = cardEl.querySelector('.enh-video-play'); + const progressRing = cardEl.querySelector('.enh-video-progress-ring'); + const progressBar = cardEl.querySelector('.enh-video-progress-bar'); + const doneIcon = cardEl.querySelector('.enh-video-done'); + const errorIcon = cardEl.querySelector('.enh-video-error'); + + if (playBtn) playBtn.classList.add('hidden'); + if (progressRing) progressRing.classList.remove('hidden'); + + fetch('/api/music-video/download', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ video_id: video.video_id, url: video.url, title: video.title, channel: video.channel }), + }).then(res => { + if (!res.ok) throw new Error('Download request failed'); + const circumference = 97.4; + const pollInterval = setInterval(async () => { + try { + const statusRes = await fetch(`/api/music-video/status/${video.video_id}`); + const status = await statusRes.json(); + if (progressBar && status.progress > 0) { + progressBar.style.strokeDashoffset = circumference - (status.progress / 100) * circumference; + } + if (status.status === 'completed') { + clearInterval(pollInterval); + cardEl.classList.remove('downloading'); + cardEl.classList.add('completed'); + if (progressRing) progressRing.classList.add('hidden'); + if (doneIcon) doneIcon.classList.remove('hidden'); + } else if (status.status === 'error') { + clearInterval(pollInterval); + cardEl.classList.remove('downloading'); + cardEl.classList.add('errored'); + if (progressRing) progressRing.classList.add('hidden'); + if (errorIcon) errorIcon.classList.remove('hidden'); + cardEl.onclick = () => _downloadMusicVideo(cardEl, video); + } + } catch (e) {} + }, 500); + }).catch(e => { + cardEl.classList.remove('downloading'); + if (progressRing) progressRing.classList.add('hidden'); + if (playBtn) playBtn.classList.remove('hidden'); + if (errorIcon) errorIcon.classList.remove('hidden'); + cardEl.onclick = () => _downloadMusicVideo(cardEl, video); + }); +} + +// Global search video click — decodes base64 video data and delegates to _downloadMusicVideo +function _gsClickVideo(cardEl) { + try { + const encoded = cardEl.dataset.video; + const video = JSON.parse(decodeURIComponent(escape(atob(encoded)))); + _downloadMusicVideo(cardEl, video); + } catch (e) { + console.error('Failed to parse video data:', e); + } +} + // GLOBAL SEARCH BAR — Spotlight-style search from anywhere // ================================================================================== @@ -17767,7 +17764,8 @@ function _gsRender(data) { h += videos.map(v => { const dur = v.duration ? `${Math.floor(v.duration / 60)}:${String(v.duration % 60).padStart(2, '0')}` : ''; const views = v.view_count >= 1000000 ? `${(v.view_count/1000000).toFixed(1)}M` : v.view_count >= 1000 ? `${(v.view_count/1000).toFixed(1)}K` : (v.view_count || ''); - return `
+ const vJson = btoa(unescape(encodeURIComponent(JSON.stringify(v)))); + return `
diff --git a/webui/static/style.css b/webui/static/style.css index e385538d..d5b371d9 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -32985,7 +32985,14 @@ body.helper-mode-active #dashboard-activity-feed:hover { /* Video download states */ .enh-video-card.downloading { pointer-events: none; - opacity: 0.8; +} + +.enh-video-card.downloading .enh-video-thumb::after { + content: ''; + position: absolute; + inset: 0; + background: rgba(0, 0, 0, 0.6); + z-index: 1; } .enh-video-card.completed .enh-video-thumb::after { @@ -33007,9 +33014,10 @@ body.helper-mode-active #dashboard-activity-feed:hover { top: 50%; left: 50%; transform: translate(-50%, -50%); - width: 44px; - height: 44px; + width: 52px; + height: 52px; z-index: 2; + filter: drop-shadow(0 0 6px rgba(var(--accent-rgb), 0.5)); } .enh-video-progress-ring svg {