
▶
✓
✗
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 19/19] 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 {