Files
Aislo/B05_Profile/B05_Profile_UI_RouteEdit_StructureMenu.ts
T

61 lines
3.2 KiB
TypeScript

/* =============================================================================
* B05_Profile_UI_RouteEdit_StructureMenu.ts
* 평면 우클릭 구조물 메뉴(513-1 · 요구 327-1).
*
* · 메뉴 = 종단 그래프 우클릭과 같은 항목 · 같은 동작(`structureMenuItemsAt` · 재료 = 지금 선 종단 그래프).
* · 자리 = 누른 곳을 노선에 내린 누가거리 · 가까운 구조물 · 배관 측점이면 삭제 · 측점선 없는 마커도(514-1).
* · 노드 위 = 옛 동작 그대로(노드 삭제 · `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 { planMarkAt } from "./B05_Profile_UI_RouteEdit_StructureMarks";
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;
const mark = near ? null : planMarkAt(canvas, line, toScreen, px, py)?.mark;
menu.open(
event.clientX - host.left,
event.clientY - host.top,
mark
? [["구조물 삭제", () => options.onRemoveMark?.(mark.id)]]
: structureMenuItemsAt(options, hit.chainageM, near?.station ?? null),
);
});
}