Example & tutorial (#127)

* Add tutorial step 001

* Update LICENSE header

* Query storage via HOC

* Tut 003, read proposals

* Tut 004 replacing HOC

* Pull Intentions & Proposals together

* Add final image

* Cleanup unused methods

* Source links

* Remove unused var
This commit is contained in:
Jaco Greeff 2018-07-09 13:38:31 +02:00 committed by GitHub
parent 8d41e8ada9
commit 3fb85094a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 874 additions and 24 deletions

View File

@ -60,3 +60,7 @@ Demos for the different libraries can be viewed with `yarn run demo:<name>` and
- `yarn run demo:identicon`
- `yarn run demo:ui`
- `yarn run demo:rx`
## tutorials
Looking for tutorials to get started? Look at [examples](examples/README.md) for a step-by-step guide to warning the API and Components to display some information.

17
examples/README.md Normal file
View File

@ -0,0 +1,17 @@
# examples
This guide will take you through some basic examples in using the Components and API to build tools to extract information from a Substrate network client. It follows the line of a set of tutorials that will setup basic infrastructure, queies and then string these together into doing some more complex things.
## tutorials
- [Basic application scaffolding](tut-001.md) Show the setup of a new app in this framework. (Typically only needed when new top-level features are added)
- [Storage and pulling from storage](tut-002.md) Shows how to use the built-in components to show something basic from storage
- [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
## ... and in the end ...
You will know how to use UI components and the API to render a mapping of intentions, their balanances to proposals
![final](https://raw.githubusercontent.com/polkadot-js/apps/master/examples/final.png)

BIN
examples/final.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

52
examples/tut-001.md Normal file
View File

@ -0,0 +1,52 @@
# basic scaffolding
To add a top-level application, some basic setup is required. This is only applicable for new top-level apps, i.e. like we currently have extrinsics, storage, accounts, etc. When just hacking away on a specific app, this section is not needed.
# basic setup
Initially we would need an new application folder to be added under [packages](../packages). For the sake of this tutorial, we have just created [app-example](../packages/app-example), a basic app that will be used in the rest of the tutorials.
Once created, initialise the app with `package.json`, `README.md`, `LICENSE.md` and the basic folder setup under `src`
# creating an app entry
First off, let's create an app entry. Since we will just have a basic sample, let's keep it simple for now. Under [our example src](../packages/app-example/src) we just create an `index.tsx` with a simple entry point.
```js
import React from 'react';
export default class App extends React.PureComponent<any> {
render () {
return (
<div>hello world</div>
);
}
}
```
(We have kept the original, which has changed as the tutorial progresses as [index-001.tsx](../packages/app-example/src/index-001.tsx))
# making it visible
Once the app is created, it needs to be hooked up to the rest, allowing TypeScript, Jest & Webpack to find it and use it.
- To enable Jest mappings, add the name of the app to [the Jest config](../jest.config.js)
- To enable TypeScript mappings, add the app to [the TypeScript config](../tsconfig.json)
- To allow the app to be included in the Webpack build, add it to [Webpack config](../packages/apps/webpack.config.js) (Note that this is not high-level, but rather at the entry point of the loader - this allows packages to have their own configs in the case of demos)
At this point the application will be available and included, even though it does not have a [navigation entry](../packages/apps/routing) yet. Adding the navigation -
- Add a reference to the application to the [apps package.json](../packages/apps/package.json)
- Add a route file the defines the [app, name & icon](../packages/apps/routing/example.ts)
- Finally include the route in the [route index](../packages/apps/touting/index.ts)
# running!
Basic app added, now let it run and see what it does.
- Start the webpack server via `WS_URL=wss://poc-1.polkadot.io:9944 yarn run start`
- Access the applcation on [localhost:3000/#/example](http://localhost:3000/#/example)
# that is it
We have something basic wired-up, now let's [build something](tut-002.md)

85
examples/tut-002.md Normal file
View File

@ -0,0 +1,85 @@
# storage via HOC
In the [previous section](tut-001.md) we setup a basic application framework in [app-example](../packages/app-example). Here we will continute adding some useful queries using bare-bones UI components.
In substrate chains, the storage (state) is exposed via API, allowing any app to perform queries against the current (and previous) states. Here we will use some built-in components to perform a query over the intentions, displaying the results.
# creating a component
Here we are creating a component that will attach to a specific storage entry, and display any changes as they happen on-chain. For this example, we are interested in the `intentions` storage area, i.e. those accounts that wish to participate in staking.
Initially we will just create it, and then walk through what actually happens. In [comp-002.tsx](../packages/app-example/src/comp-002.tsx) we have -
```js
import React from 'react';
import storage from '@polkadot/storage';
import withStorageDiv from '@polkadot/ui-react-rx/with/storageDiv';
import encodeAddress from '@polkadot/util-keyring/address/encode';
const method = storage.staking.public.intentions;
const Comp: React.ComponentType<any> = withStorageDiv(method)(
(value: Uint8Array[]): string => {
if (!value || !value.length) {
return 'No intentions found';
}
return value.map(encodeAddress).join(', ');
}
);
export default Comp;
```
Additionally, we change our entry-point to import this component.
```js
import React from 'react';
import Comp from './comp-002';
export default class App extends React.PureComponent<any> {
render () {
return (
<Comp />
);
}
}
```
Now going to the app via [localhost:3000/#/example](http://localhost:3000/#/example) renders a list of accounts.
# walk-through
So what have we done. First off, we import all available storage entries and select the method we are interested in, i.e.
```js
import storage from '@polkadot/storage';
const method = storage.staking.public.intentions;
```
Storage entries can be explored via the storage app and will always be (in code) `storage.<section>.public.<method>`. These are all defined in [@polkadot/storage](https://github.com/polkadot-js/common/tree/master/packages/type-storage/src)
Next up we use an HOC helper `withStorageDiv` which basically takes a storage method and with the specified renderer wraps it in a Div.
```js
import withStorageDiv from '@polkadot/ui-react-rx/with/storageDiv';
const Comp: React.ComponentType<any> = withStorageDiv(method)(
(value: Uint8Array[]): string => {
...
}
);
```
Here the storage query will return an `Uint8Array[]`, or rather an array of Accounts as used internally. These Accounts however are a list of the actual publicKeys, so for display we convert them with a formatter,
```js
return value.map(encodeAddress).join(', ');
```
# next steps
At this point we have, with minimal effort, used some HOC helpers to display information from a storage area. [Next up](tut-003.md) we will actually pull this together and do the same query directly via the API.

61
examples/tut-003.md Normal file
View File

@ -0,0 +1,61 @@
# storage via API
In the [previous section](tut-002.md) we have used an HOC to query a storage area. Here we will repeat the same, however this time we will actually use the Rx API to accomplish the same goal.
# creating the component
As in previous examples, we will first show the [actual component](../packages/app-example/src/comp-003.tsx) we are using and then delve into how it fits together. Focussing on the meat of the API subscription that happens in the `componentMount` cycle -
```js
class Comp extends React.PureComponent<ApiProps, State> {
...
componentDidMount () {
this.subscribeProposals();
}
// TODO We should unsubscribe from subscriptions
subscribeProposals () {
const { api } = this.props;
const key = createStorageKey(method)();
const transform = storageTransform(method);
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;
}, {})
});
});
}
...
}
export default withApi(Comp);
```
# walk-through
Since we are working on a slightly lower level now, a couple of things happen.
Unlike the case where we use an HOC, we need to manually create the actual storage key (as encoded) before calling the API. the `createStorageKey(<method>)(<params>)` function does that for us. In this case the storage entry doesn't require any params (as opposed to e.g. `staking.freeBalanceOf`). This encoding takes the method, params, applys the correct prefixes and hashes the result to form a key that is known to the staorage.
We use a helper function to decode the values as it comes back - without this we would just have a raw stream of bytes as retrieved from storage. The decding take the raw stream and splits it into a mangeable Array stream (based on trhe number of items), each containing a tuple of data - in this case the `PropIndex`, `Proposal` and `AccountId`.
We use the API provided on the component props, as injected via `withApi`, to make an API query - subscribing to any updates. As any updates happen to the storage, these will be automatically fed through the API subscriptions, updating the component display.
Finally we loop through the proposals retrieved, indexing these by the actual account address - this information we finally use in the render to update the display.
# next up
In the next round, we will extend our subscriptions to also pull the information we previously got via HOC and then map [these together](tut-004.md).

44
examples/tut-004.md Normal file
View File

@ -0,0 +1,44 @@
# storage via API (part II)
[Previously](tut-002.md) we queried intentions via HOC, then [followed up](tut-003.md) with querying via API. Here we will replace our previous HOC component with the same storage queries.
# the component
Here we extend the component from the previous section, adding a subscription to intentions to create our [new component](../packages/app-example/src/comp-004.tsx) -
```js
class Comp extends React.PureComponent<ApiProps, State> {
...
componentDidMount () {
this.setState({
subscriptions: [
this.subscribeIntentions(),
this.subscribeProposals()
]
});
}
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)
});
});
}
...
}
```
# walk-through
This subscription is much simpler than what was found before, however it follows the same basic approach - query, transform - in this case the transform is the same as the one applied even for the HOC component.
# next steps
[Next up](tut-005.md), we will be using the actual data we have collected to render something useful on-screen.

49
examples/tut-005.md Normal file
View File

@ -0,0 +1,49 @@
# pulling it together
Previously we have used the API to query the [actual storage](tut-004.md), here we will build on that base to show something usable on-screen.
# component
[This component](../packages/app-example/src/comp-005.tsx) will focus mostly on the `render()` method and display on-screen. As with other examples, we first show the actual code and then provide a short walk-through of the relevant parts -
```js
class Comp extends React.PureComponent<ApiProps, State> {
...
render () {
const { intentions, proposals } = this.state;
return (
<table>
<thead>
<tr>
<th />
<th>Address</th>
<th>Balance</th>
<th>Proposals</th>
</tr>
</thead>
<tbody>
{intentions.map((address) => (
this.renderAccount(address, proposals[address])
))}
</tbody>
</table>
);
}
renderAccount = (address: string, proposals: number[] = []) => {
return (
<tr key={address}>
<td><IdentityIcon size={24} value={address} /></td>
<td>{address}</td>
<td><Balance params={address} /></td>
<td>{proposals.length}</td>
</tr>
);
}
}
```
# 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.

View File

@ -2,7 +2,7 @@ const config = require('@polkadot/dev-react/config/jest');
module.exports = Object.assign({}, config, {
moduleNameMapper: {
'@polkadot/app-(accounts|addresses|explorer|extrinsics|rpc|storage|toolbox|vanitygen)(.*)$': '<rootDir>/packages/ui-$1/src/$2',
'@polkadot/app-(accounts|addresses|example|explorer|extrinsics|rpc|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-example 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,3 @@
# @polkadot/app-example
A set of examples, created [via tutorial](../../examples/index.md).

View File

@ -0,0 +1,19 @@
{
"name": "@polkadot/app-example",
"version": "0.18.9",
"description": "A basic example 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.26.9",
"@polkadot/ui-react-rx": "^0.18.9",
"@polkadot/util-keyring": "^0.26.9"
}
}

View File

@ -0,0 +1,23 @@
// 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 storage from '@polkadot/storage';
import withStorageDiv from '@polkadot/ui-react-rx/with/storageDiv';
import encodeAddress from '@polkadot/util-keyring/address/encode';
const method = storage.staking.public.intentions;
const Comp: React.ComponentType<any> = withStorageDiv(method)(
(value: Uint8Array[]): string => {
if (!value || !value.length) {
return 'No intentions found';
}
return value.map(encodeAddress).join(', ');
}
);
export default Comp;

View File

@ -0,0 +1,71 @@
// 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';
const method = storage.democracy.public.proposals;
type State = {
proposals: {
[index: string]: number[]
}
};
class Comp extends React.PureComponent<ApiProps, State> {
constructor (props: ApiProps) {
super(props);
this.state = {
proposals: {}
};
}
componentDidMount () {
this.subscribeProposals();
}
// TODO We should unsubscribe from subscriptions
subscribeProposals () {
const { api } = this.props;
const key = createStorageKey(method)();
const transform = storageTransform(method);
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;
}, {})
});
});
}
render () {
const { proposals } = this.state;
return (
<div>Found {Object.keys(proposals).length} accounts making proposals.</div>
);
}
}
export default withApi(Comp);

View File

@ -0,0 +1,100 @@
// 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';
const intentionsMethod = storage.staking.public.intentions;
const proposalMethod = storage.democracy.public.proposals;
type State = {
intentions: string[],
proposals: {
[index: string]: number[]
},
subscriptions: any[]
};
class Comp extends React.PureComponent<ApiProps, State> {
constructor (props: ApiProps) {
super(props);
this.state = {
intentions: [],
proposals: {},
subscriptions: []
};
}
componentDidMount () {
this.setState({
subscriptions: [
this.subscribeIntentions(),
this.subscribeProposals()
]
});
}
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;
}, {})
});
});
}
componentWillUnmount () {
const { subscriptions } = this.state;
subscriptions.forEach((sub) => sub.unsubscribe());
}
render () {
const { intentions, proposals } = this.state;
return (
<div>Found {Object.keys(proposals).length} accounts making proposals and {intentions.length} accounts intending to stake.</div>
);
}
}
export default withApi(Comp);

View File

@ -0,0 +1,127 @@
// 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;
type State = {
intentions: string[],
proposals: {
[index: string]: number[]
},
subscriptions: any[]
};
class Comp extends React.PureComponent<ApiProps, State> {
constructor (props: ApiProps) {
super(props);
this.state = {
intentions: [],
proposals: {},
subscriptions: []
};
}
componentDidMount () {
this.setState({
subscriptions: [
this.subscribeIntentions(),
this.subscribeProposals()
]
});
}
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;
}, {})
});
});
}
componentWillUnmount () {
const { subscriptions } = this.state;
subscriptions.forEach((sub) => sub.unsubscribe());
}
render () {
const { intentions, proposals } = this.state;
return (
<table>
<thead>
<tr>
<th />
<th>Address</th>
<th>Balance</th>
<th>Proposals</th>
</tr>
</thead>
<tbody>
{intentions.map((address) => (
this.renderAccount(address, proposals[address])
))}
</tbody>
</table>
);
}
renderAccount = (address: string, proposals: number[] = []) => {
return (
<tr 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);

View File

@ -0,0 +1,13 @@
// 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';
export default class App extends React.PureComponent<any> {
render () {
return (
<div className='example--App'>hello world</div>
);
}
}

View 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-002';
export default class App extends React.PureComponent<any> {
render () {
return (
<div className='example--App'>
<Comp />
</div>
);
}
}

View 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-003';
export default class App extends React.PureComponent<any> {
render () {
return (
<div className='example--App'>
<Comp />
</div>
);
}
}

View 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-004';
export default class App extends React.PureComponent<any> {
render () {
return (
<div className='example--App'>
<Comp />
</div>
);
}
}

View 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-005';
export default class App extends React.PureComponent<any> {
render () {
return (
<div className='example--App'>
<Comp />
</div>
);
}
}

View File

@ -0,0 +1,20 @@
/* 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. */
.example--App {
padding: 1em 2em;
}
.example--App .navigation {
margin-bottom: 1em;
}
.example--App a {
cursor: pointer;
margin-right: 0.5em;
}
.example--App td {
padding: 0.125em 0.5em;
}

View File

@ -0,0 +1,51 @@
// 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 './index.css';
import Tut002 from './comp-002';
import Tut003 from './comp-003';
import Tut004 from './comp-004';
import Tut005 from './comp-005';
const Components: { [index: string]: React.ComponentType<any> } = {
Tut002, Tut003, Tut004, Tut005
};
type State = {
Component: React.ComponentType<any>
};
export default class App extends React.PureComponent<any, State> {
constructor (props: any) {
super(props);
this.state = {
Component: Tut002
};
}
render () {
const { Component } = this.state;
return (
<div className='example--App'>
<div className='navigation'>{
Object.keys(Components).map((name) => (
<a key={name} onClick={() => this.onSelect(name)}>{name}</a>
))
}</div>
<div><Component /></div>
</div>
);
}
onSelect (name: string) {
this.setState({
Component: Components[name]
});
}
}

View File

@ -20,6 +20,7 @@
"@polkadot/app-accounts": "^0.18.9",
"@polkadot/app-addresses": "^0.18.9",
"@polkadot/app-explorer": "^0.18.9",
"@polkadot/app-example": "^0.18.9",
"@polkadot/app-extrinsics": "^0.18.9",
"@polkadot/app-rpc": "^0.18.9",
"@polkadot/app-storage": "^0.18.9",

View File

@ -31,22 +31,26 @@ function SideBar ({ children, className, style, t }: Props) {
vertical
>
{
routing.routes.map((route, index) => (
route
? (
<Item
key={route.name}
t={t}
route={route}
/>
)
: (
<Menu.Divider
hidden
key={index}
/>
)
))
routing.routes
.filter((route) =>
!route || !route.isHidden
)
.map((route, index) => (
route
? (
<Item
key={route.name}
t={t}
route={route}
/>
)
: (
<Menu.Divider
hidden
key={index}
/>
)
))
}
<Menu.Divider hidden />
<Menu.Item className='apps--SideBar-Item'>

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 Example from '@polkadot/app-example/index';
export default ([
{
Component: Example,
i18n: {
defaultValue: 'Example'
},
icon: 'hand spock',
isExact: false,
isHidden: true,
name: 'example'
}
] as Routes);

View File

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

View File

@ -10,7 +10,7 @@ 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-explorer', 'app-extrinsics', 'app-rpc', '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-example', 'app-explorer', 'app-extrinsics', 'app-rpc', '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

@ -28,6 +28,7 @@ function arrayToText (type: Param$Type$Array, value: Array<any>, withBound: bool
}
if (type.length === 1) {
console.error('value.length', value);
return value.map((value) =>
valueToText(type[0], value, false)
).join(', ');

View File

@ -10,8 +10,8 @@ import assert from '@polkadot/util/assert';
import { ApiConsumer } from '../Api/Context';
export default function withApi <T, P extends object> (Inner: React.ComponentType<P & ApiProps>, defaultProps: DefaultProps<T> = {}): React.ComponentType<P> {
return class WithApi extends React.PureComponent<P> {
export default function withApi <T, P extends ApiProps> (Inner: React.ComponentType<P>, defaultProps: DefaultProps<T> = {}): React.ComponentType<any> {
return class WithApi extends React.PureComponent<any> {
constructor (props: any) {
super(props);

View File

@ -11,8 +11,8 @@ import decodeParams from '@polkadot/params/decode';
export default function storageTransform ({ type }: SectionItem<Storages>): StorageTransform {
return (input: Uint8Array = new Uint8Array(0), index: number): Param$Values | null => {
try {
// FIXME We don't do any conversion checks for the type, currently not an issue, but _could_ turn out to be problematic
return decodeParams(type, input, 'latest').value;
// FIXME We don't do any conversion checks for the type, currently not an issue, but _could_ turn out to be problematic (since this is storage, apply no transforms)
return decodeParams(type, input, 'poc-1').value;
} catch (error) {
console.error('Decoding', type, '::', error);
return null;

View File

@ -247,7 +247,7 @@ class Signer extends React.PureComponent<Props, State> {
}
}
const Component: React.ComponentType<BaseProps> = translate(
const Component: React.ComponentType<any> = translate(
withApi(Signer)
);

View File

@ -6,6 +6,7 @@
"@polkadot/app-accounts/*": [ "packages/app-accounts/src/*" ],
"@polkadot/app-addresses/*": [ "packages/app-addresses/src/*" ],
"@polkadot/app-explorer/*": [ "packages/app-explorer/src/*" ],
"@polkadot/app-example/*": [ "packages/app-example/src/*" ],
"@polkadot/app-extrinsics/*": [ "packages/app-extrinsics/src/*" ],
"@polkadot/app-rpc/*": [ "packages/app-rpc/src/*" ],
"@polkadot/app-storage/*": [ "packages/app-storage/src/*" ],