Add displaying of the validators (#300)
* Add validators App Fix #191 * Pull validator breakidown into staking overview * Fix layout * Add AddressRow component * Minor CSS fix * Use standard address components in the staking app * Use new components and cleanup * Refactor and layout work on the validators * Re-use already available components * Remove app-validators in tsconfig
This commit is contained in:
parent
f16160c8f3
commit
3b4dcd13d8
98
packages/app-staking/src/Overview/CurrentList.tsx
Normal file
98
packages/app-staking/src/Overview/CurrentList.tsx
Normal file
@ -0,0 +1,98 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import AddressRow from '@polkadot/ui-app/AddressRow';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
current: Array<string>
|
||||
next: Array<string>
|
||||
};
|
||||
|
||||
class CurrentList extends React.PureComponent<Props> {
|
||||
render () {
|
||||
return (
|
||||
<div className='validator--ValidatorsList'>
|
||||
<div className='validator--current'>
|
||||
{this.renderCurrent()}
|
||||
</div>
|
||||
<div className='validator--next'>
|
||||
{this.renderNext()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private renderCurrent () {
|
||||
const { current, t } = this.props;
|
||||
|
||||
if (current.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
<h4>
|
||||
{t('list.current', {
|
||||
defaultValue: 'Current: {{count}}',
|
||||
replace: {
|
||||
count: current.length
|
||||
}
|
||||
})}
|
||||
</h4>,
|
||||
...current.map((account) => {
|
||||
return (
|
||||
<AddressRow
|
||||
className='validator--row'
|
||||
key={account}
|
||||
name={name || t('name.validator', { defaultValue: 'validator' })}
|
||||
value={account}
|
||||
withBalance={true}
|
||||
withNonce={false}
|
||||
identIconSize={48}
|
||||
isShort={true}
|
||||
/>
|
||||
);
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
private renderNext () {
|
||||
const { next, t } = this.props;
|
||||
|
||||
if (next.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
<h4>
|
||||
{t('list.next', {
|
||||
defaultValue: 'Next up: {{count}}',
|
||||
replace: {
|
||||
count: next.length
|
||||
}
|
||||
})}
|
||||
</h4>,
|
||||
...next.map((account) => {
|
||||
return (
|
||||
<AddressRow
|
||||
className='validator--row'
|
||||
key={account}
|
||||
name={name || t('name.intention', { defaultValue: 'intention' })}
|
||||
value={account}
|
||||
withBalance={true}
|
||||
withNonce={false}
|
||||
identIconSize={48}
|
||||
isShort={true}
|
||||
/>
|
||||
);
|
||||
})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(CurrentList);
|
||||
@ -10,7 +10,7 @@ import React from 'react';
|
||||
import CardSummary from '@polkadot/ui-app/CardSummary';
|
||||
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
|
||||
|
||||
import translate from './translate';
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
balances: RxBalanceMap,
|
||||
110
packages/app-staking/src/Overview/index.css
Normal file
110
packages/app-staking/src/Overview/index.css
Normal file
@ -0,0 +1,110 @@
|
||||
/* Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the ISC license. See the LICENSE file for details. */
|
||||
|
||||
.validator--Account-info-address {
|
||||
flex: 3;
|
||||
margin: 0px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.validator--Account-info-details {
|
||||
flex: 1;
|
||||
margin: 0px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.validator--row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-bottom: 1px solid #ccc;
|
||||
margin-bottom: 5px;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.validator--Account-address {
|
||||
font-family: monospace;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.validator--Account-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.validator--Account-validating {
|
||||
opacity: 0.25 !important;
|
||||
padding-top: 8px;
|
||||
vertical-align: top !important;
|
||||
}
|
||||
|
||||
.validator--Account-validating.isValidator {
|
||||
color: red;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.validator--Account-details,.column{
|
||||
flex: 1;
|
||||
border: 1px, solid blue;
|
||||
}
|
||||
|
||||
.validator--Account-balance,
|
||||
.validator--Account-nonce {
|
||||
opacity: 0.45;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.validator--ValidatorsList{
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.validator--IntensionsList{
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.validator--current,.validator--next{
|
||||
margin: 20px;
|
||||
flex: 1;
|
||||
}
|
||||
.app-validators--Query .app-validators--actionrow-value {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.app-validators--Query .ui--IdentityIcon {
|
||||
margin: -10px 0;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.app-validators--actionrow {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.app-validators--actionrow-value {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.app-validators--actionrow-button {
|
||||
flex: 0;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.validator--Account-address.isMine {
|
||||
color: #47b547;
|
||||
font-weight: 500;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.validator-icon {
|
||||
color: #2284d0;
|
||||
position: absolute;
|
||||
font-size: 1.5em !important;
|
||||
}
|
||||
|
||||
.nominator-icon {
|
||||
color: #aaa;
|
||||
position: absolute;
|
||||
font-size: 1.5em !important;
|
||||
}
|
||||
|
||||
.ui--AddressRow-icon {
|
||||
margin: 12px;
|
||||
}
|
||||
59
packages/app-staking/src/Overview/index.tsx
Normal file
59
packages/app-staking/src/Overview/index.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
// Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { RxBalanceMap } from '@polkadot/ui-react-rx/ApiObservable/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import './index.css';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
|
||||
import CurrentList from './CurrentList';
|
||||
import Summary from './Summary';
|
||||
|
||||
type Props = I18nProps & {
|
||||
balances: RxBalanceMap,
|
||||
intentions: Array<string>,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
const ZERO = new BN(0);
|
||||
|
||||
export default class Overview extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { balances, intentions, validators } = this.props;
|
||||
const intentionsSorted = this.sortByBalance(
|
||||
intentions.filter((address) =>
|
||||
!validators.includes(address)
|
||||
)
|
||||
);
|
||||
const validatorsSorted = this.sortByBalance(validators);
|
||||
|
||||
return (
|
||||
<div className='staking--Overview'>
|
||||
<Summary
|
||||
balances={balances}
|
||||
intentions={intentions}
|
||||
validators={validators}
|
||||
/>
|
||||
<CurrentList
|
||||
current={validatorsSorted}
|
||||
next={intentionsSorted}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private sortByBalance (list: Array<string>): Array<string> {
|
||||
const { balances } = this.props;
|
||||
|
||||
return list.sort((a, b) => {
|
||||
const balanceA = balances[a] || { stakingBalance: ZERO };
|
||||
const balanceB = balances[b] || { stakingBalance: ZERO };
|
||||
|
||||
return balanceB.stakingBalance.cmp(balanceA.stakingBalance);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -23,7 +23,7 @@ import decodeAddress from '@polkadot/util-keyring/address/decode';
|
||||
|
||||
import Nominating from './Nominating';
|
||||
import UnnominateButton from './UnnominateButton';
|
||||
import translate from './translate';
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
systemAccountIndexOf?: BN,
|
||||
@ -72,6 +72,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
balance={this.balanceArray(address)}
|
||||
name={name}
|
||||
value={address}
|
||||
identIconSize={96}
|
||||
>
|
||||
<div className='staking--Account-expand'>
|
||||
{this.renderButtons()}
|
||||
@ -10,7 +10,7 @@ import Button from '@polkadot/ui-app/Button';
|
||||
import Input from '@polkadot/ui-app/Input';
|
||||
import Modal from '@polkadot/ui-app/Modal';
|
||||
|
||||
import translate from './translate';
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
isOpen: boolean,
|
||||
@ -9,7 +9,7 @@ import Button from '@polkadot/ui-app/Button';
|
||||
import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
import withObservable from '@polkadot/ui-react-rx/with/observable';
|
||||
|
||||
import translate from './translate';
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
address: string,
|
||||
87
packages/app-staking/src/StakeList/index.css
Normal file
87
packages/app-staking/src/StakeList/index.css
Normal file
@ -0,0 +1,87 @@
|
||||
/* Copyright 2017-2018 @polkadot/app-staking authors & contributors
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the ISC license. See the LICENSE file for details. */
|
||||
|
||||
.staking--Summary {
|
||||
margin-bottom: 2rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.staking--StakeList {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.staking--Account {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.staking--Account .ui--Button-Group {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.staking--Account .ui--AddressMini {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.staking--Account .ui--AddressSummary {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.staking--Account-expand {
|
||||
min-width: 14rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.staking--Account-details {
|
||||
display: inline-block;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.staking--Account-nominate {
|
||||
padding: 0 0 3em 3em;
|
||||
}
|
||||
|
||||
.staking--Account-nominating {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.staking--Account-nominating * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.staking--Account-nominating .ui--IdentityIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.staking--Account-info {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.staking--Account-address {
|
||||
font-family: monospace;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.staking--Account-icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.staking--Account-validating {
|
||||
opacity: 0.25 !important;
|
||||
position: absolute;
|
||||
top: 1em;
|
||||
left: 1em;
|
||||
}
|
||||
|
||||
.staking--Account-validating.isValidator {
|
||||
color: red;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
|
||||
.staking--Account-balance,
|
||||
.staking--Account-nonce {
|
||||
opacity: 0.45;
|
||||
text-align: right;
|
||||
}
|
||||
@ -11,7 +11,7 @@ import keyring from '@polkadot/ui-keyring/index';
|
||||
import { QueueConsumer } from '@polkadot/ui-signer/Context';
|
||||
|
||||
import Account from './Account';
|
||||
import translate from './translate';
|
||||
import translate from '../translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
balances: RxBalanceMap,
|
||||
@ -2,12 +2,12 @@
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the ISC license. See the LICENSE file for details. */
|
||||
|
||||
.staking--Summary {
|
||||
margin-bottom: 2rem;
|
||||
text-align: right;
|
||||
.staking--App {
|
||||
padding: 1em 2rem;
|
||||
}
|
||||
|
||||
.staking--StakeList {
|
||||
.staking--App-navigation.ui--Button-Group {
|
||||
margin-bottom: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
||||
@ -2,37 +2,75 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { RxBalanceMap } from '@polkadot/ui-react-rx/ApiObservable/types';
|
||||
|
||||
import React from 'react';
|
||||
import Tabs from '@polkadot/ui-app/Tabs';
|
||||
import withObservable from '@polkadot/ui-react-rx/with/observable';
|
||||
import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
|
||||
import './index.css';
|
||||
|
||||
import StakeList from './StakeList';
|
||||
import Summary from './Summary';
|
||||
import Overview from './Overview';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = BareProps & {
|
||||
type Actions = 'actions' | 'overview';
|
||||
|
||||
type Props = I18nProps & {
|
||||
basePath: string,
|
||||
validatingBalances?: RxBalanceMap,
|
||||
stakingIntentions?: Array<string>,
|
||||
sessionValidators?: Array<string>
|
||||
};
|
||||
|
||||
class App extends React.PureComponent<Props> {
|
||||
type State = {
|
||||
action: Actions
|
||||
};
|
||||
|
||||
// FIXME React-router would probably be the best route, not home-grown
|
||||
const Components: { [index: string]: React.ComponentType<any> } = {
|
||||
'overview': Overview,
|
||||
'actions': StakeList
|
||||
};
|
||||
|
||||
class App extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
action: 'overview'
|
||||
};
|
||||
}
|
||||
|
||||
render () {
|
||||
const { sessionValidators = [], stakingIntentions = [], validatingBalances = {} } = this.props;
|
||||
const { action } = this.state;
|
||||
const { sessionValidators = [], stakingIntentions = [], t, validatingBalances = {} } = this.props;
|
||||
const Component = Components[action];
|
||||
const items = [
|
||||
{
|
||||
name: 'overview',
|
||||
text: t('app.overview', { defaultValue: 'Staking Overview' })
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
text: t('app.actions', { defaultValue: 'Account Actions' })
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<main className='staking--App'>
|
||||
<Summary
|
||||
balances={validatingBalances}
|
||||
intentions={stakingIntentions}
|
||||
validators={sessionValidators}
|
||||
/>
|
||||
<StakeList
|
||||
<header>
|
||||
<Tabs
|
||||
activeItem={action}
|
||||
items={items}
|
||||
onChange={this.onMenuChange}
|
||||
/>
|
||||
</header>
|
||||
<Component
|
||||
balances={validatingBalances}
|
||||
intentions={stakingIntentions}
|
||||
validators={sessionValidators}
|
||||
@ -40,10 +78,15 @@ class App extends React.PureComponent<Props> {
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
onMenuChange = (action: Actions) => {
|
||||
this.setState({ action });
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
App,
|
||||
translate,
|
||||
withObservable('stakingIntentions'),
|
||||
withObservable('sessionValidators'),
|
||||
withObservable('validatingBalances', { paramProp: 'stakingIntentions' })
|
||||
|
||||
1
packages/apps/public/locales/en/validators.json
Normal file
1
packages/apps/public/locales/en/validators.json
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
@ -2,7 +2,8 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Routing, Routes } from '../types';
|
||||
import { Routing,
|
||||
Routes } from '../types';
|
||||
|
||||
import accounts from './accounts';
|
||||
import addresses from './addresses';
|
||||
|
||||
@ -12,7 +12,7 @@ import translate from './translate';
|
||||
|
||||
class AddressRow extends AddressSummary {
|
||||
render () {
|
||||
const { className, style } = this.props;
|
||||
const { className, style, identIconSize } = this.props;
|
||||
const { address, isValid } = this.state;
|
||||
|
||||
return (
|
||||
@ -23,7 +23,7 @@ class AddressRow extends AddressSummary {
|
||||
<div className='ui--AddressRow-base'>
|
||||
<IdentityIcon
|
||||
className='ui--AddressRow-icon'
|
||||
size={96}
|
||||
size={identIconSize}
|
||||
value={address}
|
||||
/>
|
||||
<div className='ui--AddressRow-details'>
|
||||
|
||||
@ -23,6 +23,8 @@ export type Props = I18nProps & {
|
||||
name?: string,
|
||||
value: string | Uint8Array,
|
||||
withBalance?: boolean,
|
||||
identIconSize?: number,
|
||||
isShort?: boolean
|
||||
withCopy?: boolean,
|
||||
withIcon?: boolean,
|
||||
withNonce?: boolean
|
||||
@ -81,7 +83,7 @@ class AddressSummary extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
protected renderAddress () {
|
||||
const { name } = this.props;
|
||||
const { name, value, isShort = true } = this.props;
|
||||
const { shortValue } = this.state;
|
||||
|
||||
return (
|
||||
@ -90,7 +92,7 @@ class AddressSummary extends React.PureComponent<Props, State> {
|
||||
{name}
|
||||
</div>
|
||||
<div className='ui--AddressSummary-address'>
|
||||
{shortValue}
|
||||
{isShort ? shortValue : value}
|
||||
</div>
|
||||
{this.renderCopy()}
|
||||
</div>
|
||||
@ -131,7 +133,7 @@ class AddressSummary extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
protected renderIcon () {
|
||||
const { withIcon = true } = this.props;
|
||||
const { identIconSize = 96, withIcon = true } = this.props;
|
||||
const { address } = this.state;
|
||||
|
||||
if (!withIcon) {
|
||||
@ -141,7 +143,7 @@ class AddressSummary extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<IdentityIcon
|
||||
className='ui--AddressSummary-icon'
|
||||
size={96}
|
||||
size={identIconSize}
|
||||
value={address}
|
||||
/>
|
||||
);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user