Control 설정 모달 입력 보존 개선
This commit is contained in:
+41
-20
@@ -86,6 +86,7 @@
|
||||
batteryTooltipText: '',
|
||||
settingsPayload: null,
|
||||
settingsOpen: false,
|
||||
settingsDirty: false,
|
||||
};
|
||||
|
||||
const storageKeys = {
|
||||
@@ -521,9 +522,7 @@
|
||||
cleanup(true);
|
||||
};
|
||||
const onCancel = () => cleanup(mode === 'confirm' ? false : null);
|
||||
const onLayerClick = event => {
|
||||
if (event.target === layer) onCancel();
|
||||
};
|
||||
const onLayerClick = () => {};
|
||||
const onKeydown = event => {
|
||||
if (event.key === 'Escape') {
|
||||
event.preventDefault();
|
||||
@@ -606,8 +605,13 @@
|
||||
return json.data;
|
||||
}
|
||||
|
||||
function renderSettings(payload = state.settingsPayload) {
|
||||
function settingDomId(key) {
|
||||
return `setting-${String(key).replace(/[^A-Za-z0-9_-]/g, '-')}`;
|
||||
}
|
||||
|
||||
function renderSettings(payload = state.settingsPayload, options = {}) {
|
||||
if (!els.settingsBody || !payload) return;
|
||||
if (state.settingsOpen && state.settingsDirty && !options.force) return;
|
||||
state.settingsPayload = payload;
|
||||
const groups = payload.groups || {};
|
||||
const byGroup = {};
|
||||
@@ -618,21 +622,23 @@
|
||||
});
|
||||
const fieldHtml = item => {
|
||||
const key = escapeHtml(item.key);
|
||||
const id = escapeHtml(settingDomId(item.key));
|
||||
const value = item.value ?? item.default ?? '';
|
||||
const type = item.type || 'number';
|
||||
if (type === 'boolean') {
|
||||
const checked = value === true || value === 1 || value === '1' || value === 'true';
|
||||
return `
|
||||
<span class="settings-toggle">
|
||||
<input data-setting-key="${key}" type="checkbox" value="1" ${checked ? 'checked' : ''}>
|
||||
<span>${checked ? '켜기' : '끄기'}</span>
|
||||
<input id="${id}" name="${key}" data-setting-key="${key}" type="checkbox" value="1" autocomplete="off" ${checked ? 'checked' : ''}>
|
||||
<span class="settings-toggle-on">켜기</span>
|
||||
<span class="settings-toggle-off">끄기</span>
|
||||
</span>
|
||||
`;
|
||||
}
|
||||
if (type === 'select') {
|
||||
const options = item.options || {};
|
||||
return `
|
||||
<select data-setting-key="${key}" class="settings-select">
|
||||
<select id="${id}" name="${key}" data-setting-key="${key}" class="settings-select" autocomplete="off">
|
||||
${Object.entries(options).map(([optionValue, label]) => `
|
||||
<option value="${escapeHtml(optionValue)}" ${String(optionValue) === String(value) ? 'selected' : ''}>${escapeHtml(label)}</option>
|
||||
`).join('')}
|
||||
@@ -642,19 +648,25 @@
|
||||
if (type === 'text') {
|
||||
return `
|
||||
<input
|
||||
id="${id}"
|
||||
name="${key}"
|
||||
data-setting-key="${key}"
|
||||
type="text"
|
||||
maxlength="${escapeHtml(item.max_length ?? 255)}"
|
||||
autocomplete="off"
|
||||
value="${escapeHtml(value)}">
|
||||
`;
|
||||
}
|
||||
return `
|
||||
<input
|
||||
id="${id}"
|
||||
name="${key}"
|
||||
data-setting-key="${key}"
|
||||
type="number"
|
||||
min="${escapeHtml(item.min ?? '')}"
|
||||
max="${escapeHtml(item.max ?? '')}"
|
||||
step="${escapeHtml(item.step ?? '1')}"
|
||||
autocomplete="off"
|
||||
value="${escapeHtml(value)}">
|
||||
`;
|
||||
};
|
||||
@@ -675,7 +687,7 @@
|
||||
<h4>${escapeHtml(groups[group] || group)}</h4>
|
||||
<div class="settings-grid">
|
||||
${items.map(item => `
|
||||
<label class="settings-field">
|
||||
<div class="settings-field">
|
||||
<span class="settings-label">
|
||||
<span>${escapeHtml(item.label || item.key)}</span>
|
||||
<span>${escapeHtml(item.unit || '')}</span>
|
||||
@@ -684,17 +696,27 @@
|
||||
${fieldHtml(item)}
|
||||
<span class="settings-default">${t('settingsDefault')} ${escapeHtml(defaultValueText(item))}</span>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</section>
|
||||
`).join('');
|
||||
els.settingsBody.querySelectorAll('[data-setting-key]').forEach(input => {
|
||||
input.addEventListener('input', () => {
|
||||
state.settingsDirty = true;
|
||||
});
|
||||
input.addEventListener('change', () => {
|
||||
state.settingsDirty = true;
|
||||
});
|
||||
});
|
||||
els.settingsBody.querySelectorAll('.settings-toggle input').forEach(input => {
|
||||
input.addEventListener('change', () => {
|
||||
const label = input.closest('.settings-toggle')?.querySelector('span');
|
||||
if (label) label.textContent = input.checked ? '켜기' : '끄기';
|
||||
const toggle = input.closest('.settings-toggle');
|
||||
if (toggle) toggle.dataset.checked = input.checked ? '1' : '0';
|
||||
});
|
||||
input.closest('.settings-toggle')?.addEventListener('click', () => {
|
||||
const toggle = input.closest('.settings-toggle');
|
||||
toggle?.setAttribute('data-checked', input.checked ? '1' : '0');
|
||||
toggle?.addEventListener('click', () => {
|
||||
input.checked = !input.checked;
|
||||
input.dispatchEvent(new Event('change'));
|
||||
});
|
||||
@@ -716,6 +738,7 @@
|
||||
return;
|
||||
}
|
||||
state.settingsOpen = true;
|
||||
state.settingsDirty = false;
|
||||
els.settingsDialog.dataset.open = '1';
|
||||
els.settingsDialog.setAttribute('aria-hidden', 'false');
|
||||
}
|
||||
@@ -723,6 +746,7 @@
|
||||
function closeSettings() {
|
||||
if (!els.settingsDialog) return;
|
||||
state.settingsOpen = false;
|
||||
state.settingsDirty = false;
|
||||
delete els.settingsDialog.dataset.open;
|
||||
els.settingsDialog.setAttribute('aria-hidden', 'true');
|
||||
}
|
||||
@@ -742,8 +766,9 @@
|
||||
const data = await api('settings_save', {
|
||||
settings: JSON.stringify(currentSettingsFormValues()),
|
||||
});
|
||||
state.settingsDirty = false;
|
||||
render(data);
|
||||
renderSettings(data.settings);
|
||||
renderSettings(data.settings, { force: true });
|
||||
notice(t('settingsSaved'), 'success');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -757,8 +782,9 @@
|
||||
}
|
||||
try {
|
||||
const data = await api('settings_reset', {});
|
||||
state.settingsDirty = false;
|
||||
render(data);
|
||||
renderSettings(data.settings);
|
||||
renderSettings(data.settings, { force: true });
|
||||
notice(t('settingsResetDone'), 'success');
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -1809,7 +1835,7 @@
|
||||
function render(data) {
|
||||
if (data.settings) {
|
||||
state.settingsPayload = data.settings;
|
||||
if (state.settingsOpen) {
|
||||
if (!state.settingsOpen) {
|
||||
renderSettings(data.settings);
|
||||
}
|
||||
}
|
||||
@@ -2062,11 +2088,6 @@
|
||||
els.settingsClose?.addEventListener('click', closeSettings);
|
||||
els.settingsSave?.addEventListener('click', saveSettings);
|
||||
els.settingsReset?.addEventListener('click', resetSettings);
|
||||
els.settingsDialog?.addEventListener('click', event => {
|
||||
if (event.target === els.settingsDialog) {
|
||||
closeSettings();
|
||||
}
|
||||
});
|
||||
els.translateBtn?.addEventListener('click', () => {
|
||||
applyLang(state.lang === 'ko' ? 'en' : 'ko');
|
||||
refreshStatus();
|
||||
|
||||
Reference in New Issue
Block a user