add watchlist functionality to library page
This commit is contained in:
parent
c90d5e11ae
commit
90c6af078d
5 changed files with 271 additions and 7 deletions
42
artist_bubble_snapshots.json
Normal file
42
artist_bubble_snapshots.json
Normal file
|
|
@ -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…
Reference in a new issue