Allow pre-select option when nominating (#2644)

* Allow pre-select option when nominating

* Pre-select as default (faster initial operation)
This commit is contained in:
Jaco Greeff 2020-04-28 09:18:41 +02:00 committed by GitHub
parent bf90c4fd35
commit ee7c4fa45b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 112 additions and 20 deletions

View File

@ -2,12 +2,14 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import { SortedTargets } from '../../types';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import { InputAddressMulti, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import { AddressMini, InputAddressMulti, InputAddress, Modal, Static, Toggle, TxButton } from '@polkadot/react-components';
import { useFavorites } from '@polkadot/react-hooks';
import { STORE_FAVS_BASE } from '../../constants';
import { MAX_NOMINATIONS, STORE_FAVS_BASE } from '../../constants';
import { useTranslation } from '../../translate';
interface Props {
@ -17,15 +19,29 @@ interface Props {
nominating?: string[];
onClose: () => void;
stashId: string;
targets: SortedTargets;
validators: string[];
}
const MAX_NOMINEES = 16;
interface Selected {
isAutoSelect: boolean;
selected: string[];
}
function Nominate ({ className, controllerId, next, nominating, onClose, stashId, validators }: Props): React.ReactElement<Props> | null {
function autoPick (targets: SortedTargets): string[] {
return (targets.validators || []).filter((_, index) => index < MAX_NOMINATIONS).map(({ key }) => key);
}
function initialPick (targets: SortedTargets): Selected {
return targets.validators?.length
? { isAutoSelect: true, selected: autoPick(targets) }
: { isAutoSelect: false, selected: [] };
}
function Nominate ({ className, controllerId, next, nominating, onClose, stashId, targets, validators }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const [favorites] = useFavorites(STORE_FAVS_BASE);
const [selection, setSelection] = useState<string[]>([]);
const [{ isAutoSelect, selected }, setSelected] = useState<Selected>(initialPick(targets));
const [available] = useState<string[]>((): string[] => {
const shortlist = [
// ensure that the favorite is included in the list of stashes
@ -39,6 +55,24 @@ function Nominate ({ className, controllerId, next, nominating, onClose, stashId
.concat(...(next || []).filter((acc) => !shortlist.includes(acc)));
});
const _setSelected = useCallback(
(selected: string[]) => setSelected(({ isAutoSelect }: Selected) => ({
isAutoSelect,
selected
})),
[]
);
const _toggleAutoSelect = useCallback(
(isAutoSelect: boolean) => setSelected(() => ({
isAutoSelect,
selected: isAutoSelect
? autoPick(targets)
: []
})),
[targets]
);
return (
<Modal
className={`staking--Nominating ${className}`}
@ -55,25 +89,56 @@ function Nominate ({ className, controllerId, next, nominating, onClose, stashId
isDisabled
label={t('stash account')}
/>
<InputAddressMulti
available={available}
availableLabel={t('candidate accounts')}
defaultValue={nominating}
help={t('Filter available candidates based on name, address or short account index.')}
maxCount={MAX_NOMINEES}
onChange={setSelection}
valueLabel={t('nominated accounts')}
{isAutoSelect
? (
<>
<Static
label={t('auto-selected targets for nomination')}
value={
selected.map((validatorId) => (
<AddressMini
key={validatorId}
value={validatorId}
/>
))
}
/>
<article className='warning'>{t('The auto-selection is done on the current profitability of the validators taking your favorites into account. It is adjusted based on the commission and current range of backing for teh validator. The calculation may and will change over time, so it is rather a selection based on the current state of the network, not a predictor of future profitability.')}</article>
</>
)
: (
<InputAddressMulti
available={available}
availableLabel={t('candidate accounts')}
defaultValue={nominating}
help={t('Filter available candidates based on name, address or short account index.')}
maxCount={MAX_NOMINATIONS}
onChange={_setSelected}
valueLabel={t('nominated accounts')}
/>
)
}
<Toggle
className='auto--toggle'
isDisabled={!targets.validators?.length}
label={
isAutoSelect
? t('Use an automatic selection of the currently most profitable validators')
: t('Select targets manually (no auto-selection based on current profitability)')
}
onChange={_toggleAutoSelect}
value={isAutoSelect}
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={controllerId}
icon='hand paper outline'
isDisabled={!selection?.length}
isDisabled={!selected?.length}
isPrimary
label={t('Nominate')}
onStart={onClose}
params={[selection]}
params={[selected]}
tx='staking.nominate'
/>
</Modal.Actions>
@ -82,6 +147,20 @@ function Nominate ({ className, controllerId, next, nominating, onClose, stashId
}
export default React.memo(styled(Nominate)`
article.warning {
margin-top: 0;
}
.auto--toggle {
margin: 0.5rem 0 0;
text-align: right;
width: 100%;
}
.ui--Static .ui--AddressMini.padded {
padding-top: 0.5rem;
}
.shortlist {
display: flex;
flex-wrap: wrap;

View File

@ -5,6 +5,7 @@
import { DeriveBalancesAll, DeriveStakingAccount } from '@polkadot/api-derive/types';
import { EraIndex } from '@polkadot/types/interfaces';
import { StakerState } from '@polkadot/react-hooks/types';
import { SortedTargets } from '../../types';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
@ -29,10 +30,11 @@ interface Props {
info: StakerState;
next?: string[];
stashId: string;
targets: SortedTargets;
validators?: string[];
}
function Account ({ className, info: { controllerId, destination, destinationId, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, next, validators }: Props): React.ReactElement<Props> {
function Account ({ className, info: { controllerId, destination, destinationId, hexSessionIdNext, hexSessionIdQueue, isLoading, isOwnController, isOwnStash, isStashNominating, isStashValidating, nominating, sessionIds, stakingLedger, stashId }, isDisabled, next, targets, validators }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const balancesAll = useCall<DeriveBalancesAll>(api.derive.balances.all, [stashId]);
@ -76,6 +78,7 @@ function Account ({ className, info: { controllerId, destination, destinationId,
nominating={nominating}
onClose={toggleNominate}
stashId={stashId}
targets={targets}
validators={validators}
/>
)}

View File

@ -4,6 +4,7 @@
import { ActiveEraInfo, EraIndex } from '@polkadot/types/interfaces';
import { StakerState } from '@polkadot/react-hooks/types';
import { SortedTargets } from '../types';
import BN from 'bn.js';
import React, { useEffect, useMemo, useState } from 'react';
@ -23,6 +24,7 @@ interface Props {
ownStashes?: StakerState[];
next?: string[];
validators?: string[];
targets: SortedTargets;
}
interface State {
@ -30,7 +32,7 @@ interface State {
foundStashes?: StakerState[];
}
function Actions ({ className, isInElection, next, ownStashes, validators }: Props): React.ReactElement<Props> {
function Actions ({ className, isInElection, next, ownStashes, targets, validators }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const activeEra = useCall<EraIndex | undefined>(api.query.staking?.activeEra, [], {
@ -85,6 +87,7 @@ function Actions ({ className, isInElection, next, ownStashes, validators }: Pro
isDisabled={isInElection}
key={info.stashId}
next={next}
targets={targets}
validators={validators}
/>
))}

View File

@ -5,6 +5,7 @@
import { StakerState } from '@polkadot/react-hooks/types';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { AddressMini, Button, InputAddress, Modal, Static, TxButton } from '@polkadot/react-components';
import { useToggle } from '@polkadot/react-hooks';
@ -92,4 +93,8 @@ function Nominate ({ ownNominators, targets }: Props): React.ReactElement<Props>
);
}
export default React.memo(Nominate);
export default React.memo(styled(Nominate)`
.ui--AddressMini.padded {
padding-top: 0.5rem;
}
`);

View File

@ -136,7 +136,7 @@ function Targets ({ className, ownStashes, targets: { calcWith, lastReward, nomi
filter={filter}
header={header}
>
{validators && sorted?.map((index): React.ReactNode =>
{validators && sorted && (validators.length === sorted.length) && sorted.map((index): React.ReactNode =>
<Validator
canSelect={selected.length < MAX_NOMINATIONS}
info={validators[index]}

View File

@ -143,6 +143,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement<Props>
isInElection={isInElection}
next={next}
ownStashes={ownStashes}
targets={targets}
validators={validators}
/>
<Overview

View File

@ -121,6 +121,7 @@ function InputAddressMulti ({ available, availableLabel, className, defaultValue
onChange={setFilter}
placeholder={t('filter by name, address, or account index')}
value={_filter}
withLabel={false}
/>
<div className='ui--InputAddressMulti-columns'>
<div className='ui--InputAddressMulti-column'>