feat(B05 노선편집): 345-5 덧 2 곡선 바 판 위 · [맞춤] 단추 — 꺾임점 곡선 바 아래 한계 = 뜬 종단 판 바로 위(판 끌기 · 접기 따라 다시 자리) · 도구줄 [맞춤] = 노선 전체를 판 위 보이는 영역에(회전 0)
- _RouteEdit_View.fitView(처음 열기 · [맞춤] 한 벌) · _RouteEdit_Cover.onCoverChange(덮인 높이 바뀜 알림 · 떠난 편집기는 뗌) - 시험: test_345_5b_plan_visible_area 3 · 관련 81 통과 · typecheck 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Se7ytqbohmBQnfYpwYFnYf
This commit is contained in:
@@ -54,6 +54,7 @@ import {
|
||||
captureView,
|
||||
createViewMemory,
|
||||
fitCanvas,
|
||||
fitView,
|
||||
openingView,
|
||||
restoreSavedView,
|
||||
screenToMetric,
|
||||
@@ -308,6 +309,13 @@ export async function mountRouteEditor(
|
||||
rotation.set(opening.rotationRad);
|
||||
viewReady = true;
|
||||
}
|
||||
// [맞춤] — 노선 전체를 뜬 종단 판 위 보이는 영역에(회전 0 · 345-5 덧 2). 셈은 `_View` 몫.
|
||||
overlay.querySelector('[data-act="fit"]')!.addEventListener("click", () => {
|
||||
if (!meta || view.width <= 0 || !planned.length) return;
|
||||
view = fitView(meta, view, planned);
|
||||
rotation.set(0);
|
||||
draw();
|
||||
});
|
||||
|
||||
const toScreen = (vertex: Vertex): [number, number] =>
|
||||
meta ? metricToScreen(meta, view, vertex[0], vertex[1]) : [0, 0];
|
||||
|
||||
@@ -65,6 +65,8 @@ export function createRouteEditChrome(
|
||||
<option value="sheet">수치지형도</option>
|
||||
</select>
|
||||
</label>
|
||||
<button type="button" class="ui-btn ui-btn--ghost" data-act="fit"
|
||||
title="노선 전체를 종단 판 위 보이는 영역에 맞춥니다 (회전은 풂)">맞춤</button>
|
||||
<button type="button" class="ui-btn ui-btn--ghost" data-act="contour-minor"
|
||||
aria-pressed="false" disabled
|
||||
title="1m 간격 등고선을 얇고 옅게 겹칩니다 (확대하면 보임)">1m 등고선</button>
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* 노선 편집 평면 아래를 **뜬 종단 판**이 덮은 높이를 잰다(345-5 덧).
|
||||
*
|
||||
* 칸 · 판 크기가 바뀔 때마다(창 · 판 높이 끌기 · 접기 · 새 창 떼기) 다시 재서
|
||||
* 칸에 `--b05-routeedit-cover`(px)를 적는다 — 상태 · 범례 · 잰 값 글판이 판 바로 위로 오름.
|
||||
* 맞춤(`_View.openingView`)은 `coveredPx()` 를 읽는다. 셈은 `logic/_RouteEdit_Visible`.
|
||||
* 칸에 `--b05-routeedit-cover`(px)를 적는다 — 상태 · 범례 · 잰 값 글판이 판 바로 위로 오름 ·
|
||||
* 곡선 바는 `onCoverChange` 로 다시 자리 잡음.
|
||||
* 맞춤(`_View.fitView`)은 `coveredPx()` 를 읽는다. 셈은 `logic/_RouteEdit_Visible`.
|
||||
* ========================================================================== */
|
||||
|
||||
import { coveredBottomPx } from "./logic/B05_Profile_RouteEdit_Visible";
|
||||
@@ -12,12 +13,18 @@ import { coveredBottomPx } from "./logic/B05_Profile_RouteEdit_Visible";
|
||||
// ponytail: 편집기는 페이지에 하나 — 덮인 높이 한 값 · 둘이 되면 칸마다 값으로.
|
||||
let covered = 0;
|
||||
const watching = new WeakSet<HTMLElement>();
|
||||
const listeners = new Set<() => boolean>();
|
||||
|
||||
/** 지금 평면 아래 덮인 높이(px). */
|
||||
export function coveredPx(): number {
|
||||
return covered;
|
||||
}
|
||||
|
||||
/** 덮인 높이가 바뀌면 부름(곡선 바 자리 · 345-5 덧 2) — `false` 를 돌려주면 뗌(떠난 편집기). */
|
||||
export function onCoverChange(listener: () => boolean): void {
|
||||
listeners.add(listener);
|
||||
}
|
||||
|
||||
/** 같은 가운데 칸의 종단 판 — 새 창으로 떼면 없음. */
|
||||
function panelOf(wrap: HTMLElement): HTMLElement | null {
|
||||
return (
|
||||
@@ -31,8 +38,11 @@ function span(element: HTMLElement): { top: number; bottom: number } {
|
||||
}
|
||||
|
||||
function measure(wrap: HTMLElement, panel: HTMLElement | null): void {
|
||||
const before = covered;
|
||||
covered = coveredBottomPx(span(wrap), panel ? span(panel) : null);
|
||||
wrap.style.setProperty("--b05-routeedit-cover", `${covered}px`);
|
||||
if (covered !== before)
|
||||
for (const listener of listeners) if (!listener()) listeners.delete(listener);
|
||||
}
|
||||
|
||||
/** 칸을 지켜본다(여러 번 불러도 한 번만 붙음) · 부를 때마다 한 번 잰다. */
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
createCurveLabel,
|
||||
type CurveLabel,
|
||||
} from "./B05_Profile_UI_RouteEdit_Label";
|
||||
import { coveredPx, onCoverChange } from "./B05_Profile_UI_RouteEdit_Cover";
|
||||
|
||||
/** 패널이 만지는 편집값 한 벌 — 모달이 쥔 배열을 그대로 건네받는다. */
|
||||
export interface CurveBarState {
|
||||
@@ -145,7 +146,8 @@ export function createCurveBar(params: CurveBarParams): CurveBar {
|
||||
left: rect.left + 8,
|
||||
top: rect.top + 8,
|
||||
right: rect.right - 8,
|
||||
bottom: rect.bottom - 8,
|
||||
// 뜬 종단 판 밑은 안 보임 — 판 바로 위까지(345-5 덧 2 · 접으면 0).
|
||||
bottom: rect.bottom - 8 - coveredPx(),
|
||||
};
|
||||
const last = planned.length - 1;
|
||||
if (picked === 0 || picked === last) {
|
||||
@@ -202,5 +204,8 @@ export function createCurveBar(params: CurveBarParams): CurveBar {
|
||||
});
|
||||
}
|
||||
|
||||
// 판 끌기 · 접기 — 덮인 높이가 바뀌면 다시 자리 잡음(그리기 없이 · 떠나면 뗌).
|
||||
onCoverChange(() => (params.container.isConnected ? (sync(), true) : false));
|
||||
|
||||
return { label, sync };
|
||||
}
|
||||
|
||||
@@ -124,6 +124,15 @@ export function openingView(
|
||||
});
|
||||
if (seen) return { view: restored, rotationRad };
|
||||
}
|
||||
return { view: fitView(meta, view, nodes), rotationRad: 0 };
|
||||
}
|
||||
|
||||
/** 노선 전체를 뜬 종단 판 위 보이는 영역 가운데에(회전 0 기준) — 처음 열기 · [맞춤](345-5 덧). */
|
||||
export function fitView(
|
||||
meta: VWorldMeta,
|
||||
view: ViewState,
|
||||
nodes: ReadonlyArray<Vertex>,
|
||||
): ViewState {
|
||||
const xs = nodes.map((vertex) => vertex[0]);
|
||||
const ys = nodes.map((vertex) => vertex[1]);
|
||||
const [xMin, xMax, yMin, yMax] = [
|
||||
@@ -145,7 +154,7 @@ export function openingView(
|
||||
wantWidthM: Math.max(xMax - xMin, 1) + ROUTE_VIEW_MARGIN_M * 2,
|
||||
wantHeightM: Math.max(yMax - yMin, 1) + ROUTE_VIEW_MARGIN_M * 2,
|
||||
});
|
||||
return { view: { ...view, ...fitted }, rotationRad: 0 };
|
||||
return { ...view, ...fitted };
|
||||
}
|
||||
|
||||
/** 캔버스 버퍼를 감싼 칸 크기(고해상도 배율 포함)에 맞춘다. 칸의 CSS 크기를 돌려준다. */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""345-5 덧 — 노선 편집 평면의 보이는 영역(뜬 종단 판 위) · 노선 맞춤 · 글판 자리.
|
||||
"""345-5 덧 — 노선 편집 평면의 보이는 영역(뜬 종단 판 위) · 노선 맞춤 · 글판 · 곡선 바 자리.
|
||||
|
||||
`logic/B05_Profile_RouteEdit_Visible.ts` 를 tsc 로 옮겨 node 로 돌림 · 배선 · CSS 는 글로 봄.
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_345_5b_plan_visible_area.py -q
|
||||
@@ -130,3 +130,21 @@ def test_배선과_글판_자리() -> None:
|
||||
# 접힌 판은 높이 0 — 덮인 높이도 0 이 됨
|
||||
style = (B05 / "B05_Profile_UI_Style.css").read_text(encoding="utf-8")
|
||||
assert ".b05-route-profile.is-collapsed {\n height: 0;" in style
|
||||
|
||||
|
||||
def test_곡선_바는_판_위_맞춤_단추():
|
||||
"""345-5 덧 2 — 곡선 바 아래 한계 = 판 바로 위 · 판 끌기 · 접기 따라감 · [맞춤] 단추."""
|
||||
bar = (B05 / "B05_Profile_UI_RouteEdit_CurveBar.ts").read_text(encoding="utf-8")
|
||||
assert "bottom: rect.bottom - 8 - coveredPx()," in bar
|
||||
assert "onCoverChange(() => (params.container.isConnected ? (sync(), true) : false));" in bar
|
||||
cover = (B05 / "B05_Profile_UI_RouteEdit_Cover.ts").read_text(encoding="utf-8")
|
||||
assert "if (covered !== before)" in cover and "listeners.delete(listener)" in cover
|
||||
chrome = (B05 / "B05_Profile_UI_RouteEdit_Chrome.ts").read_text(encoding="utf-8")
|
||||
assert 'data-act="fit"' in chrome and ">맞춤</button>" in chrome
|
||||
editor = (B05 / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8")
|
||||
at = editor.index("""overlay.querySelector('[data-act="fit"]')!""")
|
||||
handler = editor[at : at + 260]
|
||||
assert "view = fitView(meta, view, planned);" in handler and "rotation.set(0);" in handler
|
||||
assert len(editor.splitlines()) <= 700
|
||||
view = (B05 / "B05_Profile_UI_RouteEdit_View.ts").read_text(encoding="utf-8")
|
||||
assert "return { view: fitView(meta, view, nodes), rotationRad: 0 };" in view
|
||||
|
||||
Reference in New Issue
Block a user