fix(구조물표): 322-2 다듬기(브레인 화면 확인) — 화면에 영어 키 안 보임 · 검사 글 한국어(화면 · 서버 짝) · 부품 탭 읽는 칸 = 정의 이름 · 줄바꿈 · 한눈 표 폭 줄임 · 바꿨다 되돌리면 [저장] 꺼짐

- 자리꼴 점 · 구간 · 터 · 정본 관 지점 · 구조물(키는 툴팁) · 고르기 글 = 부품 짧은 이름(괄호 설명 · 키는 툴팁) · 횡단도 없음 = 「안 만듦」 · 「벽 스윕」 → 「벽」 · 종류 키는 툴팁
- 검사 글: 「형상이 「없음」이면 횡단도도 「안 만듦」」 · 부품 짧은 이름 · 칸 이름 · 종류 이름(M02_SO_Def_Check.ts = M02_SO_Check.py)
- 부품 탭: 읽는 칸 = 그 부품을 쓰는 종류의 칸 이름(단위) · 표 줄바꿈(table-layout fixed)
- 한눈 표 형상칸 「기본 형태」 = 그림 형태 고르기(REVET_FORMS)
- [저장] 켬 = 저장할 꼴(tidy · 목록 짝 enabled)끼리 지금 판과 다를 때만(M02_SO_Def_Reflect.differs)
- 시험: 322-2 검사 글 · 되돌림 견본

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
2026-10-14 16:29:30 +09:00
co-authored by Claude Opus 5.5
parent 6c87207f76
commit 0da2fa0767
11 changed files with 207 additions and 89 deletions
@@ -9,9 +9,13 @@
import { createButton, createTag, el } from "@ui/ui_template_elements";
import type { CatalogRow } from "../../common_util/common_util_structure_catalog";
import { stepFamily, stepType, type StructDef, type TypeDef } from "./M02_SO_Def_Model";
import { REVET_FORMS } from "../../B06_Section/B06_Section_UI_Cross_Culvert_Const";
import {
isSet,
OWNER_KO,
PART_BOX,
PLACE_KO,
shortName,
resetRow,
rowsOf,
setReflect,
@@ -28,14 +32,26 @@ const LIST_PAIRS = (): Pairs => [
["b06", to("ListB06")],
["hidden", to("ListHidden")],
];
/** 갈래 고르기 — 없음(카드 이름은 종류 이름) + 부품 상자 차례. */
/** 갈래 고르기 — 없음(횡단도 = 안 만듦 · 카드 이름 = 종류 이름) + 부품 상자 차례 · 글 = 짧은 이름
* (영어 키 · 괄호 설명은 툴팁 · 322-2 다듬기). */
const OFF_TEXT: Record<Exclude<ReflectField, "목록">, () => string> = {
횡단도: () => to("StationNone"),
카드이름: () => to("TitleName"),
형상: () => to("OffNone"),
"3D": () => to("OffNone"),
};
const partPairs = (branch: Exclude<ReflectField, "목록">): Pairs => [
[
branch === "카드이름" ? "name" : "none",
branch === "카드이름" ? to("TitleName") : to("OffNone"),
],
...Object.entries(PART_BOX[branch]).map(([id, part]) => [id, `${id} · ${part.이름}`] as const),
[branch === "카드이름" ? "name" : "none", OFF_TEXT[branch]()],
...Object.entries(PART_BOX[branch]).map(([id, part]) => [id, shortName(part.이름)] as const),
];
/** 고르기 항목 툴팁 = 키 · 부품 긴 이름. */
function tipOptions(box: HTMLSelectElement, branch: ReflectField): void {
if (branch === "목록") return;
for (const option of Array.from(box.options)) {
const part = PART_BOX[branch][option.value];
option.title = part ? `${option.value} · ${part.이름}` : option.value;
}
}
interface CellCtx {
def: StructDef;
@@ -58,6 +74,7 @@ function reflectCell(ctx: CellCtx, field: ReflectField): HTMLElement {
},
{ edit, attr: `data-sod-reflect-${field}` },
);
tipOptions(box, field);
return mark(isSet(def, type.type_id, field), box);
}
@@ -69,24 +86,22 @@ function mark(changed: boolean, ...children: HTMLElement[]): HTMLElement {
});
}
/** 형상칸 — 벽(형상 wall)만: 기본 형태 글 · 물빼기. */
/** 형상칸 — 벽(형상 wall)만: 기본 형태(그림 형태 고르기) · 물빼기. */
function shapeOptsCell(ctx: CellCtx): HTMLElement {
const { def, type, row, edit, changed } = ctx;
if (row.shape !== "wall") return el("span", { className: "m02-side__empty", text: "—" });
const form = String(row.shapeOpts.form ?? "");
const forms = !form || REVET_FORMS.includes(form) ? REVET_FORMS : [form, ...REVET_FORMS];
const box = pickBox(
forms.map((f) => [f, f] as const),
form,
(v) => (setShapeOpt(def, type.type_id, "form", v), changed()),
{ edit, attr: "data-sod-shape-form" },
);
box.title = to("WallForm");
return mark(
isSet(def, type.type_id, "형상칸"),
textBox(
form,
() => undefined,
() => undefined,
{
edit,
attr: "data-sod-shape-form",
place: to("WallForm"),
max: 20,
},
),
box,
checkBox(
to("Weep"),
row.shapeOpts.weep === true,
@@ -96,15 +111,6 @@ function shapeOptsCell(ctx: CellCtx): HTMLElement {
);
}
/** 형상칸 글은 떠날 때 씀(초점 안 끊김). */
function bindForm(node: HTMLElement, ctx: CellCtx): void {
const box = node.querySelector<HTMLInputElement>("[data-sod-shape-form]");
box?.addEventListener("change", () => {
setShapeOpt(ctx.def, ctx.type.type_id, "form", box.value.trim());
ctx.changed();
});
}
/** 알약 색(style.color). */
function colorCell(ctx: CellCtx): HTMLElement {
const box = el("input", {
@@ -121,9 +127,11 @@ function colorCell(ctx: CellCtx): HTMLElement {
return box;
}
const fixed = (text: string): HTMLElement =>
/** 자리꼴 · 정본 = 한국어 글 · 「고정」(키는 툴팁). */
const fixed = (text: string, key: string): HTMLElement =>
el("span", {
className: "m02-sod__fixed",
attrs: { title: key },
children: [el("span", { text }), createTag(to("Fixed"), "neutral")],
});
@@ -137,16 +145,14 @@ export function reflectCells(
const row = rowsOf(def).get(type.type_id);
if (!row) return [];
const ctx = { def, type, row, edit, changed };
const opts = shapeOptsCell(ctx);
bindForm(opts, ctx);
return [
[to("ColList"), reflectCell(ctx, "목록")],
[to("ColPlacement"), fixed(row.placement)],
[to("ColOwner"), fixed(row.owner)],
[to("ColPlacement"), fixed(PLACE_KO[row.placement] ?? row.placement, row.placement)],
[to("ColOwner"), fixed(OWNER_KO[row.owner] ?? row.owner, row.owner)],
[to("ColStation"), reflectCell(ctx, "횡단도")],
[to("ColTitle"), reflectCell(ctx, "카드이름")],
[to("ColShape"), reflectCell(ctx, "형상")],
[to("ColShapeOpts"), opts],
[to("ColShapeOpts"), shapeOptsCell(ctx)],
[to("ColColor"), colorCell(ctx)],
[to("Col3D"), reflectCell(ctx, "3D")],
];
@@ -232,8 +238,8 @@ export function buildTable(opt: {
children: [
td(""),
el("td", {
attrs: { title: type.type_id }, // 영어 키는 툴팁만(322-2 다듬기)
children: [
el("small", { className: "m02-opts__key", text: type.type_id }),
...(edit
? [
moveButton(