Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions src/pages/ViewFns/ViewFnQuery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Circle, Dot } from "lucide-react"
import { FC, useState } from "react"
import {
combineLatest,
distinctUntilChanged,
filter,
firstValueFrom,
map,
Expand All @@ -21,10 +22,11 @@ import {
switchMap,
} from "rxjs"
import { twMerge } from "tailwind-merge"
import { addViewFnCall, selectedEntry$ } from "./viewFns.state"
import { selectedBlock$ } from "../Storage/BlockPicker"
import { addViewFnCall, viewFnEntryState } from "./viewFns.state"

export const ViewFnQuery: FC = () => {
const selectedEntry = useStateObservable(selectedEntry$)
const selectedEntry = useStateObservable(viewFnEntryState.selectedEntry$)
const isReady = useStateObservable(isReady$)
const navigate = useNavigate()

Expand All @@ -33,10 +35,27 @@ export const ViewFnQuery: FC = () => {
const submit = async () => {
const [entry, inputValues, builder, block] = await firstValueFrom(
combineLatest([
selectedEntry$,
viewFnEntryState.selectedEntry$,
inputValues$,
dynamicBuilder$,
client$.pipe(switchMap((client) => client.finalizedBlock$)),
selectedBlock$.pipe(
switchMap((block) =>
block.hash
? [
{
latest: false,
hash: block.hash,
},
]
: client$.pipe(
switchMap((client) => client.finalizedBlock$),
map((v) => ({
latest: true,
hash: v.hash,
})),
),
),
),
]),
)
const decodedValues = inputValues.map((v, i) =>
Expand All @@ -46,6 +65,7 @@ export const ViewFnQuery: FC = () => {
)

const id = await addViewFnCall({
latestBlock: block.latest,
blockHash: block.hash,
pallet: entry!.pallet,
name: entry!.name,
Expand All @@ -64,13 +84,16 @@ export const ViewFnQuery: FC = () => {
)
}

const [inputValueChange$, setInputValue] = createSignal<{
export const [inputValueChange$, setInputValue] = createSignal<{
idx: number
value: Uint8Array | "partial" | null
}>()
const inputValues$ = selectedEntry$.pipeState(
const inputValues$ = viewFnEntryState.selectedEntry$.pipeState(
filter((v) => !!v),
map((v) => v.inputs),
distinctUntilChanged(
(a, b) => a.length === b.length && a.every((v, i) => b[i].type === v.type),
),
switchMap((inputs) => {
const values: Array<Uint8Array | "partial" | null> = inputs.map(() => null)
return inputValueChange$.pipe(
Expand All @@ -91,7 +114,7 @@ const isReady$ = inputValues$.pipeState(
)

const ViewFnInputValues: FC = () => {
const selectedEntry = useStateObservable(selectedEntry$)
const selectedEntry = useStateObservable(viewFnEntryState.selectedEntry$)
if (!selectedEntry || !selectedEntry.inputs.length) return null

return (
Expand Down
50 changes: 48 additions & 2 deletions src/pages/ViewFns/ViewFnResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { ButtonGroup } from "@/components/ButtonGroup"
import { JsonDisplay } from "@/components/JsonDisplay"
import { workspaceEntryCtxOrAdd$ } from "@/components/Workspace"
import { runtimeCtx$ } from "@/state/chains/chain.state"
import { shortStr } from "@/utils"
import { state, useStateObservable, withDefault } from "@react-rxjs/core"
import { FC, useMemo, useState } from "react"
import { FC, useEffect, useMemo, useState } from "react"
import { useParams } from "react-router-dom"
import { filter, firstValueFrom } from "rxjs"
import { setBlockHashValue } from "../Storage/BlockPicker"
import { ValueDisplay } from "../Storage/StorageSubscriptions"
import { setInputValue } from "./ViewFnQuery"
import { ViewFnWorkspaceContext } from "./ViewFnWorkspaceEntry"
import { idToViewFnCall, viewFnCallToWorkspaceEntry } from "./viewFns.state"
import {
idToViewFnCall,
viewFnCallToWorkspaceEntry,
viewFnEntryState,
} from "./viewFns.state"

export const ViewFnResults: FC = () => {
const { callId } = useParams()
Expand Down Expand Up @@ -36,6 +44,7 @@ const viewFnCtx$ = state(

const ViewFnResultBox: FC<{ id: string }> = ({ id }) => {
const context = useStateObservable(viewFnCtx$(id))
useSynchronizeInputs(id)

return context ? <ViewFnResultContent id={id} context={context} /> : null
}
Expand All @@ -53,6 +62,10 @@ const ViewFnResultContent: FC<{
{context.pallet}.{context.name}
</h3>
<div className="flex items-center shrink-0 gap-2">
<div className="text-xs text-center">
<p>Block</p>
<p>{shortStr(context.blockHash, 6)}</p>
</div>
<ButtonGroup
value={mode}
onValueChange={setMode as any}
Expand Down Expand Up @@ -122,3 +135,36 @@ const ResultDisplay: FC<{
</div>
)
}

const useSynchronizeInputs = (id: string) => {
useEffect(() => {
let cancelled = false
const run = async () => {
const params = await idToViewFnCall(id)
if (cancelled) return
setBlockHashValue(params.latestBlock ? "Latest" : params.blockHash)
viewFnEntryState.selectEntry({
group: params.pallet,
item: params.name,
})
// Let entry settle
await firstValueFrom(
viewFnEntryState.selectedEntry$.pipe(
filter(
(v) => !!v && v.pallet === params.pallet && v.name === params.name,
),
),
)
if (cancelled) return
const encodedArgs = params.args.map((arg, i) =>
params.codec.inner[i].enc(arg),
)
encodedArgs.forEach((value, idx) => setInputValue({ idx, value }))
}
run()

return () => {
cancelled = true
}
}, [id])
}
1 change: 1 addition & 0 deletions src/pages/ViewFns/ViewFnWorkspaceEntry.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { FC } from "react"
import type { ViewFnResult } from "./viewFns.state"

export type ViewFnWorkspaceContext = {
blockHash: string
pallet: string
name: string
result$: DefaultedStateObservable<ViewFnResult | null>
Expand Down
121 changes: 17 additions & 104 deletions src/pages/ViewFns/ViewFns.tsx
Original file line number Diff line number Diff line change
@@ -1,115 +1,28 @@
import { DocsRenderer } from "@/components/DocsRenderer"
import { LoadingMetadata } from "@/components/Loading"
import { SearchableSelect } from "@/components/Select"
import { MetadataEntryInput } from "@/components/MetadataEntryInput"
import { withSubscribe } from "@/components/withSuspense"
import { lookup$ } from "@/state/chains/chain.state"
import { state, useStateObservable } from "@react-rxjs/core"
import { useEffect, useState } from "react"
import { Route, Routes } from "react-router-dom"
import { map } from "rxjs"
import { CenteredScrollContainer } from "../AppShell"
import { ViewFnQuery } from "./ViewFnQuery"
import { ViewFnResults } from "./ViewFnResults"
import { selectedEntry$, setSelectedFn } from "./viewFns.state"

const metadataViewFns$ = state(
lookup$.pipe(
map((lookup) => ({
lookup,
entries: Object.fromEntries(
lookup.metadata.pallets
.filter((p) => p.viewFns.length)
.map((p) => [
p.name,
Object.fromEntries(
p.viewFns.map((method) => [method.name, method]),
),
]),
),
})),
),
)
import { viewFnEntryState } from "./viewFns.state"

export const ViewFns = withSubscribe(
() => {
const { lookup, entries } = useStateObservable(metadataViewFns$)
const defaultPallet = Object.keys(entries)[0] ?? null
const defaultFn = defaultPallet
? Object.keys(entries[defaultPallet])[0]
: null
const [pallet, setPallet] = useState<string | null>(defaultPallet)
const [fnName, setFnName] = useState<string | null>(defaultFn)
const entry = useStateObservable(selectedEntry$)

const selectedPallet =
(pallet && lookup.metadata.pallets.find((p) => p.name === pallet)) || null

useEffect(
() =>
setFnName((prev) => {
if (!selectedPallet?.viewFns[0]) return null
return selectedPallet.viewFns.some((v) => v.name === prev)
? prev
: selectedPallet.viewFns[0].name
}),
// eslint-disable-next-line react-hooks/exhaustive-deps
[selectedPallet?.name],
)

useEffect(() => {
const selectedMethod =
(fnName && selectedPallet?.viewFns.find((it) => it.name === fnName)) ||
null
setSelectedFn(
selectedMethod
? { ...selectedMethod, pallet: selectedPallet!.name }
: null,
)
}, [selectedPallet, fnName])

return (
<CenteredScrollContainer className="p-4 pb-0 flex flex-col gap-2 items-start">
<div className="flex items-center gap-2">
<label>
Pallet
<SearchableSelect
value={pallet}
setValue={(v) => setPallet(v)}
options={Object.keys(entries).map((e) => ({
text: e,
value: e,
}))}
/>
</label>
{selectedPallet && pallet && (
<label>
Function
<SearchableSelect
value={fnName}
setValue={(v) => setFnName(v)}
options={
Object.keys(entries[pallet]).map((s) => ({
text: s,
value: s,
})) ?? []
}
/>
</label>
)}
</div>
{!!entry?.docs.length && (
<div className="w-full">
Docs
<DocsRenderer docs={entry.docs} />
</div>
)}
<ViewFnQuery />
<Routes>
<Route path=":callId" element={<ViewFnResults />} />
</Routes>
</CenteredScrollContainer>
)
},
() => (
<CenteredScrollContainer className="p-4 pb-0 flex flex-col gap-2 items-start">
<MetadataEntryInput
state={viewFnEntryState}
labels={{
group: "Pallet",
item: "Function",
}}
/>
<ViewFnQuery />
<Routes>
<Route path=":callId" element={<ViewFnResults />} />
</Routes>
</CenteredScrollContainer>
),
{
fallback: <LoadingMetadata />,
},
Expand Down
Loading