Skip to content

Commit

Permalink
feat: loading spinner and streaming for assets table (#1625)
Browse files Browse the repository at this point in the history
* feat: loading spinner for assets table

* feat: streaming for assets table

* feat: update mock in test
  • Loading branch information
vacekj authored Aug 2, 2024
1 parent f983923 commit 2293007
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 21 deletions.
16 changes: 14 additions & 2 deletions apps/minifront/src/components/dashboard/assets-table/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { BalancesByAccount, groupByAccount, useBalancesResponses } from '../../.
import { AbridgedZQueryState } from '@penumbra-zone/zquery/src/types';
import { shouldDisplay } from '../../../fetchers/balances/should-display';
import { sortByPriorityScore } from '../../../fetchers/balances/by-priority-score';
import { Oval } from 'react-loader-spinner';

const getTradeLink = (balance: BalancesResponse): string => {
const metadata = getMetadataFromBalancesResponseOptional(balance);
Expand All @@ -40,7 +41,18 @@ export default function AssetsTable() {
shouldReselect: (before, after) => before?.data !== after.data,
});

if (balancesByAccount?.length === 0) {
/** Are assets still loading */
const isLoading = balancesByAccount === undefined;

if (isLoading) {
return (
<div className='mt-5 flex w-full flex-col items-center justify-center'>
<Oval width={32} height={32} color='white' secondaryColor='white' />
</div>
);
}

if (balancesByAccount.length === 0) {
return (
<div className='mt-5 flex flex-col items-center gap-6'>
<p>
Expand All @@ -57,7 +69,7 @@ export default function AssetsTable() {
return (
<div className='w-full overflow-x-auto'>
<Table>
{balancesByAccount?.map(account => (
{balancesByAccount.map(account => (
<Fragment key={account.account}>
<TableHeader className='group'>
<TableRow>
Expand Down
12 changes: 6 additions & 6 deletions apps/minifront/src/fetchers/balances/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ import { AssetId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/a
import { AddressIndex } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb.js';
import { viewClient } from '../../clients';

interface BalancesProps {
export interface BalancesProps {
accountFilter?: AddressIndex;
assetIdFilter?: AssetId;
}

export const getBalances = ({ accountFilter, assetIdFilter }: BalancesProps = {}): Promise<
BalancesResponse[]
> => {
export const getBalancesStream = ({
accountFilter,
assetIdFilter,
}: BalancesProps = {}): AsyncIterable<BalancesResponse> => {
const req = new BalancesRequest();
if (accountFilter) {
req.accountFilter = accountFilter;
Expand All @@ -22,6 +23,5 @@ export const getBalances = ({ accountFilter, assetIdFilter }: BalancesProps = {}
req.assetIdFilter = assetIdFilter;
}

const iterable = viewClient.balances(req);
return Array.fromAsync(iterable);
return viewClient.balances(req);
};
35 changes: 32 additions & 3 deletions apps/minifront/src/state/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import { ZQueryState, createZQuery } from '@penumbra-zone/zquery';
import { SliceCreator, useStore } from '.';
import { getStakingTokenMetadata } from '../fetchers/registry';
import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb.js';
import { getBalances } from '../fetchers/balances';
import { getBalancesStream } from '../fetchers/balances';
import { BalancesResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb.js';
import { getAllAssets } from '../fetchers/assets';
import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb.js';
import { getAddress, getAddressIndex } from '@penumbra-zone/getters/address-view';
import { AbridgedZQueryState } from '@penumbra-zone/zquery/src/types';
import { uint8ArrayToHex } from '@penumbra-zone/types/hex';

export const { stakingTokenMetadata, useStakingTokenMetadata } = createZQuery({
name: 'stakingTokenMetadata',
Expand All @@ -22,9 +23,37 @@ export const { stakingTokenMetadata, useStakingTokenMetadata } = createZQuery({
},
});

const getHash = (bal: BalancesResponse) => uint8ArrayToHex(bal.toBinary());

export const { balancesResponses, useBalancesResponses } = createZQuery({
name: 'balancesResponses',
fetch: getBalances,
fetch: getBalancesStream,
stream: () => {
const balanceResponseIdsToKeep = new Set<string>();

return {
onValue: (
prevState: BalancesResponse[] | undefined = [],
balanceResponse: BalancesResponse,
) => {
balanceResponseIdsToKeep.add(getHash(balanceResponse));

const existingIndex = prevState.findIndex(bal => getHash(bal) === getHash(balanceResponse));

// Update any existing items in place, rather than appending
// duplicates.
if (existingIndex >= 0) {
return prevState.toSpliced(existingIndex, 1, balanceResponse);
} else {
return [...prevState, balanceResponse];
}
},

onEnd: (prevState = []) =>
// Discard any balances from a previous stream.
prevState.filter(balanceResponse => balanceResponseIdsToKeep.has(getHash(balanceResponse))),
};
},
getUseStore: () => useStore,
get: state => state.shared.balancesResponses,
set: setter => {
Expand All @@ -50,7 +79,7 @@ export const { assets, useAssets } = createZQuery({

export interface SharedSlice {
assets: ZQueryState<Metadata[]>;
balancesResponses: ZQueryState<BalancesResponse[]>;
balancesResponses: ZQueryState<BalancesResponse[], Parameters<typeof getBalancesStream>>;
stakingTokenMetadata: ZQueryState<Metadata>;
}

Expand Down
21 changes: 11 additions & 10 deletions apps/minifront/src/state/staking/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@ vi.mock('../../fetchers/registry', async () => ({
}));

vi.mock('../../fetchers/balances', () => ({
getBalances: vi.fn(async () =>
Promise.resolve([
{
getBalancesStream: vi.fn(() => ({
[Symbol.asyncIterator]: async function* () {
await new Promise(resolve => setTimeout(resolve, 0));
yield {
balanceView: new ValueView({
valueView: {
case: 'knownAssetId',
Expand All @@ -101,8 +102,8 @@ vi.mock('../../fetchers/balances', () => ({
},
},
}),
},
{
};
yield {
balanceView: new ValueView({
valueView: {
case: 'knownAssetId',
Expand All @@ -129,8 +130,8 @@ vi.mock('../../fetchers/balances', () => ({
},
},
}),
},
{
};
yield {
balanceView: new ValueView({
valueView: {
case: 'knownAssetId',
Expand All @@ -153,9 +154,9 @@ vi.mock('../../fetchers/balances', () => ({
},
},
}),
},
]),
),
};
},
})),
}));

const mockViewClient = vi.hoisted(() => ({
Expand Down

0 comments on commit 2293007

Please sign in to comment.