Improve wishlist processing and modal sync logic
Sanitizes wishlist track data for consistent backend/frontend handling, enhances wishlist process state reporting for better UI sync, and refactors frontend modal logic to prevent auto-show conflicts and ensure user-driven visibility. Also improves polling and rehydration to keep frontend state in sync with server-side auto-processing.
This commit is contained in:
parent
38d19a1f2d
commit
219a36c05b
2 changed files with 279 additions and 134 deletions
112
web_server.py
112
web_server.py
|
|
@ -4641,10 +4641,57 @@ def start_simple_background_monitor():
|
||||||
# == AUTOMATIC WISHLIST PROCESSING ==
|
# == AUTOMATIC WISHLIST PROCESSING ==
|
||||||
# ===============================
|
# ===============================
|
||||||
|
|
||||||
|
def _sanitize_track_data_for_processing(track_data):
|
||||||
|
"""
|
||||||
|
Sanitizes track data from wishlist service to ensure consistent format.
|
||||||
|
Handles album field conversion from dict to string and artist field normalization.
|
||||||
|
"""
|
||||||
|
if not isinstance(track_data, dict):
|
||||||
|
print(f"⚠️ [Sanitize] Unexpected track data type: {type(track_data)}")
|
||||||
|
return track_data
|
||||||
|
|
||||||
|
# Create a copy to avoid modifying original data
|
||||||
|
sanitized = track_data.copy()
|
||||||
|
|
||||||
|
# Handle album field - convert dictionary to string if needed
|
||||||
|
raw_album = sanitized.get('album', '')
|
||||||
|
if isinstance(raw_album, dict) and 'name' in raw_album:
|
||||||
|
sanitized['album'] = raw_album['name']
|
||||||
|
print(f"🔧 [Sanitize] Converted album from dict to string: '{raw_album['name']}'")
|
||||||
|
elif not isinstance(raw_album, str):
|
||||||
|
sanitized['album'] = str(raw_album)
|
||||||
|
print(f"🔧 [Sanitize] Converted album to string: '{sanitized['album']}'")
|
||||||
|
|
||||||
|
# Handle artists field - ensure it's a list of strings
|
||||||
|
raw_artists = sanitized.get('artists', [])
|
||||||
|
if isinstance(raw_artists, list):
|
||||||
|
processed_artists = []
|
||||||
|
for artist in raw_artists:
|
||||||
|
if isinstance(artist, str):
|
||||||
|
processed_artists.append(artist)
|
||||||
|
elif isinstance(artist, dict) and 'name' in artist:
|
||||||
|
processed_artists.append(artist['name'])
|
||||||
|
print(f"🔧 [Sanitize] Converted artist from dict to string: '{artist['name']}'")
|
||||||
|
else:
|
||||||
|
processed_artists.append(str(artist))
|
||||||
|
print(f"🔧 [Sanitize] Converted artist to string: '{str(artist)}'")
|
||||||
|
sanitized['artists'] = processed_artists
|
||||||
|
else:
|
||||||
|
print(f"⚠️ [Sanitize] Unexpected artists format: {type(raw_artists)}")
|
||||||
|
sanitized['artists'] = [str(raw_artists)] if raw_artists else []
|
||||||
|
|
||||||
|
return sanitized
|
||||||
|
|
||||||
def start_wishlist_auto_processing():
|
def start_wishlist_auto_processing():
|
||||||
"""Start automatic wishlist processing with 1-minute initial delay."""
|
"""Start automatic wishlist processing with 1-minute initial delay."""
|
||||||
global wishlist_auto_timer
|
global wishlist_auto_timer
|
||||||
|
|
||||||
|
# Skip if this is Flask's debug reloader process
|
||||||
|
import os
|
||||||
|
if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
|
||||||
|
print("🔄 Skipping auto-processing start in Flask debug reloader")
|
||||||
|
return
|
||||||
|
|
||||||
with wishlist_timer_lock:
|
with wishlist_timer_lock:
|
||||||
# Stop any existing timer to prevent duplicates
|
# Stop any existing timer to prevent duplicates
|
||||||
if wishlist_auto_timer is not None:
|
if wishlist_auto_timer is not None:
|
||||||
|
|
@ -4721,14 +4768,22 @@ def _process_wishlist_automatically():
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get wishlist tracks for processing
|
# Get wishlist tracks for processing
|
||||||
wishlist_tracks = wishlist_service.get_wishlist_tracks_for_download()
|
raw_wishlist_tracks = wishlist_service.get_wishlist_tracks_for_download()
|
||||||
if not wishlist_tracks:
|
if not raw_wishlist_tracks:
|
||||||
print("⚠️ No tracks returned from wishlist service.")
|
print("⚠️ No tracks returned from wishlist service.")
|
||||||
with wishlist_timer_lock:
|
with wishlist_timer_lock:
|
||||||
wishlist_auto_processing = False
|
wishlist_auto_processing = False
|
||||||
schedule_next_wishlist_processing()
|
schedule_next_wishlist_processing()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# SANITIZE: Ensure consistent data format from wishlist service
|
||||||
|
wishlist_tracks = []
|
||||||
|
for track in raw_wishlist_tracks:
|
||||||
|
sanitized_track = _sanitize_track_data_for_processing(track)
|
||||||
|
wishlist_tracks.append(sanitized_track)
|
||||||
|
|
||||||
|
print(f"🔧 [Auto-Wishlist] Sanitized {len(wishlist_tracks)} tracks from wishlist service")
|
||||||
|
|
||||||
# Create batch for automatic processing
|
# Create batch for automatic processing
|
||||||
batch_id = str(uuid.uuid4())
|
batch_id = str(uuid.uuid4())
|
||||||
playlist_name = "Wishlist (Auto)"
|
playlist_name = "Wishlist (Auto)"
|
||||||
|
|
@ -4857,8 +4912,15 @@ def get_wishlist_tracks():
|
||||||
try:
|
try:
|
||||||
from core.wishlist_service import get_wishlist_service
|
from core.wishlist_service import get_wishlist_service
|
||||||
wishlist_service = get_wishlist_service()
|
wishlist_service = get_wishlist_service()
|
||||||
tracks = wishlist_service.get_wishlist_tracks_for_download()
|
raw_tracks = wishlist_service.get_wishlist_tracks_for_download()
|
||||||
return jsonify({"tracks": tracks})
|
|
||||||
|
# SANITIZE: Ensure consistent data format for frontend
|
||||||
|
sanitized_tracks = []
|
||||||
|
for track in raw_tracks:
|
||||||
|
sanitized_track = _sanitize_track_data_for_processing(track)
|
||||||
|
sanitized_tracks.append(sanitized_track)
|
||||||
|
|
||||||
|
return jsonify({"tracks": sanitized_tracks})
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error getting wishlist tracks: {e}")
|
print(f"Error getting wishlist tracks: {e}")
|
||||||
return jsonify({"error": str(e)}), 500
|
return jsonify({"error": str(e)}), 500
|
||||||
|
|
@ -4874,10 +4936,18 @@ def start_wishlist_missing_downloads():
|
||||||
wishlist_service = get_wishlist_service()
|
wishlist_service = get_wishlist_service()
|
||||||
|
|
||||||
# Get wishlist tracks formatted for download modal
|
# Get wishlist tracks formatted for download modal
|
||||||
wishlist_tracks = wishlist_service.get_wishlist_tracks_for_download()
|
raw_wishlist_tracks = wishlist_service.get_wishlist_tracks_for_download()
|
||||||
if not wishlist_tracks:
|
if not raw_wishlist_tracks:
|
||||||
return jsonify({"success": False, "error": "No tracks in wishlist"}), 400
|
return jsonify({"success": False, "error": "No tracks in wishlist"}), 400
|
||||||
|
|
||||||
|
# SANITIZE: Ensure consistent data format from wishlist service
|
||||||
|
wishlist_tracks = []
|
||||||
|
for track in raw_wishlist_tracks:
|
||||||
|
sanitized_track = _sanitize_track_data_for_processing(track)
|
||||||
|
wishlist_tracks.append(sanitized_track)
|
||||||
|
|
||||||
|
print(f"🔧 [Manual-Wishlist] Sanitized {len(wishlist_tracks)} tracks from wishlist service")
|
||||||
|
|
||||||
batch_id = str(uuid.uuid4())
|
batch_id = str(uuid.uuid4())
|
||||||
|
|
||||||
# Use "wishlist" as the playlist_id for consistency in the modal system
|
# Use "wishlist" as the playlist_id for consistency in the modal system
|
||||||
|
|
@ -6418,13 +6488,28 @@ def get_active_processes():
|
||||||
with tasks_lock:
|
with tasks_lock:
|
||||||
for batch_id, batch_data in download_batches.items():
|
for batch_id, batch_data in download_batches.items():
|
||||||
if batch_data.get('phase') not in ['complete', 'error', 'cancelled']:
|
if batch_data.get('phase') not in ['complete', 'error', 'cancelled']:
|
||||||
active_processes.append({
|
process_info = {
|
||||||
"type": "batch",
|
"type": "batch",
|
||||||
"playlist_id": batch_data.get('playlist_id'),
|
"playlist_id": batch_data.get('playlist_id'),
|
||||||
"playlist_name": batch_data.get('playlist_name'),
|
"playlist_name": batch_data.get('playlist_name'),
|
||||||
"batch_id": batch_id,
|
"batch_id": batch_id,
|
||||||
"phase": batch_data.get('phase')
|
"phase": batch_data.get('phase')
|
||||||
})
|
}
|
||||||
|
|
||||||
|
# Enhanced wishlist information for better frontend state management
|
||||||
|
if batch_data.get('playlist_id') == 'wishlist':
|
||||||
|
process_info.update({
|
||||||
|
"auto_initiated": batch_data.get('auto_initiated', False),
|
||||||
|
"auto_processing_timestamp": batch_data.get('auto_processing_timestamp'),
|
||||||
|
"should_show_modal": True, # Wishlist processes should always be visible
|
||||||
|
"is_background_process": batch_data.get('auto_initiated', False)
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add current auto-processing state for frontend awareness
|
||||||
|
with wishlist_timer_lock:
|
||||||
|
process_info["auto_processing_active"] = wishlist_auto_processing
|
||||||
|
|
||||||
|
active_processes.append(process_info)
|
||||||
|
|
||||||
# Add YouTube playlists in non-fresh phases for rehydration
|
# Add YouTube playlists in non-fresh phases for rehydration
|
||||||
for url_hash, state in youtube_playlist_states.items():
|
for url_hash, state in youtube_playlist_states.items():
|
||||||
|
|
@ -8759,12 +8844,21 @@ def _run_sync_task(playlist_id, playlist_name, tracks_json):
|
||||||
print(f"🔄 Converting JSON tracks to SpotifyTrack objects...")
|
print(f"🔄 Converting JSON tracks to SpotifyTrack objects...")
|
||||||
tracks = []
|
tracks = []
|
||||||
for i, t in enumerate(tracks_json):
|
for i, t in enumerate(tracks_json):
|
||||||
|
# Handle album field - extract name if it's a dictionary
|
||||||
|
raw_album = t.get('album', '')
|
||||||
|
if isinstance(raw_album, dict) and 'name' in raw_album:
|
||||||
|
album_name = raw_album['name']
|
||||||
|
elif isinstance(raw_album, str):
|
||||||
|
album_name = raw_album
|
||||||
|
else:
|
||||||
|
album_name = str(raw_album)
|
||||||
|
|
||||||
# Create SpotifyTrack objects with proper default values for missing fields
|
# Create SpotifyTrack objects with proper default values for missing fields
|
||||||
track = SpotifyTrack(
|
track = SpotifyTrack(
|
||||||
id=t.get('id', ''), # Provide default empty string
|
id=t.get('id', ''), # Provide default empty string
|
||||||
name=t.get('name', ''),
|
name=t.get('name', ''),
|
||||||
artists=t.get('artists', []),
|
artists=t.get('artists', []),
|
||||||
album=t.get('album', ''),
|
album=album_name,
|
||||||
duration_ms=t.get('duration_ms', 0),
|
duration_ms=t.get('duration_ms', 0),
|
||||||
popularity=t.get('popularity', 0), # Default value
|
popularity=t.get('popularity', 0), # Default value
|
||||||
preview_url=t.get('preview_url'),
|
preview_url=t.get('preview_url'),
|
||||||
|
|
|
||||||
|
|
@ -1975,41 +1975,69 @@ async function rehydrateModal(processInfo, userRequested = false) {
|
||||||
|
|
||||||
// Handle wishlist processes specially
|
// Handle wishlist processes specially
|
||||||
if (playlist_id === "wishlist") {
|
if (playlist_id === "wishlist") {
|
||||||
console.log(`🔍 Current activeDownloadProcesses keys: [${Object.keys(activeDownloadProcesses).join(', ')}]`);
|
console.log(`💧 [Rehydrate] Handling wishlist modal for active process: ${batch_id}`);
|
||||||
|
|
||||||
await openDownloadMissingWishlistModal();
|
// Check if modal already exists and is visible
|
||||||
const process = activeDownloadProcesses[playlist_id];
|
const existingProcess = activeDownloadProcesses[playlist_id];
|
||||||
if (!process) {
|
const modalAlreadyOpen = existingProcess && existingProcess.modalElement &&
|
||||||
console.error('❌ Failed to create wishlist process in activeDownloadProcesses');
|
existingProcess.modalElement.style.display === 'flex';
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`✅ Setting wishlist process state - batchId: ${batch_id}, status: running`);
|
if (modalAlreadyOpen) {
|
||||||
process.status = 'running';
|
console.log(`💧 [Rehydrate] Wishlist modal already open - updating existing modal with auto-process state`);
|
||||||
process.batchId = batch_id;
|
|
||||||
// Note: Wishlist processes don't affect sync page refresh button state
|
|
||||||
|
|
||||||
const beginBtn = document.getElementById(`begin-analysis-btn-${playlist_id}`);
|
// Update existing process with new batch info
|
||||||
const cancelBtn = document.getElementById(`cancel-all-btn-${playlist_id}`);
|
existingProcess.status = 'running';
|
||||||
|
existingProcess.batchId = batch_id;
|
||||||
|
|
||||||
if (beginBtn) beginBtn.style.display = 'none';
|
// Update UI to reflect running state
|
||||||
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
const beginBtn = document.getElementById(`begin-analysis-btn-${playlist_id}`);
|
||||||
|
const cancelBtn = document.getElementById(`cancel-all-btn-${playlist_id}`);
|
||||||
|
if (beginBtn) beginBtn.style.display = 'none';
|
||||||
|
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||||
|
|
||||||
startModalDownloadPolling(playlist_id);
|
// Ensure polling is active for live updates
|
||||||
|
if (!existingProcess.intervalId) {
|
||||||
|
console.log(`💧 [Rehydrate] Starting polling for existing modal`);
|
||||||
|
startModalDownloadPolling(playlist_id);
|
||||||
|
}
|
||||||
|
|
||||||
// Enhanced logic: Show modal if user-requested OR if it was previously visible before page refresh
|
console.log(`✅ [Rehydrate] Successfully updated existing wishlist modal for auto-process`);
|
||||||
const wasModalPreviouslyVisible = WishlistModalState.wasVisible();
|
|
||||||
const shouldShowModal = userRequested || (wasModalPreviouslyVisible && !WishlistModalState.wasUserClosed());
|
|
||||||
|
|
||||||
if (shouldShowModal) {
|
|
||||||
process.modalElement.style.display = 'flex';
|
|
||||||
WishlistModalState.setVisible(); // Track that modal is now visible
|
|
||||||
WishlistModalState.clearUserClosed(); // Clear any user closed state since we're showing it
|
|
||||||
console.log(`🔍 Showing wishlist modal - User requested: ${userRequested}, Previously visible: ${wasModalPreviouslyVisible}`);
|
|
||||||
} else {
|
} else {
|
||||||
process.modalElement.style.display = 'none';
|
console.log(`💧 [Rehydrate] Creating new wishlist modal for active process: ${batch_id}`);
|
||||||
WishlistModalState.setHidden(); // Track that modal is hidden
|
|
||||||
console.log('🔍 Hiding wishlist modal for background rehydration');
|
// Create the modal with current server state
|
||||||
|
await openDownloadMissingWishlistModal();
|
||||||
|
const process = activeDownloadProcesses[playlist_id];
|
||||||
|
if (!process) {
|
||||||
|
console.error('❌ [Rehydrate] Failed to create wishlist process in activeDownloadProcesses');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sync process state with server
|
||||||
|
console.log(`✅ [Rehydrate] Syncing wishlist process state - batchId: ${batch_id}, status: running`);
|
||||||
|
process.status = 'running';
|
||||||
|
process.batchId = batch_id;
|
||||||
|
|
||||||
|
// Update UI to reflect running state
|
||||||
|
const beginBtn = document.getElementById(`begin-analysis-btn-${playlist_id}`);
|
||||||
|
const cancelBtn = document.getElementById(`cancel-all-btn-${playlist_id}`);
|
||||||
|
if (beginBtn) beginBtn.style.display = 'none';
|
||||||
|
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Start polling for live updates
|
||||||
|
startModalDownloadPolling(playlist_id);
|
||||||
|
|
||||||
|
// SIMPLIFIED VISIBILITY LOGIC: Show modal if user requested it, otherwise keep hidden for background sync
|
||||||
|
if (userRequested) {
|
||||||
|
console.log('👤 [Rehydrate] User requested - showing wishlist modal');
|
||||||
|
process.modalElement.style.display = 'flex';
|
||||||
|
WishlistModalState.setVisible();
|
||||||
|
WishlistModalState.clearUserClosed();
|
||||||
|
} else {
|
||||||
|
console.log('🔄 [Rehydrate] Background sync - keeping modal hidden until user interaction');
|
||||||
|
process.modalElement.style.display = 'none';
|
||||||
|
WishlistModalState.setHidden();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -3524,14 +3552,41 @@ function startGlobalDownloadPolling() {
|
||||||
// Get all active processes that need polling
|
// Get all active processes that need polling
|
||||||
const activeBatchIds = [];
|
const activeBatchIds = [];
|
||||||
const batchToPlaylistMap = {};
|
const batchToPlaylistMap = {};
|
||||||
|
let hasOpenWishlistModal = false;
|
||||||
|
|
||||||
Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => {
|
Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => {
|
||||||
if (process.batchId && process.status === 'running') {
|
if (process.batchId && process.status === 'running') {
|
||||||
activeBatchIds.push(process.batchId);
|
activeBatchIds.push(process.batchId);
|
||||||
batchToPlaylistMap[process.batchId] = playlistId;
|
batchToPlaylistMap[process.batchId] = playlistId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there's an open wishlist modal (visible and idle/waiting)
|
||||||
|
if (playlistId === 'wishlist' && process.modalElement &&
|
||||||
|
process.modalElement.style.display === 'flex' &&
|
||||||
|
(!process.batchId || process.status !== 'running')) {
|
||||||
|
hasOpenWishlistModal = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Special handling for open wishlist modal - check for new auto-processing
|
||||||
|
if (hasOpenWishlistModal) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/active-processes');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const processes = data.active_processes || [];
|
||||||
|
const serverWishlistProcess = processes.find(p => p.playlist_id === 'wishlist');
|
||||||
|
|
||||||
|
if (serverWishlistProcess) {
|
||||||
|
console.log('🔄 [Global Polling] Detected auto-processing for open wishlist modal - rehydrating');
|
||||||
|
await rehydrateModal(serverWishlistProcess, false); // false = not user-requested
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.debug('⚠️ [Global Polling] Failed to check for wishlist auto-processing:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (activeBatchIds.length === 0) {
|
if (activeBatchIds.length === 0) {
|
||||||
console.debug('📊 [Global Polling] No active processes, continuing polling');
|
console.debug('📊 [Global Polling] No active processes, continuing polling');
|
||||||
return;
|
return;
|
||||||
|
|
@ -3609,14 +3664,41 @@ function startGlobalDownloadPollingWithInterval(interval) {
|
||||||
globalDownloadStatusPoller = setInterval(async () => {
|
globalDownloadStatusPoller = setInterval(async () => {
|
||||||
const activeBatchIds = [];
|
const activeBatchIds = [];
|
||||||
const batchToPlaylistMap = {};
|
const batchToPlaylistMap = {};
|
||||||
|
let hasOpenWishlistModal = false;
|
||||||
|
|
||||||
Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => {
|
Object.entries(activeDownloadProcesses).forEach(([playlistId, process]) => {
|
||||||
if (process.batchId && process.status === 'running') {
|
if (process.batchId && process.status === 'running') {
|
||||||
activeBatchIds.push(process.batchId);
|
activeBatchIds.push(process.batchId);
|
||||||
batchToPlaylistMap[process.batchId] = playlistId;
|
batchToPlaylistMap[process.batchId] = playlistId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there's an open wishlist modal (visible and idle/waiting)
|
||||||
|
if (playlistId === 'wishlist' && process.modalElement &&
|
||||||
|
process.modalElement.style.display === 'flex' &&
|
||||||
|
(!process.batchId || process.status !== 'running')) {
|
||||||
|
hasOpenWishlistModal = true;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Special handling for open wishlist modal - check for new auto-processing
|
||||||
|
if (hasOpenWishlistModal) {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/active-processes');
|
||||||
|
if (response.ok) {
|
||||||
|
const data = await response.json();
|
||||||
|
const processes = data.active_processes || [];
|
||||||
|
const serverWishlistProcess = processes.find(p => p.playlist_id === 'wishlist');
|
||||||
|
|
||||||
|
if (serverWishlistProcess) {
|
||||||
|
console.log('🔄 [Global Polling] Detected auto-processing for open wishlist modal - rehydrating');
|
||||||
|
await rehydrateModal(serverWishlistProcess, false); // false = not user-requested
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.debug('⚠️ [Global Polling] Failed to check for wishlist auto-processing:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (activeBatchIds.length === 0) {
|
if (activeBatchIds.length === 0) {
|
||||||
console.debug('📊 [Global Polling] No active processes, continuing polling');
|
console.debug('📊 [Global Polling] No active processes, continuing polling');
|
||||||
return;
|
return;
|
||||||
|
|
@ -3701,16 +3783,8 @@ function processModalStatusUpdate(playlistId, data) {
|
||||||
|
|
||||||
console.debug(`📊 [Status Update] Processing update for ${playlistId}: phase=${data.phase}, tasks=${(data.tasks || []).length}`);
|
console.debug(`📊 [Status Update] Processing update for ${playlistId}: phase=${data.phase}, tasks=${(data.tasks || []).length}`);
|
||||||
|
|
||||||
// Auto-show wishlist modal during active auto-processing
|
// Note: Wishlist modal visibility is now managed by handleWishlistButtonClick() only
|
||||||
const isWishlist = (playlistId === 'wishlist');
|
// Auto-show logic has been simplified to prevent conflicts
|
||||||
const isAutoInitiated = data.auto_initiated || false;
|
|
||||||
const isModalHidden = process.modalElement && process.modalElement.style.display === 'none';
|
|
||||||
|
|
||||||
if (isWishlist && isAutoInitiated && isModalHidden && currentPage === 'dashboard' && !WishlistModalState.wasUserClosed()) {
|
|
||||||
console.log('🤖 [Status Update] Auto-showing wishlist modal during active auto-processing');
|
|
||||||
process.modalElement.style.display = 'flex';
|
|
||||||
WishlistModalState.setVisible();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.phase === 'analysis') {
|
if (data.phase === 'analysis') {
|
||||||
const progress = data.analysis_progress;
|
const progress = data.analysis_progress;
|
||||||
|
|
@ -3859,13 +3933,7 @@ function processModalStatusUpdate(playlistId, data) {
|
||||||
const isAutoInitiated = data.auto_initiated || false; // Server indicates if batch was auto-started
|
const isAutoInitiated = data.auto_initiated || false; // Server indicates if batch was auto-started
|
||||||
const isBackgroundWishlist = isWishlist && (isModalHidden || isAutoInitiated);
|
const isBackgroundWishlist = isWishlist && (isModalHidden || isAutoInitiated);
|
||||||
|
|
||||||
// Auto-show modal for wishlist auto-processing if user is on dashboard and hasn't closed it
|
// Note: Auto-show logic removed - wishlist modal visibility managed by user interaction only
|
||||||
if (isWishlist && isAutoInitiated && isModalHidden && currentPage === 'dashboard' && !WishlistModalState.wasUserClosed()) {
|
|
||||||
console.log('🤖 [Status Update] Auto-showing wishlist modal for live updates during auto-processing');
|
|
||||||
process.modalElement.style.display = 'flex';
|
|
||||||
WishlistModalState.setVisible();
|
|
||||||
showToast('Auto-processing wishlist - showing live updates', 'info', 2000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.phase === 'cancelled') {
|
if (data.phase === 'cancelled') {
|
||||||
process.status = 'cancelled';
|
process.status = 'cancelled';
|
||||||
|
|
@ -6919,34 +6987,19 @@ async function checkForAutoInitiatedWishlistProcess() {
|
||||||
if (serverWishlistProcess && serverWishlistProcess.auto_initiated) {
|
if (serverWishlistProcess && serverWishlistProcess.auto_initiated) {
|
||||||
console.log('🤖 [Auto-Processing] Detected auto-initiated wishlist process during polling');
|
console.log('🤖 [Auto-Processing] Detected auto-initiated wishlist process during polling');
|
||||||
|
|
||||||
// Check if we need to show the modal (no existing modal or modal is hidden)
|
// Only sync frontend state if needed, but don't auto-show modal
|
||||||
const needsModalDisplay = !clientWishlistProcess ||
|
const needsSync = !clientWishlistProcess ||
|
||||||
|
clientWishlistProcess.batchId !== serverWishlistProcess.batch_id ||
|
||||||
!clientWishlistProcess.modalElement ||
|
!clientWishlistProcess.modalElement ||
|
||||||
clientWishlistProcess.modalElement.style.display === 'none' ||
|
|
||||||
!document.body.contains(clientWishlistProcess.modalElement);
|
!document.body.contains(clientWishlistProcess.modalElement);
|
||||||
|
|
||||||
if (needsModalDisplay) {
|
if (needsSync) {
|
||||||
console.log('🤖 [Auto-Processing] Auto-showing wishlist modal for auto-processing');
|
console.log('🔄 [Auto-Processing] Syncing frontend state for auto-processing (background mode)');
|
||||||
|
await rehydrateModal(serverWishlistProcess, false); // Background sync only
|
||||||
// Rehydrate or create the modal
|
|
||||||
if (clientWishlistProcess && clientWishlistProcess.modalElement && document.body.contains(clientWishlistProcess.modalElement)) {
|
|
||||||
// Just show existing modal
|
|
||||||
clientWishlistProcess.modalElement.style.display = 'flex';
|
|
||||||
WishlistModalState.setVisible();
|
|
||||||
} else {
|
|
||||||
// Rehydrate the modal with userRequested=false but show it due to auto-processing
|
|
||||||
await rehydrateModal(serverWishlistProcess, false); // Don't mark as user-requested
|
|
||||||
// But then force show it since it's auto-initiated and should be visible
|
|
||||||
if (activeDownloadProcesses[playlistId] && activeDownloadProcesses[playlistId].modalElement) {
|
|
||||||
activeDownloadProcesses[playlistId].modalElement.style.display = 'flex';
|
|
||||||
WishlistModalState.setVisible();
|
|
||||||
console.log('🤖 [Auto-Processing] Forced display of rehydrated modal for auto-processing');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show a toast to inform user about auto-processing
|
|
||||||
showToast('Auto-processing wishlist tracks...', 'info', 3000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: Modal visibility is controlled by user interaction only
|
||||||
|
// User must click wishlist button to see auto-processing progress
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -8369,70 +8422,68 @@ async function handleWishlistButtonClick() {
|
||||||
try {
|
try {
|
||||||
const playlistId = 'wishlist';
|
const playlistId = 'wishlist';
|
||||||
|
|
||||||
// Always check server state first to detect any active wishlist processes
|
console.log('🎵 [Wishlist Button] User clicked wishlist button - checking server state first');
|
||||||
|
|
||||||
|
// STEP 1: Always check server state first to detect any active wishlist processes
|
||||||
const response = await fetch('/api/active-processes');
|
const response = await fetch('/api/active-processes');
|
||||||
if (response.ok) {
|
if (!response.ok) {
|
||||||
const data = await response.json();
|
throw new Error(`Failed to fetch active processes: ${response.status}`);
|
||||||
const processes = data.active_processes || [];
|
|
||||||
|
|
||||||
// Look for active wishlist process on server
|
|
||||||
const serverWishlistProcess = processes.find(p => p.playlist_id === playlistId);
|
|
||||||
const clientWishlistProcess = activeDownloadProcesses[playlistId];
|
|
||||||
|
|
||||||
if (serverWishlistProcess) {
|
|
||||||
console.log('Server has active wishlist process:', serverWishlistProcess.batch_id);
|
|
||||||
|
|
||||||
// Check if this is an auto-initiated batch (from auto-processing)
|
|
||||||
const isAutoInitiated = serverWishlistProcess.auto_initiated || false;
|
|
||||||
if (isAutoInitiated) {
|
|
||||||
console.log('🤖 [Auto-Processing] Detected auto-initiated wishlist batch');
|
|
||||||
// Clear any user closed state since user is actively requesting to see it
|
|
||||||
WishlistModalState.clearUserClosed();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if client state is properly synchronized with server
|
|
||||||
const isClientStateMismatched = !clientWishlistProcess ||
|
|
||||||
clientWishlistProcess.batchId !== serverWishlistProcess.batch_id ||
|
|
||||||
clientWishlistProcess.status !== 'running';
|
|
||||||
|
|
||||||
const isModalElementMissing = !clientWishlistProcess?.modalElement ||
|
|
||||||
!document.body.contains(clientWishlistProcess.modalElement);
|
|
||||||
|
|
||||||
if (isClientStateMismatched || isModalElementMissing) {
|
|
||||||
console.log('Server/client wishlist state mismatch or missing modal, rehydrating...');
|
|
||||||
console.log('Client batch ID:', clientWishlistProcess?.batchId, 'Server batch ID:', serverWishlistProcess.batch_id);
|
|
||||||
console.log('Client status:', clientWishlistProcess?.status, 'Modal exists:', !isModalElementMissing);
|
|
||||||
|
|
||||||
await rehydrateModal(serverWishlistProcess, true); // Force rehydration with user request
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
// Client state is properly synchronized, just show existing modal
|
|
||||||
console.log('Found properly synced wishlist process, showing modal');
|
|
||||||
if (clientWishlistProcess.modalElement) {
|
|
||||||
clientWishlistProcess.modalElement.style.display = 'flex';
|
|
||||||
WishlistModalState.setVisible(); // Track that modal is now visible
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// No active wishlist process on server, proceed with normal modal creation
|
const data = await response.json();
|
||||||
// First check if there are any tracks in the wishlist
|
const processes = data.active_processes || [];
|
||||||
const countResponse = await fetch('/api/wishlist/count');
|
const serverWishlistProcess = processes.find(p => p.playlist_id === playlistId);
|
||||||
const countData = await countResponse.json();
|
|
||||||
|
|
||||||
|
// STEP 2: Handle active server process - show current state immediately
|
||||||
|
if (serverWishlistProcess) {
|
||||||
|
console.log('🎯 [Wishlist Button] Server has active wishlist process:', {
|
||||||
|
batch_id: serverWishlistProcess.batch_id,
|
||||||
|
phase: serverWishlistProcess.phase,
|
||||||
|
auto_initiated: serverWishlistProcess.auto_initiated,
|
||||||
|
should_show: serverWishlistProcess.should_show_modal
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clear any user-closed state since user explicitly requested to see modal
|
||||||
|
WishlistModalState.clearUserClosed();
|
||||||
|
|
||||||
|
// Check if we need to create/sync the frontend modal
|
||||||
|
const clientWishlistProcess = activeDownloadProcesses[playlistId];
|
||||||
|
const needsRehydration = !clientWishlistProcess ||
|
||||||
|
clientWishlistProcess.batchId !== serverWishlistProcess.batch_id ||
|
||||||
|
!clientWishlistProcess.modalElement ||
|
||||||
|
!document.body.contains(clientWishlistProcess.modalElement);
|
||||||
|
|
||||||
|
if (needsRehydration) {
|
||||||
|
console.log('🔄 [Wishlist Button] Frontend modal needs sync/creation');
|
||||||
|
await rehydrateModal(serverWishlistProcess, true); // user-requested = true
|
||||||
|
} else {
|
||||||
|
console.log('✅ [Wishlist Button] Frontend modal already synced, showing existing modal');
|
||||||
|
clientWishlistProcess.modalElement.style.display = 'flex';
|
||||||
|
WishlistModalState.setVisible();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3: No active server process - check wishlist count and create fresh modal
|
||||||
|
console.log('📭 [Wishlist Button] No active server process, checking wishlist content');
|
||||||
|
|
||||||
|
const countResponse = await fetch('/api/wishlist/count');
|
||||||
|
if (!countResponse.ok) {
|
||||||
|
throw new Error(`Failed to fetch wishlist count: ${countResponse.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countData = await countResponse.json();
|
||||||
if (countData.count === 0) {
|
if (countData.count === 0) {
|
||||||
showToast('Wishlist is empty. No tracks to download.', 'info');
|
showToast('Wishlist is empty. No tracks to download.', 'info');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the wishlist download modal for new process
|
// STEP 4: Create fresh modal for new wishlist process
|
||||||
console.log('No active server process, creating new wishlist modal');
|
console.log(`🆕 [Wishlist Button] Creating fresh modal for ${countData.count} wishlist tracks`);
|
||||||
await openDownloadMissingWishlistModal();
|
await openDownloadMissingWishlistModal();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error handling wishlist button click:', error);
|
console.error('❌ [Wishlist Button] Error handling wishlist button click:', error);
|
||||||
showToast(`Error opening wishlist: ${error.message}`, 'error');
|
showToast(`Error opening wishlist: ${error.message}`, 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue