/* ============================================================================= * D03_PlanAnalysis_UI_Menu.ts * D03 지도 우클릭 메뉴(PLAN 591-2 · 598-2 · 요구 463 16 · 22) — 부품 상자 `createMapContextMenu` 하나 · 누른 자리마다 항목. * 긋는 중 = 긋기 끝 · 핀 = 그 지점에서 긋기 · 지우기 · 마커 = 마커 지움 · * 노드 = 노드 지움 · 지점으로 · 이어 긋기 / 갈래 시작 · 선 위 = 갈래 시작 · 여기서 연도 구역 나눔 · 다시 긋기(599) · * 빈 땅 = 여기서 긋기 시작 · 핀 · 노드 밖 = 지점 놓기(노드 둘레면 그 노드에 붙음). * 우클릭 즉시 지움 없음 — 지움은 메뉴 · 고르고 Delete. B05 우클릭(`bindNodeSplice`)과 코드 안 나눔. * 겹 = 맨 끝(맨 위) · 오른쪽 누름만 받음. * ========================================================================== */ import { createMapContextMenu, type MapContextMenuItem } from "@ui/ui_template_context_menu"; import { samePoint, type PlanNetwork, type Vertex } from "./logic/D03_PlanAnalysis_Plan"; import type { PlanDraw } from "./D03_PlanAnalysis_UI_Draw"; import type { PlanEdit } from "./D03_PlanAnalysis_UI_Edit"; import type { PlanMapLayer } from "./D03_PlanAnalysis_UI_Map"; import type { PlanPoints } from "./D03_PlanAnalysis_UI_Points"; export interface PlanMenuParams { network: () => PlanNetwork; points: () => Vertex[]; marks: PlanPoints; edit: PlanEdit; draw: PlanDraw; editable: () => boolean; /** [다시 긋기](599-3) — 구역 id · 앞 노드 · 뒤 노드. */ respan: (line: string, from: number, to: number) => boolean; } export interface PlanMenu { layer: PlanMapLayer; /** 메뉴를 띄울 지도 그릇(우클릭 자리 = 그릇 기준). */ mount: (viewport: HTMLElement) => void; } export function createPlanMenu(params: PlanMenuParams): PlanMenu { const { marks, edit, draw } = params; const menu = createMapContextMenu("d03-plan"); let menuAt: [number, number] = [0, 0]; const layer: PlanMapLayer = { draw: () => undefined, press(px, py, map, button) { if (button !== 2 || !params.editable()) return false; const items: MapContextMenuItem[] = []; const pin = marks.pinAt(px, py, map); const at = pin === null ? edit.hit(px, py, map) : null; const start = (x: number, y: number): void => { const label = { tip: "이어 긋기", point: "지점에서 긋기 시작", line: "갈래 시작", empty: "여기서 긋기 시작", }[draw.aim(x, y, map)]; items.push([label, () => draw.startAt(x, y, map)]); }; if (draw.mode() === "draw" && draw.stroke()) items.push(["긋기 끝", () => draw.setMode("idle")]); else if (pin !== null) { const [sx, sy] = map.toScreen(params.points()[pin]); // 핀 머리를 눌러도 지점 자리에서 items.push([`${marks.name(pin)}에서 긋기 시작`, () => draw.startAt(sx, sy, map)]); items.push([`${marks.name(pin)} 지우기`, () => marks.remove(pin)]); } else if (at?.mark) { const mark = at.mark; items.push([ "마커 지움", () => { edit.select([mark], mark); edit.dropMark(); }, ]); } else if (at?.node) { const node = at.node; const point = params.network().lines.find((l) => l.id === node.line)!.vertices[node.vertex]; const [sx, sy] = map.toScreen(point); start(sx, sy); items.push("-", ["노드 지움", () => edit.dropNode(node)]); if (!marks.full() && !params.points().some((p) => samePoint(p, point))) items.push(["지점으로", () => marks.place(sx, sy, map)]); } else if (at?.segment) { const seg = at.segment; start(px, py); items.push(["여기서 연도 구역 나눔", () => edit.splitAt(px, py, map)]); items.push(["다시 긋기", () => params.respan(seg.line, seg.index, seg.index + 1)]); } else start(px, py); if (pin === null && !at?.node && !at?.mark && !marks.full()) items.push("-", ["지점 놓기", () => marks.place(px, py, map)]); menu.open(menuAt[0], menuAt[1], items); return true; }, }; return { layer, mount(viewport) { viewport.append(menu.element); viewport.addEventListener( "contextmenu", (event) => { const rect = viewport.getBoundingClientRect(); menuAt = [event.clientX - rect.left, event.clientY - rect.top]; }, true, ); }, }; }