Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
148 lines
5.3 KiB
TypeScript
148 lines
5.3 KiB
TypeScript
/* =============================================================================
|
||
* M02_DF_PillPanel.ts
|
||
* 상세 내용 판 — 알약: 알약을 고르면 이름 · 값 · 단위 · 온 길(출처 사슬) · 가는 곳(이어진 선 · 받는 상자) · 끊김 까닭
|
||
* 모양은 `docs/raw/M01_쉬운단가/흐름도_정함.md` 정함 8 · 9 · M02_DF_Model 의 CfStep 그대로 · 읽기만(고치기는 선 · 상자 판에서)
|
||
* 글 [한, 영] 짝은 아래 TEXT
|
||
* ========================================================================== */
|
||
|
||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||
import { createButton, el } from "@ui/ui_template_elements";
|
||
import type { CfLogic, CfStep } from "./M02_DF_Model";
|
||
import {
|
||
boxOf,
|
||
breakLines,
|
||
evalSafe,
|
||
isOut,
|
||
labelOf,
|
||
portText,
|
||
showValue,
|
||
trace,
|
||
unitOf,
|
||
valueOf,
|
||
type DetailOptions,
|
||
} from "./M02_DF_Detail";
|
||
import "./M02_DF_LinkPanel.css";
|
||
|
||
const TEXT = {
|
||
Name: ["이름", "Name"],
|
||
Value: ["값", "Value"],
|
||
Unit: ["단위", "Unit"],
|
||
Kind_out: ["내보내는 알약", "Sending pill"],
|
||
Kind_in: ["받는 알약", "Receiving pill"],
|
||
From: ["온 길", "Where it came from"],
|
||
To: ["가는 곳", "Where it goes"],
|
||
Start: ["여기서 시작 — 더 거슬러 갈 곳이 없음", "It starts here — nothing before it"],
|
||
End: ["여기가 끝 — 더 가는 곳이 없음", "It ends here — nothing after it"],
|
||
Breaks: ["끊긴 곳", "Breaks"],
|
||
NoBreak: ["끊긴 곳 없음", "No breaks"],
|
||
Open: ["이 상자 보기", "Open this box"],
|
||
Line: ["선", "Line"],
|
||
} as const;
|
||
|
||
const tp = (key: keyof typeof TEXT): string => TEXT[key][currentLanguageIndex === 1 ? 1 : 0];
|
||
|
||
const line = (label: string, ...value: (HTMLElement | string)[]): HTMLElement =>
|
||
el("div", {
|
||
className: "m02-dflink-row",
|
||
children: [el("span", { className: "m02-dflink-label", text: label }), ...value],
|
||
});
|
||
|
||
/** 한 줄 = 선 하나면 「보낸 알약 → (규칙 글) → 받는 알약」 · 함수 안 걸음이면 알약 하나 */
|
||
function steps(
|
||
lg: CfLogic,
|
||
list: CfStep[],
|
||
arrow: string,
|
||
empty: string,
|
||
onBox?: (id: string) => void,
|
||
): HTMLElement {
|
||
if (!list.length) return el("p", { className: "m02-dflink-note", text: empty });
|
||
const pill = (box: string, name: string): HTMLElement | string =>
|
||
onBox
|
||
? createButton({
|
||
label: portText(lg, { box, name }),
|
||
variant: "ghost",
|
||
size: "sm",
|
||
onClick: () => onBox(box),
|
||
})
|
||
: portText(lg, { box, name });
|
||
return el("ol", {
|
||
className: "m02-dfpill-list",
|
||
children: list.map((s) => {
|
||
const link = s.link ? lg.links.find((l) => l.id === s.link) : undefined;
|
||
const parts: (HTMLElement | string)[] = link
|
||
? [
|
||
arrow,
|
||
pill(link.from.box, link.from.name),
|
||
"→",
|
||
...(s.rule
|
||
? [el("span", { className: "m02-dfpill-rule", text: `(${labelOf(s.rule)})` }), "→"]
|
||
: []),
|
||
pill(link.to.box, link.to.name),
|
||
]
|
||
: [arrow, pill(s.box, s.port)];
|
||
return el("li", { className: "m02-dfpill-step", children: parts });
|
||
}),
|
||
});
|
||
}
|
||
|
||
/** 알약 판 그리기 — 알약을 고르면 부름 · on.pick 이 있으면 줄의 상자 이름을 눌러 그 상자로 감 */
|
||
export function renderPillPanel(
|
||
host: HTMLElement,
|
||
lg: CfLogic,
|
||
boxId: string,
|
||
port: string,
|
||
on: { pick?(boxId: string): void } = {},
|
||
opts: DetailOptions = {},
|
||
): void {
|
||
host.classList.add("m02-dfpill");
|
||
const box = boxOf(lg, boxId);
|
||
if (!box) return host.replaceChildren();
|
||
const ev = evalSafe(lg, opts);
|
||
const out = isOut(box, port);
|
||
// 받는 알약의 값 = 선 규칙을 지나 실제로 들어온 값(없으면 보낸 알약 값)
|
||
const incoming = lg.links.find((l) => l.to.box === boxId && l.to.name === port);
|
||
const value = out
|
||
? valueOf(ev, { box: boxId, name: port })
|
||
: (ev?.arrived?.[boxId]?.[port] ?? (incoming ? valueOf(ev, incoming.from) : undefined));
|
||
const unit = out
|
||
? unitOf(lg, { box: boxId, name: port })
|
||
: incoming
|
||
? unitOf(lg, incoming.from)
|
||
: box.unit;
|
||
const back = trace(lg, { box: boxId, name: port }, "in");
|
||
const forward = trace(lg, { box: boxId, name: port }, "out");
|
||
const breaks = breakLines(lg, boxId, port, opts);
|
||
host.replaceChildren(
|
||
el("div", {
|
||
className: "m02-dflink-head",
|
||
children: [
|
||
el("strong", { className: "m02-dfpill-title", text: `${box.name} › ${port}` }),
|
||
el("span", { className: "m02-dflink-note", text: tp(out ? "Kind_out" : "Kind_in") }),
|
||
],
|
||
}),
|
||
line(tp("Name"), port),
|
||
line(tp("Value"), el("span", { className: "m02-dflink-value", text: showValue(value) })),
|
||
line(tp("Unit"), unit || "-"),
|
||
el("div", {
|
||
children: [
|
||
el("div", { className: "m02-dflink-label", text: tp("From") }),
|
||
steps(lg, back, "◀", tp("Start"), on.pick),
|
||
],
|
||
}),
|
||
el("div", {
|
||
children: [
|
||
el("div", { className: "m02-dflink-label", text: tp("To") }),
|
||
steps(lg, forward, "▶", tp("End"), on.pick),
|
||
],
|
||
}),
|
||
el("div", {
|
||
children: [
|
||
el("div", { className: "m02-dflink-label", text: tp("Breaks") }),
|
||
...(breaks.length
|
||
? breaks.map((b) => el("p", { className: "m02-dflink-warn", text: `⚠ ${b}` }))
|
||
: [el("p", { className: "m02-dflink-note", text: tp("NoBreak") })]),
|
||
],
|
||
}),
|
||
);
|
||
}
|