import type { TransactionsRecord } from "@olenbetong/appframe-updater"; import { useMemo } from "react"; function formatPreviewValue(value: unknown) { if (typeof value === "string") { let lines = value.split(/\r?\n/); return lines.length > 0 ? lines : [""]; } if (value && typeof value === "object") { try { let serialized = JSON.stringify(value, null, 2); if (serialized) { return serialized.split("\n"); } } catch {} } if (value === null) { return ["null"]; } return [String(value)]; } function wrapLineForWidth(line: string, maxWidth: number) { if (maxWidth <= 0) { return [line]; } let normalized = line.replaceAll("\t", " "); if (normalized.length <= maxWidth) { return [normalized]; } let segments: string[] = []; let remaining = normalized; while (remaining.length > maxWidth) { let breakIndex = remaining.lastIndexOf(" ", maxWidth); if (breakIndex <= 0) { breakIndex = maxWidth; } let segment = remaining.slice(0, breakIndex).trimEnd(); segments.push(segment); remaining = remaining.slice(breakIndex).replace(/^\s+/, ""); } segments.push(remaining); return segments; } function wrapLinesForWidth(lines: string[], maxWidth: number) { if (lines.length === 0) { return [""]; } return lines.flatMap((line) => { let wrapped = wrapLineForWidth(line, maxWidth); return wrapped.length > 0 ? wrapped : [""]; }); } function truncateKey(key: string, width: number) { if (width <= 0) { return ""; } if (key.length <= width) { return key.padEnd(width); } if (width === 1) { return "…"; } return `${key.slice(0, width - 1)}…`; } export type UseTransactionTextOptions = { transaction: TransactionsRecord | undefined; statusPreviewValue: string; frameWidth: number; }; export type UseTransactionTextResult = { text: string[]; dialogWidth: number; }; export function useTransactionText({ transaction, statusPreviewValue, frameWidth, }: UseTransactionTextOptions): UseTransactionTextResult { return useMemo(() => { let entries = transaction ? Object.entries(transaction) .filter(([key]) => key !== "PrimKey") .map(([key, value]) => ({ key, lines: formatPreviewValue(key === "Status" ? statusPreviewValue : value), })) : []; let maxKeyLength = entries.reduce((max, entry) => Math.max(max, entry.key.length), 0); let rawKeyColumnWidth = Math.max(maxKeyLength, 1); let availableDialogWidth = Math.max(frameWidth - 4, 1); let dialogWidth = Math.min(Math.max(rawKeyColumnWidth + 80, 10), availableDialogWidth); if (!Number.isFinite(dialogWidth) || dialogWidth <= 0) { dialogWidth = 10; } let contentWidth = Math.max(dialogWidth - 4, 1); let keyColumnWidth = entries.length > 0 ? Math.min(rawKeyColumnWidth, Math.max(contentWidth - 1, 0)) : 0; if (keyColumnWidth < 0) { keyColumnWidth = 0; } let separatorWidth = keyColumnWidth > 0 ? 1 : 0; let valueColumnWidth = Math.max(contentWidth - keyColumnWidth - separatorWidth, 1); let text: string[] = []; if (entries.length === 0) { return { text, dialogWidth }; } let spacer = ""; let blankKey = keyColumnWidth > 0 ? " ".repeat(keyColumnWidth) : ""; let separator = separatorWidth > 0 ? " " : ""; entries.forEach((entry, entryIndex) => { let valueLines = wrapLinesForWidth(entry.lines, valueColumnWidth); if (valueLines.length === 0) { valueLines = [""]; } let formattedKey = truncateKey(entry.key, keyColumnWidth); valueLines.forEach((line, lineIndex) => { let keySegment = lineIndex === 0 ? formattedKey : blankKey; let combined = keyColumnWidth > 0 ? `${keySegment}${separator}${line}` : line; text.push(combined); }); if (entryIndex < entries.length - 1) { text.push(spacer); } }); return { text, dialogWidth }; }, [transaction, statusPreviewValue, frameWidth]); }