diff --git a/eslint.config.js b/eslint.config.js
index cd72aa3..419eaaa 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -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(
},
],
},
- }
-);
+ },
+)
diff --git a/index.html b/index.html
index 7c693df..3f2f624 100644
--- a/index.html
+++ b/index.html
@@ -6,7 +6,7 @@
PAPI Console
-
+
diff --git a/package.json b/package.json
index 0cb95b6..eace897 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c7ec62b..5d2e580 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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
diff --git a/src/App.tsx b/src/App.tsx
index 6af5c93..e9054fb 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,3 +1,3 @@
export default function App() {
- return App
;
+ return App
}
diff --git a/src/lib/codecComponents/get-codec-component.tsx b/src/lib/codecComponents/get-codec-component.tsx
new file mode 100644
index 0000000..82fe524
--- /dev/null
+++ b/src/lib/codecComponents/get-codec-component.tsx
@@ -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: (x: T) => boolean = (x) => {
+ if (x === NOTIN) return true
+ return x != null && typeof x === "object"
+ ? Object.values(x).some(detectNotin)
+ : false
+}
+
+const withNotin: (
+ encoder: Encoder,
+) => (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["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 (
+ (
+
+ ))}
+ />
+ )
+ }
+
+ if (entry.type === "tuple") {
+ return (
+ (
+
+ )),
+ }}
+ />
+ )
+ }
+
+ 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 (
+ ({
+ tag,
+ idx,
+ })),
+ shape: entry,
+ inner: value.empty ? null : (
+
+ ),
+ }}
+ />
+ )
+ }
+
+ if (entry.type === "result") {
+ return (
+
+ ),
+ }}
+ />
+ )
+ }
+
+ if (entry.type === "option") {
+ return (
+
+ ) : (
+
+ ),
+ }}
+ />
+ )
+ }
+
+ if (entry.type === "void") return
+
+ if (entry.type === "array") {
+ if (entry.value.type === "primitive" && entry.value.value === "u8") {
+ return
+ }
+
+ const latestValue = value.empty
+ ? Array(entry.len).fill(NOTIN)
+ : (value.decoded as Array)
+ return (
+ (
+
+ )),
+ }}
+ />
+ )
+ }
+
+ if (entry.type === "sequence") {
+ if (entry.value.type === "primitive" && entry.value.value === "u8") {
+ return
+ }
+
+ const latestValue = value.empty ? [] : (value.decoded as Array)
+ return (
+ (
+
+ )),
+ }}
+ />
+ )
+ }
+
+ 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
+ return
+ }
+
+ 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["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 (
+
+ )
+ }
+
+ 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["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) =>
+ 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 (
+ (
+ {
+ 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 (
+ (
+ {
+ 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 (
+ ({
+ tag,
+ idx,
+ })),
+ shape: entry,
+ inner: value.empty ? null : (
+
+ 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 (
+
+ 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 (
+
+ ) : (
+
+ ),
+ }}
+ />
+ )
+ }
+
+ if (entry.type === "void") return
+
+ if (entry.type === "array") {
+ if (entry.value.type === "primitive" && entry.value.value === "u8") {
+ const decoder = Bytes(entry.len)[1]
+ return (
+
+ )
+ }
+
+ const latestValue = value.empty
+ ? Array(entry.len).fill(NOTIN)
+ : (value.decoded as Array)
+ return (
+ (
+ {
+ 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 (
+
+ )
+ }
+
+ const latestValue = value.empty ? [] : (value.decoded as Array)
+ return (
+ (
+ {
+ 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
+ return (
+
+ )
+ }
+
+ 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["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 (
+ {
+ 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
+}
diff --git a/src/lib/codecComponents/index.ts b/src/lib/codecComponents/index.ts
new file mode 100644
index 0000000..fa9a7d1
--- /dev/null
+++ b/src/lib/codecComponents/index.ts
@@ -0,0 +1,2 @@
+export * from "./types"
+export * from "./get-codec-component"
diff --git a/src/lib/codecComponents/types/edit-component-types.ts b/src/lib/codecComponents/types/edit-component-types.ts
new file mode 100644
index 0000000..4449abe
--- /dev/null
+++ b/src/lib/codecComponents/types/edit-component-types.ts
@@ -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 = (
+ | { 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 = (
+ | { 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
+
+export type EditAccountIdInterface = EditPrimitiveComponentProps
+export type EditAccountId = FC
+
+export type EditBigNumberInterface = EditPrimitiveComponentProps & {
+ numType: "u64" | "u128" | "u256" | "i64" | "i128" | "i256"
+}
+export type EditBigNumber = FC
+
+export type EditNumberInterface = EditPrimitiveComponentProps & {
+ numType: "u8" | "u16" | "u32" | "i8" | "i16" | "i32"
+}
+export type EditNumber = FC
+
+export type EditBoolInterface = EditPrimitiveComponentProps
+export type EditBool = FC
+
+export type EditStrInterface = EditPrimitiveComponentProps
+export type EditStr = FC
+
+export type EditBytesInterface = EditPrimitiveComponentProps & {
+ len?: number
+}
+export type EditBytes = FC
+
+export type EditEthAccountInterface = EditPrimitiveComponentProps
+export type EditEthAccount = FC
+
+export type EditEnumInterface = EditComplexCodecComponentProps<{
+ type: string
+ value: any
+}> & {
+ tags: Array<{ idx: number; tag: string }>
+ inner: ReactNode
+ shape: EnumVar
+}
+export type EditEnum = FC
+
+export type EditSequenceInterface = EditComplexCodecComponentProps<
+ Array
+> & {
+ innerComponents: Array
+ shape: SequenceVar
+}
+export type EditSequence = FC
+
+export type EditArrayInterface = EditComplexCodecComponentProps<
+ Array
+> & {
+ innerComponents: Array
+ shape: ArrayVar
+}
+export type EditArray = FC
+
+export type EditTupleInterface = EditComplexCodecComponentProps<
+ Array
+> & {
+ innerComponents: Array
+ shape: TupleVar
+}
+export type EditTuple = FC
+
+export type EditStructInterface = EditComplexCodecComponentProps<
+ Record
+> & {
+ innerComponents: Record
+ shape: StructVar
+}
+export type EditStruct = FC
+
+export type EditOptionInterface = EditComplexCodecComponentProps<
+ T | undefined
+> & {
+ inner: ReactNode
+ shape: OptionVar
+}
+export type EditOption = FC
+
+export type EditResultInterface = EditComplexCodecComponentProps<{
+ success: boolean
+ value: any
+}> & {
+ shape: ResultVar
+ inner: ReactNode
+}
+export type EditResult = FC
+
+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
+}
diff --git a/src/lib/codecComponents/types/index.ts b/src/lib/codecComponents/types/index.ts
new file mode 100644
index 0000000..e0e1c6d
--- /dev/null
+++ b/src/lib/codecComponents/types/index.ts
@@ -0,0 +1,2 @@
+export * from "./edit-component-types"
+export * from "./view-component-types"
diff --git a/src/lib/codecComponents/types/view-component-types.ts b/src/lib/codecComponents/types/view-component-types.ts
new file mode 100644
index 0000000..545c243
--- /dev/null
+++ b/src/lib/codecComponents/types/view-component-types.ts
@@ -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 = {
+ value: T
+ encodedValue: Uint8Array
+ path: string[]
+}
+
+export type ViewPrimitiveComponentProps = ViewCodecComponentProps
+
+export type ViewVoidInterface = {}
+export type ViewVoid = FC
+
+export type ViewAccountIdInterface = ViewPrimitiveComponentProps
+export type ViewAccountId = FC
+
+export type ViewBigNumberInterface = ViewPrimitiveComponentProps & {
+ numType: "u64" | "u128" | "u256" | "i64" | "i128" | "i256"
+}
+export type ViewBigNumber = FC
+
+export type ViewNumberInterface = ViewPrimitiveComponentProps & {
+ numType: "u8" | "u16" | "u32" | "i8" | "i16" | "i32"
+}
+export type ViewNumber = FC
+
+export type ViewBoolInterface = ViewPrimitiveComponentProps
+export type ViewBool = FC
+
+export type ViewStrInterface = ViewPrimitiveComponentProps
+export type ViewStr = FC
+
+export type ViewBytesInterface = ViewPrimitiveComponentProps & {
+ len?: number
+}
+export type ViewBytes = FC
+
+export type ViewEthAccountInterface = ViewPrimitiveComponentProps
+export type ViewEthAccount = FC
+
+export type ViewEnumInterface = ViewCodecComponentProps<{
+ type: string
+ value: any
+}> & {
+ tags: Array<{ idx: number; tag: string }>
+ inner: ReactNode
+ shape: EnumVar
+}
+export type ViewEnum = FC
+
+export type ViewSequenceInterface = ViewCodecComponentProps<
+ Array
+> & {
+ innerComponents: Array
+ shape: SequenceVar
+}
+export type ViewSequence = FC
+
+export type ViewArrayInterface = ViewCodecComponentProps> & {
+ innerComponents: Array
+ shape: ArrayVar
+}
+export type ViewArray = FC
+
+export type ViewTupleInterface = ViewCodecComponentProps> & {
+ innerComponents: Array
+ shape: TupleVar
+}
+export type ViewTuple = FC
+
+export type ViewStructInterface = ViewCodecComponentProps<
+ Record
+> & {
+ innerComponents: Record
+ shape: StructVar
+}
+export type ViewStruct = FC
+
+export type ViewOptionInterface = ViewCodecComponentProps<
+ T | undefined
+> & {
+ inner: ReactNode
+ shape: OptionVar
+}
+export type ViewOption = FC
+
+export type ViewResultInterface = ViewCodecComponentProps<{
+ success: boolean
+ value: any
+}> & {
+ shape: ResultVar
+ inner: ReactNode
+}
+export type ViewResult = FC
+
+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
+}
diff --git a/tailwind.config.js b/tailwind.config.js
index 8f62dd0..d4771a9 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -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"] &');
},
],
-}
+};
diff --git a/tsconfig.app.json b/tsconfig.app.json
index f0a2350..7610c89 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -18,7 +18,12 @@
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
- "noFallthroughCasesInSwitch": true
+ "noFallthroughCasesInSwitch": true,
+
+ "paths": {
+ "@/*": ["./src/*"],
+ "@codec-components": ["./src/lib/codecComponents"]
+ }
},
"include": ["src"]
}
diff --git a/vite.config.ts b/vite.config.ts
index 5a33944..0d1ff40 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -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()],
})