Files
Aislo/M02_MasterTemplete/DataFlow/M02_DF_InputPanel_Template.ts
T

91 lines
3.9 KiB
TypeScript

/* =============================================================================
* M02_DF_InputPanel_Template.ts
* 입력 판 — 재료 조건 찾기 「검색어 틀」: 틀(예: 「레미콘 {굵은골재최대치수}-{강도}-{슬럼프}」) 입력 · 틀 자리 {이름} 마다
* 값을 줄 변수 알약 고르기 · 글 [한, 영] 짝은 아래 · 재료 판(`M02_DF_InputPanel_Material.ts`)이 부름
* ========================================================================== */
import { currentLanguageIndex } from "@ui/ui_template_locale";
import { createInputField, createSelectField, el } from "@ui/ui_template_elements";
import type { CfLogic, CfPort } from "./M02_DF_Model";
import { templateSlots, variablePills } from "./M02_DF_InputPanel_Build";
const TEXT = {
Template: ["검색어 틀 — 자리는 {이름} 으로", "Search template — slots as {name}"],
Placeholder: ["예: 레미콘 {굵은골재최대치수}-{강도}-{슬럼프}", "e.g. Ready-mix {a}-{b}-{c}"],
Help: [
"틀의 자리마다 변수 알약을 고르면 그 값이 끼워진 글로 재료를 찾음(띄어쓴 말은 모두 들어간 것)",
"Each slot takes a variable pill's value; words must all match",
],
Slot: ["자리 「{n}」 ← 알약", "Slot “{n}” ← pill"],
NoSlot: ["틀에 자리 {이름} 을 넣어 주세요", "Add a {slot} to the template"],
NoPill: ["변수 알약이 없음 — 변수 입력 상자를 먼저 만듦", "No variable pill yet"],
} as const;
type TKey = keyof typeof TEXT;
const t = (key: TKey, v: Record<string, string> = {}): string =>
Object.entries(v).reduce(
(s, [k, x]) => s.replace(`{${k}}`, x),
TEXT[key][currentLanguageIndex] as string,
);
export interface TemplateBox {
root: HTMLElement;
/** 지금 틀 · 자리별 알약 — 틀이 비었으면 null */
get(): { template: string; slots: { name: string; from: CfPort }[] };
}
/** 틀 칸 + 자리 고르기 칸 — 바뀌면 onChange */
export function templateBox(lg: CfLogic, onChange: () => void): TemplateBox {
const pills = variablePills(lg);
const tpl = createInputField({
type: "text",
label: t("Template"),
compact: true,
placeholder: t("Placeholder"),
});
const slotBox = el("div", { className: "m02-dfip__stack" });
const picks = new Map<string, HTMLSelectElement>();
const draw = (): void => {
const names = templateSlots(tpl.input.value);
const before = new Map([...picks].map(([k, s]) => [k, s.value]));
picks.clear();
slotBox.replaceChildren(
...(names.length
? pills.length
? names.map((n) => {
// 자리 이름과 같은 알약이 있으면 먼저 고름
const same = pills.findIndex((p) => p.port.name === n);
const sel = createSelectField({
label: t("Slot", { n }),
options: pills.map((p, i) => ({
value: String(i),
text: `${p.port.name}${p.example !== null ? ` = ${p.example}` : ""}`,
})),
value: before.get(n) ?? String(same >= 0 ? same : 0),
compact: true,
onChange: () => onChange(),
});
picks.set(n, sel.select);
return sel.root;
})
: [el("p", { className: "m02-dfip__muted is-bad", text: t("NoPill") })]
: [el("p", { className: "m02-dfip__muted", text: t("NoSlot") })]),
);
};
tpl.input.addEventListener("input", () => (draw(), onChange()));
draw();
return {
root: el("div", {
className: "m02-dfip__stack",
children: [el("p", { className: "m02-dfip__muted", text: t("Help") }), tpl.root, slotBox],
}),
get: () => ({
template: tpl.input.value.trim(),
slots: templateSlots(tpl.input.value).flatMap((n) => {
const p = pills[Number(picks.get(n)?.value)];
return p ? [{ name: n, from: p.port }] : [];
}),
}),
};
}