Adjust staking targets/overview sorting displays (#2999)
* Adjust staking targets/overview sorting displays * Removce whack eslint bug comment
This commit is contained in:
parent
73aa788e0f
commit
d30b88b7f4
@ -35,6 +35,21 @@ interface State {
|
||||
foundStashes?: StakerState[];
|
||||
}
|
||||
|
||||
function sortStashes (a: StakerState, b: StakerState): number {
|
||||
return (a.isStashValidating ? 1 : (a.isStashNominating ? 5 : 99)) - (b.isStashValidating ? 1 : (b.isStashNominating ? 5 : 99));
|
||||
}
|
||||
|
||||
function extractState (ownStashes: StakerState[]): State {
|
||||
return {
|
||||
bondedTotal: ownStashes.reduce((total: BN, { stakingLedger }) =>
|
||||
stakingLedger
|
||||
? total.add(stakingLedger.total.unwrap())
|
||||
: total,
|
||||
BN_ZERO),
|
||||
foundStashes: ownStashes.sort(sortStashes)
|
||||
};
|
||||
}
|
||||
|
||||
function Actions ({ className = '', isInElection, next, ownStashes, targets, validators }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
@ -44,16 +59,7 @@ function Actions ({ className = '', isInElection, next, ownStashes, targets, val
|
||||
const [{ bondedTotal, foundStashes }, setState] = useState<State>({});
|
||||
|
||||
useEffect((): void => {
|
||||
ownStashes && setState({
|
||||
bondedTotal: ownStashes.reduce((total: BN, { stakingLedger }) =>
|
||||
stakingLedger
|
||||
? total.add(stakingLedger.total.unwrap())
|
||||
: total,
|
||||
BN_ZERO),
|
||||
foundStashes: ownStashes.sort((a, b) =>
|
||||
(a.isStashValidating ? 1 : (a.isStashNominating ? 5 : 99)) - (b.isStashValidating ? 1 : (b.isStashNominating ? 5 : 99))
|
||||
)
|
||||
});
|
||||
ownStashes && setState(extractState(ownStashes));
|
||||
}, [ownStashes]);
|
||||
|
||||
const header = useMemo(() => [
|
||||
|
||||
@ -31,9 +31,9 @@ interface Selected {
|
||||
}
|
||||
|
||||
function autoPick (targets: SortedTargets): string[] {
|
||||
return (targets.validators || []).reduce((result: string[], { key, numNominators }): string[] => {
|
||||
return (targets.validators || []).reduce((result: string[], { key, numNominators, rewardPayout }): string[] => {
|
||||
if (result.length < MAX_NOMINATIONS) {
|
||||
if (numNominators && (numNominators < MAX_PAYOUTS)) {
|
||||
if (numNominators && (numNominators < MAX_PAYOUTS) && !rewardPayout.isZero()) {
|
||||
result.push(key);
|
||||
}
|
||||
}
|
||||
@ -137,6 +137,7 @@ function Nominate ({ className = '', controllerId, next, nominating, onChange, s
|
||||
value={
|
||||
selected.map((validatorId) => (
|
||||
<AddressMini
|
||||
isHighlight={favorites.includes(validatorId)}
|
||||
key={validatorId}
|
||||
value={validatorId}
|
||||
/>
|
||||
|
||||
@ -32,8 +32,8 @@ interface Props {
|
||||
onlineCount?: false | number;
|
||||
onlineMessage?: boolean;
|
||||
points?: string;
|
||||
setNominators?: false | ((nominators: string[]) => void);
|
||||
toggleFavorite: (accountId: string) => void;
|
||||
withoutName: boolean;
|
||||
}
|
||||
|
||||
interface StakingState {
|
||||
@ -44,9 +44,7 @@ interface StakingState {
|
||||
stakeOwn?: BN;
|
||||
}
|
||||
|
||||
/* stylelint-disable */
|
||||
const PERBILL_PERCENT = 10_000_000;
|
||||
/* stylelint-enable */
|
||||
|
||||
function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingState {
|
||||
let nominators: [string, Balance][] = [];
|
||||
@ -74,15 +72,15 @@ function expandInfo ({ exposure, validatorPrefs }: DeriveStakingQuery): StakingS
|
||||
};
|
||||
}
|
||||
|
||||
function checkVisibility (api: ApiPromise, address: string, filterName: string, accountInfo?: DeriveAccountInfo): boolean {
|
||||
function checkVisibility (api: ApiPromise, address: string, filterName: string, withoutName: boolean, accountInfo?: DeriveAccountInfo): boolean {
|
||||
let isVisible = false;
|
||||
const filterLower = filterName.toLowerCase();
|
||||
|
||||
if (filterLower) {
|
||||
if (filterLower || !withoutName) {
|
||||
if (accountInfo) {
|
||||
const { accountId, accountIndex, identity, nickname } = accountInfo;
|
||||
|
||||
if (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName)) {
|
||||
if (withoutName && (accountId?.toString().includes(filterName) || accountIndex?.toString().includes(filterName))) {
|
||||
isVisible = true;
|
||||
} else if (api.query.identity && api.query.identity.identityOf) {
|
||||
isVisible = (!!identity?.display && identity.display.toLowerCase().includes(filterLower)) ||
|
||||
@ -106,7 +104,7 @@ function checkVisibility (api: ApiPromise, address: string, filterName: string,
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
function Address ({ address, className = '', filterName, hasQueries, isAuthor, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, setNominators, toggleFavorite }: Props): React.ReactElement<Props> | null {
|
||||
function Address ({ address, className = '', filterName, hasQueries, isAuthor, isElected, isFavorite, isMain, lastBlock, nominatedBy, onlineCount, onlineMessage, points, toggleFavorite, withoutName }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const { allAccounts } = useAccounts();
|
||||
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [address]);
|
||||
@ -116,19 +114,14 @@ function Address ({ address, className = '', filterName, hasQueries, isAuthor, i
|
||||
const [isNominating, setIsNominating] = useState(false);
|
||||
|
||||
useEffect((): void => {
|
||||
if (stakingInfo) {
|
||||
const info = expandInfo(stakingInfo);
|
||||
|
||||
setNominators && setNominators(info.nominators.map(([who]): string => who.toString()));
|
||||
setStakingState(info);
|
||||
}
|
||||
}, [setNominators, stakingInfo]);
|
||||
stakingInfo && setStakingState(expandInfo(stakingInfo));
|
||||
}, [stakingInfo]);
|
||||
|
||||
useEffect((): void => {
|
||||
setIsVisible(
|
||||
checkVisibility(api, address, filterName, accountInfo)
|
||||
checkVisibility(api, address, filterName, withoutName, accountInfo)
|
||||
);
|
||||
}, [api, accountInfo, address, filterName]);
|
||||
}, [api, accountInfo, address, filterName, withoutName]);
|
||||
|
||||
useEffect((): void => {
|
||||
!isMain && setIsNominating(
|
||||
@ -144,8 +137,12 @@ function Address ({ address, className = '', filterName, hasQueries, isAuthor, i
|
||||
[address]
|
||||
);
|
||||
|
||||
if (!isVisible) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<tr className={`${className} ${(isAuthor || isNominating) ? 'isHighlight' : ''} ${!isVisible ? 'staking--hidden' : ''}`}>
|
||||
<tr className={`${className} ${(isAuthor || isNominating) ? 'isHighlight' : ''}`}>
|
||||
<Favorite
|
||||
address={address}
|
||||
isFavorite={isFavorite}
|
||||
|
||||
@ -7,7 +7,7 @@ import { AccountId, Nominations } from '@polkadot/types/interfaces';
|
||||
import { Authors } from '@polkadot/react-query/BlockAuthors';
|
||||
|
||||
import React, { useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
||||
import { Input, Table } from '@polkadot/react-components';
|
||||
import { Input, Table, Toggle } from '@polkadot/react-components';
|
||||
import { useApi, useCall, useFavorites } from '@polkadot/react-hooks';
|
||||
import { BlockAuthorsContext } from '@polkadot/react-query';
|
||||
import { Option, StorageKey } from '@polkadot/types';
|
||||
@ -88,7 +88,7 @@ function extractNominators (nominations: [StorageKey, Option<Nominations>][]): R
|
||||
}, {});
|
||||
}
|
||||
|
||||
function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOverview }: Props): React.ReactElement<Props> | null {
|
||||
function CurrentList ({ hasQueries, isIntentions, next, stakingOverview }: Props): React.ReactElement<Props> | null {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const { byAuthor, eraPoints, lastBlockAuthors } = useContext(isIntentions ? EmptyAuthorsContext : BlockAuthorsContext);
|
||||
@ -98,6 +98,7 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
|
||||
const [{ elected, validators, waiting }, setFiltered] = useState<Filtered>({});
|
||||
const [nameFilter, setNameFilter] = useState<string>('');
|
||||
const [nominatedBy, setNominatedBy] = useState<Record<string, [string, number][]> | null>();
|
||||
const [withoutName, setWithoutName] = useState(true);
|
||||
|
||||
useEffect((): void => {
|
||||
stakingOverview && setFiltered(
|
||||
@ -129,14 +130,30 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
|
||||
], [t]);
|
||||
|
||||
const filter = useMemo(() => (
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t<string>('filter by name, address or index')}
|
||||
onChange={setNameFilter}
|
||||
value={nameFilter}
|
||||
/>
|
||||
), [nameFilter, t]);
|
||||
<div>
|
||||
<Input
|
||||
autoFocus
|
||||
isFull
|
||||
label={t<string>('filter by name, address or index')}
|
||||
onChange={setNameFilter}
|
||||
value={nameFilter}
|
||||
/>
|
||||
<div className='staking--optionsBar'>
|
||||
{api.query.identity && (
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={
|
||||
withoutName
|
||||
? t<string>('with/without identity')
|
||||
: t<string>('only with an identity')
|
||||
}
|
||||
onChange={setWithoutName}
|
||||
value={withoutName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
), [api, nameFilter, t, withoutName]);
|
||||
|
||||
const _renderRows = useCallback(
|
||||
(addresses?: AccountExtend[], isMain?: boolean): React.ReactNode[] =>
|
||||
@ -155,11 +172,11 @@ function CurrentList ({ hasQueries, isIntentions, next, setNominators, stakingOv
|
||||
onlineCount={recentlyOnline?.[address]?.blockCount.toNumber()}
|
||||
onlineMessage={recentlyOnline?.[address]?.hasMessage}
|
||||
points={eraPoints[address]}
|
||||
setNominators={setNominators}
|
||||
toggleFavorite={toggleFavorite}
|
||||
withoutName={withoutName}
|
||||
/>
|
||||
)),
|
||||
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, setNominators, toggleFavorite]
|
||||
[byAuthor, eraPoints, hasQueries, lastBlockAuthors, nameFilter, nominatedBy, recentlyOnline, toggleFavorite, withoutName]
|
||||
);
|
||||
|
||||
return isIntentions
|
||||
|
||||
@ -16,7 +16,7 @@ interface Props {
|
||||
className?: string;
|
||||
isVisible: boolean;
|
||||
next?: string[];
|
||||
nominators: string[];
|
||||
nominators?: string[];
|
||||
stakingOverview?: DeriveStakingOverview;
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ function Summary ({ className = '', isVisible, next, nominators, stakingOverview
|
||||
{next.length}
|
||||
</CardSummary>
|
||||
)}
|
||||
{!!nominators.length && (
|
||||
{!!nominators?.length && (
|
||||
<CardSummary label={t<string>('nominators')}>
|
||||
{nominators.length}
|
||||
</CardSummary>
|
||||
|
||||
@ -13,18 +13,16 @@ interface Props extends BareProps {
|
||||
hasQueries: boolean;
|
||||
isIntentions?: boolean;
|
||||
next?: string[];
|
||||
setNominators?: (nominators: string[]) => void;
|
||||
stakingOverview?: DeriveStakingOverview;
|
||||
}
|
||||
|
||||
function Overview ({ className = '', hasQueries, isIntentions, next, setNominators, stakingOverview }: Props): React.ReactElement<Props> {
|
||||
function Overview ({ className = '', hasQueries, isIntentions, next, stakingOverview }: Props): React.ReactElement<Props> {
|
||||
return (
|
||||
<div className={`staking--Overview ${className}`}>
|
||||
<CurrentList
|
||||
hasQueries={hasQueries}
|
||||
isIntentions={isIntentions}
|
||||
next={next}
|
||||
setNominators={setNominators}
|
||||
stakingOverview={stakingOverview}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@ -2,12 +2,16 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { DeriveAccountInfo } from '@polkadot/api-derive/types';
|
||||
import { ValidatorInfo } from '../types';
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { ApiPromise } from '@polkadot/api';
|
||||
import { AddressSmall, Icon, Toggle } from '@polkadot/react-components';
|
||||
import { FormatBalance } from '@polkadot/react-query';
|
||||
import { useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
|
||||
import MaxBadge from '../MaxBadge';
|
||||
import Favorite from '../Overview/Address/Favorite';
|
||||
@ -18,21 +22,61 @@ interface Props {
|
||||
isSelected: boolean;
|
||||
toggleFavorite: (accountId: string) => void;
|
||||
toggleSelected: (accountId: string) => void;
|
||||
withoutName: boolean;
|
||||
}
|
||||
|
||||
function Validator ({ canSelect, info: { accountId, bondOther, bondOwn, bondTotal, commissionPer, isCommission, isFavorite, isNominating, key, numNominators, rankOverall, rewardPayout, validatorPayment }, isSelected, toggleFavorite, toggleSelected }: Props): React.ReactElement<Props> {
|
||||
function checkVisibility (api: ApiPromise, accountInfo: DeriveAccountInfo): boolean {
|
||||
let isVisible = false;
|
||||
|
||||
const { accountId, identity, nickname } = accountInfo;
|
||||
|
||||
if (api.query.identity && api.query.identity.identityOf) {
|
||||
isVisible = !!(identity?.display && identity.display.toString());
|
||||
} else if (nickname) {
|
||||
isVisible = !!nickname.toString();
|
||||
}
|
||||
|
||||
if (!isVisible && accountId) {
|
||||
const account = keyring.getAddress(accountId.toString());
|
||||
|
||||
isVisible = !!account?.meta?.name;
|
||||
}
|
||||
|
||||
return isVisible;
|
||||
}
|
||||
|
||||
function Validator ({ canSelect, info, isSelected, toggleFavorite, toggleSelected, withoutName }: Props): React.ReactElement<Props> | null {
|
||||
const { api } = useApi();
|
||||
const accountInfo = useCall<DeriveAccountInfo>(api.derive.accounts.info, [info.accountId]);
|
||||
const [hasName, setHasName] = useState(true);
|
||||
|
||||
useEffect((): void => {
|
||||
if (accountInfo) {
|
||||
const hasIdentity = checkVisibility(api, accountInfo);
|
||||
|
||||
info.hasIdentity = hasIdentity;
|
||||
setHasName(hasIdentity);
|
||||
}
|
||||
}, [accountInfo, api, info]);
|
||||
|
||||
const _onQueryStats = useCallback(
|
||||
(): void => {
|
||||
window.location.hash = `/staking/query/${key}`;
|
||||
window.location.hash = `/staking/query/${info.key}`;
|
||||
},
|
||||
[key]
|
||||
[info.key]
|
||||
);
|
||||
|
||||
const _toggleSelected = useCallback(
|
||||
() => toggleSelected(key),
|
||||
[key, toggleSelected]
|
||||
() => toggleSelected(info.key),
|
||||
[info.key, toggleSelected]
|
||||
);
|
||||
|
||||
if (!hasName && !withoutName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { accountId, bondOther, bondOwn, bondTotal, commissionPer, isCommission, isFavorite, isNominating, key, numNominators, rankOverall, rewardPayout, validatorPayment } = info;
|
||||
|
||||
return (
|
||||
<tr className={`${isNominating ? 'isHighlight' : ''}`}>
|
||||
<Favorite
|
||||
@ -47,6 +91,7 @@ function Validator ({ canSelect, info: { accountId, bondOther, bondOwn, bondTota
|
||||
<td className='address all'>
|
||||
<AddressSmall value={accountId} />
|
||||
</td>
|
||||
<td className='number'>{numNominators || ''}</td>
|
||||
<td className='number'>
|
||||
{
|
||||
isCommission
|
||||
@ -62,7 +107,7 @@ function Validator ({ canSelect, info: { accountId, bondOther, bondOwn, bondTota
|
||||
value={bondOther}
|
||||
/>
|
||||
</td>
|
||||
<td className='number together'><FormatBalance value={rewardPayout} /></td>
|
||||
<td className='number together'>{!rewardPayout.isZero() && <FormatBalance value={rewardPayout} />}</td>
|
||||
<td>
|
||||
{(canSelect || isSelected) && (
|
||||
<Toggle
|
||||
|
||||
@ -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 { DeriveStakingOverview } from '@polkadot/api-derive/types';
|
||||
import { StakerState } from '@polkadot/react-hooks/types';
|
||||
import { SortedTargets, TargetSortBy, ValidatorInfo } from '../types';
|
||||
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Icon, InputBalance, Table, Button } from '@polkadot/react-components';
|
||||
import { Button, Icon, InputBalance, Table, Toggle } from '@polkadot/react-components';
|
||||
import { useApi } from '@polkadot/react-hooks';
|
||||
|
||||
import { MAX_NOMINATIONS } from '../constants';
|
||||
import { useTranslation } from '../translate';
|
||||
@ -18,7 +20,9 @@ import useOwnNominators from './useOwnNominators';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
next?: string[];
|
||||
ownStashes?: StakerState[];
|
||||
stakingOverview?: DeriveStakingOverview;
|
||||
targets: SortedTargets;
|
||||
}
|
||||
|
||||
@ -43,9 +47,11 @@ function sort (sortBy: TargetSortBy, sortFromMax: boolean, validators: Validator
|
||||
|
||||
function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward, nominators, setCalcWith, toggleFavorite, totalStaked, validators } }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const ownNominators = useOwnNominators(ownStashes);
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
const [sorted, setSorted] = useState<number[] | undefined>();
|
||||
const [withoutName, setWithoutName] = useState(true);
|
||||
const [{ sortBy, sortFromMax }, setSortBy] = useState<SortState>({ sortBy: 'rankOverall', sortFromMax: true });
|
||||
|
||||
useEffect((): void => {
|
||||
@ -75,11 +81,15 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
|
||||
const _selectProfitable = useCallback(
|
||||
() => setSelected(
|
||||
(validators || [])
|
||||
.filter((_, index) => index < MAX_NOMINATIONS)
|
||||
.map(({ key }) => key)
|
||||
(validators || []).reduce((result: string[], { hasIdentity, key, rewardPayout }): string[] => {
|
||||
if ((result.length < MAX_NOMINATIONS) && (withoutName || hasIdentity) && !rewardPayout.isZero()) {
|
||||
result.push(key);
|
||||
}
|
||||
|
||||
return result;
|
||||
}, [])
|
||||
),
|
||||
[validators]
|
||||
[validators, withoutName]
|
||||
);
|
||||
|
||||
const labels = useMemo(
|
||||
@ -87,15 +97,16 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
rankBondOther: t<string>('other stake'),
|
||||
rankBondOwn: t<string>('own stake'),
|
||||
rankBondTotal: t<string>('total stake'),
|
||||
rankComm: t<string>('commission'),
|
||||
rankOverall: t<string>('profit/era est')
|
||||
rankComm: t<string>('comm.'),
|
||||
rankNumNominators: t<string>('nominators'),
|
||||
rankOverall: t<string>('profit/era')
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
|
||||
const header = useMemo(() => [
|
||||
[t('validators'), 'start', 4],
|
||||
...['rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header) => [
|
||||
...['rankNumNominators', 'rankComm', 'rankBondTotal', 'rankBondOwn', 'rankBondOther', 'rankOverall'].map((header) => [
|
||||
<>{labels[header]}<Icon name={sortBy === header ? (sortFromMax ? 'chevron down' : 'chevron up') : 'minus'} /></>,
|
||||
sorted ? `isClickable ${sortBy === header ? 'ui--highlight--border' : ''} number` : 'number',
|
||||
1,
|
||||
@ -106,17 +117,33 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
|
||||
const filter = useMemo(() => (
|
||||
sorted && (
|
||||
<InputBalance
|
||||
className='balanceInput'
|
||||
help={t<string>('The amount that will be used on a per-validator basis to calculate profits for that validator.')}
|
||||
isFull
|
||||
isZeroable={false}
|
||||
label={t<string>('amount to use for estimation')}
|
||||
onChange={setCalcWith}
|
||||
value={calcWith}
|
||||
/>
|
||||
<div>
|
||||
<InputBalance
|
||||
className='balanceInput'
|
||||
help={t<string>('The amount that will be used on a per-validator basis to calculate profits for that validator.')}
|
||||
isFull
|
||||
isZeroable={false}
|
||||
label={t<string>('amount to use for estimation')}
|
||||
onChange={setCalcWith}
|
||||
value={calcWith}
|
||||
/>
|
||||
<div className='staking--optionsBar'>
|
||||
{api.query.identity && (
|
||||
<Toggle
|
||||
className='staking--buttonToggle'
|
||||
label={
|
||||
withoutName
|
||||
? t<string>('with/without identity')
|
||||
: t<string>('only with an identity')
|
||||
}
|
||||
onChange={setWithoutName}
|
||||
value={withoutName}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
), [calcWith, setCalcWith, sorted, t]);
|
||||
), [api, calcWith, setCalcWith, sorted, t, withoutName]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
@ -151,6 +178,7 @@ function Targets ({ className = '', ownStashes, targets: { calcWith, lastReward,
|
||||
key={validators[index].key}
|
||||
toggleFavorite={toggleFavorite}
|
||||
toggleSelected={_toggleSelected}
|
||||
withoutName={withoutName}
|
||||
/>
|
||||
)}
|
||||
</Table>
|
||||
@ -166,6 +194,7 @@ export default React.memo(styled(Targets)`
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.ui--Table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@ import { DeriveStakingOverview } from '@polkadot/api-derive/types';
|
||||
import { AppProps as Props } from '@polkadot/react-components/types';
|
||||
import { ElectionStatus } from '@polkadot/types/interfaces';
|
||||
|
||||
import React, { useEffect, useMemo, useReducer, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
@ -30,10 +30,6 @@ interface Validators {
|
||||
validators?: string[];
|
||||
}
|
||||
|
||||
function reduceNominators (nominators: string[], additional: string[]): string[] {
|
||||
return nominators.concat(...additional.filter((nominator): boolean => !nominators.includes(nominator)));
|
||||
}
|
||||
|
||||
function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
@ -47,7 +43,6 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
const isInElection = useCall<boolean>(api.query.staking?.eraElectionStatus, [], {
|
||||
transform: (status: ElectionStatus) => status.isOpen
|
||||
});
|
||||
const [nominators, dispatchNominators] = useReducer(reduceNominators, [] as string[]);
|
||||
const hasQueries = useMemo(
|
||||
(): boolean =>
|
||||
hasAccounts && !!(api.query.imOnline?.authoredBlocks) && !!(api.query.staking.activeEra),
|
||||
@ -114,7 +109,7 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
<Summary
|
||||
isVisible={pathname === basePath}
|
||||
next={next}
|
||||
nominators={nominators}
|
||||
nominators={targets.nominators}
|
||||
stakingOverview={stakingOverview}
|
||||
/>
|
||||
<Switch>
|
||||
@ -126,7 +121,9 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
</Route>
|
||||
<Route path={`${basePath}/targets`}>
|
||||
<Targets
|
||||
next={next}
|
||||
ownStashes={ownStashes}
|
||||
stakingOverview={stakingOverview}
|
||||
targets={targets}
|
||||
/>
|
||||
</Route>
|
||||
@ -152,7 +149,6 @@ function StakingApp ({ basePath, className = '' }: Props): React.ReactElement<Pr
|
||||
className={basePath === pathname ? '' : 'staking--hidden'}
|
||||
hasQueries={hasQueries}
|
||||
next={next}
|
||||
setNominators={dispatchNominators}
|
||||
stakingOverview={stakingOverview}
|
||||
/>
|
||||
</main>
|
||||
@ -175,4 +171,13 @@ export default React.memo(styled(StakingApp)`
|
||||
margin: 2.5rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
.staking--optionsBar {
|
||||
text-align: right;
|
||||
|
||||
.staking--buttonToggle {
|
||||
margin-left: 1.5rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@ -33,6 +33,7 @@ interface ValidatorInfoRank {
|
||||
rankBondOwn: number;
|
||||
rankBondTotal: number;
|
||||
rankComm: number;
|
||||
rankNumNominators: number;
|
||||
rankOverall: number;
|
||||
rankPayment: number;
|
||||
rankReward: number;
|
||||
@ -45,6 +46,7 @@ export interface ValidatorInfo extends ValidatorInfoRank {
|
||||
bondShare: number;
|
||||
bondTotal: Balance;
|
||||
commissionPer: number;
|
||||
hasIdentity: boolean;
|
||||
isCommission: boolean;
|
||||
isFavorite: boolean;
|
||||
isNominating: boolean;
|
||||
|
||||
@ -44,6 +44,8 @@ function sortValidators (list: ValidatorInfo[]): ValidatorInfo[] {
|
||||
.map(mapIndex('rankPayment'))
|
||||
.sort((a, b) => a.rewardSplit.cmp(b.rewardSplit))
|
||||
.map(mapIndex('rankReward'))
|
||||
.sort((a, b) => b.numNominators - a.numNominators)
|
||||
.map(mapIndex('rankNumNominators'))
|
||||
.sort((a, b): number => {
|
||||
const cmp = b.rewardPayout.cmp(a.rewardPayout);
|
||||
|
||||
@ -106,6 +108,7 @@ function extractInfo (allAccounts: string[], amount: BN = baseBalance(), elected
|
||||
bondShare: 0,
|
||||
bondTotal,
|
||||
commissionPer: (((prefs as ValidatorPrefs).commission?.unwrap() || BN_ZERO).toNumber() / 10_000_000),
|
||||
hasIdentity: false,
|
||||
isCommission: !!(prefs as ValidatorPrefs).commission,
|
||||
isFavorite: favorites.includes(key),
|
||||
isNominating,
|
||||
@ -115,6 +118,7 @@ function extractInfo (allAccounts: string[], amount: BN = baseBalance(), elected
|
||||
rankBondOwn: 0,
|
||||
rankBondTotal: 0,
|
||||
rankComm: 0,
|
||||
rankNumNominators: 0,
|
||||
rankOverall: 0,
|
||||
rankPayment: 0,
|
||||
rankReward: 0,
|
||||
|
||||
@ -22,6 +22,7 @@ interface Props extends BareProps {
|
||||
bonded?: BN | BN[];
|
||||
children?: React.ReactNode;
|
||||
iconInfo?: React.ReactNode;
|
||||
isHighlight?: boolean;
|
||||
isPadded?: boolean;
|
||||
isShort?: boolean;
|
||||
label?: React.ReactNode;
|
||||
@ -39,13 +40,13 @@ interface Props extends BareProps {
|
||||
withShrink?: boolean;
|
||||
}
|
||||
|
||||
function AddressMini ({ balance, bonded, children, className = '', iconInfo, isPadded = true, label, labelBalance, noLookup, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
|
||||
function AddressMini ({ balance, bonded, children, className = '', iconInfo, isHighlight, isPadded = true, label, labelBalance, noLookup, summary, value, withAddress = true, withBalance = false, withBonded = false, withLockedVote = false, withName = true, withShrink = false, withSidebar = true }: Props): React.ReactElement<Props> | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={classes('ui--AddressMini', isPadded ? 'padded' : '', withShrink ? 'withShrink' : '', className)}>
|
||||
<div className={classes('ui--AddressMini', isHighlight ? 'isHighlight' : '', isPadded ? 'padded' : '', withShrink ? 'withShrink' : '', className)}>
|
||||
{label && (
|
||||
<label className='ui--AddressMini-label'>{label}</label>
|
||||
)}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user