add a 'watch all' button on hero slider to quickly add all artists in slider.

This commit is contained in:
Broque Thomas 2026-03-02 08:40:22 -08:00
parent db2f7e999c
commit 8f2dd66aee
4 changed files with 196 additions and 0 deletions

View file

@ -21480,6 +21480,67 @@ def remove_from_watchlist():
print(f"Error removing from watchlist: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/watchlist/add-batch', methods=['POST'])
def add_batch_to_watchlist():
"""Add multiple artists to the watchlist at once"""
try:
data = request.get_json()
artists = data.get('artists', [])
if not artists or not isinstance(artists, list):
return jsonify({"success": False, "error": "Missing or invalid artists list"}), 400
database = get_database()
added = 0
skipped = 0
for artist in artists:
artist_id = artist.get('artist_id')
artist_name = artist.get('artist_name')
if not artist_id or not artist_name:
continue
# Check if already watched
if database.is_artist_in_watchlist(artist_id):
skipped += 1
continue
success = database.add_artist_to_watchlist(artist_id, artist_name)
if success:
added += 1
# Cache artist image
try:
is_itunes_id = artist_id.isdigit()
if is_itunes_id:
itunes_url = f"https://itunes.apple.com/lookup?id={artist_id}&entity=album&limit=5"
resp = requests.get(itunes_url, timeout=5)
if resp.status_code == 200:
results = resp.json().get('results', [])
for res in results:
if 'artworkUrl100' in res:
image_url = res['artworkUrl100'].replace('100x100', '600x600')
database.update_watchlist_artist_image(artist_id, image_url)
break
elif spotify_client and spotify_client.is_authenticated():
artist_data = spotify_client.get_artist(artist_id)
if artist_data and 'images' in artist_data and artist_data['images']:
image_url = artist_data['images'][1]['url'] if len(artist_data['images']) > 1 else artist_data['images'][0]['url']
if image_url:
database.update_watchlist_artist_image(artist_id, image_url)
except Exception as img_error:
print(f"⚠️ Could not fetch artist image for {artist_name}: {img_error}")
return jsonify({
"success": True,
"added": added,
"skipped": skipped,
"message": f"Added {added} artist{'s' if added != 1 else ''} to watchlist ({skipped} already watched)"
})
except Exception as e:
print(f"Error batch adding to watchlist: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/watchlist/remove-batch', methods=['POST'])
def remove_batch_from_watchlist():
"""Remove multiple artists from the watchlist"""

View file

@ -2189,6 +2189,12 @@
<!-- Slideshow Indicators -->
<div class="discover-hero-indicators" id="discover-hero-indicators"></div>
<!-- Watch All Button -->
<button class="discover-hero-watch-all" id="discover-hero-watch-all" onclick="watchAllHeroArtists(this)">
<span class="watch-all-icon">👁️</span>
<span class="watch-all-text">Watch All</span>
</button>
</div>
<!-- Recent Releases Section -->

View file

@ -33393,6 +33393,9 @@ async function loadDiscoverHero() {
}, 8000);
}
// Check if all hero artists are already watched
checkAllHeroWatchlistStatus();
} catch (error) {
console.error('Error loading discover hero:', error);
showDiscoverHeroEmpty();
@ -33543,6 +33546,90 @@ function toggleDiscoverHeroWatchlist(event) {
toggleWatchlist(event, artistId, artistName);
}
async function watchAllHeroArtists(btn) {
if (!discoverHeroArtists || discoverHeroArtists.length === 0) return;
if (btn.classList.contains('all-watched')) return;
const textEl = btn.querySelector('.watch-all-text');
const originalText = textEl ? textEl.textContent : '';
// Loading state
btn.disabled = true;
if (textEl) textEl.textContent = 'Adding...';
try {
const artists = discoverHeroArtists.map(a => ({
artist_id: a.artist_id,
artist_name: a.artist_name
}));
const response = await fetch('/api/watchlist/add-batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ artists })
});
const data = await response.json();
if (data.success) {
if (textEl) textEl.textContent = 'All Watched';
btn.classList.add('all-watched');
btn.disabled = true;
// Sync the per-slide watchlist button for current artist
const currentArtist = discoverHeroArtists[discoverHeroIndex];
if (currentArtist) {
checkAndUpdateDiscoverHeroWatchlistButton(currentArtist.artist_id);
}
// Update watchlist count badge
if (typeof updateWatchlistButtonCount === 'function') {
updateWatchlistButtonCount();
}
} else {
if (textEl) textEl.textContent = originalText;
btn.disabled = false;
}
} catch (error) {
console.error('Error watching all hero artists:', error);
if (textEl) textEl.textContent = originalText;
btn.disabled = false;
}
}
async function checkAllHeroWatchlistStatus() {
const btn = document.getElementById('discover-hero-watch-all');
if (!btn || !discoverHeroArtists || discoverHeroArtists.length === 0) return;
try {
let allWatched = true;
for (const artist of discoverHeroArtists) {
const response = await fetch('/api/watchlist/check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ artist_id: artist.artist_id })
});
const data = await response.json();
if (!data.success || !data.is_watching) {
allWatched = false;
break;
}
}
const textEl = btn.querySelector('.watch-all-text');
if (allWatched) {
if (textEl) textEl.textContent = 'All Watched';
btn.classList.add('all-watched');
btn.disabled = true;
} else {
if (textEl) textEl.textContent = 'Watch All';
btn.classList.remove('all-watched');
btn.disabled = false;
}
} catch (error) {
console.error('Error checking hero watchlist status:', error);
}
}
function navigateDiscoverHero(direction) {
if (!discoverHeroArtists || discoverHeroArtists.length === 0) return;

View file

@ -20179,6 +20179,48 @@ body {
border-radius: 4px;
}
/* Watch All Button */
.discover-hero-watch-all {
position: absolute;
bottom: 24px;
right: 24px;
z-index: 10;
display: flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
background: rgba(0, 0, 0, 0.3);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 20px;
color: rgba(255, 255, 255, 0.9);
font-size: 13px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease;
}
.discover-hero-watch-all:hover {
background: rgba(0, 0, 0, 0.5);
border-color: rgba(255, 255, 255, 0.3);
}
.discover-hero-watch-all .watch-all-icon {
font-size: 14px;
}
.discover-hero-watch-all.all-watched {
background: rgba(var(--accent-rgb), 0.15);
border-color: rgba(var(--accent-rgb), 0.4);
color: rgb(var(--accent-rgb));
cursor: default;
}
.discover-hero-watch-all:disabled:not(.all-watched) {
opacity: 0.6;
cursor: wait;
}
/* Discover Sections */
.discover-section {
margin-bottom: 50px;