Split UI-specific keyring into own package (#36)
* Swap to using split-out ui-keyring * First part of split into ui-keyring complete * Create/Edit account operational * Cleanup keyring display * InputAddress defaultValue
This commit is contained in:
parent
d8f7ef5f5e
commit
6006749297
@ -14,4 +14,4 @@ all=warn
|
||||
include_warnings=true
|
||||
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'
|
||||
module.name_mapper='^@polkadot/ui-\(keyring\|react-app\)\(.*\)$' -> '<PROJECT_ROOT>/packages/ui-\1/src\2'
|
||||
|
||||
@ -18,10 +18,13 @@ This can be accessed as a hosted application via [https://polkadot.js.org/apps/]
|
||||
The repo is split into a number of packages, each representing an application. These are -
|
||||
|
||||
- [apps](packages/apps/) This is the main entry point. It handles the selection sidebar and routing to the specific application being displayed.
|
||||
- [app-accounts](packages/app-accounts/) A basic account management app.
|
||||
- [app-explorer](packages/app-explorer/) A simple block explorer. It only shows the most recent blocks, updating as they become available.
|
||||
- [app-extrinsics](packages/app-extrinsics/) Submission of extrinsics to a node.
|
||||
- [app-storage](packages/app-storage/) A simple node storage query application. Multiple queries can be queued and updates as new values become available.
|
||||
- [app-vanitygen](packages/app-vanitygen/) A toy that allows you to generate vanity addresses. Running `yarn run vanitygen --match <string>` runs the generator as a Node CLI app. (Orders of a magnitude faster due to the use of libsoldium bindings)
|
||||
|
||||
In addition the following libraries are also included in the repo -
|
||||
In addition the following libraries are also included in the repo. These are to be moved to the [@polkadot/ui](https://github.com/polkadot-js/ui/) repository once it reaches a base level of stability and usability. (At this point with the framework being tested on the apps above, it makes development easier having it close)
|
||||
|
||||
- [ui-react-app](packages/ui-react-app/) A reactive (using RxJS) application framework with a number of useful shared components. This will be moved to the [@polkadot/ui](https://github.com/polkadot-js/ui/) repository once it reaches a base level of stability and usability. (At this point with the framework being tested on the apps above, it makes development easier having it close)
|
||||
- [ui-react-app](packages/ui-keyring/) A browser-specific wrapper around the base [@polkadot/util-keyring](https://github.com/polkadot-js/util/) libary.
|
||||
- [ui-react-app](packages/ui-react-app/) A reactive (using RxJS) application framework with a number of useful shared components.
|
||||
|
||||
@ -10,7 +10,7 @@
|
||||
"build:apps": "cd packages/apps && react-scripts build",
|
||||
"check": "eslint packages && flow check",
|
||||
"deploy:ghpages": "gh-pages -d packages/apps/build",
|
||||
"generator": "polkadot-dev-build-babel && node packages/app-vanitygen/build/generator/cli.js",
|
||||
"vanitygen": "polkadot-dev-build-babel && node packages/app-vanitygen/build/generator/cli.js",
|
||||
"start": "cd packages/apps && react-scripts start",
|
||||
"test": "echo 'no tests executed'"
|
||||
},
|
||||
|
||||
@ -3,12 +3,13 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { I18nProps, KeyringInstance } from '@polkadot/ui-react-app/types';
|
||||
import type { I18nProps } 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 keyring from '@polkadot/ui-keyring/src';
|
||||
import isHex from '@polkadot/util/is/hex';
|
||||
import hexToU8a from '@polkadot/util/hex/toU8a';
|
||||
import u8aFromString from '@polkadot/util/u8a/fromString';
|
||||
@ -21,7 +22,6 @@ import Address from './Address';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
keyring: KeyringInstance,
|
||||
onBack: () => void
|
||||
};
|
||||
|
||||
@ -203,14 +203,14 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
onCommit = (): void => {
|
||||
const { keyring, onBack } = this.props;
|
||||
const { onBack } = this.props;
|
||||
const { name, password, seed } = this.state;
|
||||
const pair = keyring.addFromSeed(
|
||||
formatSeed(seed),
|
||||
{ name }
|
||||
);
|
||||
|
||||
keyring.saveJson(pair, password);
|
||||
keyring.saveAccount(pair, password);
|
||||
|
||||
onBack();
|
||||
}
|
||||
|
||||
@ -4,20 +4,18 @@
|
||||
// @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 type { I18nProps } 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 keyring from '@polkadot/ui-keyring/src';
|
||||
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
|
||||
};
|
||||
|
||||
@ -25,8 +23,7 @@ type State = {
|
||||
currentPair: KeyringPair,
|
||||
defaultPublicKey: Uint8Array,
|
||||
editedName: string,
|
||||
isEdited: boolean,
|
||||
options: KeyringOptions
|
||||
isEdited: boolean
|
||||
}
|
||||
|
||||
class Editor extends React.PureComponent<Props, State> {
|
||||
@ -35,17 +32,16 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
const pairs = props.keyring.getPairs();
|
||||
const pairs = 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;
|
||||
const { defaultPublicKey, editedName, isEdited } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -55,11 +51,12 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
<div className='ui--form'>
|
||||
<InputAddress
|
||||
defaultValue={defaultPublicKey}
|
||||
options={options}
|
||||
isInput={false}
|
||||
label={t('editor.select', {
|
||||
defaultValue: 'using my account'
|
||||
})}
|
||||
onChange={this.onChangeAccount}
|
||||
type='account'
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--form'>
|
||||
@ -98,10 +95,6 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
createOptions (): KeyringOptions {
|
||||
return createOptions(false, this.props.keyring, true);
|
||||
}
|
||||
|
||||
createState (currentPair: KeyringPair): $Shape<State> {
|
||||
const { name = '' } = currentPair.getMeta();
|
||||
|
||||
@ -115,7 +108,7 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
nextState (newState?: $Shape<State> = {}): void {
|
||||
this.setState(
|
||||
(prevState: State): $Shape<State> => {
|
||||
let { currentPair = prevState.currentPair, editedName = prevState.editedName, options = prevState.options } = newState;
|
||||
let { currentPair = prevState.currentPair, editedName = prevState.editedName } = newState;
|
||||
|
||||
if (currentPair.address() !== prevState.currentPair.address()) {
|
||||
editedName = currentPair.getMeta().name || '';
|
||||
@ -126,15 +119,13 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
return {
|
||||
currentPair,
|
||||
editedName,
|
||||
isEdited,
|
||||
options
|
||||
isEdited
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onChangeAccount = (publicKey: Uint8Array): void => {
|
||||
const { keyring } = this.props;
|
||||
const currentPair = keyring.getPair(publicKey);
|
||||
|
||||
this.nextState({ currentPair });
|
||||
@ -148,15 +139,20 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
onCommit = (): void => {
|
||||
const { currentPair, editedName } = this.state;
|
||||
|
||||
currentPair.setMeta({ name: editedName });
|
||||
keyring.saveAccountMeta(currentPair, {
|
||||
name: editedName,
|
||||
whenEdited: Date.now()
|
||||
});
|
||||
|
||||
this.nextState({ options: this.createOptions() });
|
||||
this.nextState({});
|
||||
}
|
||||
|
||||
onDiscard = (): void => {
|
||||
const { currentPair } = this.state;
|
||||
|
||||
this.nextState({ editedName: currentPair.getMeta().name });
|
||||
this.nextState({
|
||||
editedName: currentPair.getMeta().name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ 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 & {};
|
||||
@ -75,10 +74,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</div>
|
||||
<Component
|
||||
keyring={keyring}
|
||||
onBack={this.selectEdit}
|
||||
/>
|
||||
<Component onBack={this.selectEdit} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { KeyringOption$Type } from '@polkadot/ui-keyring/types';
|
||||
import type { I18nProps } from '@polkadot/ui-react-app/types';
|
||||
|
||||
import React from 'react';
|
||||
@ -17,7 +18,8 @@ type Props = I18nProps & {
|
||||
isError?: boolean,
|
||||
isInput?: boolean,
|
||||
label: string,
|
||||
onChange: (publicKey: Uint8Array) => void
|
||||
onChange: (publicKey: Uint8Array) => void,
|
||||
type?: KeyringOption$Type
|
||||
};
|
||||
|
||||
type State = {
|
||||
@ -36,7 +38,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
render (): React$Node {
|
||||
const { className, defaultValue, isError, isInput, label, style, t } = this.props;
|
||||
const { className, defaultValue, isError, isInput, label, style, t, type } = this.props;
|
||||
const { publicKey } = this.state;
|
||||
|
||||
return (
|
||||
@ -52,6 +54,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
label={label}
|
||||
onChange={this.onChange}
|
||||
placeholder='0x...'
|
||||
type={type}
|
||||
/>
|
||||
</div>
|
||||
<div className='small'>
|
||||
|
||||
@ -52,10 +52,12 @@ class Selection extends React.PureComponent<Props, State> {
|
||||
style={style}
|
||||
>
|
||||
<Account
|
||||
isInput={false}
|
||||
label={t('display.sender', {
|
||||
defaultValue: 'using the selected account'
|
||||
})}
|
||||
onChange={this.onChangeSender}
|
||||
type='account'
|
||||
/>
|
||||
<Extrinsic
|
||||
defaultValue={defaultExtrinsic}
|
||||
|
||||
@ -7,7 +7,7 @@ import type { RxApiInterface } from '@polkadot/api-rx/types';
|
||||
import type { QueueTx } from '../types';
|
||||
|
||||
import encodeCall from '@polkadot/extrinsics-codec/encode/call';
|
||||
import keyring from '@polkadot/ui-react-app/src/keyring';
|
||||
import keyring from '@polkadot/ui-keyring/src';
|
||||
import u8aConcat from '@polkadot/util/u8a/concat';
|
||||
import u8aToHex from '@polkadot/util/u8a/toHex';
|
||||
|
||||
|
||||
15
packages/ui-keyring/LICENSE
Normal file
15
packages/ui-keyring/LICENSE
Normal 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.
|
||||
1
packages/ui-keyring/README.md
Normal file
1
packages/ui-keyring/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @polkadot/ui-keyring
|
||||
23
packages/ui-keyring/package.json
Normal file
23
packages/ui-keyring/package.json
Normal file
@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@polkadot/ui-keyring",
|
||||
"version": "0.3.1",
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@polkadot/util": "^0.19.6",
|
||||
"@polkadot/util-keyring": "^0.19.6",
|
||||
"store": "^2.0.12"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@polkadot/ui-react": "^0.11.3",
|
||||
"react": "^16.3.1",
|
||||
"semantic-ui-react": "^0.79.1"
|
||||
}
|
||||
}
|
||||
17
packages/ui-keyring/src/defaults.js
Normal file
17
packages/ui-keyring/src/defaults.js
Normal file
@ -0,0 +1,17 @@
|
||||
// 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
|
||||
|
||||
const ACCOUNT_PREFIX = 'account:';
|
||||
const ADDRESS_PREFIX = 'address:';
|
||||
|
||||
export const accountKey = (address: string): string =>
|
||||
`${ACCOUNT_PREFIX}${address}`;
|
||||
|
||||
export const addressKey = (address: string): string =>
|
||||
`${ADDRESS_PREFIX}${address}`;
|
||||
|
||||
export const accountRegex = new RegExp(`^${ACCOUNT_PREFIX}`, '');
|
||||
|
||||
export const addressRegex = new RegExp(`^${ADDRESS_PREFIX}`, '');
|
||||
39
packages/ui-keyring/src/index.js
Normal file
39
packages/ui-keyring/src/index.js
Normal file
@ -0,0 +1,39 @@
|
||||
// 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, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
import type { KeyringInstance, KeyringOption$Type, KeyringOption, KeyringOptions, State } from './types';
|
||||
|
||||
import testKeyring from '@polkadot/util-keyring/testing';
|
||||
|
||||
import loadAll from './loadAll';
|
||||
import saveAccount from './save/account';
|
||||
import saveAccountMeta from './save/accountMeta';
|
||||
import saveRecent from './save/recent';
|
||||
|
||||
const state: State = {
|
||||
available: {
|
||||
account: {},
|
||||
address: {}
|
||||
},
|
||||
keyring: testKeyring(),
|
||||
options: {}
|
||||
};
|
||||
|
||||
loadAll(state);
|
||||
|
||||
export default ({
|
||||
...state.keyring,
|
||||
getOptions: (type: KeyringOption$Type): KeyringOptions =>
|
||||
state.options[type],
|
||||
loadAll: (): void =>
|
||||
loadAll(state),
|
||||
saveAccount: (pair: KeyringPair, password?: string): void =>
|
||||
saveAccount(state, pair, password),
|
||||
saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta): void =>
|
||||
saveAccountMeta(state, pair, meta),
|
||||
saveRecent: (address: string): KeyringOption =>
|
||||
saveRecent(state, address)
|
||||
}: KeyringInstance);
|
||||
27
packages/ui-keyring/src/loadAll.js
Normal file
27
packages/ui-keyring/src/loadAll.js
Normal file
@ -0,0 +1,27 @@
|
||||
// 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$Json } from '@polkadot/util-keyring/types';
|
||||
import type { State, KeyringJson } from './types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import createOptions from './options';
|
||||
import { accountRegex, addressRegex } from './defaults';
|
||||
|
||||
export default function loadAll (state: State): void {
|
||||
const { available, keyring } = state;
|
||||
|
||||
store.each((json: KeyringJson, key: string) => {
|
||||
if (accountRegex.test(key)) {
|
||||
keyring.addFromJson((json: $Shape<KeyringPair$Json>));
|
||||
available.account[json.address] = json;
|
||||
} else if (addressRegex.test(key)) {
|
||||
available.address[json.address] = json;
|
||||
}
|
||||
});
|
||||
|
||||
createOptions(state);
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the ISC license. See the LICENSE file for details. */
|
||||
|
||||
.ui--InputyAddress-PairDisplay {
|
||||
.ui--KeyPair {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
@ -10,18 +10,18 @@
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ui--InputyAddress-PairDisplay-icon {
|
||||
.ui--KeyPair-icon {
|
||||
position: absolute;
|
||||
top: -9px;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.ui--InputyAddress-PairDisplay-address,
|
||||
.ui--InputyAddress-PairDisplay-name {
|
||||
.ui--KeyPair-address,
|
||||
.ui--KeyPair-name {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ui--InputyAddress-PairDisplay-address {
|
||||
.ui--KeyPair-address {
|
||||
flex: 1;
|
||||
font-family: monospace;
|
||||
margin-left: 1rem;
|
||||
@ -31,7 +31,7 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ui--InputyAddress-PairDisplay-name {
|
||||
.ui--KeyPair-name {
|
||||
flex: 1 0;
|
||||
margin-left: 3rem;
|
||||
overflow: hidden;
|
||||
@ -3,7 +3,7 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import './PairDisplay.css';
|
||||
import './KeyPair.css';
|
||||
|
||||
import React from 'react';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
@ -17,21 +17,21 @@ type Props = {
|
||||
}
|
||||
};
|
||||
|
||||
export default function PairDisplay ({ address, className, name, style }: Props): React$Node {
|
||||
export default function KeyPair ({ address, className, name, style }: Props): React$Node {
|
||||
return (
|
||||
<div
|
||||
className={['ui--InputyAddress-PairDisplay', className].join(' ')}
|
||||
className={['ui--KeyPair', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<IdentityIcon
|
||||
className='ui--InputyAddress-PairDisplay-icon'
|
||||
className='ui--KeyPair-icon'
|
||||
size={32}
|
||||
value={address}
|
||||
/>
|
||||
<div className='ui--InputyAddress-PairDisplay-name'>
|
||||
<div className='ui--KeyPair-name'>
|
||||
{name}
|
||||
</div>
|
||||
<div className='ui--InputyAddress-PairDisplay-address'>
|
||||
<div className='ui--KeyPair-address'>
|
||||
{address}
|
||||
</div>
|
||||
</div>
|
||||
69
packages/ui-keyring/src/options/index.js
Normal file
69
packages/ui-keyring/src/options/index.js
Normal file
@ -0,0 +1,69 @@
|
||||
// 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 { State, KeyringJson } from '../types';
|
||||
|
||||
// import React from 'react';
|
||||
// import Dropdown from 'semantic-ui-react/dist/es/modules/Dropdown';
|
||||
|
||||
import createItem from './item';
|
||||
|
||||
export default function createOptions (state: State): void {
|
||||
// FIXME We really want headers, but it seems to create havoc when searching through the list (appears as duplicates)
|
||||
state.options = {
|
||||
account: [], // <Dropdown.Header>Accounts</Dropdown.Header> ],
|
||||
address: [], // <Dropdown.Header>Addresses</Dropdown.Header> ],
|
||||
all: [],
|
||||
recent: [], // <Dropdown.Header>Recent</Dropdown.Header> ],
|
||||
testing: [] // <Dropdown.Header>Testing</Dropdown.Header> ]
|
||||
};
|
||||
|
||||
const { available, keyring, options } = state;
|
||||
|
||||
keyring
|
||||
.getPairs()
|
||||
.forEach((pair) => {
|
||||
const address = pair.address();
|
||||
|
||||
available.account[address] = {
|
||||
address,
|
||||
meta: pair.getMeta()
|
||||
};
|
||||
});
|
||||
|
||||
Object
|
||||
.values(available.account)
|
||||
// $FlowFixMe values() -> mixed, this is an object
|
||||
.forEach(({ address, meta: { name, isTesting = false } }: KeyringJson) => {
|
||||
const option = createItem(address, name);
|
||||
|
||||
if (isTesting) {
|
||||
options.testing.push(option);
|
||||
} else {
|
||||
options.account.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
Object
|
||||
.values(available.address)
|
||||
// $FlowFixMe values() -> mixed, this is an object
|
||||
.forEach(({ address, meta: { name, isRecent = false } }: KeyringJson) => {
|
||||
if (available.account[address]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const option = createItem(address, name);
|
||||
|
||||
if (isRecent) {
|
||||
options.recent.push(option);
|
||||
} else {
|
||||
options.address.push(option);
|
||||
}
|
||||
});
|
||||
|
||||
options.address = options.address.concat(options.recent);
|
||||
options.account = options.account.concat(options.testing);
|
||||
options.all = options.account.concat(options.address);
|
||||
}
|
||||
@ -3,21 +3,22 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { KeyringOption } from './types';
|
||||
import type { KeyringOption, KeyringOption$Data } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import PairDisplay from './PairDisplay';
|
||||
import KeyPair from './KeyPair';
|
||||
|
||||
export default function createItem (address: string, name?: string, isManual: boolean = false): KeyringOption {
|
||||
export default function createItem (address: string, name?: string, data?: KeyringOption$Data = {}): KeyringOption {
|
||||
// flowlint-next-line sketchy-null-string:off
|
||||
name = name || address;
|
||||
name = name || `${address.slice(0, 16)}…`;
|
||||
|
||||
return {
|
||||
'data-manual': isManual,
|
||||
...data,
|
||||
key: address,
|
||||
name,
|
||||
text: (
|
||||
<PairDisplay
|
||||
<KeyPair
|
||||
address={address}
|
||||
name={name}
|
||||
/>
|
||||
24
packages/ui-keyring/src/save/account.js
Normal file
24
packages/ui-keyring/src/save/account.js
Normal file
@ -0,0 +1,24 @@
|
||||
// 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 { State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { accountKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveAccount (state: State, pair: KeyringPair, password?: string): void {
|
||||
const json = pair.toJson(password);
|
||||
|
||||
if (!json.meta.whenCreated) {
|
||||
json.meta.whenCreated = Date.now();
|
||||
}
|
||||
|
||||
store.set(accountKey(json.address), json);
|
||||
|
||||
createOptions(state);
|
||||
}
|
||||
24
packages/ui-keyring/src/save/accountMeta.js
Normal file
24
packages/ui-keyring/src/save/accountMeta.js
Normal file
@ -0,0 +1,24 @@
|
||||
// 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, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
import type { State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { accountKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveAccountMeta (state: State, pair: KeyringPair, meta: KeyringPair$Meta): void {
|
||||
const address = pair.address();
|
||||
const json = store.get(accountKey(address));
|
||||
|
||||
pair.setMeta(meta);
|
||||
json.meta = pair.getMeta();
|
||||
|
||||
store.set(accountKey(json.address), json);
|
||||
|
||||
createOptions(state);
|
||||
}
|
||||
32
packages/ui-keyring/src/save/recent.js
Normal file
32
packages/ui-keyring/src/save/recent.js
Normal file
@ -0,0 +1,32 @@
|
||||
// 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$Meta } from '@polkadot/util-keyring/types';
|
||||
import type { KeyringJson, KeyringOption, State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { addressKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveRecent (state: State, address: string): KeyringOption {
|
||||
if (!state.available[address]) {
|
||||
const json = {
|
||||
address,
|
||||
meta: {
|
||||
isRecent: true,
|
||||
whenCreated: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
store.set(addressKey(json.address), json);
|
||||
state.available.address[address] = (json: $Shape<KeyringJson>);
|
||||
}
|
||||
|
||||
createOptions(state);
|
||||
|
||||
// $FlowFixMe we will have it now
|
||||
return state.options.recent.find(({ value }) => value === address);
|
||||
}
|
||||
57
packages/ui-keyring/src/types.js
Normal file
57
packages/ui-keyring/src/types.js
Normal file
@ -0,0 +1,57 @@
|
||||
// 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 as BaseKeyringInstance, KeyringPair, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
|
||||
export type KeyringOption$Type = 'account' | 'address' | 'all' | 'recent' | 'testing';
|
||||
|
||||
export type KeyringOption$Data = {
|
||||
'data-is-account'?: boolean,
|
||||
'data-is-recent'?: boolean,
|
||||
'data-is-testing'?: boolean,
|
||||
'data-when-created'?: number,
|
||||
'data-when-edited'?: number,
|
||||
'data-when-used'?: number
|
||||
}
|
||||
|
||||
export type KeyringOption = KeyringOption$Data & {
|
||||
key: string | null,
|
||||
name: string,
|
||||
text: React$Node | string,
|
||||
value: string
|
||||
};
|
||||
|
||||
export type KeyringOptions = Array<KeyringOption>;
|
||||
|
||||
export type KeyringJson = {
|
||||
address: string,
|
||||
meta: {
|
||||
isRecent?: boolean,
|
||||
isTesting?: boolean,
|
||||
name?: string,
|
||||
whenCreated?: number,
|
||||
whenEdited?: number,
|
||||
whenUsed?: number
|
||||
}
|
||||
};
|
||||
|
||||
export type State = {
|
||||
available: {
|
||||
account: { [string]: KeyringJson },
|
||||
address: { [string]: KeyringJson }
|
||||
},
|
||||
keyring: BaseKeyringInstance,
|
||||
options: {
|
||||
[KeyringOption$Type]: KeyringOptions
|
||||
}
|
||||
};
|
||||
|
||||
export type KeyringInstance = BaseKeyringInstance & {
|
||||
getOptions: (type: KeyringOption$Type) => KeyringOptions,
|
||||
loadAll: () => void,
|
||||
saveAccount: (pair: KeyringPair, password?: string) => void,
|
||||
saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta) => void,
|
||||
saveRecent: (address: string) => KeyringOption
|
||||
};
|
||||
@ -13,10 +13,10 @@
|
||||
"dependencies": {
|
||||
"@polkadot/extrinsics-substrate": "^0.12.9",
|
||||
"@polkadot/storage-substrate": "^0.12.9",
|
||||
"@polkadot/ui-keyring": "^0.3.1",
|
||||
"@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",
|
||||
|
||||
@ -3,18 +3,17 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
import type { KeyringOptions, KeyringOption$Type } from '@polkadot/ui-keyring/types';
|
||||
import type { BareProps } from '../types';
|
||||
import type { KeyringOptions } from './types';
|
||||
|
||||
import './InputAddress.css';
|
||||
|
||||
import React from 'react';
|
||||
import keyring from '@polkadot/ui-keyring/src';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import RxDropdown from '../RxDropdown';
|
||||
import createItem from './optionItem';
|
||||
import createOptions from './options';
|
||||
|
||||
type Props = BareProps & {
|
||||
defaultValue?: Uint8Array,
|
||||
@ -22,10 +21,13 @@ type Props = BareProps & {
|
||||
isInput?: boolean,
|
||||
label?: string,
|
||||
onChange: (value: Uint8Array) => void,
|
||||
options?: KeyringOptions,
|
||||
withInput?: boolean
|
||||
type?: KeyringOption$Type
|
||||
};
|
||||
|
||||
type State = {
|
||||
defaultValue?: string;
|
||||
}
|
||||
|
||||
const transform = (value: string): Uint8Array => {
|
||||
try {
|
||||
return addressDecode(value);
|
||||
@ -34,17 +36,60 @@ const transform = (value: string): Uint8Array => {
|
||||
}
|
||||
};
|
||||
|
||||
export default function InputAddress ({ className, defaultValue, isError, isInput = false, label, onChange, options, style }: Props): React$Node {
|
||||
const _options = options || createOptions(isInput);
|
||||
const onSearch = (filteredOptions: KeyringOptions, query: string): KeyringOptions => {
|
||||
// NOTE: We are not extending Component here since the options may change in the keyring (which needs a re-render), however the input props will be the same (so, no PureComponent with shallow compare here)
|
||||
export default class InputAddress extends React.Component<Props, State> {
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
let defaultValue;
|
||||
|
||||
if (props.defaultValue) {
|
||||
try {
|
||||
defaultValue = addressEncode(props.defaultValue);
|
||||
} catch (error) {
|
||||
console.error('Unable to encode defaultValue address', props.defaultValue);
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
defaultValue
|
||||
};
|
||||
}
|
||||
|
||||
render (): React$Node {
|
||||
const { className, isError, label, onChange, style, type = 'all' } = this.props;
|
||||
const { defaultValue } = this.state;
|
||||
const options = keyring.getOptions(type);
|
||||
|
||||
return (
|
||||
<RxDropdown
|
||||
className={['ui--InputAddress', className].join(' ')}
|
||||
defaultValue={defaultValue}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
options={options}
|
||||
search={this.onSearch}
|
||||
style={style}
|
||||
transform={transform}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
onSearch = (filteredOptions: KeyringOptions, query: string): KeyringOptions => {
|
||||
const { isInput = true } = this.props;
|
||||
const queryLower = query.toLowerCase();
|
||||
const matches = filteredOptions.filter((item) => {
|
||||
// flowlint-next-line sketchy-null-string:off
|
||||
if (!item.key) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { name, value } = item;
|
||||
const isManual = item['data-manual'] || false;
|
||||
const hasMatch = name.toLowerCase().indexOf(queryLower) !== -1 ||
|
||||
value.toLowerCase().indexOf(queryLower) !== -1;
|
||||
|
||||
return hasMatch && (isInput || !isManual);
|
||||
return hasMatch;
|
||||
});
|
||||
|
||||
// see if we should add a new item, i.e. valid address found
|
||||
@ -52,26 +97,12 @@ export default function InputAddress ({ className, defaultValue, isError, isInpu
|
||||
const publicKey = transform(query);
|
||||
|
||||
if (publicKey.length === 32) {
|
||||
const newOption = createItem(query, `${query.slice(0, 16)}…`, true);
|
||||
|
||||
matches.push(newOption);
|
||||
_options.push(newOption);
|
||||
matches.push(
|
||||
keyring.saveRecent(query)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
};
|
||||
|
||||
return (
|
||||
<RxDropdown
|
||||
className={['ui--InputAddress', className].join(' ')}
|
||||
defaultValue={defaultValue && addressEncode(defaultValue)}
|
||||
isError={isError}
|
||||
label={label}
|
||||
onChange={onChange}
|
||||
options={_options}
|
||||
search={onSearch}
|
||||
transform={transform}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,33 +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.
|
||||
// @flow
|
||||
|
||||
import type { KeyringInstance } from '@polkadot/util-keyring/types';
|
||||
import type { KeyringOptions } from './types';
|
||||
|
||||
import testKeyring from '../keyring';
|
||||
import createItem from './optionItem';
|
||||
|
||||
const cached: { [string]: ?KeyringOptions } = {
|
||||
addresses: null,
|
||||
accounts: null
|
||||
};
|
||||
|
||||
export default function createOptions (isAddress?: boolean = false, keyring?: KeyringInstance = testKeyring, forceReload: boolean = false): KeyringOptions {
|
||||
const map = isAddress ? 'addresses' : 'accounts';
|
||||
|
||||
if (forceReload || !cached[map]) {
|
||||
cached[map] = keyring
|
||||
.getPairs()
|
||||
.map((pair) => {
|
||||
const address = pair.address();
|
||||
const { name } = pair.getMeta();
|
||||
|
||||
return createItem(address, name);
|
||||
});
|
||||
}
|
||||
|
||||
// $FlowFixMe we have checked and set just above
|
||||
return cached[map];
|
||||
}
|
||||
@ -1,13 +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.
|
||||
// @flow
|
||||
|
||||
export type KeyringOption = {
|
||||
'data-manual'?: boolean,
|
||||
name: string,
|
||||
text: React$Node | string,
|
||||
value: string
|
||||
};
|
||||
|
||||
export type KeyringOptions = Array<KeyringOption>;
|
||||
@ -1,34 +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.
|
||||
// @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();
|
||||
|
||||
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;
|
||||
@ -4,7 +4,6 @@
|
||||
// @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,
|
||||
@ -26,8 +25,3 @@ export type BaseContext = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export type KeyringInstance = UKeyringInstance & {
|
||||
loadJson: () => void,
|
||||
saveJson: (pair: KeyringPair, password?: string) => void
|
||||
};
|
||||
|
||||
Loading…
Reference in New Issue
Block a user