Bags overview (#7257)
This commit is contained in:
parent
4ff27ffd49
commit
963f335336
40
packages/page-staking/src/Bags/Bag.tsx
Normal file
40
packages/page-staking/src/Bags/Bag.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u64 } from '@polkadot/types';
|
||||
import type { PalletBagsListListBag } from '@polkadot/types/lookup';
|
||||
import type { StashNode } from './types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AddressMini } from '@polkadot/react-components';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
// import useBagEntries from './useBagEntries';
|
||||
|
||||
interface Props {
|
||||
id: u64;
|
||||
info: PalletBagsListListBag;
|
||||
stashNodes?: StashNode[];
|
||||
}
|
||||
|
||||
export default function Bag ({ id, info, stashNodes = [] }: Props): React.ReactElement<Props> {
|
||||
// const entries = useBagEntries(stashNodes.length ? info.head.unwrapOr(null) : null);
|
||||
|
||||
return (
|
||||
<tr>
|
||||
<td className='number'>{formatNumber(id)}</td>
|
||||
<td className='address'>{info.head.isSome && <AddressMini value={info.head} />}</td>
|
||||
<td className='address'>{info.tail.isSome && <AddressMini value={info.tail} />}</td>
|
||||
<td className='address'>
|
||||
{stashNodes?.map(({ stashId }) => (
|
||||
<AddressMini
|
||||
key={stashId}
|
||||
value={stashId}
|
||||
withBonded
|
||||
/>
|
||||
))}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
31
packages/page-staking/src/Bags/Summary.tsx
Normal file
31
packages/page-staking/src/Bags/Summary.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { u64 } from '@polkadot/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { CardSummary, Spinner, SummaryBox } from '@polkadot/react-components';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
ids?: u64[];
|
||||
}
|
||||
|
||||
export default function Summary ({ className = '', ids }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<SummaryBox className={className}>
|
||||
<CardSummary label={t<string>('total bags')}>
|
||||
{ids
|
||||
? formatNumber(ids.length)
|
||||
: <Spinner />
|
||||
}
|
||||
</CardSummary>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
74
packages/page-staking/src/Bags/index.tsx
Normal file
74
packages/page-staking/src/Bags/index.tsx
Normal file
@ -0,0 +1,74 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { StakerState } from '@polkadot/react-hooks/types';
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
|
||||
import { Table } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import Bag from './Bag';
|
||||
import Summary from './Summary';
|
||||
import useBagsIds from './useBagsIds';
|
||||
import useBagsList from './useBagsList';
|
||||
import useBagsNodes from './useBagsNodes';
|
||||
|
||||
interface Props {
|
||||
ownStashes?: StakerState[];
|
||||
}
|
||||
|
||||
export default function Bags ({ ownStashes }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const ids = useBagsIds();
|
||||
const list = useBagsList(ids);
|
||||
const stashIds = useMemo(
|
||||
() => ownStashes
|
||||
? ownStashes.map(({ stashId }) => stashId)
|
||||
: [],
|
||||
[ownStashes]
|
||||
);
|
||||
const nodes = useBagsNodes(stashIds);
|
||||
|
||||
const headerRef = useRef([
|
||||
[t('bags')],
|
||||
[t('head'), 'address'],
|
||||
[t('tail'), 'address'],
|
||||
[t('mine'), 'address']
|
||||
]);
|
||||
|
||||
const sorted = useMemo(
|
||||
() => list
|
||||
? [...list].sort((a, b) =>
|
||||
nodes[a[0]]
|
||||
? nodes[b[0]]
|
||||
? b[1].cmp(a[1])
|
||||
: -1
|
||||
: nodes[b[0]]
|
||||
? 1
|
||||
: b[1].cmp(a[1])
|
||||
)
|
||||
: null,
|
||||
[list, nodes]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Summary ids={ids} />
|
||||
<Table
|
||||
empty={list && list.length === 0 && t<string>('No available bags')}
|
||||
emptySpinner={t<string>('Retrieving all available bags, this will take some time')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{sorted && sorted.map(([key, id, info]) => (
|
||||
<Bag
|
||||
id={id}
|
||||
info={info}
|
||||
key={key}
|
||||
stashNodes={nodes[key]}
|
||||
/>
|
||||
))}
|
||||
</Table>
|
||||
</>
|
||||
);
|
||||
}
|
||||
9
packages/page-staking/src/Bags/types.ts
Normal file
9
packages/page-staking/src/Bags/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PalletBagsListListNode } from '@polkadot/types/lookup';
|
||||
|
||||
export interface StashNode {
|
||||
stashId: string;
|
||||
node: PalletBagsListListNode;
|
||||
}
|
||||
43
packages/page-staking/src/Bags/useBagEntries.tsx
Normal file
43
packages/page-staking/src/Bags/useBagEntries.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { AccountId32 } from '@polkadot/types/interfaces';
|
||||
import type { PalletBagsListListNode } from '@polkadot/types/lookup';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
function useBagEntriesImpl (headId: AccountId32 | null): AccountId32[] {
|
||||
const { api } = useApi();
|
||||
const [[currId, entries], setCurrent] = useState<[AccountId32 | null, AccountId32[]]>([null, []]);
|
||||
const node = useCall<Option<PalletBagsListListNode>>(!!currId && api.query.bagsList.listNodes, [currId]);
|
||||
|
||||
useEffect(
|
||||
(): void => {
|
||||
setCurrent(
|
||||
headId
|
||||
? [headId, [headId]]
|
||||
: [null, []]
|
||||
);
|
||||
},
|
||||
[headId]
|
||||
);
|
||||
|
||||
useEffect((): void => {
|
||||
if (node && node.isSome) {
|
||||
const { next } = node.unwrap();
|
||||
|
||||
if (next.isSome) {
|
||||
const currId = next.unwrap();
|
||||
|
||||
setCurrent(([, entries]) => [currId, [...entries, currId]]);
|
||||
}
|
||||
}
|
||||
}, [node]);
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
export default createNamedHook('useBagEntries', useBagEntriesImpl);
|
||||
19
packages/page-staking/src/Bags/useBagsIds.ts
Normal file
19
packages/page-staking/src/Bags/useBagsIds.ts
Normal file
@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { StorageKey, u64 } from '@polkadot/types';
|
||||
|
||||
import { createNamedHook, useApi, useMapKeys } from '@polkadot/react-hooks';
|
||||
|
||||
const keyOptions = {
|
||||
transform: (keys: StorageKey<[u64]>[]): u64[] =>
|
||||
keys.map(({ args: [id] }) => id)
|
||||
};
|
||||
|
||||
function useBagsIdsImpl (): u64[] | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
return useMapKeys(api.query.bagsList.listBags, keyOptions);
|
||||
}
|
||||
|
||||
export default createNamedHook('useBagsIds', useBagsIdsImpl);
|
||||
26
packages/page-staking/src/Bags/useBagsList.ts
Normal file
26
packages/page-staking/src/Bags/useBagsList.ts
Normal file
@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option, u64 } from '@polkadot/types';
|
||||
import type { PalletBagsListListBag } from '@polkadot/types/lookup';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
type Result = [string, u64, PalletBagsListListBag];
|
||||
|
||||
const multiOptions = {
|
||||
transform: ([[ids], opts]: [[u64[]], Option<PalletBagsListListBag>[]]): Result[] =>
|
||||
ids
|
||||
.map((id, index): [u64, Option<PalletBagsListListBag>] => [id, opts[index]])
|
||||
.filter(([, opt]) => opt.isSome)
|
||||
.map(([id, opt]): Result => [id.toString(), id, opt.unwrap()]),
|
||||
withParamsTransform: true
|
||||
};
|
||||
|
||||
function useBagsListImpl (ids?: u64[]): Result[] | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
return useCall(ids && ids.length !== 0 && api.query.bagsList.listBags.multi, [ids], multiOptions);
|
||||
}
|
||||
|
||||
export default createNamedHook('useBagsList', useBagsListImpl);
|
||||
37
packages/page-staking/src/Bags/useBagsNodes.tsx
Normal file
37
packages/page-staking/src/Bags/useBagsNodes.tsx
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletBagsListListNode } from '@polkadot/types/lookup';
|
||||
import type { StashNode } from './types';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
type Result = Record<string, StashNode[]>;
|
||||
|
||||
const multiOptions = {
|
||||
defaultValue: {} as Result,
|
||||
transform: (opts: Option<PalletBagsListListNode>[]): Result =>
|
||||
opts
|
||||
.filter((o) => o.isSome)
|
||||
.map((o): PalletBagsListListNode => o.unwrap())
|
||||
.reduce((all: Result, node): Result => {
|
||||
const id = node.bagUpper.toString();
|
||||
|
||||
if (!all[id]) {
|
||||
all[id] = [];
|
||||
}
|
||||
|
||||
all[id].push({ node, stashId: node.id.toString() });
|
||||
|
||||
return all;
|
||||
}, {})
|
||||
};
|
||||
|
||||
function useBagsNodesImpl (stashIds: string[]): Result {
|
||||
const { api } = useApi();
|
||||
|
||||
return useCall(stashIds && stashIds.length !== 0 && api.query.bagsList.listNodes.multi, [stashIds], multiOptions) as Result;
|
||||
}
|
||||
|
||||
export default createNamedHook('useBagsNodes', useBagsNodesImpl);
|
||||
@ -19,6 +19,7 @@ import basicMd from './md/basic.md';
|
||||
import Summary from './Overview/Summary';
|
||||
import Actions from './Actions';
|
||||
import ActionsBanner from './ActionsBanner';
|
||||
import Bags from './Bags';
|
||||
import { STORE_FAVS_BASE } from './constants';
|
||||
import Overview from './Overview';
|
||||
import Payouts from './Payouts';
|
||||
@ -129,6 +130,9 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
targets={targets}
|
||||
/>
|
||||
<Switch>
|
||||
<Route path={`${basePath}/bags`}>
|
||||
<Bags ownStashes={ownStashes} />
|
||||
</Route>
|
||||
<Route path={`${basePath}/payout`}>
|
||||
<Payouts
|
||||
isInElection={isInElection}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user