Files
Aislo/M02_MasterTemplete/StructOptions/M02_SO_Def_Layout.ts
T
eomsangdonandClaude Opus 5.5 8ca2f1cd6e feat(M02): 48-2 구조물 옵션 정의 편집 화면 — 군 · 종류 · 옵션 · 선택지 · 배치 격자 · 그룹 형상 · 규칙 · 판 이력
- 옛 「옵션 보이기 · 그룹」 화면을 정의 편집 화면으로 넓힘(시스템 관리자 고쳐 씀 · 다른 역할 읽기만)
- 자료 손질 = M02_SO_Def_Model.ts(정의 틀 48-1 꼴) · 시험 test_m02_so_def_model.py

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
2026-10-05 16:15:10 +09:00

121 lines
3.6 KiB
TypeScript

/* =============================================================================
* M02_SO_Def_Layout.ts
* 구조물 정의 편집 화면(PLAN 48-2) — 고른 종류의 **배치 격자**(B05 · B06 좌측 구조물 칸 = 두 칸)
* 옵션마다 줄 · 칸 · 너비 + 미리보기(기준 측점은 맨 위 한 줄 · 겹치면 빨강).
* 자리를 안 정한 옵션은 차례대로 빈 칸을 채움 · 숫자를 고치면 그 옵션 자리만 정의 `배치` 에 남음.
* ========================================================================== */
import { el } from "@ui/ui_template_elements";
import {
baseShown,
GRID_COLS,
live,
overlaps,
placeAll,
type Spot,
type StructDef,
type TypeDef,
} from "./M02_SO_Def_Model";
import { head, numberBox } from "./M02_SO_Def_Ui";
import { to } from "./M02_SO_Text";
export interface LayoutBlockOptions {
def: StructDef;
type: TypeDef;
edit: boolean;
changed: () => void;
}
const ROW_MAX = 60;
/** 미리보기 — 두 칸 격자 · 옵션마다 상자 */
function preview(def: StructDef, type: TypeDef, cells: Map<string, Spot>): HTMLElement {
const bad = new Set(overlaps(cells));
const base = baseShown(def, type.type_id);
const offset = base ? 1 : 0;
const boxes: HTMLElement[] = [];
if (base)
boxes.push(
el("div", {
className: "m02-sod__cell is-base",
text: to("BaseStation"),
attrs: { style: `grid-row: 1; grid-column: 1 / span ${GRID_COLS}` },
}),
);
for (const o of live(type.options)) {
const c = cells.get(o.key)!;
boxes.push(
el("div", {
className: `m02-sod__cell${bad.has(o.key) ? " is-bad" : ""}`,
text: o.label,
attrs: {
"data-sod-cell": o.key,
title: o.label,
style: `grid-row: ${c.줄 + offset}; grid-column: ${c.칸} / span ${c.너비}`,
},
}),
);
}
return el("div", {
className: "m02-sod__grid",
attrs: {
"data-sod-preview": "",
style: `grid-template-columns: repeat(${GRID_COLS}, minmax(0, 1fr))`,
},
children: boxes,
});
}
export function buildLayoutBlock(opt: LayoutBlockOptions): HTMLElement {
const { def, type, edit, changed } = opt;
const cells = placeAll(type.options, def.배치[type.type_id]);
const rows = live(type.options).map((o) => {
const c = cells.get(o.key)!;
const fix = (patch: Partial<Spot>): void => {
(def.배치[type.type_id] ??= {})[o.key] = { ...c, ...patch };
changed();
};
return el("div", {
className: "m02-sod__place",
attrs: { "data-sod-place-row": o.key },
children: [
el("span", { className: "m02-sod__placename", text: o.label }),
el("small", { text: to("GridRow") }),
numberBox(c.줄, (n) => fix({ 줄: n }), {
edit,
min: 1,
max: ROW_MAX,
attr: "data-sod-row",
}),
el("small", { text: to("GridCol") }),
numberBox(c.칸, (n) => fix({ 칸: n }), {
edit,
min: 1,
max: GRID_COLS,
attr: "data-sod-col",
}),
el("small", { text: to("GridWidth") }),
numberBox(c.너비, (n) => fix({ 너비: n }), {
edit,
min: 1,
max: GRID_COLS,
attr: "data-sod-width",
}),
],
});
});
return el("div", {
className: "m02-sod__block",
children: [
...head(to("LayoutHead"), to("LayoutNote")),
el("div", {
className: "m02-sod__layout",
children: [
el("div", { className: "m02-sod__places", children: rows }),
preview(def, type, cells),
],
}),
],
});
}