From 3ead92db53e5f9cfea0554449fe55256d25e8189 Mon Sep 17 00:00:00 2001 From: Jaco Date: Tue, 29 Mar 2022 10:36:31 +0200 Subject: [PATCH] Add own stash jumps (#7259) --- packages/page-staking/src/Bags/Bag.tsx | 40 ++++++++++++---- packages/page-staking/src/Bags/Stash.tsx | 47 +++++++++++++++++++ packages/page-staking/src/Bags/Summary.tsx | 2 +- packages/page-staking/src/Bags/index.tsx | 3 +- packages/page-staking/src/Bags/types.ts | 8 ++++ .../page-staking/src/Bags/useBagEntries.tsx | 29 +++++++----- packages/page-staking/src/Bags/useBagsList.ts | 4 +- packages/page-staking/src/Bags/useBonded.ts | 40 ++++++++++++++++ 8 files changed, 150 insertions(+), 23 deletions(-) create mode 100644 packages/page-staking/src/Bags/Stash.tsx create mode 100644 packages/page-staking/src/Bags/useBonded.ts diff --git a/packages/page-staking/src/Bags/Bag.tsx b/packages/page-staking/src/Bags/Bag.tsx index 7d78430110..40b185f375 100644 --- a/packages/page-staking/src/Bags/Bag.tsx +++ b/packages/page-staking/src/Bags/Bag.tsx @@ -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 { - // 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 { + 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 ( @@ -28,13 +44,21 @@ export default function Bag ({ id, info, stashNodes = [] }: Props): React.ReactE {info.tail.isSome && } {stashNodes?.map(({ stashId }) => ( - ))} + + {isLoading + ? + : list.length + ? formatNumber(list.length) + : null + } + ); } diff --git a/packages/page-staking/src/Bags/Stash.tsx b/packages/page-staking/src/Bags/Stash.tsx new file mode 100644 index 0000000000..61a39a478a --- /dev/null +++ b/packages/page-staking/src/Bags/Stash.tsx @@ -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 { + const { t } = useTranslation(); + const { api } = useApi(); + const stashInfo = useMemo( + () => findEntry(stashId, list), + [list, stashId] + ); + + return ( +
+ + {stashInfo && stashInfo.jump && ( + ('Jump')} + params={[stashInfo?.jump]} + tx={api.tx.bagsList.putInFrontOf} + /> + )} +
+ ); +} diff --git a/packages/page-staking/src/Bags/Summary.tsx b/packages/page-staking/src/Bags/Summary.tsx index 82471772a3..a6d0949574 100644 --- a/packages/page-staking/src/Bags/Summary.tsx +++ b/packages/page-staking/src/Bags/Summary.tsx @@ -23,7 +23,7 @@ export default function Summary ({ className = '', ids }: Props): React.ReactEle ('total bags')}> {ids ? formatNumber(ids.length) - : + : } diff --git a/packages/page-staking/src/Bags/index.tsx b/packages/page-staking/src/Bags/index.tsx index 5ac43281d0..c66ce03d88 100644 --- a/packages/page-staking/src/Bags/index.tsx +++ b/packages/page-staking/src/Bags/index.tsx @@ -34,7 +34,8 @@ export default function Bags ({ ownStashes }: Props): React.ReactElement [t('bags')], [t('head'), 'address'], [t('tail'), 'address'], - [t('mine'), 'address'] + [t('mine'), 'address'], + [] ]); const sorted = useMemo( diff --git a/packages/page-staking/src/Bags/types.ts b/packages/page-staking/src/Bags/types.ts index 9722fc7554..026d0a6d83 100644 --- a/packages/page-staking/src/Bags/types.ts +++ b/packages/page-staking/src/Bags/types.ts @@ -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; +} diff --git a/packages/page-staking/src/Bags/useBagEntries.tsx b/packages/page-staking/src/Bags/useBagEntries.tsx index be6cc00139..6986c29cad 100644 --- a/packages/page-staking/src/Bags/useBagEntries.tsx +++ b/packages/page-staking/src/Bags/useBagEntries.tsx @@ -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>(!!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); diff --git a/packages/page-staking/src/Bags/useBagsList.ts b/packages/page-staking/src/Bags/useBagsList.ts index 6c89701209..a3e4f1701c 100644 --- a/packages/page-staking/src/Bags/useBagsList.ts +++ b/packages/page-staking/src/Bags/useBagsList.ts @@ -12,8 +12,8 @@ const multiOptions = { transform: ([[ids], opts]: [[u64[]], Option[]]): Result[] => ids .map((id, index): [u64, Option] => [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 }; diff --git a/packages/page-staking/src/Bags/useBonded.ts b/packages/page-staking/src/Bags/useBonded.ts new file mode 100644 index 0000000000..f74b37104a --- /dev/null +++ b/packages/page-staking/src/Bags/useBonded.ts @@ -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);