- 표시 필터 버튼 묶음을 헤더에서 지도 좌측 상단(8px 여백)으로 옮겼다. 버튼 줄과 상태 문구를 한 오버레이가 세로로 쌓아, 문구가 버튼 아래에서 시작한다 - 버튼 줄은 한 줄 유지. 오른쪽 진행단계 오버레이에 끝이 가려도 창을 넓히거나 그 오버레이를 접으면 드러난다(사용자 확정) — 두 줄로 접는 쪽이 지도를 더 가린다 - 오버레이 빈 자리는 pointer-events를 흘려 지도 팬·유역 고르기를 막지 않고, 버튼 위 pointerdown만 지도로 내려가지 않게 끊는다 - 유역 번호 배지를 원에서 라운드 사각으로. 지도 위 다른 숫자가 전부 동그라미라 크기로만 갈렸다. 크기(지름 22px)는 그대로. B04 지도 배지도 같은 함수라 함께 바뀐다
168 lines
7.0 KiB
TypeScript
168 lines
7.0 KiB
TypeScript
/* =============================================================================
|
|
* B05_Profile_UI_Drainage_Chrome.ts
|
|
* 배수유역 패널의 DOM 뼈대 — 헤더·버튼·뷰포트·범례·유역 목록·폭 조절 손잡이.
|
|
*
|
|
* 패널 본체(B05_Profile_UI_Drainage_Panel)가 700줄 한계에 닿아 분리했다.
|
|
* 여기에는 상태도 이벤트 배선도 없다 — 요소를 만들어 붙이고 그대로 넘긴다.
|
|
* 배선(토글·포인터·그리기)은 전부 본체가 맡는다.
|
|
* ========================================================================== */
|
|
|
|
import { createWorkflowPanelHandle } from "@ui/ui_template_overlay";
|
|
import { createPanelResizer } from "@ui/ui_template_resizer";
|
|
import { createProgressCircle } from "@ui/ui_template_progress";
|
|
import { createMapContextMenu } from "@ui/ui_template_context_menu";
|
|
import { createFlowLegend } from "../B04_PreProcess/B04_PreProcess_UI_FlowRamp";
|
|
|
|
export interface DrainageChrome {
|
|
root: HTMLElement;
|
|
panelHandle: ReturnType<typeof createWorkflowPanelHandle>;
|
|
layerButtons: HTMLElement;
|
|
analyzeButton: HTMLButtonElement;
|
|
deleteButton: HTMLButtonElement;
|
|
autoButton: HTMLButtonElement;
|
|
viewport: HTMLElement;
|
|
backgroundImage: HTMLImageElement;
|
|
canvas: HTMLCanvasElement;
|
|
status: HTMLElement;
|
|
progress: ReturnType<typeof createProgressCircle>;
|
|
contextMenu: ReturnType<typeof createMapContextMenu>;
|
|
legend: ReturnType<typeof createFlowLegend>;
|
|
summary: HTMLElement;
|
|
basinList: HTMLElement;
|
|
/** 폭 조절 손잡이 — 패널을 접을 때 본체가 dispose 한다. */
|
|
widthResizer: ReturnType<typeof createPanelResizer>;
|
|
}
|
|
|
|
export interface DrainageChromeLabels {
|
|
title: string;
|
|
analyze: string;
|
|
analyzeTip: string;
|
|
deleteSelected: string;
|
|
auto: string;
|
|
autoTip: string;
|
|
imageAlt: string;
|
|
statusNeedRoute: string;
|
|
}
|
|
|
|
export interface DrainageChromeMetrics {
|
|
minWidth: number;
|
|
maxWidthRatio: number;
|
|
widthStorageKey: string;
|
|
}
|
|
|
|
export function createDrainageChrome(
|
|
labels: DrainageChromeLabels,
|
|
metrics: DrainageChromeMetrics,
|
|
): DrainageChrome {
|
|
const root = document.createElement("aside");
|
|
root.className = "b05-drainage";
|
|
// 이 패널은 하단 패널의 **오른쪽**에 도킹해 오른쪽으로 접힌다(제목 카드와 반대).
|
|
// 방향을 넘기지 않으면 화살표가 정확히 거꾸로 나온다.
|
|
// 이름표 "배수유역" — 세로(90°)로 화살표 위에 얹힌다(2026-08-04 사용자 지시).
|
|
const panelHandle = createWorkflowPanelHandle("side", "right", "배수유역");
|
|
|
|
const header = document.createElement("div");
|
|
header.className = "b05-drainage__header";
|
|
const title = document.createElement("h3");
|
|
title.textContent = labels.title;
|
|
// 표시 필터 버튼 묶음 — 헤더가 아니라 지도 좌측 상단에 얹는다(2026-08-17 사용자 지시).
|
|
// 지도 위에 있으니 버튼을 누른 포인터가 아래 지도까지 내려가면 안 된다 — 유역 고르기가
|
|
// 딸려 일어난다. 배선이 아니라 얹은 자리 때문에 생긴 것이라 여기서 막는다.
|
|
const layerButtons = document.createElement("div");
|
|
layerButtons.className = "b05-drainage__layers";
|
|
layerButtons.addEventListener("pointerdown", (event) => event.stopPropagation());
|
|
header.append(title);
|
|
|
|
// 세부유역 산정 — B04가 미리 분석해 둔 결과를 읽어 관을 보충하고 세부유역만 나눈다.
|
|
// 격자 해석은 하지 않으므로 즉시 끝난다.
|
|
const analyzeButton = document.createElement("button");
|
|
analyzeButton.type = "button";
|
|
analyzeButton.className = "b05-drainage__analyze";
|
|
analyzeButton.textContent = labels.analyze;
|
|
analyzeButton.title = labels.analyzeTip;
|
|
// 선택된 배관 삭제 — 편집 모드에서 마커를 선택해야 활성화된다.
|
|
const deleteButton = document.createElement("button");
|
|
deleteButton.type = "button";
|
|
deleteButton.className = "b05-drainage__analyze b05-drainage__tool";
|
|
deleteButton.textContent = labels.deleteSelected;
|
|
deleteButton.disabled = true;
|
|
// 자동 제안으로 되돌리기 — 편집한 배관 배치를 버리고 백엔드 자동 제안으로 재산정.
|
|
const autoButton = document.createElement("button");
|
|
autoButton.type = "button";
|
|
autoButton.className = "b05-drainage__analyze b05-drainage__tool";
|
|
autoButton.textContent = labels.auto;
|
|
autoButton.title = labels.autoTip;
|
|
header.append(analyzeButton, deleteButton, autoButton);
|
|
|
|
const viewport = document.createElement("div");
|
|
viewport.className = "b05-drainage__viewport";
|
|
const backgroundImage = document.createElement("img");
|
|
backgroundImage.className = "b05-drainage__image";
|
|
backgroundImage.alt = labels.imageAlt;
|
|
backgroundImage.draggable = false;
|
|
const canvas = document.createElement("canvas");
|
|
canvas.className = "b05-drainage__canvas";
|
|
const status = document.createElement("span");
|
|
status.className = "b05-drainage__status";
|
|
status.textContent = labels.statusNeedRoute;
|
|
// 지도 정중앙 로딩 서클 — 배경도·도엽 레이어·유역 산정이 끝날 때까지 화면이 비어 보인다.
|
|
const progress = createProgressCircle({ overlay: true });
|
|
progress.root.hidden = true;
|
|
// 배관 추가·삭제 우클릭 메뉴 — 편집 토글을 없앤 대신 이쪽으로 옮겼다(B04 지도와 같은 조작).
|
|
const contextMenu = createMapContextMenu("b05-drainage");
|
|
// 유입 강도 색띠 범례 — CSS가 지도 우측 세로로 세운다.
|
|
const legend = createFlowLegend();
|
|
// 지도 좌상단 오버레이 — 필터 버튼 줄과 상태 문구를 세로로 쌓는다. 둘을 각각 지도에
|
|
// 절대배치하면 버튼이 두 줄로 접힐 때 문구가 그 아래에 겹친다.
|
|
const mapOverlay = document.createElement("div");
|
|
mapOverlay.className = "b05-drainage__map-overlay";
|
|
mapOverlay.append(layerButtons, status);
|
|
viewport.append(
|
|
backgroundImage,
|
|
canvas,
|
|
mapOverlay,
|
|
progress.root,
|
|
contextMenu.element,
|
|
legend.root,
|
|
);
|
|
|
|
const summary = document.createElement("div");
|
|
summary.className = "b05-drainage__summary";
|
|
summary.hidden = true;
|
|
const basinList = document.createElement("div");
|
|
basinList.className = "b05-drainage__basins";
|
|
basinList.hidden = true;
|
|
// 왼쪽 경계를 끌어 폭을 조절한다. 상한은 하단 패널 폭의 70%,
|
|
// 세로는 하단 패널에 그대로 딸려 간다(따로 조절하지 않는다 — 사용자 지시).
|
|
const widthResizer = createPanelResizer({
|
|
axis: "horizontal",
|
|
target: root,
|
|
cssVar: "--b05-drainage-width",
|
|
direction: -1,
|
|
min: metrics.minWidth,
|
|
max: () => (root.parentElement?.clientWidth ?? window.innerWidth) * metrics.maxWidthRatio,
|
|
storageKey: metrics.widthStorageKey,
|
|
});
|
|
|
|
root.append(panelHandle.root, widthResizer.root, header, viewport, summary, basinList);
|
|
|
|
return {
|
|
root,
|
|
panelHandle,
|
|
layerButtons,
|
|
analyzeButton,
|
|
deleteButton,
|
|
autoButton,
|
|
viewport,
|
|
backgroundImage,
|
|
canvas,
|
|
status,
|
|
progress,
|
|
contextMenu,
|
|
legend,
|
|
summary,
|
|
basinList,
|
|
widthResizer,
|
|
};
|
|
}
|