Extract Modal base class and fix target editor defaults
Add core/modal.js with reusable Modal class that handles open/close, body locking, backdrop close, dirty checking, error display, and a static stack for ESC key management. Migrate all 13 modals across 8 feature files to use the base class, eliminating ~200 lines of duplicated boilerplate. Replace manual ESC handler list in app.js with Modal.closeTopmost(), fixing 3 modals that were previously unreachable via ESC. Remove 5 unused initialValues variables from state.js. Fix target editor to auto-select first device/source and auto-generate name like the KC editor does. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -3,12 +3,38 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
settingsInitialValues, setSettingsInitialValues,
|
||||
_deviceBrightnessCache,
|
||||
} from '../core/state.js';
|
||||
import { API_BASE, getHeaders, escapeHtml, isSerialDevice, handle401Error } from '../core/api.js';
|
||||
import { t } from '../core/i18n.js';
|
||||
import { lockBody, unlockBody, showToast, showConfirm } from '../core/ui.js';
|
||||
import { showToast } from '../core/ui.js';
|
||||
import { Modal } from '../core/modal.js';
|
||||
|
||||
class DeviceSettingsModal extends Modal {
|
||||
constructor() { super('device-settings-modal'); }
|
||||
|
||||
deviceType = '';
|
||||
capabilities = [];
|
||||
|
||||
snapshotValues() {
|
||||
return {
|
||||
name: this.$('settings-device-name').value,
|
||||
url: this._getUrl(),
|
||||
state_check_interval: this.$('settings-health-interval').value,
|
||||
auto_shutdown: this.$('settings-auto-shutdown').checked,
|
||||
led_count: this.$('settings-led-count').value,
|
||||
};
|
||||
}
|
||||
|
||||
_getUrl() {
|
||||
if (isSerialDevice(this.deviceType)) {
|
||||
return this.$('settings-serial-port').value;
|
||||
}
|
||||
return this.$('settings-device-url').value.trim();
|
||||
}
|
||||
}
|
||||
|
||||
const settingsModal = new DeviceSettingsModal();
|
||||
|
||||
export function createDeviceCard(device) {
|
||||
const state = device.state || {};
|
||||
@@ -186,20 +212,10 @@ export async function showSettings(deviceId) {
|
||||
|
||||
document.getElementById('settings-auto-shutdown').checked = !!device.auto_shutdown;
|
||||
|
||||
setSettingsInitialValues({
|
||||
name: device.name,
|
||||
url: device.url,
|
||||
led_count: String(device.led_count || ''),
|
||||
baud_rate: String(device.baud_rate || '115200'),
|
||||
device_type: device.device_type,
|
||||
capabilities: caps,
|
||||
state_check_interval: '30',
|
||||
auto_shutdown: !!device.auto_shutdown,
|
||||
});
|
||||
|
||||
const modal = document.getElementById('device-settings-modal');
|
||||
modal.style.display = 'flex';
|
||||
lockBody();
|
||||
settingsModal.deviceType = device.device_type;
|
||||
settingsModal.capabilities = caps;
|
||||
settingsModal.snapshot();
|
||||
settingsModal.open();
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById('settings-device-name').focus();
|
||||
@@ -211,61 +227,27 @@ export async function showSettings(deviceId) {
|
||||
}
|
||||
}
|
||||
|
||||
function _getSettingsUrl() {
|
||||
if (isSerialDevice(settingsInitialValues.device_type)) {
|
||||
return document.getElementById('settings-serial-port').value;
|
||||
}
|
||||
return document.getElementById('settings-device-url').value.trim();
|
||||
}
|
||||
|
||||
export function isSettingsDirty() {
|
||||
const ledCountDirty = (settingsInitialValues.capabilities || []).includes('manual_led_count')
|
||||
&& document.getElementById('settings-led-count').value !== settingsInitialValues.led_count;
|
||||
return (
|
||||
document.getElementById('settings-device-name').value !== settingsInitialValues.name ||
|
||||
_getSettingsUrl() !== settingsInitialValues.url ||
|
||||
document.getElementById('settings-health-interval').value !== settingsInitialValues.state_check_interval ||
|
||||
document.getElementById('settings-auto-shutdown').checked !== settingsInitialValues.auto_shutdown ||
|
||||
ledCountDirty
|
||||
);
|
||||
}
|
||||
|
||||
export function forceCloseDeviceSettingsModal() {
|
||||
const modal = document.getElementById('device-settings-modal');
|
||||
const error = document.getElementById('settings-error');
|
||||
modal.style.display = 'none';
|
||||
error.style.display = 'none';
|
||||
unlockBody();
|
||||
setSettingsInitialValues({});
|
||||
}
|
||||
|
||||
export async function closeDeviceSettingsModal() {
|
||||
if (isSettingsDirty()) {
|
||||
const confirmed = await showConfirm(t('modal.discard_changes'));
|
||||
if (!confirmed) return;
|
||||
}
|
||||
forceCloseDeviceSettingsModal();
|
||||
}
|
||||
export function isSettingsDirty() { return settingsModal.isDirty(); }
|
||||
export function forceCloseDeviceSettingsModal() { settingsModal.forceClose(); }
|
||||
export function closeDeviceSettingsModal() { settingsModal.close(); }
|
||||
|
||||
export async function saveDeviceSettings() {
|
||||
const deviceId = document.getElementById('settings-device-id').value;
|
||||
const name = document.getElementById('settings-device-name').value.trim();
|
||||
const url = _getSettingsUrl();
|
||||
const error = document.getElementById('settings-error');
|
||||
const url = settingsModal._getUrl();
|
||||
|
||||
if (!name || !url) {
|
||||
error.textContent = 'Please fill in all fields correctly';
|
||||
error.style.display = 'block';
|
||||
settingsModal.showError('Please fill in all fields correctly');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const body = { name, url, auto_shutdown: document.getElementById('settings-auto-shutdown').checked };
|
||||
const ledCountInput = document.getElementById('settings-led-count');
|
||||
if ((settingsInitialValues.capabilities || []).includes('manual_led_count') && ledCountInput.value) {
|
||||
if (settingsModal.capabilities.includes('manual_led_count') && ledCountInput.value) {
|
||||
body.led_count = parseInt(ledCountInput.value, 10);
|
||||
}
|
||||
if (isSerialDevice(settingsInitialValues.device_type)) {
|
||||
if (isSerialDevice(settingsModal.deviceType)) {
|
||||
const baudVal = document.getElementById('settings-baud-rate').value;
|
||||
if (baudVal) body.baud_rate = parseInt(baudVal, 10);
|
||||
}
|
||||
@@ -279,18 +261,16 @@ export async function saveDeviceSettings() {
|
||||
|
||||
if (!deviceResponse.ok) {
|
||||
const errorData = await deviceResponse.json();
|
||||
error.textContent = `Failed to update device: ${errorData.detail}`;
|
||||
error.style.display = 'block';
|
||||
settingsModal.showError(`Failed to update device: ${errorData.detail}`);
|
||||
return;
|
||||
}
|
||||
|
||||
showToast(t('settings.saved'), 'success');
|
||||
forceCloseDeviceSettingsModal();
|
||||
settingsModal.forceClose();
|
||||
window.loadDevices();
|
||||
} catch (err) {
|
||||
console.error('Failed to save device settings:', err);
|
||||
error.textContent = 'Failed to save settings';
|
||||
error.style.display = 'block';
|
||||
settingsModal.showError('Failed to save settings');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +386,7 @@ export function updateSettingsBaudFpsHint() {
|
||||
const hintEl = document.getElementById('settings-baud-fps-hint');
|
||||
const baudRate = parseInt(document.getElementById('settings-baud-rate').value, 10);
|
||||
const ledCount = parseInt(document.getElementById('settings-led-count').value, 10);
|
||||
_renderFpsHint(hintEl, baudRate, ledCount, settingsInitialValues.device_type);
|
||||
_renderFpsHint(hintEl, baudRate, ledCount, settingsModal.deviceType);
|
||||
}
|
||||
|
||||
// Settings serial port population (used from showSettings)
|
||||
@@ -419,7 +399,7 @@ async function _populateSettingsSerialPorts(currentUrl) {
|
||||
select.appendChild(loadingOpt);
|
||||
|
||||
try {
|
||||
const discoverType = settingsInitialValues.device_type || 'adalight';
|
||||
const discoverType = settingsModal.deviceType || 'adalight';
|
||||
const resp = await fetch(`${API_BASE}/devices/discover?timeout=2&device_type=${encodeURIComponent(discoverType)}`, {
|
||||
headers: getHeaders()
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user