add watchlist functionality to library page

pull/49/head
Broque Thomas 8 months ago
parent c90d5e11ae
commit 90c6af078d

@ -0,0 +1,42 @@
{
"bubbles": {
"6FBDaR13swtiWwGhX1WQsP": {
"artist": {
"id": "6FBDaR13swtiWwGhX1WQsP",
"name": "blink-182",
"image_url": "https://i.scdn.co/image/ab6761610000e5eb5da36f8b98dd965336a1507a",
"genres": [
"pop punk",
"punk",
"rock",
"skate punk",
"emo"
],
"popularity": 80,
"confidence": 0.1111111111111111
},
"downloads": [
{
"virtualPlaylistId": "artist_album_6FBDaR13swtiWwGhX1WQsP_00txDYFrU4LjWqwKE8iQJA",
"album": {
"album_type": "album",
"external_urls": {
"spotify": "https://open.spotify.com/album/00txDYFrU4LjWqwKE8iQJA"
},
"id": "00txDYFrU4LjWqwKE8iQJA",
"image_url": "https://i.scdn.co/image/ab67616d0000b273d07226d60768eb99f38997f8",
"name": "ONE MORE TIME...",
"release_date": "2023-10-27",
"total_tracks": 19
},
"albumType": "albums",
"status": "in_progress",
"startTime": "2025-09-26T14:42:42.028Z"
}
],
"hasCompletedDownloads": false
}
},
"timestamp": "2025-09-26T07:42:43.033178",
"snapshot_id": "20250926_074243"
}

@ -2852,12 +2852,20 @@ def get_artist_detail(artist_id):
single['image_url'] = fix_artist_image_url(single['image_url'])
# Get Spotify discography for proper categorization and missing releases
spotify_artist_data = None
try:
spotify_discography = get_spotify_artist_discography(artist_info['name'])
if spotify_discography['success']:
print(f"🎵 Spotify discography found - Albums: {len(spotify_discography['albums'])}, EPs: {len(spotify_discography['eps'])}, Singles: {len(spotify_discography['singles'])}")
# Store Spotify artist data for the response
spotify_artist_data = {
'spotify_artist_id': spotify_discography.get('spotify_artist_id'),
'spotify_artist_name': spotify_discography.get('spotify_artist_name'),
'artist_image': spotify_discography.get('artist_image')
}
# Merge owned and Spotify data for complete picture
merged_discography = merge_discography_data(owned_releases, spotify_discography)
else:
@ -2869,11 +2877,17 @@ def get_artist_detail(artist_id):
# Fall back to our database categorization
merged_discography = owned_releases
return jsonify({
response_data = {
"success": True,
"artist": artist_info,
"discography": merged_discography
})
}
# Add Spotify artist data if available
if spotify_artist_data:
response_data["spotify_artist"] = spotify_artist_data
return jsonify(response_data)
except Exception as e:
print(f"❌ Error in get_artist_detail: {e}")
@ -12649,7 +12663,9 @@ def get_spotify_artist_discography(artist_name):
'albums': albums,
'eps': eps,
'singles': singles,
'artist_image': artist.image_url if hasattr(artist, 'image_url') else None
'artist_image': artist.image_url if hasattr(artist, 'image_url') else None,
'spotify_artist_id': spotify_artist_id,
'spotify_artist_name': artist.name
}
except Exception as e:

@ -737,6 +737,11 @@
<button class="back-btn" id="artist-detail-back-btn">
<span>← Back to Library</span>
</button>
<button class="library-artist-watchlist-btn" id="library-artist-watchlist-btn">
<span class="watchlist-icon">👁️</span>
<span class="watchlist-text">Add to Watchlist</span>
</button>
</div>
<!-- Artist Hero Section -->

@ -13462,8 +13462,9 @@ async function showWatchlistModal() {
<span class="watchlist-artist-scan">Last scanned ${new Date(artist.last_scan_timestamp).toLocaleDateString()}</span>
` : ''}
</div>
<button class="playlist-modal-btn playlist-modal-btn-secondary"
onclick="removeFromWatchlistModal('${artist.spotify_artist_id}', '${escapeHtml(artist.artist_name)}')">
<button class="playlist-modal-btn playlist-modal-btn-secondary watchlist-remove-btn"
data-artist-id="${artist.spotify_artist_id}"
data-artist-name="${escapeHtml(artist.artist_name)}">
Remove
</button>
</div>
@ -13478,7 +13479,16 @@ async function showWatchlistModal() {
</div>
</div>
`;
// Add event listeners for remove buttons
modal.querySelectorAll('.watchlist-remove-btn').forEach(button => {
button.addEventListener('click', () => {
const artistId = button.getAttribute('data-artist-id');
const artistName = button.getAttribute('data-artist-name');
removeFromWatchlistModal(artistId, artistName);
});
});
// Show modal
modal.style.display = 'flex';
@ -14459,6 +14469,12 @@ function populateArtistDetailPage(data) {
// Populate discography sections
populateDiscographySections(discography);
// Initialize library watchlist button if it exists (for library page)
const libraryWatchlistBtn = document.getElementById('library-artist-watchlist-btn');
if (libraryWatchlistBtn && data.spotify_artist && data.spotify_artist.spotify_artist_id) {
initializeLibraryWatchlistButton(data.spotify_artist.spotify_artist_id, data.spotify_artist.spotify_artist_name);
}
}
function updateArtistDetailImage(imageUrl, artistName) {
@ -14593,7 +14609,7 @@ function updateCategoryStats(category, releases) {
const owned = releases.filter(r => r.owned !== false).length;
const missing = releases.filter(r => r.owned === false).length;
const total = releases.length;
const completion = total > 0 ? Math.round((owned / total) * 100) : 0;
const completion = total > 0 ? Math.round((owned / total) * 100) : 100;
console.log(`📊 ${category}: ${owned} owned, ${missing} missing, ${completion}% complete`);
@ -14953,3 +14969,140 @@ function showArtistDetailHero(show) {
}
}
}
/**
* Initialize the library page watchlist button
*/
async function initializeLibraryWatchlistButton(artistId, artistName) {
const button = document.getElementById('library-artist-watchlist-btn');
if (!button) return;
console.log(`🔧 Initializing library watchlist button for: ${artistName} (${artistId})`);
// Reset button state
button.disabled = false;
button.classList.remove('watching');
// Set up click handler
button.onclick = (e) => toggleLibraryWatchlist(e, artistId, artistName);
// Check and update current status
await updateLibraryWatchlistButtonStatus(artistId);
}
/**
* Toggle watchlist status for library page
*/
async function toggleLibraryWatchlist(event, artistId, artistName) {
event.preventDefault();
const button = document.getElementById('library-artist-watchlist-btn');
const icon = button.querySelector('.watchlist-icon');
const text = button.querySelector('.watchlist-text');
// Show loading state
const originalText = text.textContent;
text.textContent = 'Loading...';
button.disabled = true;
try {
// Check current status
const checkResponse = await fetch('/api/watchlist/check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ artist_id: artistId })
});
const checkData = await checkResponse.json();
if (!checkData.success) {
throw new Error(checkData.error || 'Failed to check watchlist status');
}
const isWatching = checkData.is_watching;
// Toggle watchlist status
const endpoint = isWatching ? '/api/watchlist/remove' : '/api/watchlist/add';
const payload = isWatching ?
{ artist_id: artistId } :
{ artist_id: artistId, artist_name: artistName };
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Failed to update watchlist');
}
// Update button state based on new status
if (isWatching) {
// Was watching, now removed
icon.textContent = '👁️';
text.textContent = 'Add to Watchlist';
button.classList.remove('watching');
console.log(`❌ Removed ${artistName} from watchlist`);
} else {
// Was not watching, now added
icon.textContent = '👁️';
text.textContent = 'Watching...';
button.classList.add('watching');
console.log(`✅ Added ${artistName} to watchlist`);
}
// Update dashboard watchlist count if function exists
if (typeof updateWatchlistCount === 'function') {
updateWatchlistCount();
}
showToast(data.message, 'success');
} catch (error) {
console.error('Error toggling library watchlist:', error);
// Restore button state
text.textContent = originalText;
showToast(`Error: ${error.message}`, 'error');
} finally {
button.disabled = false;
}
}
/**
* Update library watchlist button status based on current state
*/
async function updateLibraryWatchlistButtonStatus(artistId) {
const button = document.getElementById('library-artist-watchlist-btn');
if (!button) return;
try {
const response = await fetch('/api/watchlist/check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ artist_id: artistId })
});
const data = await response.json();
if (data.success) {
const icon = button.querySelector('.watchlist-icon');
const text = button.querySelector('.watchlist-text');
if (data.is_watching) {
icon.textContent = '👁️';
text.textContent = 'Watching...';
button.classList.add('watching');
} else {
icon.textContent = '👁️';
text.textContent = 'Add to Watchlist';
button.classList.remove('watching');
}
}
} catch (error) {
console.warn('Failed to check library watchlist status:', error);
}
}

@ -876,6 +876,9 @@ body {
}
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
padding-bottom: 20px;
@ -7279,6 +7282,51 @@ body {
transform: none;
}
/* Library Artist Watchlist Button */
.library-artist-watchlist-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 18px;
font-size: 14px;
font-weight: 600;
color: #e0e0e0;
background: rgba(255, 255, 255, 0.08);
border: 1px solid rgba(255, 255, 255, 0.12);
border-radius: 16px;
cursor: pointer;
transition: all 0.2s ease;
outline: none;
font-family: 'SF Pro Text', -apple-system, BlinkMacSystemFont, sans-serif;
}
.library-artist-watchlist-btn:hover:not(:disabled) {
background: rgba(29, 185, 84, 0.15);
color: #ffffff;
border-color: rgba(29, 185, 84, 0.3);
transform: translateY(-1px);
}
.library-artist-watchlist-btn.watching {
background: rgba(255, 193, 7, 0.15);
color: #ffc107;
border-color: rgba(255, 193, 7, 0.3);
}
.library-artist-watchlist-btn.watching:hover:not(:disabled) {
background: rgba(255, 193, 7, 0.25);
color: #ffffff;
border-color: rgba(255, 193, 7, 0.5);
}
.library-artist-watchlist-btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.artist-detail-info {
display: flex;
align-items: center;

Loading…
Cancel
Save