Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fav & current as nominations #1869

Merged
merged 3 commits into from
Nov 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/app-council/src/Overview/Candidate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { AccountId } from '@polkadot/types/interfaces';

import React from 'react';
import { AddressCard } from '@polkadot/react-components';
import { AddressCard, Badge, Icon } from '@polkadot/react-components';

import translate from '../translate';
import Voters from './Voters';
Expand All @@ -21,6 +21,14 @@ function Candidate ({ address, isRunnerUp, t, voters }: Props): React.ReactEleme
return (
<AddressCard
defaultName={isRunnerUp ? t('runner up') : t('candidate')}
iconInfo={isRunnerUp && (
<Badge
hover={t('Runner up')}
info={<Icon name='chevron down' />}
isTooltip
type='runnerup'
/>
)}
value={address}
withIndexOrAddress
>
Expand Down
10 changes: 9 additions & 1 deletion packages/app-council/src/Overview/Member.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { AccountId } from '@polkadot/types/interfaces';

import React from 'react';
import { AddressCard } from '@polkadot/react-components';
import { AddressCard, Badge, Icon } from '@polkadot/react-components';

import translate from '../translate';
import Voters from './Voters';
Expand All @@ -20,6 +20,14 @@ function Member ({ address, t, voters }: Props): React.ReactElement<Props> {
return (
<AddressCard
defaultName={t('council member')}
iconInfo={
<Badge
hover={t('Current member')}
info={<Icon name='check' />}
isTooltip
type='selected'
/>
}
value={address}
withIndexOrAddress
>
Expand Down
5 changes: 4 additions & 1 deletion packages/app-council/src/Overview/Summary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,11 @@ function Summary ({ bestNumber, electionsInfo: { members, candidateCount, desire
<CardSummary label={t('seats')}>
{formatNumber(members.length)}/{formatNumber(desiredSeats)}
</CardSummary>
<CardSummary label={t('runners up')}>
{formatNumber(runnersUp.length)}
</CardSummary>
<CardSummary label={t('candidates')}>
{formatNumber(candidateCount.addn(runnersUp.length))}
{formatNumber(candidateCount)}
</CardSummary>
</section>
{voteCount && (
Expand Down
2 changes: 2 additions & 0 deletions packages/app-council/src/Overview/Vote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ interface State extends TxModalState {
// voterPositions: DerivedVoterPositions;
}

// const MAX_VOTES = 16;

// const AlreadyVoted = styled.article`
// display: flex;
// align-items: center;
Expand Down
136 changes: 120 additions & 16 deletions packages/app-staking/src/Actions/Account/Nominate.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@
import { I18nProps } from '@polkadot/react-components/types';
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';

import React, { useState } from 'react';
import { Button, InputAddress, Modal, TxButton } from '@polkadot/react-components';
import React, { useEffect, useState } from 'react';
import store from 'store';
import styled from 'styled-components';
import { AddressMini, Button, InputAddress, Modal, Toggle, TxButton } from '@polkadot/react-components';

import { STORE_FAVS } from '../../constants';
import translate from '../../translate';

interface Props extends I18nProps {
controllerId: string;
isOpen: boolean;
nominees?: string[];
onClose: () => void;
stashId: string;
Expand All @@ -22,27 +24,52 @@ interface Props extends I18nProps {
// We only allow a maximum of 16 nominees, negative to slice
const MAX_NOMINEES = -16;

function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }: Props): React.ReactElement<Props> | null {
const [nominees, setNominees] = useState<string[]>([]);
function Nominate ({ className, controllerId, nominees, onClose, stashId, stashOptions, t }: Props): React.ReactElement<Props> | null {
const [favorites] = useState<string[]>(store.get(STORE_FAVS, []));
const [next, setNext] = useState<string[] | undefined>();
const [{ options, shortlist }, setShortlist] = useState<{ options: KeyringSectionOption[]; shortlist: string[] }>({ options: [], shortlist: [] });

if (!isOpen) {
return null;
}
useEffect((): void => {
if (!next && nominees) {
setNext(nominees);
}
}, [next, nominees]);

useEffect((): void => {
if (nominees) {
const shortlist = [
// ensure that the favorite is included in the list of stashes
...favorites.filter((accountId): boolean => stashOptions.some(({ value }): boolean => value === accountId)),
// make sure the nominee is not in our favorites already
...nominees.filter((accountId): boolean => !favorites.includes(accountId))
];

setShortlist({
options: stashOptions.filter(({ value }): boolean => !shortlist.includes(value as string)),
shortlist
});
}
}, [favorites, nominees, stashOptions]);

const _onChangeNominees = (_nominees: string[]): void => {
const newNominees = _nominees.slice(MAX_NOMINEES);

if (JSON.stringify(newNominees) !== JSON.stringify(nominees)) {
setNominees(newNominees);
setNext(newNominees);
}
};
const _onToggleNominee = (nominee: string): void =>
setNext(
(next || []).includes(nominee)
? (next || []).filter((accountId): boolean => accountId !== nominee)
: [...(next || []), nominee].slice(MAX_NOMINEES)
);

return (
<Modal
className='staking--Nominating'
className={`staking--Nominating ${className}`}
dimmer='inverted'
open
size='small'
>
<Modal.Header>
{t('Nominate Validators')}
Expand All @@ -67,11 +94,39 @@ function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }:
isMultiple
label={t('nominate the following addresses')}
onChangeMulti={_onChangeNominees}
options={stashOptions}
options={options}
placeholder={t('select accounts(s) nominate')}
type='account'
value={nominees}
value={next || []}
/>
{shortlist.length !== 0 && (
<div className='shortlist'>
{shortlist.map((address): React.ReactNode => {
const isAye = next?.includes(address);
const _onChange = (): void => _onToggleNominee(address);

return (
<AddressMini
className={`candidate ${isAye ? 'isAye' : 'isNay'}`}
key={address}
value={address}
>
<div className='candidate-right'>
<Toggle
label={
isAye
? t('Aye')
: t('Nay')
}
onChange={_onChange}
value={isAye}
/>
</div>
</AddressMini>
);
})}
</div>
)}
</Modal.Content>
<Modal.Actions>
<Button.Group>
Expand All @@ -84,10 +139,10 @@ function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }:
<Button.Or />
<TxButton
accountId={controllerId}
isDisabled={nominees.length === 0}
isDisabled={!next || next.length === 0}
isPrimary
onClick={onClose}
params={[nominees]}
params={[next]}
label={t('Nominate')}
icon='hand paper outline'
tx='staking.nominate'
Expand All @@ -98,4 +153,53 @@ function Nominate ({ controllerId, isOpen, onClose, stashId, stashOptions, t }:
);
}

export default translate(Nominate);
export default translate(
styled(Nominate)`
.shortlist {
display: flex;
flex-wrap: wrap;
justify-content: center;

.candidate {
border: 1px solid #eee;
border-radius: 0.25rem;
margin: 0.25rem;
padding-bottom: 0.25rem;
padding-right: 0.5rem;
position: relative;

&::after {
content: '';
position: absolute;
top: 0;
right: 0;
border-color: transparent;
border-style: solid;
border-radius: 0.25em;
border-width: 0.25em;
}

&.isAye {
background: #fff;
border-color: #ccc;
}

&.member::after {
border-color: green;
}

&.runnerup::after {
border-color: steelblue;
}

.ui--AddressMini-icon {
z-index: 1;
}

.candidate-right {
text-align: right;
}
}
}
`
);
3 changes: 1 addition & 2 deletions packages/app-staking/src/Actions/Account/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,14 +252,13 @@ class Account extends React.PureComponent<Props, State> {
const { stashOptions } = this.props;
const { controllerId, isNominateOpen, nominees, stashId } = this.state;

if (!stashId || !controllerId) {
if (!isNominateOpen || !stashId || !controllerId) {
return null;
}

return (
<Nominate
controllerId={controllerId}
isOpen={isNominateOpen}
nominees={nominees}
onClose={this.toggleNominate}
stashId={stashId}
Expand Down
53 changes: 25 additions & 28 deletions packages/app-staking/src/Overview/CurrentList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import { DerivedHeartbeats, DerivedStakingOverview } from '@polkadot/api-derive/types';
import { I18nProps } from '@polkadot/react-components/types';
import { AccountId } from '@polkadot/types/interfaces';
import { AccountId, EraPoints, Points } from '@polkadot/types/interfaces';
import { ValidatorFilter } from '../types';

import React, { useContext, useEffect, useState } from 'react';
Expand All @@ -13,6 +13,7 @@ import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-compon
import store from 'store';
import keyring from '@polkadot/ui-keyring';

import { STORE_FAVS } from '../constants';
import translate from '../translate';
import Address from './Address';

Expand All @@ -24,31 +25,31 @@ interface Props extends I18nProps {
stakingOverview?: DerivedStakingOverview;
}

type AccountExtend = [string, boolean, boolean, number];
type AccountExtend = [string, boolean, boolean, Points?];

const STORE_FAVS = 'staking:favorites';

function filterAccounts (accounts: string[] = [], elected: string[], favorites: string[], without: string[], noPoints = false): AccountExtend[] {
function filterAccounts (accounts: string[] = [], elected: string[], favorites: string[], without: string[], eraPoints?: EraPoints): AccountExtend[] {
return accounts
.filter((accountId): boolean => !without.includes(accountId as any))
.sort((a, b): number => {
const aIdx = favorites.includes(a);
const bIdx = favorites.includes(b);
const isFavA = favorites.includes(a);
const isFavB = favorites.includes(b);

return aIdx === bIdx
return isFavA === isFavB
? 0
: aIdx
? -1
: 1;
: (isFavA ? -1 : 1);
})
.map((accountId): AccountExtend => [
accountId,
elected.includes(accountId),
favorites.includes(accountId),
noPoints
? -1
: elected.indexOf(accountId)
]);
.map((accountId): AccountExtend => {
const electedIdx = elected.indexOf(accountId);

return [
accountId,
elected.includes(accountId),
favorites.includes(accountId),
electedIdx !== -1
? eraPoints?.individual[electedIdx]
: undefined
];
});
}

function accountsToString (accounts: AccountId[]): string[] {
Expand All @@ -66,13 +67,13 @@ function CurrentList ({ authorsMap, lastAuthors, next, recentlyOnline, stakingOv
if (stakingOverview) {
const _elected = accountsToString(stakingOverview.currentElected);
const _validators = accountsToString(stakingOverview.validators);
const validators = filterAccounts(_validators, _elected, favorites, []);
const elected = isSubstrateV2 ? filterAccounts(_elected, _elected, favorites, _validators, true) : [];
const validators = filterAccounts(_validators, _elected, favorites, [], stakingOverview.eraPoints);
const elected = isSubstrateV2 ? filterAccounts(_elected, _elected, favorites, _validators) : [];

setFiltered({
elected,
validators,
waiting: filterAccounts(next, [], favorites, _elected, true)
waiting: filterAccounts(next, [], favorites, _elected)
});
}
}, [favorites, next, stakingOverview]);
Expand All @@ -88,7 +89,7 @@ function CurrentList ({ authorsMap, lastAuthors, next, recentlyOnline, stakingOv
);

const _renderColumn = (addresses: AccountExtend[], defaultName: string, withOnline: boolean): React.ReactNode =>
addresses.map(([address, isElected, isFavorite, pointIndex]): React.ReactNode => (
addresses.map(([address, isElected, isFavorite, points]): React.ReactNode => (
<Address
address={address}
authorsMap={authorsMap}
Expand All @@ -100,11 +101,7 @@ function CurrentList ({ authorsMap, lastAuthors, next, recentlyOnline, stakingOv
key={address}
myAccounts={myAccounts}
onFavorite={_onFavorite}
points={
pointIndex !== -1
? stakingOverview?.eraPoints.individual[pointIndex]
: undefined
}
points={points}
recentlyOnline={
withOnline
? recentlyOnline
Expand Down
5 changes: 5 additions & 0 deletions packages/app-staking/src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Copyright 2017-2019 @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.

export const STORE_FAVS = 'staking:favorites';
Loading