/* PLAN 346-2 덧 3 헬퍼 — 노선 초안 동안 횡단 카드 = 묶음 새 노선 상세 · 종단 알약 = follow 뒤 자리. * 진짜 TS(`B06_Section_UI_Page_Station_Swap` · `design_pipeline` · `design_store` · `B05_Profile_UI_Structures_Marks` · * `logic/B05_Profile_Structure_Follow`)를 그 자리에서 트랜스파일해 Node 로 돌림. 페이지 묶음 창구 * (`design_pipeline_page` — pagePipeline · pipelineDetail)와 알약 이름 · 벌룬 · 서버 창구만 가짜. * 결과 = 표준출력 JSON. 짝 = `test_346_2_draft_cross_pills.py`. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); const P = "p3463"; const R = 7; function memoryStorage() { const map = new Map(); return { getItem: (key) => (map.has(key) ? map.get(key) : null), setItem: (key, value) => map.set(key, String(value)), removeItem: (key) => map.delete(key), key: (index) => [...map.keys()][index] ?? null, get length() { return map.size; }, }; } globalThis.window = globalThis; globalThis.sessionStorage = memoryStorage(); globalThis.localStorage = memoryStorage(); localStorage.setItem("frd_current_project_id", P); const element = () => { const node = { style: {}, dataset: {}, children: [], classList: { toggle: () => undefined, add: () => undefined }, append: (...items) => node.children.push(...items), prepend: (item) => node.children.unshift(item), addEventListener: () => undefined, }; return node; }; globalThis.document = { createElement: element }; /* 페이지 묶음 창구 가짜 — 묶음은 진짜(`createDesignPipeline`) · 지도는 시험이 쥠 */ const fake = { pipe: null, detail: null }; const STUBS = { design_pipeline_page: { pagePipeline: (id) => (id === P ? fake.pipe : null), pipelineDetail: (id) => (id === P ? fake.detail : null), }, B06_Section_Section_Store: { onAnySwap: () => () => undefined, swapSectionStations: async () => null, }, touch_bridge: { pickPx: () => 14 }, B05_Profile_Api_Structures: { structureAnchorM: (s) => s.chainage_m ?? s.start_m ?? 0, }, B05_Profile_UI_Structures_List: { etcName: () => null, numberDuplicateNames: (names) => names }, B05_Profile_UI_Structures_Balloon: { balloonLines: () => [] }, common_util_structure_catalog: { catalogRow: (id) => (id === "wall" ? { placement: "interval", owner: "structures" } : null), }, }; const cache = new Map(); function loadTs(full) { if (cache.has(full)) return cache.get(full); const js = ts.transpileModule(fs.readFileSync(full, "utf8"), { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 }, }).outputText; const box = { exports: {} }; cache.set(full, box.exports); const localRequire = (request) => { const key = Object.keys(STUBS).find((name) => request.endsWith(name)); if (key) return STUBS[key]; if (request.endsWith("config_frontend") || request.startsWith("@config/")) return { CURRENT_PROJECT_ID_KEY: "frd_current_project_id", API_BASE_URL: "http://fake" }; if (request.startsWith("@util/")) return loadTs(path.join(ROOT, "common_util", `${request.slice(6)}.ts`)); if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`)); return require(request); }; new Function("exports", "module", "require", js)(box.exports, box, localRequire); return box.exports; } const load = (...parts) => loadTs(path.join(ROOT, ...parts)); const pipeMod = load("A00_Common", "design", "logic", "design_pipeline.ts"); const storeMod = load("A00_Common", "design", "design_store.ts"); const swap = load("B06_Section", "B06_Section_UI_Page_Station_Swap.ts"); const marks = load("B05_Profile", "B05_Profile_UI_Structures_Marks.ts"); const follow = load("B05_Profile", "logic", "B05_Profile_Structure_Follow.ts"); const tick = (ms = 30) => new Promise((resolve) => setTimeout(resolve, ms)); const out = {}; async function main() { /* 1. 횡단 카드 — 저장 노선 → 노선 초안(새 노선 상세) → 숨은 동안 안 그림 → 초안 버림(저장 노선) */ { const saved = { tag: "saved", cross_sections: [] }; const drawn = []; let shown = true; const before = swap.shownDetail(P, saved).tag; // 횡단 보기가 먼저 섬 — 묶음은 아직 없음(종단 보기가 세움) const stop = swap.followSwaps( P, () => R, { visible: () => shown }, async () => void drawn.push(swap.shownDetail(P, saved).tag), ); const pipe = pipeMod.createDesignPipeline({ stages: { profile: (ctx) => ({ grade: ctx.inputs.alignment }), cross: () => ({ byKey: {} }), }, }); // 페이지 묶음과 같은 차례 — 가게 잇기 · 새 노선 상세(초안 동안만) · 그다음 듣는 쪽 pipe.subscribe(pipeMod.publishToStore(storeMod.useDesignStore(P, null))); pipe.subscribe((event) => { if (event.kind !== "stage" || (event.stage !== "profile" && event.stage !== "cross")) return; fake.detail = pipe.input("route") ? { tag: `draft-${event.stage}-${event.generation}`, cross_sections: [] } : null; }); fake.pipe = pipe; await pipe.start({ route: { rev: 1 }, alignment: 1, cross: null, pipes: null, structures: null, }); await tick(); const afterDraft = [...drawn]; const draftNow = swap.shownDetail(P, saved).tag; shown = false; await pipe.update("grade", 2); await tick(); const whileHidden = [...drawn]; const hiddenNow = swap.shownDetail(P, saved).tag; shown = true; await pipe.update("route", null); await tick(); await pipe.update("grade", 3); await tick(); const discarded = [...drawn]; const savedNow = swap.shownDetail(P, saved).tag; stop(); await pipe.update("route", { rev: 2 }); await pipe.update("grade", 4); await tick(); out.cards = { before, afterDraft, draftNow, whileHidden, hiddenNow, discarded, savedNow, afterStop: drawn.length, }; pipe.dispose(); fake.pipe = null; fake.detail = null; } /* 2. 종단 알약 — follow 뒤 자리(구조물 id 짝 · 관은 서버 id 와 세션 id 가 달라 차례 짝 · 구간형 띠도) */ { const pill = (structure_id, type_id, chainage_m, start_m = null, end_m = null) => ({ structure_id, type_id, placement: start_m === null ? "point" : "interval", chainage_m, start_m, end_m, options: {}, }); const pills = [ pill("pipe-s1", "pipe", 30), pill("st-1", "gabion", 50), pill("st-2", "wall", 70, 60, 80), pill("pipe-s2", "pipe", 90), pill("st-9", "gabion", 120), ]; const inputs = { route: { rev: 3 }, structures: [ { structure_id: "st-1", chainage_m: 55 }, { structure_id: "st-2", chainage_m: 72, start_m: 62, end_m: 82 }, ], pipes: [ { id: "PB", chainage_m: 96 }, { id: "PA", chainage_m: 33 }, ], }; fake.pipe = { input: (key) => inputs[key] ?? null }; const lane = () => { const root = marks.buildStructureLane({ structures: pills, types: [], x: (chainage) => chainage * 10, chainageAt: (px) => px / 10, maxChainageM: 200, widthPx: 2000, axisWidthPx: 40, stationIntervalM: 20, selectedId: null, onSelect: () => undefined, onMove: () => undefined, }); const left = {}; const band = []; for (const node of root.children) { if (node.dataset.structureId) left[node.dataset.structureId] = parseFloat(node.style.left); if (node.className?.startsWith("b05-structure__band")) band.push(parseFloat(node.style.left), parseFloat(node.style.width)); } return { left, band }; }; const draft = lane(); inputs.route = null; // 초안 버림 — 저장 자리 const saved = lane(); // 관 수가 안 맞으면 관 알약은 안 옮김(잘못 짝짓지 않음) const short = follow.followedMarks(pills, inputs.structures, [{ id: "PA", chainage_m: 33 }]); out.pills = { draft, saved, short: short.map((s) => s.chainage_m), untouched: pills[1].chainage_m, }; fake.pipe = null; } process.stdout.write(JSON.stringify(out)); } main().catch((error) => { console.error(error); process.exit(1); });