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
This commit is contained in:
@@ -32,6 +32,9 @@ import { checkBox, textBox } from "./M02_SO_Def_Ui";
|
||||
import { buildGroupBlock } from "./M02_SO_Groups";
|
||||
import { to } from "./M02_SO_Text";
|
||||
|
||||
/** 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점) — 모든 종류에서 숨김(52-12 · 정의 화면 = 이름만) */
|
||||
const OLD_EDITOR = false;
|
||||
|
||||
export interface DefScreenOptions {
|
||||
/** 시스템 관리자 — 아니면 읽기만 */
|
||||
isAdmin: boolean;
|
||||
@@ -184,7 +187,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
}
|
||||
const edit = canEdit();
|
||||
const extra = (): NonNullable<StructDef["종류"][string]> => (d.종류[type.type_id] ??= {});
|
||||
// 명세가 있는 종류(PLAN 52) = 이름만 고침 — 옛 편집기(옵션 · 배치 · 그룹 · 규칙 · 기준 측점)는 숨김
|
||||
// 이름만 고침(PLAN 52-11 · 52-12) — 명세 종류 = 명세 나무 · 나머지 = 정의 그룹 나무
|
||||
const spec = STRUCTURE_SPECS[type.type_id];
|
||||
detail.replaceChildren(
|
||||
el("div", {
|
||||
@@ -200,7 +203,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
edit,
|
||||
attr: "data-sod-kind-off",
|
||||
}),
|
||||
...(spec
|
||||
...(!OLD_EDITOR
|
||||
? []
|
||||
: [
|
||||
checkBox(
|
||||
@@ -221,7 +224,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
}),
|
||||
],
|
||||
}),
|
||||
...(spec
|
||||
...(!OLD_EDITOR
|
||||
? [buildSpecBlock({ def: d, type, spec, edit, changed })]
|
||||
: [
|
||||
buildOptionsBlock({ def: d, type, edit, open, changed }),
|
||||
|
||||
@@ -3,14 +3,23 @@
|
||||
* 구조물 명세가 있는 종류(PLAN 52 · `STRUCTURE_SPECS`)의 이름 고치기 칸(52-11) — 나무 꼴
|
||||
* 컨테이너 제목 › 항목 이름 › 값 이름 · 이름만 고침 · 키 · 필수/선택 · 꼴은 읽기만 ·
|
||||
* 아래 미리보기 = B05 · B06 이 그리는 같은 명세 폼(`createSpecForm`)에 고친 이름을 얹음.
|
||||
* 명세 없는 종류(정의 그룹 꼴 · 52-12) = 그룹 제목 › 칸 이름 › 선택지 이름 · 미리보기 = B05 칸 부품
|
||||
* (`field` · `optionControl` · 그룹 상자)으로 같은 차례.
|
||||
* 구조물군 · 종류 이름은 왼쪽 목록 · 머리 칸이 그대로 고침.
|
||||
* ========================================================================== */
|
||||
|
||||
import { createTag, el } from "@ui/ui_template_elements";
|
||||
import { createSpecForm } from "../../A00_Common/design/design_structure_form";
|
||||
import 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 { setSpecName, specNameRows, type SpecNameRow } from "./M02_SO_Def_Spec_Model";
|
||||
import {
|
||||
groupNameRows,
|
||||
setSpecName,
|
||||
specNameRows,
|
||||
type SpecNameRow,
|
||||
} from "./M02_SO_Def_Spec_Model";
|
||||
import { head, textBox } from "./M02_SO_Def_Ui";
|
||||
import { to, type OptionsTextKey } from "./M02_SO_Text";
|
||||
|
||||
@@ -20,47 +29,90 @@ const TAG: Record<NonNullable<SpecNameRow["tag"]>, OptionsTextKey> = {
|
||||
select: "FormSelect",
|
||||
number: "FormNumber",
|
||||
auto: "FormAuto",
|
||||
text: "FormText",
|
||||
bool: "FormBool",
|
||||
};
|
||||
|
||||
/** 정의 그룹 꼴 미리보기 — 그룹 밖 칸 · 그룹 상자(제목 = 그룹 이름) · 선택지 = 이름 표 글(값 그대로). */
|
||||
function groupPreview(def: StructDef, type: TypeDef): HTMLElement {
|
||||
const names = def.종류[type.type_id]?.이름 ?? {};
|
||||
const options = type.options.filter((o) => o.enabled !== false);
|
||||
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;
|
||||
}),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function buildSpecBlock(opt: {
|
||||
def: StructDef;
|
||||
type: TypeDef;
|
||||
spec: StructureSpec;
|
||||
/** 없으면 정의 그룹 꼴(52-12) */
|
||||
spec: StructureSpec | undefined;
|
||||
edit: boolean;
|
||||
changed: () => void;
|
||||
}): HTMLElement {
|
||||
const { def, type, spec, edit, changed } = opt;
|
||||
const rows = specNameRows(def, spec).map((row) =>
|
||||
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: [
|
||||
textBox(row.label, (v) => setSpecName(def, type.type_id, row, v), changed, {
|
||||
edit,
|
||||
attr: "data-sod-spec-name",
|
||||
cls: "m02-sod__name",
|
||||
}),
|
||||
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")] : []),
|
||||
],
|
||||
}),
|
||||
);
|
||||
// 미리보기 — 값 없는 새 구조물 폼 · 잇기 줄 없음(조합은 프로젝트 몫)
|
||||
const preview = createSpecForm(null);
|
||||
const groups = Object.fromEntries((def.그룹[type.type_id] ?? []).map((g) => [g.id, g.이름]));
|
||||
preview.write(spec, {}, def.종류[type.type_id]?.이름 ?? {}, groups);
|
||||
preview.root.inert = true;
|
||||
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);
|
||||
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,
|
||||
...(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.root],
|
||||
children: [preview],
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -6,25 +6,29 @@
|
||||
* · 이름 자리 = 정의 `종류.{종류}.이름`(키 `{컨테이너}` · `{컨테이너}.{항목}` · `{컨테이너}.{항목}={값}`)
|
||||
* · 필수 컨테이너 제목 = 이름 표에 없으면 정의 그룹(`그룹.{종류}` 의 `id = link`) 이름 — B05 그리기
|
||||
* (`containerTitle`)와 같은 차례로 읽고 그 자리에 씀
|
||||
* · 명세 없는 종류(정의 그룹 꼴 · 52-12) = 그룹 제목(`그룹.이름`) › 칸 이름(옵션 `label`) ›
|
||||
* 선택지 이름(이름 표 키 `{옵션}={값}` · 값은 그대로) — 그룹 밖 칸은 맨 앞 머리 줄 아래
|
||||
* import 는 꼴만 — 시험이 이 파일만 옮겨 돌림.
|
||||
* ========================================================================== */
|
||||
|
||||
import type { StructureSpec } from "../../A00_Common/design/design_structure_spec";
|
||||
import type { StructDef } from "./M02_SO_Def_Model";
|
||||
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" | null;
|
||||
tag: "required" | "optional" | "select" | "number" | "auto" | "text" | "bool" | null;
|
||||
/** 필수 컨테이너 제목이 정의 그룹 이름에서 옴 — 고치면 그 그룹 이름에 씀 */
|
||||
group: string | null;
|
||||
/** 정의 옵션 이름(`label`)에서 옴 — 고치면 그 옵션 이름에 씀 */
|
||||
option: string | null;
|
||||
}
|
||||
|
||||
/** 명세 차례대로 이름 줄 — 같은 이름 키(갈래마다 겹친 값)는 한 줄. */
|
||||
@@ -48,6 +52,7 @@ export function specNameRows(def: StructDef, spec: StructureSpec): SpecNameRow[]
|
||||
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}`;
|
||||
@@ -58,6 +63,7 @@ export function specNameRows(def: StructDef, spec: StructureSpec): SpecNameRow[]
|
||||
label: names[itemKey] ?? item.label,
|
||||
tag: item.kind,
|
||||
group: null,
|
||||
option: null,
|
||||
});
|
||||
const choices = [
|
||||
...(item.choices ?? []),
|
||||
@@ -72,6 +78,7 @@ export function specNameRows(def: StructDef, spec: StructureSpec): SpecNameRow[]
|
||||
label: names[choiceKey] ?? ch.label,
|
||||
tag: null,
|
||||
group: null,
|
||||
option: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -79,8 +86,67 @@ export function specNameRows(def: StructDef, spec: StructureSpec): SpecNameRow[]
|
||||
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;
|
||||
|
||||
@@ -48,9 +48,11 @@ const TEXT = {
|
||||
FormAuto: ["자동", "Auto"],
|
||||
SpecHead: ["구조물 명세 이름", "Structure spec names"],
|
||||
SpecNote: [
|
||||
"컨테이너 제목 › 항목 이름 › 값 이름만 고침 — 키(작은 글) · 필수/선택 · 꼴 · 구조는 구조물 명세가 정함 · [저장]하면 B05 · B06 이 이 이름으로 그림",
|
||||
"Rename containers › items › values only — keys, required/optional, form and structure come from the spec",
|
||||
"제목 › 칸 이름 › 선택지 이름만 고침 — 키(작은 글) · 필수/선택 · 꼴 · 칸 더하기 · 빼기는 읽기만 · [저장]하면 B05 · B06 이 이 이름으로 그림",
|
||||
"Rename titles › fields › choices only — keys, required/optional, form and adding/removing fields are read-only",
|
||||
],
|
||||
SpecLoose: ["그룹 밖 칸", "Fields outside groups"],
|
||||
SpecNone: ["고칠 이름 없음 — 칸이 없는 종류", "No names to edit — this kind has no fields"],
|
||||
SpecRequired: ["필수", "Required"],
|
||||
SpecOptional: ["선택", "Optional"],
|
||||
SpecPreview: ["미리보기 — B05 · B06 폼", "Preview — B05 · B06 form"],
|
||||
|
||||
Reference in New Issue
Block a user