Expand the example, indicating validators with sorting (#129)
This commit is contained in:
parent
378fed132c
commit
c03a4473f6
@ -9,6 +9,8 @@ This guide will take you through some basic examples in using the Components and
|
||||
- [Query storage via API](tut-003.md) Shows how to construct an API query to pull information from storage
|
||||
- [Query storage, replace HOC](tut-004.md) Queries storage, replacing the previous use of our HOC
|
||||
- [Combining proposal and intentions](tut-005.md) Using the information collected previously, combine the displays
|
||||
- [Showing selected validators](tut-006.md) Expand on intentions, filtering with current validators
|
||||
- [Sorting intentions by balance](tut-007.md) Cleanup our display, sorting the intentions by balance
|
||||
|
||||
## ... and in the end ...
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 205 KiB After Width: | Height: | Size: 382 KiB |
@ -47,3 +47,11 @@ class Comp extends React.PureComponent<ApiProps, State> {
|
||||
# walk-through
|
||||
|
||||
The render is straight-forward. For each of our intentions, render a row in a table showing the icon & address, the balance of the account and the number of proposals.
|
||||
|
||||
# actual output
|
||||
|
||||

|
||||
|
||||
# next steps
|
||||
|
||||
[Next up](tut-006.md) we will expand our list, showing the actual activive validators.
|
||||
|
||||
BIN
examples/tut-005.png
Normal file
BIN
examples/tut-005.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
45
examples/tut-006.md
Normal file
45
examples/tut-006.md
Normal file
@ -0,0 +1,45 @@
|
||||
# adding validators
|
||||
|
||||
[Previously](tut-005.md), the list of accounts wishing to paritipate in staking has been shown. The next step would be to merge this with the list of validators (as selected from the intentions) and show these slightly differently.
|
||||
|
||||
# component
|
||||
|
||||
Building on the component created previously, we can expand [the selection](../packages/app-example/src/comp-006.tsx) (and rendering) to query the active validators.
|
||||
|
||||
```js
|
||||
class Comp extends React.PureComponent<ApiProps, State> {
|
||||
...
|
||||
subscribeValidators () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(validatorsMethod)();
|
||||
const transform = storageTransform(validatorsMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
this.setState({
|
||||
validators: (transform(value, 0) as any[]).map(encodeAddress)
|
||||
});
|
||||
});
|
||||
}
|
||||
...
|
||||
renderAccount = (address: string, proposals: number[] = [], isValidator: boolean = false) => {
|
||||
return (
|
||||
<tr className={isValidator ? 'validator' : ''} key={address}>
|
||||
...
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
# walk through
|
||||
|
||||
Building on what we have done previously, we now also subscribe to the list of validators (`storage.session.public.validators`) and uses this to highlight the actual active validators that we have found. It is important to note that the validator list is a subset of intentions, i.e. validators will continue to be shows as active intentions while they are part of the network.
|
||||
|
||||
# actual output
|
||||
|
||||

|
||||
|
||||
# next steps
|
||||
|
||||
For the [next round](tut-007.md), we will cleanup the display somewhat, allowing us to make sense of the actual unordered information displayed.
|
||||
BIN
examples/tut-006.png
Normal file
BIN
examples/tut-006.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 259 KiB |
70
examples/tut-007.md
Normal file
70
examples/tut-007.md
Normal file
@ -0,0 +1,70 @@
|
||||
# sorting by balance
|
||||
|
||||
After [the last section](tut-006.md), we are at a point we we have the intentions, actual validators and their balances. For users, we would like this display to be more usable, so here we are aiming on sorting by balance.
|
||||
|
||||
# component
|
||||
|
||||
Expanding on our previous component, we now change the previously used `<Balance />` component, into doing [actual `freeBalanceOf` queries](../packages/app-example/src/comp-007.tsx). There are 2 options here - we could also use the `onChange` event on the `Balance` component to return the display value. However here we opt for querying the raw value, transforming it and sorting with it.
|
||||
|
||||
```js
|
||||
class Comp extends React.PureComponent<ApiProps, State> {
|
||||
...
|
||||
subscribeBalances (accounts: string[]) {
|
||||
const { api } = this.props;
|
||||
const keyCreator = createStorageKey(balanceMethod);
|
||||
const { balances, subscriptions } = this.state;
|
||||
const newBalances = { ...balances };
|
||||
|
||||
accounts.forEach((account) => {
|
||||
if (newBalances[account]) {
|
||||
return;
|
||||
} else {
|
||||
newBalances[account] = ZERO;
|
||||
}
|
||||
|
||||
subscriptions.push(
|
||||
api.state
|
||||
// Here we pass a parameter to the key generator, so it points to the correct storage entry
|
||||
.getStorage(keyCreator(account))
|
||||
.subscribe((balance) => {
|
||||
this.setState(({ balances }: State) => {
|
||||
const newBalances = { ...balances };
|
||||
|
||||
newBalances[account] = u8aToBn(balance, true);
|
||||
|
||||
return {
|
||||
balances: newBalances
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
balances: newBalances,
|
||||
subscriptions
|
||||
});
|
||||
}
|
||||
...
|
||||
render () {
|
||||
const { balances, intentions, proposals, validators } = this.state;
|
||||
const sortedIntentions = intentions.sort((a, b) =>
|
||||
(balances[b] || ZERO).cmp(balances[a] || ZERO)
|
||||
);
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
# walk through
|
||||
|
||||
By now the subscription approach is known. Where things do differ however is the passing of a parameter to the key encoder. Since each balance entry is denoted by the actual account, the hashing needs to take the actual account and append it to the prefix location before making a hash that the runtime understands.
|
||||
|
||||
For the actual storage value, we have additional formatting on the raw value. Here we are taking the `Uint8Array` input and transforming it to a `BN`. All values in the storage are stored as little-endian-encoded values, hence passing the additional `isLe` flag to the `Uint8Array` -> `BN` conversion helper.
|
||||
|
||||
NOTE: It would make sense for future versions of the API to be expanded so additional formatting, either here or e.g. accountId are not needed. (At this early stage however the APIs are still at a too-low level to transparently take care of the transformation - although all information is contained in the params of the key being encoded)
|
||||
|
||||
# actual output
|
||||
|
||||

|
||||
BIN
examples/tut-007.png
Normal file
BIN
examples/tut-007.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 382 KiB |
@ -12,6 +12,7 @@
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0-beta.51",
|
||||
"@polkadot/storage": "^0.26.9",
|
||||
"@polkadot/ui-react": "^0.18.10",
|
||||
"@polkadot/ui-react-rx": "^0.18.10",
|
||||
"@polkadot/util-keyring": "^0.26.9"
|
||||
}
|
||||
|
||||
@ -94,7 +94,7 @@ class Comp extends React.PureComponent<ApiProps, State> {
|
||||
const { intentions, proposals } = this.state;
|
||||
|
||||
return (
|
||||
<table>
|
||||
<table className='accounts'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
|
||||
145
packages/app-example/src/comp-006.tsx
Normal file
145
packages/app-example/src/comp-006.tsx
Normal file
@ -0,0 +1,145 @@
|
||||
// Copyright 2017-2018 @polkadot/app-example authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { ApiProps } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import storage from '@polkadot/storage';
|
||||
import createStorageKey from '@polkadot/storage/key';
|
||||
import withApi from '@polkadot/ui-react-rx/with/api';
|
||||
import storageTransform from '@polkadot/ui-react-rx/with/transform/storage';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import Balance from '@polkadot/ui-react-rx/Balance';
|
||||
|
||||
const intentionsMethod = storage.staking.public.intentions;
|
||||
const proposalMethod = storage.democracy.public.proposals;
|
||||
const validatorsMethod = storage.session.public.validators;
|
||||
|
||||
type State = {
|
||||
intentions: string[],
|
||||
proposals: {
|
||||
[index: string]: number[]
|
||||
},
|
||||
subscriptions: any[],
|
||||
validators: string[]
|
||||
};
|
||||
|
||||
class Comp extends React.PureComponent<ApiProps, State> {
|
||||
constructor (props: ApiProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
intentions: [],
|
||||
proposals: {},
|
||||
subscriptions: [],
|
||||
validators: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({
|
||||
subscriptions: [
|
||||
this.subscribeIntentions(),
|
||||
this.subscribeProposals(),
|
||||
this.subscribeValidators()
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
subscribeIntentions () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(intentionsMethod)();
|
||||
const transform = storageTransform(intentionsMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
this.setState({
|
||||
intentions: (transform(value, 0) as any[]).map(encodeAddress)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribeProposals () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(proposalMethod)();
|
||||
const transform = storageTransform(proposalMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
this.setState({
|
||||
proposals: (transform(value, 0) as any[])
|
||||
.reduce((proposals, [propIdx, proposal, accountId]) => {
|
||||
const address = encodeAddress(accountId);
|
||||
|
||||
if (!proposals[address]) {
|
||||
proposals[address] = [propIdx];
|
||||
} else {
|
||||
proposals[address].push(propIdx);
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}, {})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribeValidators () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(validatorsMethod)();
|
||||
const transform = storageTransform(validatorsMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
this.setState({
|
||||
validators: (transform(value, 0) as any[]).map(encodeAddress)
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { subscriptions } = this.state;
|
||||
|
||||
subscriptions.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
render () {
|
||||
const { intentions, proposals, validators } = this.state;
|
||||
|
||||
return (
|
||||
<table className='accounts'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Address</th>
|
||||
<th>Balance</th>
|
||||
<th>Proposals</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{intentions.map((address) => (
|
||||
this.renderAccount(address, proposals[address], validators.includes(address))
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderAccount = (address: string, proposals: number[] = [], isValidator: boolean = false) => {
|
||||
return (
|
||||
<tr className={isValidator ? 'validator' : ''} key={address}>
|
||||
<td><IdentityIcon size={24} value={address} /></td>
|
||||
<td>{address}</td>
|
||||
<td><Balance params={address} /></td>
|
||||
<td>{proposals.length}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withApi(Comp);
|
||||
195
packages/app-example/src/comp-007.tsx
Normal file
195
packages/app-example/src/comp-007.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
// Copyright 2017-2018 @polkadot/app-example authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { ApiProps } from '@polkadot/ui-react-rx/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
|
||||
import storage from '@polkadot/storage';
|
||||
import createStorageKey from '@polkadot/storage/key';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import withApi from '@polkadot/ui-react-rx/with/api';
|
||||
import storageTransform from '@polkadot/ui-react-rx/with/transform/storage';
|
||||
import encodeAddress from '@polkadot/util-keyring/address/encode';
|
||||
import u8aToBn from '@polkadot/util/u8a/toBn';
|
||||
|
||||
const balanceMethod = storage.staking.public.freeBalanceOf;
|
||||
const intentionsMethod = storage.staking.public.intentions;
|
||||
const proposalMethod = storage.democracy.public.proposals;
|
||||
const validatorsMethod = storage.session.public.validators;
|
||||
|
||||
const ZERO = new BN(0);
|
||||
|
||||
type State = {
|
||||
balances: {
|
||||
[index: string]: BN
|
||||
},
|
||||
intentions: string[],
|
||||
proposals: {
|
||||
[index: string]: number[]
|
||||
},
|
||||
subscriptions: any[],
|
||||
validators: string[]
|
||||
};
|
||||
|
||||
class Comp extends React.PureComponent<ApiProps, State> {
|
||||
constructor (props: ApiProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
balances: {},
|
||||
intentions: [],
|
||||
proposals: {},
|
||||
subscriptions: [],
|
||||
validators: []
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
this.setState({
|
||||
subscriptions: [
|
||||
this.subscribeIntentions(),
|
||||
this.subscribeProposals(),
|
||||
this.subscribeValidators()
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
subscribeIntentions () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(intentionsMethod)();
|
||||
const transform = storageTransform(intentionsMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
const intentions = (transform(value, 0) as any[]).map(encodeAddress);
|
||||
|
||||
this.setState({ intentions }, () => {
|
||||
this.subscribeBalances(intentions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribeProposals () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(proposalMethod)();
|
||||
const transform = storageTransform(proposalMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
this.setState({
|
||||
proposals: (transform(value, 0) as any[])
|
||||
.reduce((proposals, [propIdx, proposal, accountId]) => {
|
||||
const address = encodeAddress(accountId);
|
||||
|
||||
if (!proposals[address]) {
|
||||
proposals[address] = [propIdx];
|
||||
} else {
|
||||
proposals[address].push(propIdx);
|
||||
}
|
||||
|
||||
return proposals;
|
||||
}, {})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
subscribeValidators () {
|
||||
const { api } = this.props;
|
||||
const key = createStorageKey(validatorsMethod)();
|
||||
const transform = storageTransform(validatorsMethod);
|
||||
|
||||
return api.state
|
||||
.getStorage(key)
|
||||
.subscribe((value) => {
|
||||
const validators = (transform(value, 0) as any[]).map(encodeAddress);
|
||||
|
||||
this.setState({ validators });
|
||||
});
|
||||
}
|
||||
|
||||
subscribeBalances (accounts: string[]) {
|
||||
const { api } = this.props;
|
||||
const keyCreator = createStorageKey(balanceMethod);
|
||||
const { balances, subscriptions } = this.state;
|
||||
const newBalances = { ...balances };
|
||||
|
||||
accounts.forEach((account) => {
|
||||
if (newBalances[account]) {
|
||||
return;
|
||||
} else {
|
||||
newBalances[account] = ZERO;
|
||||
}
|
||||
|
||||
subscriptions.push(
|
||||
api.state
|
||||
// Here we pass a parameter to the key generator, so it points to the correct storage entry
|
||||
.getStorage(keyCreator(account))
|
||||
.subscribe((balance) => {
|
||||
this.setState(({ balances }: State) => {
|
||||
const newBalances = { ...balances };
|
||||
|
||||
newBalances[account] = u8aToBn(balance, true);
|
||||
|
||||
return {
|
||||
balances: newBalances
|
||||
};
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this.setState({
|
||||
balances: newBalances,
|
||||
subscriptions
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
const { subscriptions } = this.state;
|
||||
|
||||
subscriptions.forEach((sub) => sub.unsubscribe());
|
||||
}
|
||||
|
||||
render () {
|
||||
const { balances, intentions, proposals, validators } = this.state;
|
||||
const sortedIntentions = intentions.sort((a, b) =>
|
||||
(balances[b] || ZERO).cmp(balances[a] || ZERO)
|
||||
);
|
||||
|
||||
return (
|
||||
<table className='accounts'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Address</th>
|
||||
<th>Balance</th>
|
||||
<th>Proposals</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{sortedIntentions.map((address) => (
|
||||
this.renderAccount(address, balances[address], proposals[address], validators.includes(address))
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
renderAccount = (address: string, balance: BN = ZERO, proposals: number[] = [], isValidator: boolean = false) => {
|
||||
return (
|
||||
<tr className={isValidator ? 'validator' : ''} key={address}>
|
||||
<td><IdentityIcon size={24} value={address} /></td>
|
||||
<td>{address}</td>
|
||||
<td>{balance.toString(10)}</td>
|
||||
<td>{proposals.length}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withApi(Comp);
|
||||
17
packages/app-example/src/index-006.tsx
Normal file
17
packages/app-example/src/index-006.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2018 @polkadot/app-example authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Comp from './comp-006';
|
||||
|
||||
export default class App extends React.PureComponent<any> {
|
||||
render () {
|
||||
return (
|
||||
<div className='example--App'>
|
||||
<Comp />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
17
packages/app-example/src/index-007.tsx
Normal file
17
packages/app-example/src/index-007.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright 2017-2018 @polkadot/app-example authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import Comp from './comp-007';
|
||||
|
||||
export default class App extends React.PureComponent<any> {
|
||||
render () {
|
||||
return (
|
||||
<div className='example--App'>
|
||||
<Comp />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,10 @@
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.example--App td {
|
||||
.example--App table.accounts td {
|
||||
padding: 0.125em 0.5em;
|
||||
}
|
||||
|
||||
.example--App table.accounts tr.validator {
|
||||
background: #f2f2f2;
|
||||
}
|
||||
|
||||
@ -10,9 +10,11 @@ import Tut002 from './comp-002';
|
||||
import Tut003 from './comp-003';
|
||||
import Tut004 from './comp-004';
|
||||
import Tut005 from './comp-005';
|
||||
import Tut006 from './comp-006';
|
||||
import Tut007 from './comp-007';
|
||||
|
||||
const Components: { [index: string]: React.ComponentType<any> } = {
|
||||
Tut002, Tut003, Tut004, Tut005
|
||||
Tut002, Tut003, Tut004, Tut005, Tut006, Tut007
|
||||
};
|
||||
|
||||
type State = {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user