Files
qapi/src/pages/Explorer/Detail/BlockInfo.tsx
T

162 lines
4.7 KiB
TypeScript

import { CopyText } from "@/components/Copy"
import { Link } from "@/hashParams"
import { state, useStateObservable } from "@react-rxjs/core"
import { combineKeys } from "@react-rxjs/utils"
import { FC, ReactNode } from "react"
import { filter, map, startWith, switchMap, take } from "rxjs"
import {
BlockInfo,
blockInfoState$,
blocksByHeight$,
BlockState,
} from "../block.state"
import { BlockAuthor } from "./BlockAuthor"
import { BlockStatusIcon, statusText } from "./BlockState"
export const BlockInfoView: FC<{
block: BlockInfo
}> = ({ block }) => (
<section className="space-y-4 px-4 py-2">
<header className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="space-y-2">
<p className="text-3xl font-semibold tracking-tight">
#{block.number.toLocaleString()}
</p>
<div className="flex flex-wrap items-center gap-2 text-sm font-mono text-foreground/90">
<span className="truncate max-w-full">{block.hash}</span>
<CopyText className="text-foreground/70" text={block.hash} binary />
</div>
</div>
<div className="flex flex-wrap items-center gap-3 md:justify-end">
<StatusChip state={block.status} />
</div>
</header>
<div className="grid gap-4 md:grid-cols-2">
<DetailTile label="Parent block">
<BlockLink hash={block.parent} />
</DetailTile>
<DetailTile label="Children">
<BlockChildren hash={block.hash} />
</DetailTile>
</div>
{block.header && (
<div className="grid gap-4 grid-cols-1 md:grid-cols-2">
<DetailTile label="Roots">
<HashPreview title="state" value={block.header.stateRoot} />
<HashPreview title="extrinsic" value={block.header.extrinsicRoot} />
</DetailTile>
<DetailTile label="Block author">
<BlockAuthor hash={block.hash} header={block.header} />
</DetailTile>
</div>
)}
</section>
)
const StatusChip: FC<{ state: BlockState }> = ({ state }) => (
<span className="inline-flex items-center gap-2 rounded-full border border-foreground/20 bg-foreground/2 px-4 py-2 text-sm font-medium">
<BlockStatusIcon size={20} state={state} />
{statusText[state]}
</span>
)
const HashPreview: FC<{ title: string; value: string }> = ({
title,
value,
}) => (
<div className="font-mono text-sm">
{title}: {value.slice(0, 18)}…
<CopyText className="align-middle" text={value} binary />
</div>
)
const DetailTile: FC<{ label: string; children: ReactNode }> = ({
label,
children,
}) => (
<div className="rounded-xl border border-foreground/10 bg-foreground/2 p-4">
<p className="text-xs uppercase tracking-widest text-foreground/60">
{label}
</p>
<div className="mt-2 text-base text-muted-foreground">{children}</div>
</div>
)
const childBlocks$ = state(
(hash: string) =>
blockInfoState$(hash).pipe(
filter((v) => !!v),
take(1),
switchMap(({ hash, number }) =>
combineKeys(
blocksByHeight$.pipe(
map((v) => v[number + 1]),
map((v) =>
v
? [...v.values()]
.filter((block) => block.parent === hash)
.map((block) => block.hash)
: [],
),
),
(hash) =>
blockInfoState$(hash).pipe(
filter((v) => !!v),
startWith({ hash }),
),
),
),
map((children) =>
[...children.values()]
.sort((a, b) => {
const valueOf = (v: typeof a) =>
"status" in v ? statusValue[v.status] : 0
return valueOf(a) - valueOf(b)
})
.map((v) => v.hash),
),
),
[],
)
const statusValue: Record<BlockState, number> = {
[BlockState.Finalized]: 3,
[BlockState.Best]: 2,
[BlockState.Fork]: 1,
[BlockState.Pruned]: 0,
[BlockState.Unknown]: -1,
}
const BlockChildren: FC<{ hash: string }> = ({ hash }) => {
const childBlocks = useStateObservable(childBlocks$(hash))
return childBlocks.length ? (
<span className="inline-flex gap-2 align-middle">
{childBlocks.map((hash) => (
<BlockLink key={hash} hash={hash} />
))}
</span>
) : (
<span className="text-slate-400">N/A</span>
)
}
const BlockLink: FC<{ hash: string }> = ({ hash }) => {
const block = useStateObservable(blockInfoState$(hash))
if (!block) {
return <span className="align-middle">{hash.slice(0, 12)}…</span>
}
return (
<Link
className="text-polkadot/70 hover:text-polkadot align-middle inline-flex items-center gap-1 underline"
to={`../${hash}`}
>
{<BlockStatusIcon state={block.status} size={20} />}
{hash.slice(0, 12)}…
</Link>
)
}