156 lines
5.7 KiB
TypeScript
156 lines
5.7 KiB
TypeScript
/* =============================================================================
|
|
* 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<T extends string>(
|
|
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);
|
|
}
|