Skip to content

Commit

Permalink
Mobx spike (#77)
Browse files Browse the repository at this point in the history
* feat: sync popover

* fix: sync bar

* refactor: change `useConnect` to use signals

* feat: change state manager to Zustand

* refactor: remove signals lib usage

* Mobx spike

* Post review updates

---------

Co-authored-by: Max Korsunov <[email protected]>
  • Loading branch information
grod220 and VanishMax authored Oct 1, 2024
1 parent 7374675 commit 5c6ae36
Show file tree
Hide file tree
Showing 21 changed files with 406 additions and 172 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@
"@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",
"@radix-ui/react-icons": "^1.3.0",
"@rehooks/component-size": "^1.0.3",
Expand All @@ -56,6 +56,8 @@
"grpc_tools_node_protoc_ts": "^5.3.3",
"lodash": "^4.17.21",
"lucide-react": "^0.445.0",
"mobx": "^6.13.3",
"mobx-react-lite": "^4.0.7",
"next": "^14.2.7",
"pg": "^8.12.0",
"react": "^18.3.1",
Expand Down
146 changes: 89 additions & 57 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion src/app/v2/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,23 @@ 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';
import { enableStaticRendering } from 'mobx-react-lite';

// Used so that observer() won't subscribe to any observables used in an SSR environment
// and no garbage collection problems are introduced.
enableStaticRendering(typeof window === 'undefined');

const V2Layout = ({ children }: { children: ReactNode }) => {
return (
<PenumbraUIProvider>
<Display>
<SyncBar />
<Header />
{children}
</Display>
</PenumbraUIProvider>
)
);
};

export default V2Layout;
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
19 changes: 10 additions & 9 deletions src/components/header/connection.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { Button } from '@penumbra-zone/ui/Button';
import { useConnect } from '@/utils/penumbra/useConnect.ts';
import { ProviderPopover } from './provider-popover.tsx';
import { ProviderPopover } from './provider-popover';
import { connectionStore } from '@/state/connection';
import { observer } from 'mobx-react-lite';

export const Connection = () => {
const { connected, connect } = useConnect();

if (!connected) {
export const Connection = observer(() => {
if (!connectionStore.connected) {
return (
<Button actionType='accent' onClick={() => void connect()}>Connect</Button>
<Button actionType='accent' onClick={() => void connectionStore.connect()}>
Connect
</Button>
);
}

return <ProviderPopover />
};
return <ProviderPopover />;
});
4 changes: 2 additions & 2 deletions src/components/header/desktop-nav.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { useRouter } from 'next/navigation';
import { Tabs } from '@penumbra-zone/ui/Tabs';
import { Density } from '@penumbra-zone/ui/Density';
import { HEADER_LINKS } from './links.ts';
import { usePagePath } from '@/utils/routes/usePagePath.ts';
import { HEADER_LINKS } from './links';
import { usePagePath } from '@/utils/routes/usePagePath';

export const DesktopNav = () => {
const pagePath = usePagePath();
Expand Down
12 changes: 6 additions & 6 deletions src/components/header/index.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Density } from '@penumbra-zone/ui/Density';
import { HeaderLogo } from './logo.tsx';
// import { StatusPopover } from './status-popover.tsx';
import { MobileNav } from './mobile-nav.tsx';
import { DesktopNav } from './desktop-nav.tsx';
import { Connection } from './connection.tsx';
import { HeaderLogo } from './logo';
import { StatusPopover } from './status-popover';
import { MobileNav } from './mobile-nav';
import { DesktopNav } from './desktop-nav';
import { Connection } from './connection';

export const Header = () => {
return (
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
2 changes: 1 addition & 1 deletion src/components/header/logo.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Link from 'next/link'
import { PagePath } from '@/utils/routes/pages.ts';
import { PagePath } from '@/utils/routes/pages';
import PenumbraLogo from './logo.svg';

export const HeaderLogo = () => {
Expand Down
10 changes: 5 additions & 5 deletions src/components/header/mobile-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ 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 { HeaderLogo } from './logo.tsx';
import { HEADER_LINKS } from './links.ts';
import { Connection } from './connection.tsx';
import { StatusPopover } from './status-popover';
import { HeaderLogo } from './logo';
import { HEADER_LINKS } from './links';
import { Connection } from './connection';

export const MobileNav = () => {
const router = useRouter();
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
13 changes: 7 additions & 6 deletions src/components/header/provider-popover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import Image from 'next/image';
import { Popover } from '@penumbra-zone/ui/Popover';
import { Button } from '@penumbra-zone/ui/Button';
import { Text } from '@penumbra-zone/ui/Text';
import { useConnect } from '@/utils/penumbra/useConnect.ts';
import { connectionStore } from '@/state/connection';
import { observer } from 'mobx-react-lite';

export const ProviderPopover = () => {
const { disconnect, manifest } = useConnect();
export const ProviderPopover = observer(() => {
const { manifest } = connectionStore;

const name = manifest?.['name'] as string;
const version = manifest?.['version'] as string;
Expand All @@ -20,7 +21,7 @@ export const ProviderPopover = () => {
<Image width={16} height={16} src={URL.createObjectURL(blob)} alt={name} className='size-4' />
);
return () => element;
}, [name, manifest]);
}, [manifest, name]);

return (
<Popover>
Expand All @@ -41,11 +42,11 @@ export const ProviderPopover = () => {
<Text body>Loading provider manifest...</Text>
)}
<div className='mt-4'>
<Button icon={Link2Off} onClick={() => void disconnect()}>
<Button icon={Link2Off} onClick={() => void connectionStore.disconnect()}>
Disconnect
</Button>
</div>
</Popover.Content>
</Popover>
);
};
});
66 changes: 66 additions & 0 deletions src/components/header/status-popover.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useMemo } from 'react';
import { Blocks } from 'lucide-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 { statusStore } from '@/state/status';
import { connectionStore } from '@/state/connection';
import { observer } from 'mobx-react-lite';

export const StatusPopover = observer(() => {
const { loading, error, syncing, fullSyncHeight, latestKnownBlockHeight } = statusStore;

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

if (loading) {
return null;
}

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

return <Pill context='technical-caution'>Block Syncing</Pill>;
}, [error, loading, syncing]);

if (!connectionStore.connected) {
return null;
}

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}
{error}
</div>
{!loading && (
<div className='flex flex-col gap-2'>
<Text technical>Block Height</Text>
<Pill context='technical-default'>
{latestKnownBlockHeight !== fullSyncHeight
? `${fullSyncHeight} of ${latestKnownBlockHeight}`
: `${latestKnownBlockHeight}`}
</Pill>
</div>
)}
</div>
</Density>
</Popover.Content>
</Popover>
);
});
17 changes: 17 additions & 0 deletions src/components/header/sync-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { Progress } from '@penumbra-zone/ui/Progress';
import { statusStore } from '@/state/status';
import { observer } from 'mobx-react-lite';

export const SyncBar = observer(() => {
const { loading, error, syncPercent, updating } = statusStore;

return (
<div className='fixed left-0 top-0 h-1 w-full'>
{loading ? (
<Progress value={0} loading error={Boolean(error)} />
) : (
<Progress value={syncPercent} loading={updating} error={Boolean(error)} />
)}
</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
82 changes: 82 additions & 0 deletions src/state/connection/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { PenumbraRequestFailure, PenumbraState, PenumbraManifest } from '@penumbra-zone/client';
import { penumbra, PRAX_ORIGIN } from '@/utils/penumbra';
import { makeAutoObservable } from 'mobx';

class ConnectionStateStore {
connected = false;
manifest: PenumbraManifest | undefined;

constructor() {
makeAutoObservable(this);

if (typeof window !== 'undefined') {
this.setup();
}
}

private setManifest(manifest: PenumbraManifest | undefined) {
this.manifest = manifest;
}

private setConnected(connected: boolean) {
this.connected = connected;
}

async reconnect() {
await penumbra.attach(PRAX_ORIGIN);
if (!penumbra.connected) {
return;
}

try {
await penumbra.connect();
this.setConnected(true);
} catch (error) {
/* no-op */
}
}

async connect() {
try {
await penumbra.connect();
} catch (error) {
if (error instanceof Error && error.cause) {
if (error.cause === PenumbraRequestFailure.Denied) {
// TODO: replace these alerts with toasts
alert(
'Connection denied: you may need to un-ignore this site in your extension settings.',
);
}
if (error.cause === PenumbraRequestFailure.NeedsLogin) {
alert('Not logged in: please login into the extension and try again');
}
}
}
}

async disconnect() {
if (!penumbra.connected) {
return;
}

try {
await penumbra.disconnect();
} catch (error) {
console.error(error);
}
}

setup() {
this.setManifest(penumbra.manifest);

// If Prax is connected on page load, reconnect to ensure the connection is still active
void this.reconnect();

penumbra.onConnectionStateChange(event => {
this.setManifest(penumbra.manifest);
this.setConnected(event.state === PenumbraState.Connected);
});
}
}

export const connectionStore = new ConnectionStateStore();
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}%`,
}
};
Loading

0 comments on commit 5c6ae36

Please sign in to comment.