Nominate multiples (#899)

* Nominate multiples

* Swap columns to stashes, pass through targets to nominate

* Nominate Validator -> Nominate Validators
This commit is contained in:
Jaco Greeff 2019-04-06 22:34:17 +02:00 committed by GitHub
parent e5474be46a
commit dc4a9318c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 153 additions and 101 deletions

View File

@ -3,10 +3,10 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
import React from 'react';
import { Button, Input, InputAddress, Modal, TxButton } from '@polkadot/ui-app';
import keyring from '@polkadot/ui-keyring';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/ui-app';
import translate from '../translate';
@ -15,19 +15,16 @@ type Props = I18nProps & {
isOpen: boolean,
onClose: () => void,
intentions: Array<string>,
stashId: string
stashId: string,
targets: Array<KeyringSectionOption>
};
type State = {
isNomineeValid: boolean,
isAddressFormatValid: boolean,
nominees: Array<string>
};
class Nominating extends React.PureComponent<Props, State> {
state: State = {
isNomineeValid: false,
isAddressFormatValid: false,
nominees: []
};
@ -53,7 +50,7 @@ class Nominating extends React.PureComponent<Props, State> {
renderButtons () {
const { accountId, onClose, t } = this.props;
const { isNomineeValid, nominees } = this.state;
const { nominees } = this.state;
return (
<Modal.Actions>
@ -66,7 +63,7 @@ class Nominating extends React.PureComponent<Props, State> {
<Button.Or />
<TxButton
accountId={accountId}
isDisabled={!isNomineeValid || !nominees.length}
isDisabled={nominees.length === 0}
isPrimary
onClick={onClose}
params={[nominees]}
@ -79,13 +76,12 @@ class Nominating extends React.PureComponent<Props, State> {
}
renderContent () {
const { accountId, stashId, t } = this.props;
const { isNomineeValid, nominees } = this.state;
const { accountId, stashId, t, targets } = this.props;
return (
<>
<Modal.Header>
{t('Nominate Validator')}
{t('Nominate Validators')}
</Modal.Header>
<Modal.Content className='ui--signer-Signer-Content'>
<InputAddress
@ -100,63 +96,23 @@ class Nominating extends React.PureComponent<Props, State> {
isDisabled
label={t('stash account')}
/>
<Input
autoFocus
<InputAddress
className='medium'
isError={!isNomineeValid}
label={t('nominate the following address (validator or intention)')}
onChange={this.onChangeNominee}
value={nominees[0]}
isMultiple
help={t('Stash accounts that are to be nominated. Block rewards are split between validators and nominators')}
label={t('nominate the following addresses')}
onChangeMulti={this.onChangeNominees}
options={targets}
placeholder={t('select accounts(s) nominate')}
type='account'
/>
{this.renderErrors()}
</Modal.Content>
</>
);
}
private renderErrors () {
const { t } = this.props;
const { isNomineeValid, isAddressFormatValid } = this.state;
const hasError = !isNomineeValid || !isAddressFormatValid;
if (!hasError) {
return null;
}
return (
<article className='error'>
{
!isNomineeValid && isAddressFormatValid
? t('The address you input is not intending to stake, and is therefore invalid. Please try again with a validator address.')
: null
}
{
!isAddressFormatValid
? t('The address does not conform to a recognized address format. Please make sure you enter a valid address.')
: null
}
</article>
);
}
private onChangeNominee = (nominee: string) => {
// const { intentions } = this.props;
let isAddressFormatValid = false;
try {
keyring.decodeAddress(nominee);
isAddressFormatValid = true;
} catch (err) {
console.error(err);
}
this.setState({
isNomineeValid: isAddressFormatValid, // intentions.includes(nominee),
isAddressFormatValid,
nominees: [nominee]
});
private onChangeNominees = (nominees: Array<string>) => {
this.setState({ nominees });
}
}

View File

@ -5,6 +5,7 @@
import { DerivedBalancesMap } from '@polkadot/api-derive/types';
import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps } from '@polkadot/ui-api/types';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
import { Nominators } from '../types';
import React from 'react';
@ -22,15 +23,16 @@ type Props = ApiProps & I18nProps & {
accountId: string,
balances: DerivedBalancesMap,
balanceArray: (_address: AccountId | string) => Array<Balance> | undefined,
intentions: Array<string>,
isValidator: boolean,
name: string,
nominators: Nominators,
session_nextKeyFor?: Option<AccountId>,
staking_bonded?: Option<AccountId>,
staking_ledger?: Option<StakingLedger>,
staking_stakers?: Exposure,
staking_validators?: [ValidatorPrefs],
intentions: Array<string>,
nominators: Nominators,
isValidator: boolean,
targets: Array<KeyringSectionOption>,
validators: Array<string>
};
@ -57,10 +59,7 @@ class Account extends React.PureComponent<Props, State> {
stashId: null
};
static getDerivedStateFromProps ({ session_nextKeyFor, staking_bonded, staking_ledger }: Props): Partial<State> {
// console.error('staking_stakers', JSON.stringify(staking_stakers));
static getDerivedStateFromProps ({ session_nextKeyFor, staking_bonded, staking_ledger }: Props, state: State): Partial<State> {
return {
bondedId: staking_bonded && staking_bonded.isSome
? staking_bonded.unwrap().toString()
@ -256,7 +255,7 @@ class Account extends React.PureComponent<Props, State> {
}
private renderNominating () {
const { accountId, intentions } = this.props;
const { accountId, intentions, targets } = this.props;
const { isNominateOpen, stashId } = this.state;
if (!stashId) {
@ -270,6 +269,7 @@ class Account extends React.PureComponent<Props, State> {
onClose={this.toggleNominate}
intentions={intentions}
stashId={stashId}
targets={targets}
/>
);
}

View File

@ -29,23 +29,40 @@ type Props = I18nProps & {
};
type State = {
stashId: string | null,
badgeExpanded: boolean;
};
class Address extends React.PureComponent<Props, State> {
state: State = {
stashId: null,
badgeExpanded: false
};
static getDerivedStateFromProps ({ staking_ledger }: Props): State | null {
if (!staking_ledger || staking_ledger.isNone) {
return null;
}
return {
stashId: staking_ledger.unwrap().stash.toString()
} as State;
}
render () {
const { address, balanceArray, isAuthor, lastBlock } = this.props;
const { balanceArray, isAuthor, lastBlock } = this.props;
const { stashId } = this.state;
if (!stashId) {
return null;
}
return (
<article key={address}>
<article key={stashId}>
<AddressRow
balance={balanceArray(address)}
balance={balanceArray(stashId)}
name={this.getDisplayName()}
value={address}
value={stashId}
withCopy={false}
withNonce={false}
>
@ -63,18 +80,23 @@ class Address extends React.PureComponent<Props, State> {
}
private getDisplayName = (): string | undefined => {
const { address, defaultName } = this.props;
const { defaultName } = this.props;
const { stashId } = this.state;
const pair = keyring.getAccount(address).isValid()
? keyring.getAccount(address)
: keyring.getAddress(address);
if (!stashId) {
return defaultName;
}
const pair = keyring.getAccount(stashId).isValid()
? keyring.getAccount(stashId)
: keyring.getAddress(stashId);
return pair.isValid()
? pair.getMeta().name
: defaultName;
}
private onClickBadge = (): void => {
private toggleBadge = (): void => {
const { badgeExpanded } = this.state;
this.setState({ badgeExpanded: !badgeExpanded });
@ -111,11 +133,8 @@ class Address extends React.PureComponent<Props, State> {
}
private renderOffline () {
const { recentlyOffline, staking_ledger, t } = this.props;
const { badgeExpanded } = this.state;
const stashId: string | null = staking_ledger && staking_ledger.isSome
? staking_ledger.unwrap().stash.toString()
: null;
const { recentlyOffline, t } = this.props;
const { badgeExpanded, stashId } = this.state;
if (!stashId || !recentlyOffline[stashId]) {
return null;
@ -128,7 +147,7 @@ class Address extends React.PureComponent<Props, State> {
return (
<div
className={['recentlyOffline', badgeExpanded ? 'expand' : ''].join(' ')}
onClick={this.onClickBadge}
onClick={this.toggleBadge}
>
<div className='badge'>
{count.toString()}

View File

@ -76,7 +76,7 @@ class CurrentList extends React.PureComponent<Props, State> {
}
})}
</h1>
{this.renderColumn(current, t('validator'))}
{this.renderColumn(current, t('validator (stash)'))}
</>
);
}
@ -87,7 +87,7 @@ class CurrentList extends React.PureComponent<Props, State> {
return (
<>
<h1>{t('next up')}</h1>
{this.renderColumn(next, t('intention'))}
{this.renderColumn(next, t('intention (stash)'))}
</>
);
}

View File

@ -7,9 +7,11 @@ import { ComponentProps } from './types';
import React from 'react';
import keyring from '@polkadot/ui-keyring';
import createOption from '@polkadot/ui-keyring/options/item';
import Account from './Account';
import translate from './translate';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
type Props = I18nProps & ComponentProps;
@ -33,6 +35,7 @@ class StakeList extends React.PureComponent<Props> {
key={address}
name={name}
nominators={nominators}
targets={this.getTargetOptions()}
validators={validators}
/>
);
@ -40,6 +43,21 @@ class StakeList extends React.PureComponent<Props> {
</div>
);
}
private getTargetOptions (): Array<KeyringSectionOption> {
const { targets } = this.props;
return targets.map((stashId) => {
const pair = keyring.getAccount(stashId).isValid()
? keyring.getAccount(stashId)
: keyring.getAddress(stashId);
const name = pair.isValid()
? pair.getMeta().name
: undefined;
return createOption(stashId, name);
});
}
}
export default translate(StakeList);

View File

@ -33,6 +33,7 @@ type State = {
intentions: Array<string>,
nominators: Nominators,
tabs: Array<TabItem>,
targets: Array<string>,
validators: Array<string>
};
@ -57,13 +58,12 @@ class App extends React.PureComponent<Props, State> {
text: t('Account Actions')
}
],
targets: [],
validators: []
};
}
static getDerivedStateFromProps ({ staking_controllers = [[], []], session_validators = [], staking_nominators = [[], []] }: Props): State {
console.error(staking_controllers[0].map((id) => id.toString()));
return {
intentions: staking_controllers[1].filter((optId) => optId.isSome).map((accountId) =>
accountId.unwrap().toString()
@ -75,6 +75,7 @@ class App extends React.PureComponent<Props, State> {
return result;
}, {} as Nominators),
targets: staking_controllers[0].map((accountId) => accountId.toString()),
validators: session_validators.map((authorityId) =>
authorityId.toString()
)
@ -107,7 +108,7 @@ class App extends React.PureComponent<Props, State> {
private renderComponent (Component: React.ComponentType<ComponentProps>) {
return (): React.ReactNode => {
const { intentions, nominators, validators } = this.state;
const { intentions, nominators, targets, validators } = this.state;
const { balances = {} } = this.props;
return (
@ -116,6 +117,7 @@ class App extends React.PureComponent<Props, State> {
balanceArray={this.balanceArray}
intentions={intentions}
nominators={nominators}
targets={targets}
validators={validators}
/>
);

View File

@ -16,6 +16,7 @@ export type ComponentProps = {
balanceArray: (_address: AccountId | string) => Array<Balance> | undefined,
intentions: Array<string>,
nominators: Nominators,
targets: Array<string>,
validators: Array<string>
};

View File

@ -18,11 +18,13 @@ type Props<Option> = BareProps & {
isButton?: boolean,
isDisabled?: boolean,
isError?: boolean,
isMultiple?: boolean,
label?: React.ReactNode,
onChange?: (value: any) => void,
onSearch?: (filteredOptions: Array<any>, query: string) => Array<Option>,
options: Array<Option>,
placeholder?: string,
renderLabel?: (item: any) => any,
transform?: (value: any) => any,
value?: any,
withLabel?: boolean
@ -55,7 +57,7 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
}
render () {
const { className, defaultValue, help, isButton, isDisabled, isError, label, onSearch, options, placeholder, style, withLabel, value } = this.props;
const { className, defaultValue, help, isButton, isDisabled, isError, isMultiple, label, onSearch, options, placeholder, renderLabel, style, withLabel, value } = this.props;
const dropdown = (
<SUIDropdown
button={isButton}
@ -63,9 +65,11 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
disabled={isDisabled}
error={isError}
floating={isButton}
multiple={isMultiple}
onChange={this.onChange}
options={options}
placeholder={placeholder}
renderLabel={renderLabel}
search={onSearch}
selection
value={

View File

@ -25,12 +25,15 @@ type Props = BareProps & {
isDisabled?: boolean,
isError?: boolean,
isInput?: boolean,
isMultiple?: boolean,
label?: string,
onChange?: (value: string | null) => void,
onChangeMulti?: (value: Array<string>) => void,
options?: Array<KeyringSectionOption>,
optionsAll?: KeyringOptions,
placeholder?: string,
type?: KeyringOption$Type,
value?: string | Uint8Array,
value?: string | Uint8Array | Array<string>,
withLabel?: boolean
};
@ -81,7 +84,10 @@ class InputAddress extends React.PureComponent<Props, State> {
static getDerivedStateFromProps ({ value }: Props): State | null {
try {
return {
value: addressToAddress(value) || undefined
value: Array.isArray(value)
? value.map(addressToAddress)
: (addressToAddress(value) || undefined)
} as State;
} catch (error) {
return null;
@ -106,9 +112,9 @@ class InputAddress extends React.PureComponent<Props, State> {
}
render () {
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, label, optionsAll, type = DEFAULT_TYPE, style, withLabel } = this.props;
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, isMultiple, label, options, optionsAll, placeholder, type = DEFAULT_TYPE, style, withLabel } = this.props;
const { value } = this.state;
const hasOptions = optionsAll && Object.keys(optionsAll[type]).length !== 0;
const hasOptions = (options && options.length !== 0) || (optionsAll && Object.keys(optionsAll[type]).length !== 0);
if (!hasOptions && !isDisabled) {
return null;
@ -128,28 +134,62 @@ class InputAddress extends React.PureComponent<Props, State> {
<Dropdown
className={classes('ui--InputAddress', hideAddress ? 'flag--hideAddress' : '', className)}
defaultValue={
value !== undefined
isMultiple || (value !== undefined)
? undefined
: actualValue
}
help={help}
isDisabled={isDisabled}
isError={isError}
isMultiple={isMultiple}
label={label}
onChange={this.onChange}
onChange={
isMultiple
? this.onChangeMulti
: this.onChange
}
onSearch={this.onSearch}
options={
isDisabled && actualValue
? [createOption(actualValue)]
: (optionsAll ? optionsAll[type] : [])
options
? options
: (
isDisabled && actualValue
? [createOption(actualValue)]
: (optionsAll ? optionsAll[type] : [])
)
}
placeholder={placeholder}
renderLabel={
isMultiple
? this.renderLabel
: undefined
}
style={style}
value={value}
value={
isMultiple
? undefined
: value
}
withLabel={withLabel}
/>
);
}
private renderLabel = ({ value }: KeyringSectionOption): string | null => {
if (!value) {
return null;
}
const pair = keyring.getAccount(value).isValid()
? keyring.getAccount(value)
: keyring.getAddress(value);
const name = pair.isValid()
? pair.getMeta().name
: undefined;
return name || `${value.slice(0, 6)}…${value.slice(-6)}`;
}
private getLastOptionValue (): KeyringSectionOption | undefined {
const { optionsAll, type = DEFAULT_TYPE } = this.props;
@ -186,6 +226,18 @@ class InputAddress extends React.PureComponent<Props, State> {
onChange && onChange(transformToAccountId(address));
}
private onChangeMulti = (addresses: Array<string>) => {
const { onChangeMulti } = this.props;
if (onChangeMulti) {
onChangeMulti(
addresses
.map(transformToAccountId)
.filter((address) => address) as Array<string>
);
}
}
private onSearch = (filteredOptions: KeyringSectionOptions, _query: string): KeyringSectionOptions => {
const { isInput = true } = this.props;
const query = _query.trim();