feat: go to block (#91)
This commit is contained in:
committed by
GitHub
parent
a3378df16b
commit
334b232d40
21
src/components/ui/search-input.tsx
Normal file
21
src/components/ui/search-input.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Search } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
import { forwardRef } from "react"
|
||||
|
||||
export const SearchInput = forwardRef<
|
||||
HTMLInputElement,
|
||||
Omit<React.ComponentProps<"input">, "type">
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="flex items-center border-b px-3" cmdk-input-wrapper="">
|
||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
<input
|
||||
type="text"
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
@@ -1,6 +1,6 @@
|
||||
import { CopyText } from "@/components/Copy"
|
||||
import { Popover } from "@/components/Popover"
|
||||
import { Link } from "@/hashParams"
|
||||
import { Link, useNavigate } from "@/hashParams"
|
||||
import { client$ } from "@/state/chains/chain.state"
|
||||
import { state, useStateObservable } from "@react-rxjs/core"
|
||||
import { FC } from "react"
|
||||
@@ -9,6 +9,9 @@ import { twMerge } from "tailwind-merge"
|
||||
import { BlockInfo, blocksByHeight$, finalized$ } from "./block.state"
|
||||
import { BlockPopover } from "./BlockPopover"
|
||||
import * as Finalizing from "./FinalizingTable"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { SearchInput } from "@/components/ui/search-input"
|
||||
import { Search } from "lucide-react"
|
||||
|
||||
const best$ = client$.pipeState(
|
||||
switchMap((client) => client.bestBlocks$.pipe(map(([best]) => best))),
|
||||
@@ -84,6 +87,34 @@ const blockTable$ = state(
|
||||
[],
|
||||
)
|
||||
|
||||
export const BlockInput: FC = () => {
|
||||
const navigate = useNavigate()
|
||||
return (
|
||||
<form
|
||||
className="grow p-0 -my-2 flex"
|
||||
onSubmit={(e) => {
|
||||
const blockTarget = new FormData(e.currentTarget).get("blockTarget") as string
|
||||
const isHex = blockTarget?.match(/^0[xX][0-9a-fA-F]+$/)
|
||||
if (
|
||||
(isHex && blockTarget.length === 66) ||
|
||||
(!isHex && !Number.isNaN(Number(blockTarget)))
|
||||
) {
|
||||
navigate(blockTarget)
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
}}
|
||||
>
|
||||
<div className="grow">
|
||||
<SearchInput placeholder="Block hash or height" name="blockTarget" />
|
||||
</div>
|
||||
<Button className="grow-0">
|
||||
<Search />{" "}
|
||||
</Button>
|
||||
</form>
|
||||
)
|
||||
}
|
||||
|
||||
export const BlockTable = () => {
|
||||
const rows = useStateObservable(blockTable$)
|
||||
const finalized = useStateObservable(finalized$)
|
||||
@@ -100,7 +131,7 @@ export const BlockTable = () => {
|
||||
|
||||
return (
|
||||
<Finalizing.Root>
|
||||
<Finalizing.Title>Recent Blocks</Finalizing.Title>
|
||||
<Finalizing.Title search={<BlockInput />}>Recent Blocks</Finalizing.Title>
|
||||
<Finalizing.Table>
|
||||
{rows.map((row, i) => (
|
||||
<Finalizing.Row
|
||||
|
||||
@@ -6,8 +6,8 @@ import { BlockBody } from "./BlockBody"
|
||||
import { BlockInfoView } from "./BlockInfo"
|
||||
|
||||
export const BlockDetail = () => {
|
||||
const { hash } = useParams()
|
||||
const block = useStateObservable(blockInfoState$(hash ?? ""))
|
||||
const { hashOrHeight } = useParams()
|
||||
const block = useStateObservable(blockInfoState$(hashOrHeight ?? ""))
|
||||
|
||||
if (!block) return <Loading>Loading block…</Loading>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { Summary } from "./Summary"
|
||||
export const Explorer = withSubscribe(
|
||||
() => (
|
||||
<Routes>
|
||||
<Route path=":hash" element={<BlockDetail />} />
|
||||
<Route path=":hashOrHeight" element={<BlockDetail />} />
|
||||
<Route
|
||||
path="*"
|
||||
element={
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
import { FC, PropsWithChildren } from "react"
|
||||
import { FC, PropsWithChildren, ReactNode } from "react"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
export const Title: FC<PropsWithChildren> = ({ children }) => (
|
||||
<h2 className="font-bold p-2 border-b border-slate-400 mb-2">{children}</h2>
|
||||
export const Title: FC<PropsWithChildren<{ search?: ReactNode }>> = ({
|
||||
children,
|
||||
search,
|
||||
}) => (
|
||||
<h2 className="font-bold p-2 border-b border-slate-400 mb-2 flex">
|
||||
<span className="grow-0">{children}</span>
|
||||
{search}
|
||||
</h2>
|
||||
)
|
||||
|
||||
export const Table: FC<PropsWithChildren> = ({ children }) => (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { chainClient$, client$ } from "@/state/chains/chain.state"
|
||||
import { SystemEvent } from "@polkadot-api/observable-client"
|
||||
import { state } from "@react-rxjs/core"
|
||||
import { combineKeys, partitionByKey } from "@react-rxjs/utils"
|
||||
import { HexString, PolkadotClient } from "polkadot-api"
|
||||
import { FixedSizeBinary, HexString, PolkadotClient } from "polkadot-api"
|
||||
import {
|
||||
catchError,
|
||||
combineLatest,
|
||||
@@ -244,13 +244,43 @@ export const inMemoryBlocks$ = state(
|
||||
)
|
||||
inMemoryBlocks$.subscribe()
|
||||
|
||||
const blockHash$ = (hashOrHeight: string) =>
|
||||
hashOrHeight.length > 63
|
||||
? of(hashOrHeight.startsWith("0x") ? hashOrHeight : `0x${hashOrHeight}`)
|
||||
: client$.pipe(
|
||||
switchMap((client) =>
|
||||
from(
|
||||
client._request<HexString[], [number]>("archive_v1_hashByHeight", [
|
||||
Number(hashOrHeight),
|
||||
]),
|
||||
).pipe(
|
||||
map((x) => {
|
||||
if (x.length) return x[0]
|
||||
throw null
|
||||
}),
|
||||
catchError(() =>
|
||||
client
|
||||
.getUnsafeApi()
|
||||
.query.System.BlockHash.getValue(Number(hashOrHeight))
|
||||
.then((x: FixedSizeBinary<32>) => x.asHex()),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
export const blockInfoState$ = state(
|
||||
(hash: string) =>
|
||||
(hashOrHeight: string) =>
|
||||
inMemoryBlocks$.pipe(
|
||||
take(1),
|
||||
switchMap((blocks) =>
|
||||
blocks.has(hash) ? blockInfo$(hash) : getUnpinnedBlockInfo$(hash),
|
||||
),
|
||||
switchMap((blocks) => {
|
||||
if (blocks.has(hashOrHeight)) return blockInfo$(hashOrHeight)
|
||||
const potentialHeight = Number(hashOrHeight)
|
||||
const target = Array.from(blocks.values()).find(
|
||||
(x) => x?.number === potentialHeight,
|
||||
)
|
||||
if (target) return blockInfo$(target.hash)
|
||||
return blockHash$(hashOrHeight).pipe(mergeMap(getUnpinnedBlockInfo$))
|
||||
}),
|
||||
),
|
||||
null,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user