From 3b4dcd13d83744a160b3ad60bff33b094a6fa7ef Mon Sep 17 00:00:00 2001 From: Chevdor Date: Thu, 13 Sep 2018 15:50:47 +0200 Subject: [PATCH] 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 --- .../app-staking/src/Overview/CurrentList.tsx | 98 ++++++++++++++++ .../src/{ => Overview}/Summary.tsx | 2 +- packages/app-staking/src/Overview/index.css | 110 ++++++++++++++++++ packages/app-staking/src/Overview/index.tsx | 59 ++++++++++ .../src/{ => StakeList}/Account.tsx | 3 +- .../src/{ => StakeList}/Nominating.tsx | 2 +- .../src/{ => StakeList}/UnnominateButton.tsx | 2 +- packages/app-staking/src/StakeList/index.css | 87 ++++++++++++++ .../{StakeList.tsx => StakeList/index.tsx} | 2 +- packages/app-staking/src/index.css | 8 +- packages/app-staking/src/index.tsx | 65 +++++++++-- .../apps/public/locales/en/validators.json | 1 + packages/apps/src/routing/index.ts | 3 +- packages/ui-app/src/AddressRow.tsx | 4 +- packages/ui-app/src/AddressSummary.tsx | 10 +- 15 files changed, 429 insertions(+), 27 deletions(-) create mode 100644 packages/app-staking/src/Overview/CurrentList.tsx rename packages/app-staking/src/{ => Overview}/Summary.tsx (98%) create mode 100644 packages/app-staking/src/Overview/index.css create mode 100644 packages/app-staking/src/Overview/index.tsx rename packages/app-staking/src/{ => StakeList}/Account.tsx (98%) rename packages/app-staking/src/{ => StakeList}/Nominating.tsx (98%) rename packages/app-staking/src/{ => StakeList}/UnnominateButton.tsx (97%) create mode 100644 packages/app-staking/src/StakeList/index.css rename packages/app-staking/src/{StakeList.tsx => StakeList/index.tsx} (97%) create mode 100644 packages/apps/public/locales/en/validators.json diff --git a/packages/app-staking/src/Overview/CurrentList.tsx b/packages/app-staking/src/Overview/CurrentList.tsx new file mode 100644 index 0000000000..1ce9c110ee --- /dev/null +++ b/packages/app-staking/src/Overview/CurrentList.tsx @@ -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 + next: Array +}; + +class CurrentList extends React.PureComponent { + render () { + return ( +
+
+ {this.renderCurrent()} +
+
+ {this.renderNext()} +
+
+ ); + } + + private renderCurrent () { + const { current, t } = this.props; + + if (current.length === 0) { + return null; + } + + return [ +

+ {t('list.current', { + defaultValue: 'Current: {{count}}', + replace: { + count: current.length + } + })} +

, + ...current.map((account) => { + return ( + + ); + }) + ]; + } + + private renderNext () { + const { next, t } = this.props; + + if (next.length === 0) { + return null; + } + + return [ +

+ {t('list.next', { + defaultValue: 'Next up: {{count}}', + replace: { + count: next.length + } + })} +

, + ...next.map((account) => { + return ( + + ); + }) + ]; + } +} + +export default translate(CurrentList); diff --git a/packages/app-staking/src/Summary.tsx b/packages/app-staking/src/Overview/Summary.tsx similarity index 98% rename from packages/app-staking/src/Summary.tsx rename to packages/app-staking/src/Overview/Summary.tsx index d8d5137e25..42808a0dfa 100644 --- a/packages/app-staking/src/Summary.tsx +++ b/packages/app-staking/src/Overview/Summary.tsx @@ -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, diff --git a/packages/app-staking/src/Overview/index.css b/packages/app-staking/src/Overview/index.css new file mode 100644 index 0000000000..c532b1f8a2 --- /dev/null +++ b/packages/app-staking/src/Overview/index.css @@ -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; +} diff --git a/packages/app-staking/src/Overview/index.tsx b/packages/app-staking/src/Overview/index.tsx new file mode 100644 index 0000000000..6746482437 --- /dev/null +++ b/packages/app-staking/src/Overview/index.tsx @@ -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, + validators: Array +}; + +const ZERO = new BN(0); + +export default class Overview extends React.PureComponent { + render () { + const { balances, intentions, validators } = this.props; + const intentionsSorted = this.sortByBalance( + intentions.filter((address) => + !validators.includes(address) + ) + ); + const validatorsSorted = this.sortByBalance(validators); + + return ( +
+ + +
+ ); + } + + private sortByBalance (list: Array): Array { + 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); + }); + } +} diff --git a/packages/app-staking/src/Account.tsx b/packages/app-staking/src/StakeList/Account.tsx similarity index 98% rename from packages/app-staking/src/Account.tsx rename to packages/app-staking/src/StakeList/Account.tsx index 739b1d4e65..a39b2eec5f 100644 --- a/packages/app-staking/src/Account.tsx +++ b/packages/app-staking/src/StakeList/Account.tsx @@ -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 { balance={this.balanceArray(address)} name={name} value={address} + identIconSize={96} >
{this.renderButtons()} diff --git a/packages/app-staking/src/Nominating.tsx b/packages/app-staking/src/StakeList/Nominating.tsx similarity index 98% rename from packages/app-staking/src/Nominating.tsx rename to packages/app-staking/src/StakeList/Nominating.tsx index cb83abaa45..11ea31fe14 100644 --- a/packages/app-staking/src/Nominating.tsx +++ b/packages/app-staking/src/StakeList/Nominating.tsx @@ -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, diff --git a/packages/app-staking/src/UnnominateButton.tsx b/packages/app-staking/src/StakeList/UnnominateButton.tsx similarity index 97% rename from packages/app-staking/src/UnnominateButton.tsx rename to packages/app-staking/src/StakeList/UnnominateButton.tsx index 6b26059264..9e55cf1a9d 100644 --- a/packages/app-staking/src/UnnominateButton.tsx +++ b/packages/app-staking/src/StakeList/UnnominateButton.tsx @@ -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, diff --git a/packages/app-staking/src/StakeList/index.css b/packages/app-staking/src/StakeList/index.css new file mode 100644 index 0000000000..2a369d7715 --- /dev/null +++ b/packages/app-staking/src/StakeList/index.css @@ -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; +} diff --git a/packages/app-staking/src/StakeList.tsx b/packages/app-staking/src/StakeList/index.tsx similarity index 97% rename from packages/app-staking/src/StakeList.tsx rename to packages/app-staking/src/StakeList/index.tsx index dba248b6ed..e802832f9f 100644 --- a/packages/app-staking/src/StakeList.tsx +++ b/packages/app-staking/src/StakeList/index.tsx @@ -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, diff --git a/packages/app-staking/src/index.css b/packages/app-staking/src/index.css index 3b54d118d4..121268bb0d 100644 --- a/packages/app-staking/src/index.css +++ b/packages/app-staking/src/index.css @@ -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; } diff --git a/packages/app-staking/src/index.tsx b/packages/app-staking/src/index.tsx index 868da8aaae..30f7139034 100644 --- a/packages/app-staking/src/index.tsx +++ b/packages/app-staking/src/index.tsx @@ -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, sessionValidators?: Array }; -class App extends React.PureComponent { +type State = { + action: Actions +}; + +// FIXME React-router would probably be the best route, not home-grown +const Components: { [index: string]: React.ComponentType } = { + 'overview': Overview, + 'actions': StakeList +}; + +class App extends React.PureComponent { + 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 (
- - + + + {
); } + + onMenuChange = (action: Actions) => { + this.setState({ action }); + } } export default withMulti( App, + translate, withObservable('stakingIntentions'), withObservable('sessionValidators'), withObservable('validatingBalances', { paramProp: 'stakingIntentions' }) diff --git a/packages/apps/public/locales/en/validators.json b/packages/apps/public/locales/en/validators.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/packages/apps/public/locales/en/validators.json @@ -0,0 +1 @@ +{} diff --git a/packages/apps/src/routing/index.ts b/packages/apps/src/routing/index.ts index efb3f21b4f..3a9b6dcd76 100644 --- a/packages/apps/src/routing/index.ts +++ b/packages/apps/src/routing/index.ts @@ -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'; diff --git a/packages/ui-app/src/AddressRow.tsx b/packages/ui-app/src/AddressRow.tsx index de69538c08..02bffc6637 100644 --- a/packages/ui-app/src/AddressRow.tsx +++ b/packages/ui-app/src/AddressRow.tsx @@ -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 {
diff --git a/packages/ui-app/src/AddressSummary.tsx b/packages/ui-app/src/AddressSummary.tsx index 9cfab2518b..8ca5e57ede 100644 --- a/packages/ui-app/src/AddressSummary.tsx +++ b/packages/ui-app/src/AddressSummary.tsx @@ -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 { } 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 { {name}
- {shortValue} + {isShort ? shortValue : value}
{this.renderCopy()}
@@ -131,7 +133,7 @@ class AddressSummary extends React.PureComponent { } 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 { return ( );