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:
Jaco Greeff 2018-05-10 09:52:49 +02:00 committed by GitHub
parent d8f7ef5f5e
commit 6006749297
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 1392 additions and 648 deletions

View File

@ -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'

View File

@ -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.

View File

@ -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'"
},

View File

@ -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();
}

View File

@ -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
});
}
}

View File

@ -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>
);
}

View File

@ -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'>

View File

@ -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}

View File

@ -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';

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/ui-keyring

View 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"
}
}

View 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}`, '');

View 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);

View 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);
}

View File

@ -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;

View File

@ -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>

View 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);
}

View File

@ -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}
/>

View 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);
}

View 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);
}

View 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);
}

View 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
};

View File

@ -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",

View File

@ -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}
/>
);
}

View File

@ -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];
}

View File

@ -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>;

View File

@ -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;

View File

@ -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
};

1428
yarn.lock

File diff suppressed because it is too large Load Diff