Add own stash jumps (#7259)

This commit is contained in:
Jaco 2022-03-29 10:36:31 +02:00 committed by GitHub
parent 2c74cd3d70
commit 3ead92db53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 150 additions and 23 deletions

View File

@ -5,12 +5,14 @@ import type { u64 } from '@polkadot/types';
import type { PalletBagsListListBag } from '@polkadot/types/lookup';
import type { StashNode } from './types';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { AddressMini } from '@polkadot/react-components';
import { AddressMini, Spinner } from '@polkadot/react-components';
import { formatNumber } from '@polkadot/util';
// import useBagEntries from './useBagEntries';
import Stash from './Stash';
import useBagEntries from './useBagEntries';
import useBonded from './useBonded';
interface Props {
id: u64;
@ -18,8 +20,22 @@ interface Props {
stashNodes?: StashNode[];
}
export default function Bag ({ id, info, stashNodes = [] }: Props): React.ReactElement<Props> {
// const entries = useBagEntries(stashNodes.length ? info.head.unwrapOr(null) : null);
const NO_NODES: StashNode[] = [];
export default function Bag ({ id, info, stashNodes = NO_NODES }: Props): React.ReactElement<Props> {
const [isLoading, setLoading] = useState(true);
const { isCompleted, list } = useBagEntries(stashNodes.length ? info.head.unwrapOr(null) : null);
const bonded = useBonded(isCompleted && list);
useEffect((): void => {
setLoading(
stashNodes.length
? isCompleted
? !bonded
: true
: false
);
}, [bonded, isCompleted, list, stashNodes]);
return (
<tr>
@ -28,13 +44,21 @@ export default function Bag ({ id, info, stashNodes = [] }: Props): React.ReactE
<td className='address'>{info.tail.isSome && <AddressMini value={info.tail} />}</td>
<td className='address'>
{stashNodes?.map(({ stashId }) => (
<AddressMini
<Stash
key={stashId}
value={stashId}
withBonded
list={bonded}
stashId={stashId}
/>
))}
</td>
<td className='number'>
{isLoading
? <Spinner noLabel />
: list.length
? formatNumber(list.length)
: null
}
</td>
</tr>
);
}

View File

@ -0,0 +1,47 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ListNode } from './types';
import React, { useMemo } from 'react';
import { AddressMini, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
interface Props {
list?: ListNode[];
stashId: string;
}
function findEntry (stashId: string, list: ListNode[] = []): ListNode | null {
return list.find((o) => o.stashId === stashId) || null;
}
export default function Stash ({ list, stashId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const stashInfo = useMemo(
() => findEntry(stashId, list),
[list, stashId]
);
return (
<div>
<AddressMini
value={stashId}
withBonded
/>
{stashInfo && stashInfo.jump && (
<TxButton
accountId={stashInfo?.stashId}
icon='caret-up'
label={t<string>('Jump')}
params={[stashInfo?.jump]}
tx={api.tx.bagsList.putInFrontOf}
/>
)}
</div>
);
}

View File

@ -23,7 +23,7 @@ export default function Summary ({ className = '', ids }: Props): React.ReactEle
<CardSummary label={t<string>('total bags')}>
{ids
? formatNumber(ids.length)
: <Spinner />
: <Spinner noLabel />
}
</CardSummary>
</SummaryBox>

View File

@ -34,7 +34,8 @@ export default function Bags ({ ownStashes }: Props): React.ReactElement<Props>
[t('bags')],
[t('head'), 'address'],
[t('tail'), 'address'],
[t('mine'), 'address']
[t('mine'), 'address'],
[]
]);
const sorted = useMemo(

View File

@ -2,8 +2,16 @@
// SPDX-License-Identifier: Apache-2.0
import type { PalletBagsListListNode } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
export interface StashNode {
stashId: string;
node: PalletBagsListListNode;
}
export interface ListNode {
bonded: BN;
index: number;
jump: string | null;
stashId: string;
}

View File

@ -9,19 +9,24 @@ import { useEffect, useState } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
function useBagEntriesImpl (headId: AccountId32 | null): AccountId32[] {
interface Result {
isCompleted: boolean;
list: AccountId32[];
}
const EMPTY: [AccountId32 | null, Result] = [null, { isCompleted: false, list: [] }];
function useBagEntriesImpl (headId: AccountId32 | null): Result {
const { api } = useApi();
const [[currId, entries], setCurrent] = useState<[AccountId32 | null, AccountId32[]]>([null, []]);
const [[currId, result], setCurrent] = useState<[AccountId32 | null, Result]>(EMPTY);
const node = useCall<Option<PalletBagsListListNode>>(!!currId && api.query.bagsList.listNodes, [currId]);
useEffect(
(): void => {
setCurrent(
headId
? [headId, [headId]]
: [null, []]
);
},
() => setCurrent(
headId
? [headId, { isCompleted: false, list: [headId] }]
: [null, { isCompleted: true, list: [] }]
),
[headId]
);
@ -32,12 +37,14 @@ function useBagEntriesImpl (headId: AccountId32 | null): AccountId32[] {
if (next.isSome) {
const currId = next.unwrap();
setCurrent(([, entries]) => [currId, [...entries, currId]]);
setCurrent(([, { list }]) => [currId, { isCompleted: false, list: [...list, currId] }]);
} else {
setCurrent(([currId, { list }]) => [currId, { isCompleted: true, list }]);
}
}
}, [node]);
return entries;
return result;
}
export default createNamedHook('useBagEntries', useBagEntriesImpl);

View File

@ -12,8 +12,8 @@ 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()]),
.filter(([, o]) => o.isSome)
.map(([id, o]): Result => [id.toString(), id, o.unwrap()]),
withParamsTransform: true
};

View File

@ -0,0 +1,40 @@
// Copyright 2017-2022 @polkadot/app-staking authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveStakingAccount } from '@polkadot/api-derive/types';
import type { AccountId32 } from '@polkadot/types/interfaces';
import type { ListNode } from './types';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
const deriveOpts = {
transform: (all: DeriveStakingAccount[]): ListNode[] => {
const infos = all.map(({ stakingLedger, stashId }, index): ListNode => ({
bonded: stakingLedger.active.unwrap(),
index,
jump: null,
stashId: stashId.toString()
}));
return infos.map((info) => {
const lower = infos.find(({ bonded, index }) =>
index < info.index &&
bonded.lt(info.bonded)
);
if (lower) {
info.jump = lower.stashId;
}
return info;
});
}
};
function useBondedImpl (ids?: false | AccountId32[]): ListNode[] | undefined {
const { api } = useApi();
return useCall(ids && ids.length !== 0 && api.derive.staking.accounts, [ids], deriveOpts);
}
export default createNamedHook('useBonded', useBondedImpl);