- ProfileViewState.corridorPending(표시와 한 칸) · sections.setCorridorPending · 모달 onOpen(지형 선 뒤 · 그 사이 닫으면 안 부름) - 시험: test_345_4_corridor_worker 4(워커 = 주 스레드 · transfer 9/9 · 저장본 먼저) · test_345_2_3d_modal 5(닫힌 동안 짓기 0) · 관련 310 통과 · typecheck 내 파일 0 오류 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VmC34fsxxbweEG3RUjs3Mn
186 lines
5.7 KiB
JavaScript
186 lines
5.7 KiB
JavaScript
/* 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));
|
|
})();
|