- app_shell ☰ 설정 묶음에 [설정] 단추(서랍 닫고 창은 처음 누를 때 불러옴) - 새 app_settings.ts(틀 · 탭 셋 · [저장] · askLeave) · app_settings_cad.ts(명령 표 · 내 별칭 · 검사) · app_settings.css - 새 openwebcad/src/commands/command-catalog.json(명령 153 · 한글 이름 · 기본 별칭) + 거울 시험 - locale 문구(ui_template_locale_common) - 시험: test_app_settings · cad/command-catalog 새로 · test_device_audit · test_touch_bridge 화면 주소 환경값(AISLO_UI_BASE · AISLO_UI_PROJECT) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy
160 lines
5.6 KiB
TypeScript
160 lines
5.6 KiB
TypeScript
/* =============================================================================
|
|
* 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<typeof L>[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<TabId, HTMLElement> = {
|
|
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));
|
|
},
|
|
});
|
|
}
|