From f2ee28e154fd7f0fcd17db3e30b418f9c96ebc7b Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Tue, 22 Oct 2024 15:31:20 +0200 Subject: [PATCH] bring theme and codec-components library --- eslint.config.js | 15 +- index.html | 2 +- package.json | 4 + pnpm-lock.yaml | 20 +- src/App.tsx | 2 +- .../codecComponents/get-codec-component.tsx | 961 ++++++++++++++++++ src/lib/codecComponents/index.ts | 2 + .../types/edit-component-types.ts | 144 +++ src/lib/codecComponents/types/index.ts | 2 + .../types/view-component-types.ts | 126 +++ tailwind.config.js | 24 +- tsconfig.app.json | 7 +- vite.config.ts | 11 +- 13 files changed, 1294 insertions(+), 26 deletions(-) create mode 100644 src/lib/codecComponents/get-codec-component.tsx create mode 100644 src/lib/codecComponents/index.ts create mode 100644 src/lib/codecComponents/types/edit-component-types.ts create mode 100644 src/lib/codecComponents/types/index.ts create mode 100644 src/lib/codecComponents/types/view-component-types.ts 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()], })