feat: set storage through chopsticks
This commit is contained in:
@@ -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<Blockchain | null>(null)
|
||||
export const isChopsticks$ = state(
|
||||
chopsticksInstance$.pipe(map((v) => !!v)),
|
||||
false,
|
||||
)
|
||||
|
||||
export const createChopsticksProvider = (endpoint: string) =>
|
||||
withChopsticksEnhancer(
|
||||
|
||||
@@ -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<HTMLDivElement | null>(null)
|
||||
const listRef = useRef<HTMLDivElement | null>(null)
|
||||
const [focusingSubtree, setFocusingSubtree] = useState<string[] | null>(null)
|
||||
@@ -50,7 +52,12 @@ export const LookupTypeEdit: FC<{
|
||||
if (!codecProps) return null
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-start overflow-hidden">
|
||||
<div
|
||||
className={twMerge(
|
||||
"flex flex-col items-start overflow-hidden",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<BinaryDisplay {...codecProps} className="pb-2" />
|
||||
{tree && (
|
||||
<FocusPath
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { chopsticksInstance$ } from "@/chopsticks/chopsticks"
|
||||
import { chopsticksInstance$, isChopsticks$ } from "@/chopsticks/chopsticks"
|
||||
import { Chopsticks } from "@/components/Icons"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { client$, runtimeCtx$ } from "@/state/chains/chain.state"
|
||||
import { state, useStateObservable, withDefault } from "@react-rxjs/core"
|
||||
import { useStateObservable, withDefault } from "@react-rxjs/core"
|
||||
import { FC, PropsWithChildren, ReactElement, useEffect, useState } from "react"
|
||||
import { firstValueFrom, map, switchMap } from "rxjs"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
@@ -31,10 +31,9 @@ const hasEpoch$ = runtimeCtx$.pipeState(
|
||||
withDefault(false),
|
||||
)
|
||||
|
||||
const canJump$ = state(chopsticksInstance$.pipe(map((v) => !!v)), false)
|
||||
export const Summary: FC = () => {
|
||||
const hasEpoch = useStateObservable(hasEpoch$)
|
||||
const canJump = useStateObservable(canJump$)
|
||||
const canJump = useStateObservable(isChopsticks$)
|
||||
|
||||
return (
|
||||
<div className="flex gap-4 items-center py-2">
|
||||
|
||||
@@ -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
|
||||
<Chopsticks
|
||||
className="inline-block align-middle ml-2"
|
||||
size={20}
|
||||
/>
|
||||
</>
|
||||
),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
{mode === "query" ? <StorageQuery /> : <StorageDecode />}
|
||||
{mode === "query" ? (
|
||||
<StorageQuery />
|
||||
) : mode === "decode" ? (
|
||||
<StorageDecode />
|
||||
) : (
|
||||
<StorageSet />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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 = () => {
|
||||
<ol className="flex flex-col gap-2">
|
||||
{keys.map((type, idx) => (
|
||||
<li key={idx} className="flex flex-row gap-2 items-center">
|
||||
<SliderToggle
|
||||
isToggled={keysEnabled > idx}
|
||||
toggle={() => toggleKey(idx)}
|
||||
/>
|
||||
{disableToggle ? null : (
|
||||
<SliderToggle
|
||||
isToggled={keysEnabled > idx}
|
||||
toggle={() => toggleKey(idx)}
|
||||
/>
|
||||
)}
|
||||
<StorageKeyInput
|
||||
idx={idx}
|
||||
type={type}
|
||||
@@ -271,7 +275,7 @@ const StorageKeyInput: FC<{ idx: number; type: number; disabled: boolean }> = ({
|
||||
)
|
||||
}
|
||||
|
||||
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$)
|
||||
|
||||
@@ -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<Uint8Array | "partial" | null>(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 (
|
||||
<div className="flex flex-col gap-4 items-start w-full overflow-hidden">
|
||||
<KeyDisplay />
|
||||
<StorageKeysInput disableToggle />
|
||||
<LookupTypeEdit
|
||||
className="w-full border rounded pt-2"
|
||||
type={selectedEntry.value}
|
||||
value={value}
|
||||
onValueChange={setValue}
|
||||
tree={complexity === "tree"}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
disabled={isLoading || !encodedKey || !(value instanceof Uint8Array)}
|
||||
onClick={async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const chopsticks = await firstValueFrom(chopsticksInstance$)
|
||||
if (!chopsticks || !encodedKey || !(value instanceof Uint8Array))
|
||||
return false
|
||||
|
||||
await setStorage(chopsticks, [[encodedKey, toHex(value)]])
|
||||
await chopsticks.newBlock()
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
}}
|
||||
>
|
||||
Set Storage
|
||||
<Chopsticks />
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user