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:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
// Layer 0: state
|
// Layer 0: state
|
||||||
import { apiKey, setApiKey, refreshInterval } from './core/state.js';
|
import { apiKey, setApiKey, refreshInterval } from './core/state.js';
|
||||||
|
import { Modal } from './core/modal.js';
|
||||||
|
|
||||||
// Layer 1: api, i18n
|
// Layer 1: api, i18n
|
||||||
import { loadServerInfo, loadDisplays, configureApiKey } from './core/api.js';
|
import { loadServerInfo, loadDisplays, configureApiKey } from './core/api.js';
|
||||||
@@ -271,31 +272,13 @@ Object.assign(window, {
|
|||||||
|
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Escape') {
|
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')) {
|
if (document.getElementById('display-picker-lightbox').classList.contains('active')) {
|
||||||
closeDisplayPicker();
|
closeDisplayPicker();
|
||||||
} else if (document.getElementById('image-lightbox').classList.contains('active')) {
|
} else if (document.getElementById('image-lightbox').classList.contains('active')) {
|
||||||
closeLightbox();
|
closeLightbox();
|
||||||
} else {
|
} else {
|
||||||
const modals = [
|
Modal.closeTopmost();
|
||||||
{ 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
93
server/src/wled_controller/static/js/core/modal.js
Normal file
93
server/src/wled_controller/static/js/core/modal.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/**
|
||||||
|
* Modal base class — eliminates open/close/dirty-check boilerplate.
|
||||||
|
*
|
||||||
|
* Simple modals: const modal = new Modal('my-modal-id');
|
||||||
|
* Dirty-check: class MyModal extends Modal { snapshotValues() { ... } }
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { t } from './i18n.js';
|
||||||
|
import { lockBody, unlockBody, setupBackdropClose, showConfirm } from './ui.js';
|
||||||
|
|
||||||
|
export class Modal {
|
||||||
|
static _stack = [];
|
||||||
|
|
||||||
|
constructor(elementId, { backdrop = true, lock = true } = {}) {
|
||||||
|
this.el = document.getElementById(elementId);
|
||||||
|
this.errorEl = this.el?.querySelector('.modal-error');
|
||||||
|
this._lock = lock;
|
||||||
|
this._backdrop = backdrop;
|
||||||
|
this._initialValues = {};
|
||||||
|
this._closing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isOpen() {
|
||||||
|
return this.el?.style.display === 'flex';
|
||||||
|
}
|
||||||
|
|
||||||
|
open() {
|
||||||
|
this.el.style.display = 'flex';
|
||||||
|
if (this._lock) lockBody();
|
||||||
|
if (this._backdrop) setupBackdropClose(this.el, () => this.close());
|
||||||
|
Modal._stack = Modal._stack.filter(m => m !== this);
|
||||||
|
Modal._stack.push(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
forceClose() {
|
||||||
|
this.el.style.display = 'none';
|
||||||
|
if (this._lock) unlockBody();
|
||||||
|
this._initialValues = {};
|
||||||
|
this.hideError();
|
||||||
|
this.onForceClose();
|
||||||
|
Modal._stack = Modal._stack.filter(m => m !== this);
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
if (this._closing) return;
|
||||||
|
if (this.isDirty()) {
|
||||||
|
this._closing = true;
|
||||||
|
const confirmed = await showConfirm(t('modal.discard_changes'));
|
||||||
|
this._closing = false;
|
||||||
|
if (!confirmed) return;
|
||||||
|
}
|
||||||
|
this.forceClose();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Override in subclass to define tracked fields for dirty checking. */
|
||||||
|
snapshotValues() { return {}; }
|
||||||
|
|
||||||
|
/** Override if current values differ from snapshot format. */
|
||||||
|
currentValues() { return this.snapshotValues(); }
|
||||||
|
|
||||||
|
snapshot() {
|
||||||
|
this._initialValues = this.snapshotValues();
|
||||||
|
}
|
||||||
|
|
||||||
|
isDirty() {
|
||||||
|
const cur = this.currentValues();
|
||||||
|
return Object.keys(this._initialValues).some(k => this._initialValues[k] !== cur[k]);
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(msg) {
|
||||||
|
if (this.errorEl) {
|
||||||
|
this.errorEl.textContent = msg;
|
||||||
|
this.errorEl.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideError() {
|
||||||
|
if (this.errorEl) this.errorEl.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Hook for subclass cleanup on force-close (canvas, observers, etc.). */
|
||||||
|
onForceClose() {}
|
||||||
|
|
||||||
|
$(id) {
|
||||||
|
return document.getElementById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static closeTopmost() {
|
||||||
|
const top = Modal._stack[Modal._stack.length - 1];
|
||||||
|
if (top) { top.close(); return true; }
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,12 +31,6 @@ export let _displayPickerSelectedIndex = null;
|
|||||||
export function set_displayPickerSelectedIndex(v) { _displayPickerSelectedIndex = v; }
|
export function set_displayPickerSelectedIndex(v) { _displayPickerSelectedIndex = v; }
|
||||||
|
|
||||||
// Calibration
|
// Calibration
|
||||||
export let settingsInitialValues = {};
|
|
||||||
export function setSettingsInitialValues(v) { settingsInitialValues = v; }
|
|
||||||
|
|
||||||
export let calibrationInitialValues = {};
|
|
||||||
export function setCalibrationInitialValues(v) { calibrationInitialValues = v; }
|
|
||||||
|
|
||||||
export const calibrationTestState = {};
|
export const calibrationTestState = {};
|
||||||
|
|
||||||
export const EDGE_TEST_COLORS = {
|
export const EDGE_TEST_COLORS = {
|
||||||
@@ -105,16 +99,10 @@ export let _lastValidatedImageSource = '';
|
|||||||
export function set_lastValidatedImageSource(v) { _lastValidatedImageSource = v; }
|
export function set_lastValidatedImageSource(v) { _lastValidatedImageSource = v; }
|
||||||
|
|
||||||
// Target editor state
|
// Target editor state
|
||||||
export let targetEditorInitialValues = {};
|
|
||||||
export function setTargetEditorInitialValues(v) { targetEditorInitialValues = v; }
|
|
||||||
|
|
||||||
export let _targetEditorDevices = [];
|
export let _targetEditorDevices = [];
|
||||||
export function set_targetEditorDevices(v) { _targetEditorDevices = v; }
|
export function set_targetEditorDevices(v) { _targetEditorDevices = v; }
|
||||||
|
|
||||||
// KC editor state
|
// KC editor state
|
||||||
export let kcEditorInitialValues = {};
|
|
||||||
export function setKcEditorInitialValues(v) { kcEditorInitialValues = v; }
|
|
||||||
|
|
||||||
export let _kcNameManuallyEdited = false;
|
export let _kcNameManuallyEdited = false;
|
||||||
export function set_kcNameManuallyEdited(v) { _kcNameManuallyEdited = v; }
|
export function set_kcNameManuallyEdited(v) { _kcNameManuallyEdited = v; }
|
||||||
|
|
||||||
@@ -143,9 +131,6 @@ export function setPatternEditorSelectedIdx(v) { patternEditorSelectedIdx = v; }
|
|||||||
export let patternEditorBgImage = null;
|
export let patternEditorBgImage = null;
|
||||||
export function setPatternEditorBgImage(v) { patternEditorBgImage = v; }
|
export function setPatternEditorBgImage(v) { patternEditorBgImage = v; }
|
||||||
|
|
||||||
export let patternEditorInitialValues = {};
|
|
||||||
export function setPatternEditorInitialValues(v) { patternEditorInitialValues = v; }
|
|
||||||
|
|
||||||
export let patternCanvasDragMode = null;
|
export let patternCanvasDragMode = null;
|
||||||
export function setPatternCanvasDragMode(v) { patternCanvasDragMode = v; }
|
export function setPatternCanvasDragMode(v) { patternCanvasDragMode = v; }
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,50 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
calibrationInitialValues, setCalibrationInitialValues,
|
|
||||||
calibrationTestState, EDGE_TEST_COLORS,
|
calibrationTestState, EDGE_TEST_COLORS,
|
||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { showToast } from '../core/ui.js';
|
||||||
import { lockBody, unlockBody, showToast, showConfirm } from '../core/ui.js';
|
import { Modal } from '../core/modal.js';
|
||||||
import { closeTutorial, startCalibrationTutorial } from './tutorials.js';
|
import { closeTutorial, startCalibrationTutorial } from './tutorials.js';
|
||||||
|
|
||||||
|
/* ── CalibrationModal subclass ────────────────────────────────── */
|
||||||
|
|
||||||
|
class CalibrationModal extends Modal {
|
||||||
|
constructor() {
|
||||||
|
super('calibration-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotValues() {
|
||||||
|
return {
|
||||||
|
start_position: this.$('cal-start-position').value,
|
||||||
|
layout: this.$('cal-layout').value,
|
||||||
|
offset: this.$('cal-offset').value,
|
||||||
|
top: this.$('cal-top-leds').value,
|
||||||
|
right: this.$('cal-right-leds').value,
|
||||||
|
bottom: this.$('cal-bottom-leds').value,
|
||||||
|
left: this.$('cal-left-leds').value,
|
||||||
|
spans: JSON.stringify(window.edgeSpans),
|
||||||
|
skip_start: this.$('cal-skip-start').value,
|
||||||
|
skip_end: this.$('cal-skip-end').value,
|
||||||
|
border_width: this.$('cal-border-width').value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onForceClose() {
|
||||||
|
closeTutorial();
|
||||||
|
const deviceId = this.$('calibration-device-id').value;
|
||||||
|
if (deviceId) clearTestMode(deviceId);
|
||||||
|
if (window._calibrationResizeObserver) window._calibrationResizeObserver.disconnect();
|
||||||
|
const error = this.$('calibration-error');
|
||||||
|
if (error) error.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const calibModal = new CalibrationModal();
|
||||||
|
|
||||||
|
/* ── Public API (exported names unchanged) ────────────────────── */
|
||||||
|
|
||||||
export async function showCalibration(deviceId) {
|
export async function showCalibration(deviceId) {
|
||||||
try {
|
try {
|
||||||
const [response, displaysResponse] = await Promise.all([
|
const [response, displaysResponse] = await Promise.all([
|
||||||
@@ -63,27 +99,12 @@ export async function showCalibration(deviceId) {
|
|||||||
left: { start: calibration.span_left_start ?? 0, end: calibration.span_left_end ?? 1 },
|
left: { start: calibration.span_left_start ?? 0, end: calibration.span_left_end ?? 1 },
|
||||||
};
|
};
|
||||||
|
|
||||||
setCalibrationInitialValues({
|
|
||||||
start_position: calibration.start_position,
|
|
||||||
layout: calibration.layout,
|
|
||||||
offset: String(calibration.offset || 0),
|
|
||||||
top: String(calibration.leds_top || 0),
|
|
||||||
right: String(calibration.leds_right || 0),
|
|
||||||
bottom: String(calibration.leds_bottom || 0),
|
|
||||||
left: String(calibration.leds_left || 0),
|
|
||||||
spans: JSON.stringify(window.edgeSpans),
|
|
||||||
skip_start: String(calibration.skip_leds_start || 0),
|
|
||||||
skip_end: String(calibration.skip_leds_end || 0),
|
|
||||||
border_width: String(calibration.border_width || 10),
|
|
||||||
});
|
|
||||||
|
|
||||||
calibrationTestState[device.id] = new Set();
|
calibrationTestState[device.id] = new Set();
|
||||||
|
|
||||||
updateCalibrationPreview();
|
updateCalibrationPreview();
|
||||||
|
|
||||||
const modal = document.getElementById('calibration-modal');
|
calibModal.snapshot();
|
||||||
modal.style.display = 'flex';
|
calibModal.open();
|
||||||
lockBody();
|
|
||||||
|
|
||||||
initSpanDrag();
|
initSpanDrag();
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
@@ -109,40 +130,15 @@ export async function showCalibration(deviceId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isCalibrationDirty() {
|
function isCalibrationDirty() {
|
||||||
return (
|
return calibModal.isDirty();
|
||||||
document.getElementById('cal-start-position').value !== calibrationInitialValues.start_position ||
|
|
||||||
document.getElementById('cal-layout').value !== calibrationInitialValues.layout ||
|
|
||||||
document.getElementById('cal-offset').value !== calibrationInitialValues.offset ||
|
|
||||||
document.getElementById('cal-top-leds').value !== calibrationInitialValues.top ||
|
|
||||||
document.getElementById('cal-right-leds').value !== calibrationInitialValues.right ||
|
|
||||||
document.getElementById('cal-bottom-leds').value !== calibrationInitialValues.bottom ||
|
|
||||||
document.getElementById('cal-left-leds').value !== calibrationInitialValues.left ||
|
|
||||||
JSON.stringify(window.edgeSpans) !== calibrationInitialValues.spans ||
|
|
||||||
document.getElementById('cal-skip-start').value !== calibrationInitialValues.skip_start ||
|
|
||||||
document.getElementById('cal-skip-end').value !== calibrationInitialValues.skip_end ||
|
|
||||||
document.getElementById('cal-border-width').value !== calibrationInitialValues.border_width
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forceCloseCalibrationModal() {
|
export function forceCloseCalibrationModal() {
|
||||||
closeTutorial();
|
calibModal.forceClose();
|
||||||
const deviceId = document.getElementById('calibration-device-id').value;
|
|
||||||
if (deviceId) clearTestMode(deviceId);
|
|
||||||
if (window._calibrationResizeObserver) window._calibrationResizeObserver.disconnect();
|
|
||||||
const modal = document.getElementById('calibration-modal');
|
|
||||||
const error = document.getElementById('calibration-error');
|
|
||||||
modal.style.display = 'none';
|
|
||||||
error.style.display = 'none';
|
|
||||||
unlockBody();
|
|
||||||
setCalibrationInitialValues({});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeCalibrationModal() {
|
export async function closeCalibrationModal() {
|
||||||
if (isCalibrationDirty()) {
|
calibModal.close();
|
||||||
const confirmed = await showConfirm(t('modal.discard_changes'));
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
|
||||||
forceCloseCalibrationModal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateOffsetSkipLock() {
|
export function updateOffsetSkipLock() {
|
||||||
@@ -681,7 +677,7 @@ export async function saveCalibration() {
|
|||||||
if (response.status === 401) { handle401Error(); return; }
|
if (response.status === 401) { handle401Error(); return; }
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
showToast('Calibration saved', 'success');
|
showToast('Calibration saved', 'success');
|
||||||
forceCloseCalibrationModal();
|
calibModal.forceClose();
|
||||||
window.loadDevices();
|
window.loadDevices();
|
||||||
} else {
|
} else {
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
|
|||||||
@@ -5,13 +5,15 @@
|
|||||||
import {
|
import {
|
||||||
_discoveryScanRunning, set_discoveryScanRunning,
|
_discoveryScanRunning, set_discoveryScanRunning,
|
||||||
_discoveryCache, set_discoveryCache,
|
_discoveryCache, set_discoveryCache,
|
||||||
settingsInitialValues,
|
|
||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, isSerialDevice, escapeHtml, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, isSerialDevice, escapeHtml, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { t } from '../core/i18n.js';
|
||||||
import { lockBody, unlockBody, showToast } from '../core/ui.js';
|
import { showToast } from '../core/ui.js';
|
||||||
|
import { Modal } from '../core/modal.js';
|
||||||
import { _computeMaxFps, _renderFpsHint } from './devices.js';
|
import { _computeMaxFps, _renderFpsHint } from './devices.js';
|
||||||
|
|
||||||
|
const addDeviceModal = new Modal('add-device-modal');
|
||||||
|
|
||||||
export function onDeviceTypeChanged() {
|
export function onDeviceTypeChanged() {
|
||||||
const deviceType = document.getElementById('device-type').value;
|
const deviceType = document.getElementById('device-type').value;
|
||||||
const urlGroup = document.getElementById('device-url-group');
|
const urlGroup = document.getElementById('device-url-group');
|
||||||
@@ -154,7 +156,6 @@ export function onSerialPortFocus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function showAddDevice() {
|
export function showAddDevice() {
|
||||||
const modal = document.getElementById('add-device-modal');
|
|
||||||
const form = document.getElementById('add-device-form');
|
const form = document.getElementById('add-device-form');
|
||||||
const error = document.getElementById('add-device-error');
|
const error = document.getElementById('add-device-error');
|
||||||
form.reset();
|
form.reset();
|
||||||
@@ -172,16 +173,13 @@ export function showAddDevice() {
|
|||||||
document.getElementById('device-serial-port').innerHTML = '';
|
document.getElementById('device-serial-port').innerHTML = '';
|
||||||
const scanBtn = document.getElementById('scan-network-btn');
|
const scanBtn = document.getElementById('scan-network-btn');
|
||||||
if (scanBtn) scanBtn.disabled = false;
|
if (scanBtn) scanBtn.disabled = false;
|
||||||
modal.style.display = 'flex';
|
addDeviceModal.open();
|
||||||
lockBody();
|
|
||||||
onDeviceTypeChanged();
|
onDeviceTypeChanged();
|
||||||
setTimeout(() => document.getElementById('device-name').focus(), 100);
|
setTimeout(() => document.getElementById('device-name').focus(), 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeAddDeviceModal() {
|
export function closeAddDeviceModal() {
|
||||||
const modal = document.getElementById('add-device-modal');
|
addDeviceModal.forceClose();
|
||||||
modal.style.display = 'none';
|
|
||||||
unlockBody();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function scanForDevices(forceType) {
|
export async function scanForDevices(forceType) {
|
||||||
|
|||||||
@@ -3,12 +3,38 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
settingsInitialValues, setSettingsInitialValues,
|
|
||||||
_deviceBrightnessCache,
|
_deviceBrightnessCache,
|
||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, escapeHtml, isSerialDevice, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, escapeHtml, isSerialDevice, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.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) {
|
export function createDeviceCard(device) {
|
||||||
const state = device.state || {};
|
const state = device.state || {};
|
||||||
@@ -186,20 +212,10 @@ export async function showSettings(deviceId) {
|
|||||||
|
|
||||||
document.getElementById('settings-auto-shutdown').checked = !!device.auto_shutdown;
|
document.getElementById('settings-auto-shutdown').checked = !!device.auto_shutdown;
|
||||||
|
|
||||||
setSettingsInitialValues({
|
settingsModal.deviceType = device.device_type;
|
||||||
name: device.name,
|
settingsModal.capabilities = caps;
|
||||||
url: device.url,
|
settingsModal.snapshot();
|
||||||
led_count: String(device.led_count || ''),
|
settingsModal.open();
|
||||||
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();
|
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
document.getElementById('settings-device-name').focus();
|
document.getElementById('settings-device-name').focus();
|
||||||
@@ -211,61 +227,27 @@ export async function showSettings(deviceId) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function _getSettingsUrl() {
|
export function isSettingsDirty() { return settingsModal.isDirty(); }
|
||||||
if (isSerialDevice(settingsInitialValues.device_type)) {
|
export function forceCloseDeviceSettingsModal() { settingsModal.forceClose(); }
|
||||||
return document.getElementById('settings-serial-port').value;
|
export function closeDeviceSettingsModal() { settingsModal.close(); }
|
||||||
}
|
|
||||||
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 async function saveDeviceSettings() {
|
export async function saveDeviceSettings() {
|
||||||
const deviceId = document.getElementById('settings-device-id').value;
|
const deviceId = document.getElementById('settings-device-id').value;
|
||||||
const name = document.getElementById('settings-device-name').value.trim();
|
const name = document.getElementById('settings-device-name').value.trim();
|
||||||
const url = _getSettingsUrl();
|
const url = settingsModal._getUrl();
|
||||||
const error = document.getElementById('settings-error');
|
|
||||||
|
|
||||||
if (!name || !url) {
|
if (!name || !url) {
|
||||||
error.textContent = 'Please fill in all fields correctly';
|
settingsModal.showError('Please fill in all fields correctly');
|
||||||
error.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const body = { name, url, auto_shutdown: document.getElementById('settings-auto-shutdown').checked };
|
const body = { name, url, auto_shutdown: document.getElementById('settings-auto-shutdown').checked };
|
||||||
const ledCountInput = document.getElementById('settings-led-count');
|
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);
|
body.led_count = parseInt(ledCountInput.value, 10);
|
||||||
}
|
}
|
||||||
if (isSerialDevice(settingsInitialValues.device_type)) {
|
if (isSerialDevice(settingsModal.deviceType)) {
|
||||||
const baudVal = document.getElementById('settings-baud-rate').value;
|
const baudVal = document.getElementById('settings-baud-rate').value;
|
||||||
if (baudVal) body.baud_rate = parseInt(baudVal, 10);
|
if (baudVal) body.baud_rate = parseInt(baudVal, 10);
|
||||||
}
|
}
|
||||||
@@ -279,18 +261,16 @@ export async function saveDeviceSettings() {
|
|||||||
|
|
||||||
if (!deviceResponse.ok) {
|
if (!deviceResponse.ok) {
|
||||||
const errorData = await deviceResponse.json();
|
const errorData = await deviceResponse.json();
|
||||||
error.textContent = `Failed to update device: ${errorData.detail}`;
|
settingsModal.showError(`Failed to update device: ${errorData.detail}`);
|
||||||
error.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showToast(t('settings.saved'), 'success');
|
showToast(t('settings.saved'), 'success');
|
||||||
forceCloseDeviceSettingsModal();
|
settingsModal.forceClose();
|
||||||
window.loadDevices();
|
window.loadDevices();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to save device settings:', err);
|
console.error('Failed to save device settings:', err);
|
||||||
error.textContent = 'Failed to save settings';
|
settingsModal.showError('Failed to save settings');
|
||||||
error.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -406,7 +386,7 @@ export function updateSettingsBaudFpsHint() {
|
|||||||
const hintEl = document.getElementById('settings-baud-fps-hint');
|
const hintEl = document.getElementById('settings-baud-fps-hint');
|
||||||
const baudRate = parseInt(document.getElementById('settings-baud-rate').value, 10);
|
const baudRate = parseInt(document.getElementById('settings-baud-rate').value, 10);
|
||||||
const ledCount = parseInt(document.getElementById('settings-led-count').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)
|
// Settings serial port population (used from showSettings)
|
||||||
@@ -419,7 +399,7 @@ async function _populateSettingsSerialPorts(currentUrl) {
|
|||||||
select.appendChild(loadingOpt);
|
select.appendChild(loadingOpt);
|
||||||
|
|
||||||
try {
|
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)}`, {
|
const resp = await fetch(`${API_BASE}/devices/discover?timeout=2&device_type=${encodeURIComponent(discoverType)}`, {
|
||||||
headers: getHeaders()
|
headers: getHeaders()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,14 +5,33 @@
|
|||||||
import {
|
import {
|
||||||
kcTestAutoRefresh, setKcTestAutoRefresh,
|
kcTestAutoRefresh, setKcTestAutoRefresh,
|
||||||
kcTestTargetId, setKcTestTargetId,
|
kcTestTargetId, setKcTestTargetId,
|
||||||
kcEditorInitialValues, setKcEditorInitialValues,
|
|
||||||
_kcNameManuallyEdited, set_kcNameManuallyEdited,
|
_kcNameManuallyEdited, set_kcNameManuallyEdited,
|
||||||
kcWebSockets,
|
kcWebSockets,
|
||||||
PATTERN_RECT_BORDERS,
|
PATTERN_RECT_BORDERS,
|
||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { t } from '../core/i18n.js';
|
||||||
import { lockBody, unlockBody, showToast, showConfirm, setupBackdropClose } from '../core/ui.js';
|
import { lockBody, showToast, showConfirm } from '../core/ui.js';
|
||||||
|
import { Modal } from '../core/modal.js';
|
||||||
|
|
||||||
|
class KCEditorModal extends Modal {
|
||||||
|
constructor() {
|
||||||
|
super('kc-editor-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotValues() {
|
||||||
|
return {
|
||||||
|
name: document.getElementById('kc-editor-name').value,
|
||||||
|
source: document.getElementById('kc-editor-source').value,
|
||||||
|
fps: document.getElementById('kc-editor-fps').value,
|
||||||
|
interpolation: document.getElementById('kc-editor-interpolation').value,
|
||||||
|
smoothing: document.getElementById('kc-editor-smoothing').value,
|
||||||
|
patternTemplateId: document.getElementById('kc-editor-pattern-template').value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const kcEditorModal = new KCEditorModal();
|
||||||
|
|
||||||
export function createKCTargetCard(target, sourceMap, patternTemplateMap) {
|
export function createKCTargetCard(target, sourceMap, patternTemplateMap) {
|
||||||
const state = target.state || {};
|
const state = target.state || {};
|
||||||
@@ -391,19 +410,8 @@ export async function showKCEditor(targetId = null) {
|
|||||||
patSelect.onchange = () => _autoGenerateKCName();
|
patSelect.onchange = () => _autoGenerateKCName();
|
||||||
if (!targetId) _autoGenerateKCName();
|
if (!targetId) _autoGenerateKCName();
|
||||||
|
|
||||||
setKcEditorInitialValues({
|
kcEditorModal.snapshot();
|
||||||
name: document.getElementById('kc-editor-name').value,
|
kcEditorModal.open();
|
||||||
source: sourceSelect.value,
|
|
||||||
fps: document.getElementById('kc-editor-fps').value,
|
|
||||||
interpolation: document.getElementById('kc-editor-interpolation').value,
|
|
||||||
smoothing: document.getElementById('kc-editor-smoothing').value,
|
|
||||||
patternTemplateId: patSelect.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const modal = document.getElementById('kc-editor-modal');
|
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closeKCEditorModal);
|
|
||||||
|
|
||||||
document.getElementById('kc-editor-error').style.display = 'none';
|
document.getElementById('kc-editor-error').style.display = 'none';
|
||||||
setTimeout(() => document.getElementById('kc-editor-name').focus(), 100);
|
setTimeout(() => document.getElementById('kc-editor-name').focus(), 100);
|
||||||
@@ -414,29 +422,15 @@ export async function showKCEditor(targetId = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isKCEditorDirty() {
|
export function isKCEditorDirty() {
|
||||||
return (
|
return kcEditorModal.isDirty();
|
||||||
document.getElementById('kc-editor-name').value !== kcEditorInitialValues.name ||
|
|
||||||
document.getElementById('kc-editor-source').value !== kcEditorInitialValues.source ||
|
|
||||||
document.getElementById('kc-editor-fps').value !== kcEditorInitialValues.fps ||
|
|
||||||
document.getElementById('kc-editor-interpolation').value !== kcEditorInitialValues.interpolation ||
|
|
||||||
document.getElementById('kc-editor-smoothing').value !== kcEditorInitialValues.smoothing ||
|
|
||||||
document.getElementById('kc-editor-pattern-template').value !== kcEditorInitialValues.patternTemplateId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeKCEditorModal() {
|
export async function closeKCEditorModal() {
|
||||||
if (isKCEditorDirty()) {
|
await kcEditorModal.close();
|
||||||
const confirmed = await showConfirm(t('modal.discard_changes'));
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
|
||||||
forceCloseKCEditorModal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forceCloseKCEditorModal() {
|
export function forceCloseKCEditorModal() {
|
||||||
document.getElementById('kc-editor-modal').style.display = 'none';
|
kcEditorModal.forceClose();
|
||||||
document.getElementById('kc-editor-error').style.display = 'none';
|
|
||||||
unlockBody();
|
|
||||||
setKcEditorInitialValues({});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveKCEditor() {
|
export async function saveKCEditor() {
|
||||||
@@ -447,17 +441,14 @@ export async function saveKCEditor() {
|
|||||||
const interpolation = document.getElementById('kc-editor-interpolation').value;
|
const interpolation = document.getElementById('kc-editor-interpolation').value;
|
||||||
const smoothing = parseFloat(document.getElementById('kc-editor-smoothing').value);
|
const smoothing = parseFloat(document.getElementById('kc-editor-smoothing').value);
|
||||||
const patternTemplateId = document.getElementById('kc-editor-pattern-template').value;
|
const patternTemplateId = document.getElementById('kc-editor-pattern-template').value;
|
||||||
const errorEl = document.getElementById('kc-editor-error');
|
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
errorEl.textContent = t('kc.error.required');
|
kcEditorModal.showError(t('kc.error.required'));
|
||||||
errorEl.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!patternTemplateId) {
|
if (!patternTemplateId) {
|
||||||
errorEl.textContent = t('kc.error.no_pattern');
|
kcEditorModal.showError(t('kc.error.no_pattern'));
|
||||||
errorEl.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -497,13 +488,12 @@ export async function saveKCEditor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showToast(targetId ? t('kc.updated') : t('kc.created'), 'success');
|
showToast(targetId ? t('kc.updated') : t('kc.created'), 'success');
|
||||||
forceCloseKCEditorModal();
|
kcEditorModal.forceClose();
|
||||||
// Use window.* to avoid circular import with targets.js
|
// Use window.* to avoid circular import with targets.js
|
||||||
if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
|
if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving KC target:', error);
|
console.error('Error saving KC target:', error);
|
||||||
errorEl.textContent = error.message;
|
kcEditorModal.showError(error.message);
|
||||||
errorEl.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
patternEditorRects, setPatternEditorRects,
|
patternEditorRects, setPatternEditorRects,
|
||||||
patternEditorSelectedIdx, setPatternEditorSelectedIdx,
|
patternEditorSelectedIdx, setPatternEditorSelectedIdx,
|
||||||
patternEditorBgImage, setPatternEditorBgImage,
|
patternEditorBgImage, setPatternEditorBgImage,
|
||||||
patternEditorInitialValues, setPatternEditorInitialValues,
|
|
||||||
patternCanvasDragMode, setPatternCanvasDragMode,
|
patternCanvasDragMode, setPatternCanvasDragMode,
|
||||||
patternCanvasDragStart, setPatternCanvasDragStart,
|
patternCanvasDragStart, setPatternCanvasDragStart,
|
||||||
patternCanvasDragOrigRect, setPatternCanvasDragOrigRect,
|
patternCanvasDragOrigRect, setPatternCanvasDragOrigRect,
|
||||||
@@ -17,7 +16,30 @@ import {
|
|||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { t } from '../core/i18n.js';
|
||||||
import { lockBody, unlockBody, showToast, showConfirm, setupBackdropClose } from '../core/ui.js';
|
import { showToast, showConfirm } from '../core/ui.js';
|
||||||
|
import { Modal } from '../core/modal.js';
|
||||||
|
|
||||||
|
class PatternTemplateModal extends Modal {
|
||||||
|
constructor() {
|
||||||
|
super('pattern-template-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotValues() {
|
||||||
|
return {
|
||||||
|
name: document.getElementById('pattern-template-name').value,
|
||||||
|
description: document.getElementById('pattern-template-description').value,
|
||||||
|
rectangles: JSON.stringify(patternEditorRects),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onForceClose() {
|
||||||
|
setPatternEditorRects([]);
|
||||||
|
setPatternEditorSelectedIdx(-1);
|
||||||
|
setPatternEditorBgImage(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const patternModal = new PatternTemplateModal();
|
||||||
|
|
||||||
export function createPatternTemplateCard(pt) {
|
export function createPatternTemplateCard(pt) {
|
||||||
const rectCount = (pt.rectangles || []).length;
|
const rectCount = (pt.rectangles || []).length;
|
||||||
@@ -77,20 +99,13 @@ export async function showPatternTemplateEditor(templateId = null) {
|
|||||||
setPatternEditorRects([]);
|
setPatternEditorRects([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
setPatternEditorInitialValues({
|
patternModal.snapshot();
|
||||||
name: document.getElementById('pattern-template-name').value,
|
|
||||||
description: document.getElementById('pattern-template-description').value,
|
|
||||||
rectangles: JSON.stringify(patternEditorRects),
|
|
||||||
});
|
|
||||||
|
|
||||||
renderPatternRectList();
|
renderPatternRectList();
|
||||||
renderPatternCanvas();
|
renderPatternCanvas();
|
||||||
_attachPatternCanvasEvents();
|
_attachPatternCanvasEvents();
|
||||||
|
|
||||||
const modal = document.getElementById('pattern-template-modal');
|
patternModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closePatternTemplateModal);
|
|
||||||
|
|
||||||
document.getElementById('pattern-template-error').style.display = 'none';
|
document.getElementById('pattern-template-error').style.display = 'none';
|
||||||
setTimeout(() => document.getElementById('pattern-template-name').focus(), 100);
|
setTimeout(() => document.getElementById('pattern-template-name').focus(), 100);
|
||||||
@@ -101,40 +116,24 @@ export async function showPatternTemplateEditor(templateId = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isPatternEditorDirty() {
|
export function isPatternEditorDirty() {
|
||||||
return (
|
return patternModal.isDirty();
|
||||||
document.getElementById('pattern-template-name').value !== patternEditorInitialValues.name ||
|
|
||||||
document.getElementById('pattern-template-description').value !== patternEditorInitialValues.description ||
|
|
||||||
JSON.stringify(patternEditorRects) !== patternEditorInitialValues.rectangles
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closePatternTemplateModal() {
|
export async function closePatternTemplateModal() {
|
||||||
if (isPatternEditorDirty()) {
|
await patternModal.close();
|
||||||
const confirmed = await showConfirm(t('modal.discard_changes'));
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
|
||||||
forceClosePatternTemplateModal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forceClosePatternTemplateModal() {
|
export function forceClosePatternTemplateModal() {
|
||||||
document.getElementById('pattern-template-modal').style.display = 'none';
|
patternModal.forceClose();
|
||||||
document.getElementById('pattern-template-error').style.display = 'none';
|
|
||||||
unlockBody();
|
|
||||||
setPatternEditorRects([]);
|
|
||||||
setPatternEditorSelectedIdx(-1);
|
|
||||||
setPatternEditorBgImage(null);
|
|
||||||
setPatternEditorInitialValues({});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function savePatternTemplate() {
|
export async function savePatternTemplate() {
|
||||||
const templateId = document.getElementById('pattern-template-id').value;
|
const templateId = document.getElementById('pattern-template-id').value;
|
||||||
const name = document.getElementById('pattern-template-name').value.trim();
|
const name = document.getElementById('pattern-template-name').value.trim();
|
||||||
const description = document.getElementById('pattern-template-description').value.trim();
|
const description = document.getElementById('pattern-template-description').value.trim();
|
||||||
const errorEl = document.getElementById('pattern-template-error');
|
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
errorEl.textContent = t('pattern.error.required');
|
patternModal.showError(t('pattern.error.required'));
|
||||||
errorEl.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,13 +164,12 @@ export async function savePatternTemplate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showToast(templateId ? t('pattern.updated') : t('pattern.created'), 'success');
|
showToast(templateId ? t('pattern.updated') : t('pattern.created'), 'success');
|
||||||
forceClosePatternTemplateModal();
|
patternModal.forceClose();
|
||||||
// Use window.* to avoid circular import with targets.js
|
// Use window.* to avoid circular import with targets.js
|
||||||
if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
|
if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving pattern template:', error);
|
console.error('Error saving pattern template:', error);
|
||||||
errorEl.textContent = error.message;
|
patternModal.showError(error.message);
|
||||||
errorEl.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,10 @@
|
|||||||
import { _profilesCache, set_profilesCache } from '../core/state.js';
|
import { _profilesCache, set_profilesCache } from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, escapeHtml, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, escapeHtml, handle401Error } from '../core/api.js';
|
||||||
import { t, updateAllText } from '../core/i18n.js';
|
import { t, updateAllText } from '../core/i18n.js';
|
||||||
import { lockBody, unlockBody, showToast, showConfirm } from '../core/ui.js';
|
import { showToast, showConfirm } from '../core/ui.js';
|
||||||
|
import { Modal } from '../core/modal.js';
|
||||||
|
|
||||||
|
const profileModal = new Modal('profile-editor-modal');
|
||||||
|
|
||||||
export async function loadProfiles() {
|
export async function loadProfiles() {
|
||||||
const container = document.getElementById('profiles-content');
|
const container = document.getElementById('profiles-content');
|
||||||
@@ -137,14 +140,12 @@ export async function openProfileEditor(profileId) {
|
|||||||
logicSelect.value = 'or';
|
logicSelect.value = 'or';
|
||||||
}
|
}
|
||||||
|
|
||||||
modal.style.display = 'flex';
|
profileModal.open();
|
||||||
lockBody();
|
|
||||||
updateAllText();
|
updateAllText();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeProfileEditorModal() {
|
export function closeProfileEditorModal() {
|
||||||
document.getElementById('profile-editor-modal').style.display = 'none';
|
profileModal.forceClose();
|
||||||
unlockBody();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadProfileTargetChecklist(selectedIds) {
|
async function loadProfileTargetChecklist(selectedIds) {
|
||||||
@@ -304,12 +305,10 @@ export async function saveProfileEditor() {
|
|||||||
const nameInput = document.getElementById('profile-editor-name');
|
const nameInput = document.getElementById('profile-editor-name');
|
||||||
const enabledInput = document.getElementById('profile-editor-enabled');
|
const enabledInput = document.getElementById('profile-editor-enabled');
|
||||||
const logicSelect = document.getElementById('profile-editor-logic');
|
const logicSelect = document.getElementById('profile-editor-logic');
|
||||||
const errorEl = document.getElementById('profile-editor-error');
|
|
||||||
|
|
||||||
const name = nameInput.value.trim();
|
const name = nameInput.value.trim();
|
||||||
if (!name) {
|
if (!name) {
|
||||||
errorEl.textContent = 'Name is required';
|
profileModal.showError('Name is required');
|
||||||
errorEl.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -336,12 +335,11 @@ export async function saveProfileEditor() {
|
|||||||
throw new Error(err.detail || 'Failed to save profile');
|
throw new Error(err.detail || 'Failed to save profile');
|
||||||
}
|
}
|
||||||
|
|
||||||
closeProfileEditorModal();
|
profileModal.forceClose();
|
||||||
showToast(isEdit ? 'Profile updated' : 'Profile created', 'success');
|
showToast(isEdit ? 'Profile updated' : 'Profile created', 'success');
|
||||||
loadProfiles();
|
loadProfiles();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
errorEl.textContent = e.message;
|
profileModal.showError(e.message);
|
||||||
errorEl.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,9 +21,19 @@ import {
|
|||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { t } from '../core/i18n.js';
|
||||||
import { setupBackdropClose, lockBody, unlockBody, showToast, showConfirm, openLightbox, openFullImageLightbox, showOverlaySpinner, hideOverlaySpinner } from '../core/ui.js';
|
import { Modal } from '../core/modal.js';
|
||||||
|
import { showToast, showConfirm, openLightbox, openFullImageLightbox, showOverlaySpinner, hideOverlaySpinner } from '../core/ui.js';
|
||||||
import { openDisplayPicker, formatDisplayLabel } from './displays.js';
|
import { openDisplayPicker, formatDisplayLabel } from './displays.js';
|
||||||
|
|
||||||
|
// ===== Modal instances =====
|
||||||
|
|
||||||
|
const templateModal = new Modal('template-modal');
|
||||||
|
const testTemplateModal = new Modal('test-template-modal');
|
||||||
|
const streamModal = new Modal('stream-modal');
|
||||||
|
const testStreamModal = new Modal('test-stream-modal');
|
||||||
|
const ppTemplateModal = new Modal('pp-template-modal');
|
||||||
|
const testPPTemplateModal = new Modal('test-pp-template-modal');
|
||||||
|
|
||||||
// ===== Capture Templates =====
|
// ===== Capture Templates =====
|
||||||
|
|
||||||
async function loadCaptureTemplates() {
|
async function loadCaptureTemplates() {
|
||||||
@@ -55,9 +65,7 @@ export async function showAddTemplateModal() {
|
|||||||
|
|
||||||
await loadAvailableEngines();
|
await loadAvailableEngines();
|
||||||
|
|
||||||
const modal = document.getElementById('template-modal');
|
templateModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
setupBackdropClose(modal, closeTemplateModal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editTemplate(templateId) {
|
export async function editTemplate(templateId) {
|
||||||
@@ -83,9 +91,7 @@ export async function editTemplate(templateId) {
|
|||||||
if (testResults) testResults.style.display = 'none';
|
if (testResults) testResults.style.display = 'none';
|
||||||
document.getElementById('template-error').style.display = 'none';
|
document.getElementById('template-error').style.display = 'none';
|
||||||
|
|
||||||
const modal = document.getElementById('template-modal');
|
templateModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
setupBackdropClose(modal, closeTemplateModal);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading template:', error);
|
console.error('Error loading template:', error);
|
||||||
showToast(t('templates.error.load') + ': ' + error.message, 'error');
|
showToast(t('templates.error.load') + ': ' + error.message, 'error');
|
||||||
@@ -93,7 +99,7 @@ export async function editTemplate(templateId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closeTemplateModal() {
|
export function closeTemplateModal() {
|
||||||
document.getElementById('template-modal').style.display = 'none';
|
templateModal.forceClose();
|
||||||
setCurrentEditingTemplateId(null);
|
setCurrentEditingTemplateId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,13 +131,11 @@ export async function showTestTemplateModal(templateId) {
|
|||||||
await loadDisplaysForTest();
|
await loadDisplaysForTest();
|
||||||
restoreCaptureDuration();
|
restoreCaptureDuration();
|
||||||
|
|
||||||
const modal = document.getElementById('test-template-modal');
|
testTemplateModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
setupBackdropClose(modal, closeTestTemplateModal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeTestTemplateModal() {
|
export function closeTestTemplateModal() {
|
||||||
document.getElementById('test-template-modal').style.display = 'none';
|
testTemplateModal.forceClose();
|
||||||
window.currentTestingTemplate = null;
|
window.currentTestingTemplate = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,10 +711,7 @@ export async function showAddStreamModal(presetType) {
|
|||||||
|
|
||||||
await populateStreamModalDropdowns();
|
await populateStreamModalDropdowns();
|
||||||
|
|
||||||
const modal = document.getElementById('stream-modal');
|
streamModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closeStreamModal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editStream(streamId) {
|
export async function editStream(streamId) {
|
||||||
@@ -754,10 +755,7 @@ export async function editStream(streamId) {
|
|||||||
if (stream.image_source) validateStaticImage();
|
if (stream.image_source) validateStaticImage();
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('stream-modal');
|
streamModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closeStreamModal);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading stream:', error);
|
console.error('Error loading stream:', error);
|
||||||
showToast(t('streams.error.load') + ': ' + error.message, 'error');
|
showToast(t('streams.error.load') + ': ' + error.message, 'error');
|
||||||
@@ -896,9 +894,8 @@ export async function deleteStream(streamId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closeStreamModal() {
|
export function closeStreamModal() {
|
||||||
document.getElementById('stream-modal').style.display = 'none';
|
streamModal.forceClose();
|
||||||
document.getElementById('stream-type').disabled = false;
|
document.getElementById('stream-type').disabled = false;
|
||||||
unlockBody();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function validateStaticImage() {
|
async function validateStaticImage() {
|
||||||
@@ -956,15 +953,11 @@ export async function showTestStreamModal(streamId) {
|
|||||||
set_currentTestStreamId(streamId);
|
set_currentTestStreamId(streamId);
|
||||||
restoreStreamTestDuration();
|
restoreStreamTestDuration();
|
||||||
|
|
||||||
const modal = document.getElementById('test-stream-modal');
|
testStreamModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closeTestStreamModal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeTestStreamModal() {
|
export function closeTestStreamModal() {
|
||||||
document.getElementById('test-stream-modal').style.display = 'none';
|
testStreamModal.forceClose();
|
||||||
unlockBody();
|
|
||||||
set_currentTestStreamId(null);
|
set_currentTestStreamId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1032,15 +1025,11 @@ export async function showTestPPTemplateModal(templateId) {
|
|||||||
select.value = lastStream;
|
select.value = lastStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
const modal = document.getElementById('test-pp-template-modal');
|
testPPTemplateModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closeTestPPTemplateModal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function closeTestPPTemplateModal() {
|
export function closeTestPPTemplateModal() {
|
||||||
document.getElementById('test-pp-template-modal').style.display = 'none';
|
testPPTemplateModal.forceClose();
|
||||||
unlockBody();
|
|
||||||
set_currentTestPPTemplateId(null);
|
set_currentTestPPTemplateId(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1296,10 +1285,7 @@ export async function showAddPPTemplateModal() {
|
|||||||
_populateFilterSelect();
|
_populateFilterSelect();
|
||||||
renderModalFilterList();
|
renderModalFilterList();
|
||||||
|
|
||||||
const modal = document.getElementById('pp-template-modal');
|
ppTemplateModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closePPTemplateModal);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function editPPTemplate(templateId) {
|
export async function editPPTemplate(templateId) {
|
||||||
@@ -1324,10 +1310,7 @@ export async function editPPTemplate(templateId) {
|
|||||||
_populateFilterSelect();
|
_populateFilterSelect();
|
||||||
renderModalFilterList();
|
renderModalFilterList();
|
||||||
|
|
||||||
const modal = document.getElementById('pp-template-modal');
|
ppTemplateModal.open();
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closePPTemplateModal);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading PP template:', error);
|
console.error('Error loading PP template:', error);
|
||||||
showToast(t('postprocessing.error.load') + ': ' + error.message, 'error');
|
showToast(t('postprocessing.error.load') + ': ' + error.message, 'error');
|
||||||
@@ -1386,9 +1369,8 @@ export async function deletePPTemplate(templateId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function closePPTemplateModal() {
|
export function closePPTemplateModal() {
|
||||||
document.getElementById('pp-template-modal').style.display = 'none';
|
ppTemplateModal.forceClose();
|
||||||
set_modalFilters([]);
|
set_modalFilters([]);
|
||||||
unlockBody();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exported helpers used by other modules
|
// Exported helpers used by other modules
|
||||||
|
|||||||
@@ -3,20 +3,53 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
targetEditorInitialValues, setTargetEditorInitialValues,
|
|
||||||
_targetEditorDevices, set_targetEditorDevices,
|
_targetEditorDevices, set_targetEditorDevices,
|
||||||
_deviceBrightnessCache,
|
_deviceBrightnessCache,
|
||||||
kcWebSockets,
|
kcWebSockets,
|
||||||
} from '../core/state.js';
|
} from '../core/state.js';
|
||||||
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml, handle401Error } from '../core/api.js';
|
||||||
import { t } from '../core/i18n.js';
|
import { t } from '../core/i18n.js';
|
||||||
import { lockBody, unlockBody, showToast, showConfirm, setupBackdropClose } from '../core/ui.js';
|
import { showToast, showConfirm } from '../core/ui.js';
|
||||||
|
import { Modal } from '../core/modal.js';
|
||||||
import { createDeviceCard, attachDeviceListeners, fetchDeviceBrightness } from './devices.js';
|
import { createDeviceCard, attachDeviceListeners, fetchDeviceBrightness } from './devices.js';
|
||||||
import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from './kc-targets.js';
|
import { createKCTargetCard, connectKCWebSocket, disconnectKCWebSocket } from './kc-targets.js';
|
||||||
|
|
||||||
// createPatternTemplateCard is imported via window.* to avoid circular deps
|
// createPatternTemplateCard is imported via window.* to avoid circular deps
|
||||||
// (pattern-templates.js calls window.loadTargetsTab)
|
// (pattern-templates.js calls window.loadTargetsTab)
|
||||||
|
|
||||||
|
class TargetEditorModal extends Modal {
|
||||||
|
constructor() {
|
||||||
|
super('target-editor-modal');
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotValues() {
|
||||||
|
return {
|
||||||
|
name: document.getElementById('target-editor-name').value,
|
||||||
|
device: document.getElementById('target-editor-device').value,
|
||||||
|
source: document.getElementById('target-editor-source').value,
|
||||||
|
fps: document.getElementById('target-editor-fps').value,
|
||||||
|
interpolation: document.getElementById('target-editor-interpolation').value,
|
||||||
|
smoothing: document.getElementById('target-editor-smoothing').value,
|
||||||
|
standby_interval: document.getElementById('target-editor-standby-interval').value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetEditorModal = new TargetEditorModal();
|
||||||
|
|
||||||
|
let _targetNameManuallyEdited = false;
|
||||||
|
|
||||||
|
function _autoGenerateTargetName() {
|
||||||
|
if (_targetNameManuallyEdited) return;
|
||||||
|
if (document.getElementById('target-editor-id').value) return;
|
||||||
|
const deviceSelect = document.getElementById('target-editor-device');
|
||||||
|
const sourceSelect = document.getElementById('target-editor-source');
|
||||||
|
const deviceName = deviceSelect.selectedOptions[0]?.dataset?.name || '';
|
||||||
|
const sourceName = sourceSelect.selectedOptions[0]?.dataset?.name || '';
|
||||||
|
if (!deviceName || !sourceName) return;
|
||||||
|
document.getElementById('target-editor-name').value = `${deviceName} \u00b7 ${sourceName}`;
|
||||||
|
}
|
||||||
|
|
||||||
function _updateStandbyVisibility() {
|
function _updateStandbyVisibility() {
|
||||||
const deviceSelect = document.getElementById('target-editor-device');
|
const deviceSelect = document.getElementById('target-editor-device');
|
||||||
const standbyGroup = document.getElementById('target-editor-standby-group');
|
const standbyGroup = document.getElementById('target-editor-standby-group');
|
||||||
@@ -43,12 +76,12 @@ export async function showTargetEditor(targetId = null) {
|
|||||||
devices.forEach(d => {
|
devices.forEach(d => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = d.id;
|
opt.value = d.id;
|
||||||
|
opt.dataset.name = d.name;
|
||||||
const shortUrl = d.url ? d.url.replace(/^https?:\/\//, '') : '';
|
const shortUrl = d.url ? d.url.replace(/^https?:\/\//, '') : '';
|
||||||
const devType = (d.device_type || 'wled').toUpperCase();
|
const devType = (d.device_type || 'wled').toUpperCase();
|
||||||
opt.textContent = `${d.name} [${devType}]${shortUrl ? ' (' + shortUrl + ')' : ''}`;
|
opt.textContent = `${d.name} [${devType}]${shortUrl ? ' (' + shortUrl + ')' : ''}`;
|
||||||
deviceSelect.appendChild(opt);
|
deviceSelect.appendChild(opt);
|
||||||
});
|
});
|
||||||
deviceSelect.onchange = _updateStandbyVisibility;
|
|
||||||
|
|
||||||
// Populate source select
|
// Populate source select
|
||||||
const sourceSelect = document.getElementById('target-editor-source');
|
const sourceSelect = document.getElementById('target-editor-source');
|
||||||
@@ -56,6 +89,7 @@ export async function showTargetEditor(targetId = null) {
|
|||||||
sources.forEach(s => {
|
sources.forEach(s => {
|
||||||
const opt = document.createElement('option');
|
const opt = document.createElement('option');
|
||||||
opt.value = s.id;
|
opt.value = s.id;
|
||||||
|
opt.dataset.name = s.name;
|
||||||
const typeIcon = s.stream_type === 'raw' ? '\uD83D\uDDA5\uFE0F' : s.stream_type === 'static_image' ? '\uD83D\uDDBC\uFE0F' : '\uD83C\uDFA8';
|
const typeIcon = s.stream_type === 'raw' ? '\uD83D\uDDA5\uFE0F' : s.stream_type === 'static_image' ? '\uD83D\uDDBC\uFE0F' : '\uD83C\uDFA8';
|
||||||
opt.textContent = `${typeIcon} ${s.name}`;
|
opt.textContent = `${typeIcon} ${s.name}`;
|
||||||
sourceSelect.appendChild(opt);
|
sourceSelect.appendChild(opt);
|
||||||
@@ -80,11 +114,9 @@ export async function showTargetEditor(targetId = null) {
|
|||||||
document.getElementById('target-editor-standby-interval-value').textContent = target.settings?.standby_interval ?? 1.0;
|
document.getElementById('target-editor-standby-interval-value').textContent = target.settings?.standby_interval ?? 1.0;
|
||||||
document.getElementById('target-editor-title').textContent = t('targets.edit');
|
document.getElementById('target-editor-title').textContent = t('targets.edit');
|
||||||
} else {
|
} else {
|
||||||
// Creating new target
|
// Creating new target — first option is selected by default
|
||||||
document.getElementById('target-editor-id').value = '';
|
document.getElementById('target-editor-id').value = '';
|
||||||
document.getElementById('target-editor-name').value = '';
|
document.getElementById('target-editor-name').value = '';
|
||||||
deviceSelect.value = '';
|
|
||||||
sourceSelect.value = '';
|
|
||||||
document.getElementById('target-editor-fps').value = 30;
|
document.getElementById('target-editor-fps').value = 30;
|
||||||
document.getElementById('target-editor-fps-value').textContent = '30';
|
document.getElementById('target-editor-fps-value').textContent = '30';
|
||||||
document.getElementById('target-editor-interpolation').value = 'average';
|
document.getElementById('target-editor-interpolation').value = 'average';
|
||||||
@@ -95,23 +127,18 @@ export async function showTargetEditor(targetId = null) {
|
|||||||
document.getElementById('target-editor-title').textContent = t('targets.add');
|
document.getElementById('target-editor-title').textContent = t('targets.add');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-name generation
|
||||||
|
_targetNameManuallyEdited = !!targetId;
|
||||||
|
document.getElementById('target-editor-name').oninput = () => { _targetNameManuallyEdited = true; };
|
||||||
|
deviceSelect.onchange = () => { _updateStandbyVisibility(); _autoGenerateTargetName(); };
|
||||||
|
sourceSelect.onchange = () => _autoGenerateTargetName();
|
||||||
|
if (!targetId) _autoGenerateTargetName();
|
||||||
|
|
||||||
// Show/hide standby interval based on selected device capabilities
|
// Show/hide standby interval based on selected device capabilities
|
||||||
_updateStandbyVisibility();
|
_updateStandbyVisibility();
|
||||||
|
|
||||||
setTargetEditorInitialValues({
|
targetEditorModal.snapshot();
|
||||||
name: document.getElementById('target-editor-name').value,
|
targetEditorModal.open();
|
||||||
device: deviceSelect.value,
|
|
||||||
source: sourceSelect.value,
|
|
||||||
fps: document.getElementById('target-editor-fps').value,
|
|
||||||
interpolation: document.getElementById('target-editor-interpolation').value,
|
|
||||||
smoothing: document.getElementById('target-editor-smoothing').value,
|
|
||||||
standby_interval: document.getElementById('target-editor-standby-interval').value,
|
|
||||||
});
|
|
||||||
|
|
||||||
const modal = document.getElementById('target-editor-modal');
|
|
||||||
modal.style.display = 'flex';
|
|
||||||
lockBody();
|
|
||||||
setupBackdropClose(modal, closeTargetEditorModal);
|
|
||||||
|
|
||||||
document.getElementById('target-editor-error').style.display = 'none';
|
document.getElementById('target-editor-error').style.display = 'none';
|
||||||
setTimeout(() => document.getElementById('target-editor-name').focus(), 100);
|
setTimeout(() => document.getElementById('target-editor-name').focus(), 100);
|
||||||
@@ -122,30 +149,15 @@ export async function showTargetEditor(targetId = null) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isTargetEditorDirty() {
|
export function isTargetEditorDirty() {
|
||||||
return (
|
return targetEditorModal.isDirty();
|
||||||
document.getElementById('target-editor-name').value !== targetEditorInitialValues.name ||
|
|
||||||
document.getElementById('target-editor-device').value !== targetEditorInitialValues.device ||
|
|
||||||
document.getElementById('target-editor-source').value !== targetEditorInitialValues.source ||
|
|
||||||
document.getElementById('target-editor-fps').value !== targetEditorInitialValues.fps ||
|
|
||||||
document.getElementById('target-editor-interpolation').value !== targetEditorInitialValues.interpolation ||
|
|
||||||
document.getElementById('target-editor-smoothing').value !== targetEditorInitialValues.smoothing ||
|
|
||||||
document.getElementById('target-editor-standby-interval').value !== targetEditorInitialValues.standby_interval
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function closeTargetEditorModal() {
|
export async function closeTargetEditorModal() {
|
||||||
if (isTargetEditorDirty()) {
|
await targetEditorModal.close();
|
||||||
const confirmed = await showConfirm(t('modal.discard_changes'));
|
|
||||||
if (!confirmed) return;
|
|
||||||
}
|
|
||||||
forceCloseTargetEditorModal();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function forceCloseTargetEditorModal() {
|
export function forceCloseTargetEditorModal() {
|
||||||
document.getElementById('target-editor-modal').style.display = 'none';
|
targetEditorModal.forceClose();
|
||||||
document.getElementById('target-editor-error').style.display = 'none';
|
|
||||||
unlockBody();
|
|
||||||
setTargetEditorInitialValues({});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveTargetEditor() {
|
export async function saveTargetEditor() {
|
||||||
@@ -157,11 +169,9 @@ export async function saveTargetEditor() {
|
|||||||
const interpolation = document.getElementById('target-editor-interpolation').value;
|
const interpolation = document.getElementById('target-editor-interpolation').value;
|
||||||
const smoothing = parseFloat(document.getElementById('target-editor-smoothing').value);
|
const smoothing = parseFloat(document.getElementById('target-editor-smoothing').value);
|
||||||
const standbyInterval = parseFloat(document.getElementById('target-editor-standby-interval').value);
|
const standbyInterval = parseFloat(document.getElementById('target-editor-standby-interval').value);
|
||||||
const errorEl = document.getElementById('target-editor-error');
|
|
||||||
|
|
||||||
if (!name) {
|
if (!name) {
|
||||||
errorEl.textContent = t('targets.error.name_required');
|
targetEditorModal.showError(t('targets.error.name_required'));
|
||||||
errorEl.style.display = 'block';
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,12 +212,11 @@ export async function saveTargetEditor() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showToast(targetId ? t('targets.updated') : t('targets.created'), 'success');
|
showToast(targetId ? t('targets.updated') : t('targets.created'), 'success');
|
||||||
forceCloseTargetEditorModal();
|
targetEditorModal.forceClose();
|
||||||
await loadTargetsTab();
|
await loadTargetsTab();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving target:', error);
|
console.error('Error saving target:', error);
|
||||||
errorEl.textContent = error.message;
|
targetEditorModal.showError(error.message);
|
||||||
errorEl.style.display = 'block';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user