feat(m02-df): 재료 조건 찾기 검색어 틀 — 틀 칸 · 자리별 알약 · 지금 잡히는 재료 미리 보기
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
/* =============================================================================
|
||||
* 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 }] : [];
|
||||
}),
|
||||
}),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user