/* ============================================================================= * M02_DF_CellTable.ts * 함수 판 「칸 연결 표」 — 칸 · 연결 알약 · 입력 / 변수 / 결과 · 이어진 선 · 표에서 바로 칸 바꾸기 · 풀기 * 결과 칸은 하나 · 색은 시트 칸 표시와 같은 쪽(입력 파랑 · 변수 노랑 · 결과 초록) + 글자(입력 · 변수 · 결과) * 글 [한, 영] 짝은 아래 TEXT * ========================================================================== */ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { createButton, el } from "@ui/ui_template_elements"; import type { CfBox, CfLogic } from "./M02_DF_Model"; import { portText } from "./M02_DF_Detail"; const TEXT = { Title: ["칸 연결 표", "Cell connections"], Cell: ["칸", "Cell"], Pill: ["연결 알약", "Pill"], Kind: ["종류", "Kind"], Line: ["이어진 선", "Line"], In: ["입력", "Input"], Var: ["변수", "Variable"], Out: ["결과", "Result"], None: ["아직 연결된 칸이 없음", "No cell is connected yet"], Unplaced: ["칸 없음", "no cell"], NoLine: ["-", "-"], Release: ["풀기", "Release"], BadCell: ["칸 주소가 아님 — 예: B2", "Not a cell address — e.g. B2"], } as const; const tt = (key: keyof typeof TEXT): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0]; export type CellKind = "in" | "var" | "out"; export interface CellTableHandlers { /** 표에서 칸을 바꿈 — key = 알약 이름 */ move(kind: CellKind, key: string, cell: string): void; /** 풀기 — 입력은 칸만 비우고 알약은 남김 · 변수는 지움 · 결과는 칸만 비움 */ release(kind: CellKind, key: string): void; } interface Row { kind: CellKind; key: string; cell: string; line: string; } function rowsOf(lg: CfLogic, box: CfBox): Row[] { const f = box.func; if (!f) return []; const into = (port: string): string => { const l = lg.links.find((x) => x.to.box === box.id && x.to.name === port); return l ? portText(lg, l.from) : tt("NoLine"); }; const out = (): string => { const names = lg.links.filter((l) => l.from.box === box.id && l.from.name === f.output.name); return names.length ? names.map((l) => portText(lg, l.to)).join(", ") : tt("NoLine"); }; return [ ...f.inputs.map((i): Row => ({ kind: "in", key: i.port, cell: i.cell, line: into(i.port) })), ...f.variables.map((v): Row => ({ kind: "var", key: v.name, cell: v.cell ?? "", line: tt("NoLine"), })), { kind: "out", key: f.output.name, cell: f.output.cell, line: out() }, ]; } /** 칸 연결 표 그리기 — 바뀜은 handlers 로만 알림(그리기는 부른 쪽이 다시) */ export function renderCellTable( host: HTMLElement, lg: CfLogic, box: CfBox, readOnly: boolean, handlers: CellTableHandlers, ): void { host.classList.add("m02-dfcells"); const rows = rowsOf(lg, box).filter((r) => r.kind === "out" || r.key); const head = el("tr", { children: [tt("Cell"), tt("Pill"), tt("Kind"), tt("Line"), ""].map((t) => el("th", { text: t }), ), }); const body = rows.map((r) => { const input = el("input", { className: "ui-input ui-input--sm m02-dfcells__cell", attrs: { type: "text", "aria-label": `${r.key} ${tt("Cell")}`, placeholder: tt("Unplaced") }, }); input.value = r.cell; input.readOnly = readOnly; input.addEventListener("change", () => { const m = /^([A-Za-z]{1,3})(\d+)$/.exec(input.value.trim()); input.classList.toggle("is-bad", !m); input.title = m ? "" : tt("BadCell"); if (m) handlers.move(r.kind, r.key, `${m[1].toUpperCase()}${m[2]}`); }); return el("tr", { className: `m02-dfcells__row is-${r.kind}`, children: [ el("td", { children: [input] }), el("td", { text: r.key }), el("td", { children: [ el("span", { className: "m02-dfcells__kind", text: tt(r.kind === "in" ? "In" : r.kind === "var" ? "Var" : "Out"), }), ], }), el("td", { text: r.line }), el("td", { children: readOnly || (r.kind !== "var" && !r.cell) ? [] : [ createButton({ label: tt("Release"), variant: "ghost", size: "sm", onClick: () => handlers.release(r.kind, r.key), }), ], }), ], }); }); host.replaceChildren( el("strong", { className: "m02-dfcells__title", text: tt("Title") }), el("table", { className: "m02-dfcells__table", children: [el("thead", { children: [head] }), el("tbody", { children: body })], }), ); }