From 6813faba18150ce751f6795d71b48f8b0859dfca Mon Sep 17 00:00:00 2001 From: Victor Oliva Date: Sat, 17 May 2025 13:41:14 +0200 Subject: [PATCH] feat: set storage through chopsticks --- src/chopsticks/chopsticks.ts | 7 ++- .../LookupTypeEdit/LookupTypeEdit.tsx | 11 +++- src/pages/Explorer/Summary.tsx | 7 +-- src/pages/Storage/Storage.tsx | 38 ++++++++++-- src/pages/Storage/StorageQuery.tsx | 19 +++--- src/pages/Storage/StorageSet.tsx | 60 +++++++++++++++++++ 6 files changed, 122 insertions(+), 20 deletions(-) create mode 100644 src/pages/Storage/StorageSet.tsx diff --git a/src/chopsticks/chopsticks.ts b/src/chopsticks/chopsticks.ts index ae1ac4a..734448c 100644 --- a/src/chopsticks/chopsticks.ts +++ b/src/chopsticks/chopsticks.ts @@ -1,10 +1,15 @@ import { Blockchain } from "@acala-network/chopsticks-core" import { getSyncProvider } from "@polkadot-api/json-rpc-provider-proxy" -import { BehaviorSubject } from "rxjs" import { blockHeader } from "@polkadot-api/substrate-bindings" +import { state } from "@react-rxjs/core" import { JsonRpcProvider } from "polkadot-api/ws-provider/web" +import { BehaviorSubject, map } from "rxjs" export const chopsticksInstance$ = new BehaviorSubject(null) +export const isChopsticks$ = state( + chopsticksInstance$.pipe(map((v) => !!v)), + false, +) export const createChopsticksProvider = (endpoint: string) => withChopsticksEnhancer( diff --git a/src/codec-components/LookupTypeEdit/LookupTypeEdit.tsx b/src/codec-components/LookupTypeEdit/LookupTypeEdit.tsx index 67eea06..49e8463 100644 --- a/src/codec-components/LookupTypeEdit/LookupTypeEdit.tsx +++ b/src/codec-components/LookupTypeEdit/LookupTypeEdit.tsx @@ -19,6 +19,7 @@ import { EditCodec } from "../EditCodec" import { TreeCodec } from "../EditCodec/Tree" import { BinaryDisplay } from "./BinaryDisplay" import { FocusPath } from "./FocusPath" +import { twMerge } from "tailwind-merge" const editTypeMetadataProps$ = state( runtimeCtx$.pipe( @@ -35,7 +36,8 @@ export const LookupTypeEdit: FC<{ value: Uint8Array | "partial" | null onValueChange: (value: Uint8Array | "partial" | null) => void tree?: boolean -}> = ({ type, value, onValueChange, tree = true }) => { + className?: string +}> = ({ type, value, onValueChange, tree = true, className }) => { const treeRef = useRef(null) const listRef = useRef(null) const [focusingSubtree, setFocusingSubtree] = useState(null) @@ -50,7 +52,12 @@ export const LookupTypeEdit: FC<{ if (!codecProps) return null return ( -
+
{tree && ( !!v)), false) export const Summary: FC = () => { const hasEpoch = useStateObservable(hasEpoch$) - const canJump = useStateObservable(canJump$) + const canJump = useStateObservable(isChopsticks$) return (
diff --git a/src/pages/Storage/Storage.tsx b/src/pages/Storage/Storage.tsx index cd376f1..82948ba 100644 --- a/src/pages/Storage/Storage.tsx +++ b/src/pages/Storage/Storage.tsx @@ -1,17 +1,20 @@ -import { lookup$ } from "@/state/chains/chain.state" +import { isChopsticks$ } from "@/chopsticks/chopsticks" import { ButtonGroup } from "@/components/ButtonGroup" +import { DocsRenderer } from "@/components/DocsRenderer" +import { Chopsticks } from "@/components/Icons" +import { LoadingMetadata } from "@/components/Loading" import { SearchableSelect } from "@/components/Select" import { withSubscribe } from "@/components/withSuspense" +import { useHashState } from "@/lib/externalState" +import { lookup$ } from "@/state/chains/chain.state" import { state, useStateObservable } from "@react-rxjs/core" import { FC, useEffect, useState } from "react" import { map } from "rxjs" import { selectedEntry$, setSelectedEntry } from "./storage.state" import { StorageDecode } from "./StorageDecode" import { StorageQuery } from "./StorageQuery" +import { StorageSet } from "./StorageSet" import { StorageSubscriptions } from "./StorageSubscriptions" -import { DocsRenderer } from "@/components/DocsRenderer" -import { LoadingMetadata } from "@/components/Loading" -import { useHashState } from "@/lib/externalState" const metadataStorage$ = state( lookup$.pipe( @@ -150,7 +153,8 @@ export const Storage = withSubscribe( const StorageEntry: FC = () => { const selectedEntry = useStateObservable(selectedEntry$) - const [mode, setMode] = useState<"query" | "decode">("query") + const canSetStorage = useStateObservable(isChopsticks$) || true + const [mode, setMode] = useState<"query" | "decode" | "set">("query") if (!selectedEntry) return null @@ -168,9 +172,31 @@ const StorageEntry: FC = () => { value: "decode", content: "Decode", }, + ...(canSetStorage + ? [ + { + value: "set", + content: ( + <> + Set + + + ), + }, + ] + : []), ]} /> - {mode === "query" ? : } + {mode === "query" ? ( + + ) : mode === "decode" ? ( + + ) : ( + + )} ) } diff --git a/src/pages/Storage/StorageQuery.tsx b/src/pages/Storage/StorageQuery.tsx index 68119d3..0e618b8 100644 --- a/src/pages/Storage/StorageQuery.tsx +++ b/src/pages/Storage/StorageQuery.tsx @@ -129,7 +129,9 @@ const isReady$ = state( false, ) -const StorageKeysInput: FC = () => { +export const StorageKeysInput: FC<{ + disableToggle?: boolean +}> = ({ disableToggle }) => { const keys = useStateObservable(keys$) const keysEnabled = useStateObservable(keysEnabled$) @@ -137,10 +139,12 @@ const StorageKeysInput: FC = () => {
    {keys.map((type, idx) => (
  1. - idx} - toggle={() => toggleKey(idx)} - /> + {disableToggle ? null : ( + idx} + toggle={() => toggleKey(idx)} + /> + )} = ({ ) } -const encodedKey$ = state( +export const encodedKey$ = state( combineLatest([ dynamicBuilder$, selectedEntry$, @@ -301,7 +305,8 @@ const encodedKey$ = state( ), null, ) -const KeyDisplay: FC = () => { + +export const KeyDisplay: FC = () => { const key = useStateObservable(encodedKey$) const builder = useStateObservable(builderState$) const selectedEntry = useStateObservable(selectedEntry$) diff --git a/src/pages/Storage/StorageSet.tsx b/src/pages/Storage/StorageSet.tsx new file mode 100644 index 0000000..e5c9ce7 --- /dev/null +++ b/src/pages/Storage/StorageSet.tsx @@ -0,0 +1,60 @@ +import { chopsticksInstance$ } from "@/chopsticks/chopsticks" +import { LookupTypeEdit } from "@/codec-components/LookupTypeEdit" +import { Chopsticks } from "@/components/Icons" +import { Button } from "@/components/ui/button" +import { lookup$ } from "@/state/chains/chain.state" +import { getTypeComplexity } from "@/utils" +import { setStorage } from "@acala-network/chopsticks-core" +import { toHex } from "@polkadot-api/utils" +import { useStateObservable } from "@react-rxjs/core" +import { FC, useState } from "react" +import { firstValueFrom } from "rxjs" +import { selectedEntry$ } from "./storage.state" +import { encodedKey$, KeyDisplay, StorageKeysInput } from "./StorageQuery" + +export const StorageSet: FC = () => { + const selectedEntry = useStateObservable(selectedEntry$) + const lookup = useStateObservable(lookup$) + const [value, setValue] = useState(null) + const encodedKey = useStateObservable(encodedKey$) + const [isLoading, setIsLoading] = useState(false) + + if (!lookup || !selectedEntry) return null + + const shape = lookup(selectedEntry.value) + const complexity = getTypeComplexity(shape) + + return ( +
    + + + + +
    + ) +}