Files
Aislo/resources/tester/helper_153_b07_cross_layout.cjs
T
eomsangdonandClaude Opus 5.5 871174d214 feat(523-1): B07 설정 모달 공용 고침 — 끌기 닫힘 막음 · 폭 절반 · 그룹 상자 · 단추 줄 하나 · 자동 체크 없앰
- 공용 모달 바깥 누르기 = 누름 · 뗌 둘 다 배경일 때만 닫음(숫자 칸 끌기로 안 닫힘 · 평면도 ⚙ 포함 모든 openModal)
- ⚙ 설정 모달(종단 · 횡단 · 토적 · 구조물 · 전개도) 폭 720 → 360 px · 그룹 상자(legend) · 맨 아래 단추 줄 하나([안내선 숨기기] [기본값] [취소] [확인])
- [확인] = 바뀐 것만 저장(축척 → 배치) 뒤 다시 그림 · 붉은 칸이면 꺼짐 · 횡단 [이 장] [모든 장] [배율 적용] 단추 → 「적용」 고르기 + [확인]
- 축척 「자동」 체크 걷음 — 기본 = 지정값(종단 평면도 1/5,000 · 토적 X 1/2,000 · 전개도 X · Y 1/50) · 옛 null = 기본으로 읽음
- 낡은 시험 고침: 153-1(지운 buildCrossLayoutControls 대신 ⚙ 횡단 모달) · 212-3 · 180-3(안내선 단추 자리) · 280-5(자동 체크 · 단추 판정) · 485 축척 · 토적 · 종단 평면 칸(옛 자동 축척 값 → 지정 기본값)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 14:34:31 +09:00

242 lines
8.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 153-1 · 523-1 B07 횡단 장 배치 헬퍼 — 좌측 「횡단도」 카드 ⚙ 하나 · ⚙ 횡단 모달(그룹 상자
* 「장 배치」 · 「표」 · 맨 아래 단추 줄 하나 · [확인] = 바뀐 것만 저장)을 Node 로 돌려 표준출력 JSON 한 벌로.
* TS 는 그 자리에서 트랜스파일 · 화면 그릇(DOM)은 쓰는 만큼만 흉내 · 공용 단추 · 입력칸 · 고르기칸은 진짜
* (`ui_template_elements`) · 모달 겉틀 · 알림만 기록용 가짜.
*/
const fs = require("fs");
const path = require("path");
const { makeLoader } = require("./helper_ts_stub_loader.cjs");
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");
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 = [];
const modals = [];
const openModal = (opts) => {
const body = new FakeNode("div");
const modal = { opts, body, open: true };
modals.push(modal);
opts.mount(body, () => (modal.open = false), new FakeNode("div"));
return { close: () => (modal.open = false) };
};
const { loadTs } = makeLoader({
root: ROOT,
requireFor: (full) => (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]),
};
if (request === "@ui/ui_template_modal") return { openModal };
if (request === "./B07_DesignDetail_UI_PlanManage") return {};
if (request === "./B07_DesignDetail_Api_Fetch") return {};
// 공용 카드(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)
},
});
const { buildDrawingSidePanel } = loadTs(path.join(B07, "B07_DesignDetail_UI_Panels.ts"));
const { openDrawingSettings } = loadTs(path.join(B07, "B07_DesignDetail_UI_Settings.ts"));
const out = {};
const settings = [];
const drawings = [
{ 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 },
];
const panel = buildDrawingSidePanel(
drawings,
() => {},
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;
// ── ⚙ 횡단 모달(523-1) ─────────────────────────────────────────────────────
const tick = () => new Promise((r) => setTimeout(r, 0));
const saved = [];
let current = drawings[1]; // cross_s1 = 1장
const guideToggle = new FakeNode("button");
guideToggle.textContent = "안내선 숨기기";
const deps = {
drawings,
current: () => current,
canRedraw: () => false,
redraw: async () => false,
cross: {
layout: async () => ({ default: [4, 4], sheets: { 1: [3, 4] }, table_scale: 1 }),
save: (next, sheet) => saved.push([next, sheet]),
guideToggle,
},
};
const open = async () => {
openDrawingSettings("cross", deps);
await tick();
const modal = modals.at(-1);
const inputs = all(modal.body).filter((n) => n.tagName === "INPUT");
const select = all(modal.body).find((n) => n.tagName === "SELECT");
const footer = byClass(modal.body, "b07-settings__actions")[0];
const btn = (label) => all(footer).find((n) => n.textContent === label);
const type = (input, value) => {
input.value = value;
(input.listeners.input || []).forEach((h) => h());
};
return { modal, inputs, select, footer, btn, type };
};
(async () => {
// 장 — 그룹 상자 둘 · 공용 칸 · 단추 줄 하나
let m = await open();
const legends = byClass(m.modal.body, "b07-settings__group").map(
(g) => g.children[0].textContent,
);
const bodyButtons = all(m.modal.body).filter((n) => n.tagName === "BUTTON");
out.sheet = {
legends,
labels: byClass(m.modal.body, "ui-field__label").map((n) => n.textContent),
inputs: m.inputs.map((n) => ({
value: n.value,
type: n.attrs.type,
min: n.attrs.min,
max: n.attrs.max,
step: n.attrs.step ?? null,
cls: n.className,
})),
scope: { options: m.select.children.map((o) => o.textContent), value: m.select.value },
footers: byClass(m.modal.body, "b07-settings__actions").length,
footerButtons: m.footer.children.map((n) => n.textContent),
buttonsOutsideFooter: bodyButtons.filter((n) => !all(m.footer).includes(n)).length,
};
// 이 장만 — 2 × 5
m.type(m.inputs[0], "2");
m.type(m.inputs[1], "5");
m.btn("확인").click();
await tick();
out.applySheet = { saved: [...saved], closed: !m.modal.open };
// 모든 장 — 고르기만 바꿔도 씀(장마다 값 지움)
saved.length = 0;
m = await open();
m.select.value = "all";
m.btn("확인").click();
await tick();
out.applyAll = [...saved];
// 범위 밖 = 칸 붉음 · [확인] 꺼짐 · 저장 없음
saved.length = 0;
m = await open();
m.type(m.inputs[0], "11");
const confirm = m.btn("확인");
out.badGrid = {
disabled: confirm.disabled,
error: m.inputs[0].className.includes("ui-input--error"),
};
confirm.click();
await tick();
out.badGrid.saved = saved.length;
out.badGrid.open = m.modal.open;
// 표 배율 — 1.5 = 배율만 · 2.5 = 꺼짐
m = await open();
m.type(m.inputs[2], "2.5");
out.badTable = m.btn("확인").disabled;
m.type(m.inputs[2], "1.5");
m.btn("확인").click();
await tick();
out.applyTable = [...saved];
// 장 아님(표준 횡단면도) — 고르기 = 모든 장 하나 · 손대지 않고 [확인] = 저장 없이 닫음
saved.length = 0;
current = drawings[0];
m = await open();
out.notSheet = {
options: m.select.children.map((o) => o.textContent),
values: m.inputs.map((n) => n.value),
};
m.btn("확인").click();
await tick();
out.notSheet.saved = saved.length;
out.notSheet.closed = !m.modal.open;
// [취소] = 저장 없이 닫음
current = drawings[1];
m = await open();
m.type(m.inputs[0], "2");
m.btn("취소").click();
out.cancel = { saved: saved.length, closed: !m.modal.open };
process.stdout.write(JSON.stringify(out));
})();