Bags overview (#7257)

This commit is contained in:
Jaco 2022-03-28 19:22:34 +02:00 committed by GitHub
parent 4ff27ffd49
commit 963f335336
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 283 additions and 0 deletions

View 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>
);
}

View 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>
);
}

View 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>
</>
);
}

View 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;
}

View 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);

View 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);

View 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);

View 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);

View File

@ -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}