show entry results
This commit is contained in:
@@ -29,6 +29,7 @@
|
||||
"react-portal": "^4.2.2",
|
||||
"react-router-dom": "^6.27.0",
|
||||
"react-svg": "^16.1.34",
|
||||
"react-virtuoso": "^4.12.0",
|
||||
"react18-json-view": "^0.2.8",
|
||||
"rxjs": "^7.8.1",
|
||||
"save-as": "^0.1.8",
|
||||
|
||||
15
pnpm-lock.yaml
generated
15
pnpm-lock.yaml
generated
@@ -61,6 +61,9 @@ importers:
|
||||
react-svg:
|
||||
specifier: ^16.1.34
|
||||
version: 16.1.34(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react-virtuoso:
|
||||
specifier: ^4.12.0
|
||||
version: 4.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react18-json-view:
|
||||
specifier: ^0.2.8
|
||||
version: 0.2.8(patch_hash=tfqbi5gr5whnc64n7zptszjgcy)(react@18.3.1)
|
||||
@@ -1991,6 +1994,13 @@ packages:
|
||||
react: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
|
||||
|
||||
react-virtuoso@4.12.0:
|
||||
resolution: {integrity: sha512-oHrKlU7xHsrnBQ89ecZoMPAK0tHnI9s1hsFW3KKg5ZGeZ5SWvbGhg/QFJFY4XETAzoCUeu+Xaxn1OUb/PGtPlA==}
|
||||
engines: {node: '>=10'}
|
||||
peerDependencies:
|
||||
react: '>=16 || >=17 || >= 18'
|
||||
react-dom: '>=16 || >=17 || >= 18'
|
||||
|
||||
react18-json-view@0.2.8:
|
||||
resolution: {integrity: sha512-uJlcf5PEDaba6yTqfcDAcMSYECZ15SLcpP94mLFTa/+fa1kZANjERqKzS7YxxsrGP4+jDxt6sIaglR0PbQcKPw==}
|
||||
peerDependencies:
|
||||
@@ -4173,6 +4183,11 @@ snapshots:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react-virtuoso@4.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
|
||||
react18-json-view@0.2.8(patch_hash=tfqbi5gr5whnc64n7zptszjgcy)(react@18.3.1):
|
||||
dependencies:
|
||||
react: 18.3.1
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
} from "@codec-components"
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { FocusPath } from "./FocusPath"
|
||||
import { ViewCodec } from "@/codec-components/ViewCodec"
|
||||
|
||||
export const EditMode: React.FC<{
|
||||
codecType: number
|
||||
@@ -67,7 +66,7 @@ export const EditMode: React.FC<{
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<div className="p-2 rounded bg-polkadot-900">
|
||||
<ViewCodec {...props} />
|
||||
<EditCodec {...props} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -30,14 +30,20 @@ const metadataStorage$ = state(
|
||||
|
||||
export const Storage = withSubscribe(() => {
|
||||
const { lookup, entries } = useStateObservable(metadataStorage$)
|
||||
const [pallet, setPallet] = useState<string | null>("System")
|
||||
const [entry, setEntry] = useState<string | null>("Account")
|
||||
const [pallet, setPallet] = useState<string | null>("Staking")
|
||||
const [entry, setEntry] = useState<string | null>("ErasStakersPaged")
|
||||
|
||||
const selectedPallet =
|
||||
(pallet && lookup.metadata.pallets.find((p) => p.name === pallet)) || null
|
||||
|
||||
useEffect(
|
||||
() => setEntry(selectedPallet?.storage?.items[0]?.name ?? null),
|
||||
() =>
|
||||
setEntry((prev) => {
|
||||
if (!selectedPallet?.storage?.items[0]) return null
|
||||
return selectedPallet.storage.items.some((v) => v.name === prev)
|
||||
? prev
|
||||
: selectedPallet.storage.items[0].name
|
||||
}),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[selectedPallet?.name],
|
||||
)
|
||||
|
||||
@@ -42,6 +42,7 @@ export const StorageDecode: FC = () => {
|
||||
|
||||
addStorageSubscription({
|
||||
name: `${entry!.pallet}.${entry!.entry}(…)`,
|
||||
args: null,
|
||||
single: true,
|
||||
stream,
|
||||
type: entry!.value,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { lookup$, unsafeApi$ } from "@/chain.state"
|
||||
import { EditCodec } from "@/codec-components/EditCodec"
|
||||
import { ActionButton } from "@/components/ActionButton"
|
||||
import { BinaryEditButton } from "@/components/BinaryEditButton"
|
||||
import { bytesToString } from "@/components/BinaryInput"
|
||||
import { CopyText } from "@/components/Copy"
|
||||
import SliderToggle from "@/components/Toggle"
|
||||
import {
|
||||
@@ -26,7 +25,11 @@ import {
|
||||
switchMap,
|
||||
} from "rxjs"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
import { addStorageSubscription, selectedEntry$ } from "./storage.state"
|
||||
import {
|
||||
addStorageSubscription,
|
||||
selectedEntry$,
|
||||
stringifyArg,
|
||||
} from "./storage.state"
|
||||
|
||||
export const StorageQuery: FC = () => {
|
||||
const selectedEntry = useStateObservable(selectedEntry$)
|
||||
@@ -45,18 +48,11 @@ export const StorageQuery: FC = () => {
|
||||
? storageEntry.watchValue(...args)
|
||||
: from(storageEntry.getEntries(...args))
|
||||
|
||||
const stringifyArg = (value: unknown) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (value instanceof Binary) {
|
||||
return bytesToString(value)
|
||||
}
|
||||
return "arg"
|
||||
}
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
const argString = [...args.map(stringifyArg), ...(single ? [] : ["…"])]
|
||||
|
||||
addStorageSubscription({
|
||||
name: `${entry!.pallet}.${entry!.entry}(${args.map(stringifyArg).join(", ")})`,
|
||||
name: `${entry!.pallet}.${entry!.entry}(${argString})`,
|
||||
args,
|
||||
single,
|
||||
stream,
|
||||
type: entry!.value,
|
||||
@@ -272,7 +268,11 @@ const encodedKey$ = state(
|
||||
selectedEntry.pallet,
|
||||
selectedEntry.entry,
|
||||
)
|
||||
return codec.enc(...args)
|
||||
try {
|
||||
return codec.enc(...args)
|
||||
} catch (_) {
|
||||
return null
|
||||
}
|
||||
}),
|
||||
),
|
||||
null,
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
import { useStateObservable } from "@react-rxjs/core"
|
||||
import { FC } from "react"
|
||||
import { lookup$, metadata$ } from "@/chain.state"
|
||||
import { ViewCodec } from "@/codec-components/ViewCodec"
|
||||
import { CopyBinary } from "@/codec-components/ViewCodec/CopyBinary"
|
||||
import { ButtonGroup } from "@/components/ButtonGroup"
|
||||
import { JsonDisplay } from "@/components/JsonDisplay"
|
||||
import { CodecComponentType, NOTIN } from "@codec-components"
|
||||
import { getDynamicBuilder } from "@polkadot-api/metadata-builders"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import { Trash2 } from "lucide-react"
|
||||
import { FC, useMemo, useState } from "react"
|
||||
import { Virtuoso } from "react-virtuoso"
|
||||
import { map } from "rxjs"
|
||||
import {
|
||||
removeStorageSubscription,
|
||||
StorageSubscription,
|
||||
storageSubscription$,
|
||||
storageSubscriptionKeys$,
|
||||
stringifyArg,
|
||||
} from "./storage.state"
|
||||
import { JsonDisplay } from "@/components/JsonDisplay"
|
||||
import { Trash2 } from "lucide-react"
|
||||
|
||||
export const StorageSubscriptions: FC = () => {
|
||||
const keys = useStateObservable(storageSubscriptionKeys$)
|
||||
@@ -31,7 +41,6 @@ const StorageSubscriptionBox: FC<{ subscription: string }> = ({
|
||||
const storageSubscription = useStateObservable(
|
||||
storageSubscription$(subscription),
|
||||
)
|
||||
|
||||
if (!storageSubscription) return null
|
||||
|
||||
return (
|
||||
@@ -48,11 +57,137 @@ const StorageSubscriptionBox: FC<{ subscription: string }> = ({
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{"result" in storageSubscription ? (
|
||||
<JsonDisplay src={storageSubscription.result} />
|
||||
) : (
|
||||
<div className="text-sm text-slate-400">Loading…</div>
|
||||
)}
|
||||
<ResultDisplay storageSubscription={storageSubscription} />
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
const ResultDisplay: FC<{
|
||||
storageSubscription: StorageSubscription
|
||||
}> = ({ storageSubscription }) => {
|
||||
if (!("result" in storageSubscription)) {
|
||||
return <div className="text-sm text-slate-400">Loading…</div>
|
||||
}
|
||||
|
||||
if (storageSubscription.single) {
|
||||
return (
|
||||
<ValueDisplay
|
||||
storageSubscription={storageSubscription}
|
||||
value={storageSubscription.result}
|
||||
title={"Result"}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const values = storageSubscription.result as Array<{
|
||||
keyArgs: unknown[]
|
||||
value: unknown
|
||||
}>
|
||||
|
||||
const renderItem = (keyArgs: unknown[], value: unknown) => {
|
||||
const title = keyArgs
|
||||
.slice(storageSubscription.args?.length ?? 0)
|
||||
.map(stringifyArg)
|
||||
.join(", ")
|
||||
return (
|
||||
<div key={title} className={itemClasses}>
|
||||
<ValueDisplay
|
||||
title={title}
|
||||
value={value}
|
||||
storageSubscription={storageSubscription}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (values.length > 10) {
|
||||
return (
|
||||
<Virtuoso
|
||||
style={{ height: "60svh" }}
|
||||
totalCount={values.length}
|
||||
itemContent={(i) =>
|
||||
values[i] && renderItem(values[i].keyArgs, values[i].value)
|
||||
}
|
||||
components={{ Item: VirtuosoItem }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="max-h-[60svh] overflow-auto">
|
||||
{values.map(({ keyArgs, value }) => renderItem(keyArgs, value))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const itemClasses = "py-2 border-b first:pt-0 last:pb-0 last:border-b-0"
|
||||
const VirtuosoItem: FC = (props) => <div {...props} className={itemClasses} />
|
||||
|
||||
const metadataState$ = state(metadata$, null)
|
||||
const dynamicBuilder$ = state(
|
||||
lookup$.pipe(map((v) => getDynamicBuilder(v))),
|
||||
null,
|
||||
)
|
||||
const ValueDisplay: FC<{
|
||||
storageSubscription: StorageSubscription
|
||||
title: string
|
||||
value: unknown | NOTIN
|
||||
}> = ({ storageSubscription, title, value }) => {
|
||||
const [mode, setMode] = useState<"json" | "decoded">("json")
|
||||
const metadata = useStateObservable(metadataState$)
|
||||
const builder = useStateObservable(dynamicBuilder$)
|
||||
|
||||
const [codec, encodedValue] = useMemo(() => {
|
||||
if (!builder) return [null!, null!]
|
||||
const codec = builder.buildDefinition(storageSubscription.type)
|
||||
const encodedValue = (() => {
|
||||
try {
|
||||
return codec.enc(value)
|
||||
} catch (_) {
|
||||
return null
|
||||
}
|
||||
})()
|
||||
return [codec, encodedValue] as const
|
||||
}, [builder, value, storageSubscription.type])
|
||||
|
||||
if (!metadata || !builder) return null
|
||||
if (!encodedValue) {
|
||||
return <div className="text-slate-400">Empty</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex justify-between items-center overflow-hidden">
|
||||
<div className="flex flex-1 gap-2 overflow-hidden">
|
||||
<CopyBinary value={encodedValue} />
|
||||
<h3 className="overflow-hidden text-ellipsis">{title}</h3>
|
||||
</div>
|
||||
<ButtonGroup
|
||||
value={mode}
|
||||
onValueChange={setMode as any}
|
||||
items={[
|
||||
{
|
||||
value: "json",
|
||||
content: "JSON",
|
||||
},
|
||||
{
|
||||
value: "decoded",
|
||||
content: "Decoded",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{mode === "decoded" ? (
|
||||
<ViewCodec
|
||||
codecType={storageSubscription.type}
|
||||
value={{
|
||||
type: CodecComponentType.Initial,
|
||||
value: codec.enc(value),
|
||||
}}
|
||||
metadata={metadata}
|
||||
/>
|
||||
) : (
|
||||
<JsonDisplay src={value} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { bytesToString } from "@/components/BinaryInput"
|
||||
import { state } from "@react-rxjs/core"
|
||||
import {
|
||||
createKeyedSignal,
|
||||
@@ -5,6 +6,7 @@ import {
|
||||
partitionByKey,
|
||||
toKeySet,
|
||||
} from "@react-rxjs/utils"
|
||||
import { Binary } from "polkadot-api"
|
||||
import { map, Observable, startWith, switchMap, takeUntil } from "rxjs"
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
@@ -21,6 +23,7 @@ export const selectedEntry$ = state(entryChange$, null)
|
||||
|
||||
export const [newStorageSubscription$, addStorageSubscription] = createSignal<{
|
||||
name: string
|
||||
args: unknown[] | null
|
||||
type: number
|
||||
single: boolean
|
||||
stream: Observable<unknown>
|
||||
@@ -30,6 +33,7 @@ export const [removeStorageSubscription$, removeStorageSubscription] =
|
||||
|
||||
export type StorageSubscription = {
|
||||
name: string
|
||||
args: unknown[] | null
|
||||
type: number
|
||||
single: boolean
|
||||
} & ({ result: unknown } | {})
|
||||
@@ -61,6 +65,17 @@ export const storageSubscriptionKeys$ = state(
|
||||
)
|
||||
|
||||
export const storageSubscription$ = state(
|
||||
(key: string) => getStorageSubscription$(key),
|
||||
(key: string): Observable<StorageSubscription> =>
|
||||
getStorageSubscription$(key),
|
||||
null,
|
||||
)
|
||||
|
||||
export const stringifyArg = (value: unknown) => {
|
||||
if (typeof value === "object" && value !== null) {
|
||||
if (value instanceof Binary) {
|
||||
return bytesToString(value)
|
||||
}
|
||||
return "arg"
|
||||
}
|
||||
return JSON.stringify(value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user