B04 - 세부유역을 눌러 고르면 그 유역만 진하게, 나머지는 옅게 물러난다. 관 마커를 눌러도 그 관이 받는 유역이 골라지고, 유역 밖을 누르면 강조가 풀린다. - 고른 유역의 제원(측점·면적·표고차·유하장)을 상태 줄에 표기한다. B05 - "배관 편집" 토글을 없애고 마커를 언제나 끌 수 있게 했다. 계획선을 그냥 누르는 것으로는 배관이 생기지 않는다 — 유역을 고르려 할 때마다 배관이 생기기 때문이다. 추가·삭제는 B04와 같은 우클릭 메뉴로 옮겼다. - 배관을 옮기거나 넣거나 지우면 세부유역을 즉시 다시 나눈다(격자 해석 없음). - "자동 제안" 버튼 이름을 "초기화"로 바꿨다. - 노선 유입 강도 색칠을 B04 지도와 같은 색띠로 추가했다(토글 포함). 공용화 - common_util_drainage_detail: 1m 구간별 유입 면적 곡선을 응답에 포함(B04·B05 동일). - B04_wf1_Surface_UI_FlowRamp.ts: 색띠·정규화·강도선 렌더러를 두 화면이 공유. - ui_template_context_menu.ts: 지도 우클릭 메뉴를 공용 부품으로 분리. - B05_wf2_Route_UI_Drainage_Parts.ts 신설로 패널을 700줄 규칙 안으로 되돌림(677줄). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
58 lines
2.4 KiB
TypeScript
58 lines
2.4 KiB
TypeScript
/* =============================================================================
|
|
* 지도 우클릭 메뉴 (B04 2D 지도 · B05 배수유역도 공용)
|
|
*
|
|
* 지도 위 객체를 우클릭했을 때 뜨는 작은 메뉴. 뷰포트의 자식으로 넣고 뷰포트 기준 절대
|
|
* 위치로 띄운다.
|
|
*
|
|
* **주의**: 메뉴는 뷰포트 안에 있으므로 메뉴 위에서 난 pointer 이벤트가 지도 조작으로
|
|
* 새어 들어간다. 걸러 내지 않으면 메뉴를 닫는 처리가 항목의 click보다 먼저 돌아 아무 일도
|
|
* 일어나지 않는다(2026-08-01 B04에서 실제로 발생). 그래서 `contains()`를 함께 제공한다 —
|
|
* 지도 쪽 pointerdown/pointerup/contextmenu는 이 판정으로 먼저 걸러야 한다.
|
|
* ========================================================================== */
|
|
|
|
export interface MapContextMenu {
|
|
/** 뷰포트에 append할 요소. */
|
|
element: HTMLElement;
|
|
/** 항목은 [문구, 눌렀을 때 할 일] 짝. 위치는 뷰포트 기준 px. */
|
|
open: (x: number, y: number, items: Array<[label: string, action: () => void]>) => void;
|
|
close: () => void;
|
|
/** 이벤트가 메뉴 안에서 났는지. 지도 조작에서 걸러 내는 데 쓴다. */
|
|
contains: (target: EventTarget | null) => boolean;
|
|
}
|
|
|
|
/** `blockClass`는 페이지 CSS 블록명(예: `b04-map`, `b05-drainage`). */
|
|
export function createMapContextMenu(blockClass: string): MapContextMenu {
|
|
const element = document.createElement("div");
|
|
element.className = `${blockClass}__context-menu`;
|
|
element.hidden = true;
|
|
|
|
function close(): void {
|
|
element.hidden = true;
|
|
// 항목까지 비운다 — 남겨 두면 다음에 열었을 때 옛 동작이 함께 걸린다.
|
|
element.replaceChildren();
|
|
}
|
|
|
|
return {
|
|
element,
|
|
open(x, y, items) {
|
|
element.replaceChildren();
|
|
items.forEach(([label, action]) => {
|
|
const entry = document.createElement("button");
|
|
entry.type = "button";
|
|
entry.className = `${blockClass}__context-menu-item`;
|
|
entry.textContent = label;
|
|
entry.addEventListener("click", () => {
|
|
close();
|
|
action();
|
|
});
|
|
element.append(entry);
|
|
});
|
|
element.style.left = `${x}px`;
|
|
element.style.top = `${y}px`;
|
|
element.hidden = false;
|
|
},
|
|
close,
|
|
contains: (target) => target instanceof Node && element.contains(target),
|
|
};
|
|
}
|