Files
Aislo/E04_Stab3D/E04_Stab3D_UI_Settings.ts
T
eomsangdonandClaude Opus 5.5 205bc275cd feat(E04): 화면 열쇠에 조건 · 건기 · 우기 결과 함께 표시 · 열 때 저장 설정 되살림 · [다시 해석] 뺌 (PLAN 564-2)
- 구간 목록 = 구간마다 건기 · 우기 안전율 둘 다 · 고른 조건 결과를 엶
- 캐시가 없으면 저장된 결과의 탐색 설정으로 칸을 채움
- [다시 해석] 뺌 — 엔진이 바뀌면 지문(엔진 파일 해시)이 바뀌어 [해석]이 다시 셈
- [기본값]이 조건을 건기로 되돌리지 않음

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:00:22 +09:00

142 lines
4.3 KiB
TypeScript

/* =============================================================================
* E04_Stab3D_UI_Settings.ts
* E04 좌측 탐색 설정 칸(PLAN 533-3) — 구간 시작 · 끝 · 기본(조건 · 모양 · 탐색 폭) · 고급(접힘 ·
* 기둥 · 구 중심 격자 · 반지름 증분 · 깊이 · 부피 제약) · [기본값]. 기본값 = 서버 SearchSettings.
* ========================================================================== */
import { createPanelCard } from "@ui/ui_template_collapsible";
import {
createButton,
createInputField,
createSelectField,
el,
type InputFieldHandle,
} from "@ui/ui_template_elements";
import { t, type LocaleKey } from "@ui/ui_template_locale";
export interface Settings {
start_m: number;
end_m: number;
condition: string;
shape: string;
ellipsoid_ratio: number;
box_half_width_m: number;
cell_m: number;
spacing_m: number;
h_min_m: number;
h_max_m: number;
h_step_m: number;
dr_m: number;
max_depth_m: number;
v_min_m3: number;
v_max_m3: number;
}
type NumberKey = Exclude<keyof Settings, "condition" | "shape">;
const BASIC: [NumberKey, LocaleKey][] = [
["ellipsoid_ratio", "E04_Ratio"],
["box_half_width_m", "E04_BoxWidth"],
];
const ADVANCED: [NumberKey, LocaleKey][] = [
["cell_m", "E04_Cell"],
["spacing_m", "E04_Spacing"],
["h_min_m", "E04_HMin"],
["h_max_m", "E04_HMax"],
["h_step_m", "E04_HStep"],
["dr_m", "E04_Dr"],
["max_depth_m", "E04_MaxDepth"],
["v_min_m3", "E04_VMin"],
["v_max_m3", "E04_VMax"],
];
export function buildSettingsCards() {
const fields = new Map<NumberKey, InputFieldHandle>();
const listeners: (() => void)[] = [];
const changed = (): void => listeners.forEach((fn) => fn());
const number = (key: NumberKey, label: LocaleKey): HTMLElement => {
const field = createInputField({ label: t(label), type: "number", compact: true });
field.input.step = "any";
field.input.dataset.key = key;
field.input.addEventListener("change", changed);
fields.set(key, field);
return field.root;
};
const rangeRow = el("div", { className: "e04-range" });
rangeRow.append(number("start_m", "E04_Start"), number("end_m", "E04_End"));
const shape = createSelectField({
label: t("E04_Shape"),
compact: true,
options: (["both", "sphere"] as const).map((v) => ({
value: v,
text: t(`E04_Shape_${v}` as LocaleKey),
})),
onChange: changed,
});
const condition = createSelectField({
label: t("E04_Condition"),
compact: true,
options: (["dry", "wet"] as const).map((v) => ({
value: v,
text: t(`E04_Condition_${v}` as LocaleKey),
})),
onChange: changed,
});
const basicCard = createPanelCard(t("E04_Card_Search"), false);
basicCard.append(condition.root, shape.root, ...BASIC.map(([k, l]) => number(k, l)));
let defaults: Partial<Settings> = {};
const advancedCard = createPanelCard(t("E04_Card_Advanced"), true);
const grid = el("div", { className: "e04-advanced" });
grid.append(...ADVANCED.map(([k, l]) => number(k, l)));
advancedCard.append(
grid,
createButton({
label: t("E04_Defaults"),
variant: "ghost",
size: "sm",
onClick: () => {
const { start_m, end_m, condition } = read(); // 구간 · 조건은 그대로
write({ ...read(), ...defaults, start_m, end_m, condition });
changed();
},
}),
);
function read(): Settings {
const out = { shape: shape.select.value, condition: condition.select.value } as Settings;
for (const [key, field] of fields) out[key] = Number(field.input.value);
return out;
}
function write(values: Partial<Settings>): void {
for (const [key, field] of fields) {
const value = values[key];
if (typeof value === "number" && Number.isFinite(value)) field.input.value = String(value);
}
if (values.shape) shape.select.value = values.shape;
if (values.condition) condition.select.value = values.condition;
}
return {
rangeRow,
basicCard,
advancedCard,
read,
write,
setRange(start: number, end: number) {
write({ start_m: start, end_m: end });
changed();
},
setDefaults(next: Partial<Settings>, maxSection: number) {
defaults = next;
fields.get("end_m")!.input.title = `≤ ${maxSection} m`;
},
onChange(fn: () => void) {
listeners.push(fn);
},
};
}