From 0c40a922e6463c912d5c772f981b73402c22d58c Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Tue, 17 Feb 2026 11:28:44 -0800 Subject: [PATCH] Add batch remove to watchlist modal --- web_server.py | 26 ++++++++++++ webui/static/script.js | 93 ++++++++++++++++++++++++++++++++++++++++-- webui/static/style.css | 81 ++++++++++++++++++++++++++++++++++++ 3 files changed, 197 insertions(+), 3 deletions(-) diff --git a/web_server.py b/web_server.py index 41a9804c..8e3819ba 100644 --- a/web_server.py +++ b/web_server.py @@ -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""" diff --git a/webui/static/script.js b/webui/static/script.js index 6630aabc..87fdf697 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -24095,12 +24095,29 @@ async function showWatchlistModal() { oninput="filterWatchlistArtists()"> + + +
${artistsData.artists.map(artist => `
+ ${artist.image_url ? ` ${escapeHtml(artist.artist_name)} { 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 diff --git a/webui/static/style.css b/webui/static/style.css index 8a690f35..1f0c4139 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -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 */