import { useMemo, useRef, useEffect, useState, useCallback, CSSProperties } from 'react' interface HexLine { addr: string hexReal: string hexPad: string asciiReal: string asciiPad: string } function parseHexLines(str: string): HexLine[] { if (!str) return [] const encoded = new TextEncoder().encode(str) const lines: HexLine[] = [] for (let i = 0; i < encoded.length; i += 16) { const chunk = encoded.slice(i, i + 16) const n = chunk.length const addr = i.toString(16).padStart(8, '0') const padded = new Uint8Array(16) padded.set(chunk) const left = Array.from(padded.slice(0, 8)).map(b => b.toString(16).padStart(2, '0')).join(' ') const right = Array.from(padded.slice(8)).map(b => b.toString(16).padStart(2, '0')).join(' ') const full = left + ' ' + right const asciiAll = Array.from(padded).map(b => b >= 0x20 && b <= 0x7e ? String.fromCharCode(b) : '.').join('') if (n === 16) { lines.push({ addr, hexReal: full, hexPad: '', asciiReal: asciiAll, asciiPad: '' }) } else { const splitPos = n <= 8 ? 3 * n - 1 : 25 + 3 * (n - 8) - 1 lines.push({ addr, hexReal: full.substring(0, splitPos), hexPad: full.substring(splitPos), asciiReal: asciiAll.substring(0, n), asciiPad: asciiAll.substring(n), }) } } return lines } const monitorStyle: CSSProperties = { fontFamily: "'Roboto Mono', monospace", fontSize: 16, lineHeight: '22px', } const contentStyle: CSSProperties = { overflow: 'hidden', } const lineStyle: CSSProperties = { display: 'flex', whiteSpace: 'nowrap', } const addrStyle: CSSProperties = { opacity: 0.5, paddingRight: 12, flexShrink: 0, } const bytesStyle: CSSProperties = { paddingRight: 12, flexShrink: 0, whiteSpace: 'pre', } const asciiStyle: CSSProperties = { borderLeft: '1px solid var(--p3xr-fieldset-border, rgba(255,255,255,0.25))', paddingLeft: 12, flexShrink: 0, } const dimStyle: CSSProperties = { opacity: 0.5 } const scrollbarStyle: CSSProperties = { overflowX: 'auto', overflowY: 'hidden', position: 'sticky', bottom: 0, } export default function HexMonitor({ value, truncated, style }: { value: string, truncated?: boolean, style?: CSSProperties }) { const lines = useMemo(() => parseHexLines(value), [value]) const contentRef = useRef(null) const scrollbarRef = useRef(null) const [scrollWidth, setScrollWidth] = useState(0) const measure = useCallback(() => { const el = contentRef.current if (el) setScrollWidth(el.scrollWidth) }, []) useEffect(() => { measure() const el = contentRef.current if (!el) return const obs = new ResizeObserver(() => measure()) obs.observe(el) return () => obs.disconnect() }, [value, measure]) const syncScroll = useCallback(() => { if (contentRef.current && scrollbarRef.current) { contentRef.current.scrollLeft = scrollbarRef.current.scrollLeft } }, []) const merged = style ? { ...monitorStyle, ...style } : monitorStyle return (
{lines.map(line => (
{line.addr} {line.hexReal}{line.hexPad} {line.asciiReal}{line.asciiPad}
))}
) }