Files
Aislo/resources/tester/helper_b05_routeedit_view.cjs
T
eomsangdonandClaude Sonnet 5.5 fdd93cdb2d test(508-1b 옛 헬퍼 공용 불러오기 6/7): 헬퍼 28 곳 loadTs 베낌 걷고 makeLoader 로
- 헬퍼 동작 그대로(풀이 함수 본문은 옮겨 담음) · 제품 코드 안 고침
- 확인: 이 묶음 헬퍼를 쓰는 시험 428 개 전 417 통과 → 후 417 통과 (시험별 junit 대조 · 어긋남 0)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-21 17:24:27 +09:00

218 lines
7.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 { makeLoader } = require("./helper_ts_stub_loader.cjs");
const ROOT = path.join(__dirname, "..", "..");
// 브라우저 저장소 흉내 — `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 } };
const { loadTs } = makeLoader({
root: ROOT,
requireFor: (full) => (request) => {
if (STUBS[request]) return STUBS[request];
if (request.startsWith(".")) return loadTs(path.join(path.dirname(full), `${request}.ts`));
return require(request);
},
});
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));
// ⑤ Ctrl+휠 = 5° 돌리기(390-3) · 손가락 · 트랙패드 벌림 · 맨 휠 = 확대 · rotateWheel 없으면(추천 지도) 확대
const input = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Input.ts"));
const rotateMod = loadTs(path.join(ROOT, "B05_Profile", "B05_Profile_UI_RouteEdit_Rotate.ts"));
function wheelCase(withRotate, event) {
const handlers = {};
const canvas = {
addEventListener: (name, fn) => (handlers[name] = fn),
getBoundingClientRect: () => ({ left: 0, top: 0, width: 400, height: 300 }),
};
let wheelView = {
width: 400,
height: 300,
scale: 1,
offsetX: 0,
offsetY: 0,
mapRect: { width: 400 },
};
let changes = 0;
const rotation = rotateMod.createMapRotation({
overlay: { querySelector: () => null },
size: () => wheelView,
onChange: () => (changes += 1),
});
input.bindRouteEditNavigation({
canvas,
getView: () => wheelView,
setView: (next) => (wheelView = next),
getMeta: () => null,
rotateWheel: withRotate ? rotation.wheel : undefined,
draw: () => {},
});
handlers.wheel({ clientX: 200, clientY: 150, preventDefault: () => {}, ...event });
return { deg: (rotation.radians() * 180) / Math.PI, scale: wheelView.scale, changes };
}
const wheel = {
ctrlDown: wheelCase(true, { ctrlKey: true, deltaY: 100 }),
ctrlUp: wheelCase(true, { ctrlKey: true, deltaY: -100 }),
trackpad: wheelCase(true, { ctrlKey: true, deltaY: -4 }),
plain: wheelCase(true, { ctrlKey: false, deltaY: -100 }),
recommend: wheelCase(false, { ctrlKey: true, deltaY: 100 }),
};
process.stdout.write(
JSON.stringify({
wheel,
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,
}),
);