/* ============================================================================= * M02_DF_InputPanel_Vars.ts * 입력 상자 판 — 변수(그룹 밖 정보: 면적 · 길이 · 부피 …) 정의: 이름 · 갈래(숫자 / 선택 / 글) · 단위 · 범위 · 예시값 * 변수마다 출력 알약 하나 · `M02_DF_InputPanel.ts` 가 부름 · 글 [한, 영] 짝은 아래 * ========================================================================== */ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createButton, createInputField, createSelectField, el } from "@ui/ui_template_elements"; import type { CfLogic, CfVariable } from "./M02_DF_Model"; import { checkVariable, putVariable, removeVariable } from "./M02_DF_InputPanel_Build"; const TEXT = { List: ["변수 — 알약마다 하나", "Variables — one pill each"], None: ["아직 변수가 없음 — 아래에서 만듦", "No variable yet"], Name: ["이름", "Name"], Type: ["갈래", "Type"], Number: ["숫자", "Number"], Choice: ["선택", "Choice"], Text: ["글", "Text"], Unit: ["단위", "Unit"], Min: ["범위 아래", "Range from"], Max: ["범위 위", "Range to"], Choices: ["선택 목록(쉼표로 나눔)", "Choices (comma separated)"], Example: ["예시값", "Example"], Add: ["변수 넣기", "Add variable"], Save: ["고치기 끝", "Save change"], Edit: ["고치기", "Edit"], Remove: ["삭제", "Remove"], Cancel: ["취소", "Cancel"], Line: ["{n} · {u} · 예시 {e}", "{n} · {u} · e.g. {e}"], Range: ["범위 {a}~{b}", "range {a}~{b}"], } as const; type TKey = keyof typeof TEXT; const t = (key: TKey, v: Record = {}): string => Object.entries(v).reduce( (s, [k, x]) => s.replace(`{${k}}`, String(x)), TEXT[key][currentLanguageIndex] as string, ); const numOf = (s: string): number | null => s.trim() !== "" && Number.isFinite(Number(s)) ? Number(s) : null; /** 변수 한 줄 글 */ export function variableLine(v: CfVariable): string { const base = t("Line", { n: v.name, u: v.unit || "-", e: v.example === null ? "—" : String(v.example), }); const range = v.type === "number" && v.range ? ` · ${t("Range", { a: v.range[0], b: v.range[1] })}` : ""; const choices = v.type === "choice" ? ` · ${(v.choices ?? []).join(" / ")}` : ""; return `${base}${range}${choices}`; } export function renderVariables( host: HTMLElement, lg: CfLogic, boxId: string, on: { change(lg: CfLogic): void }, ): void { const box = lg.boxes.find((b) => b.id === boxId); const vars = box?.input?.variables ?? []; let editing: CfVariable | null = null; const form = (): HTMLElement => { const cur = editing; const name = createInputField({ type: "text", label: t("Name"), value: cur?.name ?? "", placeholder: "예: 면적", compact: true, }); const type = createSelectField({ label: t("Type"), options: [ { value: "number", text: t("Number") }, { value: "choice", text: t("Choice") }, { value: "text", text: t("Text") }, ], value: cur?.type ?? "number", compact: true, onChange: () => paint(), }); const unit = createInputField({ type: "text", label: t("Unit"), value: cur?.unit ?? "", placeholder: "예: ㎡", compact: true, }); const lo = createInputField({ type: "text", label: t("Min"), value: cur?.range ? String(cur.range[0]) : "", compact: true, }); const hi = createInputField({ type: "text", label: t("Max"), value: cur?.range ? String(cur.range[1]) : "", compact: true, }); const choices = createInputField({ type: "text", label: t("Choices"), value: (cur?.choices ?? []).join(", "), compact: true, }); const example = createInputField({ type: "text", label: t("Example"), value: cur?.example === null || cur?.example === undefined ? "" : String(cur.example), compact: true, }); const extra = el("div", { className: "m02-dfip__stack" }); const paint = (): void => { const ty = type.select.value; extra.replaceChildren( ...(ty === "number" ? [lo.root, hi.root] : ty === "choice" ? [choices.root] : []), ); }; paint(); const submit = (): void => { const ty = type.select.value as CfVariable["type"]; const ex = example.input.value.trim(); const v: CfVariable = { name: name.input.value.trim(), type: ty, unit: unit.input.value.trim(), example: ex === "" ? null : ty === "number" ? (numOf(ex) ?? ex) : ex, ...(ty === "number" && numOf(lo.input.value) !== null && numOf(hi.input.value) !== null ? { range: [Number(lo.input.value), Number(hi.input.value)] as [number, number] } : {}), ...(ty === "choice" ? { choices: choices.input.value .split(",") .map((s) => s.trim()) .filter(Boolean), } : {}), }; const why = checkVariable( v, vars.filter((x) => x.name !== cur?.name), ); name.setError(why ?? undefined); if (why) return; on.change(putVariable(lg, boxId, v, cur?.name)); }; const ok = createButton({ label: cur ? t("Save") : t("Add"), onClick: submit }); const cancel = cur ? createButton({ label: t("Cancel"), variant: "ghost", onClick: () => ((editing = null), draw()), }) : null; return el("div", { className: "m02-dfip__form", children: [ name.root, type.root, unit.root, extra, example.root, el("div", { className: "m02-dfip__chips", children: cancel ? [ok, cancel] : [ok] }), ], }); }; const draw = (): void => { const list = vars.length ? vars.map((v) => el("div", { className: "m02-dfip__item", children: [ el("span", { text: variableLine(v) }), createButton({ label: t("Edit"), variant: "ghost", size: "sm", onClick: () => ((editing = v), draw()), }), createButton({ label: t("Remove"), variant: "ghost", size: "sm", onClick: () => on.change(removeVariable(lg, boxId, v.name)), }), ], }), ) : [el("p", { className: "m02-dfip__muted", text: t("None") })]; host.replaceChildren(el("h5", { text: t("List") }), ...list, form()); }; draw(); }