Add batch remove to watchlist modal

This commit is contained in:
Broque Thomas 2026-02-17 11:28:44 -08:00
parent 139b8530f4
commit 0c40a922e6
3 changed files with 197 additions and 3 deletions

View file

@ -19144,6 +19144,32 @@ def remove_from_watchlist():
print(f"Error removing from 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"""
try:
data = request.get_json()
artist_ids = data.get('artist_ids', [])
if not artist_ids or not isinstance(artist_ids, list):
return jsonify({"success": False, "error": "Missing or invalid artist_ids"}), 400
database = get_database()
removed = 0
for artist_id in artist_ids:
if database.remove_artist_from_watchlist(artist_id):
removed += 1
return jsonify({
"success": True,
"removed": removed,
"message": f"Removed {removed} artist{'s' if removed != 1 else ''} from watchlist"
})
except Exception as e:
print(f"Error batch removing from watchlist: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/watchlist/check', methods=['POST'])
def check_watchlist_status():
"""Check if an artist is in the watchlist"""

View file

@ -24095,12 +24095,29 @@ async function showWatchlistModal() {
oninput="filterWatchlistArtists()">
</div>
<!-- Batch action bar (hidden until selection) -->
<div class="watchlist-batch-bar" id="watchlist-batch-bar" style="display: none;">
<span class="watchlist-batch-count" id="watchlist-batch-count">0 selected</span>
<button class="playlist-modal-btn playlist-modal-btn-secondary watchlist-batch-remove-btn"
id="watchlist-batch-remove-btn"
onclick="batchRemoveFromWatchlist()">
Remove Selected
</button>
</div>
<div class="watchlist-artists-list" id="watchlist-artists-list">
${artistsData.artists.map(artist => `
<div class="watchlist-artist-item"
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '&quot;')}"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
style="cursor: pointer;">
<label class="watchlist-checkbox-wrapper" onclick="event.stopPropagation();">
<input type="checkbox" class="watchlist-select-cb"
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
data-artist-name="${escapeHtml(artist.artist_name)}"
onchange="updateWatchlistBatchBar()">
<span class="watchlist-checkbox-custom"></span>
</label>
${artist.image_url ? `
<img src="${artist.image_url}"
alt="${escapeHtml(artist.artist_name)}"
@ -24147,11 +24164,11 @@ async function showWatchlistModal() {
});
});
// Add click handlers to artist items (except for remove button)
// Add click handlers to artist items (except for remove button or checkbox)
modal.querySelectorAll('.watchlist-artist-item').forEach(item => {
item.addEventListener('click', (e) => {
// Don't trigger if clicking the remove button
if (e.target.closest('.watchlist-remove-btn')) {
// Don't trigger if clicking the remove button or checkbox
if (e.target.closest('.watchlist-remove-btn') || e.target.closest('.watchlist-checkbox-wrapper')) {
return;
}
@ -24444,6 +24461,9 @@ function filterWatchlistArtists() {
item.style.display = 'none';
}
});
// Refresh batch bar in case visible selection changed
updateWatchlistBatchBar();
}
/**
@ -24759,6 +24779,73 @@ async function removeFromWatchlistModal(artistId, artistName) {
}
/**
* Get visible checked checkboxes (not hidden by search filter)
*/
function getVisibleCheckedWatchlist() {
return Array.from(document.querySelectorAll('.watchlist-select-cb:checked')).filter(cb => {
const item = cb.closest('.watchlist-artist-item');
return item && item.style.display !== 'none';
});
}
/**
* Update the batch action bar based on checkbox selection
*/
function updateWatchlistBatchBar() {
const checked = getVisibleCheckedWatchlist();
const bar = document.getElementById('watchlist-batch-bar');
const countEl = document.getElementById('watchlist-batch-count');
if (checked.length > 0) {
bar.style.display = 'flex';
countEl.textContent = `${checked.length} selected`;
} else {
bar.style.display = 'none';
}
}
/**
* Batch remove selected artists from watchlist
*/
async function batchRemoveFromWatchlist() {
const checked = getVisibleCheckedWatchlist();
if (checked.length === 0) return;
const count = checked.length;
if (!confirm(`Remove ${count} artist${count !== 1 ? 's' : ''} from your watchlist?`)) return;
const artistIds = checked.map(cb => cb.getAttribute('data-artist-id'));
try {
const response = await fetch('/api/watchlist/remove-batch', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ artist_ids: artistIds })
});
const data = await response.json();
if (!data.success) {
throw new Error(data.error || 'Failed to remove artists');
}
console.log(`❌ Batch removed ${data.removed} artists from watchlist`);
// Refresh the modal
showWatchlistModal();
// Update button count
updateWatchlistButtonCount();
// Update any visible artist cards
updateArtistCardWatchlistStatus();
} catch (error) {
console.error('Error batch removing from watchlist:', error);
alert(`Error removing artists: ${error.message}`);
}
}
// --- Metadata Updater Functions ---
// Global state for metadata update polling

View file

@ -8221,6 +8221,87 @@ body {
transform: scale(1.05);
}
/* Watchlist Batch Action Bar */
.watchlist-batch-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 20px;
margin: 0 30px 12px;
background: rgba(255, 59, 48, 0.1);
border: 1px solid rgba(255, 59, 48, 0.25);
border-radius: 10px;
animation: batchBarSlideIn 0.2s ease-out;
}
@keyframes batchBarSlideIn {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
.watchlist-batch-count {
font-size: 13px;
font-weight: 600;
color: rgba(255, 255, 255, 0.8);
}
.watchlist-batch-remove-btn {
border-color: rgba(255, 59, 48, 0.4) !important;
color: #ff6b6b !important;
}
.watchlist-batch-remove-btn:hover {
background: rgba(255, 59, 48, 0.2) !important;
border-color: rgba(255, 59, 48, 0.6) !important;
}
/* Watchlist Checkbox */
.watchlist-checkbox-wrapper {
position: relative;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
flex-shrink: 0;
cursor: pointer;
}
.watchlist-checkbox-wrapper input[type="checkbox"] {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.watchlist-checkbox-custom {
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.2);
border-radius: 4px;
background: rgba(255, 255, 255, 0.03);
transition: all 0.15s ease;
display: flex;
align-items: center;
justify-content: center;
}
.watchlist-checkbox-wrapper:hover .watchlist-checkbox-custom {
border-color: rgba(255, 255, 255, 0.4);
background: rgba(255, 255, 255, 0.06);
}
.watchlist-checkbox-wrapper input:checked + .watchlist-checkbox-custom {
background: rgba(29, 185, 84, 0.3);
border-color: #1db954;
}
.watchlist-checkbox-wrapper input:checked + .watchlist-checkbox-custom::after {
content: '✓';
color: #1db954;
font-size: 13px;
font-weight: 700;
}
/* ===== WISHLIST OVERVIEW MODAL STYLES ===== */
/* Category Grid */