diff --git a/webui/static/discover.js b/webui/static/discover.js index 9aac9316..14661b4b 100644 --- a/webui/static/discover.js +++ b/webui/static/discover.js @@ -7875,14 +7875,115 @@ function checkForActiveDiscoverDownloads() { } async function startDiscoverPlaylistSync(playlistType, playlistName) { - console.log(`🔄 Navigating to Sync → Discover tab for ${playlistName}`); + console.log(`🔄 Starting sync for ${playlistName} (fire-and-forget from Discover page)`); - // Navigate to the Sync page → Discover tab, highlight the card, and auto-sync - navigateToSyncTab('discover', { - highlight: `discover-sync-card-${playlistType}`, - autoSync: playlistType, - autoSyncName: playlistName, - }); + // Disable the sync button on the Discover page + const buttonId = playlistType.replace(/_/g, '-') + '-sync-btn'; + const syncButton = document.getElementById(buttonId); + if (syncButton) { + syncButton.disabled = true; + syncButton.style.opacity = '0.5'; + syncButton.style.cursor = 'not-allowed'; + } + + try { + // Fetch tracks from API + const apiUrl = _discoverPlaylistApiUrl(playlistType); + if (!apiUrl) { + showToast(`Unknown playlist type: ${playlistType}`, 'error'); + if (syncButton) { syncButton.disabled = false; syncButton.style.opacity = '1'; syncButton.style.cursor = 'pointer'; } + return; + } + + const tracksResponse = await fetch(apiUrl); + let tracks = []; + if (tracksResponse.ok) { + const data = await tracksResponse.json(); + tracks = data.tracks || []; + } + + if (!tracks.length) { + showToast(`No tracks available for ${playlistName}`, 'warning'); + if (syncButton) { syncButton.disabled = false; syncButton.style.opacity = '1'; syncButton.style.cursor = 'pointer'; } + return; + } + + // Convert to sync format + const syncTracks = tracks.map(track => { + if (track.track_data_json) { + const t = track.track_data_json; + if (t.artists && Array.isArray(t.artists)) { + t.artists = t.artists.map(a => a.name || a); + } + return t; + } + return { + id: track.spotify_track_id || track.track_id || '', + name: track.track_name || track.name || '', + artists: [track.artist_name || 'Unknown Artist'], + album: track.album_name || '', + duration_ms: track.duration_ms || 0, + image_url: track.album_cover_url || track.image_url || '' + }; + }); + + const virtualPlaylistId = `discover_${playlistType}`; + + // Fire the batch download + const batchResponse = await fetch(`/api/playlists/${virtualPlaylistId}/start-missing-process`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tracks: syncTracks, playlist_name: playlistName }) + }); + + const result = await batchResponse.json(); + if (result.success) { + // Show toast with clickable link to Sync → Discover tab + _showSyncToastWithLink( + `${playlistName} (${syncTracks.length} tracks) syncing...`, + 'info', + 'View in Sync \u2192', + () => navigateToSyncTab('discover', { highlight: `discover-sync-card-${playlistType}` }) + ); + } else { + showToast(`Failed to start sync: ${result.error || 'Unknown error'}`, 'error'); + if (syncButton) { syncButton.disabled = false; syncButton.style.opacity = '1'; syncButton.style.cursor = 'pointer'; } + } + } catch (error) { + console.error(`Error syncing ${playlistName}:`, error); + showToast(`Failed to sync ${playlistName}`, 'error'); + if (syncButton) { syncButton.disabled = false; syncButton.style.opacity = '1'; syncButton.style.cursor = 'pointer'; } + } +} + +/** + * Show a toast with a clickable action link (like showToast but with a custom link). + */ +function _showSyncToastWithLink(message, type, linkText, onClick) { + const container = document.getElementById('toast-container'); + if (!container) { showToast(message, type); return; } + + const icon = { success: '\u2705', error: '\u274c', warning: '\u26a0\ufe0f', info: '\u2139\ufe0f' }[type] || '\u2139\ufe0f'; + const toast = document.createElement('div'); + toast.className = `toast-compact toast-${type}`; + toast.innerHTML = `${icon}${_escToast(message)}`; + + const link = document.createElement('span'); + link.className = 'toast-compact-link'; + link.textContent = linkText; + link.onclick = e => { e.stopPropagation(); onClick(); }; + toast.appendChild(link); + + toast.onclick = () => { toast.classList.add('toast-exit'); setTimeout(() => { if (container.contains(toast)) container.removeChild(toast); }, 200); }; + container.appendChild(toast); + requestAnimationFrame(() => toast.classList.add('toast-enter')); + + setTimeout(() => { + if (container.contains(toast)) { + toast.classList.add('toast-exit'); + setTimeout(() => { if (container.contains(toast)) container.removeChild(toast); }, 300); + } + }, 6000); } // Track active discover sync pollers