diff --git a/package-lock.json b/package-lock.json index 99adc66..c0b5f13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "@chainsafe/ssz": "^1.2.1", + "@lodestar/params": "^1.34.0", "@lodestar/types": "^1.34.0", "comlink": "^4.4.2", "js-yaml": "^4.1.0", @@ -62,7 +63,6 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -321,7 +321,6 @@ "dev": true, "hasInstallScript": true, "license": "MIT OR Apache-2.0", - "peer": true, "bin": { "biome": "bin/biome" }, @@ -1928,7 +1927,6 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -2024,7 +2022,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2683,7 +2680,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -2725,7 +2721,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -2735,7 +2730,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -2923,7 +2917,6 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/package.json b/package.json index 94565b7..1d7d74a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "@chainsafe/ssz": "^1.2.1", + "@lodestar/params": "^1.34.0", "@lodestar/types": "^1.34.0", "comlink": "^4.4.2", "js-yaml": "^4.1.0", diff --git a/src/app.tsx b/src/app.tsx index 530ab91..c7652b2 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,15 +1,18 @@ import type {Type} from "@chainsafe/ssz"; -import {useCallback, useEffect, useState} from "react"; +import {useCallback, useEffect, useMemo, useState} from "react"; +import {CustomTypeEditor} from "./components/custom-type-editor"; import {Footer} from "./components/footer"; import {Header} from "./components/header"; import {InputPanel} from "./components/input-panel"; import {OutputPanel} from "./components/output-panel"; import {StructureView} from "./components/structure-view/structure-view"; import {Toolbar} from "./components/toolbar"; +import {useDebounce} from "./hooks/use-debounce"; import {useSsz} from "./hooks/use-ssz"; import {useWorker} from "./hooks/use-worker"; +import {type CompileResult, compileCustomDsl} from "./lib/custom-type"; import {inputFormats, serializeOutputFormats} from "./lib/formats"; -import {type ForkName, forks, typeNames} from "./lib/types"; +import {CUSTOM_FORK, type ForkName, forks, typeNames} from "./lib/types"; const DEFAULT_FORK = "fulu"; const DEFAULT_TYPE = "BeaconBlock"; @@ -25,6 +28,11 @@ export default function App() { const [parsedValue, setParsedValue] = useState(null); const [inputMode, setInputMode] = useState<"editor" | "builder">("builder"); + // Custom-type DSL state + const [customDsl, setCustomDsl] = useState(""); + const [customCompile, setCustomCompile] = useState(null); + const debouncedDsl = useDebounce(customDsl, 300); + // Worker const worker = useWorker(); @@ -32,7 +40,41 @@ export default function App() { const result = useSsz(worker, serializeMode ? "serialize" : "deserialize", forkName, typeName, input, inputFormat); // Get current SSZ type - const sszType: Type | null = forks[forkName]?.[typeName] ?? null; + const sszType: Type | null = useMemo(() => { + if (forkName === CUSTOM_FORK) { + return customCompile?.ok ? (customCompile.types.get(typeName) ?? null) : null; + } + return forks[forkName]?.[typeName] ?? null; + }, [forkName, typeName, customCompile]); + + // TYPE dropdown options + const typeOptions = useMemo(() => { + if (forkName === CUSTOM_FORK) { + return customCompile?.ok ? customCompile.order : []; + } + return typeNames(forks[forkName] ?? {}); + }, [forkName, customCompile]); + + // Compile custom DSL (main thread for UI, worker for serialize/deserialize) + useEffect(() => { + if (forkName !== CUSTOM_FORK) return; + if (!debouncedDsl.trim()) { + setCustomCompile(null); + return; + } + const local = compileCustomDsl(debouncedDsl); + setCustomCompile(local); + if (worker) worker.compileCustom(debouncedDsl); + }, [debouncedDsl, forkName, worker]); + + // Keep typeName valid after custom compile changes + useEffect(() => { + if (forkName !== CUSTOM_FORK) return; + if (!customCompile?.ok) return; + if (!customCompile.types.has(typeName)) { + setTypeName(customCompile.order.at(-1) ?? ""); + } + }, [forkName, customCompile, typeName]); // Generate default value — callable from button and auto-trigger const generateDefault = useCallback(async () => { @@ -109,12 +151,19 @@ export default function App() { const handleForkChange = useCallback( (newFork: ForkName) => { setForkName(newFork); + if (newFork === CUSTOM_FORK) { + const order = customCompile?.ok ? customCompile.order : []; + if (!order.includes(typeName)) { + setTypeName(order.at(-1) ?? ""); + } + return; + } const types = typeNames(forks[newFork]); if (!types.includes(typeName)) { setTypeName(DEFAULT_TYPE); } }, - [typeName] + [typeName, customCompile] ); // Handle builder value change — sync to text input @@ -185,12 +234,22 @@ export default function App() { + {forkName === CUSTOM_FORK && ( + + )} +
{/* Left: Input */}
diff --git a/src/components/custom-type-editor.tsx b/src/components/custom-type-editor.tsx new file mode 100644 index 0000000..e9a37e5 --- /dev/null +++ b/src/components/custom-type-editor.tsx @@ -0,0 +1,40 @@ +type CustomTypeEditorProps = { + dsl: string; + onDslChange: (dsl: string) => void; + error: string | null; + parsedNames: string[]; +}; + +const PLACEHOLDER = `class MyContainer(Container): + slot: uint64 + parent_root: Bytes32 + attestations: List[Attestation, 128]`; + +export function CustomTypeEditor({dsl, onDslChange, error, parsedNames}: CustomTypeEditorProps) { + return ( +
+
+
+ + Custom Container Definition + + + {error ? "parse error" : parsedNames.length > 0 ? `parsed: ${parsedNames.join(", ")}` : "empty"} + +
+