Basic account management app (#29)

* Basic account management functions

* Allow dividers

* Use dividers to split menu

* Bump

* Basic cleanups
This commit is contained in:
Jaco Greeff 2018-05-08 17:47:34 +02:00 committed by GitHub
parent 36c8850f3a
commit dfaddef7eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 909 additions and 199 deletions

View File

@ -12,6 +12,6 @@ all=warn
[options]
include_warnings=true
module.name_mapper='^@polkadot/app-\(accounts\|explorer\|extrinsics\|home\|rpc\|settings\|storage\)\(.*\)$' -> '<PROJECT_ROOT>/packages/app-\1/src\2'
module.name_mapper='^@polkadot/app-\(accounts\|addresses\|explorer\|extrinsics\|home\|rpc\|settings\|storage\|vanitygen\)\(.*\)$' -> '<PROJECT_ROOT>/packages/app-\1/src\2'
module.name_mapper='^@polkadot/apps\(.*\)$' -> '<PROJECT_ROOT>/packages/apps/src\1'
module.name_mapper='^@polkadot/ui-react-app\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-react-app/src\1'

View File

@ -7,6 +7,13 @@ declare module 'semantic-ui-react/dist/es/collections/Menu' {
}
declare module 'semantic-ui-react/dist/es/elements/Button' {
declare module.exports: React$StatelessFunctionalComponent<*> & {
Group: React$StatelessFunctionalComponent<*>,
Or: React$StatelessFunctionalComponent<*>
};
}
declare module 'semantic-ui-react/dist/es/elements/Divider' {
declare module.exports: React$StatelessFunctionalComponent<*>;
}

9
flow-typed/store.js vendored Normal file
View File

@ -0,0 +1,9 @@
// @flow
declare module 'store' {
declare module.exports: {
each: (fn: (value: any, key: string) => void) => void,
get: (key: string) => any,
set: (key: string, value: any) => void,
}
}

View File

@ -10,5 +10,5 @@
"packages": [
"packages/*"
],
"version": "0.1.12"
"version": "0.2.0"
}

View File

@ -0,0 +1,15 @@
ISC License (ISC)
Copyright 2017-2018 Jaco Greeff <jacogr@gmail.com>
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-accounts

View File

@ -0,0 +1,16 @@
{
"name": "@polkadot/app-accounts",
"version": "0.2.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "ISC",
"scripts": {
"build": "polkadot-dev-build-babel",
"check": "stylelint 'src/**/*.css' && eslint src && flow check",
"test": "jest --coverage"
},
"devDependencies": {
"@polkadot/ui-react-app": "^0.2.0"
}
}

View File

@ -0,0 +1,41 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { BareProps } from '@polkadot/ui-react-app/types';
import React from 'react';
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
type Props = BareProps & {
address: string | null;
}
export default function Address ({ address, className, style }: Props): React$Node {
return (
<div
className={['accounts--Address', 'ui disabled dropdown selection', className].join(' ')}
style={style}
>
{
// flowlint-next-line sketchy-null-string:off
address
? (
<IdentityIcon
className='accounts--Address-icon'
size={32}
value={address}
/>
)
: null
}
<div className='accounts--Address-name'>
{
// flowlint-next-line sketchy-null-string:off
address || 'unknown'
}
</div>
</div>
);
}

View File

@ -0,0 +1,225 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps, KeyringInstance } from '@polkadot/ui-react-app/types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import Input from 'semantic-ui-react/dist/es/elements/Input';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import isHex from '@polkadot/util/is/hex';
import hexToU8a from '@polkadot/util/hex/toU8a';
import u8aFromString from '@polkadot/util/u8a/fromString';
import u8aToHex from '@polkadot/util/u8a/toHex';
import keypairFromSeed from '@polkadot/util-crypto/nacl/keypair/fromSeed';
import randomBytes from '@polkadot/util-crypto/random/asU8a';
import addressEncode from '@polkadot/util-keyring/address/encode';
import Address from './Address';
import translate from './translate';
type Props = I18nProps & {
keyring: KeyringInstance,
onBack: () => void
};
type State = {
address: string | null,
isNameValid: boolean,
isSeedValid: boolean,
isPassValid: boolean,
isPassVisible: boolean,
isValid: boolean,
name: string,
password: string,
seed: string
}
function formatSeed (seed: string): Uint8Array {
return isHex(seed)
? hexToU8a(seed)
: u8aFromString(seed.padEnd(32, ' '));
}
function addressFromSeed (seed: string): string {
const { publicKey } = keypairFromSeed(formatSeed(seed));
return addressEncode(publicKey);
}
class Creator extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props);
this.state = this.emptyState();
}
render (): React$Node {
const { className, style, t } = this.props;
const { address, isNameValid, isPassValid, isPassVisible, isSeedValid, isValid, name, password, seed } = this.state;
return (
<div
className={['accounts--Creator', className].join(' ')}
style={style}
>
<div className='ui--form'>
<div className='medium'>
<Label>{t('creator.seed', {
defaultValue: 'create from the following seed (hex or string)'
})}</Label>
<Input
error={!isSeedValid}
onChange={this.onChangeSeed}
value={seed}
/>
</div>
<div className='medium'>
<Label>{t('creator.address', {
defaultValue: 'evaluating to address'
})}</Label>
<Address address={address} />
</div>
</div>
<div className='ui--form'>
<div className='medium'>
<Label>{t('creator.name', {
defaultValue: 'name the account'
})}</Label>
<Input
error={!isNameValid}
onChange={this.onChangeName}
value={name}
/>
</div>
</div>
<div className='ui--form'>
<div className='medium'>
<Label>{t('creator.pass1', {
defaultValue: 'encrypt it using the password'
})}</Label>
<Input
action
error={!isPassValid}
onChange={this.onChangePass}
type={isPassVisible ? 'text' : 'password'}
value={password}
>
<input />
<Button
icon='eye'
primary
onClick={this.togglePassword}
/>
</Input>
</div>
</div>
<div className='ui--form-buttons'>
<Button
negative
onClick={this.onDiscard}
primary
>
{t('creator.discard', {
defaultValue: 'Reset'
})}
</Button>
<Button
disabled={!isValid}
onClick={this.onCommit}
primary
>
{t('creator.save', {
defaultValue: 'Save'
})}
</Button>
</div>
</div>
);
}
emptyState (): State {
const seed = u8aToHex(randomBytes());
const address = addressFromSeed(seed);
return {
address,
isNameValid: true,
isPassValid: false,
isPassVisible: false,
isSeedValid: true,
isValid: false,
name: 'new keypair',
password: '',
seed
};
}
nextState (newState: $Shape<State>): void {
this.setState(
(prevState: State, props: Props): $Shape<State> => {
const { name = prevState.name, password = prevState.password, seed = prevState.seed } = newState;
let address = prevState.address;
const isNameValid = !!name;
const isSeedValid = isHex(seed)
? seed.length === 66
: seed.length <= 32;
const isPassValid = password.length > 0 && password.length <= 32;
if (isSeedValid && seed !== prevState.seed) {
address = addressFromSeed(seed);
}
return {
address,
isNameValid,
isPassValid,
isSeedValid,
isValid: isNameValid && isPassValid && isSeedValid,
name,
password,
seed
};
}
);
}
// eslint-disable-next-line no-unused-vars
onChangeSeed = (event: SyntheticEvent<*>, { value }): void => {
this.nextState({ seed: value });
}
// eslint-disable-next-line no-unused-vars
onChangeName = (event: SyntheticEvent<*>, { value }): void => {
this.nextState({ name: value });
}
onChangePass = (event: SyntheticEvent<*>, { value }): void => {
this.nextState({ password: value });
}
onCommit = (): void => {
const { keyring, onBack } = this.props;
const { name, password, seed } = this.state;
const pair = keyring.addFromSeed(
formatSeed(seed),
{ name }
);
keyring.saveJson(pair, password);
onBack();
}
onDiscard = (): void => {
this.setState(this.emptyState());
}
togglePassword = (): void => {
this.setState({ isPassVisible: !this.state.isPassVisible });
}
}
export default translate(Creator);

View File

@ -0,0 +1,161 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { KeyringPair } from '@polkadot/util-keyring/types';
import type { KeyringOptions } from '@polkadot/ui-react-app/InputAddress/types';
import type { I18nProps, KeyringInstance } from '@polkadot/ui-react-app/types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import Input from 'semantic-ui-react/dist/es/elements/Input';
import Label from 'semantic-ui-react/dist/es/elements/Label';
import InputAddress from '@polkadot/ui-react-app/src/InputAddress';
import createOptions from '@polkadot/ui-react-app/src/InputAddress/options';
import translate from './translate';
type Props = I18nProps & {
keyring: KeyringInstance,
onBack: () => void
};
type State = {
currentPair: KeyringPair,
defaultPublicKey: Uint8Array,
editedName: string,
isEdited: boolean,
options: KeyringOptions
}
class Editor extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props);
const pairs = props.keyring.getPairs();
const currentPair = pairs[pairs.length - 1];
this.state = this.createState(currentPair);
this.state.defaultPublicKey = currentPair.publicKey();
this.state.options = this.createOptions();
}
render (): React$Node {
const { className, style, t } = this.props;
const { defaultPublicKey, editedName, isEdited, options } = this.state;
return (
<div
className={['accounts--Editor', className].join(' ')}
style={style}
>
<div className='ui--form'>
<InputAddress
defaultValue={defaultPublicKey}
options={options}
label={t('editor.select', {
defaultValue: 'using my account'
})}
onChange={this.onChangeAccount}
/>
</div>
<div className='ui--form'>
<div className='medium'>
<Label>{t('editor.name', {
defaultValue: 'identified by the name'
})}</Label>
<Input
onChange={this.onChangeName}
value={editedName}
/>
</div>
</div>
<div className='ui--form-buttons'>
<Button
disabled={!isEdited}
negative
onClick={this.onDiscard}
primary
>
{t('editor.reset', {
defaultValue: 'Reset'
})}
</Button>
<Button
disabled={!isEdited}
onClick={this.onCommit}
primary
>
{t('editor.save', {
defaultValue: 'Save'
})}
</Button>
</div>
</div>
);
}
createOptions (): KeyringOptions {
return createOptions(this.props.keyring);
}
createState (currentPair: KeyringPair): $Shape<State> {
const { name = '' } = currentPair.getMeta();
return {
currentPair,
editedName: name,
isEdited: false
};
}
nextState (newState?: $Shape<State> = {}): void {
this.setState(
(prevState: State): $Shape<State> => {
let { currentPair = prevState.currentPair, editedName = prevState.editedName, options = prevState.options } = newState;
if (currentPair.address() !== prevState.currentPair.address()) {
editedName = currentPair.getMeta().name || '';
}
const isEdited = editedName !== currentPair.getMeta().name;
return {
currentPair,
editedName,
isEdited,
options
};
}
);
}
onChangeAccount = (publicKey: Uint8Array): void => {
const { keyring } = this.props;
const currentPair = keyring.getPair(publicKey);
this.nextState({ currentPair });
}
// eslint-disable-next-line no-unused-vars
onChangeName = (event: SyntheticEvent<*>, { value }): void => {
this.nextState({ editedName: value });
}
onCommit = (): void => {
const { currentPair, editedName } = this.state;
currentPair.setMeta({ name: editedName });
this.nextState({ options: this.createOptions() });
}
onDiscard = (): void => {
const { currentPair } = this.state;
this.nextState({ editedName: currentPair.getMeta().name });
}
}
export default translate(Editor);

View File

@ -0,0 +1,35 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.accounts--App {
padding: 1em 2em;
}
.accounts--App-navigation {
margin-bottom: 2em;
text-align: center;
}
.accounts--Icon {
position: fixed;
top: 0.75em;
right: 0.75em;
}
.accounts--Address {
opacity: 1 !important;
position: relative;
}
.accounts--Address-icon {
position: absolute;
top: 2px;
left: 1rem;
}
.accounts--Address-name {
font-family: monospace;
padding-left: 3rem;
opacity: 0.45;
}

View File

@ -0,0 +1,99 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { I18nProps } from '@polkadot/ui-react-app/types';
import './index.css';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import Creator from './Creator';
import Editor from './Editor';
import keyring from '@polkadot/ui-react-app/src/keyring';
import translate from './translate';
type Props = I18nProps & {};
type Actions = 'create' | 'edit';
type State = {
currentAction: Actions,
currentAddress: string | null
}
// FIXME React-router would probably be the best route, not home-grown
const Components: { [Actions]: React$ComponentType<*> } = {
'create': Creator,
'edit': Editor
};
class App extends React.PureComponent<Props, State> {
constructor (props: Props) {
super(props);
this.state = {
currentAction: 'edit',
currentAddress: null
};
}
render (): React$Node {
const { className, style, t } = this.props;
const { currentAction } = this.state;
const Component = Components[currentAction];
return (
<div
className={['accounts--App', className].join(' ')}
style={style}
>
<div className='accounts--App-navigation'>
<Button.Group>
<Button
onClick={this.selectEdit}
primary={currentAction === 'edit'}
>
{t('app.edit', {
defaultValue: 'Edit account'
})}
</Button>
<Button.Or
text={t('app.or', {
defaultValue: 'or'
})}
/>
<Button
onClick={this.selectCreate}
primary={currentAction === 'create'}
>
{t('app.create', {
defaultValue: 'Create account'
})}
</Button>
</Button.Group>
</div>
<Component
keyring={keyring}
onBack={this.selectEdit}
/>
</div>
);
}
setCurrentAction (currentAction: Actions): void {
this.setState({ currentAction });
}
selectCreate = (): void => {
this.setCurrentAction('create');
}
selectEdit = (): void => {
this.setCurrentAction('edit');
}
}
export default translate(App);

View File

@ -0,0 +1,8 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import { translate } from 'react-i18next';
export default translate(['accounts', 'ui']);

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-explorer",
"version": "0.1.12",
"version": "0.2.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -11,10 +11,10 @@
"test": "jest --coverage"
},
"devDependencies": {
"@polkadot/primitives": "^0.12.8",
"@polkadot/primitives-codec": "^0.12.8",
"@polkadot/primitives-json": "^0.12.8",
"@polkadot/ui-react-app": "^0.1.12",
"@polkadot/util-crypto": "^0.19.5"
"@polkadot/primitives": "^0.12.9",
"@polkadot/primitives-codec": "^0.12.9",
"@polkadot/primitives-json": "^0.12.9",
"@polkadot/ui-react-app": "^0.2.0",
"@polkadot/util-crypto": "^0.19.6"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-extrinsics",
"version": "0.1.12",
"version": "0.2.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -11,9 +11,9 @@
"test": "jest --coverage"
},
"devDependencies": {
"@polkadot/extrinsics": "^0.12.8",
"@polkadot/extrinsics-codec": "^0.12.8",
"@polkadot/ui-react-app": "^0.1.12",
"@polkadot/extrinsics": "^0.12.9",
"@polkadot/extrinsics-codec": "^0.12.9",
"@polkadot/ui-react-app": "^0.2.0",
"react-dropzone": "^4.2.9"
}
}

View File

@ -5,18 +5,16 @@
import type BN from 'bn.js';
import type { I18nProps } from '@polkadot/ui-react-app/types';
import type { EncodedMessage, QueueTx } from '../types';
import './Selection.css';
import type { EncodedMessage, QueueTx } from './types';
import React from 'react';
import Button from 'semantic-ui-react/dist/es/elements/Button';
import extrinsics from '@polkadot/extrinsics-substrate';
import Account from '../Account';
import Extrinsic from '../Extrinsic';
import translate from '../translate';
import Account from './Account';
import Extrinsic from './Extrinsic';
import Nonce from './Nonce';
import translate from './translate';
type Props = I18nProps & {
onQueue: (value: QueueTx) => void
@ -73,7 +71,7 @@ class Selection extends React.PureComponent<Props, State> {
onChange={this.onChangeNonce}
value={publicKey}
/>
<div className='extrinsics--Selection-ButtonRow'>
<div className='ui--form-buttons'>
<Button
disabled={!isValid}
onClick={this.onQueue}

View File

@ -1,15 +0,0 @@
/* Copyright 2017-2018 Jaco Greeff
/* This software may be modified and distributed under the terms
/* of the ISC license. See the LICENSE file for details. */
.extrinsics--Selection {}
.extrinsics--Selection-ButtonRow {
margin: 1em 0;
text-align: right;
}
.extrinsics--Selection-ButtonRow .button {
margin: 0 0 0 0.5rem;
text-align: left;
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/app-storage",
"version": "0.1.12",
"version": "0.2.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -11,6 +11,6 @@
"test": "jest --coverage"
},
"devDependencies": {
"@polkadot/ui-react-app": "^0.1.12"
"@polkadot/ui-react-app": "^0.2.0"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/apps",
"version": "0.1.12",
"version": "0.2.0",
"description": "An Apps portal into the Polkadot network",
"main": "index.js",
"homepage": ".",
@ -14,10 +14,11 @@
"test": "react-scripts test --env=jsdom --coverage"
},
"devDependencies": {
"@polkadot/app-explorer": "^0.1.12",
"@polkadot/app-extrinsics": "^0.1.12",
"@polkadot/app-storage": "^0.1.12",
"@polkadot/ui-react-app": "^0.1.12",
"@polkadot/app-accounts": "^0.2.0",
"@polkadot/app-explorer": "^0.2.0",
"@polkadot/app-extrinsics": "^0.2.0",
"@polkadot/app-storage": "^0.2.0",
"@polkadot/ui-react-app": "^0.2.0",
"react-remarkable": "^1.1.3"
}
}

View File

@ -15,13 +15,9 @@ import Menu from 'semantic-ui-react/dist/es/collections/Menu';
type Props = I18nProps & Route & {};
export default function Item ({ className, i18n, icon, isExact, name, path = '', style, t }: Props): React$Node {
export default function Item ({ i18n, icon, isExact, name, path = '', t }: Props): React$Node {
return (
<Menu.Item
className={['apps--SideBar-Item', className].join(' ')}
name={name}
style={style}
>
<Menu.Item className='apps--SideBar-Item'>
<NavLink
activeClassName='apps--SideBar-Item-NavLink-active'
className='apps--SideBar-Item-NavLink'

View File

@ -10,6 +10,7 @@ import './SideBar.css';
import React from 'react';
import Icon from 'semantic-ui-react/dist/es/elements/Icon';
import Menu from 'semantic-ui-react/dist/es/collections/Menu';
import Divider from 'semantic-ui-react/dist/es/elements/Divider';
import routing from '../routing';
import translate from '../translate';
@ -29,16 +30,28 @@ function SideBar ({ className, style, t }: Props): React$Node {
>
{
routing.routes.map((route) => (
<Item
key={route.name}
t={t}
// flowlint-next-line inexact-spread:off
{...route}
/>
route
? (
<Item
key={route.name}
t={t}
// flowlint-next-line inexact-spread:off
{...route}
/>
)
: <Divider hidden />
))
}
<Divider hidden />
<Menu.Item className='apps--SideBar-Item'>
<a
className='apps--SideBar-Item-NavLink'
href='https://github.com/polkadot-js/apps'>
<Icon name='github' /> GitHub
</a>
</Menu.Item>
</Menu>
<a className='apps--SideBar-github' href='https://github.com/polkadot-js/apps'><Icon name='github' /> GitHub</a>
</div>
);
}

View File

@ -14,7 +14,9 @@ createApp((() => {
const { app } = urlParams();
if (app) {
const route = routing.routes.find(({ name }) => name === app);
const route = routing.routes.find((route) =>
route && route.name === app
);
return route
? route.component

View File

@ -0,0 +1,21 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { Routes } from '../types';
import Accounts from '@polkadot/app-accounts/src';
export default ([
{
component: Accounts,
i18n: {
defaultValue: 'Accounts'
},
icon: 'users',
isExact: false,
isHidden: false,
name: 'accounts'
}
]: Routes);

View File

@ -5,16 +5,18 @@
import type { Routing } from '../types';
// import accounts from './accounts';
import accounts from './accounts';
// import addresses from './addresses';
import explorer from './explorer';
import extrinsics from './extrinsics';
// import home from './home';
// import settings from './settings';
import storage from './storage';
// import vanitygen from './vanitygen';
export default ({
default: 'explorer',
routes: [].concat(
explorer, extrinsics, storage
explorer, extrinsics, storage, null, accounts
)
}: Routing);

View File

@ -13,7 +13,7 @@ export type Route = {
path?: string
};
export type Routes = Array<Route>;
export type Routes = Array<Route | null>;
export type Routing = {
default: string,

View File

@ -1,6 +1,6 @@
{
"name": "@polkadot/ui-react-app",
"version": "0.1.12",
"version": "0.2.0",
"main": "index.js",
"repository": "https://github.com/polkadot-js/apps.git",
"author": "Jaco Greeff <jacogr@gmail.com>",
@ -11,12 +11,12 @@
"test": "jest --coverage"
},
"dependencies": {
"@polkadot/extrinsics-substrate": "^0.12.8",
"@polkadot/storage-substrate": "^0.12.8",
"@polkadot/ui-react": "^0.11.2",
"@polkadot/ui-react-rx": "^0.11.2",
"@polkadot/util": "^0.19.5",
"@polkadot/util-keyring": "^0.19.5",
"@polkadot/extrinsics-substrate": "^0.12.9",
"@polkadot/storage-substrate": "^0.12.9",
"@polkadot/ui-react": "^0.11.3",
"@polkadot/ui-react-rx": "^0.11.3",
"@polkadot/util": "^0.19.6",
"@polkadot/util-keyring": "^0.19.6",
"i18next": "^11.1.1",
"i18next-browser-languagedetector": "^2.2.0",
"i18next-xhr-backend": "^1.5.1",
@ -25,7 +25,8 @@
"react-i18next": "^7.5.1",
"react-router-dom": "^4.2.2",
"semantic-ui-css": "^2.3.1",
"semantic-ui-react": "^0.79.1"
"semantic-ui-react": "^0.79.1",
"store": "^2.0.12"
},
"devDepependencies": {
"@polkadot/primitives": "^0.12.6"

View File

@ -3,7 +3,9 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { KeyringInstance } from '@polkadot/util-keyring/types';
import type { BareProps } from '../types';
import type { KeyringOptions } from './types';
import './InputAddress.css';
@ -11,29 +13,23 @@ import React from 'react';
import addressDecode from '@polkadot/util-keyring/address/decode';
import addressEncode from '@polkadot/util-keyring/address/encode';
// TODO: We need to actually pass the keyring in, this is the testing keyring
import keyring from '../keyring';
import RxDropdown from '../RxDropdown';
import PairDisplay from './PairDisplay';
import createOptions from './options';
type Props = BareProps & {
defaultValue?: Uint8Array,
isError?: boolean,
keyring?: KeyringInstance,
label?: string,
onChange: (value: Uint8Array) => void
onChange: (value: Uint8Array) => void,
options?: KeyringOptions
};
const options = keyring.getPairs().map((pair) => ({
text: (
<PairDisplay pair={pair} />
),
value: pair.address()
}));
const transform = (value: string): Uint8Array =>
addressDecode(value);
export default function InputAddress ({ className, defaultValue, isError, label, onChange, style }: Props): React$Node {
export default function InputAddress ({ className, defaultValue, isError, keyring, label, onChange, options, style }: Props): React$Node {
const _options = options || createOptions(keyring);
let _defaultValue;
if (defaultValue) {
@ -47,7 +43,7 @@ export default function InputAddress ({ className, defaultValue, isError, label,
isError={isError}
label={label}
onChange={onChange}
options={options}
options={_options}
transform={transform}
/>
);

View File

@ -0,0 +1,23 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
import type { KeyringInstance } from '@polkadot/util-keyring/types';
import type { KeyringOptions } from './types';
import React from 'react';
import testKeyring from '../keyring';
import PairDisplay from './PairDisplay';
export default function createOptions (keyring?: KeyringInstance = testKeyring): KeyringOptions {
return keyring
.getPairs()
.map((pair) => ({
text: (
<PairDisplay pair={pair} />
),
value: pair.address()
}));
}

View File

@ -0,0 +1,11 @@
// Copyright 2017-2018 Jaco Greeff
// This software may be modified and distributed under the terms
// of the ISC license. See the LICENSE file for details.
// @flow
export type KeyringOption = {
text: React$Node,
value: string
};
export type KeyringOptions = Array<KeyringOption>;

View File

@ -3,8 +3,32 @@
// of the ISC license. See the LICENSE file for details.
// @flow
import type { KeyringPair } from '@polkadot/util-keyring/types';
import type { KeyringInstance } from './types';
import store from 'store';
import testKeyring from '@polkadot/util-keyring/testing';
const keyring = testKeyring();
export default keyring;
const keyringExt: KeyringInstance = {
...keyring,
loadJson: (): void => {
store.each((value, key: string) => {
if (!/account:/.test(key)) {
return;
}
keyring.addFromJson(value);
});
},
saveJson: (pair: KeyringPair, password?: string): void => {
const json = pair.toJson(password);
store.set(`account:${json.address}`, json);
}
};
keyringExt.loadJson();
export default keyringExt;

View File

@ -46,3 +46,13 @@
.ui--form > div.small {
flex: 0 25%;
}
.ui--form-buttons {
margin: 1em 0;
text-align: right;
}
.ui--form-buttons .button {
margin: 0 0 0 0.5rem;
text-align: left;
}

View File

@ -4,6 +4,7 @@
// @flow
import type { RxApiInterface } from '@polkadot/api-rx/types';
import type { KeyringInstance as UKeyringInstance, KeyringPair } from '@polkadot/util-keyring/types';
export type BareProps = {
className?: string,
@ -25,3 +26,8 @@ export type BaseContext = {
}
}
};
export type KeyringInstance = UKeyringInstance & {
loadJson: () => void,
saveJson: (pair: KeyringPair, password?: string) => void
};

242
yarn.lock
View File

@ -622,49 +622,49 @@
call-me-maybe "^1.0.1"
glob-to-regexp "^0.3.0"
"@polkadot/api-format@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@polkadot/api-format/-/api-format-0.10.2.tgz#867816c0fcc00e9e88197b1e0546849649f06be9"
"@polkadot/api-format@^0.10.3":
version "0.10.3"
resolved "https://registry.yarnpkg.com/@polkadot/api-format/-/api-format-0.10.3.tgz#d6d56af776c636e3423837a516ea6754dd41ccac"
dependencies:
"@polkadot/primitives-json" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/primitives-json" "^0.12.9"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/api-jsonrpc@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@polkadot/api-jsonrpc/-/api-jsonrpc-0.10.2.tgz#55836832ee3e48437edfabeca3e8f5fd20367f13"
"@polkadot/api-jsonrpc@^0.10.3":
version "0.10.3"
resolved "https://registry.yarnpkg.com/@polkadot/api-jsonrpc/-/api-jsonrpc-0.10.3.tgz#4abe9e5f59a7bf46830e2f775d70fdb6e54fd99c"
dependencies:
babel-runtime "^6.26.0"
"@polkadot/api-provider@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@polkadot/api-provider/-/api-provider-0.10.2.tgz#4d84bdabed2e548277d3ffe87bbdc0e9f0976592"
"@polkadot/api-provider@^0.10.3":
version "0.10.3"
resolved "https://registry.yarnpkg.com/@polkadot/api-provider/-/api-provider-0.10.3.tgz#bb3201508a42e29b1fb7c96a07620a5302863036"
dependencies:
"@polkadot/storage-substrate" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/util-crypto" "^0.19.5"
"@polkadot/util-keyring" "^0.19.5"
"@polkadot/storage-substrate" "^0.12.9"
"@polkadot/util" "^0.19.6"
"@polkadot/util-crypto" "^0.19.6"
"@polkadot/util-keyring" "^0.19.6"
babel-runtime "^6.26.0"
eventemitter3 "^2.0.3"
isomorphic-fetch "^2.2.1"
websocket "^1.0.25"
"@polkadot/api-rx@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@polkadot/api-rx/-/api-rx-0.10.2.tgz#d38187b0d604e7033bfd92be40db8064ae8dc588"
"@polkadot/api-rx@^0.10.3":
version "0.10.3"
resolved "https://registry.yarnpkg.com/@polkadot/api-rx/-/api-rx-0.10.3.tgz#fd1f152c6a6ac75562e78b30325827e7f01edf5d"
dependencies:
"@polkadot/api" "^0.10.2"
"@polkadot/api-provider" "^0.10.2"
"@polkadot/api" "^0.10.3"
"@polkadot/api-provider" "^0.10.3"
rxjs "^5.5.10"
"@polkadot/api@^0.10.2":
version "0.10.2"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.10.2.tgz#8b8e28b1b73a42daaeccfb486171669520274073"
"@polkadot/api@^0.10.3":
version "0.10.3"
resolved "https://registry.yarnpkg.com/@polkadot/api/-/api-0.10.3.tgz#6941c63ea6bddb2b7bde552e3543ee90c127ef4f"
dependencies:
"@polkadot/api-format" "^0.10.2"
"@polkadot/api-jsonrpc" "^0.10.2"
"@polkadot/api-provider" "^0.10.2"
"@polkadot/util" "^0.19.5"
"@polkadot/api-format" "^0.10.3"
"@polkadot/api-jsonrpc" "^0.10.3"
"@polkadot/api-provider" "^0.10.3"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/dev@^0.17.13":
@ -705,120 +705,120 @@
stylelint "^9.1.1"
stylelint-config-standard "^18.2.0"
"@polkadot/extrinsics-codec@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics-codec/-/extrinsics-codec-0.12.8.tgz#ea42e663ce337969b977efad8fa5eaace798c414"
"@polkadot/extrinsics-codec@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics-codec/-/extrinsics-codec-0.12.9.tgz#1763b17682ee5e2313136e33d65ea9a5520b12a6"
dependencies:
"@polkadot/util" "^0.19.5"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
bn.js "^4.11.8"
"@polkadot/extrinsics-substrate@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics-substrate/-/extrinsics-substrate-0.12.8.tgz#c1929885fb6b155203109b96a561f74af496c897"
"@polkadot/extrinsics-substrate@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics-substrate/-/extrinsics-substrate-0.12.9.tgz#9d26e0c3e7ea53b668002dd0989adb22e42c392b"
dependencies:
"@polkadot/extrinsics" "^0.12.8"
"@polkadot/extrinsics" "^0.12.9"
babel-runtime "^6.26.0"
"@polkadot/extrinsics@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics/-/extrinsics-0.12.8.tgz#0e9201621cbe24a642e72233ff7303558b318d6a"
"@polkadot/extrinsics@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/extrinsics/-/extrinsics-0.12.9.tgz#1603cc55d95740a619e1ce883dc67fa3a677e761"
dependencies:
"@polkadot/primitives" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/primitives" "^0.12.9"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/primitives-builder@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-builder/-/primitives-builder-0.12.8.tgz#448e7ef4830b9c182a43e82a364e7b731336f140"
"@polkadot/primitives-builder@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-builder/-/primitives-builder-0.12.9.tgz#17754262e39b0c526f51be0e3531f0e4b91fca88"
dependencies:
"@polkadot/primitives" "^0.12.8"
"@polkadot/primitives-codec" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/util-crypto" "^0.19.5"
"@polkadot/util-triehash" "^0.19.5"
"@polkadot/primitives" "^0.12.9"
"@polkadot/primitives-codec" "^0.12.9"
"@polkadot/util" "^0.19.6"
"@polkadot/util-crypto" "^0.19.6"
"@polkadot/util-triehash" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/primitives-codec@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-codec/-/primitives-codec-0.12.8.tgz#503e925a7efd86342f96cc4e389bb2e9f9fe9f90"
"@polkadot/primitives-codec@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-codec/-/primitives-codec-0.12.9.tgz#659735a88589416311f31e74646dec7b50c1f503"
dependencies:
"@polkadot/primitives" "^0.12.8"
"@polkadot/primitives-builder" "^0.12.8"
"@polkadot/primitives-rlp" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/util-keyring" "^0.19.5"
"@polkadot/primitives" "^0.12.9"
"@polkadot/primitives-builder" "^0.12.9"
"@polkadot/primitives-rlp" "^0.12.9"
"@polkadot/util" "^0.19.6"
"@polkadot/util-keyring" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/primitives-json@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-json/-/primitives-json-0.12.8.tgz#0e218ad1e5b822319e949b5ef9651e27c280b6b8"
"@polkadot/primitives-json@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-json/-/primitives-json-0.12.9.tgz#e6d2afde7d3cf746abbb2a3d1760d7c8fb1ef49d"
dependencies:
"@polkadot/primitives" "^0.12.8"
"@polkadot/primitives-builder" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/primitives" "^0.12.9"
"@polkadot/primitives-builder" "^0.12.9"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/primitives-rlp@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-rlp/-/primitives-rlp-0.12.8.tgz#0701a328b9cb4bd855b6e02d77965691cdb81182"
"@polkadot/primitives-rlp@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/primitives-rlp/-/primitives-rlp-0.12.9.tgz#1151e61a510ba19d8f5e917d81676a2957ded306"
dependencies:
"@polkadot/primitives" "^0.12.8"
"@polkadot/primitives-builder" "^0.12.8"
"@polkadot/primitives-json" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/util-crypto" "^0.19.5"
"@polkadot/primitives" "^0.12.9"
"@polkadot/primitives-builder" "^0.12.9"
"@polkadot/primitives-json" "^0.12.9"
"@polkadot/util" "^0.19.6"
"@polkadot/util-crypto" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/primitives@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/primitives/-/primitives-0.12.8.tgz#f1efe01c498e52013ce0c59784fa1bc248c15b1d"
"@polkadot/primitives@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/primitives/-/primitives-0.12.9.tgz#bc46d5487f4ddbf4596208e3e514afbe9f4b1248"
dependencies:
"@polkadot/util" "^0.19.5"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/storage-substrate@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/storage-substrate/-/storage-substrate-0.12.8.tgz#5b74436958415cfd3b5f87285b9038a96bfbe64a"
"@polkadot/storage-substrate@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/storage-substrate/-/storage-substrate-0.12.9.tgz#3822255c8fef11308568d9a7a7694e389ed956f9"
dependencies:
"@polkadot/storage" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/storage" "^0.12.9"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/storage@^0.12.8":
version "0.12.8"
resolved "https://registry.yarnpkg.com/@polkadot/storage/-/storage-0.12.8.tgz#47438ec44b10e42898f8ac19e27ce6c5769f2c11"
"@polkadot/storage@^0.12.9":
version "0.12.9"
resolved "https://registry.yarnpkg.com/@polkadot/storage/-/storage-0.12.9.tgz#d4540056036ab75b32cc12001b220bce57d3b1be"
dependencies:
"@polkadot/primitives" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/primitives" "^0.12.9"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/ui-identicon@^0.11.2":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@polkadot/ui-identicon/-/ui-identicon-0.11.2.tgz#5141d3d52f0272f7e3a8dabea86d5ec5292216d1"
"@polkadot/ui-identicon@^0.11.3":
version "0.11.3"
resolved "https://registry.yarnpkg.com/@polkadot/ui-identicon/-/ui-identicon-0.11.3.tgz#b5c28e4e46208db0c12c8639f27eb04a73be1f45"
dependencies:
babel-runtime "^6.26.0"
color "^3.0.0"
"@polkadot/ui-react-rx@^0.11.2":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@polkadot/ui-react-rx/-/ui-react-rx-0.11.2.tgz#6f2af3f25a23b2c4fb650bda6498f57d6f564b42"
"@polkadot/ui-react-rx@^0.11.3":
version "0.11.3"
resolved "https://registry.yarnpkg.com/@polkadot/ui-react-rx/-/ui-react-rx-0.11.3.tgz#a0659fd7ad9fc0e339a36725064edfcc9d9168cb"
dependencies:
"@polkadot/api-rx" "^0.10.2"
"@polkadot/primitives" "^0.12.8"
"@polkadot/storage-substrate" "^0.12.8"
"@polkadot/util" "^0.19.5"
"@polkadot/api-rx" "^0.10.3"
"@polkadot/primitives" "^0.12.9"
"@polkadot/storage-substrate" "^0.12.9"
"@polkadot/util" "^0.19.6"
"@polkadot/ui-react@^0.11.2":
version "0.11.2"
resolved "https://registry.yarnpkg.com/@polkadot/ui-react/-/ui-react-0.11.2.tgz#a5d48d99a998c3cd9718663252d6e80f6c25cbd1"
"@polkadot/ui-react@^0.11.3":
version "0.11.3"
resolved "https://registry.yarnpkg.com/@polkadot/ui-react/-/ui-react-0.11.3.tgz#90c880466de8e47ba0d526f95976b32eb9663264"
dependencies:
"@polkadot/ui-identicon" "^0.11.2"
"@polkadot/ui-identicon" "^0.11.3"
babel-runtime "^6.26.0"
"@polkadot/util-crypto@^0.19.5":
version "0.19.5"
resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-0.19.5.tgz#570483b70434b14c3484531514297feab8c0ccd1"
"@polkadot/util-crypto@^0.19.6":
version "0.19.6"
resolved "https://registry.yarnpkg.com/@polkadot/util-crypto/-/util-crypto-0.19.6.tgz#26cf820765dbb01b8b34b309c14aec36ab204b96"
dependencies:
babel-runtime "^6.26.0"
blakejs "^1.1.0"
@ -826,34 +826,34 @@
tweetnacl "^1.0.0"
xxhashjs "^0.2.2"
"@polkadot/util-keyring@^0.19.5":
version "0.19.5"
resolved "https://registry.yarnpkg.com/@polkadot/util-keyring/-/util-keyring-0.19.5.tgz#bb75140ea9e64a3e2a9f17f66e68c1826f7a3de6"
"@polkadot/util-keyring@^0.19.6":
version "0.19.6"
resolved "https://registry.yarnpkg.com/@polkadot/util-keyring/-/util-keyring-0.19.6.tgz#6c0e9221507303bcb3445f6b40252198bb9c631c"
dependencies:
"@polkadot/util" "^0.19.5"
"@polkadot/util-crypto" "^0.19.5"
"@polkadot/util" "^0.19.6"
"@polkadot/util-crypto" "^0.19.6"
babel-runtime "^6.26.0"
bs58 "^4.0.1"
"@polkadot/util-rlp@^0.19.5":
version "0.19.5"
resolved "https://registry.yarnpkg.com/@polkadot/util-rlp/-/util-rlp-0.19.5.tgz#ae3cb42339cf54b84989fbcef093a815622d14c8"
"@polkadot/util-rlp@^0.19.6":
version "0.19.6"
resolved "https://registry.yarnpkg.com/@polkadot/util-rlp/-/util-rlp-0.19.6.tgz#f26fe2f926a381a09f81fa5c5a5e5fd155cb6c20"
dependencies:
"@polkadot/util" "^0.19.5"
"@polkadot/util" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/util-triehash@^0.19.5":
version "0.19.5"
resolved "https://registry.yarnpkg.com/@polkadot/util-triehash/-/util-triehash-0.19.5.tgz#7d273367b041a58d026975593fcf0858c68b3ece"
"@polkadot/util-triehash@^0.19.6":
version "0.19.6"
resolved "https://registry.yarnpkg.com/@polkadot/util-triehash/-/util-triehash-0.19.6.tgz#e5c004da0ccd02ba769f7605484170e4dce1804b"
dependencies:
"@polkadot/util" "^0.19.5"
"@polkadot/util-crypto" "^0.19.5"
"@polkadot/util-rlp" "^0.19.5"
"@polkadot/util" "^0.19.6"
"@polkadot/util-crypto" "^0.19.6"
"@polkadot/util-rlp" "^0.19.6"
babel-runtime "^6.26.0"
"@polkadot/util@^0.19.5":
version "0.19.5"
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-0.19.5.tgz#344b014d7050d458080a25656122d85a9f207a7e"
"@polkadot/util@^0.19.6":
version "0.19.6"
resolved "https://registry.yarnpkg.com/@polkadot/util/-/util-0.19.6.tgz#fb2eca72e710090a8ef4c17e376d7540d1df06a8"
dependencies:
babel-runtime "^6.26.0"
bn.js "^4.11.8"
@ -9428,6 +9428,10 @@ stealthy-require@^1.1.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
store@^2.0.12:
version "2.0.12"
resolved "https://registry.yarnpkg.com/store/-/store-2.0.12.tgz#8c534e2a0b831f72b75fc5f1119857c44ef5d593"
stream-browserify@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db"