import { useMemo, useRef, useState } from "react"; import { bytesToHex, triggerBlobDownload } from "@add-ideas/toolbox-helpers"; import { BASE58_LIMIT, decodeInput, encodeOutput, formatHexRows, MAX_BYTES, type ByteEncoding, } from "../core/encoding"; import { decodeStructured, encodeStructured, parseDer, parseProtobuf, parseSchema, type ByteNode, } from "../core/inspect"; type Inspector = "bytes" | "cbor" | "msgpack" | "der" | "protobuf"; const EXAMPLE = new TextEncoder().encode("Hello, binary world!\n"); const BYTE_PAGE_SIZE = 4_096; export function Workbench() { const [bytes, setBytes] = useState(EXAMPLE); const [encoding, setEncoding] = useState("utf8"); const [source, setSource] = useState("Hello, binary world!\n"); const [inspector, setInspector] = useState("bytes"); const [structured, setStructured] = useState( '{\n "hello": "world",\n "count": 3\n}', ); const [schema, setSchema] = useState( '{\n "1": { "name": "id", "type": "uint" },\n "2": { "name": "name", "type": "string" }\n}', ); const [selection, setSelection] = useState({ start: 0, end: 0 }); const [bytePage, setBytePage] = useState(0); const [error, setError] = useState(""); const fileInput = useRef(null); const views = useMemo( () => ({ utf8: encodeOutput(bytes, "utf8"), hex: encodeOutput(bytes, "hex"), base64: encodeOutput(bytes, "base64"), base32: encodeOutput(bytes, "base32"), base58: bytes.length <= BASE58_LIMIT ? encodeOutput(bytes, "base58") : "Base58 display is disabled above 4 KiB to keep this quadratic conversion responsive.", }), [bytes], ); const decoded = useMemo(() => { try { if (inspector === "cbor" || inspector === "msgpack") { return { text: decodeStructured(inspector, bytes), nodes: [] as ByteNode[], error: "", }; } if (inspector === "der") return { text: "", nodes: parseDer(bytes), error: "" }; if (inspector === "protobuf") return { text: "", nodes: parseProtobuf(bytes, parseSchema(schema)), error: "", }; return { text: "", nodes: [] as ByteNode[], error: "" }; } catch (caught) { return { text: "", nodes: [] as ByteNode[], error: caught instanceof Error ? caught.message : String(caught), }; } }, [bytes, inspector, schema]); const bytePageCount = Math.max(1, Math.ceil(bytes.length / BYTE_PAGE_SIZE)); const visibleBytePage = Math.min(bytePage, bytePageCount - 1); const bytePageStart = visibleBytePage * BYTE_PAGE_SIZE; const bytePageEnd = Math.min(bytes.length, bytePageStart + BYTE_PAGE_SIZE); function selectRange(range: { start: number; end: number }): void { setSelection(range); if (range.start < bytes.length) setBytePage(Math.floor(range.start / BYTE_PAGE_SIZE)); } function applySource(): void { try { const next = decodeInput(source, encoding); setBytes(next); setSelection({ start: 0, end: 0 }); setBytePage(0); setError(""); } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)); } } function switchEncoding(next: ByteEncoding): void { try { const encoded = encodeOutput(bytes, next); setEncoding(next); setSource(encoded); setError(""); } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)); } } async function importFile(file: File | undefined): Promise { if (!file) return; try { if (file.size > MAX_BYTES) throw new RangeError( `Files are limited to ${MAX_BYTES.toLocaleString()} bytes.`, ); const next = new Uint8Array(await file.arrayBuffer()); setBytes(next); setEncoding("hex"); setSource(bytesToHex(next)); setSelection({ start: 0, end: 0 }); setBytePage(0); setError(""); } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)); } finally { if (fileInput.current) fileInput.current.value = ""; } } function encodeDocument(): void { try { if (inspector !== "cbor" && inspector !== "msgpack") throw new Error("Choose CBOR or MessagePack to encode JSON."); const next = encodeStructured(inspector, structured); setBytes(next); setEncoding("hex"); setSource(bytesToHex(next)); setSelection({ start: 0, end: next.length }); setBytePage(0); setError(""); } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)); } } return (

Local byte workbench

Understand what the bytes say.

Convert encodings, inspect offsets and safely decode common binary structures without uploading a file.

Local only

Canonical byte buffer

Input

{bytes.length.toLocaleString()} / {MAX_BYTES.toLocaleString()} bytes
void importFile(event.target.files?.[0])} />