/* ============================================================================= * B06_wf3_ProfileCross_UI_Cross_Design.ts * 측점 표준횡단 설계 지정 컨트롤(지반유형·단면유형·측구위치)과 설계선 오버레이. * * 카드 헤더 아래에 세그먼트 버튼을 배치하고, 지반유형·단면유형이 모두 선택되면 * onChange로 계산을 요청한다. 계산 결과(section.design)는 상위에서 다시 렌더될 때 * 절·성토 단면적 표시와 설계선 오버레이로 반영된다. 편절편성은 측구위치가 자동 * 결정되어 컨트롤을 숨기고, 양절·양성에서만 배수 방향 선택을 노출한다. * ========================================================================== */ import { currentLanguageIndex, ui_locales } from "@ui/ui_template_locale"; import type { CrossDesign, CrossSection, DitchSide, GroundType, SectionMode, } from "./B06_wf3_ProfileCross_Api_Fetch"; const SVG_NS = "http://www.w3.org/2000/svg"; function L(key: keyof typeof ui_locales): string { return ui_locales[key][currentLanguageIndex]; } const GROUND_OPTIONS: Array<[GroundType, keyof typeof ui_locales]> = [ ["soil", "B06_Design_Ground_Soil"], ["ripping_rock", "B06_Design_Ground_Ripping"], ["blasting_rock", "B06_Design_Ground_Blasting"], ]; const MODE_OPTIONS: Array<[SectionMode, keyof typeof ui_locales]> = [ ["left_cut", "B06_Design_Mode_LeftCut"], ["right_cut", "B06_Design_Mode_RightCut"], ["both_cut", "B06_Design_Mode_BothCut"], ["both_fill", "B06_Design_Mode_BothFill"], ]; const DITCH_OPTIONS: Array<[DitchSide, keyof typeof ui_locales]> = [ ["left", "B06_Design_Ditch_Left"], ["right", "B06_Design_Ditch_Right"], ]; export interface CrossDesignChange { ground_type: GroundType; section_mode: SectionMode; ditch_side: DitchSide | null; } function segment( legend: string, options: Array<[T, keyof typeof ui_locales]>, selected: T | null, onPick: (value: T) => void, ): HTMLElement { const wrap = document.createElement("div"); wrap.className = "b06-design__seg"; const legendEl = document.createElement("span"); legendEl.className = "b06-design__seg-legend"; legendEl.textContent = legend; wrap.append(legendEl); const group = document.createElement("div"); group.className = "b06-design__seg-buttons"; for (const [value, labelKey] of options) { const button = document.createElement("button"); button.type = "button"; button.className = `b06-design__btn${value === selected ? " b06-design__btn--active" : ""}`; button.textContent = L(labelKey); button.setAttribute("aria-pressed", value === selected ? "true" : "false"); button.addEventListener("click", () => onPick(value)); group.append(button); } wrap.append(group); return wrap; } /** 카드 헤더용 설계 지정 컨트롤 바를 만든다. */ export function buildDesignControls( section: CrossSection, onChange: (chainageM: number, change: CrossDesignChange) => void, ): HTMLElement { const design = section.design; // 기본값: 토사(soil) + 좌절토(left_cut). 미지정 측점은 확정 시 이 기본값으로 채워진다. const state: { ground: GroundType; mode: SectionMode; ditch: DitchSide | null } = { ground: design?.ground_type ?? "soil", mode: design?.section_mode ?? "left_cut", ditch: design?.ditch_side ?? null, }; const bar = document.createElement("div"); bar.className = "b06-design"; // 컨트롤 상호작용이 카드 선택 클릭으로 전파되지 않게 한다. bar.addEventListener("click", (event) => event.stopPropagation()); const needsDitch = (): boolean => state.mode === "both_cut" || state.mode === "both_fill"; const emit = (): void => { if (!state.ground || !state.mode) return; onChange(section.chainage_m, { ground_type: state.ground, section_mode: state.mode, ditch_side: needsDitch() ? (state.ditch ?? "left") : null, }); }; bar.append( segment(L("B06_Design_Ground_Legend"), GROUND_OPTIONS, state.ground, (value) => { state.ground = value; emit(); }), segment(L("B06_Design_Mode_Legend"), MODE_OPTIONS, state.mode, (value) => { state.mode = value; emit(); }), ); if (needsDitch()) { bar.append( segment(L("B06_Design_Ditch_Legend"), DITCH_OPTIONS, state.ditch, (value) => { state.ditch = value; emit(); }), ); } const readout = document.createElement("div"); readout.className = "b06-design__areas"; if (design) { const cut = document.createElement("span"); cut.className = "b06-design__area b06-design__area--cut"; cut.textContent = `${L("B06_Design_Cut_Area")} ${design.cut_area_m2.toFixed(2)}㎡`; const fill = document.createElement("span"); fill.className = "b06-design__area b06-design__area--fill"; fill.textContent = `${L("B06_Design_Fill_Area")} ${design.fill_area_m2.toFixed(2)}㎡`; readout.append(cut, fill); } else { const unset = document.createElement("span"); unset.className = "b06-design__area b06-design__area--unset"; unset.textContent = L("B06_Design_Unset"); readout.append(unset); } bar.append(readout); return bar; } /** 횡단 SVG에 표준단면 설계선을 겹쳐 그린다. */ export function appendCrossDesignOverlay( svg: SVGSVGElement, design: CrossDesign, x: (offset: number) => number, toDisplayY: (elevation: number) => number, ): void { const line = design.design_line; if (!line || line.length < 2) return; const points = line.map((point) => `${x(point.offset_m)},${toDisplayY(point.elevation_m)}`); const polyline = document.createElementNS(SVG_NS, "polyline"); polyline.setAttribute("points", points.join(" ")); polyline.setAttribute("class", "b06-chart__design-cross"); svg.append(polyline); }