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:
2026-02-18 17:49:42 +03:00
parent 20d5a42e47
commit ed220a97e7
11 changed files with 341 additions and 329 deletions

View File

@@ -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()
});