feat: redesign storage queries (#146)
This commit is contained in:
@@ -112,7 +112,7 @@ export const AppShell: FC<PropsWithChildren> = ({ children }) => {
|
||||
<div className="flex min-w-0 flex-1 flex-col">
|
||||
<TopBar onOpenSidebar={() => setSidebarOpen(true)} />
|
||||
<div className="relative min-h-0 flex-1 overflow-auto">
|
||||
<div className="mx-auto h-full w-full max-w-(--breakpoint-xl)">
|
||||
<div className="mx-auto h-full w-full max-w-(--breakpoint-xl) @container">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -83,7 +83,7 @@ export const BlockPicker = () => {
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Input
|
||||
className="bg-input"
|
||||
className="bg-input max-w-52"
|
||||
placeholder="Block hash or number"
|
||||
value={value}
|
||||
onChange={(evt) => setValue(evt.target.value)}
|
||||
|
||||
@@ -1,95 +1,26 @@
|
||||
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 { createState } from "@/lib/externalState"
|
||||
import { canSetStorage$ } from "@/state/chains/chain.state"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import { useStateObservable } from "@react-rxjs/core"
|
||||
import { FC } from "react"
|
||||
import { Route, Routes } from "react-router-dom"
|
||||
import { map } from "rxjs"
|
||||
import { BlockPicker, selectedBlock$ } from "./BlockPicker"
|
||||
import { partialEntry$, selectedEntry$, selectEntry } from "./storage.state"
|
||||
import { StorageDecode } from "./StorageDecode"
|
||||
import { StorageQuery } from "./StorageQuery"
|
||||
import { StorageSet } from "./StorageSet"
|
||||
import { StorageSubscriptions } from "./StorageSubscriptions"
|
||||
|
||||
const metadataStorage$ = state(
|
||||
selectedBlock$.pipe(
|
||||
map(({ ctx }) => ({
|
||||
lookup: ctx.lookup,
|
||||
entries: Object.fromEntries(
|
||||
ctx.lookup.metadata.pallets
|
||||
.filter((p) => p.storage)
|
||||
.map((p) => [
|
||||
p.name,
|
||||
Object.fromEntries(
|
||||
p.storage!.items.map((item) => [item.name, item.type]),
|
||||
),
|
||||
]),
|
||||
),
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
export const Storage = withSubscribe(
|
||||
() => {
|
||||
const { entries } = useStateObservable(metadataStorage$)
|
||||
const partialEntry = useStateObservable(partialEntry$)
|
||||
const selectedEntry = useStateObservable(selectedEntry$)
|
||||
|
||||
return (
|
||||
<div className="p-4 pb-0 flex flex-col gap-2 items-start">
|
||||
<div className="flex items-center gap-1 flex-wrap">
|
||||
<label>
|
||||
Block
|
||||
<BlockPicker />
|
||||
</label>
|
||||
<div className="flex items-center gap-2 flex-wrap">
|
||||
<label>
|
||||
Pallet
|
||||
<SearchableSelect
|
||||
value={partialEntry.pallet}
|
||||
setValue={(v) => selectEntry({ pallet: v })}
|
||||
options={Object.keys(entries).map((e) => ({
|
||||
text: e,
|
||||
value: e,
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
{partialEntry.pallet && entries[partialEntry.pallet] && (
|
||||
<label>
|
||||
Entry
|
||||
<SearchableSelect
|
||||
value={partialEntry.entry}
|
||||
setValue={(v) => selectEntry({ entry: v })}
|
||||
options={
|
||||
Object.keys(entries[partialEntry.pallet]).map((s) => ({
|
||||
text: s,
|
||||
value: s,
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{selectedEntry?.docs.length ? (
|
||||
<div className="w-full">
|
||||
Docs
|
||||
<DocsRenderer docs={selectedEntry.docs} />
|
||||
</div>
|
||||
) : null}
|
||||
<StorageEntry />
|
||||
<Routes>
|
||||
<Route path=":subId" element={<StorageSubscriptions />} />
|
||||
</Routes>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
() => (
|
||||
<div className="p-4 pb-0 flex flex-col gap-2 items-start">
|
||||
<StorageEntry />
|
||||
<Routes>
|
||||
<Route path=":subId" element={<StorageSubscriptions />} />
|
||||
</Routes>
|
||||
</div>
|
||||
),
|
||||
{
|
||||
fallback: <LoadingMetadata />,
|
||||
},
|
||||
@@ -98,21 +29,18 @@ export const Storage = withSubscribe(
|
||||
export const [mode$, setMode] = createState<"query" | "decode" | "set">("query")
|
||||
|
||||
const StorageEntry: FC = () => {
|
||||
const selectedEntry = useStateObservable(selectedEntry$)
|
||||
const canSetStorage = useStateObservable(canSetStorage$)
|
||||
const mode = useStateObservable(mode$)
|
||||
|
||||
if (!selectedEntry) return null
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
<ButtonGroup
|
||||
value={mode}
|
||||
onValueChange={setMode as any}
|
||||
items={[
|
||||
{
|
||||
value: "query",
|
||||
content: "Query",
|
||||
content: "Storage Query",
|
||||
},
|
||||
{
|
||||
value: "decode",
|
||||
@@ -124,7 +52,7 @@ const StorageEntry: FC = () => {
|
||||
value: "set",
|
||||
content: (
|
||||
<>
|
||||
Set
|
||||
Set Value
|
||||
<Chopsticks
|
||||
className="inline-block align-middle ml-2"
|
||||
size={20}
|
||||
@@ -136,13 +64,15 @@ const StorageEntry: FC = () => {
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
{mode === "query" ? (
|
||||
<StorageQuery />
|
||||
) : mode === "decode" ? (
|
||||
<StorageDecode />
|
||||
) : (
|
||||
<StorageSet />
|
||||
)}
|
||||
</>
|
||||
<div className="border border-accent p-3 w-full">
|
||||
{mode === "query" ? (
|
||||
<StorageQuery />
|
||||
) : mode === "decode" ? (
|
||||
<StorageDecode />
|
||||
) : (
|
||||
<StorageSet />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import { FC } from "react"
|
||||
import { combineLatest, firstValueFrom, map } from "rxjs"
|
||||
import { selectedBlock$ } from "./BlockPicker"
|
||||
import { addStorageSubscription, selectedEntry$ } from "./storage.state"
|
||||
import { StorageEntryPicker } from "./StorageEntryPicker"
|
||||
import { Textarea } from "@/components/ui/textarea"
|
||||
|
||||
export const [value$, setValue] = createState("")
|
||||
|
||||
@@ -52,13 +54,17 @@ export const StorageDecode: FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
className="w-full bg-polkadot-100 p-2 rounded tabular-nums text-polkadot-800"
|
||||
placeholder="Enter hex …"
|
||||
/>
|
||||
<div className="w-full space-y-2">
|
||||
<StorageEntryPicker />
|
||||
<label className="block">
|
||||
Data
|
||||
<Textarea
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
className="w-full tabular-nums"
|
||||
placeholder="Enter hex …"
|
||||
/>
|
||||
</label>
|
||||
<ActionButton disabled={decoded === NOTIN} onClick={submit}>
|
||||
Decode
|
||||
</ActionButton>
|
||||
|
||||
89
src/pages/Storage/StorageEntryPicker.tsx
Normal file
89
src/pages/Storage/StorageEntryPicker.tsx
Normal file
@@ -0,0 +1,89 @@
|
||||
import { DocsRenderer } from "@/components/DocsRenderer"
|
||||
import { Popover } from "@/components/Popover"
|
||||
import { SearchableSelect } from "@/components/Select"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import { Info } from "lucide-react"
|
||||
import { map } from "rxjs"
|
||||
import { BlockPicker, selectedBlock$ } from "./BlockPicker"
|
||||
import { partialEntry$, selectedEntry$, selectEntry } from "./storage.state"
|
||||
|
||||
const metadataStorage$ = state(
|
||||
selectedBlock$.pipe(
|
||||
map(({ ctx }) => ({
|
||||
lookup: ctx.lookup,
|
||||
entries: Object.fromEntries(
|
||||
ctx.lookup.metadata.pallets
|
||||
.filter((p) => p.storage)
|
||||
.map((p) => [
|
||||
p.name,
|
||||
Object.fromEntries(
|
||||
p.storage!.items.map((item) => [item.name, item.type]),
|
||||
),
|
||||
]),
|
||||
),
|
||||
})),
|
||||
),
|
||||
)
|
||||
|
||||
export const StorageEntryPicker = () => {
|
||||
const { entries } = useStateObservable(metadataStorage$)
|
||||
const partialEntry = useStateObservable(partialEntry$)
|
||||
const selectedEntry = useStateObservable(selectedEntry$)
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
<div className="grid grid-cols-1 gap-x-3 gap-y-2 @3xl:grid-cols-3 max-w-2xl">
|
||||
<label>
|
||||
Block
|
||||
<BlockPicker />
|
||||
</label>
|
||||
<label>
|
||||
Pallet
|
||||
<SearchableSelect
|
||||
value={partialEntry.pallet}
|
||||
setValue={(v) => selectEntry({ pallet: v })}
|
||||
options={Object.keys(entries).map((e) => ({
|
||||
text: e,
|
||||
value: e,
|
||||
}))}
|
||||
/>
|
||||
</label>
|
||||
{partialEntry.pallet && entries[partialEntry.pallet] && (
|
||||
<label className="max-w-52">
|
||||
<div className="flex items-center justify-between">
|
||||
Entry
|
||||
{selectedEntry?.docs.length ? (
|
||||
<Popover
|
||||
content={
|
||||
<DocsRenderer
|
||||
docs={selectedEntry.docs}
|
||||
className="max-h-none"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
className="text-muted-foreground"
|
||||
>
|
||||
<Info size={16} />
|
||||
</button>
|
||||
</Popover>
|
||||
) : null}
|
||||
</div>
|
||||
<SearchableSelect
|
||||
value={partialEntry.entry}
|
||||
setValue={(v) => selectEntry({ entry: v })}
|
||||
options={
|
||||
Object.keys(entries[partialEntry.pallet]).map((s) => ({
|
||||
text: s,
|
||||
value: s,
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { EditCodec } from "@/codec-components/EditCodec"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { BinaryEditButton } from "@/components/BinaryEditButton"
|
||||
import { CopyText } from "@/components/Copy"
|
||||
import SliderToggle from "@/components/Toggle"
|
||||
import { useNavigate } from "@/hashParams"
|
||||
import {
|
||||
@@ -9,17 +8,19 @@ import {
|
||||
CodecComponentValue,
|
||||
NOTIN,
|
||||
} from "@polkadot-api/react-builder"
|
||||
import { Input } from "@polkahub/ui-components"
|
||||
import { state, useStateObservable, withDefault } from "@react-rxjs/core"
|
||||
import { createSignal, mergeWithKey } from "@react-rxjs/utils"
|
||||
import { Enum } from "polkadot-api"
|
||||
import { Binary, Enum } from "polkadot-api"
|
||||
import { fromHex } from "polkadot-api/utils"
|
||||
import { FC } from "react"
|
||||
import { ChangeEvent, FC } from "react"
|
||||
import {
|
||||
combineLatest,
|
||||
distinctUntilChanged,
|
||||
filter,
|
||||
firstValueFrom,
|
||||
map,
|
||||
merge,
|
||||
scan,
|
||||
startWith,
|
||||
switchMap,
|
||||
@@ -27,23 +28,21 @@ import {
|
||||
} from "rxjs"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { selectedBlock$ } from "./BlockPicker"
|
||||
import { DecodedKey, decodeKey } from "./decodeKey"
|
||||
import { decodeKey } from "./decodeKey"
|
||||
import {
|
||||
addStorageSubscription,
|
||||
selectedEntry$,
|
||||
selectEntry,
|
||||
} from "./storage.state"
|
||||
import { StorageEntryPicker } from "./StorageEntryPicker"
|
||||
|
||||
export const StorageQuery: FC = () => {
|
||||
const selectedEntry = useStateObservable(selectedEntry$)
|
||||
const isReady = useStateObservable(isReady$)
|
||||
const navigate = useNavigate()
|
||||
|
||||
if (!selectedEntry) return null
|
||||
|
||||
const submit = async () => {
|
||||
const [entry, keyValues, keysEnabled, { hash }] = await firstValueFrom(
|
||||
combineLatest([selectedEntry$, keyValues$, keysEnabled$, selectedBlock$]),
|
||||
combineLatest([selectedEntry$, keyValues$, argsEnabled$, selectedBlock$]),
|
||||
)
|
||||
const args = keyValues.slice(0, keysEnabled)
|
||||
|
||||
@@ -58,8 +57,9 @@ export const StorageQuery: FC = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-start w-full">
|
||||
<KeyDisplay />
|
||||
<StorageEntryPicker />
|
||||
<StorageKeysInput />
|
||||
<KeyInput />
|
||||
<ActionButton disabled={!isReady} onClick={submit}>
|
||||
Query
|
||||
</ActionButton>
|
||||
@@ -82,7 +82,7 @@ const hashers$ = selectedEntry$.pipeState(
|
||||
|
||||
const [toggleKey$, toggleKey] = createSignal<number>()
|
||||
export const [changeKeysEnabled$, setKeysEnabled] = createSignal<number>()
|
||||
const keysEnabled$ = keys$.pipeState(
|
||||
const argsEnabled$ = keys$.pipeState(
|
||||
switchMap((k) =>
|
||||
mergeWithKey({
|
||||
toggle: toggleKey$,
|
||||
@@ -130,7 +130,7 @@ export const keyValues$ = keys$.pipeState(
|
||||
)
|
||||
|
||||
const isReady$ = state(
|
||||
combineLatest([keyValues$, keysEnabled$]).pipe(
|
||||
combineLatest([keyValues$, argsEnabled$]).pipe(
|
||||
map(
|
||||
([keyValues, keysEnabled]) =>
|
||||
keyValues.length >= keysEnabled &&
|
||||
@@ -145,27 +145,30 @@ export const StorageKeysInput: FC<{
|
||||
}> = ({ disableToggle }) => {
|
||||
const keys = useStateObservable(keys$)
|
||||
const hashers = useStateObservable(hashers$)
|
||||
const keysEnabled = useStateObservable(keysEnabled$)
|
||||
const argsEnabled = useStateObservable(argsEnabled$)
|
||||
|
||||
return (
|
||||
<ol className="flex flex-col gap-2">
|
||||
{keys.map((type, idx) => (
|
||||
<li key={idx} className="flex flex-row gap-2 items-center">
|
||||
{disableToggle ? null : (
|
||||
<SliderToggle
|
||||
isToggled={keysEnabled > idx}
|
||||
toggle={() => toggleKey(idx)}
|
||||
<div>
|
||||
<label>Params</label>
|
||||
<ol className="flex flex-col gap-2">
|
||||
{keys.map((type, idx) => (
|
||||
<li key={idx} className="flex flex-row gap-2 items-center">
|
||||
{disableToggle ? null : (
|
||||
<SliderToggle
|
||||
isToggled={argsEnabled > idx}
|
||||
toggle={() => toggleKey(idx)}
|
||||
/>
|
||||
)}
|
||||
<StorageArgInput
|
||||
idx={idx}
|
||||
hasher={hashers[idx]}
|
||||
type={type}
|
||||
disabled={argsEnabled <= idx}
|
||||
/>
|
||||
)}
|
||||
<StorageKeyInput
|
||||
idx={idx}
|
||||
hasher={hashers[idx]}
|
||||
type={type}
|
||||
disabled={keysEnabled <= idx}
|
||||
/>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</li>
|
||||
))}
|
||||
</ol>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -181,7 +184,7 @@ const builderState$ = state(
|
||||
const keysCodec$ = combineLatest([keys$, builderState$]).pipe(
|
||||
map(([keys, builder]) => keys.map((type) => builder?.buildDefinition(type))),
|
||||
)
|
||||
const keyInputValue$ = state(
|
||||
const argInputValue$ = state(
|
||||
(idx: number) =>
|
||||
keyValues$.pipe(
|
||||
withLatestFrom(keysCodec$),
|
||||
@@ -217,14 +220,14 @@ const keyInputValue$ = state(
|
||||
type: CodecComponentType.Initial,
|
||||
} satisfies CodecComponentValue,
|
||||
)
|
||||
const StorageKeyInput: FC<{
|
||||
const StorageArgInput: FC<{
|
||||
idx: number
|
||||
type: number
|
||||
disabled: boolean
|
||||
hasher: string
|
||||
}> = ({ idx, type, disabled, hasher }) => {
|
||||
const builder = useStateObservable(builderState$)
|
||||
const value = useStateObservable(keyInputValue$(idx))
|
||||
const value = useStateObservable(argInputValue$(idx))
|
||||
|
||||
if (!builder) return null
|
||||
|
||||
@@ -317,7 +320,7 @@ const keyCodec$ = state(
|
||||
)
|
||||
|
||||
export const encodedKey$ = state(
|
||||
combineLatest([keyCodec$, keyValues$, keysEnabled$]).pipe(
|
||||
combineLatest([keyCodec$, keyValues$, argsEnabled$]).pipe(
|
||||
map(([codec, keyValues, keysEnabled]) => {
|
||||
const args = keyValues.slice(0, keysEnabled)
|
||||
if (
|
||||
@@ -338,69 +341,103 @@ export const encodedKey$ = state(
|
||||
null,
|
||||
)
|
||||
|
||||
export const KeyDisplay: FC = () => {
|
||||
const key = useStateObservable(encodedKey$)
|
||||
const [keyInputChange$, onKeyInputChange] =
|
||||
createSignal<ChangeEvent<HTMLInputElement>>()
|
||||
const [keyInputBlur$, onKeyInputBlur] = createSignal<void>()
|
||||
const keyInput$ = state(
|
||||
merge(
|
||||
keyInputChange$.pipe(
|
||||
map((v) => v.currentTarget.value),
|
||||
withLatestFrom(selectedBlock$.pipe(map((v) => v.ctx))),
|
||||
map(([value, ctx]) => {
|
||||
const error =
|
||||
value.length > 3 &&
|
||||
(() => {
|
||||
try {
|
||||
return !decodeKey(ctx, Binary.fromHex(value))
|
||||
} catch {
|
||||
return true
|
||||
}
|
||||
})()
|
||||
|
||||
return { value, error }
|
||||
}),
|
||||
),
|
||||
merge(encodedKey$, keyInputBlur$.pipe(switchMap(() => encodedKey$))).pipe(
|
||||
map((value) => ({ value: value ?? "", error: false })),
|
||||
),
|
||||
),
|
||||
{
|
||||
value: "",
|
||||
error: false,
|
||||
},
|
||||
)
|
||||
|
||||
export const KeyInput: FC = () => {
|
||||
const keyInput = useStateObservable(keyInput$)
|
||||
const builder = useStateObservable(builderState$)
|
||||
const selectedEntry = useStateObservable(selectedEntry$)
|
||||
const keysEnabled = useStateObservable(keysEnabled$)
|
||||
const keysEnabled = useStateObservable(argsEnabled$)
|
||||
|
||||
if (!builder || !selectedEntry) return null
|
||||
|
||||
return (
|
||||
<div className="flex w-full overflow-hidden border border-card-foreground/60 px-3 p-2 gap-2 items-center bg-card text-card-foreground">
|
||||
<div className="shrink-0 text-sm font-bold">Encoded key:</div>
|
||||
<div
|
||||
<div className="w-full">
|
||||
<label>Encoded key</label>
|
||||
<Input
|
||||
type="text"
|
||||
className={twMerge(
|
||||
"flex-1 overflow-hidden whitespace-nowrap text-ellipsis text-sm tabular-nums",
|
||||
key === null ? "text-card-foreground/60" : null,
|
||||
"min-w-0 w-full tabular-nums text-muted-foreground",
|
||||
keyInput.error && "text-red-600",
|
||||
)}
|
||||
>
|
||||
{key ?? "Fill in all the storage keys to calculate the encoded key"}
|
||||
</div>
|
||||
<CopyText text={key ?? ""} disabled={key === null} binary />
|
||||
<BinaryEditButton
|
||||
initialValue={key ? fromHex(key) : undefined}
|
||||
onValueChange={(value: NonNullable<DecodedKey>) => {
|
||||
let newKeysEnabled = keysEnabled
|
||||
if (
|
||||
value.pallet.name !== selectedEntry.pallet ||
|
||||
value.item.name !== selectedEntry.entry
|
||||
) {
|
||||
selectEntry({
|
||||
pallet: value.pallet.name,
|
||||
entry: value.item.name,
|
||||
})
|
||||
newKeysEnabled =
|
||||
value.item.type.tag === "plain"
|
||||
? 0
|
||||
: value.item.type.value.hashers.length
|
||||
}
|
||||
placeholder="Fill in all the storage keys to calculate the encoded key"
|
||||
value={keyInput.value}
|
||||
onChange={onKeyInputChange}
|
||||
onBlur={() => {
|
||||
onKeyInputBlur()
|
||||
|
||||
if (value.args.length !== newKeysEnabled) {
|
||||
if (value.args.length > newKeysEnabled) {
|
||||
toggleKey(value.args.length - 1)
|
||||
} else {
|
||||
toggleKey(value.args.length)
|
||||
try {
|
||||
const decoded = decodeKey(
|
||||
{
|
||||
dynamicBuilder: builder,
|
||||
lookup: builder.lookup,
|
||||
},
|
||||
Binary.fromHex(keyInput.value),
|
||||
)
|
||||
if (!decoded) return
|
||||
|
||||
let newKeysEnabled = keysEnabled
|
||||
if (
|
||||
decoded.pallet.name !== selectedEntry.pallet ||
|
||||
decoded.item.name !== selectedEntry.entry
|
||||
) {
|
||||
selectEntry({
|
||||
pallet: decoded.pallet.name,
|
||||
entry: decoded.item.name,
|
||||
})
|
||||
newKeysEnabled =
|
||||
decoded.item.type.tag === "plain"
|
||||
? 0
|
||||
: decoded.item.type.value.hashers.length
|
||||
}
|
||||
}
|
||||
|
||||
value.args.forEach((value, idx) =>
|
||||
setKeyValue({
|
||||
idx,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
}}
|
||||
decode={(v) => {
|
||||
const decoded = decodeKey(
|
||||
{
|
||||
dynamicBuilder: builder,
|
||||
lookup: builder.lookup,
|
||||
},
|
||||
v,
|
||||
)
|
||||
if (!decoded) throw null
|
||||
return decoded
|
||||
if (decoded.args.length !== newKeysEnabled) {
|
||||
if (decoded.args.length > newKeysEnabled) {
|
||||
toggleKey(decoded.args.length - 1)
|
||||
} else {
|
||||
toggleKey(decoded.args.length)
|
||||
}
|
||||
}
|
||||
|
||||
decoded.args.forEach((value, idx) =>
|
||||
setKeyValue({
|
||||
idx,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
withLatestFrom,
|
||||
} from "rxjs"
|
||||
import { selectedEntry$ } from "./storage.state"
|
||||
import { encodedKey$, KeyDisplay, StorageKeysInput } from "./StorageQuery"
|
||||
import { encodedKey$, KeyInput, StorageKeysInput } from "./StorageQuery"
|
||||
|
||||
const [setValue$, setValue] = createSignal<Uint8Array | "partial" | null>()
|
||||
const currentValue$ = state(
|
||||
@@ -71,7 +71,7 @@ export const StorageSet: FC = () => {
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 items-start w-full overflow-hidden">
|
||||
<KeyDisplay />
|
||||
<KeyInput />
|
||||
<StorageKeysInput disableToggle />
|
||||
{currentValue && currentValue.encodedKey ? (
|
||||
<>
|
||||
|
||||
Reference in New Issue
Block a user