From 9513489847abb6f0b3909d4409c3613002754da6 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 18 Oct 2026 17:06:16 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(436=20B06=20=ED=8F=89=EB=A9=B4=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90):=20=EB=86=92=EC=9D=B4=20=EB=81=8C=EA=B8=B0?= =?UTF-8?q?=20=C2=B7=20=ED=99=94=EB=A9=B4=20=EA=B8=B0=EC=96=B5=20=C2=B7=20?= =?UTF-8?q?=EC=B2=98=EC=9D=8C=2032=20%=20=C2=B7=20=EC=A1=B0=EC=9E=91=20B05?= =?UTF-8?q?=20=EA=B7=B8=EB=8C=80=EB=A1=9C=20=C2=B7=20=EC=B2=98=EC=9D=8C=20?= =?UTF-8?q?=EB=B3=B4=EC=9D=BC=20=EB=95=8C=20B05=20=EC=B9=B8=20=ED=81=AC?= =?UTF-8?q?=EA=B8=B0=EB=A1=9C=20=EC=85=88=20(=EC=9A=94=EA=B5=AC=20263-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 겹침 패널 누름 막기 걷음 — 끌기 · 끼우기 · 지우기 · 휠 · 팬 = B05 평면 처리기 그대로 - 방향키 범위 = 마지막 누름이 평면 안(.b05-routeedit · 겹침 평면 포함) - 아래 끝 끌어 높이(120 px ~ 칸 − 48 px · pref plan-peek-height · 두 번 누름 = 처음 높이) - openView = B06 겹침에서 처음 보여도 가운데 칸 크기로 맞춤 · 기억 판정(433-8) - 시험 258 고쳐 씀 · 383_2 범위 단언 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy --- A00_Common/b_page_state.ts | 2 + A00_Common/design/design_page.css | 19 ++++++-- A00_Common/design/design_plan_peek.ts | 38 +++++++-------- B05_Profile/B05_Profile_UI_RouteEdit.ts | 9 +++- B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts | 2 +- ...B05_Profile_UI_RouteEdit_StructureMarks.ts | 6 +-- resources/tester/test_258_b06_plan_peek.py | 48 ++++++++++++++----- resources/tester/test_383_2_route_keys.py | 4 +- 8 files changed, 80 insertions(+), 48 deletions(-) diff --git a/A00_Common/b_page_state.ts b/A00_Common/b_page_state.ts index b5b583ffc..c8f068986 100644 --- a/A00_Common/b_page_state.ts +++ b/A00_Common/b_page_state.ts @@ -51,6 +51,8 @@ export const STATE_REGISTRY = { legacy: () => "b05-route-profile-collapsed", }, "profile-height": { bucket: "pref", scope: "global", legacy: () => "b05-route-profile-height" }, + /** B06 위 평면 겹침 패널 높이(436) */ + "plan-peek-height": { bucket: "pref", scope: "global" }, /** 배수유역 판 접힘 — 기억 없음 = 닫힘(392-1 · 옛 "false" 기억 버림). */ "drainage-collapsed": { bucket: "pref", scope: "global", version: 2 }, "drainage-width": { bucket: "pref", scope: "global", legacy: () => "b05-route-drainage-width" }, diff --git a/A00_Common/design/design_page.css b/A00_Common/design/design_page.css index 2f352ef7f..6543b3961 100644 --- a/A00_Common/design/design_page.css +++ b/A00_Common/design/design_page.css @@ -92,8 +92,9 @@ display: none; } -/* 횡단 보기 위 평면 겹침 패널(요구 258 · 425 · `design_plan_peek`) — 카드 칸 위에 뜸(자리 안 차지) · - 닫히면 손잡이만 위 가운데 · 열면 가운데 칸 높이 45 % · 보기 · 팬 · 휠만이라 편집 도구는 감춤. */ +/* 횡단 보기 위 평면 겹침 패널(요구 258 · 263 · `design_plan_peek`) — 카드 칸 위에 뜸(자리 안 차지) · + 닫히면 손잡이만 위 가운데 · 열면 처음 높이 32 % · 아래 끝 끌어 조절 · 조작은 B05 평면 그대로라 + 단추 줄 · 안내 · 상태줄 등만 감춤. */ .design-plan-peek { position: absolute; z-index: 4; @@ -105,7 +106,19 @@ } .design-plan-peek.is-open { - height: 45%; + height: var(--design-plan-peek-height, 32%); +} + +/* 높이 끌기 손잡이 = 패널 아래 끝(공용 세로 손잡이는 위 끝) · 열렸을 때만 */ +.design-plan-peek > .ui-resizer { + top: auto; + bottom: -3px; + display: none; + pointer-events: auto; +} + +.design-plan-peek.is-open > .ui-resizer { + display: block; } .design-plan-peek__body { diff --git a/A00_Common/design/design_plan_peek.ts b/A00_Common/design/design_plan_peek.ts index c7d13941a..0ff936cae 100644 --- a/A00_Common/design/design_plan_peek.ts +++ b/A00_Common/design/design_plan_peek.ts @@ -4,11 +4,13 @@ * 그대로 옮겨 붙여 보여 줌(둘째 캔버스 없음 · 편집기는 페이지에 하나). 열 때 옮기고 · 닫을 때 · * 횡단 보기가 숨을 때 제자리(자리표 주석)로 돌림. 누름 · 휠은 캔버스에 붙어 함께 옴. * - * 보기 · 가운데 버튼 팬 · 휠 확대만 — 왼쪽 · 오른쪽 누름(끌기 · 끼우기 · 지우기 · 재기)은 패널이 - * 캡처에서 먼저 받아 캔버스로 안 보냄(편집은 B05 평면). 단추 줄 · 상태줄 등은 `design_page.css`. + * 조작 · 되돌리기 · 옵션 값은 B05 평면 그대로(같은 편집기 · 436) — 단추 줄 · 안내 · 상태줄 등만 감춤 + * (`design_page.css`). 높이 = 아래 끝 끌기(화면 기억) · 열 때 · 크기 바뀔 때 내용 맞춤 없음. * ========================================================================== */ import { createWorkflowPanelHandle } from "@ui/ui_template_overlay"; +import { createPanelResizer } from "@ui/ui_template_resizer"; +import { stateKey } from "../b_page_state"; import type { DesignPageHost } from "./design_page"; import type { DesignPageShared } from "./design_shared"; @@ -26,28 +28,19 @@ export function mountPlanPeek( note.className = "design-plan-peek__note"; body.append(note); const handle = createWorkflowPanelHandle("bottom", "up", "평면"); - root.append(body, handle.root); + // 아래 끝 끌어 높이 — 120 px ~ 칸 높이 − 48 px(손잡이 자리) · 두 번 누름 = 처음 높이(32 %) + const resizer = createPanelResizer({ + axis: "vertical", + target: root, + cssVar: "--design-plan-peek-height", + direction: 1, + min: 120, + max: () => center.clientHeight - 48, + storageKey: stateKey("plan-peek-height") ?? undefined, + }); + root.append(body, resizer.root, handle.root); center.append(root); - // 편집 몫 누름 막음 — 가운데 버튼(팬)만 통과 · 두 번 클릭(끼우기) · 오른쪽 클릭(지우기)도 막음. - // ponytail: 손가락 팬도 막힘(왼쪽 누름과 같은 단추 0) — 터치 기기에서 필요하면 pointerType 으로 가름. - body.addEventListener( - "pointerdown", - (event) => { - if (event.button !== 1) event.stopPropagation(); - }, - true, - ); - for (const type of ["dblclick", "contextmenu"]) - body.addEventListener( - type, - (event) => { - event.preventDefault(); - event.stopPropagation(); - }, - true, - ); - let opened = false; let plan: HTMLElement | null = null; let home: Comment | null = null; @@ -66,6 +59,7 @@ export function mountPlanPeek( const open = async (): Promise => { setOpen(true); + resizer.restore(); // 기억한 높이를 지금 칸에 맞춰 다시 씌움(세울 때는 칸 크기 0) note.hidden = false; note.textContent = "평면을 여는 중…"; await shared.ensureView("profile"); // B06 바로 열기 = 종단 보기를 숨긴 채 세움 diff --git a/B05_Profile/B05_Profile_UI_RouteEdit.ts b/B05_Profile/B05_Profile_UI_RouteEdit.ts index d24425bc4..85adc5795 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit.ts @@ -285,8 +285,13 @@ export async function mountRouteEditor( let viewWanted = false; // 자료를 읽었다 — 칸 크기가 서면 보던 화면(18 ③) · 없으면 노선 전체로. function openView(): void { if (!viewWanted || !meta || view.width <= 0) return; - const opening = openingView(projectId, meta, view, planned, contours?.layer ?? null); - view = opening.view; + // B06 겹침 평면(436)에서 처음 보여도 B05 칸 크기로 셈 — 그 배율이 B05 평면에 남음(433-8). + const atHome = overlay.closest(".b05-route__main") !== null; + const page = atHome ? null : overlay.closest(".design-page__center"); + const [w, h] = page ? [page.clientWidth, page.clientHeight] : [view.width, view.height]; + const home = { ...view, width: w, height: h, mapRect: computeMapRect(meta, w, h) }; + const opening = openingView(projectId, meta, home, planned, contours?.layer ?? null); + view = { ...view, ...restoreSavedView(meta, view, captureView(meta, opening.view, 0)) }; rotation.set(opening.rotationRad); viewReady = true; } diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts b/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts index ac7626873..d57b14104 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_Keys.ts @@ -105,7 +105,7 @@ export function bindRouteEditKeys(params: RouteEditPointerParams, dragging: () = function onKeyDown(event: KeyboardEvent): void { if (!canvas.isConnected) return detach(); - if (!isArrowKey(event.key) || !lastPointerWithin(".b05-route__main .b05-routeedit")) return; // B06 겹침 평면 = 키 안 먹음(425) + if (!isArrowKey(event.key) || !lastPointerWithin(".b05-routeedit")) return; // B06 겹침 평면도 같음(436) const target = event.target as HTMLElement | null; if (target?.closest?.("input, select, textarea, [contenteditable='true']")) return; // 평면이 삼킴 — 종단도(▲▼ 계획고 · `defaultPrevented` 면 비킴) · 횡단 조정창으로 안 감 diff --git a/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts b/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts index 18410a3db..6c82835d8 100644 --- a/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts +++ b/B05_Profile/B05_Profile_UI_RouteEdit_StructureMarks.ts @@ -370,11 +370,7 @@ export function bindStructureMarks( function onKeyDown(event: KeyboardEvent): void { if (!canvas.isConnected) return detach(); const escape = event.key === "Escape"; - if ( - (!escape && !isArrowKey(event.key)) || - !lastPointerWithin(".b05-route__main .b05-routeedit") - ) - return; // 겹침 평면 = 안 먹음(425) + if ((!escape && !isArrowKey(event.key)) || !lastPointerWithin(".b05-routeedit")) return; const target = event.target as HTMLElement | null; if (target?.closest?.("input, select, textarea, [contenteditable='true']")) return; // Esc = 마커 고름만 풂(공용 Esc 풀기는 오른쪽 판까지 비우므로 앞에서 받음) diff --git a/resources/tester/test_258_b06_plan_peek.py b/resources/tester/test_258_b06_plan_peek.py index 79486e66a..0f7f530a2 100644 --- a/resources/tester/test_258_b06_plan_peek.py +++ b/resources/tester/test_258_b06_plan_peek.py @@ -1,8 +1,9 @@ -"""258 · 425 — B06 위 평면 겹침 패널. +"""258 · 425 · 436 — B06 위 평면 겹침 패널. 종단 보기 평면 노드를 옮겨 붙임(둘째 캔버스 없음) · 닫을 때 · 횡단 숨김 때 제자리 · 손잡이 = -공용 bottom 위로 접힘 · z 4 · 높이 45 % · 편집 도구 감춤 · 가운데 버튼 팬 · 휠만(편집 누름 막음) · -평면 방향키는 B05 평면 안에서만. 배선은 글로 봄. +공용 bottom 위로 접힘 · z 4 · 처음 높이 32 % · 아래 끝 끌어 높이(화면 기억) · 단추 줄 · 안내 등 감춤 · +조작(끌기 · 끼우기 · 지우기 · 방향키 · 휠 · 팬)은 B05 평면 그대로 · 처음 보일 때 B05 칸 크기로 셈. +배선은 글로 봄. ./venv/Scripts/python.exe -m pytest resources/tester/test_258_b06_plan_peek.py -q """ @@ -41,19 +42,30 @@ def test_평면_노드를_옮기고_제자리로() -> None: assert len(PEEK.splitlines()) <= 700 -def test_편집_누름은_막고_팬_휠은_통과() -> None: - down = PEEK[PEEK.index('"pointerdown"') :] - down = down[: down.index("true,")] - assert "if (event.button !== 1) event.stopPropagation();" in down - assert 'for (const type of ["dblclick", "contextmenu"])' in PEEK - assert '"wheel"' not in PEEK # 휠 확대는 캔버스 그대로 +def test_조작은_B05_평면_그대로() -> None: + # 패널이 누름 · 휠을 가로채지 않음 — 캔버스에 붙은 B05 처리기가 그대로 받음 + for event in ('"pointerdown"', '"dblclick"', '"contextmenu"', '"wheel"', '"keydown"'): + assert event not in PEEK + assert "stopPropagation" not in PEEK + + +def test_높이_끌기와_기억() -> None: + assert "createPanelResizer({" in PEEK + assert 'cssVar: "--design-plan-peek-height",' in PEEK and "direction: 1," in PEEK + assert 'storageKey: stateKey("plan-peek-height")' in PEEK + assert "resizer.restore();" in PEEK[PEEK.index("const open = async") :] + state = (ROOT / "A00_Common" / "b_page_state.ts").read_text(encoding="utf-8") + assert '"plan-peek-height": { bucket: "pref", scope: "global" },' in state + handle = _rule(".design-plan-peek > .ui-resizer") + assert "bottom: -3px;" in handle and "display: none;" in handle + assert "display: block;" in _rule(".design-plan-peek.is-open > .ui-resizer") def test_겹침_자리와_감춤() -> None: root = _rule(".design-plan-peek") assert "position: absolute;" in root and "z-index: 4;" in root assert "pointer-events: none;" in root # 닫힌 동안 카드 누름 통과 - assert "height: 45%;" in _rule(".design-plan-peek.is-open") + assert "height: var(--design-plan-peek-height, 32%);" in _rule(".design-plan-peek.is-open") hidden = CSS[CSS.index(".design-plan-peek .b05-routeedit__tools,") :] hidden = hidden[: hidden.index("}")] for part in ("__tools", "__info", "__measure", "__side-wrap", "b05-contourlock"): @@ -61,8 +73,18 @@ def test_겹침_자리와_감춤() -> None: assert "display: none;" in hidden -def test_평면_방향키는_B05_평면_안만() -> None: +def test_평면_방향키는_마지막_누름이_평면_안() -> None: for name in ("B05_Profile_UI_RouteEdit_Keys.ts", "B05_Profile_UI_RouteEdit_StructureMarks.ts"): text = (B05 / name).read_text(encoding="utf-8") - assert 'lastPointerWithin(".b05-route__main .b05-routeedit")' in text - assert 'lastPointerWithin(".b05-routeedit")' not in text + assert 'lastPointerWithin(".b05-routeedit")' in text # 겹침 평면도(436) + assert ".b05-route__main .b05-routeedit" not in text + + +def test_처음_보일_때_B05_칸_크기로_셈() -> None: + editor = (B05 / "B05_Profile_UI_RouteEdit.ts").read_text(encoding="utf-8") + body = editor[editor.index("function openView(): void {") :] + body = body[: body.index("\n }")] + assert 'const atHome = overlay.closest(".b05-route__main") !== null;' in body + assert 'atHome ? null : overlay.closest(".design-page__center")' in body + assert "openingView(projectId, meta, home," in body + assert "restoreSavedView(meta, view, captureView(meta, opening.view, 0))" in body diff --git a/resources/tester/test_383_2_route_keys.py b/resources/tester/test_383_2_route_keys.py index d995591b3..13bf957c9 100644 --- a/resources/tester/test_383_2_route_keys.py +++ b/resources/tester/test_383_2_route_keys.py @@ -64,8 +64,8 @@ def test_이음_끌기_걸기_한_줄_창_가로채기() -> None: assert pointer.count("bindRouteEditKeys(") == 1 keys = _read("B05_Profile_UI_RouteEdit_Keys.ts") assert 'window.addEventListener("keydown", onKeyDown, true)' in keys # 앞서 받음(capture) - # 본 평면만(425) — B06 겹침 평면 위 화살표는 안 먹음 - assert 'lastPointerWithin(".b05-route__main .b05-routeedit")' in keys + # 마지막 누름이 평면 안 — B06 겹침 평면도 같음(436) + assert 'lastPointerWithin(".b05-routeedit")' in keys assert "event.preventDefault();\n event.stopPropagation();" in keys assert "state.apexLock[index] === true" in keys # 고정 IP 는 안 옮김 assert "shortfallCrossed(" in keys # 하한 검사 = 끌기와 같음 From eac20bd93bf078fea1b245b5e84efb81db96c962 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Sun, 18 Oct 2026 17:20:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix(436=20B06=20=ED=8F=89=EB=A9=B4=20?= =?UTF-8?q?=ED=8C=A8=EB=84=90):=20=EC=B2=98=EC=9D=8C=20=EB=86=92=EC=9D=B4?= =?UTF-8?q?=20=EB=B0=94=EB=8B=A5=20160=20px=20=C2=B7=20=EB=86=92=EC=9D=B4?= =?UTF-8?q?=20=EC=83=81=ED=95=9C=20=3D=20=ED=8C=A8=EB=84=90=20%=20?= =?UTF-8?q?=EA=B8=B0=EC=A4=80=20=EC=B9=B8(offsetParent)=20(433-8)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 종단 판 펼친 B06 = 칸 225 px 라 32 % 가 72 px — max(32%, 160px) - 끌기 상한이 높이 0 인 횡단 center 를 재어 늘 120 px 로 눌림 — offsetParent 높이 − 48 px - PLAN 436-1 검증완료 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01QfHKQs7J3hQELKMWQMUJEy --- A00_Common/design/design_page.css | 4 ++-- A00_Common/design/design_plan_peek.ts | 3 ++- resources/tester/test_258_b06_plan_peek.py | 5 ++++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/A00_Common/design/design_page.css b/A00_Common/design/design_page.css index 6543b3961..b169717c2 100644 --- a/A00_Common/design/design_page.css +++ b/A00_Common/design/design_page.css @@ -93,7 +93,7 @@ } /* 횡단 보기 위 평면 겹침 패널(요구 258 · 263 · `design_plan_peek`) — 카드 칸 위에 뜸(자리 안 차지) · - 닫히면 손잡이만 위 가운데 · 열면 처음 높이 32 % · 아래 끝 끌어 조절 · 조작은 B05 평면 그대로라 + 닫히면 손잡이만 위 가운데 · 열면 처음 높이 32 %(160 px 아래로 안 감) · 아래 끝 끌어 조절 · 조작은 B05 평면 그대로라 단추 줄 · 안내 · 상태줄 등만 감춤. */ .design-plan-peek { position: absolute; @@ -106,7 +106,7 @@ } .design-plan-peek.is-open { - height: var(--design-plan-peek-height, 32%); + height: var(--design-plan-peek-height, max(32%, 160px)); /* 종단 판 펼친 B06 = 칸이 낮음 */ } /* 높이 끌기 손잡이 = 패널 아래 끝(공용 세로 손잡이는 위 끝) · 열렸을 때만 */ diff --git a/A00_Common/design/design_plan_peek.ts b/A00_Common/design/design_plan_peek.ts index 0ff936cae..4767bd789 100644 --- a/A00_Common/design/design_plan_peek.ts +++ b/A00_Common/design/design_plan_peek.ts @@ -29,13 +29,14 @@ export function mountPlanPeek( body.append(note); const handle = createWorkflowPanelHandle("bottom", "up", "평면"); // 아래 끝 끌어 높이 — 120 px ~ 칸 높이 − 48 px(손잡이 자리) · 두 번 누름 = 처음 높이(32 %) + // 칸 = 패널 % 높이가 재는 자리(offsetParent) — `center` 는 높이 0 const resizer = createPanelResizer({ axis: "vertical", target: root, cssVar: "--design-plan-peek-height", direction: 1, min: 120, - max: () => center.clientHeight - 48, + max: () => (root.offsetParent?.clientHeight ?? 0) - 48, storageKey: stateKey("plan-peek-height") ?? undefined, }); root.append(body, resizer.root, handle.root); diff --git a/resources/tester/test_258_b06_plan_peek.py b/resources/tester/test_258_b06_plan_peek.py index 0f7f530a2..d5e12010a 100644 --- a/resources/tester/test_258_b06_plan_peek.py +++ b/resources/tester/test_258_b06_plan_peek.py @@ -53,6 +53,7 @@ def test_높이_끌기와_기억() -> None: assert "createPanelResizer({" in PEEK assert 'cssVar: "--design-plan-peek-height",' in PEEK and "direction: 1," in PEEK assert 'storageKey: stateKey("plan-peek-height")' in PEEK + assert "max: () => (root.offsetParent?.clientHeight ?? 0) - 48," in PEEK assert "resizer.restore();" in PEEK[PEEK.index("const open = async") :] state = (ROOT / "A00_Common" / "b_page_state.ts").read_text(encoding="utf-8") assert '"plan-peek-height": { bucket: "pref", scope: "global" },' in state @@ -65,7 +66,9 @@ def test_겹침_자리와_감춤() -> None: root = _rule(".design-plan-peek") assert "position: absolute;" in root and "z-index: 4;" in root assert "pointer-events: none;" in root # 닫힌 동안 카드 누름 통과 - assert "height: var(--design-plan-peek-height, 32%);" in _rule(".design-plan-peek.is-open") + assert "height: var(--design-plan-peek-height, max(32%, 160px));" in _rule( + ".design-plan-peek.is-open" + ) hidden = CSS[CSS.index(".design-plan-peek .b05-routeedit__tools,") :] hidden = hidden[: hidden.index("}")] for part in ("__tools", "__info", "__measure", "__side-wrap", "b05-contourlock"):