/* 152-5 노면공 강조 헬퍼 — 포장층 범위 표식 · 고른 노면공 포장층만 is-active · 카드 누름(③ 덮는 구조물)에서 노면공 뺌을 * Node 로 돌려 표준출력 JSON 한 벌로. TS 는 그 자리에서 트랜스파일 · SVG 그릇은 쓰는 만큼만 흉내. */ 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.dataset = {}; this.attrs = {}; this.style = {}; this._classes = new Set(); const self = this; this.classList = { contains: (name) => self._classes.has(name), toggle: (name, on) => (on ? self._classes.add(name) : self._classes.delete(name)), }; } setAttribute(key, value) { this.attrs[key] = String(value); if (key === "class") this._classes = new Set(String(value).split(/\s+/).filter(Boolean)); } append(...kids) { this.children.push(...kids); } querySelectorAll(selector) { const name = selector.slice(1); const all = (node) => node.children.flatMap((kid) => [kid, ...all(kid)]); return all(this).filter((kid) => kid.classList.contains(name)); } } globalThis.document = { createElementNS: (_, tag) => new FakeNode(tag) }; function loadTs(file, stubs = {}) { const js = ts.transpileModule(fs.readFileSync(file, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; const localRequire = (request) => { if (stubs[request]) return stubs[request]; throw new Error(`stub 없음: ${request}`); }; new Function("exports", "module", "require", js)(box.exports, box, localRequire); return box.exports; } const surface = loadTs(path.join(ROOT, "B06_Section", "B06_Section_UI_Cross_Design_Surface.ts")); const selection = loadTs(path.join(ROOT, "A00_Common", "design", "design_selection.ts"), { "../b_page_state": { readState: () => null, writeState: () => {} }, }); const out = {}; // 포장층 — 같은 측점 노면공 둘(겹침) · 범위 표식 const svg = new FakeNode("svg"); const edge = (offset) => ({ offset_m: offset, elevation_m: 100 }); const design = { carriageway_edges: { left: edge(-2), right: edge(2) }, pavement_thickness_m: 0.2 }; const entry = (start, end) => ({ type_id: "pavement_concrete", spec: null, thickness_m: 0.2, start_m: start, end_m: end, widening_on: true, }); surface.appendPavementOverlay( svg, design, (v) => v, (v) => v, [entry(0, 40), entry(20, 60)], ); const layers = () => svg.querySelectorAll(".b06-chart__pavement"); const active = () => layers().map((layer) => layer.classList.contains("is-active")); out.marks = layers().map((layer) => ({ ...layer.dataset })); surface.highlightPavement(svg, { type_id: "pavement_concrete", start_m: 60, end_m: 20 }); out.pickSecond = active(); surface.highlightPavement(svg, { type_id: "gravel_surfacing", start_m: 20, end_m: 60 }); out.otherType = active(); surface.highlightPavement(svg, { type_id: "pavement_concrete", start_m: 0, end_m: 40 }); out.pickFirst = active(); surface.highlightPavement(svg, undefined); out.cleared = active(); // 카드 누름 — ③ 덮는 구조물에서 노면공 뺌 · ① id 길은 그대로 const pave = { structure_id: "pave", type_id: "pavement_concrete", start_m: 0, end_m: 40 }; const wall = { structure_id: "wall", type_id: "retaining_wall", start_m: 10, end_m: 30 }; const coverable = (s) => s.type_id !== "pavement_concrete"; const resolve = (sel, list, fn) => selection.resolveSelection(sel, [], list, fn); out.cover = { default: resolve({ chainageM: 5, structureId: null }, [pave]).structureId, skipped: resolve({ chainageM: 5, structureId: null }, [pave], coverable).structureId, wallFirst: resolve({ chainageM: 20, structureId: null }, [pave, wall], coverable).structureId, byId: resolve({ chainageM: 5, structureId: "pave" }, [pave], coverable).structureId, }; process.stdout.write(JSON.stringify(out));