Files
Aislo/resources/tester/helper_153_b07_cross_layout.cjs
T
eomsangdonandClaude Sonnet 5.5 835f9d9290 refactor(B07): 상세 설계 화면 페이지 자체 변종을 부품 상자 부품으로 — 도면 단추 · 장 번호 · 도구 단추 · 카드 · 띠 알림 · 값 목록 (PLAN 553-1)
- 도면 단추(날 ui-panel-btn) · 장 번호 · 도구 단추 = 단추 부품 · ⚙ = 아이콘 단추 꼴 · 평면도 줄 = 목록 줄 꼴
- 머리 = 패널 머리 + 배지 · 확정 건너뜀 = 띠 알림 · 빈 글 = 빈 상태 · 측점 정보 = 값 목록 · ⚙ 모달 그룹 상자 = 카드 · 종류 이름 칸 = 입력
- window.confirm 둘 → 확인창 부품 · 죽은 CSS 걷음 · 시험 헬퍼 부품 가짜 한 벌(helper_b07_part_stubs) · 153-1 옛 오류(removeAttribute) 고침 · 가이드 선택자 · 지킴 시험

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-26 22:29:22 +09:00

249 lines
8.8 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 { partStubs } = require("./helper_b07_part_stubs.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);
}
removeAttribute(key) {
delete this.attrs[key];
}
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 parts = partStubs((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, askConfirm: async () => true };
if (parts.modules[request]) return parts.modules[request];
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.endsWith(".css")) return {}; // 부품 CSS(552-3) — 트랜스파일 안 함
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));
})();