/* ============================================================================= * 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; 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(); 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 = {}; 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 range = { start_m: read().start_m, end_m: read().end_m }; write({ ...read(), ...defaults, ...range, condition: "dry" }); 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): 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, maxSection: number) { defaults = next; fields.get("end_m")!.input.title = `≤ ${maxSection} m`; }, onChange(fn: () => void) { listeners.push(fn); }, }; }