/* ============================================================================= * app_settings_cad.ts * ☰ [설정] ② CAD 단축키 탭(PLAN 332-2) — 명령 표(명령 · 한글 이름 · 기본 별칭 · 내 별칭) · 검사. * * - 명령 목록 = `openwebcad/src/commands/command-catalog.json`(registry 거울 · commands.*.ts 는 xstate 가 딸려 와서 JSON 한 벌) * - acad.pgp 식 덮어쓰기 — CAD 는 명령 이름 > 내 별칭 > 기본 별칭 순서로 찾음(배선은 332-3) * - 검사 = 영문 · 숫자만 · 내 별칭끼리 겹침 금지 · 명령 이름과 같은 별칭 금지 * ========================================================================== */ import catalog from "../B07_DesignDetail/openwebcad/src/commands/command-catalog.json"; import { el } from "@ui/ui_template_elements"; import { t as L } from "@ui/ui_template_locale"; export interface CadCommandRow { id: string; label: string; aliases: string[]; } export const CAD_COMMANDS = catalog as CadCommandRow[]; export type AliasError = "chars" | "dup" | "name"; const ERROR_TEXT: Record[0]> = { chars: "App_Settings_Cad_ErrChars", dup: "App_Settings_Cad_ErrDup", name: "App_Settings_Cad_ErrName", }; /** 칸 글 → 별칭 목록(쉼표 · 빈칸으로 가름 · 대문자) */ export const parseAliases = (text: string): string[] => text .toUpperCase() .split(/[\s,]+/) .filter(Boolean); /** 내 별칭 검사 — `{명령: 첫 잘못}` · 비면 통과 */ export function checkCadAliases( aliases: Record, commands: CadCommandRow[] = CAD_COMMANDS, ): Record { const ids = new Set(commands.map((c) => c.id)); const owner = new Map(); const errors: Record = {}; for (const [id, list] of Object.entries(aliases)) { for (const alias of list) { const first = owner.get(alias); const error: AliasError | null = !/^[A-Z0-9]+$/.test(alias) ? "chars" : ids.has(alias) ? "name" : first !== undefined ? "dup" : null; if (!error) owner.set(alias, id); else { errors[id] ??= error; if (first !== undefined) errors[first] ??= "dup"; } } } return errors; } /** 탭 몸 — `aliases` 를 칸 입력대로 고쳐 씀 · `mark` 로 잘못 칸 표시 */ export function buildCadAliasTab(aliases: Record): { root: HTMLElement; mark: (errors: Record) => void; } { const message = el("p", { className: "app-settings__error", attrs: { role: "alert" } }); const inputs = new Map(); const rows = CAD_COMMANDS.map((command) => { const input = el("input", { className: "ui-input ui-input--sm", attrs: { type: "text", "aria-label": `${command.id} ${L("App_Settings_Cad_Mine")}` }, }); input.value = (aliases[command.id] ?? []).join(", "); input.dataset.cadCommand = command.id; input.addEventListener("input", () => { const list = parseAliases(input.value); if (list.length) aliases[command.id] = list; else delete aliases[command.id]; }); inputs.set(command.id, input); const cells = [command.id, command.label, command.aliases.join(", ")].map((text) => el("td", { text }), ); return el("tr", { children: [...cells, el("td", { children: [input] })] }); }); const head = el("tr", { children: ( [ "App_Settings_Cad_Command", "App_Settings_Cad_Label", "App_Settings_Cad_Default", "App_Settings_Cad_Mine", ] as const ).map((key) => el("th", { text: L(key) })), }); const table = el("table", { className: "app-settings__table", children: [el("thead", { children: [head] }), el("tbody", { children: rows })], }); const mark = (errors: Record): void => { for (const [id, input] of inputs) { const error = errors[id]; if (error) input.setAttribute("aria-invalid", "true"); else input.removeAttribute("aria-invalid"); input.title = error ? L(ERROR_TEXT[error]) : ""; } const kinds = [...new Set(Object.values(errors))]; message.textContent = kinds.map((k) => L(ERROR_TEXT[k])).join(" · "); }; const root = el("div", { children: [el("p", { text: L("App_Settings_Cad_Help") }), message, table], }); return { root, mark }; }