Add batch remove to watchlist modal
This commit is contained in:
parent
139b8530f4
commit
0c40a922e6
3 changed files with 197 additions and 3 deletions
|
|
@ -19144,6 +19144,32 @@ def remove_from_watchlist():
|
||||||
print(f"Error removing from watchlist: {e}")
|
print(f"Error removing from watchlist: {e}")
|
||||||
return jsonify({"success": False, "error": str(e)}), 500
|
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'])
|
@app.route('/api/watchlist/check', methods=['POST'])
|
||||||
def check_watchlist_status():
|
def check_watchlist_status():
|
||||||
"""Check if an artist is in the watchlist"""
|
"""Check if an artist is in the watchlist"""
|
||||||
|
|
|
||||||
|
|
@ -24095,12 +24095,29 @@ async function showWatchlistModal() {
|
||||||
oninput="filterWatchlistArtists()">
|
oninput="filterWatchlistArtists()">
|
||||||
</div>
|
</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">
|
<div class="watchlist-artists-list" id="watchlist-artists-list">
|
||||||
${artistsData.artists.map(artist => `
|
${artistsData.artists.map(artist => `
|
||||||
<div class="watchlist-artist-item"
|
<div class="watchlist-artist-item"
|
||||||
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '"')}"
|
data-artist-name="${artist.artist_name.toLowerCase().replace(/"/g, '"')}"
|
||||||
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
|
data-artist-id="${artist.spotify_artist_id || artist.itunes_artist_id}"
|
||||||
style="cursor: pointer;">
|
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 ? `
|
${artist.image_url ? `
|
||||||
<img src="${artist.image_url}"
|
<img src="${artist.image_url}"
|
||||||
alt="${escapeHtml(artist.artist_name)}"
|
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 => {
|
modal.querySelectorAll('.watchlist-artist-item').forEach(item => {
|
||||||
item.addEventListener('click', (e) => {
|
item.addEventListener('click', (e) => {
|
||||||
// Don't trigger if clicking the remove button
|
// Don't trigger if clicking the remove button or checkbox
|
||||||
if (e.target.closest('.watchlist-remove-btn')) {
|
if (e.target.closest('.watchlist-remove-btn') || e.target.closest('.watchlist-checkbox-wrapper')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24444,6 +24461,9 @@ function filterWatchlistArtists() {
|
||||||
item.style.display = 'none';
|
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 ---
|
// --- Metadata Updater Functions ---
|
||||||
|
|
||||||
// Global state for metadata update polling
|
// Global state for metadata update polling
|
||||||
|
|
|
||||||
|
|
@ -8221,6 +8221,87 @@ body {
|
||||||
transform: scale(1.05);
|
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 ===== */
|
/* ===== WISHLIST OVERVIEW MODAL STYLES ===== */
|
||||||
|
|
||||||
/* Category Grid */
|
/* Category Grid */
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue