57 lines
2.9 KiB
TypeScript
57 lines
2.9 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_RouteEdit_StructureMenu.ts
|
|
* 평면 우클릭 구조물 메뉴(513-1 · 요구 327-1).
|
|
*
|
|
* · 메뉴 = 종단 그래프 우클릭과 같은 항목 · 같은 동작(`structureMenuItemsAt` · 재료 = 지금 선 종단 그래프).
|
|
* · 자리 = 누른 곳을 노선에 내린 누가거리 · 가까운 구조물 · 배관 측점이면 삭제.
|
|
* · 노드 위 = 옛 동작 그대로(노드 삭제 · `bindNodeSplice`) · 노선에서 먼 곳 = 메뉴 없음.
|
|
* ========================================================================== */
|
|
|
|
import { pickPx } from "../A00_Common/touch_bridge";
|
|
import { createMapContextMenu } from "@ui/ui_template_context_menu";
|
|
import { liveStructureMenu, structureMenuItemsAt } from "./B05_Profile_UI_Profile_Structures";
|
|
import { routePointAtScreen } from "./B05_Profile_UI_RouteEdit_Input";
|
|
import type { RouteEditPointerParams } from "./B05_Profile_UI_RouteEdit_Pointer";
|
|
import { stationFrameAt } from "./logic/B05_Profile_UI_RouteEdit_Frame";
|
|
|
|
/** 노선 위로 볼 거리(px) — 이보다 멀면 구조물 메뉴를 안 띄움. */
|
|
const ROUTE_HIT_PX = 12;
|
|
/** 구조물 · 배관 측점을 잡았다고 볼 거리(px) — 평면 마커 집기와 같은 값. */
|
|
const STATION_HIT_PX = 10;
|
|
|
|
/** 평면 캔버스에 구조물 우클릭 메뉴를 붙임 — 캔버스와 수명이 같음. */
|
|
export function bindPlanStructureMenu(
|
|
params: RouteEditPointerParams,
|
|
nodeAt: (px: number, py: number) => number,
|
|
): void {
|
|
const { canvas, toScreen } = params;
|
|
const menu = createMapContextMenu("b05-profile-chart");
|
|
canvas.parentElement?.append(menu.element);
|
|
|
|
canvas.addEventListener("contextmenu", (event) => {
|
|
menu.close(); // 노선 밖 · 노드 위 우클릭이면 옛 메뉴도 닫힌 채
|
|
const options = liveStructureMenu();
|
|
const rect = canvas.getBoundingClientRect();
|
|
const [px, py] = params.unrotate(event.clientX - rect.left, event.clientY - rect.top);
|
|
if (!options || nodeAt(px, py) >= 0) return; // 노드 삭제 몫
|
|
const state = params.state();
|
|
const line = state.plannedLine.length ? state.plannedLine : state.planned;
|
|
const hit = routePointAtScreen(line, toScreen, px, py, pickPx(ROUTE_HIT_PX));
|
|
if (!hit) return;
|
|
const near = options.stations
|
|
.map((station) => {
|
|
const frame = stationFrameAt(line, station.chainage_m);
|
|
const [x, y] = frame ? toScreen(frame.origin) : [Infinity, Infinity];
|
|
return { station, distance: Math.hypot(x - px, y - py) };
|
|
})
|
|
.filter((entry) => entry.distance <= pickPx(STATION_HIT_PX))
|
|
.sort((left, right) => left.distance - right.distance)[0];
|
|
const host = menu.element.parentElement?.getBoundingClientRect() ?? rect;
|
|
menu.open(
|
|
event.clientX - host.left,
|
|
event.clientY - host.top,
|
|
structureMenuItemsAt(options, hit.chainageM, near?.station ?? null),
|
|
);
|
|
});
|
|
}
|