Files
Aislo/A00_Common/app_settings_cad.ts
T
eomsangdonandClaude Opus 5.5 aaecfa3438 feat(설정): 332-2 ☰ [설정] 창 — 마우스 · CAD 단축키 · 다른 곳 단축키 탭 · [저장] 때만 계정 pref · 닫을 때 물음
- 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
2026-10-15 12:45:03 +09:00

119 lines
4.4 KiB
TypeScript

/* =============================================================================
* 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<AliasError, Parameters<typeof L>[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<string, string[]>,
commands: CadCommandRow[] = CAD_COMMANDS,
): Record<string, AliasError> {
const ids = new Set(commands.map((c) => c.id));
const owner = new Map<string, string>();
const errors: Record<string, AliasError> = {};
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<string, string[]>): {
root: HTMLElement;
mark: (errors: Record<string, AliasError>) => void;
} {
const message = el("p", { className: "app-settings__error", attrs: { role: "alert" } });
const inputs = new Map<string, HTMLInputElement>();
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<string, AliasError>): 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 };
}