Files
Aislo/resources/tester/helper_b05_routeedit_view.cjs
T
eomsangdonandClaude Opus 5.5 08e5176f7b fix(B05): 계획노선 편집 빈 화면 — 창 크기가 바뀌어도 가운데 · 배율 유지 · 노선 안 보이는 기억은 버림(73-15)
- resize 가 px 이동량을 그대로 두고 지도 틀만 다시 잡아 지도가 밀림 → 밀린 자리가 보던 화면으로 저장돼 다시 열어도 빈 화면
- 63-1 로 편집 창이 화면 크기를 따라가게 되면서 창 크기 변경마다 밀림
- 창을 열 때 기억한 화면에 노선이 한 점도 없으면 노선 전체 맞춤으로 엶(이미 밀려 저장된 값 복구)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PSXsvyDxjNmufteN5bqmt
2026-10-07 09:32:07 +09:00

159 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 계획노선 편집 창 **보던 화면 기억**(계획서 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", "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);
// ④ 노드 끼우기·빼기 — 편집값 여섯 줄이 같은 자리에서 함께 움직인다.
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,
afterInsert,
afterRemove,
}),
);