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
This commit is contained in:
parent
c02eb0eb0a
commit
1dcdccb282
2 changed files with 78 additions and 72 deletions
|
|
@ -8940,74 +8940,6 @@ function initializeSearchModeToggle() {
|
||||||
}).join('');
|
}).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) {
|
function _formatViewCount(count) {
|
||||||
if (count >= 1000000000) return `${(count / 1000000000).toFixed(1)}B`;
|
if (count >= 1000000000) return `${(count / 1000000000).toFixed(1)}B`;
|
||||||
if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
|
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
|
// GLOBAL SEARCH BAR — Spotlight-style search from anywhere
|
||||||
// ==================================================================================
|
// ==================================================================================
|
||||||
|
|
||||||
|
|
@ -17767,7 +17764,8 @@ function _gsRender(data) {
|
||||||
h += videos.map(v => {
|
h += videos.map(v => {
|
||||||
const dur = v.duration ? `${Math.floor(v.duration / 60)}:${String(v.duration % 60).padStart(2, '0')}` : '';
|
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 || '');
|
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 `<div class="enh-video-card" data-video-id="${v.video_id}" onclick="_downloadMusicVideo(this, ${JSON.stringify(v).replace(/"/g, '"')})">
|
const vJson = btoa(unescape(encodeURIComponent(JSON.stringify(v))));
|
||||||
|
return `<div class="enh-video-card" data-video-id="${v.video_id}" data-video="${vJson}" onclick="_gsClickVideo(this)">
|
||||||
<div class="enh-video-thumb"><img src="${v.thumbnail}" alt="" loading="lazy" onerror="this.style.display='none'"><div class="enh-video-play">▶</div>
|
<div class="enh-video-thumb"><img src="${v.thumbnail}" alt="" loading="lazy" onerror="this.style.display='none'"><div class="enh-video-play">▶</div>
|
||||||
<div class="enh-video-progress-ring hidden"><svg viewBox="0 0 36 36"><circle class="enh-video-progress-bg" cx="18" cy="18" r="15.5" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="3"/><circle class="enh-video-progress-bar" cx="18" cy="18" r="15.5" fill="none" stroke="rgb(var(--accent-rgb))" stroke-width="3" stroke-dasharray="97.4" stroke-dashoffset="97.4" stroke-linecap="round" transform="rotate(-90 18 18)"/></svg></div>
|
<div class="enh-video-progress-ring hidden"><svg viewBox="0 0 36 36"><circle class="enh-video-progress-bg" cx="18" cy="18" r="15.5" fill="none" stroke="rgba(255,255,255,0.15)" stroke-width="3"/><circle class="enh-video-progress-bar" cx="18" cy="18" r="15.5" fill="none" stroke="rgb(var(--accent-rgb))" stroke-width="3" stroke-dasharray="97.4" stroke-dashoffset="97.4" stroke-linecap="round" transform="rotate(-90 18 18)"/></svg></div>
|
||||||
<div class="enh-video-done hidden">✓</div><div class="enh-video-error hidden">✗</div>
|
<div class="enh-video-done hidden">✓</div><div class="enh-video-error hidden">✗</div>
|
||||||
|
|
|
||||||
|
|
@ -32985,7 +32985,14 @@ body.helper-mode-active #dashboard-activity-feed:hover {
|
||||||
/* Video download states */
|
/* Video download states */
|
||||||
.enh-video-card.downloading {
|
.enh-video-card.downloading {
|
||||||
pointer-events: none;
|
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 {
|
.enh-video-card.completed .enh-video-thumb::after {
|
||||||
|
|
@ -33007,9 +33014,10 @@ body.helper-mode-active #dashboard-activity-feed:hover {
|
||||||
top: 50%;
|
top: 50%;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
transform: translate(-50%, -50%);
|
transform: translate(-50%, -50%);
|
||||||
width: 44px;
|
width: 52px;
|
||||||
height: 44px;
|
height: 52px;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
filter: drop-shadow(0 0 6px rgba(var(--accent-rgb), 0.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
.enh-video-progress-ring svg {
|
.enh-video-progress-ring svg {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue