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

Rework staking lists, more info per page #1972

Merged
merged 6 commits into from
Dec 1, 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
52 changes: 52 additions & 0 deletions packages/app-staking/src/AddressSmall.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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.

import { Address, AccountId } from '@polkadot/types/interfaces';

import React from 'react';
import styled from 'styled-components';
import { IdentityIcon } from '@polkadot/react-components';
import { AccountIndex, AccountName } from '@polkadot/react-query';

interface Props {
className?: string;
defaultName?: string;
value?: string | Address | AccountId;
}

function AddressSmall ({ className, defaultName, value }: Props): React.ReactElement<Props> {
return (
<div className={`ui--AddressSmall ${className}`}>
<IdentityIcon
size={32}
value={value}
/>
<div className='nameInfo'>
<AccountName defaultName={defaultName} params={value} />
<AccountIndex params={value} />
</div>
</div>
);
}

export default styled(AddressSmall)`
vertical-align: middle;
white-space: nowrap;

.ui--IdentityIcon,
.nameInfo {
display: inline-block;
vertical-align: middle;
}

.ui--IdentityIcon {
margin-right: 0.75rem;
}

.nameInfo > div {
max-width: 16rem;
overflow: hidden;
text-overflow: ellipsis;
}
`;
226 changes: 113 additions & 113 deletions packages/app-staking/src/Overview/Address.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.

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

import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { AddressCard, AddressMini, Badge, Expander, Icon } from '@polkadot/react-components';
import { classes } from '@polkadot/react-components/util';
import { AddressMini, Badge, Icon } from '@polkadot/react-components';
import { trackStream, useApi } from '@polkadot/react-hooks';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';

import AddressSmall from '../AddressSmall';
import translate from '../translate';

interface Props extends I18nProps {
Expand All @@ -35,61 +36,59 @@ interface Props extends I18nProps {
}

interface StakingState {
balanceOpts: { bonded: boolean | BN[] };
commission?: string;
controllerId?: string;
hasNominators: boolean;
isNominatorMe: boolean;
nominators: [AccountId, Balance][];
sessionId?: string;
stakeTotal?: BN;
stakeOther?: BN;
stakeOwn?: BN;
stashId: string;
validatorPayment?: BN;
}

const WITH_VALIDATOR_PREFS = { validatorPayment: true };

function Address ({ address, authorsMap, className, defaultName, filter, hasQueries, isElected, isFavorite, lastAuthors, myAccounts, points, recentlyOnline, t, toggleFavorite, withNominations = true }: Props): React.ReactElement<Props> | null {
const { api, isSubstrateV2 } = useApi();
function Address ({ address, authorsMap, className, filter, hasQueries, isElected, isFavorite, lastAuthors, myAccounts, points, recentlyOnline, t, toggleFavorite, withNominations = true }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
// FIXME Any horrors, caused by derive type mismatches
const stakingInfo = trackStream<DerivedStaking>(api.derive.staking.info as any, [address]);
const [extraInfo, setExtraInfo] = useState<[React.ReactNode, React.ReactNode][] | undefined>();
const [hasActivity, setHasActivity] = useState(true);
const [{ balanceOpts, controllerId, hasNominators, isNominatorMe, nominators, sessionId, stashId }, setStakingState] = useState<StakingState>({
balanceOpts: { bonded: true },
const [{ commission, hasNominators, isNominatorMe, nominators, stashId, stakeOwn, stakeOther, validatorPayment }, setStakingState] = useState<StakingState>({
hasNominators: false,
isNominatorMe: false,
nominators: [],
stashId: address.toString()
});

useEffect((): void => {
if (points) {
const formatted = formatNumber(points);

if (!extraInfo || extraInfo[0][1] !== formatted) {
setExtraInfo([[t('era points'), formatted]]);
}
}
}, [extraInfo, points]);
const [isExpanded, setIsExpanded] = useState(false);

useEffect((): void => {
if (stakingInfo) {
const { controllerId, nextSessionIds, stakers, stashId } = stakingInfo;
const { controllerId, nextSessionIds, stakers, stashId, validatorPrefs } = stakingInfo;
const nominators = withNominations && stakers
? stakers.others.map(({ who, value }): [AccountId, Balance] => [who, value.unwrap()])
: [];
const stakersOwn = stakers && !stakers.own.isEmpty && stakers.own.unwrap();
const stakeTotal = (stakers && !stakers.total.isEmpty && stakers.total.unwrap()) || undefined;
const stakeOwn = (stakers && !stakers.own.isEmpty && stakers.own.unwrap()) || undefined;
const stakeOther = (stakeTotal && stakeOwn) ? stakeTotal.sub(stakeOwn) : undefined;
const commission = validatorPrefs?.commission?.unwrap();

setStakingState({
balanceOpts: stakers && stakersOwn
? { bonded: [stakersOwn, stakers.total.unwrap().sub(stakersOwn)] }
: { bonded: true },
commission: commission
? `${(commission.toNumber() / 10000000).toFixed(2)}%`
: undefined,
controllerId: controllerId?.toString(),
hasNominators: nominators.length !== 0,
isNominatorMe: nominators.some(([who]): boolean =>
myAccounts.includes(who.toString())
),
nominators,
sessionId: nextSessionIds && nextSessionIds[0]?.toString(),
stashId: (stashId || address).toString()
stashId: (stashId || address).toString(),
stakeOther,
stakeOwn,
stakeTotal,
validatorPayment: (validatorPrefs as any as ValidatorPrefsTo196)?.validatorPayment?.unwrap()
});
}
}, [stakingInfo]);
Expand All @@ -115,98 +114,99 @@ function Address ({ address, authorsMap, className, defaultName, filter, hasQuer
const _onQueryStats = (): void => {
window.location.hash = `/staking/query/${stashId}`;
};
const _toggleNominators = (event: React.SyntheticEvent): void => {
event.preventDefault();
event.stopPropagation();

setIsExpanded(!isExpanded);
};

return (
<AddressCard
buttons={
<div className='staking--Address-info'>
{isSubstrateV2 && (
<div className='extras'>
{lastBlockNumber && (
<div className={`blockNumberV${isSubstrateV2 ? '2' : '1'} ${isAuthor && 'isCurrent'}`}>#{lastBlockNumber}</div>
)}
<tr className={`${className} ${isAuthor && 'isHighlight'}`}>
<td className='favorite'>
<Icon
className={`${isFavorite && 'isSelected'}`}
name={isFavorite ? 'star' : 'star outline'}
onClick={_onFavorite}
/>
</td>
<td className='together'>
{isElected && (
<Badge
hover={t('Selected for the next session')}
info={<Icon name='chevron right' />}
isInline
isTooltip
type='next'
/>
)}
{recentlyOnline && hasActivity && recentlyOnline[stashId] && (
<Badge
hover={t('Active with {{blocks}} blocks authored{{hasMessage}} heartbeat message', {
replace: {
blocks: formatNumber(recentlyOnline[stashId].blockCount),
hasMessage: recentlyOnline[stashId].hasMessage ? ' and a' : ', no'
}
})}
info={<Icon name='check' />}
isInline
isTooltip
type='online'
/>
)}
</td>
<td>
<AddressSmall value={stashId} />
</td>
<td className='number'>
{stakeOwn && <FormatBalance label={<label>{t('own stake')}</label>} value={stakeOwn} />}
</td>
<td className={'toggle number'} colSpan={isExpanded ? 5 : 1} onClick={_toggleNominators}>
{stakeOther && (
isExpanded
? (
<div>
{nominators.map(([who, bonded]): React.ReactNode =>
<AddressMini
bonded={bonded}
key={who.toString()}
value={who}
withBonded
/>
)}
</div>
)
: <FormatBalance label={<label>{t('other stake')}</label>} value={stakeOther}>&nbsp;({formatNumber(nominators.length)})&nbsp;<Icon name='angle double right' /></FormatBalance>
)}
</td>
{!isExpanded && (
<>
<td className='number'>
{(commission || validatorPayment) && (
commission
? <><label>{t('commission')}</label>{commission}</>
: <FormatBalance label={<label>{t('commission')}</label>} value={validatorPayment} />
)}
</td>
<td className='number'>
{points && points.gtn(0) && (
<><label>{t('points')}</label>{formatNumber(points)}</>
)}
</td>
<td className='number'>
{lastBlockNumber && <><label>{t('last #')}</label>{lastBlockNumber}</>}
</td>
<td>
{hasQueries && api.query.imOnline?.authoredBlocks && (
<Icon
className={`favorite ${isFavorite && 'isSelected'}`}
name={isFavorite ? 'star' : 'star outline'}
onClick={_onFavorite}
name='line graph'
onClick={_onQueryStats}
/>
</div>
)}
{controllerId && (
<div>
<label className={classes('staking--label', isSubstrateV2 && !lastBlockNumber && 'controllerSpacer')}>{t('controller')}</label>
<AddressMini value={controllerId} />
</div>
)}
{!isSubstrateV2 && sessionId && (
<div>
<label className='staking--label'>{t('session')}</label>
<AddressMini value={sessionId} />
</div>
)}
</div>
}
className={className}
defaultName={defaultName}
extraInfo={extraInfo}
iconInfo={
<>
{isElected && (
<Badge
hover={t('Selected for the next session')}
info={<Icon name='chevron right' />}
isTooltip
type='next'
/>
)}
{recentlyOnline && hasActivity && recentlyOnline[stashId] && (
<Badge
hover={t('Active with {{blocks}} blocks authored{{hasMessage}} heartbeat message', {
replace: {
blocks: formatNumber(recentlyOnline[stashId].blockCount),
hasMessage: recentlyOnline[stashId].hasMessage ? ' and a' : ', no'
}
})}
info={<Icon name='check' />}
isTooltip
type='online'
/>
)}
)}
</td>
</>
}
overlay={
hasQueries && api.query.imOnline?.authoredBlocks && (
<Icon
className='staking--stats'
name='line graph'
onClick={_onQueryStats}
/>
)
}
stakingInfo={stakingInfo}
value={stashId}
withBalance={balanceOpts}
withValidatorPrefs={WITH_VALIDATOR_PREFS}
>
{withNominations && hasNominators && (
<Expander
summary={t('Nominators ({{count}})', {
replace: {
count: nominators.length
}
})}
>
{nominators.map(([who, bonded]): React.ReactNode =>
<AddressMini
bonded={bonded}
key={who.toString()}
value={who}
withBonded
/>
)}
</Expander>
)}
</AddressCard>
</tr>
);
}

Expand Down
Loading