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
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
/* =============================================================================
|
||||
* M02_SO_Def_Rules.ts
|
||||
* 구조물 정의 편집 화면(PLAN 48-2) — 고른 종류의 **규칙 표**(정의 `규칙` 중 그 종류 것만)
|
||||
* 「만약 상위 옵션 = 값들 중 하나 → 하위 옵션의 선택지 · 보이기 · 기본값」 한 줄씩 · [규칙 더함] · 지우기.
|
||||
* 「그대로」 칸은 정의에 안 남김 · 셋 다 그대로면 틀린 줄(빨강) · 적용은 B05 · B06(48-4).
|
||||
* ========================================================================== */
|
||||
|
||||
import { createButton, el } from "@ui/ui_template_elements";
|
||||
import {
|
||||
live,
|
||||
ruleBad,
|
||||
type OptionDef,
|
||||
type RuleDef,
|
||||
type StructDef,
|
||||
type TypeDef,
|
||||
} from "./M02_SO_Def_Model";
|
||||
import { head, pickBox } from "./M02_SO_Def_Ui";
|
||||
import { to } from "./M02_SO_Text";
|
||||
|
||||
export interface RulesBlockOptions {
|
||||
def: StructDef;
|
||||
type: TypeDef;
|
||||
edit: boolean;
|
||||
changed: () => void;
|
||||
}
|
||||
|
||||
const bool = (): [string, string][] => [
|
||||
["true", to("Yes")],
|
||||
["false", to("No")],
|
||||
];
|
||||
|
||||
/** 여럿 고르기 — 체크한 것만 차례대로 */
|
||||
function multiPick(
|
||||
all: readonly string[],
|
||||
picked: readonly string[],
|
||||
set: (v: string[]) => void,
|
||||
edit: boolean,
|
||||
attr: string,
|
||||
): HTMLElement {
|
||||
const on = new Set(picked);
|
||||
return el("div", {
|
||||
className: "m02-sod__rulechoices",
|
||||
attrs: { [attr]: "" },
|
||||
children: all.map((c) => {
|
||||
const box = el("input", { attrs: { type: "checkbox", value: c } }) as HTMLInputElement;
|
||||
box.checked = on.has(c);
|
||||
box.disabled = !edit;
|
||||
box.addEventListener("change", () => {
|
||||
if (box.checked) on.add(c);
|
||||
else on.delete(c);
|
||||
set(all.filter((x) => on.has(x)));
|
||||
});
|
||||
return el("label", { className: "m02-sod__check", children: [box, el("span", { text: c })] });
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
/** 글 칸 — 쉼표로 여럿 */
|
||||
function listText(
|
||||
value: string[],
|
||||
set: (v: string[]) => void,
|
||||
edit: boolean,
|
||||
attr: string,
|
||||
): HTMLElement {
|
||||
const box = el("input", {
|
||||
className: "ui-input ui-input--sm m02-sod__rulepick",
|
||||
attrs: { type: "text", [attr]: "", placeholder: to("RuleValues") },
|
||||
}) as HTMLInputElement;
|
||||
box.value = value.join(", ");
|
||||
box.disabled = !edit;
|
||||
box.addEventListener("change", () =>
|
||||
set(
|
||||
box.value
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
);
|
||||
return box;
|
||||
}
|
||||
|
||||
/** 만약 값들 — 고르기 = 선택지 체크 · 예/아니오 = 하나 · 그 밖 = 쉼표 글 */
|
||||
function whenValues(
|
||||
src: OptionDef | undefined,
|
||||
rule: RuleDef,
|
||||
edit: boolean,
|
||||
changed: () => void,
|
||||
): HTMLElement {
|
||||
const set = (v: string[]): void => ((rule.만약.값들 = v), changed());
|
||||
if (src?.input === "select")
|
||||
return multiPick(src.choices ?? [], rule.만약.값들, set, edit, "data-sod-rule-value");
|
||||
if (src?.input === "bool")
|
||||
return pickBox(bool(), rule.만약.값들[0] ?? "true", (v) => set([v]), {
|
||||
edit,
|
||||
attr: "data-sod-rule-value",
|
||||
cls: "m02-sod__rulepick",
|
||||
});
|
||||
return listText(rule.만약.값들, set, edit, "data-sod-rule-value");
|
||||
}
|
||||
|
||||
/** 그러면 기본값 — 「그대로」 + (좁힌) 선택지 · 예/아니오 · 글 */
|
||||
function thenDefault(
|
||||
dst: OptionDef | undefined,
|
||||
rule: RuleDef,
|
||||
edit: boolean,
|
||||
changed: () => void,
|
||||
): HTMLElement {
|
||||
const then = rule.그러면;
|
||||
const now = then.기본값 === undefined ? "" : String(then.기본값);
|
||||
const set = (v: string): void => {
|
||||
if (v === "") delete then.기본값;
|
||||
else
|
||||
then.기본값 =
|
||||
dst?.input === "bool"
|
||||
? v === "true"
|
||||
: dst?.input === "number" && Number.isFinite(Number(v))
|
||||
? Number(v)
|
||||
: v;
|
||||
changed();
|
||||
};
|
||||
const pairs: [string, string][] =
|
||||
dst?.input === "select"
|
||||
? (then.선택지 ?? dst.choices ?? []).map((c) => [c, c])
|
||||
: dst?.input === "bool"
|
||||
? bool()
|
||||
: [];
|
||||
if (pairs.length || (dst?.input !== "number" && dst?.input !== "text"))
|
||||
return pickBox([["", to("KeepDefault")], ...pairs], now, set, {
|
||||
edit,
|
||||
attr: "data-sod-rule-default",
|
||||
cls: "m02-sod__rulepick",
|
||||
});
|
||||
const box = el("input", {
|
||||
className: "ui-input ui-input--sm m02-sod__rulepick",
|
||||
attrs: { type: "text", "data-sod-rule-default": "", placeholder: to("KeepDefault") },
|
||||
}) as HTMLInputElement;
|
||||
box.value = now;
|
||||
box.disabled = !edit;
|
||||
box.addEventListener("change", () => set(box.value.trim()));
|
||||
return box;
|
||||
}
|
||||
|
||||
function ruleRow(opt: RulesBlockOptions, rule: RuleDef, index: number): HTMLElement {
|
||||
const { def, type, edit, changed } = opt;
|
||||
const byKey = new Map(type.options.map((o) => [o.key, o]));
|
||||
const names = live(type.options).map((o) => [o.key, o.label] as [string, string]);
|
||||
const src = byKey.get(rule.만약.옵션);
|
||||
const dst = byKey.get(rule.그러면.옵션);
|
||||
const then = rule.그러면;
|
||||
const del = createButton({ label: to("GroupDel"), variant: "danger", size: "sm" });
|
||||
del.addEventListener("click", () => (def.규칙.splice(def.규칙.indexOf(rule), 1), changed()));
|
||||
const show = then.보이기 === undefined ? "" : then.보이기 ? "1" : "0";
|
||||
return el("div", {
|
||||
className: `m02-sod__rule${ruleBad(def, rule) ? " is-bad" : ""}`,
|
||||
attrs: { "data-sod-rule": String(index) },
|
||||
children: [
|
||||
el("small", { text: to("RuleIf") }),
|
||||
pickBox(names, rule.만약.옵션, (v) => ((rule.만약 = { 옵션: v, 값들: [] }), changed()), {
|
||||
edit,
|
||||
attr: "data-sod-rule-top",
|
||||
cls: "m02-sod__rulepick",
|
||||
}),
|
||||
el("small", { text: "=" }),
|
||||
whenValues(src, rule, edit, changed),
|
||||
el("small", { text: to("RuleThen") }),
|
||||
pickBox(names, then.옵션, (v) => ((rule.그러면 = { 옵션: v }), changed()), {
|
||||
edit,
|
||||
attr: "data-sod-rule-target",
|
||||
cls: "m02-sod__rulepick",
|
||||
}),
|
||||
...(dst?.input === "select"
|
||||
? [
|
||||
el("small", { text: to("RuleChoices") }),
|
||||
multiPick(
|
||||
dst.choices ?? [],
|
||||
then.선택지 ?? [],
|
||||
(v) => {
|
||||
if (v.length) then.선택지 = v;
|
||||
else delete then.선택지;
|
||||
changed();
|
||||
},
|
||||
edit,
|
||||
"data-sod-rule-choice",
|
||||
),
|
||||
]
|
||||
: []),
|
||||
pickBox(
|
||||
[
|
||||
["", to("Keep")],
|
||||
["1", to("Show")],
|
||||
["0", to("Hide")],
|
||||
],
|
||||
show,
|
||||
(v) => {
|
||||
if (v === "") delete then.보이기;
|
||||
else then.보이기 = v === "1";
|
||||
changed();
|
||||
},
|
||||
{ edit, attr: "data-sod-rule-show", cls: "m02-sod__rulepick" },
|
||||
),
|
||||
thenDefault(dst, rule, edit, changed),
|
||||
...(edit ? [del] : []),
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
export function buildRulesBlock(opt: RulesBlockOptions): HTMLElement {
|
||||
const { def, type, edit, changed } = opt;
|
||||
const mine = def.규칙.filter((r) => r.종류 === type.type_id);
|
||||
const opts = live(type.options);
|
||||
const add = createButton({ label: to("RuleAdd"), variant: "ghost", size: "sm" });
|
||||
add.setAttribute("data-sod-rule-add", "");
|
||||
add.disabled = opts.length < 2;
|
||||
add.addEventListener("click", () => {
|
||||
const top = opts.find((o) => o.input === "select") ?? opts[0];
|
||||
const target = opts.find((o) => o.key !== top.key)!;
|
||||
def.규칙.push({
|
||||
종류: type.type_id,
|
||||
만약: { 옵션: top.key, 값들: top.choices?.length ? [top.choices[0]] : [] },
|
||||
그러면: { 옵션: target.key, 보이기: false },
|
||||
});
|
||||
changed();
|
||||
});
|
||||
return el("div", {
|
||||
className: "m02-sod__block",
|
||||
children: [
|
||||
...head(to("RuleHead"), to("RuleNote")),
|
||||
...(mine.length
|
||||
? mine.map((r) => ruleRow(opt, r, def.규칙.indexOf(r)))
|
||||
: [el("p", { className: "m02-side__empty", text: to("RuleNone") })]),
|
||||
...(edit ? [el("div", { className: "m02-side__row", children: [add] })] : []),
|
||||
],
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user