Files
Aislo/resources/tester/helper_153_b07_cross_layout.cjs
T
eomsangdonandClaude Opus 5.5 47b396b53d feat(ui-kit): 묶음 부품 다섯 — 표 · ⋯ 메뉴 · 모달 · 확인창 · 설정 줄 · 파일 놓기 칸 (PLAN 552-3)
- 새 부품 파일: ui_template_table · setting_row · drop · createRowMenu(context_menu) · 모달 폭 단 · askConfirm · askText(window.prompt 대신) · confirmPanel
- 단추 = 552-2 단추 부품(⋯ 아이콘 단추 28 · 띠 알림 닫기 20 · 놓기 칸 [파일 고르기] 28)
- 대시보드 PageKit 의 dataTable · tableShell · tableRow · rowMenu · settingRow · settingCard · valueText = 부품을 다시 내보냄(한 벌 · 부름 꼴 그대로) · B01 옛 CSS 걷음
- showConfirmDialog 는 모달 부품으로 옮기고 본체가 다시 내보냄 · 모달 가림막 옛 보라 → --color-scrim
- 시험: test_552_3_* 다섯 더함 · 옮긴 함수 자리를 읽던 test_290_1 · test_505_2 고침 · 부품 CSS import 를 못 읽던 시험 도우미 넷(130 · 153 · 280_5 · 311)이 .css 를 건너뜀
- 전후 촬영: B01 여덟 화면 자리 같음 · ⋯ 아이콘만 1 px 왼쪽(단추 부품 가운데 맞춤)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 21:55:11 +09:00

243 lines
8.6 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.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));
})();