From 9b41526c5c5599f17c4bd8f8408ea7f563ce49de Mon Sep 17 00:00:00 2001 From: Kirtan-pc Date: Tue, 30 Jun 2026 11:42:27 +0530 Subject: [PATCH] fix: replace alert/confirm with inline toast and modal for profile ops Profile and backup operations in settings.js previously used native window.alert() and window.confirm() which surface as OS-level blocking modals. These are inconsistent with the dark-themed settings UI and block the Electron renderer thread. Changes: - Add showProfileStatus(elementId, message, isError, persistent) helper that renders a styled, auto-dismissing inline banner beneath each operation group (theme profiles, settings backup, aurora engine profiles), mirroring the existing showHotkeyStatus() pattern. - Add showInlineConfirm(message) Promise-based helper that shows a dark-themed, non-blocking modal overlay instead of window.confirm(). Clicking outside or Cancel resolves false; clicking Confirm resolves true. Falls back to window.confirm() if DOM elements are absent. - Add three .profile-status-msg elements in settings.html: #profile-status-msg (Theme Profiles section) #backup-status-msg (Settings Backup section) #aurora-status-msg (Aurora Engine Profiles section) - Add #inline-confirm-modal overlay in settings.html. - Add .profile-status-msg and .inline-confirm-overlay CSS in settings.css with slide-in animation and error/success colour coding. - Replace every alert() / confirm() call in profile save, delete, duplicate, import, export, backup export/import, restore defaults, aurora engine profile save/load/delete, and gradient-stop limit with the new helpers. Fixes: alert/confirm blocking OS modals in Electron renderer --- settings.css | 117 ++++++++++++++++++++++++++++++++++++++ settings.html | 15 +++++ settings.js | 151 ++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 249 insertions(+), 34 deletions(-) diff --git a/settings.css b/settings.css index 50f0cb1..85bae22 100644 --- a/settings.css +++ b/settings.css @@ -1199,4 +1199,121 @@ input[type="number"]:focus { box-shadow: var(--shadow-input), 0 0 0 3px rgba(0, 212, 255, 0.08); } +/* ============================================ + PROFILE STATUS TOAST + ============================================ */ + +.profile-status-msg { + font-size: 13px; + font-weight: 500; + margin-top: 10px; + padding: 8px 12px; + border-radius: var(--radius-sm); + opacity: 0; + transition: opacity 0.3s ease, transform 0.3s ease; + transform: translateY(-4px); + pointer-events: none; + border-left: 3px solid transparent; +} + +.profile-status-msg.visible { + opacity: 1; + transform: translateY(0); +} + +.profile-status-msg.success { + color: #2ecc71; + background: rgba(46, 204, 113, 0.08); + border-left-color: #2ecc71; +} + +.profile-status-msg.error { + color: #e74c3c; + background: rgba(231, 76, 60, 0.08); + border-left-color: #e74c3c; +} + +/* ============================================ + INLINE CONFIRM MODAL (replaces window.confirm) + ============================================ */ + +.inline-confirm-overlay { + display: none; + position: fixed; + inset: 0; + z-index: 9999; + background: rgba(0, 0, 0, 0.6); + backdrop-filter: blur(4px); + align-items: center; + justify-content: center; +} + +.inline-confirm-overlay.visible { + display: flex; +} + +.inline-confirm-box { + background: #0f1320; + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: var(--radius-md); + padding: 28px 32px; + max-width: 400px; + width: 90%; + box-shadow: 0 24px 64px rgba(0, 0, 0, 0.7), 0 0 0 1px rgba(0, 212, 255, 0.06); + animation: confirm-pop-in 0.2s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; +} + +@keyframes confirm-pop-in { + from { transform: scale(0.93); opacity: 0; } + to { transform: scale(1); opacity: 1; } +} + +.inline-confirm-box p { + color: var(--text-main); + font-size: 14px; + line-height: 1.6; + margin: 0 0 20px; +} + +.inline-confirm-actions { + display: flex; + gap: 10px; + justify-content: flex-end; +} + +.inline-confirm-actions .btn-confirm-cancel { + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + color: var(--text-secondary); + border-radius: var(--radius-sm); + padding: 8px 18px; + font-size: 13px; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; +} + +.inline-confirm-actions .btn-confirm-cancel:hover { + background: rgba(255, 255, 255, 0.09); + color: var(--text-main); +} + +.inline-confirm-actions .btn-confirm-ok { + background: linear-gradient(135deg, rgba(231, 76, 60, 0.25), rgba(192, 57, 43, 0.35)); + border: 1px solid rgba(231, 76, 60, 0.4); + color: #e74c3c; + border-radius: var(--radius-sm); + padding: 8px 18px; + font-size: 13px; + cursor: pointer; + transition: all 0.2s ease; + font-family: inherit; + font-weight: 500; +} + +.inline-confirm-actions .btn-confirm-ok:hover { + background: linear-gradient(135deg, rgba(231, 76, 60, 0.38), rgba(192, 57, 43, 0.5)); + border-color: rgba(231, 76, 60, 0.65); + box-shadow: 0 0 12px rgba(231, 76, 60, 0.2); +} diff --git a/settings.html b/settings.html index 1c53e6e..ad12f94 100644 --- a/settings.html +++ b/settings.html @@ -420,6 +420,7 @@

Advanced Aurora Engine Presets

+
@@ -472,6 +473,7 @@

Theme Profiles

Duplicate
+
@@ -489,6 +491,7 @@

Settings Backup

Import Settings Backup
+
@@ -767,6 +770,18 @@

About Paraline

+ + + + diff --git a/settings.js b/settings.js index e7ca420..4eb6540 100644 --- a/settings.js +++ b/settings.js @@ -695,6 +695,87 @@ refreshThemeProfiles(); } } + // ---------------------------------------- + // PROFILE STATUS TOAST + // ---------------------------------------- + const profileStatusTimers = {}; + + /** + * Shows an auto-dismissing inline status message instead of alert(). + * @param {string} elementId - ID of the .profile-status-msg element + * @param {string} message - Text to display + * @param {boolean} isError - true for red error style, false for green success + * @param {boolean} persistent - if true, never auto-hides + */ + function showProfileStatus(elementId, message, isError = false, persistent = false) { + const el = document.getElementById(elementId); + if (!el) return; + + el.textContent = message; + el.className = 'profile-status-msg ' + (isError ? 'error' : 'success'); + + // Trigger reflow so the transition fires even when class was already applied + // eslint-disable-next-line no-unused-expressions + el.offsetHeight; + el.classList.add('visible'); + + if (profileStatusTimers[elementId]) { + clearTimeout(profileStatusTimers[elementId]); + delete profileStatusTimers[elementId]; + } + + if (!persistent) { + profileStatusTimers[elementId] = setTimeout(() => { + el.classList.remove('visible'); + }, 3000); + } + } + + // ---------------------------------------- + // INLINE CONFIRM MODAL (replaces window.confirm) + // ---------------------------------------- + + /** + * Shows a styled, non-blocking confirm dialog and resolves with the user's choice. + * @param {string} message - Question to ask the user + * @returns {Promise} - resolves true (OK) or false (Cancel) + */ + function showInlineConfirm(message) { + return new Promise((resolve) => { + const modal = document.getElementById('inline-confirm-modal'); + const textEl = document.getElementById('inline-confirm-text'); + const btnOk = document.getElementById('btn-inline-confirm-ok'); + const btnCancel = document.getElementById('btn-inline-confirm-cancel'); + + if (!modal || !textEl || !btnOk || !btnCancel) { + // Fallback to native if DOM elements are missing + resolve(window.confirm(message)); + return; + } + + textEl.textContent = message; + modal.classList.add('visible'); + + function cleanup(result) { + modal.classList.remove('visible'); + btnOk.removeEventListener('click', onOk); + btnCancel.removeEventListener('click', onCancel); + modal.removeEventListener('click', onBackdrop); + resolve(result); + } + + function onOk() { cleanup(true); } + function onCancel() { cleanup(false); } + function onBackdrop(e) { + if (e.target === modal) cleanup(false); + } + + btnOk.addEventListener('click', onOk); + btnCancel.addEventListener('click', onCancel); + modal.addEventListener('click', onBackdrop); + }); + } + function dispatchHotkeyUpdate(settingKey, value) { if (!window.visualizerSettings) return; if (!cachedSettings.shortcuts) cachedSettings.shortcuts = {}; @@ -1068,7 +1149,8 @@ refreshThemeProfiles(); const btnResetTheme = document.getElementById('btn-reset-theme'); if (btnResetTheme) { btnResetTheme.addEventListener('click', async () => { - if (confirm("Reset theme settings to default?")) { + const ok = await showInlineConfirm("Reset theme settings to default?"); + if (ok) { await window.paralineApp.resetActiveThemeSettings(); location.reload(); } @@ -1096,24 +1178,24 @@ refreshThemeProfiles(); const profileName = themeProfileNameInput.value.trim(); if (!profileName) { - alert("Profile name cannot be empty."); + showProfileStatus('profile-status-msg', "Profile name cannot be empty.", true); return; } const validation = isValidProfileName(profileName); if (!validation.valid) { - alert(validation.message); + showProfileStatus('profile-status-msg', validation.message, true); return; } const result = await window.paralineApp.saveThemeProfile(profileName); if (!result) { - alert(`Failed to save profile "${profileName}". The name is invalid or rejected by the system.`); + showProfileStatus('profile-status-msg', `Failed to save profile "${profileName}". The name is invalid or rejected by the system.`, true); return; } themeProfileNameInput.value = ''; - alert(`Theme profile "${profileName}" saved!`); + showProfileStatus('profile-status-msg', `✓ Theme profile "${profileName}" saved!`); refreshThemeProfiles(); }); @@ -1138,7 +1220,7 @@ refreshThemeProfiles(); if (!selectedProfile) return; await window.paralineApp.deleteThemeProfile(selectedProfile); - alert("Theme profile deleted successfully."); + showProfileStatus('profile-status-msg', `✓ Theme profile deleted successfully.`); refreshThemeProfiles(); }); @@ -1151,14 +1233,14 @@ refreshThemeProfiles(); const result = await window.paralineApp.duplicateThemeProfile(selectedProfile); if (!result || !result.success) { - alert(result?.error || "Failed to duplicate profile"); + showProfileStatus('profile-status-msg', result?.error || "Failed to duplicate profile", true); return; } - alert(`Profile duplicated as "${result.profileName}"`); + showProfileStatus('profile-status-msg', `✓ Profile duplicated as "${result.profileName}"`); await refreshThemeProfiles(result.profileName); } catch (error) { - alert("Failed to duplicate profile"); + showProfileStatus('profile-status-msg', "Failed to duplicate profile", true); console.error(error); } }); @@ -1170,7 +1252,7 @@ refreshThemeProfiles(); const res = await window.paralineApp.exportThemeProfile(selectedProfile); if (res && res.success) { - alert("Theme profile exported successfully!"); + showProfileStatus('profile-status-msg', "✓ Theme profile exported successfully!"); } }); @@ -1178,10 +1260,10 @@ refreshThemeProfiles(); const res = await window.paralineApp.importThemeProfile(); if (res && res.success) { - alert(`Theme profile "${res.profileName}" imported successfully!`); + showProfileStatus('profile-status-msg', `✓ Theme profile "${res.profileName}" imported successfully!`); refreshThemeProfiles(); } else if (res && res.error) { - alert(`Failed to import theme: ${res.error}`); + showProfileStatus('profile-status-msg', `Failed to import theme: ${res.error}`, true); } }); @@ -1190,32 +1272,32 @@ refreshThemeProfiles(); const res = await window.paralineApp.exportAllSettings(); if (res && res.success) { - alert("Settings backup exported successfully!"); + showProfileStatus('backup-status-msg', "✓ Settings backup exported successfully!"); } else if (res && res.error) { - alert(`Failed to export settings backup: ${res.error}`); + showProfileStatus('backup-status-msg', `Failed to export settings backup: ${res.error}`, true); } }); } if (btnImportAllSettings) { btnImportAllSettings.addEventListener('click', async () => { - if (!confirm("Import a settings backup? This will replace your current settings and theme profiles.")) { - return; - } + const ok = await showInlineConfirm("Import a settings backup? This will replace your current settings and theme profiles."); + if (!ok) return; const res = await window.paralineApp.importAllSettings(); if (res && res.success) { - alert("Settings backup imported successfully! The app will reload to apply changes."); - location.reload(); + showProfileStatus('backup-status-msg', "✓ Settings backup imported! Reloading..."); + setTimeout(() => location.reload(), 1200); } else if (res && res.error) { - alert(`Failed to import settings backup: ${res.error}`); + showProfileStatus('backup-status-msg', `Failed to import settings backup: ${res.error}`, true); } }); } btnResetThemeProfile.addEventListener('click', async () => { - if (confirm("Are you sure you want to restore default settings? This will reset all your theme customizations.")) { + const ok = await showInlineConfirm("Are you sure you want to restore default settings? This will reset all your theme customizations."); + if (ok) { await window.paralineApp.resetThemeSettings(); location.reload(); } @@ -1960,7 +2042,7 @@ refreshThemeProfiles(); if (e.target !== previewBar) return; let stops = cachedSettings.auroraDrift?.gradientStops || []; if (stops.length >= 6) { - alert("Maximum 6 gradient stops allowed!"); + showProfileStatus('aurora-status-msg', "Maximum 6 gradient stops allowed!", true); return; } @@ -2048,11 +2130,11 @@ refreshThemeProfiles(); btnSave.addEventListener('click', () => { const name = inputName.value.trim(); if (!name) { - alert("Please enter a profile name!"); + showProfileStatus('aurora-status-msg', "Please enter a profile name!", true); return; } if (!isSafePresetName(name)) { - alert("Profile name contains reserved characters. Please choose a different name."); + showProfileStatus('aurora-status-msg', "Profile name contains reserved characters. Please choose a different name.", true); return; } @@ -2060,7 +2142,7 @@ refreshThemeProfiles(); // known fields are ever written to localStorage. const clean = sanitizeAuroraPreset({ ...cachedSettings.auroraDrift }); if (!clean) { - alert("Could not save profile: current Aurora settings appear invalid."); + showProfileStatus('aurora-status-msg', "Could not save profile: current Aurora settings appear invalid.", true); return; } customAuroraPresets[name] = clean; @@ -2072,7 +2154,7 @@ refreshThemeProfiles(); refreshAuroraPresetsDropdown(); document.getElementById('aurora-custom-preset-select').value = name; inputName.value = ''; - alert(`Engine profile "${name}" successfully saved!`); + showProfileStatus('aurora-status-msg', `✓ Engine profile "${name}" successfully saved!`); }); } @@ -2083,35 +2165,36 @@ refreshThemeProfiles(); btnLoad.addEventListener('click', () => { const name = dropdownSelect.value; if (!name || !customAuroraPresets[name]) { - alert("Please select a valid saved profile first!"); + showProfileStatus('aurora-status-msg', "Please select a valid saved profile first!", true); return; } const profileData = customAuroraPresets[name]; dispatchAuroraAdvancedUpdate(profileData); syncAuroraUI(); - alert(`Engine profile "${name}" successfully loaded!`); + showProfileStatus('aurora-status-msg', `✓ Engine profile "${name}" successfully loaded!`); }); } // Delete Preset button const btnDelete = document.getElementById('btn-delete-aurora-preset'); if (btnDelete && dropdownSelect) { - btnDelete.addEventListener('click', () => { + btnDelete.addEventListener('click', async () => { const name = dropdownSelect.value; if (!name || !customAuroraPresets[name]) { - alert("Please select a profile to delete!"); + showProfileStatus('aurora-status-msg', "Please select a profile to delete!", true); return; } - - if (confirm(`Are you sure you want to delete profile "${name}"?`)) { + + const ok = await showInlineConfirm(`Are you sure you want to delete profile "${name}"?`); + if (ok) { delete customAuroraPresets[name]; try { localStorage.setItem('paraline_aurora_presets', JSON.stringify(customAuroraPresets)); } catch(e) {} - + refreshAuroraPresetsDropdown(); - alert(`Profile "${name}" successfully deleted!`); + showProfileStatus('aurora-status-msg', `✓ Profile "${name}" successfully deleted!`); } }); }