/* ============================================================================= * B06_Section_UI_Cross_Ford_Panel.ts * 세월교 측벽 **조정 오버레이 창**(2026-08-25 사용자 확정). * * 배수관 조정창(`_Cross_Structure_Panel.ts`)은 기슭막이 4축·다단·구간값·연동까지 * 안고 있어 592줄이다 — 세월교는 조작 축이 다르고 그쪽에 얹으면 700줄을 넘긴다. * 그래서 창을 따로 두되 **CSS 클래스와 조작 관례(십자 D-pad·0.1m 눈금)는 같다**. * * 조작(사용자 확정): 측벽 높이 · 측벽 좌우 · 측벽 상하(1:1.2 대각) · 관경 · 관 수량. * 바닥판 연장은 날개벽 각도가 정하므로 여기서 직접 만지지 않는다(B05 옵션). * ========================================================================== */ import type { FordAdjust, FordWallAdjust, FordWallRole } from "./B06_Section_UI_Cross_Ford_Geom"; /** 조정창이 쓰는 값·동작. 카드가 자기 상태에 맞춰 물려 준다. */ export interface FordPanelDeps { /** 지금 조작값(적용된 결과 — 한계에 잘린 뒤 값). */ adjustFor: (role: FordWallRole) => FordWallAdjust; /** 실제 그려진 벽 높이(m) — 자동일 때도 숫자를 보여 준다. */ heightFor: (role: FordWallRole) => number; /** 높이 ±0.1m. */ nudgeHeight: (role: FordWallRole, deltaM: number) => void; /** 좌우: 화면 좌(+)/우(−)로 미는 양(m). 부호 환산은 카드가 한다. */ nudge: (role: FordWallRole, screenDeltaM: number) => void; /** 상하: 1:1.2 사면을 타는 대각 이동(수평 성분 m, + = 사면 아래). */ nudgeSlope: (role: FordWallRole, deltaM: number) => void; /** 자동 자리로 되돌린다(세 축 모두). */ reset: (role: FordWallRole) => void; /** 관경(mm)·수량(련) — 값은 B05 정본(`pipe_points`)에 되돌려 쓴다. */ pipeDiameterMm: () => number; setPipeDiameterMm: (value: number) => void; pipeCount: () => number; setPipeCount: (value: number) => void; /** 바닥판 길이(m) — 날개벽 투영이 정한 값을 읽기 전용으로 보여 준다. */ slabLengthM: () => number; /** 창을 닫는다 = 구조물 선택 해제. */ close: () => void; } export interface FordPanelHandle { root: HTMLElement; /** null이면 숨긴다. 값이 오면 그 측벽 기준으로 다시 그린다. */ show: (role: FordWallRole | null) => void; } /** 한 걸음 — 집수정 9키와 같은 0.1m 눈금. */ const STEP_M = 0.1; /** 관경 선택지(mm) — B05 레지스트리 `ford_bridge.pipe_diameter_mm` choices와 같다. */ const DIAMETER_CHOICES_MM = [800, 1000, 1200, 1500]; function makeButton(label: string, title: string, onClick: () => void): HTMLButtonElement { const button = document.createElement("button"); button.type = "button"; button.className = "b06-structure-panel__btn"; button.textContent = label; button.title = title; button.addEventListener("click", (event) => { // 카드 선택·팬으로 번지면 도면이 다시 그려져 맞춰 둔 배율이 날아간다. event.stopPropagation(); onClick(); }); return button; } /** 항목 행 — 1행 이름 라벨 + 2행 값 조작(배수관 조정창과 같은 2행 구조). */ function makeRow(labelText: string): { row: HTMLElement; controls: HTMLElement } { const row = document.createElement("div"); row.className = "b06-structure-panel__struct"; const label = document.createElement("span"); label.className = "b06-structure-panel__label"; label.textContent = labelText; const controls = document.createElement("div"); controls.className = "b06-structure-panel__controls"; row.append(label, controls); return { row, controls }; } /** 세월교 측벽 조정 오버레이 창을 만든다. 처음에는 숨어 있다. */ export function buildFordPanel(deps: FordPanelDeps): FordPanelHandle { const root = document.createElement("div"); root.className = "b06-structure-panel is-hidden"; root.addEventListener("click", (event) => event.stopPropagation()); const title = document.createElement("span"); title.className = "b06-structure-panel__title"; const value = document.createElement("div"); value.className = "b06-structure-panel__value"; const heightLine = document.createElement("span"); const moveLine = document.createElement("span"); const slabLine = document.createElement("span"); value.append(heightLine, moveLine, slabLine); let current: FordWallRole | null = null; const act = (run: (role: FordWallRole) => void) => () => { if (current) run(current); }; const heightRow = makeRow("측벽 높이"); heightRow.controls.append( makeButton( "−", "측벽 높이 0.1m 낮추기", act((role) => deps.nudgeHeight(role, -STEP_M)), ), makeButton( "+", "측벽 높이 0.1m 높이기", act((role) => deps.nudgeHeight(role, STEP_M)), ), ); const moveRow = makeRow("이동"); moveRow.controls.classList.add("b06-structure-panel__buttons"); const dpad = ( label: string, slot: "up" | "down" | "left" | "right" | "reset", tip: string, onClick: () => void, ): HTMLButtonElement => { const button = makeButton(label, tip, onClick); button.classList.add(`b06-structure-panel__btn--${slot}`); return button; }; moveRow.controls.append( dpad( "▲", "up", "사면 위로(1:1.2 대각)", act((role) => deps.nudgeSlope(role, -STEP_M)), ), dpad( "◀", "left", "화면 왼쪽으로", act((role) => deps.nudge(role, STEP_M)), ), dpad("↺", "reset", "이 측벽 조정값 초기화", act(deps.reset)), dpad( "▶", "right", "화면 오른쪽으로", act((role) => deps.nudge(role, -STEP_M)), ), dpad( "▼", "down", "사면 아래로(1:1.2 대각)", act((role) => deps.nudgeSlope(role, STEP_M)), ), ); const pipeRow = makeRow("관경 · 수량"); const diameter = document.createElement("select"); diameter.className = "b06-structure-panel__select"; for (const mm of DIAMETER_CHOICES_MM) { const option = document.createElement("option"); option.value = String(mm); option.textContent = `Ø${mm}`; diameter.append(option); } diameter.addEventListener("change", () => deps.setPipeDiameterMm(Number(diameter.value))); const countValue = document.createElement("span"); countValue.className = "b06-structure-panel__count"; pipeRow.controls.append( diameter, makeButton("−", "관 수량 1련 줄이기", () => deps.setPipeCount(Math.max(1, deps.pipeCount() - 1)), ), countValue, makeButton("+", "관 수량 1련 늘리기", () => deps.setPipeCount(deps.pipeCount() + 1)), ); const closeButton = makeButton("✕", "닫기", () => deps.close()); closeButton.classList.add("b06-structure-panel__close"); root.append(title, closeButton, value, heightRow.row, moveRow.row, pipeRow.row); function render(): void { if (!current) return; const role = current === "inlet" ? "유입" : "유출"; title.textContent = `세월교 ${role} 측벽`; const adjust: FordWallAdjust = deps.adjustFor(current); heightLine.textContent = `높이 ${deps.heightFor(current).toFixed(2)}m` + (adjust.heightM === null ? " (자동)" : ""); moveLine.textContent = `좌우 ${adjust.lateralM.toFixed(1)}m · 상하 ${adjust.slopeM.toFixed(1)}m`; slabLine.textContent = `바닥판 ${deps.slabLengthM().toFixed(2)}m (날개벽 각도 종속)`; diameter.value = String(deps.pipeDiameterMm()); countValue.textContent = `${deps.pipeCount()}련`; } return { root, show(role) { current = role; root.classList.toggle("is-hidden", role === null); render(); }, }; } /** 측점별 세월교 조작값 제어 — 페이지(세션·정본)가 구현한다. */ export interface FordControl { adjustFor: (chainageM: number) => FordAdjust; update: (chainageM: number, role: FordWallRole, patch: Partial) => void; reset: (chainageM: number, role: FordWallRole) => void; /** 관경(mm)·수량(련) 저장 — B05 정본(`pipe_points`)으로 간다. */ setPipe: (chainageM: number, patch: { pipe_diameter_mm?: number; pipe_count?: number }) => void; /** 지금 선택된 측벽(측점별). */ selectedFor: (chainageM: number) => FordWallRole | null; select: (chainageM: number, role: FordWallRole | null) => void; }