/* 계획노선 편집 창 **보던 화면 기억**(계획서 18 ③)과 노드 끼우기·빼기 단위검증 헬퍼 — * `B05_Profile_UI_RouteEdit_View.ts` · `_Edits.ts` 를 그 자리에서 트랜스파일해 Node 로 돌린다. * * ⚠ 확장자가 `.cjs` 인 까닭은 `helper_b05_cut_merged_loop.cjs` 머리에 적힌 것과 같다. * 브라우저 전용인 저장소·팔레트는 가짜로 채운다 — 셈만 본다. */ const fs = require("fs"); const path = require("path"); const ts = require(path.join(__dirname, "..", "..", "config", "node_modules", "typescript")); const ROOT = path.join(__dirname, "..", ".."); const cache = new Map(); // 브라우저 저장소 흉내 — `b_page_state` 가 읽고 쓰는 자리. 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; }, _map: map, }; } const local = memoryStorage(); globalThis.window = { localStorage: local, sessionStorage: memoryStorage() }; globalThis.fetch = () => Promise.resolve({ ok: false }); const STUBS = { "@ui/ui_template_palette": { themeColor: (_name, fallback) => fallback } }; function loadTs(file) { const full = path.resolve(file); 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) => { if (STUBS[request]) return STUBS[request]; 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); cache.set(full, box.exports); return box.exports; } const viewMod = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_View.ts")); const render = loadTs(path.join(ROOT, "B04_PreProcess", "B04_PreProcess_UI_MapRender.ts")); const edits = loadTs(path.join(ROOT, "B05_Profile", "logic", "B05_Profile_UI_RouteEdit_Edits.ts")); // 도엽 한 장 — 1200m × 900m 사업지 좌표. const meta = { x_min: 200000, y_min: 500000, width_meters: 1200, height_meters: 900, lon_min: 127, lon_max: 127.01, lat_min: 37, lat_max: 37.008, }; function viewOf(width, height, scale, offsetX, offsetY) { return { width, height, scale, offsetX, offsetY, mapRect: render.computeMapRect(meta, width, height), }; } // ① 한 창 크기에서 잡은 화면을 **다른 창 크기**로 되살린다 — 가운데 자리와 1m 당 픽셀이 같아야 한다. const before = viewOf(1000, 700, 3.2, -140, 85); const saved = viewMod.captureView(meta, before, (30 * Math.PI) / 180); const resized = viewOf(1400, 820, 1, 0, 0); const restored = { ...resized, ...viewMod.restoreSavedView(meta, resized, saved) }; const again = viewMod.captureView(meta, restored, (30 * Math.PI) / 180); // ② 저장소 왕복 — 적은 값을 같은 프로젝트로 다시 읽고, 다른 프로젝트에는 없다. const memory = viewMod.createViewMemory("p-1", () => saved); memory.flush(); const readBack = viewMod.readSavedView("p-1"); const otherProject = viewMod.readSavedView("p-2"); const storedKeys = [...local._map.keys()]; // ③ 보던 화면이 없으면 노선 전체에 맞춘다(회전 0). const fresh = viewMod.openingView("p-none", meta, viewOf(1000, 700, 1, 0, 0), [ [200300, 500300], [200600, 500500], ]); const withSaved = viewMod.openingView("p-1", meta, viewOf(1000, 700, 1, 0, 0), [ [saved.cx, saved.cy], ]); // ③-2 기억한 화면에 노선이 한 점도 안 보이면(밀려 저장된 화면) 버리고 노선에 맞춘다. const strayMemory = viewMod.createViewMemory("p-stray", () => ({ cx: 201150, cy: 500850, pxPerMeter: 8, rotationDeg: 45, })); strayMemory.flush(); const strayNodes = [ [200300, 500300], [200400, 500350], ]; const stray = viewMod.openingView("p-stray", meta, viewOf(1000, 700, 1, 0, 0), strayNodes); const strayOnScreen = strayNodes.some(([x, y]) => { const [px, py] = render.metricToScreen(meta, stray.view, x, y); return px >= 0 && px <= 1000 && py >= 0 && py <= 700; }); // ③-3 창 크기가 바뀌어도(편집 중 resize) 가운데 · 배율이 그대로 — 편집 창 `resize()` 와 같은 셈. const zoomed = viewOf(1500, 1000, 6, -900, 400); const keptBefore = viewMod.captureView(meta, zoomed, 0); const shrunk = viewOf(1280, 760, zoomed.scale, zoomed.offsetX, zoomed.offsetY); const refit = { ...shrunk, ...viewMod.restoreSavedView(meta, shrunk, keptBefore) }; const keptAfter = viewMod.captureView(meta, refit, 0); // ③-4 숨김(크기 0) 뒤 다시 보임(349-0h) — 크기 0 으로 셈한 화면은 엉뚱함 · 그 셈을 건너뛰고 // 떠나기 전 화면을 다시 보일 때 크기로 되살리면 그대로. const shown = viewOf(1500, 1000, 4, -600, 250); const keptShown = viewMod.captureView(meta, shown, 0); const zero = viewOf(0, 0, shown.scale, shown.offsetX, shown.offsetY); const zeroView = { ...zero, ...viewMod.restoreSavedView(meta, zero, keptShown) }; const zeroBack = viewOf(1500, 1000, 1, 0, 0); const fromZero = viewMod.captureView( meta, { ...zeroBack, ...viewMod.restoreSavedView(meta, zeroBack, viewMod.captureView(meta, zeroView, 0)), }, 0, ); const back = viewOf(1500, 1000, shown.scale, shown.offsetX, shown.offsetY); const keptBack = viewMod.captureView( meta, { ...back, ...viewMod.restoreSavedView(meta, back, keptShown), }, 0, ); // ④ 노드 끼우기·빼기 — 편집값 여섯 줄이 같은 자리에서 함께 움직인다. const columns = { planned: [ [0, 0], [10, 0], [20, 0], ], curveOn: [true, false, true], curveRadius: [null, 30, null], curveLock: [null, "radius", null], curveArc: [null, 12, null], apexLock: [false, true, false], }; edits.spliceNode(columns, 1, [5, 5]); const afterInsert = JSON.parse(JSON.stringify(columns)); edits.spliceNode(columns, 1); const afterRemove = JSON.parse(JSON.stringify(columns)); process.stdout.write( JSON.stringify({ saved, again, readBack, otherProject, storedKeys, fresh: { rotationRad: fresh.rotationRad, scale: fresh.view.scale }, withSaved: { rotationRad: withSaved.rotationRad }, stray: { rotationRad: stray.rotationRad, onScreen: strayOnScreen }, keptBefore, keptAfter, hidden: { keptShown, fromZero, keptBack }, afterInsert, afterRemove, }), );