/* ============================================================================= * 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; layerButtons: HTMLElement; analyzeButton: HTMLButtonElement; deleteButton: HTMLButtonElement; autoButton: HTMLButtonElement; viewport: HTMLElement; backgroundImage: HTMLImageElement; canvas: HTMLCanvasElement; status: HTMLElement; progress: ReturnType; contextMenu: ReturnType; legend: ReturnType; summary: HTMLElement; basinList: HTMLElement; /** 폭 조절 손잡이 — 패널을 접을 때 본체가 dispose 한다. */ widthResizer: ReturnType; } export interface DrainageChromeLabels { title: string; analyze: string; analyzeTip: string; deleteSelected: string; deleteSelectedTip: 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.title = labels.deleteSelectedTip; 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, }; }