@ -2481,7 +2481,7 @@ async function loadBeatportChartsFromBackend() {
// Rehydrate the chart state first to get discovery results
await rehydrateBeatportChart ( chartInfo , false ) ;
// Create /update active download process with tracks
// Create the download modal using the Beatport-specific function (like YouTube)
if ( ! activeDownloadProcesses [ convertedPlaylistId ] ) {
// Get tracks from the rehydrated state
const ytState = youtubePlaylistStates [ chartInfo . hash ] ;
@ -2513,15 +2513,36 @@ async function loadBeatportChartsFromBackend() {
} ) ;
}
activeDownloadProcesses [ convertedPlaylistId ] = {
status : chartInfo . phase === 'download_complete' ? 'complete' : 'running' ,
batchId : chartInfo . download _process _id ,
tracks : spotifyTracks ,
modalElement : null
} ;
}
if ( spotifyTracks . length > 0 ) {
await openDownloadMissingModalForYouTube (
convertedPlaylistId ,
` [Beatport] ${ chartInfo . name } ` ,
spotifyTracks
) ;
// Set the modal to running state with the correct batch ID
const process = activeDownloadProcesses [ convertedPlaylistId ] ;
if ( process ) {
process . status = chartInfo . phase === 'download_complete' ? 'complete' : 'running' ;
process . batchId = chartInfo . download _process _id ;
console . log ( ` ✅ Set up active download process for Beatport chart: ${ chartInfo . name } with ${ activeDownloadProcesses [ convertedPlaylistId ] . tracks . length } tracks ` ) ;
// 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 ) ;
// Hide modal since this is background rehydration
process . modalElement . style . display = 'none' ;
console . log ( ` ✅ Rehydrated download modal for Beatport chart: ${ chartInfo . name } ` ) ;
}
} else {
console . warn ( ` ⚠️ No Spotify tracks found for Beatport download modal: ${ chartInfo . name } ` ) ;
}
}
} catch ( error ) {
console . warn ( ` ⚠️ Error setting up download process for Beatport chart " ${ chartInfo . name } ": ` , error . message ) ;
}
@ -4270,6 +4291,29 @@ async function startMissingTracksProcess(playlistId) {
}
process . batchId = data . batch _id ;
// Update Beatport backend state with download_process_id now that we have the batchId
if ( playlistId . startsWith ( 'beatport_' ) ) {
const urlHash = playlistId . replace ( 'beatport_' , '' ) ;
const state = youtubePlaylistStates [ urlHash ] ;
if ( state && state . is _beatport _playlist ) {
const chartHash = state . beatport _chart _hash || urlHash ;
try {
fetch ( ` /api/beatport/charts/update-phase/ ${ chartHash } ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( {
phase : 'downloading' ,
download _process _id : data . batch _id
} )
} ) ;
console . log ( ` 🔄 Updated Beatport backend with download_process_id: ${ data . batch _id } ` ) ;
} catch ( error ) {
console . warn ( '⚠️ Error updating Beatport backend with download_process_id:' , error ) ;
}
}
}
startModalDownloadPolling ( playlistId ) ;
} catch ( error ) {
showToast ( ` Failed to start process: ${ error . message } ` , 'error' ) ;
@ -10658,56 +10702,134 @@ async function handleBeatportCardClick(chartHash) {
console . log ( ` 🎧 [Card Click] Opening Beatport discovery modal for ${ state . phase } phase ` ) ;
openYouTubeDiscoveryModal ( chartHash ) ;
} else if ( state . phase === 'downloading' || state . phase === 'download_complete' ) {
// Open download modal if we have the converted playlist ID
// Open download modal if we have the converted playlist ID (following YouTube/Tidal pattern)
const ytState = youtubePlaylistStates [ chartHash ] ;
if ( ytState && ytState . is _beatport _playlist && ytState . convertedSpotifyPlaylistId ) {
console . log ( ` 📥 [Card Click] Opening Beatport download modal for ${ state . phase } phase ` ) ;
// Get Spotify tracks from discovery results (like startBeatportDownloadMissing does)
if ( ytState . discovery _results && ytState . discovery _results . length > 0 ) {
const spotifyTracks = ytState . 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 || { }
} ;
} ) ;
console . log ( ` 📥 [Card Click] Opening download modal for Beatport chart: ${ ytState . playlist . name } (phase: ${ state . phase } ) ` ) ;
const playlistName = ` [Beatport] ${ ytState . playlist . name } ` ;
try {
await openDownloadMissingModalForYouTube ( ytState . convertedSpotifyPlaylistId , playlistName , spotifyTracks ) ;
} catch ( error ) {
console . error ( ` ❌ Error opening Beatport download modal: ` , error ) ;
hideLoadingOverlay ( ) ;
showToast ( 'Error opening download modal' , 'error' ) ;
// Check if modal already exists, if not create it (like Tidal implementation)
if ( activeDownloadProcesses [ ytState . convertedSpotifyPlaylistId ] ) {
const process = activeDownloadProcesses [ ytState . convertedSpotifyPlaylistId ] ;
if ( process . modalElement ) {
console . log ( ` 📱 [Card Click] Showing existing download modal for ${ state . phase } phase ` ) ;
process . modalElement . style . display = 'flex' ;
} else {
console . warn ( ` ⚠️ [Card Click] Download process exists but modal element missing - rehydrating ` ) ;
await rehydrateBeatportDownloadModal ( chartHash , ytState ) ;
}
} else {
console . warn ( ` ⚠️ [Card Click] No discovery results found for Beatport chart: ${ chartHash } ` ) ;
showToast ( 'No tracks found - try refreshing the page' , 'error' ) ;
// Need to create the download modal - fetch the discovery results if needed
console . log ( ` 🔧 [Card Click] Rehydrating Beatport download modal for ${ state . phase } phase ` ) ;
await rehydrateBeatportDownloadModal ( chartHash , ytState ) ;
}
} else {
console . warn ( ` ⚠️ [Card Click] No converted playlist ID found for Beatport chart: ${ chartHash } ` ) ;
showToast ( 'Download data not found - try refreshing the page' , 'error' ) ;
console . error ( '❌ [Card Click] No converted Spotify playlist ID found for Beatport download modal' ) ;
console . log ( '📊 [Card Click] Available state data:' , Object . keys ( ytState || { } ) ) ;
// Fallback: try to open discovery modal if we have discovery results
if ( ytState && ytState . discovery _results && ytState . discovery _results . length > 0 ) {
console . log ( ` 🔄 [Card Click] Fallback: Opening discovery modal with ${ ytState . discovery _results . length } results ` ) ;
openYouTubeDiscoveryModal ( chartHash ) ;
} else {
showToast ( 'Unable to open download modal - missing playlist data' , 'error' ) ;
}
}
}
}
async function rehydrateBeatportDownloadModal ( chartHash , ytState ) {
try {
console . log ( ` 💧 [Rehydration] Attempting fallback rehydration for Beatport chart: ${ chartHash } ` ) ;
// This function is only called as a fallback when the modal wasn't created during backend loading
// In most cases, the modal should already exist from loadBeatportChartsFromBackend()
if ( ! ytState || ! ytState . playlist || ! ytState . convertedSpotifyPlaylistId ) {
console . error ( ` ❌ [Rehydration] Invalid state data for Beatport chart: ${ chartHash } ` ) ;
showToast ( 'Cannot open download modal - invalid playlist data' , 'error' ) ;
return ;
}
// Get discovery results from backend if not already loaded
if ( ! ytState . discovery _results ) {
console . log ( ` 🔍 Fetching discovery results from backend for Beatport chart: ${ chartHash } ` ) ;
const stateResponse = await fetch ( ` /api/beatport/charts/status/ ${ chartHash } ` ) ;
if ( stateResponse . ok ) {
const fullState = await stateResponse . json ( ) ;
ytState . discovery _results = fullState . discovery _results ;
ytState . download _process _id = fullState . download _process _id ;
console . log ( ` ✅ Loaded ${ fullState . discovery _results ? . length || 0 } discovery results from backend ` ) ;
} else {
console . error ( '❌ Failed to fetch Beatport discovery results from backend' ) ;
showToast ( 'Error loading playlist data' , 'error' ) ;
return ;
}
}
// Extract Spotify tracks from discovery results
const spotifyTracks = ytState . 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 ) {
console . error ( '❌ No Spotify tracks found for download modal' ) ;
showToast ( 'No Spotify matches found for download' , 'error' ) ;
return ;
}
const virtualPlaylistId = ytState . convertedSpotifyPlaylistId ;
const playlistName = ` [Beatport] ${ ytState . playlist . name } ` ;
// Create the download modal
await openDownloadMissingModalForYouTube ( virtualPlaylistId , playlistName , spotifyTracks ) ;
// Set up the modal for the running state if we have a download process ID
if ( ytState . download _process _id ) {
const process = activeDownloadProcesses [ virtualPlaylistId ] ;
if ( process ) {
process . status = 'running' ;
process . batchId = ytState . download _process _id ;
// Update UI to reflect running state
const beginBtn = document . getElementById ( ` begin-analysis-btn- ${ virtualPlaylistId } ` ) ;
const cancelBtn = document . getElementById ( ` cancel-all-btn- ${ virtualPlaylistId } ` ) ;
if ( beginBtn ) beginBtn . style . display = 'none' ;
if ( cancelBtn ) cancelBtn . style . display = 'inline-block' ;
// Start polling for this process
startModalDownloadPolling ( virtualPlaylistId ) ;
console . log ( ` ✅ [Rehydration] Fallback modal rehydrated for running download process ` ) ;
}
}
} catch ( error ) {
console . error ( ` ❌ [Rehydration] Error in fallback rehydration for Beatport chart: ` , error ) ;
showToast ( 'Error opening download modal' , 'error' ) ;
hideLoadingOverlay ( ) ;
}
}
function updateBeatportCardPhase ( chartHash , phase ) {
const state = beatportChartStates [ chartHash ] ;
if ( ! state ) return ;
@ -10723,7 +10845,7 @@ function updateBeatportCardPhase(chartHash, phase) {
// Re-attach click handler
const newCard = document . getElementById ( ` beatport-card- ${ chartHash } ` ) ;
if ( newCard ) {
newCard . addEventListener ( 'click' , ( ) => handleBeatportCardClick ( chartHash ) ) ;
newCard . addEventListener ( 'click' , async ( ) => await handleBeatportCardClick ( chartHash ) ) ;
state . cardElement = newCard ;
}
}