Split waiting/inactive nominees (#2655)

This commit is contained in:
Jaco Greeff 2020-04-29 12:40:47 +02:00 committed by GitHub
parent 215395f31d
commit 0d11423ea3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 120 additions and 53 deletions

View File

@ -0,0 +1,62 @@
// Copyright 2017-2020 @polkadot/app-staking authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import { AddressMini, Expander } from '@polkadot/react-components';
import { useTranslation } from '../../translate';
import useInactives from '../useInactives';
interface Props {
nominating?: string[];
stashId: string;
}
function ListNominees ({ nominating, stashId }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { nomsActive, nomsInactive, nomsWaiting } = useInactives(stashId, nominating);
return (
<>
{nomsActive && nomsActive.length !== 0 && (
<Expander summary={t('Active nominations ({{count}})', { replace: { count: nomsActive.length } })}>
{nomsActive.map((nomineeId, index): React.ReactNode => (
<AddressMini
key={index}
value={nomineeId}
withBalance={false}
withBonded
/>
))}
</Expander>
)}
{nomsInactive && nomsInactive.length !== 0 && (
<Expander summary={t('Inactive nominations ({{count}})', { replace: { count: nomsInactive.length } })}>
{nomsInactive.map((nomineeId, index): React.ReactNode => (
<AddressMini
key={index}
value={nomineeId}
withBalance={false}
withBonded
/>
))}
</Expander>
)}
{nomsWaiting && nomsWaiting.length !== 0 && (
<Expander summary={t('Waiting nominations ({{count}})', { replace: { count: nomsWaiting.length } })}>
{nomsWaiting.map((nomineeId, index): React.ReactNode => (
<AddressMini
key={index}
value={nomineeId}
withBalance={false}
withBonded
/>
))}
</Expander>
)}
</>
);
}
export default React.memo(ListNominees);

View File

@ -7,15 +7,15 @@ import { EraIndex } from '@polkadot/types/interfaces';
import { StakerState } from '@polkadot/react-hooks/types';
import { SortedTargets } from '../../types';
import React, { useEffect, useState } from 'react';
import React from 'react';
import styled from 'styled-components';
import { AddressInfo, AddressMini, AddressSmall, Button, Expander, Menu, Popup, StakingBonded, StakingRedeemable, StakingUnbonding, TxButton } from '@polkadot/react-components';
import { AddressInfo, AddressMini, AddressSmall, Button, Menu, Popup, StakingBonded, StakingRedeemable, StakingUnbonding, TxButton } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { useTranslation } from '../../translate';
import useInactives from '../useInactives';
import BondExtra from './BondExtra';
import InjectKeys from './InjectKeys';
import ListNominees from './ListNominees';
import Nominate from './Nominate';
import SetControllerAccount from './SetControllerAccount';
import SetRewardDestination from './SetRewardDestination';
@ -39,8 +39,6 @@ function Account ({ className, info: { controllerId, destination, destinationId,
const { api } = useApi();
const balancesAll = useCall<DeriveBalancesAll>(api.derive.balances.all, [stashId]);
const stakingAccount = useCall<DeriveStakingAccount>(api.derive.staking.account, [stashId]);
const [activeNoms, setActiveNoms] = useState<string[]>([]);
const inactiveNoms = useInactives(stashId, nominating);
const [isBondExtraOpen, toggleBondExtra] = useToggle();
const [isInjectOpen, toggleInject] = useToggle();
const [isNominateOpen, toggleNominate] = useToggle();
@ -51,12 +49,6 @@ function Account ({ className, info: { controllerId, destination, destinationId,
const [isUnbondOpen, toggleUnbond] = useToggle();
const [isValidateOpen, toggleValidate] = useToggle();
useEffect((): void => {
nominating && setActiveNoms(
nominating.filter((id): boolean => !inactiveNoms.includes(id))
);
}, [inactiveNoms, nominating]);
return (
<tr className={className}>
<td className='address'>
@ -141,32 +133,10 @@ function Account ({ className, info: { controllerId, destination, destinationId,
: (
<td className='all'>
{isStashNominating && (
<>
{activeNoms.length !== 0 && (
<Expander summary={t('Active nominations ({{count}})', { replace: { count: activeNoms.length } })}>
{activeNoms.map((nomineeId, index): React.ReactNode => (
<AddressMini
key={index}
value={nomineeId}
withBalance={false}
withBonded
/>
))}
</Expander>
)}
{inactiveNoms.length !== 0 && (
<Expander summary={t('Inactive nominations ({{count}})', { replace: { count: inactiveNoms.length } })}>
{inactiveNoms.map((nomineeId, index): React.ReactNode => (
<AddressMini
key={index}
value={nomineeId}
withBalance={false}
withBonded
/>
))}
</Expander>
)}
</>
<ListNominees
nominating={nominating}
stashId={stashId}
/>
)}
</td>
)

View File

@ -3,15 +3,54 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { DeriveSessionIndexes } from '@polkadot/api-derive/types';
import { Exposure } from '@polkadot/types/interfaces';
import { EraIndex, Exposure, Nominations } from '@polkadot/types/interfaces';
import { useEffect, useState } from 'react';
import { useApi, useCall, useIsMountedRef } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
export default function useInactives (stashId: string, nominees?: string[]): string[] {
interface Inactives {
nomsActive?: string[];
nomsInactive?: string[];
nomsWaiting?: string[];
}
function extractState (stashId: string, nominees: string[], activeEra: EraIndex, submittedIn: EraIndex, exposures: Exposure[]): Inactives {
// first a blanket find of nominations not in the active set
let nomsInactive = exposures
.map((exposure, index) =>
exposure.others.some(({ who }) => who.eq(stashId))
? null
: nominees[index]
)
.filter((inactiveId): inactiveId is string => !!inactiveId);
// waiting if validator is inactive or we have not submitted long enough ago
const nomsWaiting = exposures
.map((exposure, index) =>
exposure.total.unwrap().isZero() || (nomsInactive.includes(nominees[index]) && activeEra.sub(submittedIn).lten(2))
? nominees[index]
: null
)
.filter((waitingId): waitingId is string => !!waitingId);
// filter based on all inactives
const nomsActive = nominees.filter((nominee) => !nomsInactive.includes(nominee));
// inactive also contains waiting, remove those
nomsInactive = nomsInactive.filter((inactiveId) => !nomsWaiting.includes(inactiveId));
return {
nomsActive,
nomsInactive,
nomsWaiting
};
}
export default function useInactives (stashId: string, nominees?: string[]): Inactives {
const { api } = useApi();
const mountedRef = useIsMountedRef();
const [inactives, setInactives] = useState<string[]>([]);
const [state, setState] = useState<Inactives>({});
const indexes = useCall<DeriveSessionIndexes>(api.derive.session.indexes, []);
useEffect((): () => void => {
@ -20,18 +59,14 @@ export default function useInactives (stashId: string, nominees?: string[]): str
if (mountedRef.current && nominees?.length && indexes) {
api
.queryMulti(
api.query.staking.erasStakers
? nominees.map((id) => [api.query.staking.erasStakers, [indexes.activeEra, id]])
: nominees.map((id) => [api.query.staking.stakers, id]),
(exposures: Exposure[]): void => {
mountedRef.current && setInactives(
exposures
.map((exposure, index) =>
exposure.others.some(({ who }) => who.eq(stashId))
? null
: nominees[index].toString()
)
.filter((inactiveId): inactiveId is string => !!inactiveId)
[[api.query.staking.nominators, stashId] as any].concat(
api.query.staking.erasStakers
? nominees.map((id) => [api.query.staking.erasStakers, [indexes.activeEra, id]])
: nominees.map((id) => [api.query.staking.stakers, id])
),
([optNominators, ...exposures]: [Option<Nominations>, ...Exposure[]]): void => {
mountedRef.current && setState(
extractState(stashId, nominees, indexes.activeEra, optNominators.unwrapOrDefault().submittedIn, exposures)
);
}
)
@ -45,5 +80,5 @@ export default function useInactives (stashId: string, nominees?: string[]): str
};
}, [api, indexes, mountedRef, nominees, stashId]);
return inactives;
return state;
}