터치 디스플레이 설정 관리 추가

This commit is contained in:
seo
2026-07-23 00:48:55 +09:00
parent 01cc393ebe
commit 923f886c46
4 changed files with 486 additions and 6 deletions
+149 -2
View File
@@ -199,6 +199,25 @@
settingsSaveFailed: 'settings save failed',
settingsResetConfirm: 'Reset all Control settings to defaults?',
settingsDefault: 'default',
touchDisplaySettings: 'Touch Display',
touchDisplayStatus: 'Status',
touchDisplayDetected: 'Detected',
touchDisplayConnected: 'Connected',
touchDisplayMode: 'Mode',
touchDisplayFramebuffer: 'Framebuffer',
touchDisplayOverlay: 'Manual overlay',
touchDisplayAutoDetect: 'Auto detect',
touchDisplayEnabled: 'LCD output',
touchInputEnabled: 'Touch input',
touchRotation: 'Console rotation',
touchInvx: 'Invert X',
touchInvy: 'Invert Y',
touchSwapxy: 'Swap X/Y',
touchSizeX: 'Touch X size',
touchSizeY: 'Touch Y size',
touchDisplaySave: 'Save display boot settings',
touchDisplaySaved: 'Display settings saved. Reboot is required.',
touchDisplayNoChange: 'Display settings already match.',
rebootButton: 'Reboot',
dialogAlert: 'Alert',
dialogConfirm: 'Confirm',
@@ -351,6 +370,25 @@
settingsSaveFailed: '설정 저장 실패',
settingsResetConfirm: 'Control 설정을 모두 기본값으로 되돌릴까요?',
settingsDefault: '기본값',
touchDisplaySettings: '터치 디스플레이',
touchDisplayStatus: '상태',
touchDisplayDetected: '인식',
touchDisplayConnected: '연결',
touchDisplayMode: '모드',
touchDisplayFramebuffer: '프레임버퍼',
touchDisplayOverlay: '수동 overlay',
touchDisplayAutoDetect: '자동 감지',
touchDisplayEnabled: 'LCD 출력',
touchInputEnabled: '터치 입력',
touchRotation: '콘솔 회전',
touchInvx: '터치 X 반전',
touchInvy: '터치 Y 반전',
touchSwapxy: '터치 X/Y 교환',
touchSizeX: '터치 X 크기',
touchSizeY: '터치 Y 크기',
touchDisplaySave: '디스플레이 부팅 설정 저장',
touchDisplaySaved: '디스플레이 설정을 저장했습니다. 재부팅이 필요합니다.',
touchDisplayNoChange: '디스플레이 설정이 이미 동일합니다.',
rebootButton: '재부팅',
dialogAlert: '알림',
dialogConfirm: '확인',
@@ -645,6 +683,85 @@
return `setting-${String(key).replace(/[^A-Za-z0-9_-]/g, '-')}`;
}
function displayDomId(key) {
return `touch-display-${String(key).replace(/[^A-Za-z0-9_-]/g, '-')}`;
}
function boolLabel(value) {
return value ? 'ON' : 'OFF';
}
function renderTouchDisplaySettings(display) {
if (!display || !display.config) return '';
const config = display.config || {};
const statusRows = [
[t('touchDisplayDetected'), boolLabel(!!display.detected)],
[t('touchDisplayConnected'), `${display.status || 'unknown'} / ${display.enabled_state || 'unknown'} / ${display.dpms || 'unknown'}`],
[t('touchDisplayMode'), display.current_mode || '-'],
[t('touchDisplayFramebuffer'), display.framebuffer || '-'],
[t('touchDisplayAutoDetect'), boolLabel(!!config.display_auto_detect)],
[t('touchDisplayOverlay'), boolLabel(!!config.manual_overlay)],
];
const checkboxField = (key, label, checked) => {
const id = escapeHtml(displayDomId(key));
return `
<div class="settings-field">
<span class="settings-label"><span>${escapeHtml(label)}</span><span></span></span>
<span class="settings-control">
<span class="settings-toggle">
<input id="${id}" data-display-key="${escapeHtml(key)}" type="checkbox" value="1" autocomplete="off" ${checked ? 'checked' : ''}>
<span class="settings-toggle-on">켜기</span>
<span class="settings-toggle-off">끄기</span>
</span>
</span>
</div>
`;
};
const numberField = (key, label, value, min, max, step = 1) => `
<div class="settings-field">
<span class="settings-label"><span>${escapeHtml(label)}</span><span></span></span>
<span class="settings-control">
<input id="${escapeHtml(displayDomId(key))}" data-display-key="${escapeHtml(key)}" type="number" min="${min}" max="${max}" step="${step}" autocomplete="off" value="${escapeHtml(value)}">
</span>
</div>
`;
return `
<section class="settings-group touch-display-settings">
<h4>${escapeHtml(t('touchDisplaySettings'))}</h4>
<div class="touch-display-status">
${statusRows.map(([key, value]) => `
<div><span>${escapeHtml(key)}</span><strong>${escapeHtml(value)}</strong></div>
`).join('')}
</div>
<div class="settings-grid">
<div class="settings-field">
<span class="settings-label"><span>${escapeHtml(t('touchRotation'))}</span><span>deg</span></span>
<span class="settings-control">
<select data-display-key="rotation" class="settings-select" autocomplete="off">
${[0, 90, 180, 270].map(value => `
<option value="${value}" ${Number(config.rotation || 0) === value ? 'selected' : ''}>${value}</option>
`).join('')}
</select>
</span>
</div>
${checkboxField('manual_overlay', t('touchDisplayOverlay'), !!config.manual_overlay)}
${checkboxField('display_enabled', t('touchDisplayEnabled'), !!config.display_enabled)}
${checkboxField('touch_enabled', t('touchInputEnabled'), !!config.touch_enabled)}
${checkboxField('touch_invx', t('touchInvx'), !!config.touch_invx)}
${checkboxField('touch_invy', t('touchInvy'), !!config.touch_invy)}
${checkboxField('touch_swapxy', t('touchSwapxy'), !!config.touch_swapxy)}
${numberField('touch_sizex', t('touchSizeX'), config.touch_sizex || 800, 100, 4096)}
${numberField('touch_sizey', t('touchSizeY'), config.touch_sizey || 480, 100, 4096)}
</div>
<div class="settings-inline-actions">
<button id="touchDisplaySave" class="btn" type="button">${escapeHtml(t('touchDisplaySave'))}</button>
<span class="settings-note">/boot/firmware/config.txt, /boot/firmware/cmdline.txt</span>
</div>
</section>
`;
}
function renderSettings(payload = state.settingsPayload, options = {}) {
if (!els.settingsBody || !payload) return;
if (state.settingsOpen && state.settingsDirty && !options.force) return;
@@ -718,7 +835,7 @@
return item.default ?? '';
};
els.settingsBody.innerHTML = Object.entries(byGroup).map(([group, items]) => `
els.settingsBody.innerHTML = renderTouchDisplaySettings(payload.touch_display) + Object.entries(byGroup).map(([group, items]) => `
<section class="settings-group">
<h4>${escapeHtml(groups[group] || group)}</h4>
<div class="settings-grid">
@@ -737,7 +854,7 @@
</div>
</section>
`).join('');
els.settingsBody.querySelectorAll('[data-setting-key]').forEach(input => {
els.settingsBody.querySelectorAll('[data-setting-key],[data-display-key]').forEach(input => {
input.addEventListener('input', () => {
state.settingsDirty = true;
});
@@ -757,6 +874,7 @@
input.dispatchEvent(new Event('change'));
});
});
$('#touchDisplaySave', els.settingsBody)?.addEventListener('click', saveTouchDisplaySettings);
}
function settingByKey(key, fallback = null) {
@@ -797,6 +915,35 @@
return values;
}
function currentTouchDisplayFormValues() {
const values = {};
els.settingsBody?.querySelectorAll('[data-display-key]').forEach(input => {
values[input.dataset.displayKey] = input.type === 'checkbox'
? (input.checked ? '1' : '0')
: input.value;
});
return values;
}
async function saveTouchDisplaySettings() {
if (!await window.customConfirm('디스플레이 부팅 설정을 저장할까요? 저장 후 재부팅해야 적용됩니다.', { title: t('touchDisplaySettings') })) {
return;
}
try {
const data = await api('touch_display_save', {
display: JSON.stringify(currentTouchDisplayFormValues()),
});
state.settingsDirty = false;
render(data);
renderSettings(data.settings, { force: true });
const changed = data.touch_display_save?.changed || [];
notice(changed.length ? t('touchDisplaySaved') : t('touchDisplayNoChange'), 'success');
} catch (e) {
console.error(e);
notice(e.message || t('settingsSaveFailed'), 'error');
}
}
async function saveSettings() {
try {
const data = await api('settings_save', {