import type { LongitudinalSection, SectionDetailResponse, } from "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_Api_Fetch"; import { createLongitudinalProfile, longitudinalMinimumWidth, } from "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Section_View"; import { createWorkflowPanelHandle } from "@ui/ui_template_overlay"; import "../B06_wf3_ProfileCross/B06_wf3_ProfileCross_UI_Style.css"; const COLLAPSED_KEY = "b05-route-profile-collapsed"; const HORIZONTAL_SCROLLBAR_HEIGHT = 16; function normalizedLongitudinal(data: LongitudinalSection): LongitudinalSection { return { ...data, samples: data.samples.map((sample) => ({ ...sample, elevation_m: sample.elevation_m ?? sample.z ?? null, })), }; } export function createRouteProfilePanel(onSelectStation: (stationId: string) => void) { const root = document.createElement("section"); root.className = "b05-route-profile"; const panelHandle = createWorkflowPanelHandle("bottom"); const toggle = panelHandle.root; const body = document.createElement("div"); body.className = "b05-route-profile__body"; const empty = document.createElement("p"); empty.className = "b05-route-profile__empty"; empty.textContent = "최적 경로를 계산하면 종단면도가 표시됩니다."; body.append(empty); root.append(toggle, body); let detail: SectionDetailResponse | null = null; let selectedStationId: string | null = null; let stationInterval: number | undefined; let resizeTimer = 0; let lastWidth = 0; let lastHeight = 0; function draw(): void { if (!detail || body.clientWidth <= 0 || body.clientHeight <= 0) return; const availableWidth = Math.max(1, body.clientWidth - 30); const height = Math.max(1, body.clientHeight - HORIZONTAL_SCROLLBAR_HEIGHT); const minimumWidth = longitudinalMinimumWidth(detail.longitudinal, stationInterval); const width = Math.max(availableWidth, minimumWidth); lastWidth = body.clientWidth; lastHeight = height; body.replaceChildren( createLongitudinalProfile( normalizedLongitudinal(detail.longitudinal), selectedStationId, 1, undefined, onSelectStation, stationInterval, width, height, minimumWidth, ), ); } const resizeObserver = new ResizeObserver(() => { if ( body.clientWidth <= 0 || body.clientHeight <= 0 || (Math.abs(body.clientWidth - lastWidth) < 1 && Math.abs(body.clientHeight - lastHeight) < 1) ) return; window.clearTimeout(resizeTimer); resizeTimer = window.setTimeout(draw, 150); }); resizeObserver.observe(body); function setCollapsed(collapsed: boolean): void { root.classList.toggle("is-collapsed", collapsed); panelHandle.setOpen(!collapsed); sessionStorage.setItem(COLLAPSED_KEY, String(collapsed)); if (!collapsed) requestAnimationFrame(draw); } toggle.addEventListener("click", () => setCollapsed(!root.classList.contains("is-collapsed"))); setCollapsed(sessionStorage.getItem(COLLAPSED_KEY) === "true"); return { root, render(nextDetail: SectionDetailResponse, nextStationInterval?: number) { detail = nextDetail; stationInterval = nextStationInterval; draw(); requestAnimationFrame(draw); }, setSelectedStation(stationId: string | null) { selectedStationId = stationId; draw(); }, clear() { detail = null; selectedStationId = null; body.replaceChildren(empty); }, dispose() { window.clearTimeout(resizeTimer); resizeObserver.disconnect(); }, }; }