Files
Aislo/B05_Profile/logic/B05_Profile_RouteEdit_Visible.ts
T
eomsangdonandClaude Opus 5.5 4a55092bf8 feat(B05 노선편집): 345-5 덧 뜬 판 가려짐 정리 — 노선 맞춤 = 뜬 종단 판 위 보이는 영역 가운데 · 상태 · 범례 · 잰 값 글판 = 판 바로 위(판 높이 끌기 · 접기 따라감)
- 새 logic/B05_Profile_RouteEdit_Visible(덮인 높이 · 보이는 영역 맞춤 — 덮인 높이 0 이면 computeRouteView 와 같음)
- 새 _RouteEdit_Cover(칸 · 판 ResizeObserver → --b05-routeedit-cover) · fitCanvas 가 편집 평면 칸만 붙임
- 보던 화면 되살림도 판 밑 노드는 안 보인 것으로
- 시험: test_345_5b_plan_visible_area 2 · 관련 68 통과 · typecheck 0

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

61 lines
2.5 KiB
TypeScript

/* =============================================================================
* B05_Profile_RouteEdit_Visible.ts
* 노선 편집 평면의 **보이는 영역**(345-5 덧) — 아래 뜬 종단 판이 덮은 높이 · 그 위 영역에 노선 맞춤.
*
* 덮인 높이 = 칸 아래 끝 − 판 위 끝(0 ~ 칸 높이) · 판 없음 · 접힘(높이 0)이면 0.
* 맞춤 = B04 `computeRouteView` 와 같은 공식 · 세로 몫만 보이는 높이 비율로 줄이고 가운데를 그 영역
* 가운데로 · 덮인 높이 0 이면 `computeRouteView` 와 같은 값.
* ========================================================================== */
export interface VerticalSpan {
top: number;
bottom: number;
}
/** 칸(`wrap`) 아래쪽을 판(`panel`)이 덮은 높이(px). */
export function coveredBottomPx(wrap: VerticalSpan, panel: VerticalSpan | null): number {
if (!panel || panel.bottom - panel.top <= 1) return 0; // 없음 · 접힘(높이 0 + 테두리)
const height = Math.max(wrap.bottom - wrap.top, 0);
return Math.min(Math.max(wrap.bottom - panel.top, 0), height);
}
export interface FitInput {
/** 도엽 크기(m) — `meta.width_meters` · `height_meters`. */
mapWidthM: number;
mapHeightM: number;
/** 배율 1 일 때 도엽이 놓이는 칸 안 사각형(`computeMapRect`). */
mapRect: { x: number; y: number; width: number; height: number };
/** 칸 크기(px) · 아래 덮인 높이(px). */
width: number;
height: number;
coveredPx: number;
/** 노선 가운데 · 크기(m) — 도엽 왼쪽 아래 기준 · 여유 포함. */
centerXM: number;
centerYM: number;
wantWidthM: number;
wantHeightM: number;
}
/** 노선(+여유)이 보이는 영역 가운데 들도록 배율 · 이동량. */
export function fitRouteToVisible(input: FitInput): {
scale: number;
offsetX: number;
offsetY: number;
} {
const { mapRect, width, height } = input;
const ratio = height > 0 ? Math.max(height - input.coveredPx, 1) / height : 1;
const fit = Math.min(
input.mapWidthM / input.wantWidthM,
(input.mapHeightM / input.wantHeightM) * ratio,
);
// 하한 1(빈 여백 없음 · computeRouteView) — 덮인 만큼 내려 줌(노선이 판 밑에 안 숨게).
const scale = Math.max(fit, Math.min(1, ratio));
const baseX = mapRect.x + (input.centerXM / input.mapWidthM) * mapRect.width;
const baseY = mapRect.y + (1 - input.centerYM / input.mapHeightM) * mapRect.height;
return {
scale,
offsetX: -(baseX - width / 2) * scale,
offsetY: -(baseY - height / 2) * scale - input.coveredPx / 2,
};
}