bring theme and codec-components library

This commit is contained in:
Victor Oliva
2024-10-22 15:31:20 +02:00
parent 9b6c0b3622
commit f2ee28e154
13 changed files with 1294 additions and 26 deletions

View File

@@ -1,8 +1,8 @@
import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";
import js from "@eslint/js"
import globals from "globals"
import reactHooks from "eslint-plugin-react-hooks"
import reactRefresh from "eslint-plugin-react-refresh"
import tseslint from "typescript-eslint"
export default tseslint.config(
{ ignores: ["dist"] },
@@ -25,6 +25,7 @@ export default tseslint.config(
],
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unnecessary-type-constraint": "off",
"@typescript-eslint/no-empty-object-type": "off",
"@typescript-eslint/no-unused-vars": [
"warn",
{
@@ -38,5 +39,5 @@ export default tseslint.config(
},
],
},
}
);
},
)

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>PAPI Console</title>
</head>
<body>
<body class="bg-polkadot-950 text-polkadot-100">
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>

View File

@@ -9,6 +9,9 @@
"lint": "eslint .",
"preview": "vite preview"
},
"prettier": {
"semi": false
},
"dependencies": {
"@polkadot-api/metadata-builders": "^0.8.2",
"@polkadot-api/substrate-bindings": "^0.9.2",
@@ -28,6 +31,7 @@
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/node": "^22.7.8",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",

20
pnpm-lock.yaml generated
View File

@@ -55,6 +55,9 @@ devDependencies:
'@eslint/js':
specifier: ^9.11.1
version: 9.13.0
'@types/node':
specifier: ^22.7.8
version: 22.7.8
'@types/react':
specifier: ^18.3.10
version: 18.3.11
@@ -96,7 +99,7 @@ devDependencies:
version: 8.11.0(eslint@9.13.0)(typescript@5.6.3)
vite:
specifier: ^5.4.8
version: 5.4.9
version: 5.4.9(@types/node@22.7.8)
packages:
@@ -926,6 +929,12 @@ packages:
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
dev: true
/@types/node@22.7.8:
resolution: {integrity: sha512-a922jJy31vqR5sk+kAdIENJjHblqcZ4RmERviFsER4WJcEONqxKcjNOlk0q7OUfrF5sddT+vng070cdfMlrPLg==}
dependencies:
undici-types: 6.19.8
dev: true
/@types/prop-types@15.7.13:
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
@@ -1079,7 +1088,7 @@ packages:
'@babel/plugin-transform-react-jsx-source': 7.25.7(@babel/core@7.25.8)
'@types/babel__core': 7.20.5
react-refresh: 0.14.2
vite: 5.4.9
vite: 5.4.9(@types/node@22.7.8)
transitivePeerDependencies:
- supports-color
dev: true
@@ -2508,6 +2517,10 @@ packages:
hasBin: true
dev: true
/undici-types@6.19.8:
resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==}
dev: true
/update-browserslist-db@1.1.1(browserslist@4.24.2):
resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==}
hasBin: true
@@ -2537,7 +2550,7 @@ packages:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
dev: true
/vite@5.4.9:
/vite@5.4.9(@types/node@22.7.8):
resolution: {integrity: sha512-20OVpJHh0PAM0oSOELa5GaZNWeDjcAvQjGXy2Uyr+Tp+/D2/Hdz6NLgpJLsarPTA2QJ6v8mX2P1ZfbsSKvdMkg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
@@ -2568,6 +2581,7 @@ packages:
terser:
optional: true
dependencies:
'@types/node': 22.7.8
esbuild: 0.21.5
postcss: 8.4.47
rollup: 4.24.0

View File

@@ -1,3 +1,3 @@
export default function App() {
return <div className="bg-red-300">App</div>;
return <div>App</div>
}

View File

@@ -0,0 +1,961 @@
/* eslint-disable react-hooks/rules-of-hooks */
import {
V15,
V14,
HexString,
Encoder,
Bytes,
} from "@polkadot-api/substrate-bindings"
import * as scale from "@polkadot-api/substrate-bindings"
import {
getDynamicBuilder,
getLookupFn,
LookupEntry,
MetadataLookup,
Var,
} from "@polkadot-api/metadata-builders"
import React, { FC, useRef } from "react"
import { fromHex, mapObject, mergeUint8 } from "@polkadot-api/utils"
import type {
EditAccountId,
EditBigNumber,
EditBool,
EditComponents,
EditEthAccount,
EditNumber,
EditStr,
ViewAccountId,
ViewBigNumber,
ViewBool,
ViewComponents,
ViewEthAccount,
ViewNumber,
ViewStr,
} from "./types"
import { NOTIN } from "./types"
export type MetadataType = V14 | V15
export const enum CodecComponentType {
Initial,
Updated,
}
export type CodecComponentInitial = Uint8Array | HexString
export type CodecComponentUpdate =
| { empty: true }
| { empty: false; decoded: any; encoded?: Uint8Array }
export type CodecComponentValue =
| { type: CodecComponentType.Initial; value?: CodecComponentInitial }
| { type: CodecComponentType.Updated; value: CodecComponentUpdate }
export type LookupType = Var["type"]
const detectNotin: <T>(x: T) => boolean = (x) => {
if (x === NOTIN) return true
return x != null && typeof x === "object"
? Object.values(x).some(detectNotin)
: false
}
const withNotin: <T>(
encoder: Encoder<T>,
) => (input: T) => Uint8Array | undefined = (encoder) => (input) =>
detectNotin(input) ? undefined : encoder(input)
const getInnerEnumCodec = (
input: scale.Codec<{ type: string; value: any }>,
idx: number,
type: string,
) => {
return scale.createCodec(
(value: any) => input.enc({ type, value }).slice(1),
(value) => {
const val: Uint8Array =
value instanceof Uint8Array
? value
: typeof value === "string"
? fromHex(value)
: new Uint8Array(value)
return input.dec(mergeUint8(new Uint8Array([idx]), val)).value
},
)
}
export function getViewCodecComponent(
baseComponents: ViewComponents,
): React.FC<{
metadata: MetadataType
codecType: number
value: Uint8Array | HexString
}> {
const {
CVoid,
CAccountId,
CBigNumber,
CNumber,
CBool,
CStr,
CBytes,
CEthAccount,
CEnum,
CSequence,
CArray,
CTuple,
CStruct,
COption,
CResult,
} = baseComponents as ViewComponents
const CodecComponent: React.FC<{
path: string[]
dynCodecs: ReturnType<typeof getDynamicBuilder>["buildDefinition"]
entry: LookupEntry
parent?: { id: number; variantIdx: number; variantTag: string }
value:
| {
empty: true
}
| { empty: false; decoded: any; encoded?: Uint8Array }
}> = ({ entry, value, dynCodecs, parent, path }) => {
if (value.empty) return <></>
const codec =
"id" in entry
? dynCodecs(entry.id)
: getInnerEnumCodec(
dynCodecs(parent!.id),
parent!.variantIdx,
parent!.variantTag,
)
const encodedValue = value.encoded || codec.enc(value.decoded)
const valueProps = { value: value.decoded, encodedValue: encodedValue }
if (entry.type === "struct") {
const latestValue = value.decoded
return (
<CStruct
{...valueProps}
shape={entry}
path={path}
innerComponents={mapObject(entry.value, (entry, key) => (
<CodecComponent
path={path.concat(key as string)}
dynCodecs={dynCodecs}
entry={entry}
value={
latestValue[key] === NOTIN
? { empty: true }
: {
empty: false,
decoded: latestValue[key],
}
}
/>
))}
/>
)
}
if (entry.type === "tuple") {
return (
<CTuple
{...{
...valueProps,
shape: entry,
path,
innerComponents: entry.value.map((entry, idx) => (
<CodecComponent
path={path.concat(idx.toString())}
dynCodecs={dynCodecs}
entry={entry}
value={
value.empty
? value
: value.decoded[idx] === NOTIN
? { empty: true }
: {
empty: false,
decoded: value.decoded[idx],
}
}
/>
)),
}}
/>
)
}
if (entry.type === "enum") {
let innerEntry
if (!value.empty) {
innerEntry = entry.value[value.decoded.type as string]
if (innerEntry.type === "lookupEntry")
innerEntry = innerEntry.value as any
}
return (
<CEnum
{...{
...valueProps,
path,
tags: Object.entries(entry.value).map(([tag, { idx }]) => ({
tag,
idx,
})),
shape: entry,
inner: value.empty ? null : (
<CodecComponent
path={path.concat(value.decoded.type)}
dynCodecs={dynCodecs}
entry={innerEntry}
parent={{
id: entry.id,
variantTag: value.decoded.type,
variantIdx:
entry.value[value.decoded.type as keyof typeof entry.value]
.idx,
}}
value={
value.decoded.value === NOTIN
? { empty: true }
: {
empty: false,
decoded: value.decoded.value,
encoded:
(valueProps as any).encodedValue &&
(valueProps as any).encodedValue.slice(1),
}
}
/>
),
}}
/>
)
}
if (entry.type === "result") {
return (
<CResult
{...{
...valueProps,
path,
shape: entry,
inner: value.empty ? null : (
<CodecComponent
path={path.concat(
`Result(${value.decoded.success ? "Success" : "Failure"})`,
)}
dynCodecs={dynCodecs}
entry={entry.value[value.decoded.success ? "ok" : "ko"]}
value={
value.decoded.value === NOTIN
? { empty: true }
: {
empty: false,
decoded: value.decoded.value,
}
}
/>
),
}}
/>
)
}
if (entry.type === "option") {
return (
<COption
{...{
...valueProps,
path,
shape: entry,
inner:
!value.empty && value.decoded === undefined ? (
<CVoid />
) : (
<CodecComponent
path={path.concat(
`Option(${value.empty ? "Void" : value.decoded === NOTIN ? "" : "Value"})`,
)}
dynCodecs={dynCodecs}
entry={entry.value}
value={
value.empty || value.decoded === NOTIN
? { empty: true }
: {
empty: false,
decoded: value.decoded,
}
}
/>
),
}}
/>
)
}
if (entry.type === "void") return <CVoid />
if (entry.type === "array") {
if (entry.value.type === "primitive" && entry.value.value === "u8") {
return <CBytes len={entry.len} path={path} {...(valueProps as any)} />
}
const latestValue = value.empty
? Array(entry.len).fill(NOTIN)
: (value.decoded as Array<any>)
return (
<CArray
{...{
...valueProps,
path,
shape: entry,
innerComponents: latestValue.map((decoded, idx) => (
<CodecComponent
dynCodecs={dynCodecs}
entry={entry.value}
path={path.concat(idx.toString())}
value={
decoded === NOTIN
? { empty: true }
: { empty: false, decoded }
}
/>
)),
}}
/>
)
}
if (entry.type === "sequence") {
if (entry.value.type === "primitive" && entry.value.value === "u8") {
return <CBytes {...(valueProps as any)} path={path} />
}
const latestValue = value.empty ? [] : (value.decoded as Array<any>)
return (
<CSequence
{...{
...valueProps,
path,
shape: entry,
innerComponents: latestValue.map((decoded, idx) => (
<CodecComponent
path={path.concat(idx.toString())}
dynCodecs={dynCodecs}
entry={entry.value}
value={
decoded === NOTIN
? { empty: true }
: { empty: false, decoded }
}
/>
)),
}}
/>
)
}
let type: any = undefined
let ResultComponent:
| ViewBigNumber
| ViewNumber
| ViewBool
| ViewStr
| ViewAccountId
| ViewEthAccount = null as any
switch (entry.type) {
case "compact": {
ResultComponent = entry.isBig ? CBigNumber : CNumber
type = (entry as any).size
break
}
case "primitive": {
if (entry.value === "str" || entry.value === "char")
ResultComponent = CStr
else if (entry.value === "bool") ResultComponent = CBool
else {
const nBits = Number(entry.value.slice(1))
ResultComponent = nBits < 64 ? CNumber : CBigNumber
type = entry.value
}
break
}
case "bitSequence": {
return null
}
case "AccountId32": {
ResultComponent = CAccountId
break
}
case "AccountId20": {
ResultComponent = CEthAccount
break
}
}
const Result = ResultComponent as FC<any>
return <Result {...valueProps} path={path} numType={type} />
}
const result: FC<{
metadata: V14 | V15
codecType: number
value: Uint8Array | HexString
}> = ({ metadata, codecType, value: propsValue }) => {
const lookupRef = useRef<{
metadata: V14 | V15
lookup: MetadataLookup
dynCodecs: ReturnType<typeof getDynamicBuilder>["buildDefinition"]
refresh: (
codecType: number,
value: Uint8Array | HexString,
) => { empty: true } | { empty: false; decoded: any; encoded: Uint8Array }
}>({
metadata: null as any,
lookup: null as any,
dynCodecs: null as any,
refresh: () => ({ empty: true }),
})
if (lookupRef.current.metadata !== metadata) {
const lookup = getLookupFn(metadata)
lookupRef.current = {
metadata,
lookup,
dynCodecs: getDynamicBuilder(lookup).buildDefinition,
refresh: (codecType, val) => {
try {
const decoded = lookupRef.current.dynCodecs(codecType).dec(val)
return {
empty: false,
decoded,
encoded: typeof val === "string" ? fromHex(val) : val,
}
} catch (_error) {
console.error("invalid hex for the codec")
return { empty: true }
}
},
}
}
const value = lookupRef.current.refresh(codecType, propsValue)
return (
<CodecComponent
path={[]}
{...{
dynCodecs: lookupRef.current.dynCodecs,
entry: lookupRef.current.lookup(codecType),
value: value,
}}
/>
)
}
return result as any
}
export function getCodecComponent(baseComponent: ViewComponents): React.FC<{
metadata: V14 | V15
codecType: number
value: CodecComponentValue
}>
export function getCodecComponent(baseComponent: EditComponents): React.FC<{
metadata: V14 | V15
codecType: number
value: CodecComponentValue
onUpdate?: (newValue: CodecComponentUpdate) => void
}>
export function getCodecComponent(
_baseComponents: EditComponents | ViewComponents,
) {
const {
CVoid,
CAccountId,
CBigNumber,
CNumber,
CBool,
CStr,
CBytes,
CEthAccount,
CEnum,
CSequence,
CArray,
CTuple,
CStruct,
COption,
CResult,
} = _baseComponents as EditComponents
const CodecComponent: React.FC<{
path: string[]
dynCodecs: ReturnType<typeof getDynamicBuilder>["buildDefinition"]
entry: LookupEntry
parent?: { id: number; variantIdx: number; variantTag: string }
value:
| {
empty: true
}
| { empty: false; decoded: any; encoded?: Uint8Array }
onChange: (value: any) => boolean
}> = ({ entry, value, dynCodecs, onChange, parent, path }) => {
const codec =
"id" in entry
? dynCodecs(entry.id)
: getInnerEnumCodec(
dynCodecs(parent!.id),
parent!.variantIdx,
parent!.variantTag,
)
const createDecode = (decoder: scale.Decoder<any>) =>
scale.createDecoder((bytes) => {
try {
const result = decoder(bytes)
return bytes.length === (bytes as Uint8Array & { i: number }).i
? result
: NOTIN
} catch {
return NOTIN
}
})
const decode = createDecode(codec.dec)
let valueProps:
| { type: "blank"; value: NOTIN; encodedValue: undefined }
| { type: "partial"; value: any; encodedValue: undefined }
| { type: "complete"; value: any; encodedValue: Uint8Array } = {
type: "blank" as const,
value: NOTIN,
encodedValue: undefined,
}
if (!value.empty) {
const encodedValue = value.encoded || withNotin(codec.enc)(value.decoded)
valueProps = encodedValue
? { type: "complete", value: value.decoded, encodedValue }
: { type: "partial", value: value.decoded, encodedValue: undefined }
}
if (entry.type === "struct") {
const latestValue = value.empty
? mapObject(entry.value, () => NOTIN)
: value.decoded
return (
<CStruct
{...valueProps}
decode={decode}
shape={entry}
onValueChanged={onChange}
path={path}
innerComponents={mapObject(entry.value, (entry, key) => (
<CodecComponent
path={path.concat(key as string)}
dynCodecs={dynCodecs}
entry={entry}
onChange={(x) => {
const value = { ...latestValue }
value[key] = x
return onChange(value)
}}
value={
latestValue[key] === NOTIN
? { empty: true }
: {
empty: false,
decoded: latestValue[key],
}
}
/>
))}
/>
)
}
if (entry.type === "tuple") {
const latestValue = value.empty
? entry.value.map(() => NOTIN)
: value.decoded
return (
<CTuple
{...{
...valueProps,
shape: entry,
onValueChanged: onChange,
decode,
path,
innerComponents: entry.value.map((entry, idx) => (
<CodecComponent
path={path.concat(idx.toString())}
dynCodecs={dynCodecs}
entry={entry}
onChange={(x) => {
const value = [...latestValue]
value[idx] = x
return onChange(value)
}}
value={
value.empty
? value
: value.decoded[idx] === NOTIN
? { empty: true }
: {
empty: false,
decoded: value.decoded[idx],
}
}
/>
)),
}}
/>
)
}
if (entry.type === "enum") {
let innerEntry
if (!value.empty) {
innerEntry = entry.value[value.decoded.type as string]
if (innerEntry.type === "lookupEntry")
innerEntry = innerEntry.value as any
}
return (
<CEnum
{...{
...valueProps,
decode,
onValueChanged: onChange,
path,
tags: Object.entries(entry.value).map(([tag, { idx }]) => ({
tag,
idx,
})),
shape: entry,
inner: value.empty ? null : (
<CodecComponent
path={path.concat(value.decoded.type)}
dynCodecs={dynCodecs}
entry={innerEntry}
onChange={(x) =>
onChange({ type: value.decoded.type, value: x })
}
parent={{
id: entry.id,
variantTag: value.decoded.type,
variantIdx:
entry.value[value.decoded.type as keyof typeof entry.value]
.idx,
}}
value={
value.decoded.value === NOTIN
? { empty: true }
: {
empty: false,
decoded: value.decoded.value,
encoded:
(valueProps as any).encodedValue &&
(valueProps as any).encodedValue.slice(1),
}
}
/>
),
}}
/>
)
}
if (entry.type === "result") {
return (
<CResult
{...{
...valueProps,
onValueChanged: onChange,
path,
decode,
shape: entry,
inner: value.empty ? null : (
<CodecComponent
path={path.concat(
`Result(${value.decoded.success ? "Success" : "Failure"})`,
)}
onChange={(x) =>
onChange({ success: value.decoded.success, value: x })
}
dynCodecs={dynCodecs}
entry={entry.value[value.decoded.success ? "ok" : "ko"]}
value={
value.decoded.value === NOTIN
? { empty: true }
: { empty: false, decoded: value.decoded.value }
}
/>
),
}}
/>
)
}
if (entry.type === "option") {
return (
<COption
{...{
...valueProps,
path,
shape: entry,
onValueChanged: onChange,
decode,
inner:
!value.empty && value.decoded === undefined ? (
<CVoid />
) : (
<CodecComponent
path={path.concat(
`Option(${value.empty ? "Void" : value.decoded === NOTIN ? "" : "Value"})`,
)}
dynCodecs={dynCodecs}
entry={entry.value}
onChange={onChange}
value={
value.empty || value.decoded === NOTIN
? { empty: true }
: { empty: false, decoded: value.decoded }
}
/>
),
}}
/>
)
}
if (entry.type === "void") return <CVoid />
if (entry.type === "array") {
if (entry.value.type === "primitive" && entry.value.value === "u8") {
const decoder = Bytes(entry.len)[1]
return (
<CBytes
decode={scale.createDecoder(decoder)}
onValueChanged={onChange}
len={entry.len}
path={path}
{...(valueProps as any)}
/>
)
}
const latestValue = value.empty
? Array(entry.len).fill(NOTIN)
: (value.decoded as Array<any>)
return (
<CArray
{...{
...valueProps,
path,
shape: entry,
decode,
onValueChanged: onChange,
innerComponents: latestValue.map((decoded, idx) => (
<CodecComponent
dynCodecs={dynCodecs}
entry={entry.value}
path={path.concat(idx.toString())}
onChange={(x) => {
const value = [...latestValue]
value[idx] = x
return onChange(value)
}}
value={
decoded === NOTIN
? { empty: true }
: { empty: false, decoded }
}
/>
)),
}}
/>
)
}
if (entry.type === "sequence") {
if (entry.value.type === "primitive" && entry.value.value === "u8") {
const decoder = Bytes()[1]
return (
<CBytes
{...(valueProps as any)}
path={path}
decode={createDecode(decoder)}
onValueChanged={onChange}
/>
)
}
const latestValue = value.empty ? [] : (value.decoded as Array<any>)
return (
<CSequence
{...{
...valueProps,
path,
shape: entry,
onValueChanged: onChange,
decode,
innerComponents: latestValue.map((decoded, idx) => (
<CodecComponent
path={path.concat(idx.toString())}
dynCodecs={dynCodecs}
entry={entry.value}
onChange={(x) => {
const value = [...latestValue]
value[idx] = x
return onChange(value)
}}
value={
decoded === NOTIN
? { empty: true }
: { empty: false, decoded }
}
/>
)),
}}
/>
)
}
let type: any = undefined
let ResultComponent:
| EditBigNumber
| EditNumber
| EditBool
| EditStr
| EditAccountId
| EditEthAccount = null as any
switch (entry.type) {
case "compact": {
ResultComponent = entry.isBig ? CBigNumber : CNumber
type = (entry as any).size
break
}
case "primitive": {
if (entry.value === "str" || entry.value === "char")
ResultComponent = CStr
else if (entry.value === "bool") ResultComponent = CBool
else {
const nBits = Number(entry.value.slice(1))
ResultComponent = nBits < 64 ? CNumber : CBigNumber
type = entry.value
}
break
}
case "bitSequence": {
return null
}
case "AccountId32": {
ResultComponent = CAccountId
break
}
case "AccountId20": {
ResultComponent = CEthAccount
break
}
}
const Result = ResultComponent as FC<any>
return (
<Result
{...valueProps}
path={path}
decode={decode}
onValueChanged={onChange}
numType={type}
/>
)
}
const result: FC<{
metadata: V14 | V15
codecType: number
value: CodecComponentValue
onUpdate: (newValue: CodecComponentUpdate) => void
}> = ({ metadata, codecType, value: propsValue, onUpdate }) => {
const lookupRef = useRef<{
metadata: V14 | V15
lookup: MetadataLookup
dynCodecs: ReturnType<typeof getDynamicBuilder>["buildDefinition"]
refresh: (
codecType: number,
value?: Uint8Array | HexString,
) =>
| { empty: true }
| { empty: false; decoded: any; encoded?: Uint8Array }
}>({
metadata: null as any,
lookup: null as any,
dynCodecs: null as any,
refresh: () => ({ empty: true }),
})
if (lookupRef.current.metadata !== metadata) {
const lookup = getLookupFn(metadata)
lookupRef.current = {
metadata,
lookup,
dynCodecs: getDynamicBuilder(lookup).buildDefinition,
refresh: (codecType, val) => {
try {
return val
? {
empty: false,
decoded: lookupRef.current.dynCodecs(codecType).dec(val),
encoded: typeof val === "string" ? fromHex(val) : val,
}
: { empty: true }
} catch (_error) {
console.error("invalid hex for the codec")
return { empty: true }
}
},
}
}
const value =
propsValue.type === CodecComponentType.Initial
? lookupRef.current.refresh(
codecType,
propsValue.value as CodecComponentInitial,
)
: propsValue.value
return (
<CodecComponent
path={[]}
onChange={(x) => {
if (x === NOTIN) {
onUpdate({ empty: true })
return true
}
let encoded = undefined
const hasNotin = detectNotin(x)
if (!hasNotin) {
try {
encoded = lookupRef.current.dynCodecs(codecType).enc(x)
} catch (_e) {
console.warn("Error encoding", x)
return false
}
}
onUpdate({ empty: false, decoded: x, encoded })
return true
}}
{...{
dynCodecs: lookupRef.current.dynCodecs,
entry: lookupRef.current.lookup(codecType),
value: value,
}}
/>
)
}
return result as any
}

View File

@@ -0,0 +1,2 @@
export * from "./types"
export * from "./get-codec-component"

View File

@@ -0,0 +1,144 @@
import type {
ArrayVar,
EnumVar,
OptionVar,
ResultVar,
SequenceVar,
StructVar,
TupleVar,
} from "@polkadot-api/metadata-builders"
import type {
Binary,
HexString,
SS58String,
} from "@polkadot-api/substrate-bindings"
import type { ReactNode, FC } from "react"
export const NOTIN = Symbol("Notin")
export type NOTIN = typeof NOTIN
export type EditComplexCodecComponentProps<T> = (
| { type: "blank"; value: NOTIN; encodedValue: undefined }
| { type: "partial"; value: T; encodedValue: undefined }
| { type: "complete"; value: T; encodedValue: Uint8Array }
) & {
path: string[]
onValueChanged: (newValue: T | NOTIN) => boolean
decode: (value: Uint8Array | HexString) => T | NOTIN
}
export type EditPrimitiveComponentProps<T = any> = (
| { type: "blank"; value: NOTIN; encodedValue: undefined }
| { type: "complete"; value: T; encodedValue: Uint8Array }
) & {
path: string[]
onValueChanged: (newValue: T | NOTIN) => boolean
decode: (value: Uint8Array | HexString) => T | NOTIN
}
export type EditVoidInterface = {}
export type EditVoid = FC<EditVoidInterface>
export type EditAccountIdInterface = EditPrimitiveComponentProps<SS58String>
export type EditAccountId = FC<EditAccountIdInterface>
export type EditBigNumberInterface = EditPrimitiveComponentProps<bigint> & {
numType: "u64" | "u128" | "u256" | "i64" | "i128" | "i256"
}
export type EditBigNumber = FC<EditBigNumberInterface>
export type EditNumberInterface = EditPrimitiveComponentProps<number> & {
numType: "u8" | "u16" | "u32" | "i8" | "i16" | "i32"
}
export type EditNumber = FC<EditNumberInterface>
export type EditBoolInterface = EditPrimitiveComponentProps<boolean>
export type EditBool = FC<EditBoolInterface>
export type EditStrInterface = EditPrimitiveComponentProps<string>
export type EditStr = FC<EditStrInterface>
export type EditBytesInterface = EditPrimitiveComponentProps<Binary> & {
len?: number
}
export type EditBytes = FC<EditBytesInterface>
export type EditEthAccountInterface = EditPrimitiveComponentProps<HexString>
export type EditEthAccount = FC<EditEthAccountInterface>
export type EditEnumInterface = EditComplexCodecComponentProps<{
type: string
value: any
}> & {
tags: Array<{ idx: number; tag: string }>
inner: ReactNode
shape: EnumVar
}
export type EditEnum = FC<EditEnumInterface>
export type EditSequenceInterface<T = any> = EditComplexCodecComponentProps<
Array<T>
> & {
innerComponents: Array<ReactNode>
shape: SequenceVar
}
export type EditSequence = FC<EditSequenceInterface>
export type EditArrayInterface<T = any> = EditComplexCodecComponentProps<
Array<T>
> & {
innerComponents: Array<ReactNode>
shape: ArrayVar
}
export type EditArray = FC<EditArrayInterface>
export type EditTupleInterface<T = any> = EditComplexCodecComponentProps<
Array<T>
> & {
innerComponents: Array<ReactNode>
shape: TupleVar
}
export type EditTuple = FC<EditTupleInterface>
export type EditStructInterface = EditComplexCodecComponentProps<
Record<string, any>
> & {
innerComponents: Record<string, ReactNode>
shape: StructVar
}
export type EditStruct = FC<EditStructInterface>
export type EditOptionInterface<T = any> = EditComplexCodecComponentProps<
T | undefined
> & {
inner: ReactNode
shape: OptionVar
}
export type EditOption = FC<EditOptionInterface>
export type EditResultInterface = EditComplexCodecComponentProps<{
success: boolean
value: any
}> & {
shape: ResultVar
inner: ReactNode
}
export type EditResult = FC<EditResultInterface>
export interface EditComponents {
CVoid: EditVoid
CAccountId: EditAccountId
CBigNumber: EditBigNumber
CNumber: EditNumber
CBool: EditBool
CStr: EditStr
CBytes: EditBytes
CEthAccount: EditEthAccount
CEnum: EditEnum
CSequence: EditSequence
CArray: EditArray
CTuple: EditTuple
CStruct: EditStruct
COption: EditOption
CResult: EditResult
}

View File

@@ -0,0 +1,2 @@
export * from "./edit-component-types"
export * from "./view-component-types"

View File

@@ -0,0 +1,126 @@
import type {
ArrayVar,
EnumVar,
OptionVar,
ResultVar,
SequenceVar,
StructVar,
TupleVar,
} from "@polkadot-api/metadata-builders"
import type {
Binary,
HexString,
SS58String,
} from "@polkadot-api/substrate-bindings"
import type { ReactNode, FC } from "react"
export type ViewCodecComponentProps<T = any> = {
value: T
encodedValue: Uint8Array
path: string[]
}
export type ViewPrimitiveComponentProps<T = any> = ViewCodecComponentProps<T>
export type ViewVoidInterface = {}
export type ViewVoid = FC<ViewVoidInterface>
export type ViewAccountIdInterface = ViewPrimitiveComponentProps<SS58String>
export type ViewAccountId = FC<ViewAccountIdInterface>
export type ViewBigNumberInterface = ViewPrimitiveComponentProps<bigint> & {
numType: "u64" | "u128" | "u256" | "i64" | "i128" | "i256"
}
export type ViewBigNumber = FC<ViewBigNumberInterface>
export type ViewNumberInterface = ViewPrimitiveComponentProps<number> & {
numType: "u8" | "u16" | "u32" | "i8" | "i16" | "i32"
}
export type ViewNumber = FC<ViewNumberInterface>
export type ViewBoolInterface = ViewPrimitiveComponentProps<boolean>
export type ViewBool = FC<ViewBoolInterface>
export type ViewStrInterface = ViewPrimitiveComponentProps<string>
export type ViewStr = FC<ViewStrInterface>
export type ViewBytesInterface = ViewPrimitiveComponentProps<Binary> & {
len?: number
}
export type ViewBytes = FC<ViewBytesInterface>
export type ViewEthAccountInterface = ViewPrimitiveComponentProps<HexString>
export type ViewEthAccount = FC<ViewEthAccountInterface>
export type ViewEnumInterface = ViewCodecComponentProps<{
type: string
value: any
}> & {
tags: Array<{ idx: number; tag: string }>
inner: ReactNode
shape: EnumVar
}
export type ViewEnum = FC<ViewEnumInterface>
export type ViewSequenceInterface<T = any> = ViewCodecComponentProps<
Array<T>
> & {
innerComponents: Array<ReactNode>
shape: SequenceVar
}
export type ViewSequence = FC<ViewSequenceInterface>
export type ViewArrayInterface<T = any> = ViewCodecComponentProps<Array<T>> & {
innerComponents: Array<ReactNode>
shape: ArrayVar
}
export type ViewArray = FC<ViewArrayInterface>
export type ViewTupleInterface<T = any> = ViewCodecComponentProps<Array<T>> & {
innerComponents: Array<ReactNode>
shape: TupleVar
}
export type ViewTuple = FC<ViewTupleInterface>
export type ViewStructInterface = ViewCodecComponentProps<
Record<string, any>
> & {
innerComponents: Record<string, ReactNode>
shape: StructVar
}
export type ViewStruct = FC<ViewStructInterface>
export type ViewOptionInterface<T = any> = ViewCodecComponentProps<
T | undefined
> & {
inner: ReactNode
shape: OptionVar
}
export type ViewOption = FC<ViewOptionInterface>
export type ViewResultInterface = ViewCodecComponentProps<{
success: boolean
value: any
}> & {
shape: ResultVar
inner: ReactNode
}
export type ViewResult = FC<ViewResultInterface>
export interface ViewComponents {
CVoid: ViewVoid
CAccountId: ViewAccountId
CBigNumber: ViewBigNumber
CNumber: ViewNumber
CBool: ViewBool
CStr: ViewStr
CBytes: ViewBytes
CEthAccount: ViewEthAccount
CEnum: ViewEnum
CSequence: ViewSequence
CArray: ViewArray
CTuple: ViewTuple
CStruct: ViewStruct
COption: ViewOption
CResult: ViewResult
}

View File

@@ -7,23 +7,25 @@ export default {
extend: {
colors: {
polkadot: {
0: "#FFFFFF",
100: "#E1DDFF",
200: "#D6BBFF",
300: "#DF98FF",
400: "#FC76FF",
500: "#E6007A",
950: "#030110",
900: "#0C052C",
800: "#23126D",
700: "#4027AE",
650: "#6543EF",
600: "#5E39F2",
700: "#472FA7",
800: "#0F1750",
900: "#01050C",
500: "#E6007A",
400: "#FC76FF",
300: "#DB9EFF",
200: "#E4DAFF",
100: "#F2F3FF",
0: "#FBFCFF",
},
},
},
},
plugins: [
function ({ addVariant }) {
addVariant("group-state-open", ':merge(.group)[data-state="open"] &')
addVariant("group-state-open", ':merge(.group)[data-state="open"] &');
},
],
}
};

View File

@@ -18,7 +18,12 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"paths": {
"@/*": ["./src/*"],
"@codec-components": ["./src/lib/codecComponents"]
}
},
"include": ["src"]
}

View File

@@ -1,7 +1,14 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { defineConfig } from "vite"
import react from "@vitejs/plugin-react"
import path from "node:path"
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
"@codec-components": path.resolve(__dirname, "./src/lib/codecComponents"),
},
},
plugins: [react()],
})