Files
Aislo/D03_PlanAnalysis/D03_PlanAnalysis_UI_Menu.ts
T
eomsangdonandClaude Opus 5.5 ed7f7fa674 feat(D 예정노선): 노드 판 — 방식 · 값 고침(앞 노드 기준 다시 놓음 · 뒤 노드 그대로 · 어긋남 ⚠) · [지점으로] · [다시 긋기] 이음 (PLAN 598-4 · 요구 463-11 · 464)
- logic/Select placeNode — 지점 자리 노드면 지점 같이 · 첫 노드 = 까닭 글
- UI_Props 노드 판 = 599-1 방식 칸(createDrawKindFields) · 방식 고름 = 바로 · 수 칸 = Enter · 칸 떠날 때
- 판 · 메뉴 [다시 긋기] = draw.respan(599-3)
- 시험 598_1 placeNode 줄

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 15:02:48 +09:00

107 lines
4.5 KiB
TypeScript

/* =============================================================================
* 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,
);
},
};
}