From b6b83c8bf89559103d8a5b74153523babe80c145 Mon Sep 17 00:00:00 2001 From: Broque Thomas <26755000+Nezreka@users.noreply.github.com> Date: Thu, 21 May 2026 15:19:43 -0700 Subject: [PATCH] Polish torrent and usenet download UX Clarifies album-bundle progress text in the download modal and active downloads panel so release-first downloads read as downloading a release, then matching tracks after staging. Adds waiting-state copy and tooltips for rows blocked on release staging, plus source-specific library history badge styling for Torrent, Usenet, Staging, and Auto-Import. --- webui/static/downloads.js | 52 +++++++++++++++++++++++++++++----- webui/static/pages-extra.js | 51 ++++++++++++++++++++++++++++----- webui/static/style.css | 31 ++++++++++++++++++++ webui/static/wishlist-tools.js | 5 +++- 4 files changed, 124 insertions(+), 15 deletions(-) diff --git a/webui/static/downloads.js b/webui/static/downloads.js index 7df12aff..36142faf 100644 --- a/webui/static/downloads.js +++ b/webui/static/downloads.js @@ -3328,6 +3328,46 @@ function _downloadModalFormatSpeed(bytesPerSecond) { return formatted ? `${formatted}/s` : ''; } +function _downloadModalSourceLabel(source) { + const labels = { + torrent: 'Torrent', + usenet: 'Usenet', + soulseek: 'Soulseek', + youtube: 'YouTube', + tidal: 'Tidal', + qobuz: 'Qobuz', + hifi: 'HiFi', + deezer_dl: 'Deezer', + amazon: 'Amazon', + lidarr: 'Lidarr', + soundcloud: 'SoundCloud' + }; + const key = String(source || '').toLowerCase(); + return labels[key] || (source ? String(source) : 'Release'); +} + +function _downloadModalBundleStateLabel(state) { + const labels = { + searching: 'searching for release', + downloading: 'downloading release', + staged: 'matching tracks', + failed: 'release failed' + }; + const key = String(state || '').toLowerCase(); + return labels[key] || (state ? String(state).replace(/_/g, ' ') : 'downloading release'); +} + +function _downloadModalBundleProgressText(bundle) { + const percent = _downloadModalBundleProgressPercent(bundle); + const source = _downloadModalSourceLabel(bundle && bundle.source); + const state = _downloadModalBundleStateLabel(bundle && bundle.state); + const release = bundle && bundle.release ? ` - ${bundle.release}` : ''; + const speed = _downloadModalFormatSpeed(bundle && bundle.speed); + const size = _downloadModalFormatBytes(bundle && bundle.size); + const detail = speed || size ? ` (${[speed, size].filter(Boolean).join(' of ')})` : ''; + return `${source} ${state} ${percent}%${release}${detail}`; +} + function processModalStatusUpdate(playlistId, data) { // This function contains ALL the existing polling logic from startModalDownloadPolling // Extracted so it can be called from both individual and batched polling @@ -3384,16 +3424,12 @@ function processModalStatusUpdate(playlistId, data) { const bundle = data.album_bundle || {}; const percent = _downloadModalBundleProgressPercent(bundle); - const source = bundle.source ? `${bundle.source} ` : ''; - const release = bundle.release ? ` - ${bundle.release}` : ''; - const speed = _downloadModalFormatSpeed(bundle.speed); - const size = _downloadModalFormatBytes(bundle.size); - const detail = speed || size ? ` (${[speed, size].filter(Boolean).join(' of ')})` : ''; const downloadFill = document.getElementById(`download-progress-fill-${playlistId}`); const downloadText = document.getElementById(`download-progress-text-${playlistId}`); if (downloadFill) downloadFill.style.width = `${percent}%`; if (downloadText) { - downloadText.textContent = `${source}album ${bundle.state || 'downloading'} ${percent}%${release}${detail}`; + downloadText.textContent = _downloadModalBundleProgressText(bundle); + downloadText.title = 'SoulSync downloads one album release first, then matches the selected tracks from the staged files.'; } const modal = document.getElementById(`download-missing-modal-${playlistId}`); @@ -3401,7 +3437,9 @@ function processModalStatusUpdate(playlistId, data) { modal.querySelectorAll('[id^="download-"]').forEach(statusEl => { if (!statusEl.id.startsWith(`download-${playlistId}-`)) return; if (!statusEl.textContent || statusEl.textContent === '-' || statusEl.textContent.includes('Pending')) { - statusEl.textContent = 'Waiting for album bundle'; + statusEl.textContent = 'Waiting for release'; + statusEl.classList.add('album-bundle-waiting'); + statusEl.title = 'The album release is downloading first. Tracks will move to processing once SoulSync can match files from it.'; } }); } diff --git a/webui/static/pages-extra.js b/webui/static/pages-extra.js index 915040f1..47a65710 100644 --- a/webui/static/pages-extra.js +++ b/webui/static/pages-extra.js @@ -2473,6 +2473,46 @@ function _adlFormatSpeed(bytesPerSecond) { return formatted ? `${formatted}/s` : ''; } +function _adlSourceLabel(source) { + const labels = { + torrent: 'Torrent', + usenet: 'Usenet', + soulseek: 'Soulseek', + youtube: 'YouTube', + tidal: 'Tidal', + qobuz: 'Qobuz', + hifi: 'HiFi', + deezer_dl: 'Deezer', + amazon: 'Amazon', + lidarr: 'Lidarr', + soundcloud: 'SoundCloud' + }; + const key = String(source || '').toLowerCase(); + return labels[key] || (source ? String(source) : 'Release'); +} + +function _adlBundleStateLabel(state) { + const labels = { + searching: 'searching for release', + downloading: 'downloading release', + staged: 'matching tracks', + failed: 'release failed' + }; + const key = String(state || '').toLowerCase(); + return labels[key] || (state ? String(state).replace(/_/g, ' ') : 'downloading release'); +} + +function _adlBundleProgressText(bundle) { + const pct = _adlBundleProgressPercent(bundle); + const source = _adlSourceLabel(bundle && bundle.source); + const state = _adlBundleStateLabel(bundle && bundle.state); + const release = bundle && bundle.release ? ` - ${bundle.release}` : ''; + const speed = _adlFormatSpeed(bundle && bundle.speed); + const size = _adlFormatBytes(bundle && bundle.size); + const detail = speed || size ? ` (${[speed, size].filter(Boolean).join(' of ')})` : ''; + return `${source} ${state} ${pct}%${release}${detail}`; +} + async function adlClearCompleted() { try { const resp = await fetch('/api/downloads/clear-completed', { method: 'POST' }); @@ -2565,12 +2605,7 @@ function _adlRenderBatchPanel() { phaseText = 'Analyzing...'; phaseIcon = ''; } else if (batch.phase === 'album_downloading') { - const source = albumBundle && albumBundle.source ? `${albumBundle.source} ` : ''; - const release = albumBundle && albumBundle.release ? ` - ${albumBundle.release}` : ''; - const speed = _adlFormatSpeed(albumBundle && albumBundle.speed); - const size = _adlFormatBytes(albumBundle && albumBundle.size); - const detail = speed || size ? ` (${[speed, size].filter(Boolean).join(' of ')})` : ''; - phaseText = `${source}album ${albumBundle && albumBundle.state ? albumBundle.state : 'downloading'} ${bundleProgress}%${release}${detail}`; + phaseText = _adlBundleProgressText(albumBundle); phaseIcon = ''; } else if (batch.phase === 'downloading') { phaseText = `${batch.completed}/${total} tracks`; @@ -2635,7 +2670,9 @@ function _adlRenderBatchPanel() { `; }).join(''); } else { - tracksHtml = '