Feat: Added 'add to wishlist' button on all download missing track modals.
This commit is contained in:
parent
b7b76b378d
commit
573bd6080c
2 changed files with 204 additions and 1 deletions
41
artist_bubble_snapshots.json
Normal file
41
artist_bubble_snapshots.json
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
{
|
||||||
|
"bubbles": {
|
||||||
|
"165ZgPlLkK7bf5bDoFc6Sb": {
|
||||||
|
"artist": {
|
||||||
|
"id": "165ZgPlLkK7bf5bDoFc6Sb",
|
||||||
|
"name": "Limp Bizkit",
|
||||||
|
"image_url": "https://i.scdn.co/image/4654955890cb62a6abe9daadfd2b3ecdee8036e4",
|
||||||
|
"genres": [
|
||||||
|
"nu metal",
|
||||||
|
"rap metal",
|
||||||
|
"alternative metal",
|
||||||
|
"rap rock"
|
||||||
|
],
|
||||||
|
"popularity": 79,
|
||||||
|
"confidence": 0.21052631578947367
|
||||||
|
},
|
||||||
|
"downloads": [
|
||||||
|
{
|
||||||
|
"virtualPlaylistId": "artist_album_165ZgPlLkK7bf5bDoFc6Sb_5mi7FKaWE5CtcOjdyxScA7",
|
||||||
|
"album": {
|
||||||
|
"album_type": "album",
|
||||||
|
"external_urls": {
|
||||||
|
"spotify": "https://open.spotify.com/album/5mi7FKaWE5CtcOjdyxScA7"
|
||||||
|
},
|
||||||
|
"id": "5mi7FKaWE5CtcOjdyxScA7",
|
||||||
|
"image_url": "https://i.scdn.co/image/ab67616d0000b2734a31b146c7cf07705d912efe",
|
||||||
|
"name": "Chocolate Starfish And The Hot Dog Flavored Water",
|
||||||
|
"release_date": "2000-10-17",
|
||||||
|
"total_tracks": 15
|
||||||
|
},
|
||||||
|
"albumType": "albums",
|
||||||
|
"status": "in_progress",
|
||||||
|
"startTime": "2025-11-23T21:24:21.696Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hasCompletedDownloads": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"timestamp": "2025-11-23T13:24:22.715644",
|
||||||
|
"snapshot_id": "20251123_132422"
|
||||||
|
}
|
||||||
|
|
@ -2349,9 +2349,11 @@ async function rehydrateArtistAlbumModal(virtualPlaylistId, playlistName, batchI
|
||||||
// Update button states to reflect running status
|
// Update button states to reflect running status
|
||||||
const beginBtn = document.getElementById(`begin-analysis-btn-${virtualPlaylistId}`);
|
const beginBtn = document.getElementById(`begin-analysis-btn-${virtualPlaylistId}`);
|
||||||
const cancelBtn = document.getElementById(`cancel-all-btn-${virtualPlaylistId}`);
|
const cancelBtn = document.getElementById(`cancel-all-btn-${virtualPlaylistId}`);
|
||||||
|
const wishlistBtn = document.getElementById(`add-to-wishlist-btn-${virtualPlaylistId}`);
|
||||||
if (beginBtn) beginBtn.style.display = 'none';
|
if (beginBtn) beginBtn.style.display = 'none';
|
||||||
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
if (cancelBtn) cancelBtn.style.display = 'inline-block';
|
||||||
|
if (wishlistBtn) wishlistBtn.style.display = 'none';
|
||||||
|
|
||||||
// Hide the modal - this is background rehydration, not user-requested
|
// Hide the modal - this is background rehydration, not user-requested
|
||||||
if (process.modalElement) {
|
if (process.modalElement) {
|
||||||
process.modalElement.style.display = 'none';
|
process.modalElement.style.display = 'none';
|
||||||
|
|
@ -2669,6 +2671,10 @@ async function rehydrateModal(processInfo, userRequested = false) {
|
||||||
document.getElementById(`begin-analysis-btn-${playlist_id}`).style.display = 'none';
|
document.getElementById(`begin-analysis-btn-${playlist_id}`).style.display = 'none';
|
||||||
document.getElementById(`cancel-all-btn-${playlist_id}`).style.display = 'inline-block';
|
document.getElementById(`cancel-all-btn-${playlist_id}`).style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Hide wishlist button if it exists
|
||||||
|
const wishlistBtn = document.getElementById(`add-to-wishlist-btn-${playlist_id}`);
|
||||||
|
if (wishlistBtn) wishlistBtn.style.display = 'none';
|
||||||
|
|
||||||
startModalDownloadPolling(playlist_id);
|
startModalDownloadPolling(playlist_id);
|
||||||
|
|
||||||
process.modalElement.style.display = 'none';
|
process.modalElement.style.display = 'none';
|
||||||
|
|
@ -4078,6 +4084,9 @@ async function openDownloadMissingModal(playlistId) {
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${playlistId}" onclick="startMissingTracksProcess('${playlistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${playlistId}" onclick="startMissingTracksProcess('${playlistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
<button class="download-control-btn" id="add-to-wishlist-btn-${playlistId}" onclick="addModalTracksToWishlist('${playlistId}')" style="background-color: #9333ea; color: white;">
|
||||||
|
Add to Wishlist
|
||||||
|
</button>
|
||||||
<button class="download-control-btn danger" id="cancel-all-btn-${playlistId}" onclick="cancelAllOperations('${playlistId}')" style="display: none;">
|
<button class="download-control-btn danger" id="cancel-all-btn-${playlistId}" onclick="cancelAllOperations('${playlistId}')" style="display: none;">
|
||||||
Cancel All
|
Cancel All
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -4436,6 +4445,9 @@ async function openDownloadMissingModalForYouTube(virtualPlaylistId, playlistNam
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
<button class="download-control-btn" id="add-to-wishlist-btn-${virtualPlaylistId}" onclick="addModalTracksToWishlist('${virtualPlaylistId}')" style="background-color: #9333ea; color: white;">
|
||||||
|
Add to Wishlist
|
||||||
|
</button>
|
||||||
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
||||||
Cancel All
|
Cancel All
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -4947,6 +4959,12 @@ async function startMissingTracksProcess(playlistId) {
|
||||||
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
document.getElementById(`begin-analysis-btn-${playlistId}`).style.display = 'none';
|
||||||
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
document.getElementById(`cancel-all-btn-${playlistId}`).style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Hide wishlist button if it exists (only for non-wishlist modals)
|
||||||
|
const wishlistBtn = document.getElementById(`add-to-wishlist-btn-${playlistId}`);
|
||||||
|
if (wishlistBtn) {
|
||||||
|
wishlistBtn.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// Add to discover download sidebar if this is a discover page download
|
// Add to discover download sidebar if this is a discover page download
|
||||||
if (process.discoverMetadata) {
|
if (process.discoverMetadata) {
|
||||||
const playlistName = process.playlist.name;
|
const playlistName = process.playlist.name;
|
||||||
|
|
@ -5036,6 +5054,21 @@ async function startMissingTracksProcess(playlistId) {
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showToast(`Failed to start process: ${error.message}`, 'error');
|
showToast(`Failed to start process: ${error.message}`, 'error');
|
||||||
process.status = 'cancelled';
|
process.status = 'cancelled';
|
||||||
|
|
||||||
|
// Reset button states on error
|
||||||
|
const beginBtn = document.getElementById(`begin-analysis-btn-${playlistId}`);
|
||||||
|
const cancelBtn = document.getElementById(`cancel-all-btn-${playlistId}`);
|
||||||
|
const wishlistBtn = document.getElementById(`add-to-wishlist-btn-${playlistId}`);
|
||||||
|
if (beginBtn) beginBtn.style.display = 'inline-block';
|
||||||
|
if (cancelBtn) cancelBtn.style.display = 'none';
|
||||||
|
if (wishlistBtn) wishlistBtn.style.display = 'inline-block';
|
||||||
|
|
||||||
|
// Show the force download toggle again
|
||||||
|
const forceToggleContainer = document.querySelector(`#force-download-all-${playlistId}`)?.closest('.force-download-toggle-container');
|
||||||
|
if (forceToggleContainer) {
|
||||||
|
forceToggleContainer.style.display = 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
cleanupDownloadProcess(playlistId);
|
cleanupDownloadProcess(playlistId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8257,6 +8290,128 @@ function closeAddToWishlistModal() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all tracks from any download modal to the wishlist
|
||||||
|
* Universal handler for all modal types (artist albums, playlists, YouTube, Tidal, etc.)
|
||||||
|
*/
|
||||||
|
async function addModalTracksToWishlist(playlistId) {
|
||||||
|
const process = activeDownloadProcesses[playlistId];
|
||||||
|
if (!process) {
|
||||||
|
console.error('❌ No active process found for:', playlistId);
|
||||||
|
showToast('Error: Could not find playlist data', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify we have tracks
|
||||||
|
if (!process.tracks || process.tracks.length === 0) {
|
||||||
|
console.error('❌ No tracks found in process:', process);
|
||||||
|
showToast('Error: No tracks to add', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tracks = process.tracks;
|
||||||
|
// Get artist/album context if available (for artist album downloads)
|
||||||
|
const artist = process.artist || { name: 'Unknown Artist', id: null };
|
||||||
|
const album = process.album || process.playlist || { name: 'Playlist', id: playlistId };
|
||||||
|
|
||||||
|
console.log(`🔄 Adding ${tracks.length} tracks from "${album.name}" to wishlist`);
|
||||||
|
|
||||||
|
// Disable the button to prevent double-clicks
|
||||||
|
const wishlistBtn = document.getElementById(`add-to-wishlist-btn-${playlistId}`);
|
||||||
|
if (wishlistBtn) {
|
||||||
|
wishlistBtn.disabled = true;
|
||||||
|
wishlistBtn.classList.add('loading');
|
||||||
|
wishlistBtn.textContent = 'Adding...';
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let successCount = 0;
|
||||||
|
let errorCount = 0;
|
||||||
|
|
||||||
|
// Add each track to wishlist individually
|
||||||
|
for (const track of tracks) {
|
||||||
|
try {
|
||||||
|
// Format artists field to match backend expectations
|
||||||
|
let formattedArtists = track.artists;
|
||||||
|
if (typeof track.artists === 'string') {
|
||||||
|
formattedArtists = [{ name: track.artists }];
|
||||||
|
} else if (Array.isArray(track.artists)) {
|
||||||
|
formattedArtists = track.artists.map(artistItem => {
|
||||||
|
if (typeof artistItem === 'string') {
|
||||||
|
return { name: artistItem };
|
||||||
|
} else if (typeof artistItem === 'object' && artistItem !== null) {
|
||||||
|
return artistItem;
|
||||||
|
} else {
|
||||||
|
return { name: 'Unknown Artist' };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
formattedArtists = [{ name: artist.name }];
|
||||||
|
}
|
||||||
|
|
||||||
|
const formattedTrack = {
|
||||||
|
...track,
|
||||||
|
artists: formattedArtists
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch('/api/add-album-to-wishlist', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
track: formattedTrack,
|
||||||
|
artist: artist,
|
||||||
|
album: album,
|
||||||
|
source_type: 'album',
|
||||||
|
source_context: {
|
||||||
|
album_name: album.name,
|
||||||
|
artist_name: artist.name,
|
||||||
|
album_type: album.album_type || 'album'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
successCount++;
|
||||||
|
} else {
|
||||||
|
errorCount++;
|
||||||
|
console.error(`❌ Failed to add "${track.name}" to wishlist: ${result.error}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
errorCount++;
|
||||||
|
console.error(`❌ Error adding "${track.name}" to wishlist:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show result toast
|
||||||
|
if (successCount > 0) {
|
||||||
|
const message = errorCount > 0
|
||||||
|
? `Added ${successCount}/${tracks.length} tracks to wishlist (${errorCount} failed)`
|
||||||
|
: `Added ${successCount} tracks to wishlist`;
|
||||||
|
showToast(message, 'success');
|
||||||
|
|
||||||
|
// Close the modal on success
|
||||||
|
await closeDownloadMissingModal(playlistId);
|
||||||
|
} else {
|
||||||
|
showToast('Failed to add any tracks to wishlist', 'error');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error in addModalTracksToWishlist:', error);
|
||||||
|
showToast(`Error adding to wishlist: ${error.message}`, 'error');
|
||||||
|
} finally {
|
||||||
|
// Re-enable button if still on screen (in case of error)
|
||||||
|
if (wishlistBtn) {
|
||||||
|
wishlistBtn.disabled = false;
|
||||||
|
wishlistBtn.classList.remove('loading');
|
||||||
|
wishlistBtn.textContent = 'Add to Wishlist';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format duration from milliseconds to MM:SS format
|
* Format duration from milliseconds to MM:SS format
|
||||||
*/
|
*/
|
||||||
|
|
@ -8290,6 +8445,7 @@ window.handleWishlistButtonClick = handleWishlistButtonClick;
|
||||||
window.openAddToWishlistModal = openAddToWishlistModal;
|
window.openAddToWishlistModal = openAddToWishlistModal;
|
||||||
window.closeAddToWishlistModal = closeAddToWishlistModal;
|
window.closeAddToWishlistModal = closeAddToWishlistModal;
|
||||||
window.handleAddToWishlist = handleAddToWishlist;
|
window.handleAddToWishlist = handleAddToWishlist;
|
||||||
|
window.addModalTracksToWishlist = addModalTracksToWishlist;
|
||||||
|
|
||||||
// Helper functions
|
// Helper functions
|
||||||
window.escapeHtml = escapeHtml;
|
window.escapeHtml = escapeHtml;
|
||||||
|
|
@ -11388,6 +11544,9 @@ async function openDownloadMissingModalForTidal(virtualPlaylistId, playlistName,
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
<button class="download-control-btn" id="add-to-wishlist-btn-${virtualPlaylistId}" onclick="addModalTracksToWishlist('${virtualPlaylistId}')" style="background-color: #9333ea; color: white;">
|
||||||
|
Add to Wishlist
|
||||||
|
</button>
|
||||||
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
||||||
Cancel All
|
Cancel All
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -17822,6 +17981,9 @@ async function openDownloadMissingModalForArtistAlbum(virtualPlaylistId, playlis
|
||||||
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
<button class="download-control-btn primary" id="begin-analysis-btn-${virtualPlaylistId}" onclick="startMissingTracksProcess('${virtualPlaylistId}')">
|
||||||
Begin Analysis
|
Begin Analysis
|
||||||
</button>
|
</button>
|
||||||
|
<button class="download-control-btn" id="add-to-wishlist-btn-${virtualPlaylistId}" onclick="addModalTracksToWishlist('${virtualPlaylistId}')" style="background-color: #9333ea; color: white;">
|
||||||
|
Add to Wishlist
|
||||||
|
</button>
|
||||||
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
<button class="download-control-btn danger" id="cancel-all-btn-${virtualPlaylistId}" onclick="cancelAllOperations('${virtualPlaylistId}')" style="display: none;">
|
||||||
Cancel All
|
Cancel All
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue