From 1d50ece62c5661fec3f86f333e400d2a1bee7df2 Mon Sep 17 00:00:00 2001 From: Broque Thomas Date: Thu, 19 Feb 2026 08:37:27 -0800 Subject: [PATCH] debounced saving on settings page for all drop-down, tick boxes, input boxes and sliders. --- webui/static/script.js | 45 +++++++++++++++++++++++++++++++++--------- webui/static/style.css | 20 +++++++++++++++---- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/webui/static/script.js b/webui/static/script.js index 975fdbca..3440ceff 100644 --- a/webui/static/script.js +++ b/webui/static/script.js @@ -1542,14 +1542,39 @@ async function copyAddress(address, cryptoName) { // SETTINGS FUNCTIONALITY // =============================== +let settingsAutoSaveTimer = null; + +function debouncedAutoSaveSettings() { + if (settingsAutoSaveTimer) clearTimeout(settingsAutoSaveTimer); + settingsAutoSaveTimer = setTimeout(() => saveSettings(true), 2000); +} + +function handleManualSaveClick() { + if (settingsAutoSaveTimer) clearTimeout(settingsAutoSaveTimer); + saveSettings(false); +} + function initializeSettings() { // This function is called when the settings page is loaded. // It attaches event listeners to all interactive elements on the page. - // Main save button + // Main save button (manual save, non-quiet) + // Uses named function reference so addEventListener deduplicates across repeated calls const saveButton = document.getElementById('save-settings'); if (saveButton) { - saveButton.addEventListener('click', saveSettings); + saveButton.addEventListener('click', handleManualSaveClick); + } + + // Debounced auto-save on all settings inputs + // Uses named function reference (debouncedAutoSaveSettings) so addEventListener deduplicates + const settingsPage = document.getElementById('settings-page'); + if (settingsPage) { + settingsPage.querySelectorAll('input[type="text"], input[type="url"], input[type="password"], input[type="number"], input[type="range"]').forEach(input => { + input.addEventListener('input', debouncedAutoSaveSettings); + }); + settingsPage.querySelectorAll('input[type="checkbox"], select').forEach(input => { + input.addEventListener('change', debouncedAutoSaveSettings); + }); } // Server toggle buttons @@ -1592,7 +1617,7 @@ function resetFileOrganizationTemplates() { document.getElementById('template-single-path').value = defaults.single; document.getElementById('template-playlist-path').value = defaults.playlist; - showToast('Templates reset to defaults. Click "Save Settings" to apply.', 'success'); + debouncedAutoSaveSettings(); } function validateFileOrganizationTemplates() { @@ -1886,6 +1911,9 @@ function toggleServer(serverType) { loadJellyfinUsers(); loadJellyfinMusicLibraries(); } + + // Auto-save after server toggle change + debouncedAutoSaveSettings(); } function updateDownloadSourceUI() { @@ -2112,11 +2140,11 @@ async function saveQualityProfile() { // END QUALITY PROFILE FUNCTIONS // =============================== -async function saveSettings() { +async function saveSettings(quiet = false) { // Validate file organization templates before saving const validationErrors = validateFileOrganizationTemplates(); if (validationErrors.length > 0) { - showToast('Template validation failed: ' + validationErrors.join(', '), 'error'); + if (!quiet) showToast('Template validation failed: ' + validationErrors.join(', '), 'error'); return; } @@ -2197,7 +2225,7 @@ async function saveSettings() { }; try { - showLoadingOverlay('Saving settings...'); + if (!quiet) showLoadingOverlay('Saving settings...'); // Save main settings const response = await fetch(API.settings, { @@ -2228,8 +2256,7 @@ async function saveSettings() { } if (result.success && qualityProfileSaved && lookbackSaved) { - showToast('Settings saved successfully', 'success'); - // Trigger immediate status update + showToast(quiet ? 'Settings auto-saved' : 'Settings saved successfully', 'success'); setTimeout(updateServiceStatus, 1000); } else if (result.success && qualityProfileSaved && !lookbackSaved) { showToast('Settings saved, but discovery lookback period failed to save', 'warning'); @@ -2244,7 +2271,7 @@ async function saveSettings() { console.error('Error saving settings:', error); showToast('Failed to save settings', 'error'); } finally { - hideLoadingOverlay(); + if (!quiet) hideLoadingOverlay(); } } diff --git a/webui/static/style.css b/webui/static/style.css index 82161dc0..f58f9852 100644 --- a/webui/static/style.css +++ b/webui/static/style.css @@ -8246,8 +8246,15 @@ body { } @keyframes batchBarSlideIn { - from { opacity: 0; transform: translateY(-8px); } - to { opacity: 1; transform: translateY(0); } + from { + opacity: 0; + transform: translateY(-8px); + } + + to { + opacity: 1; + transform: translateY(0); + } } .watchlist-batch-count { @@ -8301,12 +8308,12 @@ body { background: rgba(255, 255, 255, 0.06); } -.watchlist-checkbox-wrapper input:checked + .watchlist-checkbox-custom { +.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 { +.watchlist-checkbox-wrapper input:checked+.watchlist-checkbox-custom::after { content: '✓'; color: #1db954; font-size: 13px; @@ -13212,6 +13219,11 @@ body { flex-shrink: 0; } +#artist-hero-section #artist-detail-image { + width: 100%; + height: 100%; +} + .artist-detail-image { width: 200px; height: 200px;