add a 'watch all' button on hero slider to quickly add all artists in slider.
This commit is contained in:
parent
db2f7e999c
commit
8f2dd66aee
4 changed files with 196 additions and 0 deletions
|
|
@ -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"""
|
||||
|
|
|
|||
|
|
@ -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 -->
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue