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:
parent
bf90c4fd35
commit
ee7c4fa45b
@ -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;
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
))}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -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]}
|
||||
|
||||
@ -143,6 +143,7 @@ function StakingApp ({ basePath, className }: Props): React.ReactElement<Props>
|
||||
isInElection={isInElection}
|
||||
next={next}
|
||||
ownStashes={ownStashes}
|
||||
targets={targets}
|
||||
validators={validators}
|
||||
/>
|
||||
<Overview
|
||||
|
||||
@ -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'>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user