Skip to content

Commit

Permalink
feat: implement sync status popover
Browse files Browse the repository at this point in the history
  • Loading branch information
VanishMax committed Sep 25, 2024
1 parent 93e0e53 commit 5ce235e
Show file tree
Hide file tree
Showing 15 changed files with 458 additions and 76 deletions.
8 changes: 8 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

const nextConfig = {
transpilePackages: ["@penumbra-zone/protobuf"],
experimental: {
swcPlugins: [
[
"@preact-signals/safe-react/swc",
{ mode: "auto" }
],
],
},
webpack: (config) => {
config.module.rules.push({
test: /\.svg$/i,
Expand Down
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
"@emotion/styled": "^11.13.0",
"@grpc/proto-loader": "^0.7.13",
"@penumbra-labs/registry": "^11.3.1",
"@penumbra-zone/client": "^18.1.0",
"@penumbra-zone/protobuf": "^6.0.0",
"@penumbra-zone/client": "^19.0.0",
"@penumbra-zone/protobuf": "^6.1.0",
"@penumbra-zone/transport-dom": "^7.5.0",
"@penumbra-zone/ui": "^9.0.0",
"@penumbra-zone/ui": "^10.0.0",
"@penumbra-zone/wasm": "^26.2.0",
"@preact-signals/safe-react": "^0.7.0",
"@preact/signals-react": "^2.2.0",
"@radix-ui/react-icons": "^1.3.0",
"@rehooks/component-size": "^1.0.3",
"@styled-icons/octicons": "^10.47.0",
Expand Down
326 changes: 260 additions & 66 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/app/v2/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import { ReactNode } from 'react';
import { PenumbraUIProvider } from '@penumbra-zone/ui/PenumbraUIProvider';
import { Display } from '@penumbra-zone/ui/Display';
import { Header } from '@/components/header';
import { SyncBar } from '@/components/header/sync-bar.tsx';

const V2Layout = ({ children }: { children: ReactNode }) => {
return (
<PenumbraUIProvider>
<Display>
<SyncBar />
<Header />
{children}
</Display>
Expand Down
2 changes: 1 addition & 1 deletion src/components/copiedTx.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import React, { FC, useState } from "react";
import { CopyIcon } from "@radix-ui/react-icons";
import { HStack } from "@chakra-ui/react";
import { Constants } from "@/constants/configConstants";
import { Constants } from "@/utils/configConstants.ts";

interface CopyTxToClipboardProps {
txHash: string;
Expand Down
4 changes: 2 additions & 2 deletions src/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Density } from '@penumbra-zone/ui/Density';
import { HeaderLogo } from './logo.tsx';
// import { StatusPopover } from './status-popover.tsx';
import { StatusPopover } from './status-popover.tsx';
import { MobileNav } from './mobile-nav.tsx';
import { DesktopNav } from './desktop-nav.tsx';
import { Connection } from './connection.tsx';
Expand All @@ -14,7 +14,7 @@ export const Header = () => {

<Density compact>
<div className='hidden gap-2 lg:flex'>
{/* <StatusPopover />*/}
<StatusPopover />
<Connection />
</div>
<div className='block lg:hidden'>
Expand Down
4 changes: 2 additions & 2 deletions src/components/header/mobile-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Button } from '@penumbra-zone/ui/Button';
import { Dialog } from '@penumbra-zone/ui/Dialog';
import { Display } from '@penumbra-zone/ui/Display';
import { MenuItem } from '@penumbra-zone/ui/MenuItem';
// import { StatusPopover } from './status-popover.tsx';
import { StatusPopover } from './status-popover.tsx';
import { HeaderLogo } from './logo.tsx';
import { HEADER_LINKS } from './links.ts';
import { Connection } from './connection.tsx';
Expand All @@ -31,7 +31,7 @@ export const MobileNav = () => {
<HeaderLogo />

<div className='flex gap-2'>
{/* <StatusPopover />*/}
<StatusPopover />
<Connection />
<Button iconOnly icon={X} onClick={() => setIsOpen(false)}>
Close
Expand Down
68 changes: 68 additions & 0 deletions src/components/header/status-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useEffect } from 'react';
import { Blocks } from 'lucide-react';
import { useComputed } from '@preact-signals/safe-react';
import { Popover } from '@penumbra-zone/ui/Popover';
import { Button } from '@penumbra-zone/ui/Button';
import { Density } from '@penumbra-zone/ui/Density';
import { Pill } from '@penumbra-zone/ui/Pill';
import { Text } from '@penumbra-zone/ui/Text';
import { penumbraStatus, penumbraStatusError, streamPenumbraStatus } from '@/state/status';
import { useConnect } from '@/utils/penumbra/useConnect.ts';

export const StatusPopover = () => {
const { connected } = useConnect();

// a ReactNode displaying the sync status in form of a pill
const pill = useComputed(() => {
if (penumbraStatusError.value) {
return <Pill context='technical-destructive'>Block Sync Error</Pill>;
}

if (!penumbraStatus.value) {
return null;
}

if (!penumbraStatus.value.syncing) {
return <Pill context='technical-success'>Blocks Synced</Pill>;
}

return <Pill context='technical-caution'>Block Syncing</Pill>;
});

useEffect(() => {
if (connected) {
void streamPenumbraStatus();
}
}, [connected]);

return (
<Popover>
<Popover.Trigger>
<Button icon={Blocks} iconOnly>
Status
</Button>
</Popover.Trigger>
<Popover.Content align='end' side='bottom'>
<Density compact>
<div className='flex flex-col gap-4'>
<div className='flex flex-col gap-2'>
<Text technical>Status</Text>
{pill.value}
{penumbraStatusError.value}
</div>
{penumbraStatus.value && (
<div className='flex flex-col gap-2'>
<Text technical>Block Height</Text>
<Pill context='technical-default'>
{penumbraStatus.value.latestKnownBlockHeight !== penumbraStatus.value.fullSyncHeight
? `${penumbraStatus.value.fullSyncHeight} of ${penumbraStatus.value.latestKnownBlockHeight}`
: `${penumbraStatus.value.latestKnownBlockHeight}`}
</Pill>
</div>
)}
</div>
</Density>
</Popover.Content>
</Popover>
);
};
18 changes: 18 additions & 0 deletions src/components/header/sync-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Progress } from '@penumbra-zone/ui/Progress';
import { penumbraStatus, penumbraStatusError } from '@/state/status';

export const SyncBar = () => {
return (
<div className='fixed left-0 top-0 h-1 w-full'>
{!penumbraStatus.value ? (
<Progress value={0} loading />
) : (
<Progress
value={penumbraStatus.value.syncPercent ?? 0}

Check failure on line 11 in src/components/header/sync-bar.tsx

View workflow job for this annotation

GitHub Actions / Lint

Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined
loading={penumbraStatus.value.updating}
error={Boolean(penumbraStatusError.value)}
/>
)}
</div>
);
};
2 changes: 1 addition & 1 deletion src/pages/block/[block_height].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { BlockDetailedSummaryData } from "@/utils/types/block";
import { BlockInfo, LiquidityPositionEvent } from "@/utils/indexer/types/lps";
import { SwapExecutionWithBlockHeight } from "@/utils/protos/types/DexQueryServiceClientInterface";
import { LoadingSpinner } from "@/components/util/loadingSpinner";
import { Constants } from "@/constants/configConstants";
import { Constants } from "@/utils/configConstants.ts";
import { formatTimestampShort } from "@/components/blockTimestamp";
import { AddIcon, MinusIcon } from "@chakra-ui/icons";
import { innerToBech32Address } from "@/utils/math/bech32";
Expand Down
20 changes: 20 additions & 0 deletions src/state/status/getSyncPercent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const getSyncPercent = (
fullSyncHeight: bigint,
latestKnownBlockHeight: bigint,
): { syncPercent: number, syncPercentStringified: string } => {
let percentSyncedNumber = 0;
if (latestKnownBlockHeight) {
percentSyncedNumber = Number(fullSyncHeight) / Number(latestKnownBlockHeight);
if (percentSyncedNumber > 1) {
percentSyncedNumber = 1;
}
}

// Round down to ensure whole numbers
const roundedPercentSyncedNumber = Math.floor(percentSyncedNumber * 100);

return {
syncPercent: roundedPercentSyncedNumber / 100,
syncPercentStringified: `${roundedPercentSyncedNumber}%`,
}
};
65 changes: 65 additions & 0 deletions src/state/status/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { signal } from '@preact-signals/safe-react';
import { ViewService } from '@penumbra-zone/protobuf';
import { penumbra } from '@/utils/penumbra/penumbra';
import { getSyncPercent } from '@/state/status/getSyncPercent';

interface StatusState {
/** Indicates that the account needs syncing with the blockchain */
syncing: boolean;
/** Indicates that the account is almost in sync with the blockchain (amount of unsynced blocks is less than 10) */
updating?: boolean;
/** The amount of synced blocks */
fullSyncHeight: bigint;
/** The total amount of blocks in the blockchain */
latestKnownBlockHeight?: bigint;
/** A number between 0 and 1 indicating the sync progress */
syncPercent: number;
/** A stringified sync percentage, e.g. '100%' or '17%' */
syncPercentStringified: string;
}

export const penumbraStatus = signal<StatusState>();
export const penumbraStatusError = signal<string>();

/**
* Initial status request doesn't return `latestKnownBlockHeight`.
* Instead, it returns `catchingUp` to know if the sync is in progress
*/
const getPenumbraStatus = async (): Promise<StatusState> => {
const status = await penumbra.service(ViewService).status({});
return {
syncing: status.catchingUp,
fullSyncHeight: status.fullSyncHeight,
latestKnownBlockHeight: status.catchingUp ? undefined : status.fullSyncHeight,
syncPercent: status.catchingUp ? 0 : 1,
syncPercentStringified: status.catchingUp ? '0%' : '100%',
};
};

/**
* Receives the status stream from the view service
* and stores it in the `penumbraStatus` signal.
*/
export const streamPenumbraStatus = async () => {
try {
penumbraStatus.value = await getPenumbraStatus();
penumbraStatusError.value = undefined;

const stream = penumbra.service(ViewService).statusStream({});
for await (const status of stream) {
const syncPercents = getSyncPercent(status.fullSyncHeight, status.latestKnownBlockHeight);

penumbraStatusError.value = undefined;
penumbraStatus.value = {
syncing: status.fullSyncHeight !== status.latestKnownBlockHeight,
fullSyncHeight: status.fullSyncHeight,
latestKnownBlockHeight: status.latestKnownBlockHeight,
...syncPercents,
};
}
} catch (error) {
penumbraStatus.value = undefined;
penumbraStatusError.value = error instanceof Error ? `${error.name}: ${error.message}` : 'Streaming error';
setTimeout(() => void streamPenumbraStatus(), 1000);
}
};
File renamed without changes.
2 changes: 1 addition & 1 deletion src/utils/token/tokenFetch.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { uint8ArrayToBase64, base64ToUint8Array } from "../math/base64";
import { Constants } from "../../constants/configConstants";
import { Constants } from "../configConstants.ts";
import {
AssetId,
AssetImage,
Expand Down
5 changes: 5 additions & 0 deletions styles/v2.css
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,8 @@ a {
* {
box-sizing: border-box;
}

body > section {
position: relative;
z-index: 0;
}

0 comments on commit 5ce235e

Please sign in to comment.