/* ============================================================================= * B06_Section_UI_Cross_Culvert_Wire.ts * 횡단 카드의 배수관 세트 **계산 호출 + 이동량 되받기(토스트)** — Cross_View에서 * 분리(700줄 제한). 카드가 다시 그려질 때마다 한 번 불린다. * ========================================================================== */ import type { CrossSection, SectionSample } from "./B06_Section_Api_Fetch"; import { computeCulvertLayout } from "./B06_Section_UI_Cross_Culvert"; import type { CulvertLayout, InletStructureChoice, RevetKey } from "./B06_Section_UI_Cross_Culvert"; import { materialLabel, materialLimit } from "./B06_Section_UI_Cross_Culvert_Const"; import type { WallAdjust } from "./B06_Section_UI_Cross_Culvert_Types"; /** * 기슭막이 4축 조작 제어(2026-08-22 확정) — 벽을 눌러 고르고 조정창에서 * 좌우(x)·상하(d, 성토선 대각)·높이(h)·재질(m)을 만진다. 고른 벽은 여기 담아 두어 * 카드가 다시 그려져도 되살아난다. `select`는 값만 담고 다시 그리지 않는다. */ export interface RevetOffsetControl { adjustFor: (section: CrossSection, role: RevetKey) => WallAdjust; selectedFor: (section: CrossSection) => RevetKey | null; select: (chainageM: number, key: RevetKey | null) => void; /** 기하가 실제로 적용한 조작값을 되받아 담는다(한계에 걸린 요청값을 잘라 낸다). */ syncApplied: (chainageM: number, role: RevetKey, applied: WallAdjust) => void; /** 일부 축만 바꾼다 — 나머지는 유지. h: null = 기본값 복귀. */ update: (chainageM: number, role: RevetKey, patch: Partial) => void; reset: (chainageM: number, role: RevetKey) => void; } /** 유입측 구조물 형식 선택(드롭다운 — 2026-08-22 사용자). 세션 보관은 Page가 한다. */ export interface InletStructureControl { valueFor: (section: CrossSection) => InletStructureChoice; set: (chainageM: number, value: InletStructureChoice) => void; } /** 유출측 다단 기슭막이 단 수 제어(2026-08-22 사용자 — 유출 벽 기준 숫자 입력). */ export interface ExtraWallControl { countFor: (section: CrossSection) => number; /** 단 수 지정 — 줄이면 사라지는 단의 이동량도 함께 지운다. */ setCount: (chainageM: number, count: number) => void; /** 기하가 실제로 세운 단 수로 잘라 동기화(지형상 못 세운 단 정리). */ syncCount: (chainageM: number, built: number) => void; } import { showToast } from "@ui/ui_template_elements"; import { L } from "./B06_Section_UI_Section_Common"; /** * 배수관 세트 기하 계산 + 제어 상태 동기화. * * ① 세션의 이동량(기슭막이·추가 벽)·유입 구조물 선택을 모아 기하를 계산하고, * ② 성토부가 짧아져 못 세운 추가 벽은 개수를 잘라 동기화하며(고아 이동량 방지), * ③ 요청 이동량이 한계에 잘렸으면 실제 적용값으로 되돌려 담는다 — 안 그러면 눌러도 * 안 움직이는데 창의 숫자만 계속 커진다(2026-08-21 사용자 지적). 잘린 순간에는 * **왜 안 움직였는지 토스트**로 알린다(2026-08-22 사용자 — 13+15.7처럼 자동 * 자리가 이미 안쪽 한계라 ◀가 그대로 먹히지 않는 경우). 조작 중(그 벽이 선택된 * 상태)일 때만 띄운다 — 리로드로 복원된 옛 세션 값에는 침묵. */ export function computeCardCulvert( section: CrossSection, sourceSamples: SectionSample[], activeRevet: RevetKey | null, revetOffset?: RevetOffsetControl, inletStructure?: InletStructureControl, extraWalls?: ExtraWallControl, ): CulvertLayout | null { const layout = computeCulvertLayout( section, sourceSamples, revetOffset ? { inlet: revetOffset.adjustFor(section, "inlet"), outlet: revetOffset.adjustFor(section, "outlet"), extras: Array.from({ length: extraWalls?.countFor(section) ?? 0 }, (_, i) => revetOffset.adjustFor(section, `extra${i}`), ), } : undefined, inletStructure?.valueFor(section), ); if (!layout) return null; if (extraWalls && extraWalls.countFor(section) > layout.extraWalls.length) { // 요청 단 수보다 지형이 허락하는 단이 적다(벽이 원지반에 0.5m 이상 묻히면 성토 // 불필요 — 그 아래 단은 못 세운다). 조작 중일 때만 가능한 단 수를 토스트로 알린다. if (activeRevet === "outlet" || activeRevet?.startsWith("extra")) { showToast( L("B06_Cross_Extra_Limit").replace("{n}", String(layout.extraWalls.length)), "info", ); } extraWalls.syncCount(section.chainage_m, layout.extraWalls.length); } if (revetOffset) { const roles: Array<[RevetKey, WallAdjust]> = [ ["inlet", layout.revetShift.inlet], ["outlet", layout.revetShift.outlet], ...layout.revetShift.extras.map((applied, i): [RevetKey, WallAdjust] => [ `extra${i}`, applied, ]), ]; const wallOf = (role: RevetKey) => role.startsWith("extra") ? layout.extraWalls[Number(role.slice(5))] : layout.walls.find((wall) => wall.role === role); for (const [role, applied] of roles) { const requested = revetOffset.adjustFor(section, role); // 축별로 요청이 잘렸으면 왜 안 됐는지 토스트로 알린다(조작 중인 벽만). if (activeRevet === role) { if (Math.abs(requested.x - applied.x) > 0.05) { showToast( L( requested.x < applied.x ? "B06_Cross_Revet_Limit_Inward" : "B06_Cross_Revet_Limit_Outward", ), "info", ); } if (Math.abs(requested.d - applied.d) > 0.05) { showToast( L(requested.d < applied.d ? "B06_Cross_Revet_Limit_Up" : "B06_Cross_Revet_Limit_Down"), "info", ); } // 높이가 재질 한계에 잘렸으면 재질 변경을 안내(2026-08-22 사용자 — 경고와 // 높이 해제는 재질 선택으로). const wall = wallOf(role); if (requested.h != null && applied.h != null && requested.h > applied.h + 0.05 && wall) { showToast( L("B06_Cross_Height_Limit") .replace("{mat}", materialLabel(wall.material)) .replace("{limit}", materialLimit(wall.material).toFixed(1)), "info", ); } if (requested.h != null && applied.h != null && requested.h < applied.h - 0.05) { showToast(L("B06_Cross_Height_Floor"), "info"); } } revetOffset.syncApplied(section.chainage_m, role, applied); } } return layout; }