download mising tracks
This commit is contained in:
parent
ae98bb8806
commit
f39eb561dc
2 changed files with 409 additions and 9 deletions
|
|
@ -4390,6 +4390,13 @@ def _on_download_completed(batch_id, task_id, success=True):
|
|||
youtube_playlist_states[url_hash]['phase'] = 'download_complete'
|
||||
print(f"📋 Updated YouTube playlist {url_hash} to download_complete phase")
|
||||
|
||||
# Update Tidal playlist phase to 'download_complete' if this is a Tidal playlist
|
||||
if playlist_id and playlist_id.startswith('tidal_'):
|
||||
tidal_playlist_id = playlist_id.replace('tidal_', '')
|
||||
if tidal_playlist_id in tidal_discovery_states:
|
||||
tidal_discovery_states[tidal_playlist_id]['phase'] = 'download_complete'
|
||||
print(f"📋 Updated Tidal playlist {tidal_playlist_id} to download_complete phase")
|
||||
|
||||
print(f"🎉 [Batch Manager] Batch {batch_id} complete - stopping monitor")
|
||||
download_monitor.stop_monitoring(batch_id)
|
||||
|
||||
|
|
@ -4474,6 +4481,13 @@ def _run_full_missing_tracks_process(batch_id, playlist_id, tracks_json):
|
|||
if url_hash in youtube_playlist_states:
|
||||
youtube_playlist_states[url_hash]['phase'] = 'download_complete'
|
||||
print(f"📋 Updated YouTube playlist {url_hash} to download_complete phase (no missing tracks)")
|
||||
|
||||
# Update Tidal playlist phase to 'download_complete' if this is a Tidal playlist
|
||||
if playlist_id.startswith('tidal_'):
|
||||
tidal_playlist_id = playlist_id.replace('tidal_', '')
|
||||
if tidal_playlist_id in tidal_discovery_states:
|
||||
tidal_discovery_states[tidal_playlist_id]['phase'] = 'download_complete'
|
||||
print(f"📋 Updated Tidal playlist {tidal_playlist_id} to download_complete phase (no missing tracks)")
|
||||
return
|
||||
|
||||
print(f" transitioning batch {batch_id} to download phase with {len(missing_tracks)} tracks.")
|
||||
|
|
@ -5335,6 +5349,15 @@ def start_missing_tracks_process(playlist_id):
|
|||
youtube_playlist_states[url_hash]['phase'] = 'downloading'
|
||||
youtube_playlist_states[url_hash]['converted_spotify_playlist_id'] = playlist_id
|
||||
print(f"🔗 Linked YouTube playlist {url_hash} to download process {batch_id} (converted ID: {playlist_id})")
|
||||
|
||||
# Link Tidal playlist to download process if this is a Tidal playlist
|
||||
if playlist_id.startswith('tidal_'):
|
||||
tidal_playlist_id = playlist_id.replace('tidal_', '')
|
||||
if tidal_playlist_id in tidal_discovery_states:
|
||||
tidal_discovery_states[tidal_playlist_id]['download_process_id'] = batch_id
|
||||
tidal_discovery_states[tidal_playlist_id]['phase'] = 'downloading'
|
||||
tidal_discovery_states[tidal_playlist_id]['converted_spotify_playlist_id'] = playlist_id
|
||||
print(f"🔗 Linked Tidal playlist {tidal_playlist_id} to download process {batch_id} (converted ID: {playlist_id})")
|
||||
|
||||
missing_download_executor.submit(_run_full_missing_tracks_process, batch_id, playlist_id, tracks)
|
||||
|
||||
|
|
@ -5763,6 +5786,8 @@ def get_tidal_playlist_states():
|
|||
'spotify_matches': state['spotify_matches'],
|
||||
'spotify_total': state['spotify_total'],
|
||||
'discovery_results': state['discovery_results'],
|
||||
'converted_spotify_playlist_id': state.get('converted_spotify_playlist_id'),
|
||||
'download_process_id': state.get('download_process_id'),
|
||||
'last_accessed': state['last_accessed']
|
||||
}
|
||||
states.append(state_info)
|
||||
|
|
@ -5794,6 +5819,10 @@ def get_tidal_playlist_state(playlist_id):
|
|||
'spotify_matches': state['spotify_matches'],
|
||||
'spotify_total': state['spotify_total'],
|
||||
'discovery_results': state['discovery_results'],
|
||||
'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', {}),
|
||||
'last_accessed': state['last_accessed']
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3000,6 +3000,12 @@ function closeDownloadMissingModal(playlistId) {
|
|||
updateYouTubeCardPhase(urlHash, 'discovered');
|
||||
}
|
||||
|
||||
// Reset Tidal playlist phase to 'discovered' when modal is closed after completion
|
||||
if (playlistId.startsWith('tidal_')) {
|
||||
const tidalPlaylistId = playlistId.replace('tidal_', '');
|
||||
updateTidalCardPhase(tidalPlaylistId, 'discovered');
|
||||
}
|
||||
|
||||
// Clear wishlist modal state when modal is fully closed
|
||||
if (playlistId === 'wishlist') {
|
||||
WishlistModalState.clear(); // Clear all tracking since modal is fully closed
|
||||
|
|
@ -3234,6 +3240,16 @@ async function startMissingTracksProcess(playlistId) {
|
|||
const urlHash = playlistId.replace('youtube_', '');
|
||||
updateYouTubeCardPhase(urlHash, 'downloading');
|
||||
}
|
||||
|
||||
// Update Tidal playlist phase to 'downloading' if this is a Tidal playlist
|
||||
if (playlistId.startsWith('tidal_')) {
|
||||
const tidalPlaylistId = playlistId.replace('tidal_', '');
|
||||
if (tidalPlaylistStates[tidalPlaylistId]) {
|
||||
tidalPlaylistStates[tidalPlaylistId].phase = 'downloading';
|
||||
updateTidalCardPhase(tidalPlaylistId, 'downloading');
|
||||
console.log(`🔄 Updated Tidal playlist ${tidalPlaylistId} to downloading phase`);
|
||||
}
|
||||
}
|
||||
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
||||
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
||||
|
||||
|
|
@ -3419,6 +3435,16 @@ function startModalDownloadPolling(playlistId) {
|
|||
updateYouTubeCardPhase(urlHash, 'download_complete');
|
||||
}
|
||||
|
||||
// Update Tidal playlist phase to 'download_complete' if this is a Tidal playlist
|
||||
if (playlistId.startsWith('tidal_')) {
|
||||
const tidalPlaylistId = playlistId.replace('tidal_', '');
|
||||
if (tidalPlaylistStates[tidalPlaylistId]) {
|
||||
tidalPlaylistStates[tidalPlaylistId].phase = 'download_complete';
|
||||
updateTidalCardPhase(tidalPlaylistId, 'download_complete');
|
||||
console.log(`✅ Updated Tidal playlist ${tidalPlaylistId} to download_complete phase`);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle background wishlist processing completion specially
|
||||
if (isBackgroundWishlist) {
|
||||
console.log(`🎉 Background wishlist processing complete: ${completedCount} downloaded, ${failedOrCancelledCount} failed`);
|
||||
|
|
@ -6435,6 +6461,99 @@ async function handleTidalCardClick(playlistId) {
|
|||
} else if (state.phase === 'discovering' || state.phase === 'discovered' || state.phase === 'syncing' || state.phase === 'sync_complete') {
|
||||
// Reopen existing modal (like sync.py)
|
||||
openTidalDiscoveryModal(playlistId, state.playlist);
|
||||
} else if (state.phase === 'downloading' || state.phase === 'download_complete') {
|
||||
// Open download modal if we have the converted playlist ID
|
||||
if (state.convertedSpotifyPlaylistId) {
|
||||
console.log(`🔍 Opening download modal for Tidal playlist: ${state.playlist.name}`);
|
||||
// Check if modal already exists, if not create it
|
||||
if (activeDownloadProcesses[state.convertedSpotifyPlaylistId]) {
|
||||
const process = activeDownloadProcesses[state.convertedSpotifyPlaylistId];
|
||||
if (process.modalElement) {
|
||||
process.modalElement.style.display = 'flex';
|
||||
}
|
||||
} else {
|
||||
// Need to create the download modal - fetch the discovery results
|
||||
await rehydrateTidalDownloadModal(playlistId, state);
|
||||
}
|
||||
} else {
|
||||
console.error('❌ No converted Spotify playlist ID found for Tidal download modal');
|
||||
showToast('Unable to open download modal - missing playlist data', 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function rehydrateTidalDownloadModal(playlistId, state) {
|
||||
try {
|
||||
console.log(`💧 Rehydrating Tidal download modal for: ${state.playlist.name}`);
|
||||
|
||||
// Get discovery results from backend if not already loaded
|
||||
if (!state.discovery_results) {
|
||||
console.log(`🔍 Fetching discovery results from backend for Tidal playlist: ${playlistId}`);
|
||||
const stateResponse = await fetch(`/api/tidal/state/${playlistId}`);
|
||||
if (stateResponse.ok) {
|
||||
const fullState = await stateResponse.json();
|
||||
state.discovery_results = fullState.discovery_results;
|
||||
state.convertedSpotifyPlaylistId = fullState.converted_spotify_playlist_id;
|
||||
state.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 Tidal discovery results from backend');
|
||||
showToast('Error loading playlist data', 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract Spotify tracks from discovery results
|
||||
const spotifyTracks = [];
|
||||
for (const result of state.discovery_results) {
|
||||
if (result.spotify_data) {
|
||||
spotifyTracks.push(result.spotify_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (spotifyTracks.length === 0) {
|
||||
console.error('❌ No Spotify tracks found for download modal');
|
||||
showToast('No Spotify matches found for download', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const virtualPlaylistId = state.convertedSpotifyPlaylistId;
|
||||
const playlistName = `[Tidal] ${state.playlist.name}`;
|
||||
|
||||
// Create the download modal
|
||||
await openDownloadMissingModalForTidal(virtualPlaylistId, playlistName, spotifyTracks);
|
||||
|
||||
// If we have a download process ID, set up the modal for the running state
|
||||
if (state.download_process_id) {
|
||||
const process = activeDownloadProcesses[virtualPlaylistId];
|
||||
if (process) {
|
||||
process.status = state.phase === 'download_complete' ? 'complete' : 'running';
|
||||
process.batchId = state.download_process_id;
|
||||
|
||||
// Update UI based on phase
|
||||
const beginBtn = document.getElementById(`begin-analysis-btn-${virtualPlaylistId}`);
|
||||
const cancelBtn = document.getElementById(`cancel-all-btn-${virtualPlaylistId}`);
|
||||
|
||||
if (state.phase === 'downloading') {
|
||||
if (beginBtn) beginBtn.style.display = 'none';
|
||||
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||
|
||||
// Start polling for live updates
|
||||
startModalDownloadPolling(virtualPlaylistId);
|
||||
console.log(`🔄 Started polling for active Tidal download: ${state.download_process_id}`);
|
||||
} else if (state.phase === 'download_complete') {
|
||||
if (beginBtn) beginBtn.style.display = 'none';
|
||||
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||
console.log(`✅ Showing completed Tidal download results: ${state.download_process_id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`✅ Successfully rehydrated Tidal download modal for: ${state.playlist.name}`);
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Error rehydrating Tidal download modal:`, error);
|
||||
showToast('Error opening download modal', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -6679,6 +6798,62 @@ async function loadTidalPlaylistStatesFromBackend() {
|
|||
await applyTidalPlaylistState(stateInfo);
|
||||
}
|
||||
|
||||
// Rehydrate download modals for Tidal playlists in downloading/download_complete phases
|
||||
for (const stateInfo of states) {
|
||||
if ((stateInfo.phase === 'downloading' || stateInfo.phase === 'download_complete') &&
|
||||
stateInfo.converted_spotify_playlist_id && stateInfo.download_process_id) {
|
||||
|
||||
const convertedPlaylistId = stateInfo.converted_spotify_playlist_id;
|
||||
|
||||
if (!activeDownloadProcesses[convertedPlaylistId]) {
|
||||
console.log(`💧 Rehydrating download modal for Tidal playlist: ${stateInfo.playlist_id}`);
|
||||
try {
|
||||
// Get the playlist data
|
||||
const playlistData = tidalPlaylists.find(p => p.id === stateInfo.playlist_id);
|
||||
if (!playlistData) {
|
||||
console.warn(`⚠️ Playlist data not found for rehydration: ${stateInfo.playlist_id}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the download modal using the Tidal-specific function
|
||||
const spotifyTracks = tidalPlaylistStates[stateInfo.playlist_id]?.discovery_results
|
||||
?.filter(result => result.spotify_data)
|
||||
?.map(result => result.spotify_data) || [];
|
||||
|
||||
if (spotifyTracks.length > 0) {
|
||||
await openDownloadMissingModalForTidal(
|
||||
convertedPlaylistId,
|
||||
`[Tidal] ${playlistData.name}`,
|
||||
spotifyTracks
|
||||
);
|
||||
|
||||
// Set the modal to running state with the correct batch ID
|
||||
const process = activeDownloadProcesses[convertedPlaylistId];
|
||||
if (process) {
|
||||
process.status = 'running';
|
||||
process.batchId = stateInfo.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';
|
||||
|
||||
// Start polling for this process
|
||||
startModalDownloadPolling(convertedPlaylistId);
|
||||
|
||||
console.log(`✅ Rehydrated Tidal download modal for batch ${stateInfo.download_process_id}`);
|
||||
}
|
||||
} else {
|
||||
console.warn(`⚠️ No Spotify tracks found for Tidal playlist rehydration: ${stateInfo.playlist_id}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error rehydrating Tidal download modal for ${stateInfo.playlist_id}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ Tidal playlist states loaded and applied');
|
||||
|
||||
} catch (error) {
|
||||
|
|
@ -6687,7 +6862,7 @@ async function loadTidalPlaylistStatesFromBackend() {
|
|||
}
|
||||
|
||||
async function applyTidalPlaylistState(stateInfo) {
|
||||
const { playlist_id, phase, discovery_progress, spotify_matches, discovery_results } = stateInfo;
|
||||
const { playlist_id, phase, discovery_progress, spotify_matches, discovery_results, converted_spotify_playlist_id, download_process_id } = stateInfo;
|
||||
|
||||
try {
|
||||
console.log(`🎵 Applying saved state for Tidal playlist: ${playlist_id}, Phase: ${phase}`);
|
||||
|
|
@ -6713,6 +6888,8 @@ async function applyTidalPlaylistState(stateInfo) {
|
|||
tidalPlaylistStates[playlist_id].discovery_progress = discovery_progress;
|
||||
tidalPlaylistStates[playlist_id].spotify_matches = spotify_matches;
|
||||
tidalPlaylistStates[playlist_id].discovery_results = discovery_results;
|
||||
tidalPlaylistStates[playlist_id].convertedSpotifyPlaylistId = converted_spotify_playlist_id;
|
||||
tidalPlaylistStates[playlist_id].download_process_id = download_process_id;
|
||||
tidalPlaylistStates[playlist_id].playlist = playlistData; // Ensure playlist data is set
|
||||
|
||||
// Fetch full discovery results for non-fresh playlists (matching YouTube pattern)
|
||||
|
|
@ -6729,6 +6906,8 @@ async function applyTidalPlaylistState(stateInfo) {
|
|||
tidalPlaylistStates[playlist_id].discovery_results = fullState.discovery_results;
|
||||
tidalPlaylistStates[playlist_id].discovery_progress = fullState.discovery_progress;
|
||||
tidalPlaylistStates[playlist_id].spotify_matches = fullState.spotify_matches;
|
||||
tidalPlaylistStates[playlist_id].convertedSpotifyPlaylistId = fullState.converted_spotify_playlist_id;
|
||||
tidalPlaylistStates[playlist_id].download_process_id = fullState.download_process_id;
|
||||
console.log(`✅ Restored ${fullState.discovery_results.length} discovery results for Tidal playlist: ${playlistData.name}`);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -7017,17 +7196,209 @@ function updateTidalModalButtons(urlHash, phase) {
|
|||
}
|
||||
}
|
||||
|
||||
function startTidalDownloadMissing(urlHash) {
|
||||
console.log(`🎵 Starting Tidal download missing tracks for: ${urlHash}`);
|
||||
const state = youtubePlaylistStates[urlHash];
|
||||
if (!state || !state.is_tidal_playlist) {
|
||||
console.error('❌ Invalid Tidal playlist state for download');
|
||||
async function startTidalDownloadMissing(urlHash) {
|
||||
try {
|
||||
console.log('🔍 Starting download missing tracks for Tidal playlist:', urlHash);
|
||||
|
||||
const state = youtubePlaylistStates[urlHash];
|
||||
if (!state || !state.is_tidal_playlist) {
|
||||
console.error('❌ Invalid Tidal playlist state for download');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the actual Tidal playlist ID
|
||||
const tidalPlaylistId = state.tidal_playlist_id;
|
||||
const tidalState = tidalPlaylistStates[tidalPlaylistId];
|
||||
|
||||
if (!tidalState || !tidalState.discovery_results) {
|
||||
showToast('No discovery results available for download', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert Tidal discovery results to Spotify tracks format (same as YouTube)
|
||||
const spotifyTracks = [];
|
||||
for (const result of tidalState.discovery_results) {
|
||||
if (result.spotify_data) {
|
||||
spotifyTracks.push(result.spotify_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (spotifyTracks.length === 0) {
|
||||
showToast('No Spotify matches found for download', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a virtual playlist for the download system
|
||||
const virtualPlaylistId = `tidal_${tidalPlaylistId}`;
|
||||
const playlistName = `[Tidal] ${tidalState.playlist.name}`;
|
||||
|
||||
// Store reference for card navigation (same as YouTube)
|
||||
tidalState.convertedSpotifyPlaylistId = virtualPlaylistId;
|
||||
state.convertedSpotifyPlaylistId = virtualPlaylistId;
|
||||
|
||||
// Close the discovery modal if it's open (same as YouTube)
|
||||
const discoveryModal = document.getElementById(`youtube-discovery-modal-${urlHash}`);
|
||||
if (discoveryModal) {
|
||||
discoveryModal.style.display = 'none';
|
||||
console.log('🔄 Closed Tidal discovery modal to show download modal');
|
||||
}
|
||||
|
||||
// Open download missing tracks modal for Tidal playlist
|
||||
await openDownloadMissingModalForTidal(virtualPlaylistId, playlistName, spotifyTracks);
|
||||
|
||||
// Phase will change to 'downloading' when user clicks "Begin Analysis" button
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error starting download missing tracks:', error);
|
||||
showToast(`Error starting downloads: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
async function openDownloadMissingModalForTidal(virtualPlaylistId, playlistName, spotifyTracks) {
|
||||
// Check if a process is already active for this virtual playlist
|
||||
if (activeDownloadProcesses[virtualPlaylistId]) {
|
||||
console.log(`Modal for ${virtualPlaylistId} already exists. Showing it.`);
|
||||
const process = activeDownloadProcesses[virtualPlaylistId];
|
||||
if (process.modalElement) {
|
||||
if (process.status === 'complete') {
|
||||
showToast('Showing previous results. Close this modal to start a new analysis.', 'info');
|
||||
}
|
||||
process.modalElement.style.display = 'flex';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`📥 Opening Download Missing Tracks modal for Tidal playlist: ${virtualPlaylistId}`);
|
||||
|
||||
// TODO: Implement Tidal download missing tracks logic
|
||||
// For now, show a message that this feature is coming soon
|
||||
showToast('🔍 Tidal download missing tracks functionality coming soon!', 'info');
|
||||
// Create virtual playlist object for compatibility with existing modal logic
|
||||
const virtualPlaylist = {
|
||||
id: virtualPlaylistId,
|
||||
name: playlistName,
|
||||
track_count: spotifyTracks.length
|
||||
};
|
||||
|
||||
// Store the tracks in the cache for the modal to use
|
||||
playlistTrackCache[virtualPlaylistId] = spotifyTracks;
|
||||
currentPlaylistTracks = spotifyTracks;
|
||||
currentModalPlaylistId = virtualPlaylistId;
|
||||
|
||||
let modal = document.createElement('div');
|
||||
modal.id = `download-missing-modal-${virtualPlaylistId}`;
|
||||
modal.className = 'download-missing-modal';
|
||||
modal.style.display = 'none';
|
||||
document.body.appendChild(modal);
|
||||
|
||||
// Register the new process in our global state tracker using the same structure as Spotify
|
||||
activeDownloadProcesses[virtualPlaylistId] = {
|
||||
status: 'idle',
|
||||
modalElement: modal,
|
||||
poller: null,
|
||||
batchId: null,
|
||||
playlist: virtualPlaylist,
|
||||
tracks: spotifyTracks
|
||||
};
|
||||
|
||||
// Use the exact same modal HTML structure as the existing Spotify modal
|
||||
modal.innerHTML = `
|
||||
<div class="download-missing-modal-content">
|
||||
<div class="download-missing-modal-header">
|
||||
<h2 class="download-missing-modal-title">Download Missing Tracks - ${escapeHtml(playlistName)}</h2>
|
||||
<span class="download-missing-modal-close" onclick="closeDownloadMissingModal('${virtualPlaylistId}')">×</span>
|
||||
</div>
|
||||
|
||||
<div class="download-missing-modal-body">
|
||||
<div class="download-dashboard-stats">
|
||||
<div class="dashboard-stat stat-total">
|
||||
<div class="dashboard-stat-number" id="stat-total-${virtualPlaylistId}">${spotifyTracks.length}</div>
|
||||
<div class="dashboard-stat-label">Total Tracks</div>
|
||||
</div>
|
||||
<div class="dashboard-stat stat-found">
|
||||
<div class="dashboard-stat-number" id="stat-found-${virtualPlaylistId}">-</div>
|
||||
<div class="dashboard-stat-label">Found in Library</div>
|
||||
</div>
|
||||
<div class="dashboard-stat stat-missing">
|
||||
<div class="dashboard-stat-number" id="stat-missing-${virtualPlaylistId}">-</div>
|
||||
<div class="dashboard-stat-label">Missing Tracks</div>
|
||||
</div>
|
||||
<div class="dashboard-stat stat-downloaded">
|
||||
<div class="dashboard-stat-number" id="stat-downloaded-${virtualPlaylistId}">0</div>
|
||||
<div class="dashboard-stat-label">Downloaded</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="download-progress-section">
|
||||
<div class="progress-item">
|
||||
<div class="progress-label">
|
||||
🔍 Library Analysis
|
||||
<span id="analysis-progress-text-${virtualPlaylistId}">Ready to start</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill analysis" id="analysis-progress-fill-${virtualPlaylistId}"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress-item">
|
||||
<div class="progress-label">
|
||||
⏬ Downloads
|
||||
<span id="download-progress-text-${virtualPlaylistId}">Waiting for analysis</span>
|
||||
</div>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill download" id="download-progress-fill-${virtualPlaylistId}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="download-tracks-section">
|
||||
<div class="download-tracks-header">
|
||||
<h3 class="download-tracks-title">📋 Track Analysis & Download Status</h3>
|
||||
</div>
|
||||
<div class="download-tracks-table-container">
|
||||
<table class="download-tracks-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Track</th>
|
||||
<th>Artist</th>
|
||||
<th>Duration</th>
|
||||
<th>Library Match</th>
|
||||
<th>Download Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="download-tracks-tbody-${virtualPlaylistId}">
|
||||
${spotifyTracks.map((track, index) => `
|
||||
<tr data-track-index="${index}">
|
||||
<td class="track-number">${index + 1}</td>
|
||||
<td class="track-name" title="${escapeHtml(track.name)}">${escapeHtml(track.name)}</td>
|
||||
<td class="track-artist" title="${escapeHtml(track.artists.join(', '))}">${track.artists.join(', ')}</td>
|
||||
<td class="track-duration">${formatDuration(track.duration_ms)}</td>
|
||||
<td class="track-match-status match-checking" id="match-${virtualPlaylistId}-${index}">🔍 Pending</td>
|
||||
<td class="track-download-status" id="download-${virtualPlaylistId}-${index}">-</td>
|
||||
<td class="track-actions" id="actions-${virtualPlaylistId}-${index}">-</td>
|
||||
</tr>
|
||||
`).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="download-missing-modal-footer">
|
||||
<div class="download-phase-controls">
|
||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||
Begin Analysis
|
||||
</button>
|
||||
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
||||
Cancel All
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-close-section">
|
||||
<button class="download-control-btn secondary" onclick="closeDownloadMissingModal('${virtualPlaylistId}')">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue