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

@@ -4,6 +4,7 @@
// Layer 0: state
import { apiKey, setApiKey, refreshInterval } from './core/state.js';
import { Modal } from './core/modal.js';
// Layer 1: api, i18n
import { loadServerInfo, loadDisplays, configureApiKey } from './core/api.js';
@@ -271,31 +272,13 @@ Object.assign(window, {
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
// Close in order: overlay lightboxes first, then modals
// Close in order: overlay lightboxes first, then modals via stack
if (document.getElementById('display-picker-lightbox').classList.contains('active')) {
closeDisplayPicker();
} else if (document.getElementById('image-lightbox').classList.contains('active')) {
closeLightbox();
} else {
const modals = [
{ id: 'test-pp-template-modal', close: closeTestPPTemplateModal },
{ id: 'test-stream-modal', close: closeTestStreamModal },
{ id: 'test-template-modal', close: closeTestTemplateModal },
{ id: 'stream-modal', close: closeStreamModal },
{ id: 'pp-template-modal', close: closePPTemplateModal },
{ id: 'template-modal', close: closeTemplateModal },
{ id: 'device-settings-modal', close: forceCloseDeviceSettingsModal },
{ id: 'calibration-modal', close: forceCloseCalibrationModal },
{ id: 'target-editor-modal', close: forceCloseTargetEditorModal },
{ id: 'add-device-modal', close: closeAddDeviceModal },
];
for (const m of modals) {
const el = document.getElementById(m.id);
if (el && el.style.display === 'flex') {
m.close();
break;
}
}
Modal.closeTopmost();
}
}
});