/* 313 [구조물 전체 삭제] 헬퍼 — 진짜 TS(목록 그리기 · 입력 배선)를 Node 로 돌려 표준출력 JSON 한 벌로. * 확인창(@ui showConfirmDialog)은 답을 정해 둔 가짜로 막음. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const CATALOG = require("./helper_322_catalog_module.cjs"); // 구조물 표(322-1) const ROOT = path.join(__dirname, "..", ".."); const cache = new Map(); const confirms = []; let answer = true; const UI = { showConfirmDialog: async (message, ok) => { confirms.push([message, ok]); return answer; }, showToast: () => {}, }; const load = (file) => { if (cache.has(file)) return cache.get(file); const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; cache.set(file, box.exports); const near = (m) => { if (CATALOG.isCatalogRequest(m)) return CATALOG; if (m.startsWith("@ui/")) return UI; if (m.startsWith("@config/")) return load(path.join(ROOT, "config", `${m.slice(8)}.ts`)); if (m.startsWith("@util/")) return load(path.join(ROOT, "common_util", `${m.slice(6)}.ts`)); if (m.endsWith(".json")) return require(path.join(path.dirname(file), m)); if (m.startsWith(".")) return load(path.join(path.dirname(file), `${m}.ts`)); return new Proxy({}, { get: () => () => {} }); }; new Function("exports", "module", "require", js)(box.exports, box, near); return box.exports; }; // DOM 흉내 — 목록이 쓰는 것만 class Node { constructor(tag) { this.tag = tag; this.children = []; this.className = ""; this.textContent = ""; this.style = { setProperty() {} }; this.listeners = {}; this.title = ""; } get classList() { const names = () => this.className.split(" ").filter(Boolean); return { add: (c) => (this.className = [...names(), c].join(" ")), toggle: (c, on) => { const rest = names().filter((n) => n !== c); this.className = (on ? [...rest, c] : rest).join(" "); }, contains: (c) => names().includes(c), }; } append(...kids) { this.children.push(...kids); } replaceChildren(...kids) { this.children = [...kids]; } addEventListener(type, fn) { this.listeners[type] = fn; } scrollIntoView() {} } globalThis.document = { createElement: (tag) => new Node(tag), activeElement: null }; globalThis.requestAnimationFrame = (fn) => fn(); const memory = new Map(); globalThis.window = { sessionStorage: { getItem: (k) => memory.get(k) ?? null, setItem: (k, v) => memory.set(k, String(v)), removeItem: (k) => memory.delete(k), }, requestAnimationFrame: (fn) => fn(), }; globalThis.sessionStorage = globalThis.window.sessionStorage; const B05 = (f) => load(path.join(ROOT, "B05_Profile", `${f}.ts`)); const { renderStructureList } = B05("B05_Profile_UI_Structures_List"); const { bindStructuresEvents } = B05("B05_Profile_UI_Structures_Panel_Events"); const typeMap = new Map([ ["sign", { type_id: "sign", name: "표지", group: "E" }], ["pipe", { type_id: "pipe", name: "배수관", group: "A" }], ]); const sign = { structure_id: "s1", type_id: "sign", placement: "point", chainage_m: 40 }; const pipe = { chainage_m: 80, facility: "pipe", source: "auto" }; const draw = (structures, pipes, withClear = true) => { const list = new Node("ul"); let cleared = 0; renderStructureList({ list, structures, pipeFacilities: pipes, typeMap, intervalM: 20, editingId: null, selectedPipeChainage: null, onSelectStructure: () => {}, onSelectPipe: () => {}, ...(withClear ? { onClearAll: () => (cleared += 1) } : {}), }); const last = list.children.at(-1); const button = last?.children?.[0]; const clear = last?.className === "b05-route__irregular-clear"; if (clear) button.listeners.click(); return { rows: list.children.length, lastClass: last?.className ?? null, button: clear ? { text: button.textContent, className: button.className, type: button.type } : null, cleared, }; }; const listCases = { filled: draw([sign], [pipe]), empty: draw([], []), noHandler: draw([sign], [pipe], false), }; // 입력 배선 — 가짜 ctx(누름 듣개는 담기만) const el = () => new Node("div"); const makeCtx = (structures, pipes) => { const calls = { reset: 0, release: 0, removed: [], emit: 0 }; let pipeList = pipes.map((p) => ({ ...p })); const ctx = { groupSelect: el(), typeSelect: el(), primary: el(), removeButton: el(), resetButton: el(), positionRow: el(), memoField: el(), optionRow: el(), optionInputs: [], anchorFields: { station: el(), remainder: el() }, startFields: { station: el(), remainder: el() }, endFields: { station: el(), remainder: el() }, facilityOptions: { root: el() }, structures, pipeFacilities: () => pipeList, projectId: "p", callbacks: { onListRelease: () => (calls.release += 1), onPipeRemove: (at) => { calls.removed.push(at); pipeList = pipeList.filter((p) => p.chainage_m !== at); // 하나씩 빼기 길이 목록을 갈아 끼움 }, }, resetForm: () => (calls.reset += 1), emit: () => (calls.emit += 1), }; return { ctx, calls, left: () => structures.length + pipeList.length }; }; const runClear = async (structures, pipes, ok) => { answer = ok; confirms.length = 0; const { ctx, calls, left } = makeCtx(structures, pipes); const { clearAll } = bindStructuresEvents(ctx); await clearAll(); return { confirms: [...confirms], calls, left: left() }; }; (async () => { const pipes = [pipe, { chainage_m: 160.37, facility: "ford_pavement", source: "user" }]; const result = { listCases, confirmed: await runClear([{ ...sign }, { ...sign, structure_id: "s2" }], pipes, true), cancelled: await runClear([{ ...sign }], pipes, false), pipesOnly: await runClear([], [pipe], true), nothing: await runClear([], [], true), }; process.stdout.write(JSON.stringify(result)); })();