feat(구조물표): 322-2 M02 표 화면 — 탭 셋(한눈 표 · 종류별 · 부품) · 반영 · 부품 · 알약 색 저장 · 목록 + enabled 함께 · 검사 짝(화면 = 서버 · 1-3 규칙 · 만든 뒤 고정) · 명세 폼 보임 따름 · 판 알림
- 한눈 표(M02_SO_Def_Table): 군 · 종류 ▲▼ · 이름 · 목록 · 자리꼴 · 정본(고정) · 횡단도 · 카드 이름 · 형상(+ 벽 기본 형태 · 물빼기) · 알약 색 · 3D · 검사 · 처음 값 = 흐림 「기본」 · 바꿈 = 진함 · [처음 값으로] - 종류별: 반영 칸 + 명세 이름 나무 줄마다 보임(컨테이너 = 그 안 칸) · 못 숨김 · 미리보기 숨김 따름 · 「안 씀」 = 목록 B06 만 - 부품 탭(M02_SO_Def_Parts): 부품 · 읽는 칸 · 자리꼴 · 정본 · 쓰는 종류 · 켬/끔(정의 부품 = 끈 것만) - 편집 손질(M02_SO_Def_Reflect): 처음 값이면 반영에서 지움 · [저장] 때 enabled = 목록 both 일 때만 참 - 검사 짝 M02_SO_Def_Check.ts ↔ M02_SO_Check.py(글까지 같음): 반영 · 부품 꼴 · 형상 none → 횡단도 none · 부품 자리꼴 · 정본 · 기본값 없는 읽는 칸 새로 숨김 금지(앞 판 숨김 그대로) · 종류 지우기 · 자리꼴 · 정본 · 군 글자 · 선택지 값 고정 · 서버 check(doc, 지금 판) · 앞 판 없으면 꼴만 - 명세 폼: 쓰는 옵션 키가 모두 숨긴 칸 · 상자는 안 그림(값 그대로) · B05 · B06 시설 · 구조물 명세 폼이 정의 「표시」를 따름 - 판 알림: GET 구조물 목록에 definition_version · structures.json 판 ≠ 지금 판이면 「구조물 정의가 판 N 으로 바뀜 — 저장하면 반영」 한 번 - 시험: test_322_2_check_mirror(거울 · 규칙 · 반영 손질 · 서버 저장 · 422) · 판 알림 · 옛 정의 화면 시험 = 선택지 값 대신 이름 표 · 못 숨김 아닌 칸 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ji4nuyLVHcxrXWHTdb5nRD
This commit is contained in:
@@ -1,8 +1,10 @@
|
||||
/* =============================================================================
|
||||
* M02_SO_Def.ts
|
||||
* 구조물 정의 편집 화면(PLAN 48-2 · 메인 칸) — 왼쪽 구조물군 · 종류 목록(이름 바꿈 · 순서 · 안 씀) ·
|
||||
* 오른쪽 고른 종류(이름 · 안 씀 · 기준 측점 표시 · 옵션 · 배치 격자 · 그룹 · 규칙) ·
|
||||
* 아래 [저장](판 +1 · 시스템 관리자만) · 지난 판 목록(누르면 그 판을 읽기만으로 봄).
|
||||
* 구조물 정의 편집 화면(PLAN 48-2 · 메인 칸) — 탭 셋(322-2): 「한눈 표」(종류마다 반영 한 줄 ·
|
||||
* `M02_SO_Def_Table`) · 「종류별」(왼쪽 구조물군 · 종류 목록 · 오른쪽 고른 종류 이름 · 반영 · 명세 이름 나무 ·
|
||||
* 줄마다 보임 · 미리보기) · 「부품」(`M02_SO_Def_Parts`) ·
|
||||
* 아래 검사(화면 = 서버 짝 `M02_SO_Def_Check`) · [저장](판 +1 · 목록 + enabled 함께 · 시스템 관리자만) ·
|
||||
* 지난 판 목록(누르면 그 판을 읽기만으로 봄).
|
||||
* 다른 역할은 읽기만. 고칠 사본은 저장 전까지 서버에 안 감 · 정본 = `구조물정의.json`(48-1).
|
||||
* ========================================================================== */
|
||||
|
||||
@@ -28,6 +30,10 @@ import { buildOptionsBlock } from "./M02_SO_Def_Options";
|
||||
import { buildRulesBlock } from "./M02_SO_Def_Rules";
|
||||
import { buildSpecBlock } from "./M02_SO_Def_Spec";
|
||||
import { STRUCTURE_SPECS } from "../../A00_Common/design/design_structure_pipe";
|
||||
import { catalogProblems, type CheckDef } from "./M02_SO_Def_Check";
|
||||
import { buildParts } from "./M02_SO_Def_Parts";
|
||||
import { setReflect, syncEnabled } from "./M02_SO_Def_Reflect";
|
||||
import { buildReflectBlock, buildTable } from "./M02_SO_Def_Table";
|
||||
import { checkBox, textBox } from "./M02_SO_Def_Ui";
|
||||
import { buildGroupBlock } from "./M02_SO_Groups";
|
||||
import { to } from "./M02_SO_Text";
|
||||
@@ -44,10 +50,12 @@ export interface DefScreenOptions {
|
||||
|
||||
const why = (error: unknown): string => (error instanceof Error ? error.message : "");
|
||||
|
||||
/** 빈 묶음 걷어냄 — 그리기 중 만든 빈 그룹 목록 · 배치 · 종류 덧칸 */
|
||||
/** 저장할 꼴 — 목록 + enabled 함께(322-2) · 빈 묶음 걷어냄(그룹 목록 · 배치 · 종류 덧칸 · 표시 · 반영) */
|
||||
function tidy(def: StructDef): StructDef {
|
||||
const out = clone(def);
|
||||
for (const part of [out.그룹, out.배치, out.종류, out.표시] as Record<string, unknown>[])
|
||||
syncEnabled(out);
|
||||
const reflect = (out.반영 ?? {}) as Record<string, unknown>;
|
||||
for (const part of [out.그룹, out.배치, out.종류, out.표시, reflect] as Record<string, unknown>[])
|
||||
for (const [k, v] of Object.entries(part))
|
||||
if (Array.isArray(v) ? !v.length : !Object.keys(v as object).length) delete part[k];
|
||||
return out;
|
||||
@@ -88,6 +96,8 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
let current: string | null = null;
|
||||
const open = new Set<string>(); // 선택지를 펼친 옵션
|
||||
let versions: DefVersion[] | null = null;
|
||||
type Tab = "table" | "kinds" | "parts";
|
||||
let tab: Tab = "table";
|
||||
|
||||
const shown = (): StructDef => past ?? work;
|
||||
const canEdit = (): boolean => opt.isAdmin && !past;
|
||||
@@ -100,6 +110,13 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
attrs: { "data-sod-history": "" },
|
||||
});
|
||||
|
||||
/** 검사 글 — 이름 · 배치 · 규칙(`problems`) + 구조물 표(서버 짝 · 앞 판 = 지금 서버 판) */
|
||||
const allProblems = (): string[] =>
|
||||
past ? [] : [...problems(work), ...catalogProblems(work as CheckDef, base as CheckDef)];
|
||||
const tabBar = el("div", { className: "m02-sod__tabs", attrs: { "data-sod-tabs": "" } });
|
||||
const body = el("div", { className: "m02-sod__body" });
|
||||
const panes = el("div", { className: "m02-opts__panes", children: [kindList, detail] });
|
||||
|
||||
const changed = (): void => {
|
||||
dirty = true;
|
||||
sync();
|
||||
@@ -199,10 +216,13 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
cls: "m02-sod__name",
|
||||
}),
|
||||
el("small", { className: "m02-opts__key", text: type.type_id }),
|
||||
checkBox(to("Unused"), type.enabled === false, (v) => ((type.enabled = !v), changed()), {
|
||||
edit,
|
||||
attr: "data-sod-kind-off",
|
||||
}),
|
||||
// 안 씀 = 목록 B06 만(enabled 는 목록 짝 · 322-2) · 숨김(hidden)은 한눈 표 목록 칸에서
|
||||
checkBox(
|
||||
to("Unused"),
|
||||
type.enabled === false,
|
||||
(v) => (setReflect(d, type.type_id, "목록", v ? "b06" : "both"), changed()),
|
||||
{ edit, attr: "data-sod-kind-off" },
|
||||
),
|
||||
...(!OLD_EDITOR
|
||||
? []
|
||||
: [
|
||||
@@ -225,7 +245,10 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
],
|
||||
}),
|
||||
...(!OLD_EDITOR
|
||||
? [buildSpecBlock({ def: d, type, spec, edit, changed })]
|
||||
? [
|
||||
buildReflectBlock(d, type, edit, changed),
|
||||
buildSpecBlock({ def: d, base, type, spec, edit, changed }),
|
||||
]
|
||||
: [
|
||||
buildOptionsBlock({ def: d, type, edit, open, changed }),
|
||||
buildLayoutBlock({ def: d, type, edit, changed }),
|
||||
@@ -235,6 +258,27 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
);
|
||||
};
|
||||
|
||||
const paintTabs = (): void => {
|
||||
const pairs: Array<[Tab, string]> = [
|
||||
["table", to("TabTable")],
|
||||
["kinds", to("TabKinds")],
|
||||
["parts", to("TabParts")],
|
||||
];
|
||||
tabBar.replaceChildren(
|
||||
...pairs.map(([key, label]) => {
|
||||
const b = createButton({ label, variant: key === tab ? "filled" : "ghost", size: "sm" });
|
||||
b.setAttribute("data-sod-tab", key);
|
||||
b.addEventListener("click", () => ((tab = key), paint()));
|
||||
return b;
|
||||
}),
|
||||
);
|
||||
const edit = canEdit();
|
||||
if (tab === "table")
|
||||
body.replaceChildren(buildTable({ def: shown(), edit, changed, issues: allProblems() }));
|
||||
else if (tab === "parts") body.replaceChildren(buildParts({ def: shown(), edit, changed }));
|
||||
else body.replaceChildren(panes);
|
||||
};
|
||||
|
||||
const paintFoot = (): void => {
|
||||
banner.replaceChildren(
|
||||
...(past
|
||||
@@ -248,7 +292,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
]),
|
||||
);
|
||||
banner.classList.toggle("is-past", !!past);
|
||||
const list = past ? [] : problems(work);
|
||||
const list = allProblems();
|
||||
issues.replaceChildren(...list.map((t) => el("p", { className: "m02-sod__bad", text: t })));
|
||||
save.disabled = !opt.isAdmin || !!past || !dirty || list.length > 0;
|
||||
};
|
||||
@@ -256,6 +300,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
const paint = (): void => {
|
||||
paintKinds();
|
||||
paintDetail();
|
||||
paintTabs();
|
||||
paintFoot();
|
||||
};
|
||||
|
||||
@@ -273,7 +318,7 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
const save = createButton({ label: to("Save"), variant: "filled" });
|
||||
save.setAttribute("data-sod-save", "");
|
||||
save.addEventListener("click", async () => {
|
||||
if (problems(work).length) return;
|
||||
if (allProblems().length) return;
|
||||
save.disabled = true; // 연달아 눌러도 한 번만
|
||||
try {
|
||||
base = await saveStructureDef(base.판, tidy(work));
|
||||
@@ -339,7 +384,8 @@ function drawDef(first: StructDef, opt: DefScreenOptions): void {
|
||||
children: [
|
||||
el("p", { className: "m02-side__empty", text: to("DefNote") }),
|
||||
banner,
|
||||
el("div", { className: "m02-opts__panes", children: [kindList, detail] }),
|
||||
tabBar,
|
||||
body,
|
||||
issues,
|
||||
el("div", {
|
||||
className: "m02-side__row",
|
||||
|
||||
Reference in New Issue
Block a user