Files
Aislo/resources/tester/helper_153_b07_cross_layout.cjs
T
eomsangdonandClaude Opus 5.5 bbd6254892 feat(구조물표): 322-1 부품 상자 · 표 읽기 — 목록 · 측점 · 카드 이름 · 형상 고르기 · 3D 몸체 · 알약 띠가 종류 키 · 군 글자 · 자리꼴 · managed_by 대신 구조물 표(catalog) 한 줄을 읽음 · 코드 숨김 목록 걷음 · ★ 넷 + 관 공식 한 벌
- 부품 상자 common_util_structure_parts.json(갈래마다 부품 · 읽는 칸 · 자리꼴 · 정본 · 42종 처음값) · 표 읽기 common_util_structure_catalog.ts / .py 짝(정의 반영 ⊕ 처음값 ⊕ 부품 끔) · 거울 시험 test_322_1_catalog_mirror
- 목록: pickGroups 군 차례 = 구조물군 열쇠 · 페이지 거르기 listPick(B05 both · B06 both + b06) · HIDDEN_PICK_GROUPS · HIDDEN_PICK_TYPES · pickable 걷음(옛 대피 셋 asRoad 그대로)
- 측점: structureStationMarks · pipeStationMarks · is_station_planting_type · resolve_extra_stations = 표 횡단도(anchor · span3 · none)
- 카드 이름: cardTitle / card_title 한 공식(빈칸 = 그 종류 정의 기본값) · 표시 이름이 정의 관종도 앎
- 형상: 벽 = shape wall(형태 = 형상칸.form · 물빼기 = 형상칸.weep) · 포장 = shape pavement(「사용 중」 거르기 없앰) · 맹암거 · 사토 · 관 지점 시설 세트 = shape
- 3D 몸체 = 그림 자료 주인 종류의 표 3D 부품 · 알약 띠 = 표 자리꼴 구간(관 지점 시설은 알약만)
- ★1 노출형 · 개거 횡단도 none · ★2 쇄석 화면에서도 포장층 · ★3 기슭막이 표 자리꼴 interval(정의 파일 그대로) · ★4 코드 숨김 10종 list hidden · ★5 관 빈칸 D800 → 정의 기본 D1000
- 옛 시험: 도움 파일 로더 27개가 표 모듈 공용 한 벌(helper_322_catalog_module) 씀 · 기대값 고침(272_1 · b05_extra_stations · roundtrip · 314 · 52_7 · walls 거울 · wall_drain_pipe)

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

196 lines
6.4 KiB
JavaScript

/* 153-1 B07 횡단 장 배치 칸 헬퍼 — 좌측 「횡단도」 묶음 맨 아래 자리 · 공용 입력칸 · 작은 단추를
* Node 로 돌려 표준출력 JSON 한 벌로. TS 는 그 자리에서 트랜스파일 · 화면 그릇(DOM)은 쓰는 만큼만 흉내 ·
* 공용 단추 · 입력칸은 진짜(`ui_template_elements`) · 알림만 기록용 가짜.
*/
const fs = require("fs");
const path = require("path");
const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript"));
const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1)
const ROOT = path.join(__dirname, "..", "..");
const B07 = path.join(ROOT, "B07_DesignDetail");
const UI = path.join(ROOT, "ui_template");
const cache = new Map();
class FakeNode {
constructor(tag) {
this.tagName = tag.toUpperCase();
this.children = [];
this.dataset = {};
this.attrs = {};
this.listeners = {};
this._text = "";
this._classes = new Set();
this.value = "";
this.disabled = false;
const self = this;
this.classList = {
add: (...names) => names.forEach((n) => self._classes.add(n)),
remove: (...names) => names.forEach((n) => self._classes.delete(n)),
contains: (name) => self._classes.has(name),
};
}
set className(value) {
this._classes = new Set(String(value).split(/\s+/).filter(Boolean));
}
get className() {
return [...this._classes].join(" ");
}
set textContent(value) {
this._text = String(value);
this.children = [];
}
get textContent() {
return this._text + this.children.map((c) => c.textContent).join("");
}
setAttribute(key, value) {
this.attrs[key] = String(value);
}
append(...kids) {
kids.forEach((k) => this.children.push(k));
}
replaceChildren(...kids) {
this.children = [...kids];
}
addEventListener(type, handler) {
(this.listeners[type] ||= []).push(handler);
}
click() {
if (!this.disabled) (this.listeners.click || []).forEach((handler) => handler({}));
}
}
const all = (node) => [node, ...node.children.flatMap(all)];
const byClass = (node, name) => all(node).filter((n) => n.classList.contains(name));
globalThis.document = { createElement: (tag) => new FakeNode(tag) };
const toasts = [];
function loadTs(file) {
const full = path.resolve(file);
if (cache.has(full)) return cache.get(full);
const js = ts.transpileModule(fs.readFileSync(full, "utf8"), {
compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 },
}).outputText;
const box = { exports: {} };
cache.set(full, box.exports);
const localRequire = (request) => {
if (CATALOG.isCatalogRequest(request)) return CATALOG;
if (request === "@ui/ui_template_elements")
return {
...loadTs(path.join(UI, "ui_template_elements.ts")),
showToast: (message, kind) => toasts.push([kind, message]),
};
// 공용 카드(269) = 진짜 모듈
if (request === "@ui/ui_template_collapsible")
return loadTs(path.join(UI, "ui_template_collapsible.ts"));
if (request === "@ui/ui_template_locale")
return {
currentLanguageIndex: 0,
ui_locales: new Proxy({}, { get: (_, key) => [String(key)] }),
};
if (request.startsWith(".")) {
const target = path.join(path.dirname(full), `${request}.ts`);
return fs.existsSync(target) ? loadTs(target) : {};
}
return {}; // 이 시험이 안 부르는 바깥 길(API · 설정 · CAD)
};
new Function("exports", "module", "require", js)(box.exports, box, localRequire);
cache.set(full, box.exports);
return box.exports;
}
const { buildDrawingSidePanel, buildCrossLayoutControls } = loadTs(
path.join(B07, "B07_DesignDetail_UI_Panels.ts"),
);
const out = {};
const slot = new FakeNode("div");
slot.className = "b07-cross-layout-slot";
const panel = buildDrawingSidePanel(
[
{ id: "cross_standard", kind: "cross_standard", label: "표준 횡단면도", confirmed: false },
{ id: "cross_s1", kind: "cross", label: "1장", confirmed: false },
{ id: "cross_s2", kind: "cross", label: "2장", confirmed: true },
],
() => {},
undefined,
false,
undefined,
slot,
);
const cross = all(panel).find((n) => n.dataset.kind === "cross");
out.slotLast = cross.children.at(-1) === slot;
out.slotCount = all(panel).filter((n) => n === slot).length;
// 장 — [이 장] 켜짐 · 값 바꿔 누름
const applied = [];
const defaults = [];
slot.replaceChildren(
buildCrossLayoutControls(
[3, 4],
(grid) => applied.push(grid),
(grid) => defaults.push(grid),
),
);
const box = slot.children[0];
const buttons = () => all(slot).filter((n) => n.tagName === "BUTTON");
const inputs = () => all(slot).filter((n) => n.tagName === "INPUT");
out.sheet = {
boxClass: box.className,
labels: byClass(slot, "ui-field__label").map((n) => n.textContent),
inputs: inputs().map((n) => ({
value: n.value,
type: n.attrs.type,
min: n.attrs.min,
max: n.attrs.max,
cls: n.className,
})),
buttons: buttons().map((n) => ({ text: n.textContent, cls: n.className, disabled: n.disabled })),
};
inputs()[0].value = "2";
inputs()[1].value = "5";
buttons()[0].click();
buttons()[1].click();
inputs()[0].value = "11";
buttons()[0].click();
out.sheetClicks = { applied, defaults, toasts: [...toasts] };
// 장 아님 — [이 장] 꺼짐 · [모든 장]은 됨
const defaults2 = [];
slot.replaceChildren(buildCrossLayoutControls([4, 4], null, (grid) => defaults2.push(grid)));
buttons()[0].click();
buttons()[1].click();
out.notSheet = {
disabled: buttons().map((n) => n.disabled),
defaults: defaults2,
values: inputs().map((n) => n.value),
};
// 180-2 표 배율 칸 — 배치 칸 줄 뒤 · 0.5~2.0 · step 0.1 · [배율 적용]
const scales = [];
const toastStart = toasts.length;
slot.replaceChildren(
buildCrossLayoutControls([4, 4], null, () => {}, {
scale: { value: 1, onApply: (value) => scales.push(value) },
}),
);
const scaleInput = inputs()[2];
out.scale = {
labels: byClass(slot, "ui-field__label").map((n) => n.textContent),
input: {
value: scaleInput.value,
min: scaleInput.attrs.min,
max: scaleInput.attrs.max,
step: scaleInput.attrs.step,
},
buttons: buttons().map((n) => n.textContent),
};
scaleInput.value = "1.5";
buttons()[2].click();
scaleInput.value = "2.5";
buttons()[2].click();
out.scale.applied = scales;
out.scale.toasts = toasts.slice(toastStart).map((t) => t[0]);
process.stdout.write(JSON.stringify(out));