/* ============================================================================= * 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 => 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 const commas = (n: number): string => Number(n.toPrecision(12)).toLocaleString("ko-KR", { maximumFractionDigits: 6 }); /** 화면에 보일 값 글 — 표는 줄 수 */ 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" ? commas(v) : 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); }