diff --git a/README.md b/README.md index e542afe596..e0a8f8eb0f 100644 --- a/README.md +++ b/README.md @@ -60,3 +60,7 @@ Demos for the different libraries can be viewed with `yarn run demo:` 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. diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000000..cdcf78485c --- /dev/null +++ b/examples/README.md @@ -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) diff --git a/examples/final.png b/examples/final.png new file mode 100644 index 0000000000..6c9f606483 Binary files /dev/null and b/examples/final.png differ diff --git a/examples/tut-001.md b/examples/tut-001.md new file mode 100644 index 0000000000..d4e9ae61dd --- /dev/null +++ b/examples/tut-001.md @@ -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 { + render () { + return ( +
hello world
+ ); + } +} +``` + +(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) diff --git a/examples/tut-002.md b/examples/tut-002.md new file mode 100644 index 0000000000..2f5730b6f9 --- /dev/null +++ b/examples/tut-002.md @@ -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 = 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 { + render () { + return ( + + ); + } +} +``` + +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.
.public.`. 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 = 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. diff --git a/examples/tut-003.md b/examples/tut-003.md new file mode 100644 index 0000000000..e449cf8486 --- /dev/null +++ b/examples/tut-003.md @@ -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 { + ... + 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()()` 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). diff --git a/examples/tut-004.md b/examples/tut-004.md new file mode 100644 index 0000000000..fe022c6170 --- /dev/null +++ b/examples/tut-004.md @@ -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 { + ... + 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. diff --git a/examples/tut-005.md b/examples/tut-005.md new file mode 100644 index 0000000000..28c984e30d --- /dev/null +++ b/examples/tut-005.md @@ -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 { + ... + render () { + const { intentions, proposals } = this.state; + + return ( + + + + + + + + + + {intentions.map((address) => ( + this.renderAccount(address, proposals[address]) + ))} + +
+ AddressBalanceProposals
+ ); + } + + renderAccount = (address: string, proposals: number[] = []) => { + return ( + + + {address} + + {proposals.length} + + ); + } +} +``` + +# 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. diff --git a/jest.config.js b/jest.config.js index 263482d4d2..5fc8ee3eea 100644 --- a/jest.config.js +++ b/jest.config.js @@ -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)(.*)$': '/packages/ui-$1/src/$2', + '@polkadot/app-(accounts|addresses|example|explorer|extrinsics|rpc|storage|toolbox|vanitygen)(.*)$': '/packages/ui-$1/src/$2', '@polkadot/ui-(app|identicon|keyring|react-rx|react|signer)(.*)$': '/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' diff --git a/packages/app-example/LICENSE b/packages/app-example/LICENSE new file mode 100644 index 0000000000..a5074080f6 --- /dev/null +++ b/packages/app-example/LICENSE @@ -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. diff --git a/packages/app-example/README.md b/packages/app-example/README.md new file mode 100644 index 0000000000..df63f0054e --- /dev/null +++ b/packages/app-example/README.md @@ -0,0 +1,3 @@ +# @polkadot/app-example + +A set of examples, created [via tutorial](../../examples/index.md). diff --git a/packages/app-example/package.json b/packages/app-example/package.json new file mode 100644 index 0000000000..f4e4bd228d --- /dev/null +++ b/packages/app-example/package.json @@ -0,0 +1,19 @@ +{ + "name": "@polkadot/app-example", + "version": "0.18.9", + "description": "A basic example app", + "main": "index.js", + "scripts": { + }, + "author": "Jaco Greeff ", + "maintainers": [ + "Jaco Greeff " + ], + "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" + } +} diff --git a/packages/app-example/src/comp-002.tsx b/packages/app-example/src/comp-002.tsx new file mode 100644 index 0000000000..e2aa3807ff --- /dev/null +++ b/packages/app-example/src/comp-002.tsx @@ -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 = withStorageDiv(method)( + (value: Uint8Array[]): string => { + if (!value || !value.length) { + return 'No intentions found'; + } + + return value.map(encodeAddress).join(', '); + } +); + +export default Comp; diff --git a/packages/app-example/src/comp-003.tsx b/packages/app-example/src/comp-003.tsx new file mode 100644 index 0000000000..ea1017b777 --- /dev/null +++ b/packages/app-example/src/comp-003.tsx @@ -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 { + 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 ( +
Found {Object.keys(proposals).length} accounts making proposals.
+ ); + } +} + +export default withApi(Comp); diff --git a/packages/app-example/src/comp-004.tsx b/packages/app-example/src/comp-004.tsx new file mode 100644 index 0000000000..13bf41ec56 --- /dev/null +++ b/packages/app-example/src/comp-004.tsx @@ -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 { + 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 ( +
Found {Object.keys(proposals).length} accounts making proposals and {intentions.length} accounts intending to stake.
+ ); + } +} + +export default withApi(Comp); diff --git a/packages/app-example/src/comp-005.tsx b/packages/app-example/src/comp-005.tsx new file mode 100644 index 0000000000..a33179d5f5 --- /dev/null +++ b/packages/app-example/src/comp-005.tsx @@ -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 { + 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 ( + + + + + + + + + + {intentions.map((address) => ( + this.renderAccount(address, proposals[address]) + ))} + +
+ AddressBalanceProposals
+ ); + } + + renderAccount = (address: string, proposals: number[] = []) => { + return ( + + + {address} + + {proposals.length} + + ); + } +} + +export default withApi(Comp); diff --git a/packages/app-example/src/index-001.tsx b/packages/app-example/src/index-001.tsx new file mode 100644 index 0000000000..604a942eda --- /dev/null +++ b/packages/app-example/src/index-001.tsx @@ -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 { + render () { + return ( +
hello world
+ ); + } +} diff --git a/packages/app-example/src/index-002.tsx b/packages/app-example/src/index-002.tsx new file mode 100644 index 0000000000..b56a0934ef --- /dev/null +++ b/packages/app-example/src/index-002.tsx @@ -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 { + render () { + return ( +
+ +
+ ); + } +} diff --git a/packages/app-example/src/index-003.tsx b/packages/app-example/src/index-003.tsx new file mode 100644 index 0000000000..48e03ee2e0 --- /dev/null +++ b/packages/app-example/src/index-003.tsx @@ -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 { + render () { + return ( +
+ +
+ ); + } +} diff --git a/packages/app-example/src/index-004.tsx b/packages/app-example/src/index-004.tsx new file mode 100644 index 0000000000..760bf781d5 --- /dev/null +++ b/packages/app-example/src/index-004.tsx @@ -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 { + render () { + return ( +
+ +
+ ); + } +} diff --git a/packages/app-example/src/index-005.tsx b/packages/app-example/src/index-005.tsx new file mode 100644 index 0000000000..52967bd643 --- /dev/null +++ b/packages/app-example/src/index-005.tsx @@ -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 { + render () { + return ( +
+ +
+ ); + } +} diff --git a/packages/app-example/src/index.css b/packages/app-example/src/index.css new file mode 100644 index 0000000000..3c538f3ce8 --- /dev/null +++ b/packages/app-example/src/index.css @@ -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; +} diff --git a/packages/app-example/src/index.tsx b/packages/app-example/src/index.tsx new file mode 100644 index 0000000000..8a1577a17c --- /dev/null +++ b/packages/app-example/src/index.tsx @@ -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 } = { + Tut002, Tut003, Tut004, Tut005 +}; + +type State = { + Component: React.ComponentType +}; + +export default class App extends React.PureComponent { + constructor (props: any) { + super(props); + + this.state = { + Component: Tut002 + }; + } + + render () { + const { Component } = this.state; + + return ( +
+
{ + Object.keys(Components).map((name) => ( + this.onSelect(name)}>{name} + )) + }
+
+
+ ); + } + + onSelect (name: string) { + this.setState({ + Component: Components[name] + }); + } +} diff --git a/packages/apps/package.json b/packages/apps/package.json index d24a6be061..b72ed09766 100644 --- a/packages/apps/package.json +++ b/packages/apps/package.json @@ -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", diff --git a/packages/apps/src/SideBar/index.tsx b/packages/apps/src/SideBar/index.tsx index 3b9c11b0ce..bc21d961b4 100644 --- a/packages/apps/src/SideBar/index.tsx +++ b/packages/apps/src/SideBar/index.tsx @@ -31,22 +31,26 @@ function SideBar ({ children, className, style, t }: Props) { vertical > { - routing.routes.map((route, index) => ( - route - ? ( - - ) - : ( -