Files
Aislo/M02_MasterTemplete/StructOptions/M02_SO_Def_Spec_Model.ts
T
eomsangdonandClaude Opus 5.5 2471a14a62 feat(M02): 구조물 옵션 정의 = 정의 그룹 꼴 종류도 이름 나무 · 옛 편집기 모든 종류에서 숨김(52-12)
- 명세 없는 종류 = 그룹 제목(그룹.이름) › 칸 이름(옵션 label) › 선택지 이름(종류.이름 `{옵션}={값}` · 값 그대로) · 그룹 밖 칸은 머리 줄 아래
- 칸 더하기 · 빼기 · 꼴 · 필수는 읽기만 · 칸 없는 종류(개거 · 노출형 횡단수로)는 「고칠 이름 없음」 · 종류 이름은 머리 칸
- 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점)는 OLD_EDITOR 거짓으로 숨김(코드 그대로)
- 미리보기 = B05 칸 부품(field · optionControl · 그룹 상자)으로 같은 차례
- B05 · B06 고르기 칸 보기 글 = 정의 이름 표(값 그대로 · 규칙 선택지 · 시설 폼 덧보기 포함)
- 시험: test_m02_so_def_spec 정의 그룹 꼴 더함 · 헬퍼가 이웃 TS 를 읽게 고침 · ui_checks/52-11 에 맹암거 · 바닥막이 · 개거 더함

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UoSr2sqvYtBf1dtQdus88s
2026-10-05 23:59:41 +09:00

158 lines
5.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* =============================================================================
* M02_SO_Def_Spec_Model.ts
* 구조물 명세(PLAN 52 · `design_structure_spec`) 이름 고치기(52-11)의 자료 손질 — 화면 그리기 없음.
*
* · 명세 = 구조 정본(키 · 컨테이너 · 필수/선택 · 꼴) — 여기서는 **이름만** 고침
* · 이름 자리 = 정의 `종류.{종류}.이름`(키 `{컨테이너}` · `{컨테이너}.{항목}` · `{컨테이너}.{항목}={값}`)
* · 필수 컨테이너 제목 = 이름 표에 없으면 정의 그룹(`그룹.{종류}` 의 `id = link`) 이름 — B05 그리기
* (`containerTitle`)와 같은 차례로 읽고 그 자리에 씀
* · 명세 없는 종류(정의 그룹 꼴 · 52-12) = 그룹 제목(`그룹.이름`) › 칸 이름(옵션 `label`) ›
* 선택지 이름(이름 표 키 `{옵션}={값}` · 값은 그대로) — 그룹 밖 칸은 맨 앞 머리 줄 아래
* import 는 꼴만 — 시험이 이 파일만 옮겨 돌림.
* ========================================================================== */
import type { StructureSpec } from "../../A00_Common/design/design_structure_spec";
import type { StructDef, TypeDef } from "./M02_SO_Def_Model";
export interface SpecNameRow {
/** 0 = 컨테이너 · 1 = 항목 · 2 = 값 */
level: 0 | 1 | 2;
/** 이름 표 키 — 빈 글 = 고칠 이름 없는 머리 줄(그룹 밖 칸) */
nameKey: string;
/** 읽기만 표시 — 컨테이너 id · 항목 id · 값 */
key: string;
/** 지금 이름 */
label: string;
/** 읽기만 표시 — 필수/선택 · 꼴 */
tag: "required" | "optional" | "select" | "number" | "auto" | "text" | "bool" | null;
/** 필수 컨테이너 제목이 정의 그룹 이름에서 옴 — 고치면 그 그룹 이름에 씀 */
group: string | null;
/** 정의 옵션 이름(`label`)에서 옴 — 고치면 그 옵션 이름에 씀 */
option: string | null;
}
/** 명세 차례대로 이름 줄 — 같은 이름 키(갈래마다 겹친 값)는 한 줄. */
export function specNameRows(def: StructDef, spec: StructureSpec): SpecNameRow[] {
const names = def.종류[spec.typeId]?.이름 ?? {};
const groups = def.그룹[spec.typeId] ?? [];
const rows: SpecNameRow[] = [];
const seen = new Set<string>();
const push = (row: SpecNameRow): void => {
if (seen.has(row.nameKey)) return;
seen.add(row.nameKey);
rows.push(row);
};
for (const c of spec.containers) {
const group =
c.required && names[c.id] === undefined ? groups.find((g) => g.id === c.link) : undefined;
push({
level: 0,
nameKey: c.id,
key: c.id,
label: names[c.id] ?? group?.이름 ?? c.title,
tag: c.required ? "required" : "optional",
group: group ? group.id : null,
option: null,
});
for (const item of c.items) {
const itemKey = `${c.id}.${item.id}`;
push({
level: 1,
nameKey: itemKey,
key: item.id,
label: names[itemKey] ?? item.label,
tag: item.kind,
group: null,
option: null,
});
const choices = [
...(item.choices ?? []),
...Object.values(item.by?.map ?? {}).flatMap((b) => b.choices ?? []),
];
for (const ch of choices) {
const choiceKey = `${itemKey}=${ch.value}`;
push({
level: 2,
nameKey: choiceKey,
key: ch.value,
label: names[choiceKey] ?? ch.label,
tag: null,
group: null,
option: null,
});
}
}
}
return rows;
}
/** 정의 그룹 꼴 종류(명세 없음) 이름 줄 — 그룹 밖 칸 · 그룹마다 제목 › 칸 › 선택지(B05 그리기 차례). */
export function groupNameRows(def: StructDef, type: TypeDef): SpecNameRow[] {
const names = def.종류[type.type_id]?.이름 ?? {};
const options = type.options.filter((o) => o.enabled !== false);
const groups = def.그룹[type.type_id] ?? [];
const inGroup = new Set(groups.flatMap((g) => g.옵션));
const rows: SpecNameRow[] = [];
const optionRows = (keys: readonly string[]): void => {
for (const o of options.filter((x) => keys.includes(x.key))) {
rows.push({
level: 1,
nameKey: o.key,
key: o.key,
label: o.label,
tag: o.input,
group: null,
option: o.key,
});
for (const ch of o.input === "select" ? (o.choices ?? []) : []) {
const nameKey = `${o.key}=${ch}`;
rows.push({
level: 2,
nameKey,
key: ch,
label: names[nameKey] ?? ch,
tag: null,
group: null,
option: null,
});
}
}
};
const loose = options.filter((o) => !inGroup.has(o.key)).map((o) => o.key);
if (loose.length) {
rows.push({ level: 0, nameKey: "", key: "", label: "", tag: null, group: null, option: null });
optionRows(loose);
}
for (const g of groups) {
rows.push({
level: 0,
nameKey: `#${g.id}`,
key: g.id,
label: g.이름,
tag: null,
group: g.id,
option: null,
});
optionRows(g.옵션);
}
return rows;
}
/** 이름 하나를 사본에 씀 — 그룹에서 온 제목은 그 그룹 이름 · 옵션 칸은 옵션 이름 · 나머지는 이름 표. */
export function setSpecName(def: StructDef, typeId: string, row: SpecNameRow, value: string): void {
const option = row.option
? def.types.find((t) => t.type_id === typeId)?.options.find((o) => o.key === row.option)
: undefined;
if (option) {
option.label = value;
return;
}
const group = row.group ? def.그룹[typeId]?.find((g) => g.id === row.group) : undefined;
if (group) {
group.이름 = value;
return;
}
const kind = (def.종류[typeId] ??= {});
(kind.이름 ??= {})[row.nameKey] = value;
}