Files
Aislo/M02_MasterTemplete/StructOptions/M02_SO_Def_Spec.ts
T
eomsangdonandClaude Opus 5.5 3abd2bdfd4 feat(구조물표): 322-2 M02 표 화면 — 탭 셋(한눈 표 · 종류별 · 부품) · 반영 · 부품 · 알약 색 저장 · 목록 + enabled 함께 · 검사 짝(화면 = 서버 · 1-3 규칙 · 만든 뒤 고정) · 명세 폼 보임 따름 · 판 알림
- 한눈 표(M02_SO_Def_Table): 군 · 종류 ▲▼ · 이름 · 목록 · 자리꼴 · 정본(고정) · 횡단도 · 카드 이름 · 형상(+ 벽 기본 형태 · 물빼기) · 알약 색 · 3D · 검사 · 처음 값 = 흐림 「기본」 · 바꿈 = 진함 · [처음 값으로]
- 종류별: 반영 칸 + 명세 이름 나무 줄마다 보임(컨테이너 = 그 안 칸) · 못 숨김 · 미리보기 숨김 따름 · 「안 씀」 = 목록 B06 만
- 부품 탭(M02_SO_Def_Parts): 부품 · 읽는 칸 · 자리꼴 · 정본 · 쓰는 종류 · 켬/끔(정의 부품 = 끈 것만)
- 편집 손질(M02_SO_Def_Reflect): 처음 값이면 반영에서 지움 · [저장] 때 enabled = 목록 both 일 때만 참
- 검사 짝 M02_SO_Def_Check.ts ↔ M02_SO_Check.py(글까지 같음): 반영 · 부품 꼴 · 형상 none → 횡단도 none · 부품 자리꼴 · 정본 · 기본값 없는 읽는 칸 새로 숨김 금지(앞 판 숨김 그대로) · 종류 지우기 · 자리꼴 · 정본 · 군 글자 · 선택지 값 고정 · 서버 check(doc, 지금 판) · 앞 판 없으면 꼴만
- 명세 폼: 쓰는 옵션 키가 모두 숨긴 칸 · 상자는 안 그림(값 그대로) · B05 · B06 시설 · 구조물 명세 폼이 정의 「표시」를 따름
- 판 알림: GET 구조물 목록에 definition_version · structures.json 판 ≠ 지금 판이면 「구조물 정의가 판 N 으로 바뀜 — 저장하면 반영」 한 번
- 시험: test_322_2_check_mirror(거울 · 규칙 · 반영 손질 · 서버 저장 · 422) · 판 알림 · 옛 정의 화면 시험 = 선택지 값 대신 이름 표 · 못 숨김 아닌 칸

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
2026-10-14 15:40:38 +09:00

174 lines
7.0 KiB
TypeScript

/* =============================================================================
* M02_SO_Def_Spec.ts
* 구조물 명세가 있는 종류(PLAN 52 · `STRUCTURE_SPECS`)의 이름 고치기 칸(52-11) — 나무 꼴
* 컨테이너 제목 › 항목 이름 › 값 이름 · 이름만 고침 · 키 · 필수/선택 · 꼴은 읽기만 ·
* 아래 미리보기 = B05 · B06 이 그리는 같은 명세 폼(`createSpecForm`)에 고친 이름을 얹음.
* 명세 없는 종류(정의 그룹 꼴 · 52-12) = 그룹 제목 › 칸 이름 › 선택지 이름 · 미리보기 = B05 칸 부품
* (`field` · `optionControl` · 그룹 상자)으로 같은 차례.
* 구조물군 · 종류 이름은 왼쪽 목록 · 머리 칸이 그대로 고침.
* 줄마다 보임(322-2 · 정의 「표시」 · 컨테이너 = 그 안 칸 전부) · 부품이 읽는 칸 = 「못 숨김」(앞 판에서
* 이미 숨긴 칸은 다시 보이기만) · 미리보기도 숨김을 따름.
* ========================================================================== */
import { createTag, el } from "@ui/ui_template_elements";
import { createSpecForm } from "../../A00_Common/design/design_structure_form";
import { specItemKeys, type StructureSpec } from "../../A00_Common/design/design_structure_spec";
import { group } from "../../B05_Profile/B05_Profile_UI_Drainage_Facility_Fields";
import { field, optionControl } from "../../B05_Profile/B05_Profile_UI_Structures_Fields";
import type { StructDef, TypeDef } from "./M02_SO_Def_Model";
import {
groupNameRows,
setSpecName,
specNameRows,
type SpecNameRow,
} from "./M02_SO_Def_Spec_Model";
import { hiddenKeys, readKeys, setShown } from "./M02_SO_Def_Reflect";
import { checkBox, head, textBox } from "./M02_SO_Def_Ui";
import { to, type OptionsTextKey } from "./M02_SO_Text";
const TAG: Record<NonNullable<SpecNameRow["tag"]>, OptionsTextKey> = {
required: "SpecRequired",
optional: "SpecOptional",
select: "FormSelect",
number: "FormNumber",
text: "FormText",
auto: "FormAuto",
bool: "FormBool",
};
/** 정의 그룹 꼴 미리보기 — 그룹 밖 칸 · 그룹 상자(제목 = 그룹 이름) · 선택지 = 이름 표 글(값 그대로). */
function groupPreview(def: StructDef, type: TypeDef): HTMLElement {
const names = def.종류[type.type_id]?.이름 ?? {};
const hidden = hiddenKeys(def, type.type_id);
const options = type.options.filter((o) => o.enabled !== false && !hidden.has(o.key));
const cell = (o: (typeof options)[number]): HTMLElement =>
field(
o.unit ? `${o.label} (${o.unit})` : o.label,
optionControl(
{
input: o.input === "select" || o.input === "number" ? o.input : "text",
choices: o.choices ?? [],
default: null,
required: !!o.required,
},
undefined,
(c) => names[`${o.key}=${c}`] ?? c,
),
);
const groups = def.그룹[type.type_id] ?? [];
const inGroup = new Set(groups.flatMap((g) => g.옵션));
const grid = (cells: HTMLElement[]): HTMLElement =>
el("div", { className: "b05-structure__grid", children: cells });
return el("div", {
children: [
grid(options.filter((o) => !inGroup.has(o.key)).map(cell)),
...groups.map((g) => {
const box = group(g.이름);
box.body.append(grid(options.filter((o) => g.옵션.includes(o.key)).map(cell)));
return box.root;
}),
],
});
}
/** 이름 줄이 쓰는 정의 옵션 키 — 명세 상자 = 그 안 칸 전부 · 칸 = 저장 · 갈래 키 · 그룹 = 그 옵션들. */
function rowKeys(type: TypeDef, spec: StructureSpec | undefined, nameKey: string): string[] {
if (!nameKey || nameKey.includes("=")) return [];
if (!spec) {
if (!nameKey.startsWith("#")) return [nameKey];
return [];
}
const [cid, iid] = nameKey.split(".");
const c = spec.containers.find((x) => x.id === cid);
const items = (c?.items ?? []).filter((i) => !iid || i.id === iid);
const own = new Set(type.options.map((o) => o.key));
return items.flatMap(specItemKeys).filter((key) => own.has(key));
}
/** 줄 보임 칸 — 키 없는 줄은 비움 · 부품이 읽는 보이는 칸은 못 숨김. */
function shownBox(
opt: { def: StructDef; base: StructDef; type: TypeDef; edit: boolean; changed: () => void },
keys: string[],
): HTMLElement[] {
if (!keys.length) return [];
const { def, base, type, edit, changed } = opt;
const hidden = hiddenKeys(def, type.type_id);
const kept = hiddenKeys(base, type.type_id);
const read = readKeys(def, type.type_id);
const shown = keys.some((key) => !hidden.has(key));
const locked = shown && keys.some((key) => read.has(key) && !kept.has(key));
return [
checkBox(to("ShownCol"), shown, (v) => (setShown(def, type.type_id, keys, v), changed()), {
edit: edit && !locked,
attr: "data-sod-spec-shown",
}),
...(locked ? [createTag(to("CannotHide"), "neutral")] : []),
];
}
export function buildSpecBlock(opt: {
def: StructDef;
/** 지금 서버 판 — 이미 숨긴 칸(못 숨김 예외)을 가름 */
base: StructDef;
type: TypeDef;
/** 없으면 정의 그룹 꼴(52-12) */
spec: StructureSpec | undefined;
edit: boolean;
changed: () => void;
}): HTMLElement {
const { def, type, spec, edit, changed } = opt;
const rows = (spec ? specNameRows(def, spec) : groupNameRows(def, type)).map((row) =>
el("div", {
className: `m02-sod__specrow is-level-${row.level}`,
attrs: { "data-sod-spec": row.nameKey },
children: [
row.nameKey
? textBox(row.label, (v) => setSpecName(def, type.type_id, row, v), changed, {
edit,
attr: "data-sod-spec-name",
cls: "m02-sod__name",
})
: el("span", { text: to("SpecLoose") }),
el("small", { className: "m02-opts__key", text: row.key }),
...(row.tag ? [createTag(to(TAG[row.tag]), "neutral")] : []),
...shownBox(
opt,
row.group && !spec
? ((def.그룹[type.type_id] ?? []).find((g) => g.id === row.group)?.옵션 ?? [])
: rowKeys(type, spec, row.nameKey),
),
],
}),
);
// 미리보기 — 값 없는 새 구조물 폼 · 잇기 줄 없음(조합은 프로젝트 몫)
let preview: HTMLElement;
if (spec) {
const form = createSpecForm(null);
const groups = Object.fromEntries((def.그룹[type.type_id] ?? []).map((g) => [g.id, g.이름]));
form.write(
spec,
{},
def.종류[type.type_id]?.이름 ?? {},
groups,
{},
hiddenKeys(def, type.type_id),
);
preview = form.root;
} else preview = groupPreview(def, type);
preview.inert = true;
return el("div", {
className: "m02-sod__block",
attrs: { "data-sod-spec-block": "" },
children: [
...head(to("SpecHead"), to("SpecNote")),
...(rows.length ? rows : [el("p", { className: "m02-side__empty", text: to("SpecNone") })]),
...head(to("SpecPreview")),
el("div", {
className: "m02-sod__specpreview",
attrs: { "data-sod-spec-preview": "" },
children: [preview],
}),
],
});
}