- 공용 createPanelCard 곁값 onSettings — 캐럿 왼쪽 ⚙ · 누름이 접힘 안 일으킴(B08 · B09 그대로) - 평면도 ⚙ = 평면도 관리 모달([평면도 관리] 단추 지움) · 횡단도 ⚙ = 배치 칸 · 표 배율 · 안내선(카드 밑 칸 지움) · 구조물 ⚙ = 「M02 그림 — 축척 없음」 · 종단 1장 · 토적 = 단추 안 ⚙ · 표지 없음 - ⚙ 머리 [원본에서 다시 그리기] — 그 묶음 도면을 연 때만 · 저장본이면 invalidate - 잠금 없음(요구 281) — 읽기 전용 안 묶음 · 자동 B08 이동 · 「모든 횡단 확정」 잠금 지움 - onStepClick 한 길 = 고친 탭(●) + 지금 탭 저장 → B08 · B09 면 단계 4 완료(새 POST design-drawings-complete) → 이동 · 실패 = 머묾 - 저장 · 원본 묶음 = 새 B07_DesignDetail_UI_Save.ts · 모달 = 새 B07_DesignDetail_UI_Settings.ts(UI_Page 633 → 543줄) - 탭 줄 = .cad-host 의 --cad-tab-slot-left/right(px · CSS left · right 꼴) 있으면 리본 빈 자리에 겹침 · 없으면 지금 자리 - 시험: 새 test_280_5_b07_settings · 130 · 153-1 · 180-3 · 212-3 · 269 · 311 고쳐 씀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
203 lines
6.7 KiB
JavaScript
203 lines
6.7 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() {
|
|
const event = { stopPropagation() {}, preventDefault() {} };
|
|
if (!this.disabled) (this.listeners.click || []).forEach((handler) => handler(event));
|
|
}
|
|
}
|
|
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 settings = [];
|
|
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,
|
|
(kind) => settings.push(kind),
|
|
);
|
|
// 280-5 배치 칸은 ⚙ 횡단 모달로 — 카드엔 ⚙ 하나 · 배치 칸 없음
|
|
const cross = all(panel).find((n) => n.dataset.kind === "cross");
|
|
const gears = byClass(cross, "ui-collapsible__settings");
|
|
out.card = {
|
|
gears: gears.length,
|
|
layoutInCard: byClass(cross, "b07-cross-layout").length,
|
|
};
|
|
gears[0].click();
|
|
out.card.settings = settings;
|
|
|
|
// 장 — [이 장] 켜짐 · 값 바꿔 누름
|
|
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));
|