Add global watchlist override and UI
Introduce a global watchlist override feature and UI to control release/content filters across all watchlist artists. Backend: add /api/watchlist/global-config (GET/POST) for reading/updating global settings, validation to require at least one release type when override is enabled, and _apply_watchlist_global_overrides() to apply settings to WatchlistArtist objects. Scanners (manual and automatic) now call _apply_watchlist_global_overrides() and perform additional checks (_should_include_release, _should_include_track) to skip releases/tracks according to config. Frontend: add a Global Watchlist Settings modal, controls (release types, content filters, include-all shortcut), save/validation logic, banners/notices when global override is active, and integration into the watchlist modal and per-artist config. Styles: add supporting CSS for the modal and banners. Small cleanup/whitespace adjustments included.
This commit is contained in:
parent
63624a4f6e
commit
35a03d6839
4 changed files with 535 additions and 3 deletions
118
web_server.py
118
web_server.py
|
|
@ -20850,6 +20850,9 @@ def start_watchlist_scan():
|
|||
database = get_database()
|
||||
watchlist_artists = database.get_watchlist_artists()
|
||||
|
||||
# Apply global overrides if enabled
|
||||
_apply_watchlist_global_overrides(watchlist_artists)
|
||||
|
||||
if not watchlist_artists:
|
||||
watchlist_scan_state['status'] = 'completed'
|
||||
watchlist_scan_state['summary'] = {
|
||||
|
|
@ -20993,6 +20996,10 @@ def start_watchlist_scan():
|
|||
|
||||
tracks = album_data['tracks']['items']
|
||||
|
||||
# Check release type filter (album/EP/single)
|
||||
if not scanner._should_include_release(len(tracks), artist):
|
||||
continue
|
||||
|
||||
# Get album image
|
||||
album_image_url = ''
|
||||
if 'images' in album_data and album_data['images']:
|
||||
|
|
@ -21007,6 +21014,10 @@ def start_watchlist_scan():
|
|||
|
||||
# Check each track
|
||||
for track in tracks:
|
||||
# Check content type filter (live/remix/acoustic/compilation)
|
||||
if not scanner._should_include_track(track, album_data, artist):
|
||||
continue
|
||||
|
||||
# Update current track being processed
|
||||
track_name = track.get('name', 'Unknown Track')
|
||||
watchlist_scan_state['current_track_name'] = track_name
|
||||
|
|
@ -21033,7 +21044,7 @@ def start_watchlist_scan():
|
|||
# Keep only last 10
|
||||
if len(watchlist_scan_state['recent_wishlist_additions']) > 10:
|
||||
watchlist_scan_state['recent_wishlist_additions'].pop()
|
||||
|
||||
|
||||
# Rate-limited delay between albums
|
||||
import time
|
||||
time.sleep(album_delay)
|
||||
|
|
@ -21440,6 +21451,100 @@ def watchlist_artist_config(artist_id):
|
|||
traceback.print_exc()
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
@app.route('/api/watchlist/global-config', methods=['GET', 'POST'])
|
||||
def watchlist_global_config():
|
||||
"""Get or update global watchlist configuration (overrides per-artist settings)"""
|
||||
try:
|
||||
if request.method == 'GET':
|
||||
config = {
|
||||
'global_override_enabled': config_manager.get('watchlist.global_override_enabled', False),
|
||||
'include_albums': config_manager.get('watchlist.global_include_albums', True),
|
||||
'include_eps': config_manager.get('watchlist.global_include_eps', True),
|
||||
'include_singles': config_manager.get('watchlist.global_include_singles', True),
|
||||
'include_live': config_manager.get('watchlist.global_include_live', False),
|
||||
'include_remixes': config_manager.get('watchlist.global_include_remixes', False),
|
||||
'include_acoustic': config_manager.get('watchlist.global_include_acoustic', False),
|
||||
'include_compilations': config_manager.get('watchlist.global_include_compilations', False),
|
||||
}
|
||||
return jsonify({"success": True, "config": config})
|
||||
|
||||
else: # POST
|
||||
data = request.get_json()
|
||||
if not data:
|
||||
return jsonify({"success": False, "error": "No data provided"}), 400
|
||||
|
||||
global_override_enabled = data.get('global_override_enabled', False)
|
||||
include_albums = data.get('include_albums', True)
|
||||
include_eps = data.get('include_eps', True)
|
||||
include_singles = data.get('include_singles', True)
|
||||
include_live = data.get('include_live', False)
|
||||
include_remixes = data.get('include_remixes', False)
|
||||
include_acoustic = data.get('include_acoustic', False)
|
||||
include_compilations = data.get('include_compilations', False)
|
||||
|
||||
# When override is enabled, validate at least one release type
|
||||
if global_override_enabled and not (include_albums or include_eps or include_singles):
|
||||
return jsonify({"success": False, "error": "At least one release type must be selected"}), 400
|
||||
|
||||
config_manager.set('watchlist.global_override_enabled', global_override_enabled)
|
||||
config_manager.set('watchlist.global_include_albums', include_albums)
|
||||
config_manager.set('watchlist.global_include_eps', include_eps)
|
||||
config_manager.set('watchlist.global_include_singles', include_singles)
|
||||
config_manager.set('watchlist.global_include_live', include_live)
|
||||
config_manager.set('watchlist.global_include_remixes', include_remixes)
|
||||
config_manager.set('watchlist.global_include_acoustic', include_acoustic)
|
||||
config_manager.set('watchlist.global_include_compilations', include_compilations)
|
||||
|
||||
print(f"✅ Updated global watchlist config: override={global_override_enabled}, "
|
||||
f"albums={include_albums}, eps={include_eps}, singles={include_singles}, "
|
||||
f"live={include_live}, remixes={include_remixes}, acoustic={include_acoustic}, "
|
||||
f"compilations={include_compilations}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": "Global watchlist configuration updated",
|
||||
"config": {
|
||||
'global_override_enabled': global_override_enabled,
|
||||
'include_albums': include_albums,
|
||||
'include_eps': include_eps,
|
||||
'include_singles': include_singles,
|
||||
'include_live': include_live,
|
||||
'include_remixes': include_remixes,
|
||||
'include_acoustic': include_acoustic,
|
||||
'include_compilations': include_compilations,
|
||||
}
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error in watchlist global config: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return jsonify({"success": False, "error": str(e)}), 500
|
||||
|
||||
def _apply_watchlist_global_overrides(watchlist_artists):
|
||||
"""If global override is enabled, overwrite per-artist settings on WatchlistArtist objects."""
|
||||
if not config_manager.get('watchlist.global_override_enabled', False):
|
||||
return
|
||||
# Read global settings once
|
||||
g_albums = config_manager.get('watchlist.global_include_albums', True)
|
||||
g_eps = config_manager.get('watchlist.global_include_eps', True)
|
||||
g_singles = config_manager.get('watchlist.global_include_singles', True)
|
||||
g_live = config_manager.get('watchlist.global_include_live', False)
|
||||
g_remixes = config_manager.get('watchlist.global_include_remixes', False)
|
||||
g_acoustic = config_manager.get('watchlist.global_include_acoustic', False)
|
||||
g_compilations = config_manager.get('watchlist.global_include_compilations', False)
|
||||
print(f"🌐 [Watchlist] Global override is ACTIVE — applying to {len(watchlist_artists)} artists "
|
||||
f"(albums={g_albums}, eps={g_eps}, singles={g_singles}, live={g_live}, "
|
||||
f"remixes={g_remixes}, acoustic={g_acoustic}, compilations={g_compilations})")
|
||||
for artist in watchlist_artists:
|
||||
artist.include_albums = g_albums
|
||||
artist.include_eps = g_eps
|
||||
artist.include_singles = g_singles
|
||||
artist.include_live = g_live
|
||||
artist.include_remixes = g_remixes
|
||||
artist.include_acoustic = g_acoustic
|
||||
artist.include_compilations = g_compilations
|
||||
|
||||
def _update_similar_artists_worker():
|
||||
"""Background worker to update similar artists for all watchlist artists"""
|
||||
global similar_artists_update_state
|
||||
|
|
@ -21661,6 +21766,9 @@ def _process_watchlist_scan_automatically():
|
|||
watchlist_artists = database.get_watchlist_artists()
|
||||
scanner = get_watchlist_scanner(spotify_client)
|
||||
|
||||
# Apply global overrides if enabled
|
||||
_apply_watchlist_global_overrides(watchlist_artists)
|
||||
|
||||
# Initialize detailed progress tracking (same as manual scan)
|
||||
watchlist_scan_state = {
|
||||
'status': 'scanning',
|
||||
|
|
@ -21780,6 +21888,10 @@ def _process_watchlist_scan_automatically():
|
|||
|
||||
tracks = album_data['tracks']['items']
|
||||
|
||||
# Check release type filter (album/EP/single)
|
||||
if not scanner._should_include_release(len(tracks), artist):
|
||||
continue
|
||||
|
||||
# Get album image
|
||||
album_image_url = ''
|
||||
if 'images' in album_data and album_data['images']:
|
||||
|
|
@ -21794,6 +21906,10 @@ def _process_watchlist_scan_automatically():
|
|||
|
||||
# Check each track
|
||||
for track in tracks:
|
||||
# Check content type filter (live/remix/acoustic/compilation)
|
||||
if not scanner._should_include_track(track, album_data, artist):
|
||||
continue
|
||||
|
||||
# Update current track being processed
|
||||
track_name = track.get('name', 'Unknown Track')
|
||||
watchlist_scan_state['current_track_name'] = track_name
|
||||
|
|
|
|||
162
webui/index.html
162
webui/index.html
|
|
@ -3950,6 +3950,168 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Watchlist Global Config Modal -->
|
||||
<div class="modal-overlay hidden" id="watchlist-global-config-modal-overlay">
|
||||
<div class="watchlist-artist-config-modal" id="watchlist-global-config-modal">
|
||||
<div class="watchlist-artist-config-header" style="padding: 24px 28px;">
|
||||
<div style="display: flex; align-items: center; gap: 12px;">
|
||||
<span style="font-size: 28px;">⚙️</span>
|
||||
<div>
|
||||
<h2 style="color: #fff; margin: 0; font-size: 22px;">Global Watchlist Settings</h2>
|
||||
<p style="color: #b3b3b3; margin: 4px 0 0; font-size: 13px;">
|
||||
Override per-artist settings for all watchlist scans
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<span class="watchlist-artist-config-close" onclick="closeWatchlistGlobalSettingsModal()">×</span>
|
||||
</div>
|
||||
|
||||
<div class="watchlist-artist-config-content">
|
||||
<div class="watchlist-artist-config-body">
|
||||
<!-- Global Override Toggle -->
|
||||
<div class="config-section">
|
||||
<label class="config-option global-override-toggle" id="global-override-toggle-label">
|
||||
<input type="checkbox" id="global-override-enabled" onchange="toggleGlobalOverrideOptions()">
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">🌐</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Enable Global Override</span>
|
||||
<span class="config-option-description">When enabled, the settings below apply to ALL
|
||||
artists, ignoring individual artist configs</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div id="global-override-options" style="opacity: 0.4; pointer-events: none; transition: opacity 0.3s;">
|
||||
<!-- Release Types -->
|
||||
<div class="config-section">
|
||||
<h3 class="config-section-title">Release Types</h3>
|
||||
<p class="config-section-subtitle">Select which release types to download for all artists</p>
|
||||
|
||||
<div class="config-options">
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-albums" checked>
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">💿</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Albums</span>
|
||||
<span class="config-option-description">Full-length studio albums</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-eps" checked>
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">🎵</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">EPs</span>
|
||||
<span class="config-option-description">Extended plays (4-6 tracks)</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-singles" checked>
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">🎶</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Singles</span>
|
||||
<span class="config-option-description">Single tracks and 2-3 track
|
||||
releases</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Filters -->
|
||||
<div class="config-section">
|
||||
<h3 class="config-section-title">Content Filters</h3>
|
||||
<p class="config-section-subtitle">Check to INCLUDE, leave unchecked to EXCLUDE (default: all
|
||||
excluded)</p>
|
||||
|
||||
<div class="config-options">
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-live">
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">🎤</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Include Live Versions</span>
|
||||
<span class="config-option-description">Live performances and concerts</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-remixes">
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">🎧</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Include Remixes</span>
|
||||
<span class="config-option-description">Remix versions and edits</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-acoustic">
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">🎸</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Include Acoustic Versions</span>
|
||||
<span class="config-option-description">Acoustic and stripped versions</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
<label class="config-option">
|
||||
<input type="checkbox" id="global-include-compilations">
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">📀</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Include Compilations</span>
|
||||
<span class="config-option-description">Greatest hits and collections</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Include Everything shortcut -->
|
||||
<div class="config-section">
|
||||
<label class="config-option include-everything-option">
|
||||
<input type="checkbox" id="global-include-all" onchange="toggleGlobalIncludeAll()">
|
||||
<div class="config-option-content">
|
||||
<div class="config-option-icon">✅</div>
|
||||
<div class="config-option-text">
|
||||
<span class="config-option-title">Include Everything</span>
|
||||
<span class="config-option-description">Enable all release types AND content filters
|
||||
(live, remixes, acoustic, compilations)</span>
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="watchlist-artist-config-footer">
|
||||
<div class="config-modal-actions">
|
||||
<button class="config-modal-btn config-modal-btn-secondary"
|
||||
onclick="closeWatchlistGlobalSettingsModal()">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="config-modal-btn config-modal-btn-primary" id="save-global-config-btn"
|
||||
onclick="saveWatchlistGlobalConfig()">
|
||||
Save Global Settings
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Genre Browser Modal -->
|
||||
<div class="genre-browser-modal-overlay" id="genre-browser-modal">
|
||||
<div class="genre-browser-modal-container">
|
||||
|
|
|
|||
|
|
@ -25126,11 +25126,20 @@ async function showWatchlistModal() {
|
|||
document.body.appendChild(modal);
|
||||
}
|
||||
|
||||
// Get scan status
|
||||
// Get scan status and global config
|
||||
const statusResponse = await fetch('/api/watchlist/scan/status');
|
||||
const statusData = await statusResponse.json();
|
||||
const scanStatus = statusData.success ? statusData.status : 'idle';
|
||||
|
||||
let globalOverrideActive = false;
|
||||
try {
|
||||
const globalConfigResponse = await fetch('/api/watchlist/global-config');
|
||||
const globalConfigData = await globalConfigResponse.json();
|
||||
globalOverrideActive = globalConfigData.success && globalConfigData.config.global_override_enabled;
|
||||
} catch (e) {
|
||||
console.debug('Could not fetch global config:', e);
|
||||
}
|
||||
|
||||
// Format countdown timer
|
||||
const nextRunSeconds = countData.next_run_in_seconds || 0;
|
||||
const countdownText = formatCountdownTime(nextRunSeconds);
|
||||
|
|
@ -25187,7 +25196,7 @@ async function showWatchlistModal() {
|
|||
</div>
|
||||
|
||||
<div class="playlist-modal-body">
|
||||
<div class="watchlist-actions" style="margin-bottom: 20px; display: flex; gap: 12px;">
|
||||
<div class="watchlist-actions" style="margin-bottom: 20px; display: flex; gap: 12px; align-items: center;">
|
||||
<button class="playlist-modal-btn playlist-modal-btn-primary"
|
||||
id="scan-watchlist-btn"
|
||||
onclick="startWatchlistScan()"
|
||||
|
|
@ -25200,8 +25209,21 @@ async function showWatchlistModal() {
|
|||
${scanStatus === 'scanning' ? 'disabled' : ''}>
|
||||
Update Similar Artists
|
||||
</button>
|
||||
<button class="playlist-modal-btn playlist-modal-btn-secondary ${globalOverrideActive ? 'watchlist-global-settings-active' : ''}"
|
||||
id="watchlist-global-settings-btn"
|
||||
onclick="openWatchlistGlobalSettingsModal()"
|
||||
>
|
||||
${globalOverrideActive ? '⚙️ Global Override ON' : '⚙️ Global Settings'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
${globalOverrideActive ? `
|
||||
<div class="watchlist-global-override-banner">
|
||||
<span>⚠️</span>
|
||||
<span>Global override is active — per-artist settings are being ignored during scans.</span>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<!-- Search Bar -->
|
||||
<div class="watchlist-search-container" style="margin-bottom: 16px;">
|
||||
<input type="text"
|
||||
|
|
@ -25386,6 +25408,16 @@ async function openWatchlistArtistConfigModal(artistId, artistName) {
|
|||
|
||||
const { config, artist } = data;
|
||||
|
||||
// Check if global override is active
|
||||
let globalOverrideActive = false;
|
||||
try {
|
||||
const globalResponse = await fetch('/api/watchlist/global-config');
|
||||
const globalData = await globalResponse.json();
|
||||
globalOverrideActive = globalData.success && globalData.config.global_override_enabled;
|
||||
} catch (e) {
|
||||
console.debug('Could not check global config:', e);
|
||||
}
|
||||
|
||||
// Generate hero section
|
||||
const heroHTML = `
|
||||
${artist.image_url ? `
|
||||
|
|
@ -25431,6 +25463,18 @@ async function openWatchlistArtistConfigModal(artistId, artistName) {
|
|||
document.getElementById('config-include-acoustic').checked = config.include_acoustic || false;
|
||||
document.getElementById('config-include-compilations').checked = config.include_compilations || false;
|
||||
|
||||
// Show global override notice if active
|
||||
const existingNotice = document.querySelector('.global-override-notice');
|
||||
if (existingNotice) existingNotice.remove();
|
||||
|
||||
if (globalOverrideActive) {
|
||||
const notice = document.createElement('div');
|
||||
notice.className = 'global-override-notice watchlist-global-override-banner';
|
||||
notice.innerHTML = '<span>⚠️</span><span>Global override is active — these per-artist settings are currently ignored during scans.</span>';
|
||||
const configBody = document.querySelector('.watchlist-artist-config-body');
|
||||
if (configBody) configBody.insertBefore(notice, configBody.firstChild);
|
||||
}
|
||||
|
||||
// Store artist ID for saving
|
||||
const modal = document.getElementById('watchlist-artist-config-modal');
|
||||
if (modal) {
|
||||
|
|
@ -25476,6 +25520,178 @@ function closeWatchlistArtistConfigModal() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open global watchlist settings modal
|
||||
*/
|
||||
async function openWatchlistGlobalSettingsModal() {
|
||||
try {
|
||||
const response = await fetch('/api/watchlist/global-config');
|
||||
const data = await response.json();
|
||||
|
||||
if (!data.success) {
|
||||
showToast(`Error loading global settings: ${data.error}`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = data.config;
|
||||
|
||||
// Populate checkboxes
|
||||
document.getElementById('global-override-enabled').checked = config.global_override_enabled;
|
||||
document.getElementById('global-include-albums').checked = config.include_albums;
|
||||
document.getElementById('global-include-eps').checked = config.include_eps;
|
||||
document.getElementById('global-include-singles').checked = config.include_singles;
|
||||
document.getElementById('global-include-live').checked = config.include_live;
|
||||
document.getElementById('global-include-remixes').checked = config.include_remixes;
|
||||
document.getElementById('global-include-acoustic').checked = config.include_acoustic;
|
||||
document.getElementById('global-include-compilations').checked = config.include_compilations;
|
||||
|
||||
// Sync "Include Everything" checkbox
|
||||
syncGlobalIncludeAllCheckbox();
|
||||
|
||||
// Update options visibility based on toggle state
|
||||
toggleGlobalOverrideOptions();
|
||||
|
||||
// Update toggle label border
|
||||
const toggleLabel = document.getElementById('global-override-toggle-label');
|
||||
if (toggleLabel) {
|
||||
toggleLabel.style.border = config.global_override_enabled
|
||||
? '2px solid rgba(29, 185, 84, 0.5)'
|
||||
: '2px solid rgba(255, 255, 255, 0.1)';
|
||||
}
|
||||
|
||||
// Show modal
|
||||
const overlay = document.getElementById('watchlist-global-config-modal-overlay');
|
||||
if (overlay) overlay.classList.remove('hidden');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error opening global watchlist settings:', error);
|
||||
showToast(`Error: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close global watchlist settings modal
|
||||
*/
|
||||
function closeWatchlistGlobalSettingsModal() {
|
||||
const overlay = document.getElementById('watchlist-global-config-modal-overlay');
|
||||
if (overlay) overlay.classList.add('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle global override options visibility
|
||||
*/
|
||||
function toggleGlobalOverrideOptions() {
|
||||
const enabled = document.getElementById('global-override-enabled').checked;
|
||||
const options = document.getElementById('global-override-options');
|
||||
if (options) {
|
||||
options.style.opacity = enabled ? '1' : '0.4';
|
||||
options.style.pointerEvents = enabled ? 'auto' : 'none';
|
||||
}
|
||||
|
||||
// Update toggle label border
|
||||
const toggleLabel = document.getElementById('global-override-toggle-label');
|
||||
if (toggleLabel) {
|
||||
toggleLabel.style.border = enabled
|
||||
? '2px solid rgba(29, 185, 84, 0.5)'
|
||||
: '2px solid rgba(255, 255, 255, 0.1)';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle all global include checkboxes
|
||||
*/
|
||||
function toggleGlobalIncludeAll() {
|
||||
const checked = document.getElementById('global-include-all').checked;
|
||||
['global-include-albums', 'global-include-eps', 'global-include-singles',
|
||||
'global-include-live', 'global-include-remixes', 'global-include-acoustic',
|
||||
'global-include-compilations'].forEach(id => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.checked = checked;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the "Include Everything" checkbox based on individual checkbox states
|
||||
*/
|
||||
function syncGlobalIncludeAllCheckbox() {
|
||||
const allIds = ['global-include-albums', 'global-include-eps', 'global-include-singles',
|
||||
'global-include-live', 'global-include-remixes', 'global-include-acoustic',
|
||||
'global-include-compilations'];
|
||||
const allChecked = allIds.every(id => {
|
||||
const el = document.getElementById(id);
|
||||
return el && el.checked;
|
||||
});
|
||||
const includeAllEl = document.getElementById('global-include-all');
|
||||
if (includeAllEl) includeAllEl.checked = allChecked;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save global watchlist configuration
|
||||
*/
|
||||
async function saveWatchlistGlobalConfig() {
|
||||
try {
|
||||
const globalOverrideEnabled = document.getElementById('global-override-enabled').checked;
|
||||
const includeAlbums = document.getElementById('global-include-albums').checked;
|
||||
const includeEps = document.getElementById('global-include-eps').checked;
|
||||
const includeSingles = document.getElementById('global-include-singles').checked;
|
||||
const includeLive = document.getElementById('global-include-live').checked;
|
||||
const includeRemixes = document.getElementById('global-include-remixes').checked;
|
||||
const includeAcoustic = document.getElementById('global-include-acoustic').checked;
|
||||
const includeCompilations = document.getElementById('global-include-compilations').checked;
|
||||
|
||||
if (globalOverrideEnabled && !includeAlbums && !includeEps && !includeSingles) {
|
||||
showToast('Please select at least one release type', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const saveBtn = document.getElementById('save-global-config-btn');
|
||||
if (saveBtn) {
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Saving...';
|
||||
}
|
||||
|
||||
const response = await fetch('/api/watchlist/global-config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
global_override_enabled: globalOverrideEnabled,
|
||||
include_albums: includeAlbums,
|
||||
include_eps: includeEps,
|
||||
include_singles: includeSingles,
|
||||
include_live: includeLive,
|
||||
include_remixes: includeRemixes,
|
||||
include_acoustic: includeAcoustic,
|
||||
include_compilations: includeCompilations,
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.success) {
|
||||
showToast('Global watchlist settings saved', 'success');
|
||||
closeWatchlistGlobalSettingsModal();
|
||||
|
||||
// Refresh the watchlist modal to update button and banner
|
||||
const watchlistModal = document.getElementById('watchlist-modal');
|
||||
if (watchlistModal && watchlistModal.style.display === 'flex') {
|
||||
await showWatchlistModal();
|
||||
}
|
||||
} else {
|
||||
showToast(`Error: ${data.error}`, 'error');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving global config:', error);
|
||||
showToast(`Error: ${error.message}`, 'error');
|
||||
} finally {
|
||||
const saveBtn = document.getElementById('save-global-config-btn');
|
||||
if (saveBtn) {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Save Global Settings';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save watchlist artist configuration
|
||||
* @param {string} artistId - Spotify artist ID
|
||||
|
|
|
|||
|
|
@ -9517,6 +9517,44 @@ body {
|
|||
}
|
||||
}
|
||||
|
||||
/* Watchlist Global Settings */
|
||||
#watchlist-global-config-modal-overlay {
|
||||
z-index: 11000;
|
||||
}
|
||||
|
||||
.global-override-toggle {
|
||||
border: 2px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.include-everything-option {
|
||||
border: 1px dashed rgba(255, 152, 0, 0.4);
|
||||
border-radius: 12px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.watchlist-global-settings-active {
|
||||
background: rgba(255, 152, 0, 0.2) !important;
|
||||
border-color: rgba(255, 152, 0, 0.4) !important;
|
||||
color: #ff9800 !important;
|
||||
}
|
||||
|
||||
.watchlist-global-override-banner {
|
||||
background: rgba(255, 152, 0, 0.15);
|
||||
border: 1px solid rgba(255, 152, 0, 0.3);
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: #ff9800;
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
/* Add these styles to the end of style.css */
|
||||
|
||||
.sync-progress-indicator {
|
||||
|
|
|
|||
Loading…
Reference in a new issue