Introduce higher-level ApiObservables, use stakingBalance displays (#210)
* Move observables to ui-react-rx * Move observables into ApiObservable * Allow AddressSummary to render passed-in balance(s) * Render Balance consistently in summaries * Style Mini balance * Use ApiObservable via HOC * Nominee balances expanded * Render nominators for account * Add full balance when intending
This commit is contained in:
parent
759ff2d5f0
commit
4d984d9f72
@ -7,6 +7,7 @@ import { Extrinsics } from '@polkadot/extrinsics/types';
|
||||
import { SectionItem } from '@polkadot/params/types';
|
||||
import { RawParam$Value } from '@polkadot/ui-app/Params/types';
|
||||
import { QueueTx$ExtrinsicAdd } from '@polkadot/ui-signer/types';
|
||||
import { ExtendedBalanceMap } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
@ -29,8 +30,9 @@ import translate from './translate';
|
||||
type Props = I18nProps & {
|
||||
accountIndex?: BN,
|
||||
address: string,
|
||||
balances: ExtendedBalanceMap,
|
||||
name: string,
|
||||
nominating?: string,
|
||||
nominee?: string,
|
||||
nominatorsFor?: Array<string>,
|
||||
intentions: Array<string>,
|
||||
isValidator: boolean,
|
||||
@ -52,7 +54,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { address, className, intentions, isValidator, name, nominating, style } = this.props;
|
||||
const { address, className, intentions, isValidator, name, style } = this.props;
|
||||
const { isNominateOpen } = this.state;
|
||||
|
||||
return (
|
||||
@ -67,6 +69,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
intentions={intentions}
|
||||
/>
|
||||
<AddressSummary
|
||||
balance={this.balanceArray(address)}
|
||||
name={name}
|
||||
value={address}
|
||||
>
|
||||
@ -77,17 +80,58 @@ class Account extends React.PureComponent<Props, State> {
|
||||
size='large'
|
||||
/>
|
||||
{this.renderButtons()}
|
||||
<AddressMini value={nominating} />
|
||||
{this.renderNominee()}
|
||||
{this.renderNominators()}
|
||||
</div>
|
||||
</AddressSummary>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private balanceArray (address: string): Array<BN> | undefined {
|
||||
const { balances } = this.props;
|
||||
|
||||
return balances[address]
|
||||
? [balances[address].stakingBalance, balances[address].nominatedBalance]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
private renderNominee () {
|
||||
const { nominee } = this.props;
|
||||
|
||||
if (!nominee) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<AddressMini
|
||||
balance={this.balanceArray(nominee)}
|
||||
value={nominee}
|
||||
withBalance
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderNominators () {
|
||||
const { nominatorsFor } = this.props;
|
||||
|
||||
if (!nominatorsFor) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nominatorsFor.map((nominator) => (
|
||||
<AddressMini
|
||||
key={nominator}
|
||||
value={nominator}
|
||||
withBalance
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
private renderButtons () {
|
||||
const { address, intentions, nominating, t } = this.props;
|
||||
const { address, intentions, nominee, t } = this.props;
|
||||
const isIntending = intentions.includes(address);
|
||||
const isNominating = !!nominating;
|
||||
const isNominating = !!nominee;
|
||||
const canStake = !isIntending && !isNominating;
|
||||
|
||||
if (canStake) {
|
||||
@ -120,7 +164,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
<Button.Group>
|
||||
<UnnominateButton
|
||||
address={address || ''}
|
||||
nominating={nominating || ''}
|
||||
nominating={nominee || ''}
|
||||
onClick={this.unnominate}
|
||||
/>
|
||||
</Button.Group>
|
||||
@ -199,7 +243,7 @@ export default withMulti(
|
||||
withStorage(
|
||||
storage.staking.public.nominating,
|
||||
{
|
||||
propName: 'nominating',
|
||||
propName: 'nominee',
|
||||
paramProp: 'address',
|
||||
transform: encodeAddress
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { QueueProps } from '@polkadot/ui-signer/types';
|
||||
import { ExtendedBalanceMap } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import React from 'react';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
@ -14,13 +15,14 @@ import Account from './Account';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
balances: ExtendedBalanceMap,
|
||||
intentions: Array<string>,
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
class StakeList extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, intentions, style, validators } = this.props;
|
||||
const { balances, className, intentions, style, validators } = this.props;
|
||||
|
||||
return (
|
||||
<QueueConsumer>
|
||||
@ -36,6 +38,7 @@ class StakeList extends React.PureComponent<Props> {
|
||||
return (
|
||||
<Account
|
||||
address={address}
|
||||
balances={balances}
|
||||
intentions={intentions}
|
||||
isValidator={validators.includes(address)}
|
||||
key={address}
|
||||
|
||||
@ -4,8 +4,7 @@
|
||||
|
||||
import { Header } from '@polkadot/primitives/header';
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
import { ApiProps } from '@polkadot/ui-react-rx/types';
|
||||
import { Balance, BalanceMap } from './observables/types';
|
||||
import { ApiProps, ExtendedBalance, ExtendedBalanceMap } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
@ -17,10 +16,10 @@ import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
import withStorage from '@polkadot/ui-react-rx/with/storage';
|
||||
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
|
||||
|
||||
import validatingBalances from './observables/validatingBalances';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = ApiProps & I18nProps & {
|
||||
balances: ExtendedBalanceMap,
|
||||
intentions: Array<string>,
|
||||
lastBlockHeader?: Header,
|
||||
lastLengthChange?: BN,
|
||||
@ -28,60 +27,11 @@ type Props = ApiProps & I18nProps & {
|
||||
validators: Array<string>
|
||||
};
|
||||
|
||||
type State = {
|
||||
balances: BalanceMap,
|
||||
subBalances: any
|
||||
};
|
||||
|
||||
const DEFAULT_BLOCKNUMBER = new BN(0);
|
||||
const DEFAULT_SESSION_CHANGE = new BN(0);
|
||||
const DEFAULT_SESSION_LENGTH = new BN(60);
|
||||
|
||||
class Summary extends React.PureComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
balances: {},
|
||||
subBalances: null
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps: Props) {
|
||||
const { intentions } = this.props;
|
||||
|
||||
if (intentions !== prevProps.intentions) {
|
||||
this.subscribeBalances(intentions);
|
||||
}
|
||||
}
|
||||
|
||||
private unsubscribeBalances () {
|
||||
const { subBalances } = this.state;
|
||||
|
||||
if (subBalances) {
|
||||
subBalances.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
private subscribeBalances (accounts: string[] = []) {
|
||||
const { api } = this.props;
|
||||
|
||||
this.unsubscribeBalances();
|
||||
|
||||
// FIXME: We want these as a HOC on the class
|
||||
const subBalances = validatingBalances(api, accounts).subscribe((balances: BalanceMap) => {
|
||||
this.setState({ balances });
|
||||
});
|
||||
|
||||
this.setState({
|
||||
subBalances
|
||||
} as State);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unsubscribeBalances();
|
||||
}
|
||||
|
||||
class Summary extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, intentions, lastBlockHeader, lastLengthChange = DEFAULT_SESSION_CHANGE, style, sessionLength = DEFAULT_SESSION_LENGTH, t, validators } = this.props;
|
||||
const blockNumber = lastBlockHeader
|
||||
@ -130,11 +80,10 @@ class Summary extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private calcIntentionsHigh (): Balance | null {
|
||||
const { intentions, validators } = this.props;
|
||||
const { balances } = this.state;
|
||||
private calcIntentionsHigh (): ExtendedBalance | null {
|
||||
const { balances, intentions, validators } = this.props;
|
||||
|
||||
return intentions.reduce((high: Balance | null, addr) => {
|
||||
return intentions.reduce((high: ExtendedBalance | null, addr) => {
|
||||
const balance = validators.includes(addr) || !balances[addr]
|
||||
? null
|
||||
: balances[addr];
|
||||
@ -147,11 +96,10 @@ class Summary extends React.PureComponent<Props, State> {
|
||||
}, null);
|
||||
}
|
||||
|
||||
private calcValidatorLow (): Balance | null {
|
||||
const { validators } = this.props;
|
||||
const { balances } = this.state;
|
||||
private calcValidatorLow (): ExtendedBalance | null {
|
||||
const { balances, validators } = this.props;
|
||||
|
||||
return validators.reduce((low: Balance | null, addr) => {
|
||||
return validators.reduce((low: ExtendedBalance | null, addr) => {
|
||||
const balance = balances[addr] || null;
|
||||
|
||||
if (low === null || (balance && low.stakingBalance.gt(balance.stakingBalance))) {
|
||||
|
||||
@ -3,11 +3,13 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
import { ExtendedBalanceMap } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import React from 'react';
|
||||
import storage from '@polkadot/storage';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
import classes from '@polkadot/ui-app/util/classes';
|
||||
import withApiObservable from '@polkadot/ui-react-rx/with/apiObservable';
|
||||
import withStorage from '@polkadot/ui-react-rx/with/storage';
|
||||
import withMulti from '@polkadot/ui-react-rx/with/multi';
|
||||
|
||||
@ -17,6 +19,7 @@ import StakeList from './StakeList';
|
||||
import Summary from './Summary';
|
||||
|
||||
type Props = BareProps & {
|
||||
balances?: ExtendedBalanceMap,
|
||||
intentions?: Array<string>,
|
||||
validators?: Array<string>
|
||||
};
|
||||
@ -26,7 +29,7 @@ const transformAddresses = (publicKeys: Array<Uint8Array>) =>
|
||||
|
||||
class App extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, intentions = [], style, validators = [] } = this.props;
|
||||
const { balances = {}, className, intentions = [], style, validators = [] } = this.props;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -34,10 +37,12 @@ class App extends React.PureComponent<Props> {
|
||||
style={style}
|
||||
>
|
||||
<Summary
|
||||
balances={balances}
|
||||
intentions={intentions}
|
||||
validators={validators}
|
||||
/>
|
||||
<StakeList
|
||||
balances={balances}
|
||||
intentions={intentions}
|
||||
validators={validators}
|
||||
/>
|
||||
@ -61,5 +66,12 @@ export default withMulti(
|
||||
propName: 'validators',
|
||||
transform: transformAddresses
|
||||
}
|
||||
),
|
||||
withApiObservable(
|
||||
'validatingBalances',
|
||||
{
|
||||
paramProp: 'intentions',
|
||||
propName: 'balances'
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
19
packages/app-staking/src/observables/types.d.ts
vendored
19
packages/app-staking/src/observables/types.d.ts
vendored
@ -1,19 +0,0 @@
|
||||
// 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 BN from 'bn.js';
|
||||
|
||||
export type Balance = {
|
||||
address: string,
|
||||
freeBalance: BN,
|
||||
nominatedBalance: BN,
|
||||
reservedBalance: BN,
|
||||
votingBalance: BN,
|
||||
stakingBalance: BN,
|
||||
nominators?: Array<Balance>
|
||||
}
|
||||
|
||||
export type BalanceMap = {
|
||||
[index: string]: Balance
|
||||
}
|
||||
@ -1,45 +0,0 @@
|
||||
// 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 { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import { Balance } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map, switchMap, toArray } from 'rxjs/operators';
|
||||
import storage from '@polkadot/storage';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import votingBalance from './votingBalance';
|
||||
import votingBalances from './/votingBalances';
|
||||
|
||||
type Result = [Balance, [Array<Balance>]];
|
||||
|
||||
export default function validatingBalance (api: RxApiInterface, address: string): Observable<Balance> {
|
||||
return combineLatest(
|
||||
votingBalance(api, address),
|
||||
api.state
|
||||
.getStorage(storage.staking.public.nominatorsFor, address)
|
||||
.pipe(
|
||||
switchMap((nominators = []) =>
|
||||
// FIXME: Will not be needed after https://github.com/polkadot-js/apps/issues/128
|
||||
votingBalances(api, nominators.map(encodeAddress))
|
||||
),
|
||||
toArray()
|
||||
)
|
||||
).pipe(
|
||||
map(([balance, [nominators = []]]: Result): Balance => {
|
||||
const nominatedBalance = nominators.reduce((total, nominator: Balance) => {
|
||||
return total.add(nominator.votingBalance);
|
||||
}, new BN(0));
|
||||
|
||||
return {
|
||||
...balance,
|
||||
nominators,
|
||||
nominatedBalance,
|
||||
stakingBalance: nominatedBalance.add(balance.votingBalance)
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
// 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 { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import { Balance, BalanceMap } from './types';
|
||||
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
import validatingBalance from './validatingBalance';
|
||||
|
||||
export default function validatingBalances (api: RxApiInterface, addresses: Array<string>): Observable<BalanceMap> {
|
||||
return combineLatest(
|
||||
addresses.map((address) =>
|
||||
validatingBalance(api, address)
|
||||
)
|
||||
).pipe(
|
||||
map((result: Array<Balance>): BalanceMap => {
|
||||
return result.reduce((balances, balance) => {
|
||||
balances[balance.address] = balance;
|
||||
|
||||
return balances;
|
||||
}, {} as BalanceMap);
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -1,27 +0,0 @@
|
||||
// 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 { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import { Balance } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import storage from '@polkadot/storage';
|
||||
|
||||
export default function votingBalance (api: RxApiInterface, address: string): Observable<Balance> {
|
||||
return combineLatest(
|
||||
api.state.getStorage(storage.staking.public.freeBalanceOf, address),
|
||||
api.state.getStorage(storage.staking.public.reservedBalanceOf, address)
|
||||
).pipe(
|
||||
map(([freeBalance = new BN(0), reservedBalance = new BN(0)]): Balance => ({
|
||||
address,
|
||||
freeBalance,
|
||||
nominatedBalance: new BN(0),
|
||||
reservedBalance,
|
||||
stakingBalance: new BN(0),
|
||||
votingBalance: freeBalance.add(reservedBalance)
|
||||
}))
|
||||
);
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
// 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 { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import { Balance } from './types';
|
||||
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
|
||||
import votingBalance from './votingBalance';
|
||||
|
||||
export default function votingBalances (api: RxApiInterface, addresses: Array<string>): Observable<Balance[]> {
|
||||
return combineLatest(
|
||||
addresses.map((address) =>
|
||||
votingBalance(api, address)
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -4,19 +4,22 @@
|
||||
|
||||
import { BareProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
|
||||
import classes from './util/classes';
|
||||
import toShortAddress from './util/toShortAddress';
|
||||
import translate from './translate';
|
||||
import Balance from './Balance';
|
||||
|
||||
type Props = BareProps & {
|
||||
balance?: BN | Array<BN>,
|
||||
isShort?: boolean,
|
||||
value?: string
|
||||
value?: string,
|
||||
withBalance?: boolean
|
||||
};
|
||||
|
||||
class AddressMini extends React.PureComponent<Props> {
|
||||
export default class AddressMini extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { className, isShort = true, style, value } = this.props;
|
||||
|
||||
@ -24,21 +27,36 @@ class AddressMini extends React.PureComponent<Props> {
|
||||
return null;
|
||||
}
|
||||
|
||||
const shortValue = toShortAddress(value);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('ui--AddressMini', className)}
|
||||
style={style}
|
||||
>
|
||||
<IdentityIcon
|
||||
size={24}
|
||||
value={value}
|
||||
/>
|
||||
<div>{isShort ? shortValue : value}</div>
|
||||
<div className='ui--AddressMini-info'>
|
||||
<IdentityIcon
|
||||
size={24}
|
||||
value={value}
|
||||
/>
|
||||
<div>{isShort ? toShortAddress(value) : value}</div>
|
||||
</div>
|
||||
{this.renderBalance()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(AddressMini);
|
||||
private renderBalance () {
|
||||
const { balance, value, withBalance = false } = this.props;
|
||||
|
||||
if (!withBalance || !value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Balance
|
||||
balance={balance}
|
||||
className='ui--AddressSummary-balance'
|
||||
value={value}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ class AddressRow extends AddressSummary {
|
||||
<div className='ui--AddressRow-details'>
|
||||
{this.renderAddress()}
|
||||
{this.renderBalance()}
|
||||
{this.renderNonce()}
|
||||
</div>
|
||||
</div>
|
||||
{this.renderChildren()}
|
||||
|
||||
@ -4,20 +4,21 @@
|
||||
|
||||
import { I18nProps } from '@polkadot/ui-app/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import Balance from '@polkadot/ui-react-rx/Balance';
|
||||
import Nonce from '@polkadot/ui-react-rx/Nonce';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import classes from './util/classes';
|
||||
import toShortAddress from './util/toShortAddress';
|
||||
import Balance from './Balance';
|
||||
import CopyButton from './CopyButton';
|
||||
import translate from './translate';
|
||||
|
||||
export type Props = I18nProps & {
|
||||
balance?: BN | Array<BN>,
|
||||
children?: React.ReactNode,
|
||||
name?: string,
|
||||
value: string,
|
||||
@ -74,6 +75,7 @@ class AddressSummary extends React.PureComponent<Props, State> {
|
||||
/>
|
||||
{this.renderAddress()}
|
||||
{this.renderBalance()}
|
||||
{this.renderNonce()}
|
||||
</div>
|
||||
{this.renderChildren()}
|
||||
</div>
|
||||
@ -99,40 +101,42 @@ class AddressSummary extends React.PureComponent<Props, State> {
|
||||
|
||||
protected renderBalance () {
|
||||
const { isValid, publicKey } = this.state;
|
||||
const { balance, t, withBalance = true } = this.props;
|
||||
|
||||
if (!isValid) {
|
||||
if (!withBalance || !isValid || !publicKey) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { t, withBalance = true, withNonce = true } = this.props;
|
||||
return (
|
||||
<Balance
|
||||
balance={balance}
|
||||
className='ui--AddressSummary-balance'
|
||||
label={t('addressSummary.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
value={publicKey}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return [
|
||||
withBalance
|
||||
? (
|
||||
<Balance
|
||||
className='ui--AddressSummary-balance'
|
||||
key='balance'
|
||||
label={t('addressSummary.balance', {
|
||||
defaultValue: 'balance '
|
||||
})}
|
||||
params={publicKey}
|
||||
/>
|
||||
)
|
||||
: null,
|
||||
withNonce
|
||||
? (
|
||||
<Nonce
|
||||
className='ui--AddressSummary-nonce'
|
||||
key='nonce'
|
||||
params={publicKey}
|
||||
>
|
||||
{t('addressSummary.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</Nonce>
|
||||
)
|
||||
: null
|
||||
];
|
||||
protected renderNonce () {
|
||||
const { isValid, publicKey } = this.state;
|
||||
const { t, withNonce = true } = this.props;
|
||||
|
||||
if (!withNonce || !isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Nonce
|
||||
className='ui--AddressSummary-nonce'
|
||||
params={publicKey}
|
||||
>
|
||||
{t('addressSummary.transactions', {
|
||||
defaultValue: ' transactions'
|
||||
})}
|
||||
</Nonce>
|
||||
);
|
||||
}
|
||||
|
||||
protected renderChildren () {
|
||||
|
||||
66
packages/ui-app/src/Balance.tsx
Normal file
66
packages/ui-app/src/Balance.tsx
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-app authors & contributors
|
||||
// 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 BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import RxBalance from '@polkadot/ui-react-rx/Balance';
|
||||
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
|
||||
|
||||
import classes from './util/classes';
|
||||
import translate from './translate';
|
||||
|
||||
export type Props = BareProps & {
|
||||
balance?: BN | Array<BN>,
|
||||
label?: string,
|
||||
value: string | Uint8Array,
|
||||
withLabel?: boolean
|
||||
};
|
||||
|
||||
class Balance extends React.PureComponent<Props> {
|
||||
render () {
|
||||
const { balance, className, label, value, style } = this.props;
|
||||
|
||||
return balance
|
||||
? this.renderProvided()
|
||||
: (
|
||||
<RxBalance
|
||||
className={classes('ui--Balance', className)}
|
||||
label={label}
|
||||
params={value}
|
||||
style={style}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private renderProvided () {
|
||||
const { balance, className, label, style } = this.props;
|
||||
|
||||
if (!balance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let value = `${numberFormat(Array.isArray(balance) ? balance[0] : balance)}`;
|
||||
|
||||
if (Array.isArray(balance)) {
|
||||
const totals = balance
|
||||
.filter((value, index) => index !== 0)
|
||||
.map(numberFormat);
|
||||
|
||||
value = `${value} (${totals.length === 1 ? '+' : ''}${totals.join(', ')})`;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('ui--Balance', className)}
|
||||
style={style}
|
||||
>
|
||||
{label}{value}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Balance);
|
||||
@ -36,16 +36,22 @@
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.ui--AddressMini > div {
|
||||
.ui--AddressMini-info div {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ui--AddressMini > .ui--IdentityIcon {
|
||||
.ui--AddressMini .ui--IdentityIcon {
|
||||
margin-left: 1rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
.ui--AddressMini .ui--Balance {
|
||||
font-size: 0.75rem;
|
||||
margin-top: -0.5rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.ui--AddressRow {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -88,6 +94,7 @@
|
||||
display: block
|
||||
}
|
||||
|
||||
.ui--Balance,
|
||||
.ui--AddressSummary-balance,
|
||||
.ui--AddressSummary-nonce {
|
||||
opacity: 0.45;
|
||||
|
||||
@ -14,6 +14,7 @@ import WsProvider from '@polkadot/api-provider/ws';
|
||||
import createApi from '@polkadot/api-rx';
|
||||
import defaults from '@polkadot/api-rx/defaults';
|
||||
|
||||
import ApiObservable from '../ApiObservable';
|
||||
import ApiContext from './Context';
|
||||
|
||||
type Props = {
|
||||
@ -44,7 +45,9 @@ export default class Api extends React.PureComponent<Props, State> {
|
||||
: provider
|
||||
);
|
||||
const setApi = (api: RxApiInterface): void => {
|
||||
this.setState({ api }, () => {
|
||||
const apiObservable = new ApiObservable(api);
|
||||
|
||||
this.setState({ api, apiObservable }, () => {
|
||||
this.updateSubscriptions();
|
||||
});
|
||||
};
|
||||
@ -56,6 +59,7 @@ export default class Api extends React.PureComponent<Props, State> {
|
||||
this.state = {
|
||||
api,
|
||||
apiConnected: false,
|
||||
apiObservable: new ApiObservable(api),
|
||||
apiSupport: 'poc-1',
|
||||
setApi,
|
||||
setApiProvider,
|
||||
@ -111,12 +115,13 @@ export default class Api extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render () {
|
||||
const { api, apiConnected, apiSupport, setApi, setApiProvider, setApiWsUrl } = this.state;
|
||||
const { api, apiConnected, apiObservable, apiSupport, setApi, setApiProvider, setApiWsUrl } = this.state;
|
||||
|
||||
return (
|
||||
<ApiContext.Provider value={{
|
||||
api,
|
||||
apiConnected,
|
||||
apiObservable,
|
||||
apiSupport,
|
||||
setApi,
|
||||
setApiProvider,
|
||||
|
||||
88
packages/ui-react-rx/src/ApiObservable.ts
Normal file
88
packages/ui-react-rx/src/ApiObservable.ts
Normal file
@ -0,0 +1,88 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-react-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import { ExtendedBalance, ExtendedBalanceMap, ObservableApiInterface } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Observable, combineLatest } from 'rxjs';
|
||||
import { map, switchMap, toArray } from 'rxjs/operators';
|
||||
import storage from '@polkadot/storage';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
export default class ObservableApi implements ObservableApiInterface {
|
||||
private api: RxApiInterface;
|
||||
|
||||
constructor (api: RxApiInterface) {
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
validatingBalance = (address: string): Observable<ExtendedBalance> => {
|
||||
return combineLatest(
|
||||
this.votingBalance(address),
|
||||
this.api.state
|
||||
.getStorage(storage.staking.public.nominatorsFor, address)
|
||||
.pipe(
|
||||
switchMap((nominators = []) =>
|
||||
// FIXME: Will not be needed after https://github.com/polkadot-js/apps/issues/128
|
||||
this.votingBalances(nominators.map(encodeAddress))
|
||||
),
|
||||
toArray()
|
||||
)
|
||||
).pipe(
|
||||
map(([balance, [nominators = []]]: [ExtendedBalance, [Array<ExtendedBalance>]]): ExtendedBalance => {
|
||||
const nominatedBalance = nominators.reduce((total, nominator: ExtendedBalance) => {
|
||||
return total.add(nominator.votingBalance);
|
||||
}, new BN(0));
|
||||
|
||||
return {
|
||||
...balance,
|
||||
nominators,
|
||||
nominatedBalance,
|
||||
stakingBalance: nominatedBalance.add(balance.votingBalance)
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
validatingBalances = (addresses: Array<string>): Observable<ExtendedBalanceMap> => {
|
||||
return combineLatest(
|
||||
addresses.map((address) =>
|
||||
this.validatingBalance(address)
|
||||
)
|
||||
).pipe(
|
||||
map((result: Array<ExtendedBalance>): ExtendedBalanceMap => {
|
||||
return result.reduce((balances, balance) => {
|
||||
balances[balance.address] = balance;
|
||||
|
||||
return balances;
|
||||
}, {} as ExtendedBalanceMap);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
votingBalance = (address: string): Observable<ExtendedBalance> => {
|
||||
return combineLatest(
|
||||
this.api.state.getStorage(storage.staking.public.freeBalanceOf, address),
|
||||
this.api.state.getStorage(storage.staking.public.reservedBalanceOf, address)
|
||||
).pipe(
|
||||
map(([freeBalance = new BN(0), reservedBalance = new BN(0)]): ExtendedBalance => ({
|
||||
address,
|
||||
freeBalance,
|
||||
nominatedBalance: new BN(0),
|
||||
reservedBalance,
|
||||
stakingBalance: new BN(0),
|
||||
votingBalance: freeBalance.add(reservedBalance)
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
votingBalances = (addresses: Array<string>): Observable<ExtendedBalance[]> => {
|
||||
return combineLatest(
|
||||
addresses.map((address) =>
|
||||
this.votingBalance(address)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
24
packages/ui-react-rx/src/types.d.ts
vendored
24
packages/ui-react-rx/src/types.d.ts
vendored
@ -2,6 +2,8 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import BN from 'bn.js';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ProviderInterface } from '@polkadot/api-provider/types';
|
||||
import { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import { EncodingVersions } from '@polkadot/params/types';
|
||||
@ -12,9 +14,31 @@ export type BareProps = {
|
||||
style?: { [index: string]: any }
|
||||
};
|
||||
|
||||
export type ExtendedBalance = {
|
||||
address: string,
|
||||
freeBalance: BN,
|
||||
nominatedBalance: BN,
|
||||
reservedBalance: BN,
|
||||
votingBalance: BN,
|
||||
stakingBalance: BN,
|
||||
nominators?: Array<ExtendedBalance>
|
||||
}
|
||||
|
||||
export type ExtendedBalanceMap = {
|
||||
[index: string]: ExtendedBalance
|
||||
}
|
||||
|
||||
export interface ObservableApiInterface {
|
||||
validatingBalance: (address: string) => Observable<ExtendedBalance>,
|
||||
validatingBalances: (addresses: Array<string>) => Observable<ExtendedBalanceMap>,
|
||||
votingBalance: (address: string) => Observable<ExtendedBalance>,
|
||||
votingBalances: (addresses: Array<string>) => Observable<ExtendedBalance[]>
|
||||
}
|
||||
|
||||
export type ApiProps = {
|
||||
api: RxApiInterface,
|
||||
apiConnected: boolean,
|
||||
apiObservable: ObservableApiInterface,
|
||||
apiSupport: EncodingVersions,
|
||||
setApi: (api: RxApiInterface) => void,
|
||||
setApiProvider: (provider?: ProviderInterface) => void,
|
||||
|
||||
120
packages/ui-react-rx/src/with/apiObservable.tsx
Normal file
120
packages/ui-react-rx/src/with/apiObservable.tsx
Normal file
@ -0,0 +1,120 @@
|
||||
// Copyright 2017-2018 @polkadot/ui-react-rx authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
// TODO: Lots of duplicated code between this and withObservable, surely there ois a better way of doing this?
|
||||
|
||||
import { RxProps, ObservableApiInterface } from '../types';
|
||||
import { HOC, StorageOptions, DefaultProps } from './types';
|
||||
|
||||
import React from 'react';
|
||||
import { map } from 'rxjs/operators/map';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
|
||||
import intervalSubscribe from '../util/intervalSubscribe';
|
||||
import isEqual from '../util/isEqual';
|
||||
import triggerChange from '../util/triggerChange';
|
||||
import echoTransform from './transform/echo';
|
||||
import withApi from './api';
|
||||
|
||||
type State<T> = RxProps<T> & {
|
||||
subscriptions: Array<any>; // FIXME subscriptions
|
||||
};
|
||||
|
||||
type ObservableNames = keyof ObservableApiInterface;
|
||||
|
||||
// FIXME proper types for attributes
|
||||
|
||||
export default function withApiObservable<T> (observable: ObservableNames, { onChange, params, paramProp = 'params', propName = 'value', transform = echoTransform }: StorageOptions<T> = {}): HOC<T> {
|
||||
return (Inner: React.ComponentType<any>, defaultProps: DefaultProps<T> = {}): React.ComponentType<any> => {
|
||||
class WithStorage extends React.Component<any, State<T>> {
|
||||
state: State<T>;
|
||||
|
||||
constructor (props: any) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
rxUpdated: false,
|
||||
rxUpdatedAt: 0,
|
||||
subscriptions: [],
|
||||
value: void 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidUpdate (prevProps: any) {
|
||||
if (!isEqual(this.props[paramProp], prevProps[paramProp])) {
|
||||
this.subscribe();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.subscribe();
|
||||
}
|
||||
|
||||
private subscribe () {
|
||||
const { apiObservable } = this.props;
|
||||
const propValue = params || this.props[paramProp];
|
||||
|
||||
this.unsubscribe();
|
||||
|
||||
if (isUndefined(propValue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({
|
||||
subscriptions: [
|
||||
apiObservable[observable](propValue)
|
||||
.pipe(map(transform))
|
||||
.subscribe((value: any) =>
|
||||
this.triggerUpdate(this.props, value)
|
||||
),
|
||||
intervalSubscribe(this)
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
private unsubscribe () {
|
||||
this.state.subscriptions.forEach((subscription) =>
|
||||
subscription.unsubscribe()
|
||||
);
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
this.unsubscribe();
|
||||
}
|
||||
|
||||
triggerUpdate = (props: any, value?: T): void => {
|
||||
if (isEqual(value, this.state.value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
triggerChange(value, onChange, props.onChange || defaultProps.onChange);
|
||||
|
||||
this.setState({
|
||||
rxUpdated: true,
|
||||
rxUpdatedAt: Date.now(),
|
||||
value
|
||||
});
|
||||
}
|
||||
|
||||
render () {
|
||||
const { rxUpdated, rxUpdatedAt, value } = this.state;
|
||||
const _props = {
|
||||
...defaultProps,
|
||||
...this.props,
|
||||
rxUpdated,
|
||||
rxUpdatedAt,
|
||||
[propName]: value
|
||||
};
|
||||
|
||||
delete _props.onChange;
|
||||
|
||||
return (
|
||||
<Inner {..._props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return withApi(WithStorage);
|
||||
};
|
||||
}
|
||||
@ -7,7 +7,11 @@ import React from 'react';
|
||||
type HOC = (Component: React.ComponentType<any>) => React.ComponentType<any>;
|
||||
|
||||
export default function withMulti<T> (Component: React.ComponentType<any>, ...hocs: Array<HOC>): React.ComponentType<any> {
|
||||
return hocs.reduce((Component, hoc) => {
|
||||
return hoc(Component);
|
||||
}, Component);
|
||||
// NOTE: Order is reversed so it makes sense in the props, i.e. component
|
||||
// after something can use the value of the preceding version
|
||||
return hocs
|
||||
.reverse()
|
||||
.reduce((Component, hoc) => {
|
||||
return hoc(Component);
|
||||
}, Component);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user