Basic democracy (Referendum) overview (#216)

* Basic democacry (Referendum) overview

* Summary in-place

* Fix package name

* Proposals & Referendums are showing up

* Some basic Extrinsic formatting. (WIP)

* Basic referendum/proposal summary in place

* Referendum outcome chart

* Voting in place. It works.

* Remove extra console.log/error

* Only show balances as label
This commit is contained in:
Jaco Greeff 2018-08-15 10:35:53 +02:00 committed by GitHub
parent 7768f51639
commit 4484883afc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1626 additions and 234 deletions

View File

@ -2,7 +2,7 @@ const config = require('@polkadot/dev-react/config/jest');
module.exports = Object.assign({}, config, {
moduleNameMapper: {
'@polkadot/app-(accounts|addresses|example|explorer|extrinsics|rpc|staking|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
'@polkadot/app-(accounts|addresses|democracy|example|explorer|extrinsics|rpc|staking|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
'@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 'empty/object',
'\\.(css|less)$': 'empty/object'

View File

@ -0,0 +1,15 @@
ISC License (ISC)
Copyright 2017-2018 @polkadot/app-democracy authors & contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.

View File

@ -0,0 +1 @@
# @polkadot/app-democracy

View File

@ -0,0 +1,19 @@
{
"name": "@polkadot/app-democracy",
"version": "0.19.30",
"description": "A referendum & proposal app",
"main": "index.js",
"scripts": {},
"author": "Jaco Greeff <jacogr@gmail.com>",
"maintainers": [
"Jaco Greeff <jacogr@gmail.com>"
],
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/storage": "^0.28.11",
"@polkadot/ui-react": "^0.19.30",
"@polkadot/ui-react-rx": "^0.19.30",
"@polkadot/util-keyring": "^0.28.1"
}
}

View File

@ -0,0 +1,45 @@
// 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 { ExtrinsicDecoded } from '@polkadot/params/types';
import { I18nProps } from '@polkadot/ui-app/types';
import { RawParam } from '@polkadot/ui-app/Params/types';
import React from 'react';
import Params from '@polkadot/ui-app/Params';
import classes from '@polkadot/ui-app/util/classes';
import translate from './translate';
export type Props = I18nProps & {
children?: React.ReactNode,
value: ExtrinsicDecoded
};
class Extrinsic extends React.PureComponent<Props> {
render () {
const { children, className, style, value: { extrinsic, params } } = this.props;
const values: Array<RawParam> = extrinsic.params.map(({ type }, index) => ({
isValid: true,
value: params[index],
type
}));
return (
<div
className={classes('democracy--Extrinsic', className)}
style={style}
>
{children}
<Params
isDisabled
item={extrinsic}
values={values}
/>
</div>
);
}
}
export default translate(Extrinsic);

View File

@ -0,0 +1,64 @@
// Copyright 2017-2018 @polkadot/app-democracy authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { ExtrinsicDecoded } from '@polkadot/params/types';
import { I18nProps } from '@polkadot/ui-app/types';
import BN from 'bn.js';
import React from 'react';
import Card from '@polkadot/ui-app/Card';
import classes from '@polkadot/ui-app/util/classes';
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
// TODO This would be nice as a shared component, move to ui-app as soon as
// we have actual "more-than-one-use" apps
import Extrinsic from './Extrinsic';
import translate from './translate';
type Props = I18nProps & {
children?: React.ReactNode,
proposal: ExtrinsicDecoded,
proposalExtra?: React.ReactNode,
idNumber: BN
};
class Item extends React.PureComponent<Props> {
render () {
const { children, className, idNumber, proposal, proposalExtra, style } = this.props;
return (
<Card
className={classes('democracy--Item', className)}
style={style}
>
<div className='democracy--Item-header'>
<div className='democracy--Item-header-info'>
<div className='democracy--Item-header-name'>
{proposal.extrinsic.section}.{proposal.extrinsic.name}
</div>
<div className='democracy--Item-header-description'>
{proposal.extrinsic.description}
</div>
</div>
<div className='democracy--Item-header-id'>
#{numberFormat(idNumber)}
</div>
</div>
<div className='democracy--Item-body'>
<Extrinsic
className='democracy--Item-extrinsic'
value={proposal}
>
{proposalExtra}
</Extrinsic>
<div className='democracy--Item-children'>
{children}
</div>
</div>
</Card>
);
}
}
export default translate(Item);

View File

@ -0,0 +1,86 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 { RxProposal, RxProposalDeposits } from '@polkadot/ui-react-rx/ApiObservable/types';
import BN from 'bn.js';
import React from 'react';
import AddressMini from '@polkadot/ui-app/AddressMini';
import Labelled from '@polkadot/ui-app/Labelled';
import Static from '@polkadot/ui-app/Static';
import classes from '@polkadot/ui-app/util/classes';
import withObservable from '@polkadot/ui-react-rx/with/observable';
import withMulti from '@polkadot/ui-react-rx/with/multi';
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
import Item from './Item';
import translate from './translate';
type Props = I18nProps & {
democracyProposalDeposits?: RxProposalDeposits,
idNumber: BN,
value: RxProposal
};
class Proposal extends React.PureComponent<Props> {
render () {
const { className, idNumber, style, value } = this.props;
return (
<Item
className={classes('democracy--Proposal', className)}
idNumber={idNumber}
proposal={value.proposal}
proposalExtra={this.renderExtra()}
style={style}
>
{this.renderVoting()}
</Item>
);
}
private renderExtra () {
const { democracyProposalDeposits, t } = this.props;
if (!democracyProposalDeposits) {
return null;
}
const { balance, addresses } = democracyProposalDeposits;
return (
<div className='democracy--Proposal-info'>
<Labelled label={t('proposal.depositsAddresses', {
defaultValue: 'depositors'
})}>
<div>
{addresses.map((address) => (
<AddressMini
isPadded={false}
key={address}
value={address}
/>
))}
</div>
</Labelled>
<Static label={t('proposal.depositsBalanceLabel', {
defaultValue: 'balance'
})}>
{numberFormat(balance)}
</Static>
</div>
);
}
private renderVoting () {
return null;
}
}
export default withMulti(
Proposal,
translate,
withObservable('democracyProposalDeposits', { paramProp: 'idNumber' })
);

View File

@ -0,0 +1,64 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 { RxProposal } from '@polkadot/ui-react-rx/ApiObservable/types';
import React from 'react';
import classes from '@polkadot/ui-app/util/classes';
import withObservable from '@polkadot/ui-react-rx/with/observable';
import withMulti from '@polkadot/ui-react-rx/with/multi';
import Proposal from './Proposal';
import translate from './translate';
type Props = I18nProps & {
democracyProposals?: Array<RxProposal>
};
class Proposals extends React.PureComponent<Props> {
render () {
const { className, style, t } = this.props;
return (
<div
className={classes('democracy--Proposals', className)}
style={style}
>
<h1>{t('proposals.header', {
defaultValue: 'proposals'
})}</h1>
{this.renderProposals()}
</div>
);
}
private renderProposals () {
const { democracyProposals, t } = this.props;
if (!democracyProposals || !democracyProposals.length) {
return (
<div className='ui disabled'>
{t('proposals.none', {
defaultValue: 'no available proposals'
})}
</div>
);
}
return democracyProposals.map((proposal) => (
<Proposal
idNumber={proposal.id}
key={proposal.id.toString()}
value={proposal}
/>
));
}
}
export default withMulti(
Proposals,
translate,
withObservable('democracyProposals')
);

View File

@ -0,0 +1,171 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 { RawParam } from '@polkadot/ui-app/Params/types';
import { RxReferendum, RxReferendumVote } from '@polkadot/ui-react-rx/ApiObservable/types';
import BN from 'bn.js';
import React from 'react';
import Static from '@polkadot/ui-app/Static';
import Doughnut from '@polkadot/ui-app/Chart/Doughnut';
import VoteThreshold from '@polkadot/ui-app/Params/Param/VoteThreshold';
import classes from '@polkadot/ui-app/util/classes';
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
import withObservable from '@polkadot/ui-react-rx/with/observable';
import withMulti from '@polkadot/ui-react-rx/with/multi';
import Item from './Item';
import Voting from './Voting';
import translate from './translate';
const COLORS_YAY = ['#4d4', '#4e4'];
const COLORS_NAY = ['#d44', '#e44'];
type Props = I18nProps & {
bestNumber?: BN,
democracyReferendumVoters?: Array<RxReferendumVote>,
idNumber: BN,
value: RxReferendum
};
type State = {
voteCount: number,
voteCountYay: number,
voteCountNay: number,
votedTotal: BN,
votedNay: BN,
votedYay: BN
};
class Referendum extends React.PureComponent<Props, State> {
state: State;
constructor (props: Props) {
super(props);
this.state = {
voteCount: 0,
voteCountYay: 0,
voteCountNay: 0,
votedTotal: new BN(0),
votedYay: new BN(0),
votedNay: new BN(0)
};
}
static getDerivedStateFromProps ({ democracyReferendumVoters }: Props, prevState: State): State | null {
if (!democracyReferendumVoters) {
return null;
}
const newState: State = democracyReferendumVoters.reduce((state, { balance, vote }) => {
if (vote) {
state.voteCountYay++;
state.votedYay = state.votedYay.add(balance);
} else {
state.voteCountNay++;
state.votedNay = state.votedNay.add(balance);
}
state.voteCount++;
state.votedTotal = state.votedTotal.add(balance);
return state;
}, {
voteCount: 0,
voteCountYay: 0,
voteCountNay: 0,
votedTotal: new BN(0),
votedYay: new BN(0),
votedNay: new BN(0)
});
if (newState.votedYay.eq(prevState.votedNay) && newState.votedNay.eq(prevState.votedNay)) {
return null;
}
return newState;
}
render () {
const { className, idNumber, style, value } = this.props;
return (
<Item
className={classes('democracy--Referendum', className)}
idNumber={idNumber}
proposal={value.proposal}
proposalExtra={this.renderExtra()}
style={style}
>
<Voting referendumId={idNumber} />
{this.renderResults()}
</Item>
);
}
private renderExtra () {
const { bestNumber, t, value: { blockNumber, voteThreshold } } = this.props;
if (!bestNumber) {
return null;
}
return (
<div className='democracy--Referendum-info'>
<Static label={t('referendum.endLabel', {
defaultValue: 'remaining time'
})}>
{t('referendum.endInfo', {
defaultValue: '{{remaining}} blocks remaining, ending at block #{{blockNumber}}',
replace: {
blockNumber: numberFormat(blockNumber),
remaining: numberFormat(blockNumber.sub(bestNumber))
}
})}
</Static>
<VoteThreshold
isDisabled
defaultValue={{ value: voteThreshold } as RawParam}
label={t('referendum.thresholdLabel', {
defaultValue: 'vote threshold'
})}
name='voteThreshold'
/>
</div>
);
}
private renderResults () {
const { voteCount, voteCountYay, voteCountNay, votedTotal, votedYay, votedNay } = this.state;
if (voteCount === 0 || votedTotal.eqn(0)) {
return null;
}
return (
<div className='democracy--Referendum-results chart'>
<Doughnut values={[
{
colors: COLORS_YAY,
label: `${numberFormat(votedYay)} (${numberFormat(voteCountYay)})`,
value: votedYay.muln(10000).div(votedTotal).toNumber() / 100
},
{
colors: COLORS_NAY,
label: `${numberFormat(votedNay)} (${numberFormat(voteCountNay)})`,
value: votedNay.muln(10000).div(votedTotal).toNumber() / 100
}
]} />
</div>
);
}
}
export default withMulti(
translate(Referendum),
withObservable('bestNumber'),
withObservable('democracyReferendumVoters', { paramProp: 'idNumber' })
);

View File

@ -0,0 +1,64 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 { RxReferendum } from '@polkadot/ui-react-rx/ApiObservable/types';
import React from 'react';
import classes from '@polkadot/ui-app/util/classes';
import withObservable from '@polkadot/ui-react-rx/with/observable';
import withMulti from '@polkadot/ui-react-rx/with/multi';
import Referendum from './Referendum';
import translate from './translate';
type Props = I18nProps & {
democracyReferendums?: Array<RxReferendum>
};
class Referendums extends React.PureComponent<Props> {
render () {
const { className, style, t } = this.props;
return (
<div
className={classes('democracy--Referendums', className)}
style={style}
>
<h1>{t('referendums.header', {
defaultValue: 'referendums'
})}</h1>
{this.renderReferendums()}
</div>
);
}
private renderReferendums () {
const { democracyReferendums, t } = this.props;
if (!democracyReferendums || !democracyReferendums.length) {
return (
<div className='ui disabled'>
{t('proposals.none', {
defaultValue: 'no available referendums'
})}
</div>
);
}
return democracyReferendums.map((referendum) => (
<Referendum
idNumber={referendum.id}
key={referendum.id.toString()}
value={referendum}
/>
));
}
}
export default withMulti(
Referendums,
translate,
withObservable('democracyReferendums')
);

View File

@ -0,0 +1,79 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 BN from 'bn.js';
import React from 'react';
import classes from '@polkadot/ui-app/util/classes';
import CardSummary from '@polkadot/ui-app/CardSummary';
import withObservable from '@polkadot/ui-react-rx/with/observable';
import withMulti from '@polkadot/ui-react-rx/with/multi';
import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
import translate from './translate';
type Props = I18nProps & {
democracyLaunchPeriod?: BN,
democracyNextTally?: BN,
democracyProposalCount?: BN,
democracyReferendumCount?: BN,
democracyVotingPeriod?: BN
};
class Summary extends React.PureComponent<Props> {
render () {
const { className, democracyLaunchPeriod, democracyNextTally = new BN(0), democracyProposalCount, democracyReferendumCount = new BN(0), democracyVotingPeriod, style, t } = this.props;
return (
<div
className={classes('democracy--Summary', className)}
style={style}
>
<div className='democracy--Summary-column'>
<CardSummary label={t('summary.proposalCount', {
defaultValue: 'proposals'
})}>
{numberFormat(democracyProposalCount)}
</CardSummary>
<CardSummary label={t('summary.referendumCount', {
defaultValue: 'referendums'
})}>
{numberFormat(democracyReferendumCount)}
</CardSummary>
<CardSummary label={t('summary.active', {
defaultValue: 'active num'
})}>
{democracyNextTally && democracyReferendumCount
? numberFormat(democracyReferendumCount.sub(democracyNextTally))
: 0
}
</CardSummary>
</div>
<div className='democracy--Summary-column'>
<CardSummary label={t('summary.votingPeriod', {
defaultValue: 'voting period'
})}>
{numberFormat(democracyVotingPeriod)}
</CardSummary>
<CardSummary label={t('summary.launchPeriod', {
defaultValue: 'launch period'
})}>
{numberFormat(democracyLaunchPeriod)}
</CardSummary>
</div>
</div>
);
}
}
export default withMulti(
Summary,
translate,
withObservable('democracyLaunchPeriod'),
withObservable('democracyReferendumCount'),
withObservable('democracyNextTally'),
withObservable('democracyProposalCount'),
withObservable('democracyVotingPeriod')
);

View File

@ -0,0 +1,72 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 { QueueProps } from '@polkadot/ui-signer/types';
import BN from 'bn.js';
import React from 'react';
import InputAddress from '@polkadot/ui-app/InputAddress';
import { QueueConsumer } from '@polkadot/ui-signer/Context';
import VotingButtons from './VotingButtons';
import translate from './translate';
type Props = I18nProps & {
referendumId: BN
};
type State = {
publicKey?: Uint8Array
};
class Voting extends React.PureComponent<Props, State> {
state: State = {};
render () {
const { t } = this.props;
return (
<div className='democracy--Referendum-vote'>
<InputAddress
label={t('voting.account', {
defaultValue: 'vote using my account'
})}
onChange={this.onChangeAccount}
placeholder='0x...'
type='account'
withLabel
/>
{this.renderButtons()}
</div>
);
}
private renderButtons () {
const { referendumId } = this.props;
const { publicKey } = this.state;
if (!publicKey) {
return null;
}
return (
<QueueConsumer>
{({ queueExtrinsic }: QueueProps) => (
<VotingButtons
publicKey={publicKey}
queueExtrinsic={queueExtrinsic}
referendumId={referendumId}
/>
)}
</QueueConsumer>
);
}
private onChangeAccount = (publicKey?: Uint8Array) => {
this.setState({ publicKey });
}
}
export default translate(Voting);

View File

@ -0,0 +1,78 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 { QueueTx$ExtrinsicAdd } from '@polkadot/ui-signer/types';
import BN from 'bn.js';
import React from 'react';
import extrinsics from '@polkadot/extrinsics';
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';
type Props = I18nProps & {
publicKey?: Uint8Array,
queueExtrinsic: QueueTx$ExtrinsicAdd,
referendumId: BN,
systemAccountIndexOf?: BN
};
class VotingButton extends React.PureComponent<Props> {
render () {
const { publicKey, t } = this.props;
return (
<Button.Group>
<Button
isDisabled={!publicKey}
isPositive
text={t('votebtn.yay', {
defaultValue: 'yay'
})}
onClick={this.onClickYay}
/>
<Button.Or />
<Button
isDisabled={!publicKey}
isNegative
text={t('votebtn.nay', {
defaultValue: 'nay'
})}
onClick={this.onClickNay}
/>
</Button.Group>
);
}
private doVote (vote: boolean) {
const { publicKey, queueExtrinsic, referendumId, systemAccountIndexOf = new BN(0) } = this.props;
if (!publicKey) {
return;
}
queueExtrinsic({
extrinsic: extrinsics.democracy.public.vote,
nonce: systemAccountIndexOf,
publicKey,
values: [referendumId, vote]
});
}
private onClickYay = () => {
this.doVote(true);
}
private onClickNay = () => {
this.doVote(false);
}
}
export default withMulti(
translate(VotingButton),
withObservable('systemAccountIndexOf', { paramProp: 'publicKey' })
);

View File

@ -0,0 +1,100 @@
/* Copyright 2017-2018 @polkadot/app-democracy authors & contributors
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.democracy--App {
margin-bottom: 1em;
}
/* FIXME These are just copied as-is from staking/Summary - once happy and proven to be
shareable, it needs to be moved into ui-app for consistency.
*/
.democracy--Summary {
align-items: stretch;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
margin-bottom: 2em;
}
.democracy--Summary-column {
display: flex;
flex: 0 1 auto;
text-align: left;
}
.democracy--Summary-card {
flex: 0 1 auto;
text-align: left;
}
.democracy--Proposals,
.democracy--Referendums {
margin-bottom: 2rem;
}
.democracy--Item-header {
display: flex;
justify-content: space-between;
margin-bottom: 1rem;
}
.democracy--Item-header-info,
.democracy--Item-header-id {
flex: 1;
}
.democracy--Item-header-id {
font-size: 2rem;
line-height: 2rem;
text-align: right;
}
.democracy--Item-header-description {
color: rgba(0, 0, 0, 0.6);
}
.democracy--Item-header-name {
font-size: 1.2rem;
line-height: 1.2rem;
}
.democracy--Item-body {
display: flex;
flex-direction: row;
justify-content: stretch;
}
.democracy--Item-extrinsic,
.democracy--Item-children {
flex: 1;
width: 0;
padding: 0 1em;
text-align: left;
}
.democracy--Item-extrinsic .ui--Params-Content {
padding-left: 0;
}
.democracy--Extrinsic .ui--Labelled {
margin-bottom: 0.25rem;
}
.democracy--Extrinsic .ui--Labelled .ui.label {
display: block;
padding-left: 0;
}
.democracy--Proposal-info,
.democracy--Referendum-info {
margin-bottom: 0;
}
.democracy--Referendum-results {
margin-bottom: 1em;
}
.democracy--Referendum-results.chart {
text-align: center;
}

View File

@ -0,0 +1,28 @@
// Copyright 2017-2018 @polkadot/app-democracy 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 './index.css';
import React from 'react';
import Page from '@polkadot/ui-app/Page';
import Proposals from './Proposals';
import Referendums from './Referendums';
import Summary from './Summary';
type Props = BareProps & {};
export default class App extends React.PureComponent<Props> {
render () {
return (
<Page className='democracy--App'>
<Summary />
<Referendums />
<Proposals />
</Page>
);
}
}

View File

@ -0,0 +1,7 @@
// Copyright 2017-2018 @polkadot/app-explorer authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { translate } from 'react-i18next';
export default translate(['democracy', 'ui']);

View File

@ -11,7 +11,7 @@
"license": "ISC",
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/storage": "^0.28.9",
"@polkadot/storage": "^0.28.11",
"@polkadot/ui-react": "^0.19.30",
"@polkadot/ui-react-rx": "^0.19.30",
"@polkadot/util-keyring": "^0.28.1"

View File

@ -16,7 +16,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/primitives": "^0.28.9",
"@polkadot/primitives": "^0.28.11",
"@polkadot/ui-app": "^0.19.30",
"@polkadot/util-crypto": "^0.28.1"
}

View File

@ -16,7 +16,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/extrinsics": "^0.28.9",
"@polkadot/extrinsics": "^0.28.11",
"@polkadot/ui-app": "^0.19.30",
"@polkadot/ui-signer": "^0.19.30",
"react-dropzone": "^4.2.9"

View File

@ -20,7 +20,7 @@ type Props = I18nProps & {
isError?: boolean,
isInput?: boolean,
label: string,
onChange: (publicKey: Uint8Array) => void,
onChange?: (publicKey: Uint8Array) => void,
type?: KeyringOption$Type,
withLabel?: boolean
};
@ -94,7 +94,7 @@ class Account extends React.PureComponent<Props, State> {
const { onChange } = this.props;
this.setState({ publicKey }, () =>
onChange(publicKey)
onChange && onChange(publicKey)
);
}
}

View File

@ -31,7 +31,7 @@ export default class Account extends React.PureComponent<Props> {
onChange = (publicKey?: Uint8Array): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid: !!publicKey && publicKey.length === 32,
value: publicKey
});

View File

@ -19,7 +19,7 @@ type Props = I18nProps & {
isError?: boolean,
isPrivate: boolean,
label: string,
onChange: RawParam$OnChange,
onChange?: RawParam$OnChange,
withLabel?: boolean
};
@ -56,7 +56,7 @@ class Extrinsic extends React.PureComponent<Props> {
onChange = ({ isValid, values }: EncodedMessage): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid,
value: (values[0] as Uint8Array)
});

View File

@ -12,7 +12,7 @@
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/app-extrinsics": "^0.19.30",
"@polkadot/storage": "^0.28.9",
"@polkadot/storage": "^0.28.11",
"@polkadot/ui-app": "^0.19.30",
"@polkadot/ui-keyring": "^0.19.30",
"@polkadot/ui-react": "^0.19.30",

View File

@ -7,7 +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 { RxBalanceMap } from '@polkadot/ui-react-rx/ApiObservable/types';
import BN from 'bn.js';
import React from 'react';
@ -29,7 +29,7 @@ import translate from './translate';
type Props = I18nProps & {
systemAccountIndexOf?: BN,
address: string,
balances: ExtendedBalanceMap,
balances: RxBalanceMap,
name: string,
stakingNominating?: string,
stakingNominatorsFor?: Array<string>,

View File

@ -4,7 +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 { RxBalanceMap } from '@polkadot/ui-react-rx/ApiObservable/types';
import React from 'react';
import classes from '@polkadot/ui-app/util/classes';
@ -15,7 +15,7 @@ import Account from './Account';
import translate from './translate';
type Props = I18nProps & {
balances: ExtendedBalanceMap,
balances: RxBalanceMap,
intentions: Array<string>,
validators: Array<string>
};

View File

@ -3,7 +3,8 @@
// of the ISC license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/ui-app/types';
import { ApiProps, ExtendedBalance, ExtendedBalanceMap } from '@polkadot/ui-react-rx/types';
import { RxBalance, RxBalanceMap } from '@polkadot/ui-react-rx/ApiObservable/types';
import { ApiProps } from '@polkadot/ui-react-rx/types';
import BN from 'bn.js';
import React from 'react';
@ -15,7 +16,7 @@ import numberFormat from '@polkadot/ui-react-rx/util/numberFormat';
import translate from './translate';
type Props = ApiProps & I18nProps & {
balances: ExtendedBalanceMap,
balances: RxBalanceMap,
bestNumber?: BN,
intentions: Array<string>,
lastLengthChange?: BN,
@ -74,10 +75,10 @@ class Summary extends React.PureComponent<Props> {
);
}
private calcIntentionsHigh (): ExtendedBalance | null {
private calcIntentionsHigh (): RxBalance | null {
const { balances, intentions, validators } = this.props;
return intentions.reduce((high: ExtendedBalance | null, addr) => {
return intentions.reduce((high: RxBalance | null, addr) => {
const balance = validators.includes(addr) || !balances[addr]
? null
: balances[addr];
@ -90,10 +91,10 @@ class Summary extends React.PureComponent<Props> {
}, null);
}
private calcValidatorLow (): ExtendedBalance | null {
private calcValidatorLow (): RxBalance | null {
const { balances, validators } = this.props;
return validators.reduce((low: ExtendedBalance | null, addr) => {
return validators.reduce((low: RxBalance | null, addr) => {
const balance = balances[addr] || null;
if (low === null || (balance && low.stakingBalance.gt(balance.stakingBalance))) {

View File

@ -3,7 +3,7 @@
// 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 { RxBalanceMap } from '@polkadot/ui-react-rx/ApiObservable/types';
import React from 'react';
import Page from '@polkadot/ui-app/Page';
@ -17,7 +17,7 @@ import StakeList from './StakeList';
import Summary from './Summary';
type Props = BareProps & {
validatingBalances?: ExtendedBalanceMap,
validatingBalances?: RxBalanceMap,
stakingIntentions?: Array<string>,
sessionValidators?: Array<string>
};

View File

@ -19,6 +19,7 @@
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/app-accounts": "^0.19.30",
"@polkadot/app-addresses": "^0.19.30",
"@polkadot/app-democracy": "^0.19.30",
"@polkadot/app-example": "^0.19.30",
"@polkadot/app-explorer": "^0.19.30",
"@polkadot/app-extrinsics": "^0.19.30",

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1 @@
{}

View File

@ -0,0 +1,20 @@
// Copyright 2017-2018 @polkadot/apps authors & contributors
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
import { Routes } from '../types';
import Democracy from '@polkadot/app-democracy/index';
export default ([
{
Component: Democracy,
i18n: {
defaultValue: 'Democracy'
},
icon: 'calendar check',
isExact: false,
isHidden: false,
name: 'democracy'
}
] as Routes);

View File

@ -6,6 +6,7 @@ import { Routing, Routes } from '../types';
import accounts from './accounts';
import addresses from './addresses';
import democracy from './democracy';
import example from './example';
import explorer from './explorer';
import extrinsics from './extrinsics';
@ -21,7 +22,20 @@ import vanitygen from './vanitygen';
export default ({
default: 'explorer',
routes: ([] as Routes).concat(
example, explorer, staking, extrinsics, storage, null, accounts, addresses, vanitygen, null, rpc, toolbox
example,
explorer,
staking,
democracy,
null,
extrinsics,
storage,
null,
accounts,
addresses,
vanitygen,
null,
rpc,
toolbox
),
unknown
} as Routing);

View File

@ -10,7 +10,25 @@ const CopyWebpackPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const packages = ['app-accounts', 'app-addresses', 'app-example', 'app-explorer', 'app-extrinsics', 'app-rpc', 'app-staking', 'app-storage', 'app-toolbox', 'app-vanitygen', 'ui-app', 'ui-identicon', 'ui-keyring', 'ui-react-rx', 'ui-react', 'ui-signer'];
const packages = [
'app-accounts',
'app-addresses',
'app-democracy',
'app-example',
'app-explorer',
'app-extrinsics',
'app-rpc',
'app-staking',
'app-storage',
'app-toolbox',
'app-vanitygen',
'ui-app',
'ui-identicon',
'ui-keyring',
'ui-react-rx',
'ui-react',
'ui-signer'
];
function createWebpack ({ alias = {}, context, name = 'index' }) {
const pkgJson = require(path.join(context, 'package.json'));

View File

@ -9,28 +9,26 @@
],
"contributors": [],
"license": "ISC",
"scripts": {
"build": "polkadot-dev-build-babel",
"check": "stylelint 'src/**/*.css' && eslint src && flow check",
"test": "jest --coverage"
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/extrinsics": "^0.28.9",
"@polkadot/storage": "^0.28.9",
"@polkadot/extrinsics": "^0.28.11",
"@polkadot/storage": "^0.28.11",
"@polkadot/ui-keyring": "^0.19.30",
"@polkadot/ui-react": "^0.19.30",
"@polkadot/ui-react-rx": "^0.19.30",
"@types/chart.js": "^2.7.30",
"@types/i18next-browser-languagedetector": "^2.0.1",
"@types/i18next-xhr-backend": "^1.4.1",
"@types/react-copy-to-clipboard": "^4.2.5",
"@types/react-dropzone": "^4.2.0",
"@types/react-i18next": "^7.6.1",
"@types/react-router-dom": "^4.2.7",
"chart.js": "^2.7.2",
"i18next": "^11.1.1",
"i18next-browser-languagedetector": "^2.2.0",
"i18next-xhr-backend": "^1.5.1",
"react": "^16.4.1",
"react-chartjs-2": "^2.7.4",
"react-copy-to-clipboard": "^5.0.1",
"react-dom": "^16.4.1",
"react-i18next": "^7.7.0",

View File

@ -14,6 +14,7 @@ import Balance from './Balance';
type Props = BareProps & {
balance?: BN | Array<BN>,
isPadded?: boolean,
isShort?: boolean,
value?: string,
withBalance?: boolean
@ -21,7 +22,7 @@ type Props = BareProps & {
export default class AddressMini extends React.PureComponent<Props> {
render () {
const { className, isShort = true, style, value } = this.props;
const { className, isPadded = true, isShort = true, style, value } = this.props;
if (!value) {
return null;
@ -29,7 +30,7 @@ export default class AddressMini extends React.PureComponent<Props> {
return (
<div
className={classes('ui--AddressMini', className)}
className={classes('ui--AddressMini', isPadded ? 'padded' : '', className)}
style={style}
>
<div className='ui--AddressMini-info'>

View File

@ -17,7 +17,7 @@ import Or from './Or';
class Button extends React.PureComponent<ButtonProps> {
render () {
const { children, className, floated, icon, isBasic = false, isCircular = false, isDisabled = false, isNegative = false, isPrimary = false, onClick, size, style, text } = this.props;
const { children, className, floated, icon, isBasic = false, isCircular = false, isDisabled = false, isNegative = false, isPositive = false, isPrimary = false, onClick, size, style, text } = this.props;
const props = {
basic: isBasic || false,
@ -28,9 +28,10 @@ class Button extends React.PureComponent<ButtonProps> {
icon,
negative: isNegative,
onClick,
positive: isPositive,
primary: isPrimary,
size,
secondary: isBasic && !(isPrimary || isNegative || false),
secondary: isBasic && !(isPositive || isPrimary || isNegative || false),
style
};

View File

@ -14,6 +14,7 @@ export type ButtonProps = BareProps & {
isCircular?: boolean,
isDisabled?: boolean,
isNegative?: boolean,
isPositive?: boolean,
isPrimary?: boolean,
onClick?: () => void | Promise<void>,
size?: Button$Sizes,

View File

@ -38,7 +38,7 @@ export default class CardSummary extends React.PureComponent<Props> {
<div className='ui--CardSummary-large'>
{children}{
progress && (
(isUndefined(progress.value) || progress.value.ltn(0)) || isUndefined(progress.total)
(isUndefined(progress.value) || isUndefined(progress.total) || progress.value.ltn(0)) || progress.value.gt(progress.total) || progress.total.eqn(0)
? '-'
: `${progress.value.toString()}/${progress.total.toString()}`
)

View File

@ -0,0 +1,69 @@
// 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 '../types';
import BN from 'bn.js';
import React from 'react';
import { Doughnut } from 'react-chartjs-2';
import bnToBn from '@polkadot/util/bn/toBn';
import classes from '../util/classes';
type Value = {
colors: Array<string>,
label: string,
value: number | BN
};
type Props = BareProps & {
size?: number,
values: Array<Value>
};
type Options = {
colorNormal: Array<string>,
colorHover: Array<string>,
data: Array<number>,
labels: Array<string>
};
export default class ChartDoughnut extends React.PureComponent<Props> {
render () {
const { className, size = 100, style, values } = this.props;
const options = values.reduce((options, { colors: [normalColor = '#00f', hoverColor], label, value }) => {
options.colorNormal.push(normalColor);
options.colorHover.push(hoverColor || normalColor);
options.data.push(bnToBn(value).toNumber());
options.labels.push(label);
return options;
}, {
colorNormal: [],
colorHover: [],
data: [],
labels: []
} as Options);
return (
<div
className={classes('ui--Chart', className)}
style={style}
>
<Doughnut
data={{
labels: options.labels,
datasets: [{
data: options.data,
backgroundColor: options.colorNormal,
hoverBackgroundColor: options.colorHover
}]
}}
height={size}
width={size} />
</div>
);
}
}

View File

@ -20,7 +20,7 @@ export default class Account extends React.PureComponent<Props> {
style={style}
>
<InputAddress
className='large'
className={isDisabled ? 'full' : 'large'}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
@ -37,7 +37,7 @@ export default class Account extends React.PureComponent<Props> {
onChange = (value?: Uint8Array): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid: !!value && value.length === 32,
value
});

View File

@ -21,8 +21,8 @@ export default class Amount extends React.PureComponent<Props> {
style={style}
>
<Input
className='small'
defaultValue={defaultValue || 0}
className={isDisabled ? 'full' : 'small'}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
label={label}
@ -38,7 +38,7 @@ export default class Amount extends React.PureComponent<Props> {
onChange = (value: string): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid: true,
value: new BN(value || 0)
});

View File

@ -11,20 +11,21 @@ import Labelled from '../../Labelled';
import Bare from './Bare';
type Props = BareProps & {
children: any, // node?
children: React.ReactNode,
isDisabled?: boolean,
label?: string,
size?: Size,
withLabel?: boolean
};
export default function Base ({ children, className, label, size = 'medium', style, withLabel }: Props) {
export default function Base ({ children, className, isDisabled, label, size = 'medium', style, withLabel }: Props) {
return (
<Bare
className={className}
style={style}
>
<Labelled
className={size}
className={isDisabled ? 'full' : size}
label={label}
withLabel={withLabel}
>

View File

@ -8,6 +8,7 @@ import React from 'react';
import hexToU8a from '@polkadot/util/hex/toU8a';
import bnToU8a from '@polkadot/util/bn/toU8a';
import u8aConcat from '@polkadot/util/u8a/concat';
import u8aToHex from '@polkadot/util/u8a/toHex';
import Input from '../../Input';
import Bare from './Bare';
@ -24,7 +25,10 @@ const defaultValidate = (u8a: Uint8Array): boolean =>
export default class BaseBytes extends React.PureComponent<Props> {
render () {
const { className, isDisabled, isError, label, size = 'full', style, withLabel } = this.props;
const { className, defaultValue: { value }, isDisabled, isError, label, size = 'full', style, withLabel } = this.props;
const defaultValue = value
? u8aToHex(value as Uint8Array)
: undefined;
return (
<Bare
@ -33,6 +37,7 @@ export default class BaseBytes extends React.PureComponent<Props> {
>
<Input
className={size}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
label={label}
@ -61,7 +66,7 @@ export default class BaseBytes extends React.PureComponent<Props> {
: u8a.length !== 0;
const isValid = isValidLength && validate(u8a);
onChange({
onChange && onChange({
isValid,
value: u8aConcat(
withLength

View File

@ -25,7 +25,7 @@ export default class Bool extends React.PureComponent<Props> {
style={style}
>
<Dropdown
className='small'
className={isDisabled ? 'full' : 'small'}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
@ -41,7 +41,7 @@ export default class Bool extends React.PureComponent<Props> {
onChange = (value: boolean): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid: true,
value
});

View File

@ -9,15 +9,20 @@ import React from 'react';
import bnToU8a from '@polkadot/util/bn/toU8a';
import u8aConcat from '@polkadot/util/u8a/concat';
import Bytes from './Bytes';
import BytesFile from './File';
export default class Code extends React.PureComponent<Props> {
render () {
const { className, isDisabled, isError, label, style, withLabel } = this.props;
const { className, defaultValue, isDisabled, isError, label, style, withLabel } = this.props;
const Component = isDisabled
? Bytes
: BytesFile;
return (
<BytesFile
<Component
className={className}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
label={label}
@ -32,7 +37,7 @@ export default class Code extends React.PureComponent<Props> {
onChange = (value: Uint8Array): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid: value.length !== 0,
value: u8aConcat(bnToU8a(value.length, 32, true), value)
});

View File

@ -16,7 +16,7 @@ type Props = BareProps & {
isDisabled?: boolean,
isError?: boolean,
label: string,
onChange: (contents: Uint8Array) => void,
onChange?: (contents: Uint8Array) => void,
placeholder?: string,
t: TranslationFunction,
withLabel?: boolean
@ -87,7 +87,7 @@ class BytesFile extends React.PureComponent<Props, State> {
reader.onload = ({ target: { result } }: LoadEvent) => {
const data = new Uint8Array(result);
onChange(data);
onChange && onChange(data);
this.setState({
file: {

View File

@ -19,7 +19,7 @@ export default function Hash ({ className, defaultValue, isDisabled, isError, la
length={32}
name={name}
onChange={onChange}
size='medium'
size={isDisabled ? 'full' : 'medium'}
style={style}
withLabel={withLabel}
/>

View File

@ -23,7 +23,7 @@ type State = {
value: State$Param
};
export default class KeyValue extends React.PureComponent<Props, State> {
export default class StorageKeyValue extends React.PureComponent<Props, State> {
state: State = {
key: {
isValid: false,
@ -91,7 +91,7 @@ export default class KeyValue extends React.PureComponent<Props, State> {
(prevState: State, { onChange }: Props) => {
const { key = prevState.key, value = prevState.value } = newState;
onChange({
onChange && onChange({
isValid: key.isValid && value.isValid,
value: u8aConcat(
u8aConcat(bnToU8a(key.u8a.length, 32, true), key.u8a),

View File

@ -4,15 +4,18 @@
import { TranslationFunction } from 'i18next';
import { KeyValue } from '@polkadot/params/types';
import { Props as BaseProps } from '../types';
import { Props as BaseProps, RawParam } from '../types';
import React from 'react';
import assert from '@polkadot/util/assert';
import isHex from '@polkadot/util/is/hex';
import u8aToUtf8 from '@polkadot/util/u8a/toUtf8';
import toU8a from '@polkadot/util/u8a/toU8a';
import u8aToHex from '@polkadot/util/u8a/toHex';
import translate from '../../translate';
import Base from './Base';
import Bytes from './Bytes';
import BytesFile from './File';
type Props = BaseProps & {
@ -23,7 +26,9 @@ type State = {
placeholder?: string;
};
class KeyValueStorageArray extends React.PureComponent<Props, State> {
type Pairs = Array<KeyValue>;
class StorageKeyValueArray extends React.PureComponent<Props, State> {
private placeholderEmpty: string;
constructor (props: Props) {
@ -41,6 +46,10 @@ class KeyValueStorageArray extends React.PureComponent<Props, State> {
const { className, isDisabled, isError, label, style, withLabel } = this.props;
const { placeholder } = this.state;
if (isDisabled) {
return this.renderReadOnly();
}
return (
<BytesFile
className={className}
@ -55,7 +64,33 @@ class KeyValueStorageArray extends React.PureComponent<Props, State> {
);
}
onChange = (raw: Uint8Array): void => {
private renderReadOnly () {
const { className, defaultValue: { value }, label, style } = this.props;
return (
<Base
className={className}
label={label}
size='full'
style={style}
>
{(value as Pairs).map(({ key, value }) => {
const keyHex = u8aToHex(key);
return (
<Bytes
defaultValue={{ value } as RawParam}
key={keyHex}
label={keyHex}
name={keyHex}
/>
);
})}
</Base>
);
}
private onChange = (raw: Uint8Array): void => {
const { onChange, t } = this.props;
let value: KeyValue[] = [];
@ -78,7 +113,7 @@ class KeyValueStorageArray extends React.PureComponent<Props, State> {
});
}
onChange({
onChange && onChange({
isValid: value.length !== 0,
value
});
@ -100,4 +135,4 @@ class KeyValueStorageArray extends React.PureComponent<Props, State> {
}
}
export default translate(KeyValueStorageArray);
export default translate(StorageKeyValueArray);

View File

@ -11,7 +11,8 @@ import Bare from './Bare';
export default class StringParam extends React.PureComponent<Props> {
render () {
const { className, isDisabled, isError, label, style, withLabel } = this.props;
const { className, defaultValue: { value }, isDisabled, isError, label, style, withLabel } = this.props;
const defaultValue = value as string;
return (
<Bare
@ -20,6 +21,7 @@ export default class StringParam extends React.PureComponent<Props> {
>
<Input
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
label={label}
@ -34,10 +36,9 @@ export default class StringParam extends React.PureComponent<Props> {
onChange = (value: string): void => {
const { onChange } = this.props;
const isValid = value.length !== 0;
onChange({
onChange && onChange({
isValid,
value
});

View File

@ -5,6 +5,7 @@
import { Props } from '../types';
import React from 'react';
import bnToBn from '@polkadot/util/bn/toBn';
import Dropdown from '../../Dropdown';
import Bare from './Bare';
@ -26,7 +27,7 @@ export const textMap = options.reduce((textMap, { text, value }) => {
export default class VoteThreshold extends React.PureComponent<Props> {
render () {
const { className, defaultValue: { value }, isDisabled, isError, label, style, withLabel } = this.props;
const defaultValue = (value as number);
const defaultValue = bnToBn(value as number).toNumber();
return (
<Bare
@ -34,7 +35,7 @@ export default class VoteThreshold extends React.PureComponent<Props> {
style={style}
>
<Dropdown
className='small'
className={isDisabled ? 'full' : 'small'}
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
@ -50,7 +51,7 @@ export default class VoteThreshold extends React.PureComponent<Props> {
onChange = (value: number): void => {
const { onChange } = this.props;
onChange({
onChange && onChange({
isValid: true,
value
});

View File

@ -11,8 +11,8 @@ import Bool from './Bool';
import Bytes from './Bytes';
import Code from './Code';
import Hash from './Hash';
import KeyValue from './KeyValue';
import KeyValueStorageArray from './KeyValueStorageArray';
import StorageKeyValue from './StorageKeyValue';
import StorageKeyValueArray from './StorageKeyValueArray';
import StringParam from './String';
import Unknown from './Unknown';
import VoteThreshold from './VoteThreshold';
@ -28,9 +28,9 @@ const components: ComponentMap = {
'Digest': Unknown,
'Hash': Hash,
'Index': Amount,
'KeyValue': KeyValue,
'KeyValueStorage': KeyValue,
'KeyValueStorage[]': KeyValueStorageArray,
'KeyValue': StorageKeyValue,
'StorageKeyValue': StorageKeyValue,
'StorageKeyValue[]': StorageKeyValueArray,
'MisbehaviorReport': Unknown,
'ParachainId': Amount,
'PropIndex': Amount,
@ -46,7 +46,7 @@ const components: ComponentMap = {
'VoteThreshold': VoteThreshold
};
export default function findComponent (type: Param$Types, overrides: ComponentMap = {}): React.ComponentType<Props> {
function getFromMap (type: Param$Types, overrides: ComponentMap): React.ComponentType<Props> | [React.ComponentType<Props>, React.ComponentType<Props>] {
if (Array.isArray(type)) {
// Special case for components where we have a specific override formatter
if (type.length === 1) {
@ -60,3 +60,11 @@ export default function findComponent (type: Param$Types, overrides: ComponentMa
return overrides[type] || components[type] || Unknown;
}
export default function findComponent (type: Param$Types, overrides: ComponentMap = {}, isDisabled: boolean = false): React.ComponentType<Props> {
const component = getFromMap(type, overrides);
return Array.isArray(component)
? component[0]
: component;
}

View File

@ -14,6 +14,7 @@ import translate from '../../translate';
import findComponent from './findComponent';
type Props = I18nProps & BaseProps & {
isDisabled?: boolean,
overrides?: ComponentMap
};
@ -26,11 +27,11 @@ class ParamComponent extends React.PureComponent<Props, State> {
Component: null
};
static getDerivedStateFromProps ({ defaultValue: { type }, overrides }: Props): State {
static getDerivedStateFromProps ({ defaultValue: { type }, isDisabled, overrides }: Props): State {
return {
Component: !type
? null
: findComponent(type, overrides)
: findComponent(type, overrides, isDisabled)
} as State;
}
@ -41,7 +42,7 @@ class ParamComponent extends React.PureComponent<Props, State> {
return null;
}
const { className, defaultValue, name, onChange, style } = this.props;
const { className, defaultValue, isDisabled, name, onChange, style } = this.props;
const type = typeToString(defaultValue.type);
return (
@ -49,6 +50,7 @@ class ParamComponent extends React.PureComponent<Props, State> {
className={classes('ui--Param', className)}
defaultValue={defaultValue}
key={`${name}:${type}`}
isDisabled={isDisabled}
label={`${name}: ${type}`}
name={name}
onChange={onChange}

View File

@ -16,9 +16,11 @@ import Param from './Param';
import createValues from './values';
type Props<S> = I18nProps & {
isDisabled?: boolean,
item: S,
onChange: (value: RawParams) => void,
overrides?: ComponentMap
onChange?: (value: RawParams) => void,
overrides?: ComponentMap,
values?: RawParams
};
type State<S> = {
@ -40,7 +42,9 @@ class Params<T, S extends SectionItem<T>> extends React.PureComponent<Props<S>,
}
static getDerivedStateFromProps (props: Props<any>, { item, onChangeParam }: State<any>): State<any> | null {
if (item && item.name === props.item.name && item.section === props.item.section) {
const isSame = item && item.name === props.item.name && item.section === props.item.section;
if (props.isDisabled || isSame) {
return null;
}
@ -61,19 +65,19 @@ class Params<T, S extends SectionItem<T>> extends React.PureComponent<Props<S>,
// NOTE This is needed in the case where the item changes, i.e. the values get initialised and we need to alert the parent that we have new values
componentDidUpdate (prevProps: Props<S>, prevState: State<S>) {
const { onChange } = this.props;
const { onChange, isDisabled } = this.props;
const { values } = this.state;
if (prevState.values !== values) {
onChange(values);
if (!isDisabled && prevState.values !== values) {
onChange && onChange(values);
}
}
render () {
const { className, item: { params }, overrides, style } = this.props;
const { handlers, values } = this.state;
const { className, isDisabled, item: { params }, overrides, style } = this.props;
const { handlers = [], values = this.props.values } = this.state;
if (values.length === 0 || params.length === 0) {
if (!values || values.length === 0 || params.length === 0) {
return null;
}
@ -86,6 +90,7 @@ class Params<T, S extends SectionItem<T>> extends React.PureComponent<Props<S>,
{params.map(({ name }, index) => (
<Param
defaultValue={values[index]}
isDisabled={isDisabled}
key={`${name}:${name}:${index}`}
name={name}
onChange={handlers[index]}
@ -97,7 +102,13 @@ class Params<T, S extends SectionItem<T>> extends React.PureComponent<Props<S>,
);
}
onChangeParam = (at: number, { isValid = false, value }: RawParam$OnChange$Value): void => {
private onChangeParam = (at: number, { isValid = false, value }: RawParam$OnChange$Value): void => {
const { isDisabled } = this.props;
if (isDisabled) {
return;
}
this.setState(
(prevState: State<S>): State<S> => ({
values: prevState.values.map((prev, index) =>
@ -116,9 +127,13 @@ class Params<T, S extends SectionItem<T>> extends React.PureComponent<Props<S>,
triggerUpdate = (): void => {
const { values } = this.state;
const { onChange } = this.props;
const { onChange, isDisabled } = this.props;
onChange(values);
if (isDisabled) {
return;
}
onChange && onChange(values);
}
}

View File

@ -49,7 +49,7 @@ export default function getInitValue (type: Param$Types): RawParam$Value | Array
case 'Hash':
case 'Header':
case 'KeyValue':
case 'KeyValueStorage':
case 'StorageKeyValue':
case 'MisbehaviorReport':
case 'Proposal':
case 'Signature':

View File

@ -25,12 +25,13 @@ export type RawParams = Array<RawParam>;
export type BaseProps = BareProps & {
defaultValue: RawParam,
name: string,
onChange: RawParam$OnChange
onChange?: RawParam$OnChange
};
export type Props = BaseProps & {
isDisabled?: boolean,
isError?: boolean,
isReadOnly?: boolean,
label: string,
withLabel?: boolean
};
@ -38,5 +39,5 @@ export type Props = BaseProps & {
export type Size = 'full' | 'large' | 'medium' | 'small';
export type ComponentMap = {
[index: string]: React.ComponentType<Props> // Param$Type
[index: string]: React.ComponentType<Props> | [React.ComponentType<Props>, React.ComponentType<Props>] // Param$Type
};

View File

@ -16,3 +16,9 @@ body {
font-family: sans-serif;
height: 100%;
}
h1 {
color: rgba(0, 0, 0, .6);
font-weight: 400;
text-transform: lowercase;
}

View File

@ -32,6 +32,11 @@
}
.ui--AddressMini {
display: inline-block;
padding: 0 0.25rem 0 0;
}
.ui--AddressMini.padded {
display: inline-block;
padding: 0.25rem;
}
@ -137,6 +142,14 @@
padding-top: 0;
}
.ui--Chart {
position: relative;
display: inline-block;
padding: 1em 1em 0;
height: 15vw;
width: 15vw;
}
.ui--CardSummary-large {
font-size: 3rem;
line-height: 3rem;

View File

@ -36,9 +36,9 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/api-rx": "^0.28.9",
"@polkadot/extrinsics": "^0.28.9",
"@polkadot/storage": "^0.28.9",
"@polkadot/api-rx": "^0.28.11",
"@polkadot/extrinsics": "^0.28.11",
"@polkadot/storage": "^0.28.11",
"rxjs-compat": "^6.2.2"
},
"devDependencies": {

View File

@ -5,20 +5,25 @@
import { Header } from '@polkadot/primitives/header';
import { RxApiInterface, RxApiInterface$Method } from '@polkadot/api-rx/types';
import { Interfaces } from '@polkadot/jsonrpc/types';
import { SectionItem } from '@polkadot/params/types';
import { ExtrinsicDecoded, SectionItem } from '@polkadot/params/types';
import { Storages } from '@polkadot/storage/types';
import { ExtendedBalance, ExtendedBalanceMap, ObservableApiInterface, KeyWithParams } from './types';
import { RxBalance, RxBalanceMap, RxProposal, RxProposalDeposits, RxReferendum, ObservableApiInterface, KeyWithParams, RxReferendumVote } from './types';
import BN from 'bn.js';
import { Observable, combineLatest } from 'rxjs';
import { concatMap, defaultIfEmpty, map } from 'rxjs/operators';
import { EMPTY, Observable, combineLatest } from 'rxjs';
import { switchMap, defaultIfEmpty, map } from 'rxjs/operators';
import storage from '@polkadot/storage';
import assert from '@polkadot/util/assert';
import isUndefined from '@polkadot/util/is/undefined';
type OptBN = BN | undefined;
type OptDate = Date | undefined;
type MapFn<R, T> = (combined: R) => T;
type ResultReferendum = [BN, ExtrinsicDecoded, number];
type ResultProposal = [BN, ExtrinsicDecoded, string];
type ResultProposalDeposits = [BN, Array<string>];
const defaultMapFn = (result: any): any =>
result;
@ -52,31 +57,218 @@ export default class ObservableApi implements ObservableApiInterface {
rawStorage = <T> (key: SectionItem<Storages>, ...params: Array<any>): Observable<T> => {
return this
.rawStorageMulti([key, ...params] as KeyWithParams)
.pipe(map(([result]: Array<T>): T =>
result
));
.pipe(
map(([result]: Array<T>): T =>
result
)
);
}
rawStorageMulti = <T> (...keys: Array<KeyWithParams>): Observable<T> => {
return this.api.state
.subscribeStorage(keys)
.pipe(map((result?: any) =>
result || []
));
.pipe(
map((result?: any) =>
isUndefined(result)
? []
: result
)
);
}
bestNumber = (): Observable<OptBN> => {
return this.chainNewHead().pipe(
map((header?: Header): OptBN =>
header && header.number
? header.number
: undefined
return this
.chainNewHead()
.pipe(
map((header?: Header): OptBN =>
header && header.number
? header.number
: undefined
)
);
}
chainNewHead = (): Observable<Header | undefined> => {
return this.api.chain.subscribeNewHead().pipe(
defaultIfEmpty()
);
}
democracyLaunchPeriod = (): Observable<OptBN> => {
return this.rawStorage(storage.democracy.public.launchPeriod);
}
democracyNextTally = (): Observable<OptBN> => {
return this.rawStorage(storage.democracy.public.nextTally);
}
democracyProposals = (): Observable<Array<RxProposal>> => {
return this
.rawStorage(storage.democracy.public.proposals)
.pipe(
map((proposals: Array<ResultProposal> = []) =>
proposals
.map((result: ResultProposal): RxProposal | undefined =>
result && result[1]
? {
address: result[2],
id: result[0],
proposal: result[1]
}
: undefined
)
.filter((proposal) =>
proposal
)
)
);
}
democracyProposalCount = (): Observable<number> => {
return this
.democracyProposals()
.pipe(
map((proposals: Array<RxProposal>) =>
proposals.length
)
);
}
democracyProposalDeposits = (proposalId: BN): Observable<RxProposalDeposits | undefined> => {
return this
.rawStorage(storage.democracy.public.depositOf, proposalId)
.pipe(
map((result: ResultProposalDeposits): RxProposalDeposits | undefined =>
result && result[0]
? {
addresses: result[1] || [],
balance: result[0]
}
: undefined
)
);
}
democracyReferendumCount = (): Observable<OptBN> => {
return this.rawStorage(storage.democracy.public.referendumCount);
}
democracyReferendumInfoOf = (referendumId: BN | number): Observable<RxReferendum> => {
return this
.rawStorage(storage.democracy.public.referendumInfoOf, referendumId)
.pipe(
map((result: ResultReferendum) =>
result && result[1]
? {
blockNumber: result[0],
id: new BN(referendumId),
proposal: result[1],
voteThreshold: result[2]
}
: undefined
)
);
}
democracyReferendumInfos = (referendumIds: Array<number>): Observable<Array<RxReferendum>> => {
return this.combine(
referendumIds.map((referendumId) =>
this.democracyReferendumInfoOf(referendumId)
),
(referendums: Array<RxReferendum> = []): Array<RxReferendum> =>
referendums.filter((referendum) =>
referendum
)
);
}
democracyReferendumVoters = (referendumId: BN): Observable<Array<RxReferendumVote>> => {
return this.combine(
[
this.democacyVotersFor(referendumId),
this.democracyVotersBalancesOf(referendumId),
this.democracyVotersVotesOf(referendumId)
],
([voters, balances, votes]: [Array<string>, Array<BN>, Array<boolean>]): Array<RxReferendumVote> =>
voters.map((address, index): RxReferendumVote => ({
address,
balance: balances[index] || new BN(0),
vote: votes[index] || false
}))
);
}
democracyReferendums = (): Observable<Array<RxReferendum>> => {
return this.combine(
[
this.democracyReferendumCount(),
this.democracyNextTally()
]
).pipe(
switchMap(([referendumCount, nextTally]: [OptBN, OptBN]): Observable<Array<RxReferendum>> =>
referendumCount && nextTally && referendumCount.gt(nextTally) && referendumCount.gtn(0)
? this.democracyReferendumInfos(
[...Array(referendumCount.sub(nextTally).toNumber())].map((_, i) =>
nextTally.addn(i).toNumber()
)
)
: EMPTY
),
defaultIfEmpty([])
);
}
democacyVoteOf = (index: BN, address: string): Observable<boolean> => {
return this.rawStorage(storage.democracy.public.voteOf, index, address);
}
democracyVotesOf = (index: BN, addresses: Array<string>): Observable<boolean> => {
return this.combine(
addresses.map((address) =>
this.democacyVoteOf(index, address)
)
);
}
chainNewHead = (): Observable<Header | undefined> => {
return this.api.chain.subscribeNewHead();
democacyVotersFor = (index: BN): Observable<Array<string>> => {
return this
.rawStorage(storage.democracy.public.votersFor, index)
.pipe(
map((voters: Array<string> = []) =>
voters
)
);
}
democracyVotersBalancesOf = (referendumId: BN): Observable<Array<BN>> => {
return this
.democacyVotersFor(referendumId)
.pipe(
switchMap((voters: Array<string> = []) =>
this.votingBalances(...voters)
),
defaultIfEmpty([]),
map((balances: Array<RxBalance>) =>
balances.map(({ votingBalance }) =>
votingBalance
)
)
);
}
democracyVotersVotesOf = (referendumId: BN): Observable<Array<boolean>> => {
return this
.democacyVotersFor(referendumId)
.pipe(
switchMap((voters: Array<string> = []) =>
this.democracyVotesOf(referendumId, voters)
),
defaultIfEmpty([])
);
}
democracyVotingPeriod = (): Observable<OptBN> => {
return this.rawStorage(storage.democracy.public.votingPeriod);
}
eraBlockLength = (): Observable<OptBN> => {
@ -231,17 +423,21 @@ export default class ObservableApi implements ObservableApiInterface {
sessionValidators = (): Observable<Array<string>> => {
return this
.rawStorage(storage.session.public.validators)
.pipe(map((validators: Array<string> = []) =>
validators
));
.pipe(
map((validators: Array<string> = []) =>
validators
)
);
}
stakingIntentions = (): Observable<Array<string>> => {
return this
.rawStorage(storage.staking.public.intentions)
.pipe(map((intentions: Array<string> = []) =>
intentions
));
.pipe(
map((intentions: Array<string> = []) =>
intentions
)
);
}
stakingFreeBalanceOf = (address: string): Observable<OptBN> => {
@ -251,9 +447,11 @@ export default class ObservableApi implements ObservableApiInterface {
stakingNominatorsFor = (address: string): Observable<Array<string>> => {
return this
.rawStorage(storage.staking.public.nominatorsFor, address)
.pipe(map((nominators: Array<string> = []) =>
nominators
));
.pipe(
map((nominators: Array<string> = []) =>
nominators
)
);
}
stakingNominating = (address: string): Observable<string | undefined> => {
@ -276,14 +474,14 @@ export default class ObservableApi implements ObservableApiInterface {
return this.rawStorage(storage.system.public.accountIndexOf, address);
}
validatingBalance = (address: string): Observable<ExtendedBalance> => {
validatingBalance = (address: string): Observable<RxBalance> => {
return this.combine(
[
this.votingBalance(address),
this.votingBalancesNominatorsFor(address)
],
([balance, nominators = []]: [ExtendedBalance, Array<ExtendedBalance>]): ExtendedBalance => {
const nominatedBalance = nominators.reduce((total, nominator: ExtendedBalance) => {
([balance, nominators = []]: [RxBalance, Array<RxBalance>]): RxBalance => {
const nominatedBalance = nominators.reduce((total, nominator: RxBalance) => {
return total.add(nominator.votingBalance);
}, new BN(0));
@ -299,27 +497,27 @@ export default class ObservableApi implements ObservableApiInterface {
);
}
validatingBalances = (...addresses: Array<string>): Observable<ExtendedBalanceMap> => {
validatingBalances = (...addresses: Array<string>): Observable<RxBalanceMap> => {
return this.combine(
addresses.map((address) =>
this.validatingBalance(address)
),
(result: Array<ExtendedBalance>): ExtendedBalanceMap =>
(result: Array<RxBalance>): RxBalanceMap =>
result.reduce((balances, balance) => {
balances[balance.address] = balance;
return balances;
}, {} as ExtendedBalanceMap)
}, {} as RxBalanceMap)
);
}
votingBalance = (address: string): Observable<ExtendedBalance> => {
votingBalance = (address: string): Observable<RxBalance> => {
return this.combine(
[
this.stakingFreeBalanceOf(address),
this.stakingReservedBalanceOf(address)
],
([freeBalance = new BN(0), reservedBalance = new BN(0)]: [OptBN, OptBN]): ExtendedBalance => ({
([freeBalance = new BN(0), reservedBalance = new BN(0)]: [OptBN, OptBN]): RxBalance => ({
address,
freeBalance,
nominatedBalance: new BN(0),
@ -331,15 +529,17 @@ export default class ObservableApi implements ObservableApiInterface {
}
votingBalancesNominatorsFor = (address: string) => {
return this.stakingNominatorsFor(address).pipe(
concatMap((nominators: Array<string>) =>
this.votingBalances(...nominators)
),
defaultIfEmpty([])
);
return this
.stakingNominatorsFor(address)
.pipe(
switchMap((nominators: Array<string>) =>
this.votingBalances(...nominators)
),
defaultIfEmpty([])
);
}
votingBalances = (...addresses: Array<string>): Observable<ExtendedBalance[]> => {
votingBalances = (...addresses: Array<string>): Observable<RxBalance[]> => {
return this.combine(
addresses.map((address) =>
this.votingBalance(address)

View File

@ -0,0 +1,92 @@
// 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 BN from 'bn.js';
import { Observable } from 'rxjs';
import { Interfaces } from '@polkadot/jsonrpc/types';
import { ExtrinsicDecoded, SectionItem } from '@polkadot/params/types';
import { Header } from '@polkadot/primitives/header';
import { Storages } from '@polkadot/storage/types';
export type RxBalance = {
address: string,
freeBalance: BN,
nominatedBalance: BN,
reservedBalance: BN,
votingBalance: BN,
stakingBalance: BN,
nominators?: Array<RxBalance>
}
export type RxProposal = {
address: string,
id: BN,
proposal: ExtrinsicDecoded
};
export type RxProposalDeposits = {
balance: BN,
addresses: Array<string>
}
export type RxReferendum = {
blockNumber: BN,
id: BN,
proposal: ExtrinsicDecoded,
voteThreshold: number
}
export type RxReferendumVote = {
address: string,
balance: BN,
vote: boolean
};
export type RxBalanceMap = {
[index: string]: RxBalance
}
export type KeyWithParams = [SectionItem<Storages>, any];
export interface ObservableApiInterface {
rawCall: <T> ({ name, section }: SectionItem<Interfaces>, ...params: Array<any>) => Observable<T>,
rawStorage: <T> (key: SectionItem<Storages>, ...params: Array<any>) => Observable<T>,
rawStorageMulti: <T> (...keys: Array<[SectionItem<Storages>, any]>) => Observable<T>,
bestNumber: () => Observable<BN | undefined>,
chainNewHead: () => Observable<Header | undefined>,
democracyLaunchPeriod: () => Observable<BN | undefined>,
democracyNextTally: () => Observable<BN | undefined>,
democracyProposalCount: () => Observable<number>,
democracyProposalDeposits: (index: BN) => Observable<RxProposalDeposits | undefined>,
democracyProposals: () => Observable<Array<RxProposal>>,
democracyReferendumCount: () => Observable<BN | undefined>,
democracyReferendums: () => Observable<Array<RxReferendum>>,
democracyReferendumVoters:(index: BN) => Observable<Array<RxReferendumVote>>,
democracyVotingPeriod: () => Observable<BN | undefined>,
eraBlockLength: () => Observable<BN | undefined>,
eraBlockProgress: () => Observable<BN | undefined>,
eraBlockRemaining: () => Observable<BN | undefined>,
sessionBlockProgress: () => Observable<BN | undefined>,
sessionBlockRemaining: () => Observable<BN | undefined>,
sessionBrokenPercentLate: () => Observable<BN | undefined>,
sessionBrokenValue: () => Observable<BN | undefined>,
sessionLength: () => Observable<BN | undefined>,
sessionTimeExpected: () => Observable<BN | undefined>,
sessionTimeRemaining: () => Observable<BN | undefined>,
sessionValidators: () => Observable<Array<string>>,
stakingFreeBalanceOf: (address: string) => Observable<BN | undefined>,
stakingIntentions: () => Observable<Array<string>>,
stakingNominatorsFor: (address: string) => Observable<Array<string>>,
stakingNominating: (address: string) => Observable<string | undefined>,
stakingReservedBalanceOf: (address: string) => Observable<BN | undefined>,
systemAccountIndexOf: (address: string) => Observable<BN | undefined>,
timestampBlockPeriod: () => Observable<BN | undefined>,
timestampNow: () => Observable<Date | undefined>,
validatingBalance: (address: string) => Observable<RxBalance>,
validatingBalances: (...addresses: Array<string>) => Observable<RxBalanceMap>,
votingBalance: (address: string) => Observable<RxBalance>,
votingBalances: (...addresses: Array<string>) => Observable<RxBalance[]>
}
export type ObservableApiNames = keyof ObservableApiInterface;

View File

@ -10,61 +10,13 @@ import { Interfaces } from '@polkadot/jsonrpc/types';
import { EncodingVersions, SectionItem } from '@polkadot/params/types';
import { Header } from '@polkadot/primitives/header';
import { Storages, Storage$Key$Value } from '@polkadot/storage/types';
import { ObservableApiInterface } from './ApiObservable/types';
export type BareProps = {
className?: string,
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 type KeyWithParams = [SectionItem<Storages>, any];
export interface ObservableApiInterface {
rawCall: <T> ({ name, section }: SectionItem<Interfaces>, ...params: Array<any>) => Observable<T>,
rawStorage: <T> (key: SectionItem<Storages>, ...params: Array<any>) => Observable<T>,
rawStorageMulti: <T> (...keys: Array<[SectionItem<Storages>, any]>) => Observable<T>,
bestNumber: () => Observable<BN | undefined>,
chainNewHead: () => Observable<Header | undefined>,
eraBlockLength: () => Observable<BN | undefined>,
eraBlockProgress: () => Observable<BN | undefined>,
eraBlockRemaining: () => Observable<BN | undefined>,
sessionBlockProgress: () => Observable<BN | undefined>,
sessionBlockRemaining: () => Observable<BN | undefined>,
sessionBrokenPercentLate: () => Observable<BN | undefined>,
sessionBrokenValue: () => Observable<BN | undefined>,
sessionLength: () => Observable<BN | undefined>,
sessionTimeExpected: () => Observable<BN | undefined>,
sessionTimeRemaining: () => Observable<BN | undefined>,
sessionValidators: () => Observable<Array<string>>,
stakingFreeBalanceOf: (address: string) => Observable<BN | undefined>,
stakingIntentions: () => Observable<Array<string>>,
stakingNominatorsFor: (address: string) => Observable<Array<string>>,
stakingNominating: (address: string) => Observable<string | undefined>,
stakingReservedBalanceOf: (address: string) => Observable<BN | undefined>,
systemAccountIndexOf: (address: string) => Observable<BN | undefined>,
timestampBlockPeriod: () => Observable<BN | undefined>,
timestampNow: () => Observable<Date | undefined>,
validatingBalance: (address: string) => Observable<ExtendedBalance>,
validatingBalances: (...addresses: Array<string>) => Observable<ExtendedBalanceMap>,
votingBalance: (address: string) => Observable<ExtendedBalance>,
votingBalances: (...addresses: Array<string>) => Observable<ExtendedBalance[]>
}
export type ObservableApiNames = keyof ObservableApiInterface;
export type ApiProps = {
api: RxApiInterface,
apiConnected: boolean,

View File

@ -4,7 +4,8 @@
// TODO: Lots of duplicated code between this and withObservable, surely there ois a better way of doing this?
import { RxProps, ObservableApiNames } from '../types';
import { ObservableApiNames } from '../ApiObservable/types';
import { RxProps } from '../types';
import { HOC, Options, DefaultProps, RenderFn } from './types';
import React from 'react';
@ -24,8 +25,6 @@ type State<T> = RxProps<T> & {
// FIXME proper types for attributes
export default function withObservable<T> (observable: ObservableApiNames, { onChange, params = [], paramProp = 'params', propName = observable, transform = echoTransform }: Options<T> = {}): HOC<T> {
console.log('observable', observable, paramProp);
return (Inner: React.ComponentType<any>, defaultProps: DefaultProps<T> = {}, render?: RenderFn): React.ComponentType<any> => {
class WithObservable extends React.Component<any, State<T>> {
state: State<T>;

View File

@ -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 { BaseProps, ObservableApiNames } from '../types';
import { ObservableApiNames } from '../ApiObservable/types';
import { BaseProps } from '../types';
import { ComponentRenderer, DefaultProps, RenderFn, Options } from './types';
import Div from '../Div';

View File

@ -16,7 +16,7 @@
},
"dependencies": {
"@babel/runtime": "^7.0.0-beta.51",
"@polkadot/extrinsics": "^0.28.9",
"@polkadot/extrinsics": "^0.28.11",
"@polkadot/ui-app": "^0.19.30",
"@polkadot/ui-keyring": "^0.19.30"
},

View File

@ -5,6 +5,7 @@
"paths": {
"@polkadot/app-accounts/*": [ "packages/app-accounts/src/*" ],
"@polkadot/app-addresses/*": [ "packages/app-addresses/src/*" ],
"@polkadot/app-democracy/*": [ "packages/app-democracy/src/*" ],
"@polkadot/app-explorer/*": [ "packages/app-explorer/src/*" ],
"@polkadot/app-example/*": [ "packages/app-example/src/*" ],
"@polkadot/app-extrinsics/*": [ "packages/app-extrinsics/src/*" ],

125
yarn.lock
View File

@ -765,22 +765,22 @@
version "1.1.0"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a"
"@polkadot/api-format@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/api-format/-/api-format-0.28.9.tgz#1360e37534d21e871b1bee0d58a0a2ebb99ad753"
"@polkadot/api-format@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/api-format/-/api-format-0.28.11.tgz#7497f36d764016cd715795b6a62de62424d78c5b"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/jsonrpc" "^0.28.9"
"@polkadot/primitives" "^0.28.9"
"@polkadot/jsonrpc" "^0.28.11"
"@polkadot/primitives" "^0.28.11"
"@polkadot/util" "^0.28.1"
"@polkadot/util-keyring" "^0.28.1"
"@polkadot/api-provider@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/api-provider/-/api-provider-0.28.9.tgz#642cc45d9248a61c7f80969290e19461c8f67047"
"@polkadot/api-provider@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/api-provider/-/api-provider-0.28.11.tgz#3d2ccbb039d84130e828e8a9883d4e5cdee10d99"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/storage" "^0.28.9"
"@polkadot/storage" "^0.28.11"
"@polkadot/util" "^0.28.1"
"@polkadot/util-crypto" "^0.28.1"
"@polkadot/util-keyring" "^0.28.1"
@ -789,25 +789,25 @@
isomorphic-fetch "^2.2.1"
websocket "^1.0.25"
"@polkadot/api-rx@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/api-rx/-/api-rx-0.28.9.tgz#5ad6352ce42df7888c37baef40c2acf24e5a7d76"
"@polkadot/api-rx@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/api-rx/-/api-rx-0.28.11.tgz#5f0a057ea7b160b3242f12f153bff581278c9271"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/api" "^0.28.9"
"@polkadot/api-provider" "^0.28.9"
"@polkadot/api" "^0.28.11"
"@polkadot/api-provider" "^0.28.11"
"@types/rx" "^4.1.1"
rxjs "^6.2.2"
"@polkadot/api@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.28.9.tgz#86c2f2db81256ba4d101d37ca18c3fcaa076e0c7"
"@polkadot/api@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.28.11.tgz#46b2a7b395dabdc9cb47a769aec3c419a7c2e405"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/api-format" "^0.28.9"
"@polkadot/api-provider" "^0.28.9"
"@polkadot/jsonrpc" "^0.28.9"
"@polkadot/params" "^0.28.9"
"@polkadot/api-format" "^0.28.11"
"@polkadot/api-provider" "^0.28.11"
"@polkadot/jsonrpc" "^0.28.11"
"@polkadot/params" "^0.28.11"
"@polkadot/util" "^0.28.1"
"@polkadot/dev-react@^0.20.12":
@ -877,48 +877,48 @@
typedoc "^0.11.1"
typescript "^2.9.2"
"@polkadot/extrinsics@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics/-/extrinsics-0.28.9.tgz#b7804179153c29e793a9d549b566f23a13f98558"
"@polkadot/extrinsics@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics/-/extrinsics-0.28.11.tgz#834c04b58dbdeee9750bdca7a2cc125c2411bb2e"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/params" "^0.28.9"
"@polkadot/primitives" "^0.28.9"
"@polkadot/params" "^0.28.11"
"@polkadot/primitives" "^0.28.11"
"@polkadot/util" "^0.28.1"
"@polkadot/jsonrpc@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.28.9.tgz#564116660f703084048139c3bda7f01c979e55b4"
"@polkadot/jsonrpc@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/jsonrpc/-/jsonrpc-0.28.11.tgz#0687b93077be6ed3dc16f1d8937918ed9647b5e8"
dependencies:
"@polkadot/params" "^0.28.9"
"@polkadot/params" "^0.28.11"
babel-runtime "^6.26.0"
"@polkadot/params@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/params/-/params-0.28.9.tgz#220888b70d147c71f195d06b1e38525c520e04aa"
"@polkadot/params@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/params/-/params-0.28.11.tgz#9dea2efa3c6b4725547fd6dd8b3c2312d1d226d7"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/extrinsics" "^0.28.9"
"@polkadot/params" "^0.28.9"
"@polkadot/primitives" "^0.28.9"
"@polkadot/extrinsics" "^0.28.11"
"@polkadot/params" "^0.28.11"
"@polkadot/primitives" "^0.28.11"
"@polkadot/util" "^0.28.1"
"@polkadot/util-keyring" "^0.28.1"
"@polkadot/primitives@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/primitives/-/primitives-0.28.9.tgz#e451adc1faf78b397f7020f0f036d8542e48aab0"
"@polkadot/primitives@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/primitives/-/primitives-0.28.11.tgz#4c32f181a779f002d9def60cecf46237034ef270"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/trie-hash" "^0.28.1"
"@polkadot/util" "^0.28.1"
"@polkadot/storage@^0.28.9":
version "0.28.9"
resolved "https://registry.yarnpkg.com/@polkadot/storage/-/storage-0.28.9.tgz#2ac372d64ba1f92310da88476a1be16f92a30443"
"@polkadot/storage@^0.28.11":
version "0.28.11"
resolved "https://registry.yarnpkg.com/@polkadot/storage/-/storage-0.28.11.tgz#c388142dcc409fb151512f2c34b6d191656c9bd3"
dependencies:
"@babel/runtime" "^7.0.0-beta.51"
"@polkadot/params" "^0.28.9"
"@polkadot/primitives" "^0.28.9"
"@polkadot/params" "^0.28.11"
"@polkadot/primitives" "^0.28.11"
"@polkadot/util" "^0.28.1"
"@polkadot/util-crypto" "^0.28.1"
"@polkadot/util-keyring" "^0.28.1"
@ -1021,6 +1021,10 @@
dependencies:
"@types/base-x" "*"
"@types/chart.js@^2.7.30":
version "2.7.30"
resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.30.tgz#b3a973d4790ff5cbf1352ee756f4bfde5ae1cd27"
"@types/color-convert@*":
version "1.9.0"
resolved "https://registry.yarnpkg.com/@types/color-convert/-/color-convert-1.9.0.tgz#bfa8203e41e7c65471e9841d7e306a7cd8b5172d"
@ -2862,6 +2866,26 @@ chardet@^0.4.0:
version "0.4.2"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.4.2.tgz#b5473b33dc97c424e5d98dc87d55d4d8a29c8bf2"
chart.js@^2.7.2:
version "2.7.2"
resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-2.7.2.tgz#3c9fde4dc5b95608211bdefeda7e5d33dffa5714"
dependencies:
chartjs-color "^2.1.0"
moment "^2.10.2"
chartjs-color-string@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.5.0.tgz#8d3752d8581d86687c35bfe2cb80ac5213ceb8c1"
dependencies:
color-name "^1.0.0"
chartjs-color@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/chartjs-color/-/chartjs-color-2.2.0.tgz#84a2fb755787ed85c39dd6dd8c7b1d88429baeae"
dependencies:
chartjs-color-string "^0.5.0"
color-convert "^0.5.3"
cheerio@^1.0.0-rc.2:
version "1.0.0-rc.2"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db"
@ -3092,6 +3116,10 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
color-convert@^0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd"
color-convert@^1.3.0, color-convert@^1.8.2, color-convert@^1.9.0, color-convert@^1.9.1:
version "1.9.2"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.2.tgz#49881b8fba67df12a96bdf3f56c0aab9e7913147"
@ -7570,7 +7598,7 @@ modify-values@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022"
moment@^2.22.2, moment@^2.6.0:
moment@^2.10.2, moment@^2.22.2, moment@^2.6.0:
version "2.22.2"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66"
@ -9225,6 +9253,13 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
minimist "^1.2.0"
strip-json-comments "~2.0.1"
react-chartjs-2@^2.7.4:
version "2.7.4"
resolved "https://registry.yarnpkg.com/react-chartjs-2/-/react-chartjs-2-2.7.4.tgz#e41ea4e81491dc78347111126a48e96ee57db1a6"
dependencies:
lodash "^4.17.4"
prop-types "^15.5.8"
react-copy-to-clipboard@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz#8eae107bb400be73132ed3b6a7b4fb156090208e"