/* ============================================================================= * 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, 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], }), ], }); }