Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
91 lines
3.9 KiB
TypeScript
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 }] : [];
|
|
}),
|
|
}),
|
|
};
|
|
}
|