/* ============================================================================= * app_settings.ts * ☰ [설정] 창(PLAN 332-2 · 요구 224-8) — 탭 셋: ① 마우스 ② CAD 단축키 ③ 다른 곳 단축키(읽기 전용). * * - 고침은 창 안 사본에만 · [저장] 때만 계정 pref(`input-prefs`)로 한 번 씀 — 읽기는 `input_prefs.ts` * - 저장 안 한 고침이 있으면 닫을 때 물음(askLeave) · 잘못 있는 별칭은 저장 안 함 * - 로그인 전엔 이 브라우저에만 남음(계정 올리기는 `b_page_state` 가 로그인 뒤에) * ========================================================================== */ import { createButton, el, showToast } from "@ui/ui_template_elements"; import { askLeave } from "@ui/ui_template_leave_dialog"; import { t as L } from "@ui/ui_template_locale"; import { openModal } from "@ui/ui_template_modal"; import { writeState } from "./b_page_state"; import { inputPrefs, type InputPrefs, type WheelZoom } from "./input_prefs"; import { buildCadAliasTab, checkCadAliases } from "./app_settings_cad"; import "./app_settings.css"; type LocaleKey = Parameters[0]; type TabId = "mouse" | "cad" | "keys"; const TABS: [TabId, LocaleKey][] = [ ["mouse", "App_Settings_Tab_Mouse"], ["cad", "App_Settings_Tab_Cad"], ["keys", "App_Settings_Tab_Keys"], ]; const WHEEL: [WheelZoom, LocaleKey][] = [ ["pull", "App_Settings_Wheel_Pull"], ["push", "App_Settings_Wheel_Push"], ]; const MOUSE_RULES: LocaleKey[] = [ "App_Settings_Rule_Pan", "App_Settings_Rule_Pick", "App_Settings_Rule_Right", "App_Settings_Rule_Pinch", ]; /** ③ 안내표 줄 — [자리, 키] */ const KEY_ROWS: [LocaleKey, LocaleKey][] = [ ["App_Settings_Keys_All", "App_Settings_Keys_All_Keys"], ["App_Settings_Keys_Profile", "App_Settings_Keys_Profile_Keys"], ["App_Settings_Keys_RouteEdit", "App_Settings_Keys_RouteEdit_Keys"], ["App_Settings_Keys_Section", "App_Settings_Keys_Section_Keys"], ["App_Settings_Keys_Cad", "App_Settings_Keys_Cad_Keys"], ["App_Settings_Keys_Table", "App_Settings_Keys_Table_Keys"], ["App_Settings_Keys_M02", "App_Settings_Keys_M02_Keys"], ]; /** 비교 · 저장 꼴 — 별칭 명령 차례를 맞춤 */ const normalize = (p: InputPrefs): InputPrefs => ({ wheelZoom: p.wheelZoom, cadAliases: Object.fromEntries(Object.entries(p.cadAliases).sort(([a], [b]) => (a < b ? -1 : 1))), }); const snapshot = (p: InputPrefs): string => JSON.stringify(normalize(p)); function mouseTab(draft: InputPrefs): HTMLElement { const group = el("div", { className: "app-settings__radios", attrs: { role: "radiogroup", "aria-label": L("App_Settings_Wheel") }, }); for (const [mode, key] of WHEEL) { const input = el("input", { attrs: { type: "radio", name: "app-settings-wheel", value: mode }, }); input.checked = draft.wheelZoom === mode; input.addEventListener("change", () => (draft.wheelZoom = mode)); group.append(el("label", { children: [input, L(key)] })); } return el("div", { children: [ el("h4", { text: L("App_Settings_Wheel") }), group, el("h4", { text: L("App_Settings_Rules") }), el("ul", { children: MOUSE_RULES.map((key) => el("li", { text: L(key) })) }), ], }); } function keysTab(): HTMLElement { const rows = KEY_ROWS.map(([place, keys]) => el("tr", { children: [el("th", { text: L(place) }), el("td", { text: L(keys) })] }), ); return el("table", { className: "app-settings__table", children: [el("tbody", { children: rows })], }); } /** ☰ [설정] — 창을 열고 지금 설정을 사본으로 고침 */ export function openAppSettings(): void { const draft = normalize(inputPrefs()); let saved = snapshot(draft); const cad = buildCadAliasTab(draft.cadAliases); const panels: Record = { mouse: mouseTab(draft), cad: cad.root, keys: keysTab(), }; const tabs = el("div", { className: "app-settings__tabs ui-subtabs", attrs: { role: "tablist" }, }); const show = (id: TabId): void => { tabs.replaceChildren( ...TABS.map(([tab, key]) => { const button = createButton({ label: L(key), variant: tab === id ? "filled" : "ghost", size: "sm", onClick: () => show(tab), }); button.setAttribute("role", "tab"); button.setAttribute("aria-selected", String(tab === id)); button.dataset.settingsTab = tab; return button; }), ); for (const [tab, panel] of Object.entries(panels)) panel.hidden = tab !== id; }; /** [저장] — 잘못 있는 별칭이면 CAD 탭을 보이고 안 씀 */ const save = (): boolean => { const errors = checkCadAliases(draft.cadAliases); cad.mark(errors); if (Object.keys(errors).length) { show("cad"); showToast(L("App_Settings_Cad_Invalid"), "error"); return false; } if (snapshot(draft) !== saved) writeState("input-prefs", normalize(draft)); saved = snapshot(draft); showToast(L("App_Settings_Saved"), "success"); return true; }; const saveButton = createButton({ label: L("Common_Btn_Save"), variant: "filled", size: "sm", onClick: () => void save(), }); openModal({ title: L("App_Settings"), closeLabel: L("Common_Btn_Close"), dialogClass: "app-settings", actions: [saveButton], beforeClose: async () => { if (snapshot(draft) === saved) return true; const choice = await askLeave(L("App_Settings_Leave")); return choice === "discard" || (choice === "save" && save()); }, mount: (body) => { show("mouse"); body.append(tabs, ...Object.values(panels)); }, }); }