Merge remote-tracking branch 'origin/sub_laptop_2' into main_laptop_1

This commit is contained in:
2026-10-18 15:57:42 +09:00
6 changed files with 215 additions and 2 deletions
+52
View File
@@ -91,3 +91,55 @@
.b05-route__panel [class*="b06-structure-panel__struct--"] {
display: none;
}
/* 횡단 보기 위 평면 겹침 패널(요구 258 · 425 · `design_plan_peek`) — 카드 칸 위에 뜸(자리 안 차지) ·
닫히면 손잡이만 위 가운데 · 열면 가운데 칸 높이 45 % · 보기 · 팬 · 휠만이라 편집 도구는 감춤. */
.design-plan-peek {
position: absolute;
z-index: 4;
top: 0;
right: 0;
left: 0;
height: 0;
pointer-events: none;
}
.design-plan-peek.is-open {
height: 45%;
}
.design-plan-peek__body {
display: none;
height: 100%;
border-bottom: 1px solid var(--panel-handle-border, var(--color-border));
background: var(--color-surface);
box-shadow: var(--shadow-lg);
pointer-events: auto;
}
.design-plan-peek.is-open > .design-plan-peek__body {
display: flex;
}
.design-plan-peek__note {
margin: auto;
color: var(--color-text-secondary);
}
/* 손잡이 = 패널 아래 가장자리 가운데(위로 접힘) — 공용 bottom 손잡이를 위아래 뒤집음. */
.design-plan-peek > .ui-workflow-overlay__handle--bottom {
top: 100%;
border-top: 0;
border-bottom: 1px solid var(--panel-handle-border, var(--color-border));
border-radius: 0 0 var(--radius-buttons) var(--radius-buttons);
}
.design-plan-peek .b05-routeedit__tools,
.design-plan-peek .b05-routeedit__hint,
.design-plan-peek .b05-routeedit__info,
.design-plan-peek .b05-routeedit__measure,
.design-plan-peek .b05-routeedit__side-wrap,
.design-plan-peek .b05-routeedit__label,
.design-plan-peek .b05-contourlock {
display: none;
}
+87
View File
@@ -0,0 +1,87 @@
/* =============================================================================
* design_plan_peek.ts
* 횡단 보기 위 **평면 겹침 패널**(요구 258 · 계획서 425) — 종단 보기의 평면(노선 편집) 노드를
* 그대로 옮겨 붙여 보여 줌(둘째 캔버스 없음 · 편집기는 페이지에 하나). 열 때 옮기고 · 닫을 때 ·
* 횡단 보기가 숨을 때 제자리(자리표 주석)로 돌림. 누름 · 휠은 캔버스에 붙어 함께 옴.
*
* 보기 · 가운데 버튼 팬 · 휠 확대만 — 왼쪽 · 오른쪽 누름(끌기 · 끼우기 · 지우기 · 재기)은 패널이
* 캡처에서 먼저 받아 캔버스로 안 보냄(편집은 B05 평면). 단추 줄 · 상태줄 등은 `design_page.css`.
* ========================================================================== */
import { createWorkflowPanelHandle } from "@ui/ui_template_overlay";
import type { DesignPageHost } from "./design_page";
import type { DesignPageShared } from "./design_shared";
/** 횡단 보기 가운데(`center`)에 겹침 패널 · 손잡이를 세운다. */
export function mountPlanPeek(
center: HTMLElement,
host: DesignPageHost,
shared: DesignPageShared,
): void {
const root = document.createElement("div");
root.className = "design-plan-peek";
const body = document.createElement("div");
body.className = "design-plan-peek__body";
const note = document.createElement("p");
note.className = "design-plan-peek__note";
body.append(note);
const handle = createWorkflowPanelHandle("bottom", "up", "평면");
root.append(body, 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;
const setOpen = (open: boolean): void => {
opened = open;
root.classList.toggle("is-open", open);
handle.setOpen(open);
};
const close = (): void => {
setOpen(false);
if (plan && home) home.replaceWith(plan);
home = null;
};
const open = async (): Promise<void> => {
setOpen(true);
note.hidden = false;
note.textContent = "평면을 여는 중…";
await shared.ensureView("profile"); // B06 바로 열기 = 종단 보기를 숨긴 채 세움
if (!opened || !host.visible() || home) return;
plan =
center
.closest(".design-page__center")
?.querySelector<HTMLElement>(".b05-route__main > .b05-routeedit") ?? null;
if (!plan) return void (note.textContent = "평면을 불러오지 못했습니다.");
note.hidden = true;
home = document.createComment("design-plan-peek");
plan.replaceWith(home);
body.append(plan); // 크기 0 → 첫 크기 알림에 편집기가 다시 그림
};
handle.root.addEventListener("click", () => (opened ? close() : void open()));
host.onHide(close);
setOpen(false);
}
+2
View File
@@ -93,6 +93,7 @@ import {
type DesignPageShared,
} from "./design_shared";
import "@util/common_util_mass_haul.css";
import { mountPlanPeek } from "./design_plan_peek";
export async function mountCrossView(
center: HTMLElement,
@@ -409,6 +410,7 @@ export async function mountCrossView(
mainArea.className = "b06-profile__main";
mainArea.append(sectionView.root);
center.append(mainArea);
mountPlanPeek(center, host, shared); // 위 평면 겹침 패널(요구 258 · 425)
function showSectionView(): void {
if (!mainArea.contains(sectionView.root)) mainArea.replaceChildren(sectionView.root);
+1 -1
View File
@@ -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-routeedit")) return;
if (!isArrowKey(event.key) || !lastPointerWithin(".b05-route__main .b05-routeedit")) return; // B06 겹침 평면 = 키 안 먹음(425)
const target = event.target as HTMLElement | null;
if (target?.closest?.("input, select, textarea, [contenteditable='true']")) return;
// 평면이 삼킴 — 종단도(▲▼ 계획고 · `defaultPrevented` 면 비킴) · 횡단 조정창으로 안 감
@@ -370,7 +370,11 @@ 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-routeedit")) return;
if (
(!escape && !isArrowKey(event.key)) ||
!lastPointerWithin(".b05-route__main .b05-routeedit")
)
return; // 겹침 평면 = 안 먹음(425)
const target = event.target as HTMLElement | null;
if (target?.closest?.("input, select, textarea, [contenteditable='true']")) return;
// Esc = 마커 고름만 풂(공용 Esc 풀기는 오른쪽 판까지 비우므로 앞에서 받음)
@@ -0,0 +1,68 @@
"""258 · 425 — B06 위 평면 겹침 패널.
종단 보기 평면 노드를 옮겨 붙임(둘째 캔버스 없음) · 닫을 때 · 횡단 숨김 때 제자리 · 손잡이 =
공용 bottom 위로 접힘 · z 4 · 높이 45 % · 편집 도구 감춤 · 가운데 버튼 팬 · 휠만(편집 누름 막음) ·
평면 방향키는 B05 평면 안에서만. 배선은 글로 봄.
./venv/Scripts/python.exe -m pytest resources/tester/test_258_b06_plan_peek.py -q
"""
from __future__ import annotations
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
DESIGN = ROOT / "A00_Common" / "design"
PEEK = (DESIGN / "design_plan_peek.ts").read_text(encoding="utf-8")
CROSS = (DESIGN / "design_view_cross.ts").read_text(encoding="utf-8")
CSS = (DESIGN / "design_page.css").read_text(encoding="utf-8")
B05 = ROOT / "B05_Profile"
def _rule(selector: str) -> str:
body = CSS[CSS.index(selector + " {") :]
return body[: body.index("}")]
def test_횡단_보기가_패널을_세움() -> None:
assert 'import { mountPlanPeek } from "./design_plan_peek";' in CROSS
assert CROSS.index("center.append(mainArea);") < CROSS.index(
"mountPlanPeek(center, host, shared);"
)
def test_평면_노드를_옮기고_제자리로() -> None:
assert 'createWorkflowPanelHandle("bottom", "up", "평면")' in PEEK
assert 'await shared.ensureView("profile");' in PEEK # B06 바로 열기 = 종단 보기 숨긴 채
assert '".b05-route__main > .b05-routeedit"' in PEEK
assert "plan.replaceWith(home);" in PEEK and "body.append(plan);" in PEEK
assert "home.replaceWith(plan);" in PEEK # 닫기 = 자리표 자리로
assert "host.onHide(close);" in PEEK # 횡단 숨김(B05 로 감) = 제자리
assert 'createElement("canvas")' not in PEEK
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_겹침_자리와_감춤() -> 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")
hidden = CSS[CSS.index(".design-plan-peek .b05-routeedit__tools,") :]
hidden = hidden[: hidden.index("}")]
for part in ("__tools", "__info", "__measure", "__side-wrap", "b05-contourlock"):
assert part in hidden
assert "display: none;" in hidden
def test_평면_방향키는_B05_평면_안만() -> 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