/* ============================================================================= * M02_DF_Entry_Detail.ts * 단가산출 흐름도 — 「상세 내용」 칸 틀(정함 9): 머리(왼쪽 「상세 내용」 · 오른쪽 고른 상자 제목 · 알약이면 그 옆 알약 이름) * · 설정 · 본문 · 경고 차례. 상자 · 알약 · 선 무엇을 골라도 바뀜 — 본문은 Entry 가 종류별 판을 끼움. * 선 · 알약 본문은 규칙 판 · 알약 판(sub2)이 오기 전까지 이 파일이 흐름 줄(보내는 곳 → 받는 곳 · 출처 사슬 · 가는 곳)만 보임. * ========================================================================== */ import { currentLanguageIndex } from "@ui/ui_template_locale"; import { el } from "@ui/ui_template_elements"; import type { CfLogic, CfPort, CfStep } from "./M02_DF_Model"; const TEXT = { Detail: ["상세 내용", "Details"], Settings: ["설정", "Settings"], Body: ["본문", "Content"], Warn: ["경고", "Warnings"], WarnNone: ["경고 없음", "No warnings"], PickSome: [ "다이어그램에서 상자 · 알약 · 선을 고르면 여기에 그 상세 내용이 보임", "Choose a box, pill or line in the diagram", ], Line: ["선", "Line"], From: ["보내는 곳", "From"], To: ["받는 곳", "To"], Rule: ["선 규칙", "Line rule"], RuleNone: ["없음 — 값을 그대로 보냄", "None — sends the value as is"], Back: ["온 길(출처)", "Came from"], Forward: ["가는 길", "Goes to"], NoTrace: ["아직 따라갈 수 없음", "Can't trace yet"], Nowhere: ["이어진 곳 없음", "Not connected"], In: ["받는 알약", "Incoming pill"], Out: ["내보내는 알약", "Outgoing pill"], } as const; const tt = (key: keyof typeof TEXT): string => (TEXT[key][currentLanguageIndex as 0 | 1] ?? TEXT[key][0]) as string; /** 고른 것 — 상자 · 선 · 알약 */ export type DfPick = | { kind: "box"; id: string } | { kind: "link"; id: string } | { kind: "pill"; box: string; name: string; dir: "in" | "out" } | null; export interface DetailParts { root: HTMLElement; title: HTMLElement; pill: HTMLElement; hint: HTMLElement; settings: HTMLElement; body: HTMLElement; warn: HTMLElement; } const row = (label: string, host: HTMLElement): HTMLElement => el("div", { className: "m02-df__detail-row", children: [el("h4", { text: label }), host], }); /** 상세 내용 칸 — 머리 + 설정 · 본문 · 경고 */ export function detailFrame(): DetailParts { const title = el("strong", { className: "m02-df__detail-title" }); const pill = el("span", { className: "m02-df__detail-pill" }); const hint = el("p", { className: "m02-df__muted", text: tt("PickSome") }); const settings = el("div", { className: "m02-df__detail-settings" }); const body = el("div", { className: "m02-df__detail-body" }); const warn = el("ul", { className: "m02-df__detail-warn" }); const root = el("section", { className: "m02-df__part m02-df__detail", children: [ el("div", { className: "m02-df__detail-head", children: [ el("h3", { text: tt("Detail") }), el("div", { className: "m02-df__detail-name", children: [title, pill] }), ], }), el("div", { className: "m02-df__part-body", children: [ hint, row(tt("Settings"), settings), row(tt("Body"), body), row(tt("Warn"), warn), ], }), ], }); return { root, title, pill, hint, settings, body, warn }; } const boxName = (lg: CfLogic, id: string): string => lg.boxes.find((b) => b.id === id)?.name ?? id; const portText = (lg: CfLogic, p: CfPort): string => `${boxName(lg, p.box)} · ${p.name}`; /** 머리 · 빈 자리 — 고른 것에 맞춤 · 설정 · 경고 줄은 Entry 가 채움 */ export function drawHead(d: DetailParts, lg: CfLogic, pick: DfPick): void { d.hint.hidden = !!pick; for (const r of [d.settings, d.body, d.warn]) (r.parentElement as HTMLElement).hidden = !pick; const link = pick?.kind === "link" ? lg.links.find((l) => l.id === pick.id) : undefined; d.title.textContent = pick?.kind === "box" ? boxName(lg, pick.id) : pick?.kind === "pill" ? boxName(lg, pick.box) : link ? `${tt("Line")} · ${boxName(lg, link.from.box)} → ${boxName(lg, link.to.box)}` : ""; d.pill.textContent = pick?.kind === "pill" ? `${tt(pick.dir === "in" ? "In" : "Out")} · ${pick.name}` : ""; d.pill.hidden = pick?.kind !== "pill"; } /** 경고 — 화면 계산 · 검사 글 가운데 고른 것(상자 이름 · 알약 이름)이 든 줄만 */ export function drawWarnings(d: DetailParts, lg: CfLogic, pick: DfPick, all: string[]): void { const names: string[] = []; if (pick?.kind === "box") names.push(`「${boxName(lg, pick.id)}」`); if (pick?.kind === "pill") names.push(`「${boxName(lg, pick.box)}」`); if (pick?.kind === "link") { const l = lg.links.find((x) => x.id === pick.id); if (l) names.push(`「${boxName(lg, l.from.box)}」`, `「${boxName(lg, l.to.box)}」`); } const mine = [...new Set(all)].filter( (w) => names.some((n) => w.includes(n)) && (pick?.kind !== "pill" || w.includes(pick.name)), ); d.warn.replaceChildren( ...(mine.length ? mine.map((w) => el("li", { text: w })) : [el("li", { className: "m02-df__muted", text: tt("WarnNone") })]), ); } /** 선 본문 — 보내는 곳 → 받는 곳 · 선 규칙 글(규칙 판이 오면 그쪽이 그림) */ export function lineBody( host: HTMLElement, lg: CfLogic, id: string, ruleText: string | null, ): void { const l = lg.links.find((x) => x.id === id); if (!l) return void host.replaceChildren(); host.replaceChildren( el("dl", { className: "m02-df__detail-dl", children: [ el("dt", { text: tt("From") }), el("dd", { text: portText(lg, l.from) }), el("dt", { text: tt("To") }), el("dd", { text: portText(lg, l.to) }), el("dt", { text: tt("Rule") }), el("dd", { text: ruleText ?? tt("RuleNone") }), ], }), ); } /** 알약 본문 — 온 길(출처 사슬) · 가는 길 · 흐름 따라가기(Model traceBack · traceForward)가 아직이면 한 줄 */ export function pillBody( host: HTMLElement, lg: CfLogic, back: () => CfStep[], forward: () => CfStep[], ): void { const list = (steps: () => CfStep[]): HTMLElement => { let got: CfStep[]; try { got = steps(); } catch { return el("p", { className: "m02-df__muted", text: tt("NoTrace") }); } if (!got.length) return el("p", { className: "m02-df__muted", text: tt("Nowhere") }); return el("ol", { className: "m02-df__detail-trace", children: got.map((s) => el("li", { text: portText(lg, { box: s.box, name: s.port }) })), }); }; host.replaceChildren( el("h5", { text: tt("Back") }), list(back), el("h5", { text: tt("Forward") }), list(forward), ); }