- 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
119 lines
4.4 KiB
TypeScript
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 };
|
|
}
|