- 새 D03_PlanAnalysis_UI_Features.ts — 노선 그리기 기능 표(요구서 546 4장 · pages d03 · b05 = use · view · hide · 까닭) · featureUse · featureOn · featureShown - 새 D03_PlanAnalysis_UI_Kit.ts — createPlanKit(부품 엮기 · 왼쪽 칸 위치 · 지점 · 그리기 · 구역·연도 · 겹 차례 · 키 · sync) · bindPlanInput(누름 · 맴돎 · 놓음 · 우클릭 · 연도 마커 끌어 놓기 · capture = B05 켠 동안 먼저 받음) - 예정노선 UI_Page 680 → 620 줄 · 키트로 엮음(화면 · 저장 · 되돌리기 그대로) · UI_Map 지도 입력 줄 → map.input - UI_Draw · UI_Menu · UI_Props · UI_DrawPanel — oneLine 플래그 → 기능 표 page 읽기 · 지점 · 경위도(lonLat) · 연도 시작 = 없으면 숨김(빈 지점 · epsg 0 가짜 값 걷음) · oneLineBlock → lineLimitBlock - B05 ExpectedDraw — page: "b05" 넘김 · NO_POINTS · 가짜 값 걷음 · 지도 입력 = bindPlanInput capture(동작 그대로) · 속성 판 경위도 빈칸 → 칸 숨김 - 시험 고침: test_626_3(oneLine 글 대조 → 기능 표 · 키트 capture 대조) · test_475(위치 칸 서클 = 키트 site.title) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
116 lines
5.1 KiB
TypeScript
116 lines
5.1 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 { lineLimitBlock, type PlanDraw } from "./D03_PlanAnalysis_UI_Draw";
|
|
import type { PlanEdit } from "./D03_PlanAnalysis_UI_Edit";
|
|
import { featureOn, type PlanPage } from "./D03_PlanAnalysis_UI_Features";
|
|
import type { PlanMapLayer } from "./D03_PlanAnalysis_UI_Map";
|
|
import type { PlanPoints } from "./D03_PlanAnalysis_UI_Points";
|
|
|
|
export interface PlanMenuParams {
|
|
/** 기능 표 페이지 — 갈래 · 연도 구역 나눔 · 지점 항목. */
|
|
page: PlanPage;
|
|
network: () => PlanNetwork;
|
|
/** 지점 · 핀(표 「points」 켠 페이지만). */
|
|
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 { edit, draw, page } = params;
|
|
const marks = featureOn(page, "points") ? params.marks : undefined;
|
|
const points = (): Vertex[] => params.points?.() ?? [];
|
|
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) ?? null;
|
|
const at = pin === null ? edit.hit(px, py, map) : null;
|
|
const start = (x: number, y: number): void => {
|
|
const aim = draw.aim(x, y, map);
|
|
if (!featureOn(page, "branch") && lineLimitBlock(aim, params.network())) return;
|
|
const label = {
|
|
tip: "이어 긋기",
|
|
point: "지점에서 긋기 시작",
|
|
line: "갈래 시작",
|
|
empty: "여기서 긋기 시작",
|
|
}[aim];
|
|
items.push([label, () => draw.startAt(x, y, map)]);
|
|
};
|
|
if (draw.mode() === "draw" && draw.stroke())
|
|
items.push(["긋기 끝", () => draw.setMode("idle")]);
|
|
else if (marks && pin !== null) {
|
|
const [sx, sy] = map.toScreen(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 && !marks.full() && !points().some((p) => samePoint(p, point)))
|
|
items.push(["지점으로", () => marks.place(sx, sy, map)]);
|
|
} else if (at?.segment) {
|
|
const seg = at.segment;
|
|
start(px, py);
|
|
if (featureOn(page, "years"))
|
|
items.push(["여기서 연도 구역 나눔", () => edit.splitAt(px, py, map)]);
|
|
items.push(["다시 긋기", () => params.respan(seg.line, seg.index, seg.index + 1)]);
|
|
} else start(px, py);
|
|
if (marks && 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,
|
|
);
|
|
},
|
|
};
|
|
}
|