show entry results

This commit is contained in:
Victor Oliva
2024-10-25 17:57:53 +02:00
parent a33c7b7198
commit 30fc98eef5
8 changed files with 201 additions and 29 deletions

View File

@@ -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
View File

@@ -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

View File

@@ -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>

View File

@@ -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],
)

View File

@@ -42,6 +42,7 @@ export const StorageDecode: FC = () => {
addStorageSubscription({
name: `${entry!.pallet}.${entry!.entry}(…)`,
args: null,
single: true,
stream,
type: entry!.value,

View File

@@ -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,

View File

@@ -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>
)
}

View File

@@ -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)
}