debounced saving on settings page for all drop-down, tick boxes, input boxes and sliders.

This commit is contained in:
Broque Thomas 2026-02-19 08:37:27 -08:00
parent 8cf0950d3b
commit 1d50ece62c
2 changed files with 52 additions and 13 deletions

View file

@ -1542,14 +1542,39 @@ async function copyAddress(address, cryptoName) {
// SETTINGS FUNCTIONALITY // 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() { function initializeSettings() {
// This function is called when the settings page is loaded. // This function is called when the settings page is loaded.
// It attaches event listeners to all interactive elements on the page. // 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'); const saveButton = document.getElementById('save-settings');
if (saveButton) { 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 // Server toggle buttons
@ -1592,7 +1617,7 @@ function resetFileOrganizationTemplates() {
document.getElementById('template-single-path').value = defaults.single; document.getElementById('template-single-path').value = defaults.single;
document.getElementById('template-playlist-path').value = defaults.playlist; document.getElementById('template-playlist-path').value = defaults.playlist;
showToast('Templates reset to defaults. Click "Save Settings" to apply.', 'success'); debouncedAutoSaveSettings();
} }
function validateFileOrganizationTemplates() { function validateFileOrganizationTemplates() {
@ -1886,6 +1911,9 @@ function toggleServer(serverType) {
loadJellyfinUsers(); loadJellyfinUsers();
loadJellyfinMusicLibraries(); loadJellyfinMusicLibraries();
} }
// Auto-save after server toggle change
debouncedAutoSaveSettings();
} }
function updateDownloadSourceUI() { function updateDownloadSourceUI() {
@ -2112,11 +2140,11 @@ async function saveQualityProfile() {
// END QUALITY PROFILE FUNCTIONS // END QUALITY PROFILE FUNCTIONS
// =============================== // ===============================
async function saveSettings() { async function saveSettings(quiet = false) {
// Validate file organization templates before saving // Validate file organization templates before saving
const validationErrors = validateFileOrganizationTemplates(); const validationErrors = validateFileOrganizationTemplates();
if (validationErrors.length > 0) { if (validationErrors.length > 0) {
showToast('Template validation failed: ' + validationErrors.join(', '), 'error'); if (!quiet) showToast('Template validation failed: ' + validationErrors.join(', '), 'error');
return; return;
} }
@ -2197,7 +2225,7 @@ async function saveSettings() {
}; };
try { try {
showLoadingOverlay('Saving settings...'); if (!quiet) showLoadingOverlay('Saving settings...');
// Save main settings // Save main settings
const response = await fetch(API.settings, { const response = await fetch(API.settings, {
@ -2228,8 +2256,7 @@ async function saveSettings() {
} }
if (result.success && qualityProfileSaved && lookbackSaved) { if (result.success && qualityProfileSaved && lookbackSaved) {
showToast('Settings saved successfully', 'success'); showToast(quiet ? 'Settings auto-saved' : 'Settings saved successfully', 'success');
// Trigger immediate status update
setTimeout(updateServiceStatus, 1000); setTimeout(updateServiceStatus, 1000);
} else if (result.success && qualityProfileSaved && !lookbackSaved) { } else if (result.success && qualityProfileSaved && !lookbackSaved) {
showToast('Settings saved, but discovery lookback period failed to save', 'warning'); showToast('Settings saved, but discovery lookback period failed to save', 'warning');
@ -2244,7 +2271,7 @@ async function saveSettings() {
console.error('Error saving settings:', error); console.error('Error saving settings:', error);
showToast('Failed to save settings', 'error'); showToast('Failed to save settings', 'error');
} finally { } finally {
hideLoadingOverlay(); if (!quiet) hideLoadingOverlay();
} }
} }

View file

@ -8246,8 +8246,15 @@ body {
} }
@keyframes batchBarSlideIn { @keyframes batchBarSlideIn {
from { opacity: 0; transform: translateY(-8px); } from {
to { opacity: 1; transform: translateY(0); } opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
} }
.watchlist-batch-count { .watchlist-batch-count {
@ -8301,12 +8308,12 @@ body {
background: rgba(255, 255, 255, 0.06); 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); background: rgba(29, 185, 84, 0.3);
border-color: #1db954; border-color: #1db954;
} }
.watchlist-checkbox-wrapper input:checked + .watchlist-checkbox-custom::after { .watchlist-checkbox-wrapper input:checked+.watchlist-checkbox-custom::after {
content: '✓'; content: '✓';
color: #1db954; color: #1db954;
font-size: 13px; font-size: 13px;
@ -13212,6 +13219,11 @@ body {
flex-shrink: 0; flex-shrink: 0;
} }
#artist-hero-section #artist-detail-image {
width: 100%;
height: 100%;
}
.artist-detail-image { .artist-detail-image {
width: 200px; width: 200px;
height: 200px; height: 200px;