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
This commit is contained in:
2026-10-07 09:32:07 +09:00
co-authored by Claude Opus 5.5
parent 689216e227
commit 08e5176f7b
4 changed files with 56 additions and 2 deletions
+5
View File
@@ -48,6 +48,7 @@ import {
createViewMemory,
fitCanvas,
openingView,
restoreSavedView,
screenToMetric,
viewPxPerMeter,
} from "./B05_Profile_UI_RouteEdit_View";
@@ -232,8 +233,12 @@ export async function openRouteEditModal(
function resize(): void {
if (closed) return;
// 크기가 바뀌어도 가운데 자리 · 배율을 지킨다 — px 이동량을 그대로 두면 지도가 밀려
// 노선이 화면 밖으로 나가고, 그 자리가 보던 화면으로 저장돼 다시 열어도 빈 화면이 된다.
const kept = meta && viewReady ? captureView(meta, view, rotation.radians()) : null;
const { width, height } = fitCanvas(canvas, context);
view = { ...view, width, height, mapRect: computeMapRect(meta, width, height) };
if (meta && kept) view = { ...view, ...restoreSavedView(meta, view, kept) };
draw();
}
window.addEventListener("resize", resize);
+9 -1
View File
@@ -108,7 +108,15 @@ export function openingView(
const saved = readSavedView(projectId);
if (saved) {
const rotationRad = (saved.rotationDeg * Math.PI) / 180;
return { view: { ...view, ...restoreSavedView(meta, view, saved) }, rotationRad };
const restored: ViewState = { ...view, ...restoreSavedView(meta, view, saved) };
// 노선이 한 점도 안 보이는 기억(밀려 저장된 화면)은 버린다 — 빈 화면으로 열리지 않게.
// 가운데에서 짧은 변 절반 안 — 회전축이 가운데라 돌려도 같은 판정이다.
const reach = Math.min(view.width, view.height) / 2;
const seen = nodes.some(([x, y]) => {
const [px, py] = metricToScreen(meta, restored, x, y);
return Math.hypot(px - view.width / 2, py - view.height / 2) < reach;
});
if (seen) return { view: restored, rotationRad };
}
const xs = nodes.map((vertex) => vertex[0]);
const ys = nodes.map((vertex) => vertex[1]);
+30 -1
View File
@@ -94,7 +94,33 @@ 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), [[200300, 500300]]);
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 = {
@@ -123,6 +149,9 @@ process.stdout.write(
storedKeys,
fresh: { rotationRad: fresh.rotationRad, scale: fresh.view.scale },
withSaved: { rotationRad: withSaved.rotationRad },
stray: { rotationRad: stray.rotationRad, onScreen: strayOnScreen },
keptBefore,
keptAfter,
afterInsert,
afterRemove,
}),
@@ -49,6 +49,18 @@ def test_보던_화면이_없으면_노선에_맞추고_회전_0(results):
assert results["withSaved"]["rotationRad"] == pytest.approx(math.radians(30))
def test_노선이_안_보이는_기억은_버리고_노선에_맞춤(results):
assert results["stray"]["rotationRad"] == 0
assert results["stray"]["onScreen"] is True
def test_창_크기가_바뀌어도_가운데와_배율_그대로(results):
before, after = results["keptBefore"], results["keptAfter"]
assert after["cx"] == pytest.approx(before["cx"], abs=1e-6)
assert after["cy"] == pytest.approx(before["cy"], abs=1e-6)
assert after["pxPerMeter"] == pytest.approx(before["pxPerMeter"], rel=1e-9)
def test_노드_끼우기_빼기는_편집값이_같은_자리로(results):
inserted = results["afterInsert"]
assert inserted["planned"][1] == [5, 5]