From a61c581c253e6a0405aac6afde30b23e1253a9f4 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Tue, 25 Nov 2025 17:41:22 -0800 Subject: [PATCH] listenbrainz fully connected to discovery modal --- web_server.py | 45 +++- webui/static/script.js | 497 ++++++++++++++++++++++++----------------- 2 files changed, 336 insertions(+), 206 deletions(-) diff --git a/web_server.py b/web_server.py index b5a3f1bd..358c3aed 100644 --- a/web_server.py +++ b/web_server.py @@ -16777,9 +16777,10 @@ def get_listenbrainz_playlist_state(playlist_mbid): 'spotify_matches': state['spotify_matches'], 'spotify_total': state['spotify_total'], 'discovery_results': state['discovery_results'], - 'sync_playlist_id': state['sync_playlist_id'], - 'converted_spotify_playlist_id': state['converted_spotify_playlist_id'], - 'sync_progress': state['sync_progress'], + 'sync_playlist_id': state.get('sync_playlist_id'), + 'converted_spotify_playlist_id': state.get('converted_spotify_playlist_id'), + 'download_process_id': state.get('download_process_id'), + 'sync_progress': state.get('sync_progress', {}), 'created_at': state['created_at'], 'last_accessed': state['last_accessed'] } @@ -16931,6 +16932,44 @@ def get_listenbrainz_discovery_status(playlist_mbid): print(f"โŒ Error getting ListenBrainz discovery status: {e}") return jsonify({"error": str(e)}), 500 +@app.route('/api/listenbrainz/update-phase/', methods=['POST']) +def update_listenbrainz_phase(playlist_mbid): + """Update ListenBrainz playlist phase (for phase transitions and persistence)""" + try: + if playlist_mbid not in listenbrainz_playlist_states: + return jsonify({"error": "ListenBrainz playlist not found"}), 404 + + data = request.get_json() or {} + new_phase = data.get('phase') + + if not new_phase: + return jsonify({"error": "Phase is required"}), 400 + + state = listenbrainz_playlist_states[playlist_mbid] + state['phase'] = new_phase + state['last_accessed'] = time.time() + + # Update download process ID if provided (for download persistence) + if 'download_process_id' in data: + state['download_process_id'] = data['download_process_id'] + logger.info(f"๐ŸŽต Updated ListenBrainz download_process_id: {data['download_process_id']}") + + # Update converted Spotify playlist ID if provided (for download persistence) + if 'converted_spotify_playlist_id' in data: + state['converted_spotify_playlist_id'] = data['converted_spotify_playlist_id'] + logger.info(f"๐ŸŽต Updated ListenBrainz converted_spotify_playlist_id: {data['converted_spotify_playlist_id']}") + + logger.info(f"๐ŸŽต Updated ListenBrainz playlist {playlist_mbid} phase to: {new_phase}") + + return jsonify({ + "success": True, + "phase": new_phase + }) + + except Exception as e: + print(f"โŒ Error updating ListenBrainz playlist phase: {e}") + return jsonify({"error": str(e)}), 500 + @app.route('/api/listenbrainz/discovery/update_match', methods=['POST']) def update_listenbrainz_discovery_match(): """Update a ListenBrainz discovery result with manually selected Spotify track""" diff --git a/webui/static/script.js b/webui/static/script.js index 47a654bd..6a881fa0 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -3094,7 +3094,7 @@ async function loadListenBrainzPlaylistsFromBackend() { for (const playlistInfo of playlists) { const playlistMbid = playlistInfo.playlist_mbid; - console.log(`๐ŸŽต Hydrating ListenBrainz playlist: ${playlistInfo.playlist.name} (Phase: ${playlistInfo.phase})`); + console.log(`๐ŸŽต Hydrating ListenBrainz playlist: ${playlistInfo.playlist.name} (Phase: ${playlistInfo.phase}, MBID: ${playlistMbid})`); // Fetch full state for non-fresh playlists to restore discovery results if (playlistInfo.phase !== 'fresh') { @@ -3155,6 +3155,15 @@ async function loadListenBrainzPlaylistsFromBackend() { console.log(`๐Ÿ”„ [Backend Loading] Auto-starting polling for discovering playlist: ${playlistInfo.playlist.name}`); startListenBrainzDiscoveryPolling(playlistInfo.playlist_mbid); } + // Show sync button for discovered playlists (hidden by default) + else if (playlistInfo.phase === 'discovered' || playlistInfo.phase === 'syncing' || playlistInfo.phase === 'sync_complete') { + const playlistId = `discover-lb-playlist-${playlistInfo.playlist_mbid}`; + const syncBtn = document.getElementById(`${playlistId}-sync-btn`); + if (syncBtn) { + syncBtn.style.display = 'inline-block'; + console.log(`โœ… Showing sync button for discovered playlist: ${playlistInfo.playlist.name}`); + } + } } listenbrainzPlaylistsLoaded = true; @@ -4745,7 +4754,46 @@ async function closeDownloadMissingModal(playlistId) { console.error(`โŒ [Modal Close] Error updating backend phase for Tidal playlist ${tidalPlaylistId}:`, error); } } - + + // Reset ListenBrainz playlist phase to 'discovered' when modal is closed + if (playlistId.startsWith('listenbrainz_')) { + const playlistMbid = playlistId.replace('listenbrainz_', ''); + + console.log(`๐Ÿงน [Modal Close] Processing ListenBrainz playlist close: playlistId="${playlistId}", mbid="${playlistMbid}"`); + + // Clear download-specific state but preserve discovery results + if (listenbrainzPlaylistStates[playlistMbid]) { + const currentPhase = listenbrainzPlaylistStates[playlistMbid].phase; + console.log(`๐Ÿงน [Modal Close] Current phase before reset: ${currentPhase}`); + + // Reset to discovered phase (unless download actually completed successfully) + if (currentPhase !== 'download_complete') { + // Clear download-specific fields + delete listenbrainzPlaylistStates[playlistMbid].download_process_id; + delete listenbrainzPlaylistStates[playlistMbid].convertedSpotifyPlaylistId; + + // Set back to discovered + listenbrainzPlaylistStates[playlistMbid].phase = 'discovered'; + + // Update backend state + try { + await fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phase: 'discovered' }) + }); + console.log(`โœ… [Modal Close] Updated backend phase for ListenBrainz playlist ${playlistMbid} to 'discovered'`); + } catch (error) { + console.error(`โŒ [Modal Close] Error updating backend phase for ListenBrainz playlist ${playlistMbid}:`, error); + } + + console.log(`๐Ÿ”„ [Modal Close] Reset ListenBrainz playlist ${playlistMbid} to discovered phase`); + } + } else { + console.error(`โŒ [Modal Close] No ListenBrainz state found for mbid: ${playlistMbid}`); + } + } + // Clear wishlist modal state when modal is fully closed if (playlistId === 'wishlist') { WishlistModalState.clear(); // Clear all tracking since modal is fully closed @@ -5511,6 +5559,30 @@ async function startMissingTracksProcess(playlistId) { console.log(`๐Ÿ”„ Updated Beatport chart ${chartHash} to downloading phase`); } } + + // Update ListenBrainz playlist phase to 'downloading' if this is a ListenBrainz playlist + if (playlistId.startsWith('listenbrainz_')) { + const playlistMbid = playlistId.replace('listenbrainz_', ''); + const state = listenbrainzPlaylistStates[playlistMbid]; + + if (state) { + // Update frontend state + state.phase = 'downloading'; + + // Update backend state + try { + fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phase: 'downloading' }) + }); + } catch (error) { + console.warn('โš ๏ธ Error updating backend ListenBrainz phase to downloading:', error); + } + + console.log(`๐Ÿ”„ Updated ListenBrainz playlist ${playlistMbid} to downloading phase`); + } + } document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none'; document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block'; @@ -5605,6 +5677,33 @@ async function startMissingTracksProcess(playlistId) { } } + // Update ListenBrainz backend state with download_process_id and convertedSpotifyPlaylistId + if (playlistId.startsWith('listenbrainz_')) { + const playlistMbid = playlistId.replace('listenbrainz_', ''); + const state = listenbrainzPlaylistStates[playlistMbid]; + if (state) { + // Store in frontend state + state.download_process_id = data.batch_id; + state.convertedSpotifyPlaylistId = playlistId; + + // Update backend state + try { + fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + phase: 'downloading', + download_process_id: data.batch_id, + converted_spotify_playlist_id: playlistId + }) + }); + console.log(`๐Ÿ”„ Updated ListenBrainz backend with download_process_id: ${data.batch_id}`); + } catch (error) { + console.warn('โš ๏ธ Error updating ListenBrainz backend with download_process_id:', error); + } + } + } + startModalDownloadPolling(playlistId); } catch (error) { showToast(`Failed to start process: ${error.message}`, 'error'); @@ -16819,7 +16918,19 @@ function startListenBrainzDiscoveryPolling(playlistMbid) { clearInterval(pollInterval); delete activeYouTubePollers[playlistMbid]; - // Update phase + // Update phase in backend for persistence (like Beatport does) + try { + await fetch(`/api/listenbrainz/update-phase/${playlistMbid}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phase: 'discovered' }) + }); + console.log('โœ… Updated ListenBrainz backend phase to discovered'); + } catch (error) { + console.warn('โš ๏ธ Failed to update backend phase:', error); + } + + // Update phase in frontend state if (listenbrainzPlaylistStates[playlistMbid]) { listenbrainzPlaylistStates[playlistMbid].phase = 'discovered'; } @@ -16827,6 +16938,14 @@ function startListenBrainzDiscoveryPolling(playlistMbid) { // Update modal buttons to show sync and download buttons updateYouTubeModalButtons(playlistMbid, 'discovered'); + // Show sync button in playlist listing (hidden by default until discovered) + const playlistId = `discover-lb-playlist-${playlistMbid}`; + const syncBtn = document.getElementById(`${playlistId}-sync-btn`); + if (syncBtn) { + syncBtn.style.display = 'inline-block'; + console.log('โœ… Showing sync button after discovery completion'); + } + console.log('โœ… ListenBrainz discovery complete:', playlistMbid); showToast('ListenBrainz discovery complete!', 'success'); } @@ -16962,6 +17081,22 @@ async function startListenBrainzPlaylistSync(playlistMbid) { try { console.log('๐Ÿ”„ Starting ListenBrainz sync for:', state.playlist.name); + // Check if being called from playlist listing (has UI elements) or modal + const listingPlaylistId = `discover-lb-playlist-${playlistMbid}`; + const statusDisplay = document.getElementById(`${listingPlaylistId}-sync-status`); + const isFromListing = statusDisplay !== null; + + if (isFromListing) { + console.log('๐Ÿ”„ Sync initiated from playlist listing'); + // Show status display in listing + statusDisplay.style.display = 'block'; + const syncButton = document.getElementById(`${listingPlaylistId}-sync-btn`); + if (syncButton) { + syncButton.disabled = true; + syncButton.style.opacity = '0.5'; + } + } + // Call backend to start sync const response = await fetch(`/api/listenbrainz/sync/start/${playlistMbid}`, { method: 'POST' @@ -16976,10 +17111,12 @@ async function startListenBrainzPlaylistSync(playlistMbid) { state.phase = 'syncing'; // Start polling for sync progress - startListenBrainzSyncPolling(playlistMbid); - - // Update modal - updateYouTubeModalButtons(playlistMbid, 'syncing'); + if (isFromListing) { + startListenBrainzListingSyncPolling(playlistMbid, listingPlaylistId); + } else { + startListenBrainzSyncPolling(playlistMbid); + updateYouTubeModalButtons(playlistMbid, 'syncing'); + } showToast('Starting ListenBrainz sync...', 'info'); @@ -16989,6 +17126,86 @@ async function startListenBrainzPlaylistSync(playlistMbid) { } } +function startListenBrainzListingSyncPolling(playlistMbid, listingPlaylistId) { + console.log(`๐Ÿ”„ Starting listing sync polling for: ${playlistMbid} (UI: ${listingPlaylistId})`); + + // Stop any existing polling + if (activeYouTubePollers[playlistMbid]) { + clearInterval(activeYouTubePollers[playlistMbid]); + } + + const pollInterval = setInterval(async () => { + try { + const response = await fetch(`/api/listenbrainz/sync/status/${playlistMbid}`); + const status = await response.json(); + + if (status.error) { + console.error('โŒ Error polling ListenBrainz sync status:', status.error); + clearInterval(pollInterval); + delete activeYouTubePollers[playlistMbid]; + return; + } + + // Update UI elements in listing + const totalEl = document.getElementById(`${listingPlaylistId}-sync-total`); + const matchedEl = document.getElementById(`${listingPlaylistId}-sync-matched`); + const failedEl = document.getElementById(`${listingPlaylistId}-sync-failed`); + const percentageEl = document.getElementById(`${listingPlaylistId}-sync-percentage`); + + console.log(`๐Ÿ“Š ListenBrainz listing sync progress:`, { + total: status.progress?.total_tracks, + matched: status.progress?.matched_tracks, + failed: status.progress?.failed_tracks, + complete: status.complete + }); + + if (totalEl) totalEl.textContent = status.progress?.total_tracks || 0; + if (matchedEl) matchedEl.textContent = status.progress?.matched_tracks || 0; + if (failedEl) failedEl.textContent = status.progress?.failed_tracks || 0; + + const percentage = status.progress?.total_tracks > 0 + ? Math.round(((status.progress?.matched_tracks || 0) / status.progress.total_tracks) * 100) + : 0; + if (percentageEl) percentageEl.textContent = percentage; + + // Check if complete + if (status.complete) { + clearInterval(pollInterval); + delete activeYouTubePollers[playlistMbid]; + + const statusDisplay = document.getElementById(`${listingPlaylistId}-sync-status`); + const syncButton = document.getElementById(`${listingPlaylistId}-sync-btn`); + + if (statusDisplay) { + setTimeout(() => { + statusDisplay.style.display = 'none'; + }, 3000); + } + + if (syncButton) { + syncButton.disabled = false; + syncButton.style.opacity = '1'; + } + + // Update state + if (listenbrainzPlaylistStates[playlistMbid]) { + listenbrainzPlaylistStates[playlistMbid].phase = 'sync_complete'; + } + + showToast(`Sync complete: ${status.progress?.matched_tracks || 0}/${status.progress?.total_tracks || 0} tracks matched`, 'success'); + console.log('โœ… ListenBrainz listing sync complete:', playlistMbid); + } + + } catch (error) { + console.error('โŒ Error polling ListenBrainz listing sync:', error); + clearInterval(pollInterval); + delete activeYouTubePollers[playlistMbid]; + } + }, 1000); + + activeYouTubePollers[playlistMbid] = pollInterval; +} + // ============================================================================ // ARTISTS PAGE FUNCTIONALITY - ELEGANT SEARCH & DISCOVERY // ============================================================================ @@ -28026,7 +28243,7 @@ async function loadListenBrainzTabContent(tabId) { trackCount = playlistData.track.length; } - const playlistId = `lb-${tabId}-${index}`; + const playlistId = `discover-lb-playlist-${identifier}`; // Use consistent MBID-based ID const virtualPlaylistId = `discover_lb_${tabId}_${identifier}`; html += ` @@ -28045,8 +28262,9 @@ async function loadListenBrainzTabContent(tabId) { @@ -28082,7 +28300,7 @@ async function loadListenBrainzTabContent(tabId) { playlists.forEach((playlist, index) => { const playlistData = playlist.playlist || playlist; const identifier = playlistData.identifier?.split('/').pop() || ''; - const playlistId = `lb-${tabId}-${index}`; + const playlistId = `discover-lb-playlist-${identifier}`; // Use consistent MBID-based ID loadListenBrainzPlaylistTracks(identifier, playlistId); }); } @@ -28208,147 +28426,6 @@ function displayListenBrainzTracks(tracks, playlistId) { playlistContainer.innerHTML = html; } -async function startListenBrainzPlaylistSync(identifier, title, playlistId) { - try { - console.log(`๐Ÿ”„ Starting sync for ListenBrainz playlist:`, { identifier, title, playlistId }); - - const tracks = listenbrainzTracksCache[identifier]; - console.log(`๐Ÿ“Š Cached tracks for ${identifier}:`, tracks?.length || 0); - - if (!tracks || tracks.length === 0) { - console.error('โŒ No tracks found in cache'); - showToast('No tracks to sync', 'error'); - return; - } - - console.log(`โœ… Found ${tracks.length} tracks to sync`); - - // Convert ListenBrainz tracks to Spotify format (same as other discover playlists) - const spotifyTracks = tracks.map(track => ({ - id: null, // No Spotify ID for ListenBrainz tracks - name: track.track_name, - artists: [cleanArtistName(track.artist_name)], // Clean featured artists, array of strings for sync compatibility - album: { - name: track.album_name, - images: track.album_cover_url ? [{ url: track.album_cover_url }] : [] - }, - duration_ms: track.duration_ms || 0, - mbid: track.mbid // Include MusicBrainz ID for matching - })); - - const virtualPlaylistId = `discover_lb_${identifier}`; - console.log(`๐Ÿ†” Virtual playlist ID: ${virtualPlaylistId}`); - - // Store in cache for sync function (CRITICAL - same as other discover playlists) - playlistTrackCache[virtualPlaylistId] = spotifyTracks; - - // Create virtual playlist object (CRITICAL - same as other discover playlists) - const virtualPlaylist = { - id: virtualPlaylistId, - name: title, - track_count: spotifyTracks.length - }; - - // Add to spotify playlists array if not already there (CRITICAL) - if (!spotifyPlaylists.find(p => p.id === virtualPlaylistId)) { - spotifyPlaylists.push(virtualPlaylist); - } - - // Show sync status display - const statusDisplay = document.getElementById(`${playlistId}-sync-status`); - const syncButton = document.getElementById(`${playlistId}-sync-btn`); - - if (statusDisplay) statusDisplay.style.display = 'block'; - if (syncButton) { - syncButton.disabled = true; - syncButton.style.opacity = '0.5'; - syncButton.style.cursor = 'not-allowed'; - } - - // Use the same sync function as all other discover playlists - await startPlaylistSync(virtualPlaylistId); - - // Extract image URL from first track for download bar bubble - let imageUrl = null; - if (spotifyTracks && spotifyTracks.length > 0) { - const firstTrack = spotifyTracks[0]; - if (firstTrack.album && firstTrack.album.images && firstTrack.album.images.length > 0) { - imageUrl = firstTrack.album.images[0].url; - } - } - - // Add to discover download bar - addDiscoverDownload(virtualPlaylistId, title, 'listenbrainz', imageUrl); - - // Start polling for progress updates (using discover playlist pattern) - startListenBrainzSyncPolling(playlistId, virtualPlaylistId); - - } catch (error) { - console.error('โŒ Error starting ListenBrainz playlist sync:', error); - showToast('Failed to start sync', 'error'); - - const statusDisplay = document.getElementById(`${playlistId}-sync-status`); - const syncButton = document.getElementById(`${playlistId}-sync-btn`); - if (statusDisplay) statusDisplay.style.display = 'none'; - if (syncButton) { - syncButton.disabled = false; - syncButton.style.opacity = '1'; - syncButton.style.cursor = 'pointer'; - } - } -} - -function startListenBrainzSyncPolling(playlistId, virtualPlaylistId) { - const pollInterval = setInterval(async () => { - try { - const response = await fetch(`/api/sync/status/${virtualPlaylistId}`); - if (!response.ok) { - clearInterval(pollInterval); - return; - } - - const status = await response.json(); - - // Update UI - const totalEl = document.getElementById(`${playlistId}-sync-total`); - const matchedEl = document.getElementById(`${playlistId}-sync-matched`); - const failedEl = document.getElementById(`${playlistId}-sync-failed`); - const percentageEl = document.getElementById(`${playlistId}-sync-percentage`); - - if (totalEl) totalEl.textContent = status.total_tracks || 0; - if (matchedEl) matchedEl.textContent = status.matched_tracks || 0; - if (failedEl) failedEl.textContent = status.failed_tracks || 0; - - const percentage = status.total_tracks > 0 - ? Math.round(((status.matched_tracks || 0) / status.total_tracks) * 100) - : 0; - if (percentageEl) percentageEl.textContent = percentage; - - // Check if complete - if (status.is_complete) { - clearInterval(pollInterval); - - const statusDisplay = document.getElementById(`${playlistId}-sync-status`); - const syncButton = document.getElementById(`${playlistId}-sync-btn`); - - if (statusDisplay) { - setTimeout(() => { - statusDisplay.style.display = 'none'; - }, 3000); - } - - if (syncButton) syncButton.disabled = false; - - showToast(`Sync complete: ${status.matched_tracks}/${status.total_tracks} tracks matched`, 'success'); - } - - } catch (error) { - console.error('Error polling sync status:', error); - clearInterval(pollInterval); - } - }, 2000); -} - async function openDownloadModalForListenBrainzPlaylist(identifier, title) { try { const tracks = listenbrainzTracksCache[identifier]; @@ -28358,9 +28435,12 @@ async function openDownloadModalForListenBrainzPlaylist(identifier, title) { } console.log(`๐ŸŽต Opening ListenBrainz discovery modal: ${title}`); + console.log(`๐Ÿ” Looking for existing state with identifier: ${identifier}`); + console.log(`๐Ÿ“‹ All ListenBrainz states:`, Object.keys(listenbrainzPlaylistStates)); // Check if state already exists from backend hydration (like Beatport does) const existingState = listenbrainzPlaylistStates[identifier]; + console.log(`๐Ÿ” Existing state found:`, existingState ? `Phase: ${existingState.phase}` : 'None'); if (existingState && existingState.phase !== 'fresh') { // State exists - rehydrate the modal with existing data @@ -28376,66 +28456,77 @@ async function openDownloadModalForListenBrainzPlaylist(identifier, title) { const convertedPlaylistId = existingState.convertedSpotifyPlaylistId; try { + // Check if modal already exists (user just closed it) + if (activeDownloadProcesses[convertedPlaylistId]) { + console.log(`โœ… Download modal already exists, just showing it`); + const process = activeDownloadProcesses[convertedPlaylistId]; + if (process.modalElement) { + process.modalElement.style.display = 'flex'; + } + return; + } + // Create the download modal using the ListenBrainz state - if (!activeDownloadProcesses[convertedPlaylistId]) { - // Get tracks from the existing state - let spotifyTracks = []; + console.log(`๐Ÿ†• Creating new download modal for rehydration`); + // Get tracks from the existing state + let spotifyTracks = []; - if (existingState && existingState.discovery_results) { - spotifyTracks = existingState.discovery_results - .filter(result => result.spotify_data) - .map(result => { - const track = result.spotify_data; - // Ensure artists is an array of strings - if (track.artists && Array.isArray(track.artists)) { - track.artists = track.artists.map(artist => - typeof artist === 'string' ? artist : (artist.name || artist) - ); - } else if (track.artists && typeof track.artists === 'string') { - track.artists = [track.artists]; - } else { - track.artists = ['Unknown Artist']; - } - return { - id: track.id, - name: track.name, - artists: track.artists, - album: track.album || 'Unknown Album', - duration_ms: track.duration_ms || 0, - external_urls: track.external_urls || {} - }; - }); - } + if (existingState && existingState.discovery_results) { + spotifyTracks = existingState.discovery_results + .filter(result => result.spotify_data) + .map(result => { + const track = result.spotify_data; + // Ensure artists is an array of strings + if (track.artists && Array.isArray(track.artists)) { + track.artists = track.artists.map(artist => + typeof artist === 'string' ? artist : (artist.name || artist) + ); + } else if (track.artists && typeof track.artists === 'string') { + track.artists = [track.artists]; + } else { + track.artists = ['Unknown Artist']; + } + return { + id: track.id, + name: track.name, + artists: track.artists, + album: track.album || 'Unknown Album', + duration_ms: track.duration_ms || 0, + external_urls: track.external_urls || {} + }; + }); + } - if (spotifyTracks.length > 0) { - await openDownloadMissingModalForYouTube( - convertedPlaylistId, - `[ListenBrainz] ${title}`, - spotifyTracks - ); + if (spotifyTracks.length > 0) { + await openDownloadMissingModalForYouTube( + convertedPlaylistId, + `[ListenBrainz] ${title}`, + spotifyTracks + ); - // Set the modal to running state with the correct batch ID - const process = activeDownloadProcesses[convertedPlaylistId]; - if (process) { - process.status = existingState.phase === 'download_complete' ? 'complete' : 'running'; - process.batchId = existingState.download_process_id; + // Set the modal to running state with the correct batch ID + const process = activeDownloadProcesses[convertedPlaylistId]; + if (process) { + process.status = existingState.phase === 'download_complete' ? 'complete' : 'running'; + process.batchId = existingState.download_process_id; - // Update UI to running state - const beginBtn = document.getElementById(`begin-analysis-btn-${convertedPlaylistId}`); - const cancelBtn = document.getElementById(`cancel-all-btn-${convertedPlaylistId}`); - if (beginBtn) beginBtn.style.display = 'none'; - if (cancelBtn) cancelBtn.style.display = 'inline-block'; + // Update UI to running state + const beginBtn = document.getElementById(`begin-analysis-btn-${convertedPlaylistId}`); + const cancelBtn = document.getElementById(`cancel-all-btn-${convertedPlaylistId}`); + if (beginBtn) beginBtn.style.display = 'none'; + if (cancelBtn) cancelBtn.style.display = 'inline-block'; - // Start polling for this process - startModalDownloadPolling(convertedPlaylistId); + // Start polling for this process + startModalDownloadPolling(convertedPlaylistId); - // Hide modal since this is background rehydration - process.modalElement.style.display = 'none'; - console.log(`โœ… Rehydrated download modal for ListenBrainz playlist: ${title}`); + // Show modal since user clicked the download button (different from background rehydration) + if (process.modalElement) { + process.modalElement.style.display = 'flex'; } - } else { - console.warn(`โš ๏ธ No Spotify tracks found for ListenBrainz download modal: ${title}`); + console.log(`โœ… Rehydrated download modal for ListenBrainz playlist: ${title}`); } + } else { + console.warn(`โš ๏ธ No Spotify tracks found for ListenBrainz download modal: ${title}`); } } catch (error) { console.warn(`โš ๏ธ Error setting up download process for ListenBrainz playlist "${title}":`, error.message);