Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D2nUMUMTsqm93PbQtipo6c
137 lines
4.8 KiB
TypeScript
137 lines
4.8 KiB
TypeScript
/* =============================================================================
|
||
* M02_DF_Detail.ts
|
||
* 상세 내용 판(선 · 알약) 공용 — 값 읽기 · 규칙 미리 보기 · 끊김 글 · 흐름 줄 · DOM 없음
|
||
* 셈은 모델 것 그대로(Eval 의 evalLogic · breaks · Model 의 traceBack · traceForward · Rule 의 ruleLabel)
|
||
* 글 [한, 영] 짝은 아래 TEXT
|
||
* ========================================================================== */
|
||
|
||
import { currentLanguageIndex } from "@ui/ui_template_locale";
|
||
import {
|
||
breaks,
|
||
evalLogic,
|
||
type CfBreak,
|
||
type CfEvalOptions,
|
||
type CfEvalResult,
|
||
type CfValue,
|
||
type CfValues,
|
||
} from "./M02_DF_Eval";
|
||
import {
|
||
portDir,
|
||
portsOut,
|
||
traceBack,
|
||
traceForward,
|
||
type CfBox,
|
||
type CfGateOp,
|
||
type CfLink,
|
||
type CfLogic,
|
||
type CfPort,
|
||
type CfRule,
|
||
type CfStep,
|
||
} from "./M02_DF_Model";
|
||
import { ruleLabel } from "./M02_DF_Rule";
|
||
|
||
const TEXT = {
|
||
Table: ["표 {n}줄", "table, {n} rows"],
|
||
Empty: ["값 없음", "no value"],
|
||
NoRule: ["규칙 없음 — 값이 그대로 감", "No rule — the value goes as is"],
|
||
} as const;
|
||
|
||
export const td = (key: keyof typeof TEXT, fill: Record<string, string | number> = {}): string =>
|
||
Object.entries(fill).reduce(
|
||
(s, [k, v]) => s.replace(`{${k}}`, String(v)),
|
||
TEXT[key][currentLanguageIndex === 1 ? 1 : 0] as string,
|
||
);
|
||
|
||
export const GATE_OPS: CfGateOp[] = ["=", "≠", "≥", "≤", ">", "<"];
|
||
export const SCALE_OPS: ("×" | "÷" | "+%" | "-%")[] = ["×", "÷", "+%", "-%"];
|
||
|
||
/** 셈에 넘길 것 — 값 묶음 · 다른 로직들 */
|
||
export interface DetailOptions {
|
||
values?: CfValues;
|
||
logics?: CfEvalOptions["logics"];
|
||
}
|
||
|
||
export const boxOf = (lg: CfLogic, id: string): CfBox | undefined =>
|
||
lg.boxes.find((b) => b.id === id);
|
||
|
||
export const portText = (lg: CfLogic, p: CfPort): string =>
|
||
`${boxOf(lg, p.box)?.name ?? "?"} › ${p.name}`;
|
||
|
||
/** 계산 한 번 — 한창 만드는 중 막히면 null */
|
||
export function evalSafe(lg: CfLogic, o: DetailOptions = {}): CfEvalResult | null {
|
||
try {
|
||
return evalLogic(lg, o.values, { logics: o.logics });
|
||
} catch {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
export const valueOf = (ev: CfEvalResult | null, p: CfPort): CfValue | undefined =>
|
||
ev?.ports[p.box]?.[p.name];
|
||
|
||
/** 화면에 보일 값 글 — 표는 줄 수 */
|
||
export function showValue(v: CfValue | undefined): string {
|
||
if (v === undefined || v === null) return td("Empty");
|
||
if (Array.isArray(v)) return td("Table", { n: Math.max(0, v.length - 1) });
|
||
return typeof v === "number" ? String(Number(v.toPrecision(12))) : String(v);
|
||
}
|
||
|
||
export function unitOf(lg: CfLogic, p: CfPort): string {
|
||
const box = boxOf(lg, p.box);
|
||
return (box && portsOut(box).find((x) => x.name === p.name)?.unit) || box?.unit || "-";
|
||
}
|
||
|
||
/** 알약이 나가는(out) 쪽인가 */
|
||
export const isOut = (box: CfBox, name: string): boolean => portDir(box, name) === "out";
|
||
|
||
/** 선 위 규칙 글 */
|
||
export const labelOf = (rule: CfRule | undefined): string =>
|
||
rule ? ruleLabel(rule) : td("NoRule");
|
||
|
||
/** 지나가기 전 값 → 지난 뒤 값 — 지난 뒤 값은 셈이 받는 알약에 실제로 들여보낸 것 · 못 들어갔으면 끊긴 까닭 */
|
||
export function preview(link: CfLink, ev: CfEvalResult | null): { before: string; after: string } {
|
||
const before = showValue(valueOf(ev, link.from));
|
||
if (!link.rule) return { before, after: before };
|
||
const arrived = ev?.arrived?.[link.to.box]?.[link.to.name];
|
||
if (arrived !== undefined && arrived !== null) return { before, after: showValue(arrived) };
|
||
const why = ev?.breaks?.find((b) => b.link === link.id)?.text;
|
||
return { before, after: why ?? td("Empty") };
|
||
}
|
||
|
||
/** 이 선에서 알릴 것 — 이 선이 막힌 까닭 · 같은 알약에서 나간 조건 선의 겹침 · 안 맞음 */
|
||
export function linkNotes(link: CfLink, ev: CfEvalResult | null): string[] {
|
||
return (ev?.breaks ?? [])
|
||
.filter(
|
||
(b) =>
|
||
b.link === link.id ||
|
||
((b.kind === "조건 겹침" || b.kind === "조건 안 맞음") &&
|
||
b.box === link.from.box &&
|
||
b.port === link.from.name),
|
||
)
|
||
.map((b) => b.text);
|
||
}
|
||
|
||
/** 온 길(dir "in") · 가는 길(dir "out") — 모델 것에서 자기 자신 줄은 뺌 */
|
||
export function trace(lg: CfLogic, p: CfPort, dir: "in" | "out"): CfStep[] {
|
||
const steps = dir === "in" ? traceBack(lg, p) : traceForward(lg, p);
|
||
return steps.filter((s) => !(s.box === p.box && s.port === p.name));
|
||
}
|
||
|
||
/** 그 상자(알약)에서 끊긴 곳 — 쉬운 말 */
|
||
export function breakLines(
|
||
lg: CfLogic,
|
||
boxId: string,
|
||
name?: string,
|
||
o: DetailOptions = {},
|
||
): string[] {
|
||
let list: CfBreak[] = [];
|
||
try {
|
||
list = breaks(lg, o.values, { logics: o.logics });
|
||
} catch {
|
||
// 만드는 중 셈이 막히면 끊긴 곳 없음으로 둠
|
||
}
|
||
return list
|
||
.filter((b) => b.box === boxId && (!name || !b.port || b.port === name))
|
||
.map((b) => b.text);
|
||
}
|