/* 345-2 3D 큰 모달 헬퍼 — `design_view_profile_3d_modal.ts` 를 트랜스파일해 가짜 DOM · 가짜 뷰어로 * 닫힌 동안 그리기 0 · 열면 그림 · 닫으면 숨긴 자리로 되돌림 · 지형은 처음 열 때 한 번 · 버림은 떠날 때만을 본다. * 결과는 JSON 한 벌(표준출력). */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); class FakeNode { constructor(tag) { this.tagName = tag; this.children = []; this.parent = null; this.hidden = false; this.title = ""; this.listeners = {}; const classes = new Set(); this.classList = { add: (...n) => n.forEach((x) => classes.add(x)), contains: (n) => classes.has(n), }; this._classes = classes; } set className(v) { this._classes.clear(); String(v) .split(/\s+/) .filter(Boolean) .forEach((x) => this._classes.add(x)); } append(...kids) { kids.forEach((kid) => { if (kid.parent) kid.parent.children = kid.parent.children.filter((c) => c !== kid); kid.parent = this; this.children.push(kid); }); } remove() { if (this.parent) this.parent.children = this.parent.children.filter((c) => c !== this); this.parent = null; } addEventListener(type, fn) { (this.listeners[type] ??= []).push(fn); } click() { (this.listeners.click ?? []).forEach((fn) => fn({})); } } global.document = { createElement: (tag) => new FakeNode(tag) }; const modals = []; const STUBS = { "@ui/ui_template_elements": { createButton: ({ label, onClick }) => { const button = new FakeNode("button"); button.label = label; if (onClick) button.addEventListener("click", onClick); return button; }, }, "@ui/ui_template_modal": { openModal: (opts) => { const body = new FakeNode("div"); const record = { opts, body, open: true }; opts.mount(body, () => (record.open = false), new FakeNode("div")); record.close = () => (record.open = false); modals.push(record); return { close: record.close }; }, }, "@ui/ui_template_progress": { createProgressCircle: () => { const root = new FakeNode("div"); return { root, set: () => undefined, remove: () => root.remove() }; }, }, }; function loadTs(file) { const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; const localRequire = (request) => STUBS[request] ?? require(request); new Function("exports", "module", "require", js)(box.exports, box, localRequire); return box.exports; } const { createViewer3dModal } = loadTs( path.join(ROOT, "A00_Common", "design", "design_view_profile_3d_modal.ts"), ); // 가짜 뷰어 — 만들면 그리기 루프가 돎(진짜와 같음) · setActive 가 루프를 켜고 끔. const viewer = { root: new FakeNode("div"), running: true, draws: 0, disposed: false, setActive(active) { this.running = active; }, dispose() { this.disposed = true; }, }; const frames = (n) => { for (let i = 0; i < n; i += 1) if (viewer.running) viewer.draws += 1; }; let terrainLoads = 0; let resolveTerrain; let opens = 0; // 345-4 — 열 때(지형 뒤) 낡은 3D 길 모형 다시 짓기 부름 const view3d = createViewer3dModal({ viewer, loadTerrain: () => { terrainLoads += 1; return new Promise((resolve) => (resolveTerrain = resolve)); }, visible: () => true, onOpen: () => (opens += 1), }); (async () => { const out = {}; frames(10); out.closedDraws = viewer.draws; out.inPark = viewer.root.parent === view3d.park && view3d.park.hidden; out.terrainBeforeOpen = terrainLoads; view3d.button.click(); const first = modals[0]; out.wide = first.opts.wide === true; out.inModal = viewer.root.parent === first.body; await new Promise((r) => setImmediate(r)); out.opensBeforeTerrain = opens; out.spinnerWhileLoading = viewer.root.children.length === 1; frames(3); out.openDraws = viewer.draws; resolveTerrain(); await new Promise((r) => setImmediate(r)); out.spinnerGone = viewer.root.children.length === 0; out.opensAfterTerrain = opens; // 닫기 단추 · Esc · 바깥 누름 = beforeClose 뒤 닫힘(공용 모달 규칙) out.beforeCloseOk = (await first.opts.beforeClose()) === true; first.close(); frames(10); out.afterCloseDraws = viewer.draws; out.backInPark = viewer.root.parent === view3d.park; out.disposedOnClose = viewer.disposed; await new Promise((r) => setImmediate(r)); out.opensWhileClosed = opens; view3d.open(); view3d.open(); // 열린 동안 또 눌러도 하나 out.modalCount = modals.length; out.terrainLoads = terrainLoads; frames(2); out.reopenDraws = viewer.draws; await new Promise((r) => setImmediate(r)); out.opensAfterReopen = opens; view3d.dispose(); // 페이지 떠남 — 열려 있으면 닫고 버림 out.closedOnDispose = modals[1].open === false; out.disposed = viewer.disposed; out.runningAfterDispose = viewer.running; // 지형 받는 사이 닫으면 — 다시 짓기 안 부름(닫힌 동안 짓기 0) let lateOpens = 0; let lateResolve; const late = createViewer3dModal({ viewer: { root: new FakeNode("div"), setActive() {}, dispose() {} }, loadTerrain: () => new Promise((resolve) => (lateResolve = resolve)), visible: () => true, onOpen: () => (lateOpens += 1), }); late.open(); late.close(); lateResolve(); await new Promise((r) => setImmediate(r)); out.closedBeforeTerrainOpens = lateOpens; console.log(JSON.stringify(out)); })();