- 파일 M02_CF_* → M02_DF_* · css m02-cf/m01-cf → m02-df · 서버 길 /api/m02/cf → /api/m02/df · 키 CF- → DF- · 사용자 요소 UE- 그대로 - _sample · .gitignore(_data git 밖) 옮김 · DataFlow 는 CostFlow 를 import 안 함 - 시험 test_m02_df_model · router · sample · inputpanel_build(옛 cf 시험은 그대로) - 서버 등록은 sub1 몫 — M02_MasterTemplete.DataFlow.M02_DF_Router 의 router Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VBToTCGk5dqDKeXCEP3vhk
202 lines
6.6 KiB
TypeScript
202 lines
6.6 KiB
TypeScript
/* =============================================================================
|
|
* 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, string | number> = {}): 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();
|
|
}
|