diff --git a/webui/static/script.js b/webui/static/script.js index d89912d0..9761bbba 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -11805,11 +11805,11 @@ function _pollWingItSyncProgress(syncPlaylistId, playlistName, cardPlaylistId) { setTimeout(() => clearInterval(poll), 180000); } -function _wingItFromModal(urlHash) { - // Extract tracks from the discovery modal state +async function _wingItFromModal(urlHash) { + // Extract tracks from the discovery modal state — tracks can be in various locations const state = listenbrainzPlaylistStates[urlHash] || youtubePlaylistStates[urlHash] || {}; - const tracks = state.tracks || state.rawTracks || []; - const name = state.playlistName || state.name || 'Playlist'; + const tracks = state.tracks || state.rawTracks || state.playlist?.tracks || []; + const name = state.playlistName || state.name || state.playlist?.name || 'Playlist'; const isTidal = state.is_tidal_playlist; const isLB = state.is_listenbrainz_playlist; const isBeatport = state.is_beatport_playlist; @@ -11821,7 +11821,56 @@ function _wingItFromModal(urlHash) { return; } - // Close the discovery modal first + const choice = await _showWingItChoiceDialog(tracks.length, source); + if (!choice) return; + + if (choice === 'sync') { + // Sync inline — keep modal open, show progress in modal + showToast('Starting Wing It sync...', 'info'); + updateYouTubeModalButtons(urlHash, 'syncing'); + + try { + // Format and send sync request + const syncTracks = tracks.map((t, i) => { + let artists = t.artists || []; + if (!Array.isArray(artists)) artists = [{ name: String(artists) }]; + return { + id: t.id || t.source_track_id || `wing_it_${i}`, + name: t.name || t.track_name || 'Unknown', + artists: artists.map(a => typeof a === 'string' ? { name: a } : a), + album: typeof t.album === 'object' ? t.album : { name: t.album || t.album_name || '' }, + duration_ms: t.duration_ms || 0, + }; + }); + + const res = await fetch('/api/wing-it/sync', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ tracks: syncTracks, playlist_name: name }) + }); + const data = await res.json(); + + if (data.error) { + showToast(`Sync failed: ${data.error}`, 'error'); + updateYouTubeModalButtons(urlHash, 'discovered'); + return; + } + + // Use the same sync polling as normal sync — works for any source + if (isLB) { + if (state) state.syncPlaylistId = data.sync_playlist_id; + startListenBrainzSyncPolling(urlHash, data.sync_playlist_id); + } else { + startYouTubeSyncPolling(urlHash, data.sync_playlist_id); + } + } catch (e) { + showToast('Sync failed: ' + e.message, 'error'); + updateYouTubeModalButtons(urlHash, 'discovered'); + } + return; + } + + // choice === 'download' — close modal and open download modal const modal = document.getElementById(`youtube-discovery-modal-${urlHash}`); if (modal) modal.remove(); const overlay = document.getElementById(`youtube-discovery-overlay-${urlHash}`); @@ -30970,6 +31019,9 @@ function getModalActionButtons(urlHash, phase, state = null) { syncCompleteButtons += ``; } + // Wing It button + syncCompleteButtons += ` `; + return syncCompleteButtons; case 'download_complete': @@ -31617,16 +31669,20 @@ function updateYouTubeCardSyncProgress(urlHash, progress) { } function updateYouTubeModalSyncProgress(urlHash, progress) { - const statusDisplay = document.getElementById(`youtube-sync-status-${urlHash}`); + // Try all source-specific element ID prefixes + const prefixes = ['youtube', 'listenbrainz', 'tidal', 'deezer', 'spotify-public', 'beatport']; + let statusDisplay = null; + let prefix = 'youtube'; + for (const p of prefixes) { + statusDisplay = document.getElementById(`${p}-sync-status-${urlHash}`); + if (statusDisplay) { prefix = p; break; } + } if (!statusDisplay || !progress) return; - console.log(`📊 Updating YouTube modal sync progress for ${urlHash}:`, progress); - - // Update individual counters exactly like Spotify sync - const totalEl = document.getElementById(`youtube-total-${urlHash}`); - const matchedEl = document.getElementById(`youtube-matched-${urlHash}`); - const failedEl = document.getElementById(`youtube-failed-${urlHash}`); - const percentageEl = document.getElementById(`youtube-percentage-${urlHash}`); + const totalEl = document.getElementById(`${prefix}-total-${urlHash}`); + const matchedEl = document.getElementById(`${prefix}-matched-${urlHash}`); + const failedEl = document.getElementById(`${prefix}-failed-${urlHash}`); + const percentageEl = document.getElementById(`${prefix}-percentage-${urlHash}`); const total = progress.total_tracks || 0; const matched = progress.matched_tracks || 0; diff --git a/webui/static/style.css b/webui/static/style.css index f8aa4adb..e1247836 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -5171,18 +5171,18 @@ body.helper-mode-active #dashboard-activity-feed:hover { /* ── Wing It Button ── */ .wing-it-btn { - background: transparent !important; + background: rgba(255, 183, 77, 0.08) !important; border: 1px solid rgba(255, 183, 77, 0.3) !important; color: #ffb74d !important; - font-size: 12px !important; - font-weight: 600 !important; - padding: 6px 14px !important; - border-radius: 8px !important; + font-size: 14px !important; + font-weight: 700 !important; + padding: 10px 20px !important; + border-radius: 10px !important; cursor: pointer; transition: all 0.2s; } .wing-it-btn:hover { - background: rgba(255, 183, 77, 0.1) !important; + background: rgba(255, 183, 77, 0.15) !important; border-color: rgba(255, 183, 77, 0.5) !important; }