Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
136 lines
4.7 KiB
TypeScript
136 lines
4.7 KiB
TypeScript
/* =============================================================================
|
|
* 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 })],
|
|
}),
|
|
);
|
|
}
|