Basic address management app (#38)
* Update vanity regex (no 'I') * Allow copy of generated seed * Update connecting info * ui--form -> ui--row * Cleanup layouts with ui--row * negative -> default (non-removal) * Embedded rows don't have padding * Allow ability to hide address * Adjust button layouts * Make account edit/create (slightly) prettier * @flow fixes * Some spacing adjustments * Outline ofg the addressbook app * Address create & edit operational * @flow fixes * Small UI cleanups, done as a first round
This commit is contained in:
parent
cd64a4de5f
commit
0eab4f6f22
5
flow-typed/react-copy-to-clipboard.js
vendored
Normal file
5
flow-typed/react-copy-to-clipboard.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// @flow
|
||||
|
||||
declare module 'react-copy-to-clipboard' {
|
||||
declare module.exports: React$ComponentType<{ text: string }>;
|
||||
}
|
||||
@ -6,36 +6,60 @@
|
||||
import type { BareProps } from '@polkadot/ui-react-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import Button from 'semantic-ui-react/dist/es/elements/Button';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import Balance from '@polkadot/ui-react-rx/Balance';
|
||||
import Nonce from '@polkadot/ui-react-rx/Nonce';
|
||||
|
||||
type Props = BareProps & {
|
||||
address: string | null;
|
||||
value: null | {
|
||||
address: string,
|
||||
publicKey: Uint8Array
|
||||
};
|
||||
}
|
||||
|
||||
export default function Address ({ address, className, style }: Props): React$Node {
|
||||
export default function Address ({ className, style, value }: Props): React$Node {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { address, publicKey } = value;
|
||||
const short = `${address.slice(0, 7)}…${address.slice(-7)}`;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['accounts--Address', 'ui disabled dropdown selection', className].join(' ')}
|
||||
className={['accounts--Address', 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'
|
||||
}
|
||||
<IdentityIcon
|
||||
className='accounts--Address-icon'
|
||||
size={96}
|
||||
value={address}
|
||||
/>
|
||||
<div className='accounts--Address-data'>
|
||||
<div className='accounts--Address-address'>
|
||||
{short}
|
||||
</div>
|
||||
<CopyToClipboard text={address}>
|
||||
<Button
|
||||
icon='copy'
|
||||
primary
|
||||
size='tiny'
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
</div>
|
||||
<Balance
|
||||
className='accounts--Address-balance'
|
||||
label='balance '
|
||||
value={publicKey}
|
||||
/>
|
||||
<Nonce
|
||||
className='accounts--Address-nonce'
|
||||
value={publicKey}
|
||||
>
|
||||
{' transactions'}
|
||||
</Nonce>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -27,6 +27,7 @@ type Props = I18nProps & {
|
||||
|
||||
type State = {
|
||||
address: string | null,
|
||||
publicKey: Uint8Array | null,
|
||||
fieldName: string,
|
||||
isNameValid: boolean,
|
||||
isSeedValid: boolean,
|
||||
@ -44,10 +45,14 @@ function formatSeed (seed: string): Uint8Array {
|
||||
: u8aFromString(seed.padEnd(32, ' '));
|
||||
}
|
||||
|
||||
function addressFromSeed (seed: string): string {
|
||||
function infoFromSeed (seed: string): { address: string, publicKey: Uint8Array } {
|
||||
const { publicKey } = keypairFromSeed(formatSeed(seed));
|
||||
const address = addressEncode(publicKey);
|
||||
|
||||
return addressEncode(publicKey);
|
||||
return {
|
||||
address,
|
||||
publicKey
|
||||
};
|
||||
}
|
||||
|
||||
class Creator extends React.PureComponent<Props, State> {
|
||||
@ -61,72 +66,80 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
|
||||
render (): React$Node {
|
||||
const { className, style, t } = this.props;
|
||||
const { address, fieldName, isNameValid, isPassValid, isPassVisible, isSeedValid, isValid, name, password, seed } = this.state;
|
||||
const { address, fieldName, isNameValid, isPassValid, isPassVisible, isSeedValid, isValid, name, password, publicKey, seed } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['accounts--Creator', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--form'>
|
||||
<div className='ui--grid'>
|
||||
<div className='medium'>
|
||||
<Label>{t('creator.seed', {
|
||||
defaultValue: 'create from the following seed (hex or string)'
|
||||
})}</Label>
|
||||
<Input
|
||||
error={!isSeedValid}
|
||||
name={`${fieldName}_seed`}
|
||||
onChange={this.onChangeSeed}
|
||||
value={seed}
|
||||
/>
|
||||
</div>
|
||||
<div className='medium'>
|
||||
<Label>{t('creator.address', {
|
||||
defaultValue: 'evaluating to address'
|
||||
})}</Label>
|
||||
<Address address={address} />
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('creator.seed', {
|
||||
defaultValue: 'create from the following seed (hex or string)'
|
||||
})}</Label>
|
||||
<Input
|
||||
error={!isSeedValid}
|
||||
name={`${fieldName}_seed`}
|
||||
onChange={this.onChangeSeed}
|
||||
value={seed}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('creator.name', {
|
||||
defaultValue: 'name the account'
|
||||
})}</Label>
|
||||
<Input
|
||||
error={!isNameValid}
|
||||
name={`${fieldName}_name`}
|
||||
onChange={this.onChangeName}
|
||||
value={name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('creator.pass1', {
|
||||
defaultValue: 'encrypt it using the password'
|
||||
})}</Label>
|
||||
<Input
|
||||
action
|
||||
error={!isPassValid}
|
||||
name={`${fieldName}_pass`}
|
||||
onChange={this.onChangePass}
|
||||
type={isPassVisible ? 'text' : 'password'}
|
||||
value={password}
|
||||
>
|
||||
<input />
|
||||
<Button
|
||||
icon='eye'
|
||||
primary
|
||||
onClick={this.togglePassword}
|
||||
/>
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Address
|
||||
className='medium'
|
||||
value={
|
||||
// flowlint-next-line sketchy-null-string:off
|
||||
!address || !publicKey
|
||||
? null
|
||||
: {
|
||||
address,
|
||||
publicKey
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--form'>
|
||||
<div className='medium'>
|
||||
<Label>{t('creator.name', {
|
||||
defaultValue: 'name the account'
|
||||
})}</Label>
|
||||
<Input
|
||||
error={!isNameValid}
|
||||
name={`${fieldName}_name`}
|
||||
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}
|
||||
name={`${fieldName}_pass`}
|
||||
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'>
|
||||
<div className='ui--row-buttons'>
|
||||
<Button
|
||||
negative
|
||||
onClick={this.onDiscard}
|
||||
primary
|
||||
>
|
||||
{t('creator.discard', {
|
||||
defaultValue: 'Reset'
|
||||
@ -148,7 +161,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
|
||||
emptyState (): State {
|
||||
const seed = u8aToHex(randomBytes());
|
||||
const address = addressFromSeed(seed);
|
||||
const { address, publicKey } = infoFromSeed(seed);
|
||||
|
||||
return {
|
||||
address,
|
||||
@ -160,6 +173,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
isValid: false,
|
||||
name: 'new keypair',
|
||||
password: '',
|
||||
publicKey,
|
||||
seed
|
||||
};
|
||||
}
|
||||
@ -169,6 +183,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
(prevState: State, props: Props): $Shape<State> => {
|
||||
const { name = prevState.name, password = prevState.password, seed = prevState.seed } = newState;
|
||||
let address = prevState.address;
|
||||
let publicKey = prevState.publicKey;
|
||||
const isNameValid = !!name;
|
||||
const isSeedValid = isHex(seed)
|
||||
? seed.length === 66
|
||||
@ -176,7 +191,10 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
const isPassValid = password.length > 0 && password.length <= 32;
|
||||
|
||||
if (isSeedValid && seed !== prevState.seed) {
|
||||
address = addressFromSeed(seed);
|
||||
const info = infoFromSeed(seed);
|
||||
|
||||
address = info.address;
|
||||
publicKey = info.publicKey;
|
||||
}
|
||||
|
||||
return {
|
||||
@ -187,6 +205,7 @@ class Creator extends React.PureComponent<Props, State> {
|
||||
isValid: isNameValid && isPassValid && isSeedValid,
|
||||
name,
|
||||
password,
|
||||
publicKey,
|
||||
seed
|
||||
};
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ 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 Address from './Address';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
@ -41,41 +42,52 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
|
||||
render (): React$Node {
|
||||
const { className, style, t } = this.props;
|
||||
const { defaultPublicKey, editedName, isEdited } = this.state;
|
||||
const { defaultPublicKey, currentPair, editedName, isEdited } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['accounts--Editor', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--form'>
|
||||
<InputAddress
|
||||
defaultValue={defaultPublicKey}
|
||||
isInput={false}
|
||||
label={t('editor.select', {
|
||||
defaultValue: 'using my account'
|
||||
})}
|
||||
onChange={this.onChangeAccount}
|
||||
type='account'
|
||||
<div className='ui--grid'>
|
||||
<div className='medium'>
|
||||
<div className='ui--row'>
|
||||
<InputAddress
|
||||
className='full'
|
||||
defaultValue={defaultPublicKey}
|
||||
hideAddress
|
||||
isInput={false}
|
||||
label={t('editor.select', {
|
||||
defaultValue: 'using my account'
|
||||
})}
|
||||
onChange={this.onChangeAccount}
|
||||
type='account'
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('editor.name', {
|
||||
defaultValue: 'identified by the name'
|
||||
})}</Label>
|
||||
<Input
|
||||
onChange={this.onChangeName}
|
||||
value={editedName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Address
|
||||
className='medium'
|
||||
value={{
|
||||
address: currentPair.address(),
|
||||
publicKey: currentPair.publicKey()
|
||||
}}
|
||||
/>
|
||||
</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'>
|
||||
<div className='ui--row-buttons'>
|
||||
<Button
|
||||
disabled={!isEdited}
|
||||
negative
|
||||
onClick={this.onDiscard}
|
||||
primary
|
||||
>
|
||||
{t('editor.reset', {
|
||||
defaultValue: 'Reset'
|
||||
@ -128,7 +140,9 @@ class Editor extends React.PureComponent<Props, State> {
|
||||
onChangeAccount = (publicKey: Uint8Array): void => {
|
||||
const currentPair = keyring.getPair(publicKey);
|
||||
|
||||
this.nextState({ currentPair });
|
||||
this.nextState({
|
||||
currentPair
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
@ -11,25 +11,29 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.accounts--Icon {
|
||||
position: fixed;
|
||||
top: 0.75em;
|
||||
right: 0.75em;
|
||||
}
|
||||
|
||||
.accounts--Address {
|
||||
opacity: 1 !important;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.accounts--Address-icon {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 1rem;
|
||||
display: block
|
||||
}
|
||||
|
||||
.accounts--Address-name {
|
||||
font-family: monospace;
|
||||
padding-left: 3rem;
|
||||
.accounts--Address-balance,
|
||||
.accounts--Address-nonce {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.accounts--Address-data {}
|
||||
|
||||
.accounts--Address-data * {
|
||||
margin: 0 0.25rem !important;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.accounts--Address-address {
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
font-size: 1.5em;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
15
packages/app-addresses/LICENSE
Normal file
15
packages/app-addresses/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/app-addresses/README.md
Normal file
1
packages/app-addresses/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @polkadot/app-addresses
|
||||
17
packages/app-addresses/package.json
Normal file
17
packages/app-addresses/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@polkadot/app-addresses",
|
||||
"version": "0.3.3",
|
||||
"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/app-accounts": "^0.3.3",
|
||||
"@polkadot/ui-react-app": "^0.3.3"
|
||||
}
|
||||
}
|
||||
186
packages/app-addresses/src/Creator.js
Normal file
186
packages/app-addresses/src/Creator.js
Normal file
@ -0,0 +1,186 @@
|
||||
// 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 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 addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
import Address from '@polkadot/app-accounts/src/Address';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
onBack: () => void
|
||||
};
|
||||
|
||||
type State = {
|
||||
address: string | null,
|
||||
publicKey: Uint8Array | null,
|
||||
fieldName: string,
|
||||
isAddressValid: boolean,
|
||||
isNameValid: boolean,
|
||||
isValid: boolean,
|
||||
name: string
|
||||
}
|
||||
|
||||
class Creator extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = this.emptyState();
|
||||
}
|
||||
|
||||
render (): React$Node {
|
||||
const { className, style, t } = this.props;
|
||||
const { address, fieldName, isAddressValid, isNameValid, isValid, name, publicKey } = this.state;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['addresses--Creator', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--grid'>
|
||||
<div className='medium'>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('creator.address', {
|
||||
defaultValue: 'add the following address'
|
||||
})}</Label>
|
||||
<Input
|
||||
error={!isAddressValid}
|
||||
name={`${fieldName}_address`}
|
||||
onChange={this.onChangeAddress}
|
||||
value={address}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('creator.name', {
|
||||
defaultValue: 'name the entry'
|
||||
})}</Label>
|
||||
<Input
|
||||
error={!isNameValid}
|
||||
name={`${fieldName}_name`}
|
||||
onChange={this.onChangeName}
|
||||
value={name}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Address
|
||||
className='medium'
|
||||
value={
|
||||
// flowlint-next-line sketchy-null-string:off
|
||||
!address || !publicKey
|
||||
? null
|
||||
: {
|
||||
address,
|
||||
publicKey
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row-buttons'>
|
||||
<Button
|
||||
onClick={this.onDiscard}
|
||||
>
|
||||
{t('creator.discard', {
|
||||
defaultValue: 'Reset'
|
||||
})}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
onClick={this.onCommit}
|
||||
primary
|
||||
>
|
||||
{t('creator.save', {
|
||||
defaultValue: 'Save'
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
emptyState (): State {
|
||||
return {
|
||||
address: '',
|
||||
fieldName: `field_${Date.now()}`,
|
||||
isAddressValid: false,
|
||||
isNameValid: true,
|
||||
isValid: false,
|
||||
name: 'new address',
|
||||
publicKey: null
|
||||
};
|
||||
}
|
||||
|
||||
nextState (newState: $Shape<State>): void {
|
||||
this.setState(
|
||||
(prevState: State, props: Props): $Shape<State> => {
|
||||
const { address = prevState.address, name = prevState.name } = newState;
|
||||
|
||||
let nextAddress;
|
||||
let publicKey;
|
||||
|
||||
try {
|
||||
// $FlowFixMe we do expect failures... sometimes
|
||||
publicKey = addressDecode(address);
|
||||
nextAddress = addressEncode(publicKey);
|
||||
} catch (error) {
|
||||
nextAddress = void 0;
|
||||
publicKey = void 0;
|
||||
}
|
||||
|
||||
const isNameValid = !!name;
|
||||
const isAddressValid = !!nextAddress;
|
||||
|
||||
return {
|
||||
address: nextAddress,
|
||||
isAddressValid,
|
||||
isNameValid,
|
||||
isValid: isAddressValid && isNameValid,
|
||||
name,
|
||||
publicKey
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onChangeAddress = (event: SyntheticEvent<*>, { value }): void => {
|
||||
this.nextState({ address: value });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onChangeName = (event: SyntheticEvent<*>, { value }): void => {
|
||||
this.nextState({ name: value });
|
||||
}
|
||||
|
||||
onCommit = (): void => {
|
||||
const { onBack } = this.props;
|
||||
const { address, name } = this.state;
|
||||
|
||||
// flowlint-next-line sketchy-null-string:off
|
||||
if (address) {
|
||||
keyring.saveAddress(address, { name });
|
||||
}
|
||||
|
||||
onBack();
|
||||
}
|
||||
|
||||
onDiscard = (): void => {
|
||||
this.setState(this.emptyState());
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Creator);
|
||||
200
packages/app-addresses/src/Editor.js
Normal file
200
packages/app-addresses/src/Editor.js
Normal file
@ -0,0 +1,200 @@
|
||||
// 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 { KeyringAddress } from '@polkadot/ui-keyring/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 Address from '@polkadot/app-accounts/src/Address';
|
||||
import translate from './translate';
|
||||
|
||||
type Props = I18nProps & {
|
||||
onBack: () => void
|
||||
};
|
||||
|
||||
type State = {
|
||||
currentAddress?: KeyringAddress,
|
||||
defaultPublicKey?: Uint8Array,
|
||||
editedName: string,
|
||||
isEdited: boolean,
|
||||
info: KeyringAddress | null
|
||||
}
|
||||
|
||||
class Editor extends React.PureComponent<Props, State> {
|
||||
state: State;
|
||||
|
||||
constructor (props: Props) {
|
||||
super(props);
|
||||
|
||||
const addresses = keyring.getAddresses();
|
||||
const currentAddress = addresses[addresses.length - 1];
|
||||
|
||||
this.state = this.createState(currentAddress);
|
||||
this.state.defaultPublicKey = currentAddress
|
||||
? currentAddress.publicKey()
|
||||
: void 0;
|
||||
}
|
||||
|
||||
render (): React$Node {
|
||||
const { className, style, t } = this.props;
|
||||
const { currentAddress, defaultPublicKey, editedName, isEdited } = this.state;
|
||||
|
||||
if (!currentAddress) {
|
||||
return (
|
||||
<div
|
||||
className={['addresses--Editor', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{t('editor.none', {
|
||||
defaultValue: 'There are no saved addresses. Add some first.'
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['addresses--Editor', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--grid'>
|
||||
<div className='medium'>
|
||||
<div className='ui--row'>
|
||||
<InputAddress
|
||||
className='full'
|
||||
defaultValue={defaultPublicKey}
|
||||
hideAddress
|
||||
isInput={false}
|
||||
label={t('editor.select', {
|
||||
defaultValue: 'edit the selected address'
|
||||
})}
|
||||
onChange={this.onChangeAddress}
|
||||
type='address'
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row'>
|
||||
<div className='full'>
|
||||
<Label>{t('editor.name', {
|
||||
defaultValue: 'identified by the name'
|
||||
})}</Label>
|
||||
<Input
|
||||
onChange={this.onChangeName}
|
||||
value={editedName}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Address
|
||||
className='medium'
|
||||
value={{
|
||||
address: currentAddress.address(),
|
||||
publicKey: currentAddress.publicKey()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className='ui--row-buttons'>
|
||||
<Button
|
||||
disabled={!isEdited}
|
||||
onClick={this.onDiscard}
|
||||
>
|
||||
{t('editor.reset', {
|
||||
defaultValue: 'Reset'
|
||||
})}
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!isEdited}
|
||||
onClick={this.onCommit}
|
||||
primary
|
||||
>
|
||||
{t('editor.save', {
|
||||
defaultValue: 'Save'
|
||||
})}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
createState (currentAddress?: KeyringAddress): $Shape<State> {
|
||||
const { name = '' } = currentAddress
|
||||
? currentAddress.getMeta()
|
||||
: {};
|
||||
|
||||
return {
|
||||
currentAddress,
|
||||
editedName: name,
|
||||
isEdited: false
|
||||
};
|
||||
}
|
||||
|
||||
nextState (newState?: $Shape<State> = {}): void {
|
||||
this.setState(
|
||||
(prevState: State): $Shape<State> => {
|
||||
let { currentAddress = prevState.currentAddress, editedName = prevState.editedName } = newState;
|
||||
const previous = prevState.currentAddress || { address: () => null };
|
||||
|
||||
if (currentAddress && currentAddress.address() !== previous.address()) {
|
||||
editedName = currentAddress.getMeta().name || '';
|
||||
}
|
||||
|
||||
const isEdited = !!currentAddress && editedName !== currentAddress.getMeta().name;
|
||||
|
||||
return {
|
||||
currentAddress,
|
||||
editedName,
|
||||
isEdited
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
onChangeAddress = (publicKey: Uint8Array): void => {
|
||||
const currentAddress = keyring.getAddress(publicKey);
|
||||
|
||||
this.nextState({
|
||||
currentAddress
|
||||
});
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
onChangeName = (event: SyntheticEvent<*>, { value }): void => {
|
||||
this.nextState({ editedName: value });
|
||||
}
|
||||
|
||||
onCommit = (): void => {
|
||||
const { currentAddress, editedName } = this.state;
|
||||
|
||||
if (!currentAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
keyring.saveAddress(currentAddress.address(), {
|
||||
name: editedName,
|
||||
whenEdited: Date.now()
|
||||
});
|
||||
|
||||
this.nextState({});
|
||||
}
|
||||
|
||||
onDiscard = (): void => {
|
||||
const { currentAddress } = this.state;
|
||||
|
||||
if (!currentAddress) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.nextState({
|
||||
editedName: currentAddress.getMeta().name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(Editor);
|
||||
12
packages/app-addresses/src/index.css
Normal file
12
packages/app-addresses/src/index.css
Normal file
@ -0,0 +1,12 @@
|
||||
/* 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. */
|
||||
|
||||
.addresses--App {
|
||||
padding: 1em 2em;
|
||||
}
|
||||
|
||||
.addresses--App-navigation {
|
||||
margin-bottom: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
95
packages/app-addresses/src/index.js
Normal file
95
packages/app-addresses/src/index.js
Normal file
@ -0,0 +1,95 @@
|
||||
// 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 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={['addresses--App', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='addresses--App-navigation'>
|
||||
<Button.Group>
|
||||
<Button
|
||||
onClick={this.selectEdit}
|
||||
primary={currentAction === 'edit'}
|
||||
>
|
||||
{t('app.edit', {
|
||||
defaultValue: 'Edit address'
|
||||
})}
|
||||
</Button>
|
||||
<Button.Or
|
||||
text={t('app.or', {
|
||||
defaultValue: 'or'
|
||||
})}
|
||||
/>
|
||||
<Button
|
||||
onClick={this.selectCreate}
|
||||
primary={currentAction === 'create'}
|
||||
>
|
||||
{t('app.create', {
|
||||
defaultValue: 'Add address'
|
||||
})}
|
||||
</Button>
|
||||
</Button.Group>
|
||||
</div>
|
||||
<Component 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);
|
||||
8
packages/app-addresses/src/translate.js
Normal file
8
packages/app-addresses/src/translate.js
Normal 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(['addresses', 'ui']);
|
||||
@ -43,7 +43,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['extrinsics--Account', 'ui--form', className].join(' ')}
|
||||
className={['extrinsics--Account', 'ui--row', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='large'>
|
||||
|
||||
@ -47,19 +47,17 @@ export default class ExtrinsicDisplay extends React.PureComponent<Props, State>
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['extrinsics--Extrinsic', 'ui--form', className].join(' ')}
|
||||
className={['extrinsics--Extrinsic', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='full'>
|
||||
<InputExtrinsic
|
||||
defaultValue={defaultValue}
|
||||
isError={isError}
|
||||
isPrivate={isPrivate}
|
||||
labelMethod={labelMethod}
|
||||
labelSection={labelSection}
|
||||
onChange={this.onChangeExtrinsic}
|
||||
/>
|
||||
</div>
|
||||
<InputExtrinsic
|
||||
defaultValue={defaultValue}
|
||||
isError={isError}
|
||||
isPrivate={isPrivate}
|
||||
labelMethod={labelMethod}
|
||||
labelSection={labelSection}
|
||||
onChange={this.onChangeExtrinsic}
|
||||
/>
|
||||
<Params
|
||||
item={extrinsic}
|
||||
onChange={this.onChangeValues}
|
||||
|
||||
@ -19,7 +19,7 @@ type Props = BareProps & {
|
||||
export default function Nonce ({ className, label, style, onChange, value }: Props): React$Node {
|
||||
return (
|
||||
<div
|
||||
className={['ui--form', className].join(' ')}
|
||||
className={['ui--row', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='small'>
|
||||
|
||||
@ -73,7 +73,7 @@ class Selection extends React.PureComponent<Props, State> {
|
||||
onChange={this.onChangeNonce}
|
||||
value={publicKey}
|
||||
/>
|
||||
<div className='ui--form-buttons'>
|
||||
<div className='ui--row-buttons'>
|
||||
<Button
|
||||
disabled={!isValid}
|
||||
onClick={this.onQueue}
|
||||
|
||||
@ -67,7 +67,7 @@ class Unlock extends React.PureComponent<Props, State> {
|
||||
className={['extrinsics--Signer-Unlock', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='expanded ui--form'>
|
||||
<div className='expanded ui--row'>
|
||||
<div className='medium'>
|
||||
<Label>{t('unlock.password', {
|
||||
defaultValue: 'unlock account using'
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
|
||||
.storage--Query {
|
||||
margin-bottom: 0.25em;
|
||||
padding: 0.25em 0.75em;
|
||||
padding: 0.25em 0;
|
||||
}
|
||||
|
||||
.storage--Query .ui.disabled.dropdown.selection {
|
||||
@ -24,6 +24,10 @@
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
|
||||
.storage--Query .storage--actionrow-value {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.storage--Query .ui--IdentityIcon {
|
||||
margin: -10px 0;
|
||||
vertical-align: middle;
|
||||
@ -40,5 +44,5 @@
|
||||
|
||||
.storage--actionrow-button {
|
||||
flex: 0;
|
||||
padding-left: 0.5rem;
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@
|
||||
"devDependencies": {
|
||||
"@polkadot/ui-react-app": "^0.3.3",
|
||||
"chalk": "^2.4.1",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"yargs": "10.1.2"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
import type { BareProps } from '@polkadot/ui-react-app/types';
|
||||
|
||||
import React from 'react';
|
||||
import CopyToClipboard from 'react-copy-to-clipboard';
|
||||
import Button from 'semantic-ui-react/dist/es/elements/Button';
|
||||
import IdentityIcon from '@polkadot/ui-react/IdentityIcon';
|
||||
import u8aToHex from '@polkadot/util/u8a/toHex';
|
||||
@ -21,6 +22,7 @@ type Props = BareProps & {
|
||||
export default function Match ({ address, className, count, offset, onRemove, seed, style }: Props): React$Node {
|
||||
const _onRemove = (): void =>
|
||||
onRemove(address);
|
||||
const hexSeed = u8aToHex(seed);
|
||||
|
||||
return (
|
||||
<div
|
||||
@ -33,18 +35,29 @@ export default function Match ({ address, className, count, offset, onRemove, se
|
||||
size={48}
|
||||
value={address}
|
||||
/>
|
||||
<div className='vanity--Match-addr'>
|
||||
<span className='no'>{address.slice(0, offset)}</span><span className='yes'>{address.slice(offset, count + offset)}</span><span className='no'>{address.slice(count + offset)}</span>
|
||||
<div className='vanity--Match-data'>
|
||||
<div className='vanity--Match-addr'>
|
||||
<span className='no'>{address.slice(0, offset)}</span><span className='yes'>{address.slice(offset, count + offset)}</span><span className='no'>{address.slice(count + offset)}</span>
|
||||
</div>
|
||||
<div className='vanity--Match-seed'>
|
||||
{hexSeed}
|
||||
</div>
|
||||
</div>
|
||||
<div className='vanity--Match-seed'>
|
||||
{u8aToHex(seed)}
|
||||
<div className='vanity--Match-buttons'>
|
||||
<CopyToClipboard text={hexSeed}>
|
||||
<Button
|
||||
icon='copy'
|
||||
primary
|
||||
size='tiny'
|
||||
/>
|
||||
</CopyToClipboard>
|
||||
<Button
|
||||
icon='close'
|
||||
negative
|
||||
onClick={_onRemove}
|
||||
size='tiny'
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
className='vanity--Match-remove'
|
||||
icon='close'
|
||||
negative
|
||||
onClick={_onRemove}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -34,7 +34,7 @@ let total = 0;
|
||||
let indicator = -1;
|
||||
|
||||
if (!matchRegex.test(match)) {
|
||||
console.error("Invalid character found in match string, allowed is '1-9' (no '0'), 'A-N & P-Z' (no 'O'), 'a-k & m-z' (no 'l') and '?' (wildcard)");
|
||||
console.error("Invalid character found in match string, allowed is '1-9' (no '0'), 'A-H, J-N & P-Z' (no 'I' or 'O'), 'a-k & m-z' (no 'l') and '?' (wildcard)");
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,6 @@
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
// @flow
|
||||
|
||||
// NOTE bs58 characters, no '0', 'O' or 'l'
|
||||
const regex = new RegExp('^[1-9A-NP-Za-km-z?]*$', '');
|
||||
const regex = new RegExp('^[1-9A-HJ-NP-Za-km-z]*$', '');
|
||||
|
||||
module.exports = regex;
|
||||
|
||||
@ -27,20 +27,21 @@
|
||||
.vanity--Match-item {
|
||||
display: inline-block;
|
||||
font-family: monospace;
|
||||
margin: 0.5em auto;
|
||||
margin: 0 auto;
|
||||
padding: 0.5em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vanity--Match-icon {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 1rem;
|
||||
.vanity--Match-icon,
|
||||
.vanity--Match-data,
|
||||
.vanity--Match-buttons {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.vanity--Match-addr,
|
||||
.vanity--Match-seed {
|
||||
padding: 0 5rem;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.vanity--Match-addr {
|
||||
@ -54,12 +55,6 @@
|
||||
color: red;
|
||||
}
|
||||
|
||||
.vanity--Match-remove {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.vanity--Match-seed {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
className={['vanity--App', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--form'>
|
||||
<div className='ui--row'>
|
||||
<div className='medium'>
|
||||
<Label>{t('vanity.matching', {
|
||||
defaultValue: 'generate address containing (? wildcard)'
|
||||
@ -104,17 +104,16 @@ class App extends React.PureComponent<Props, State> {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='ui--form-buttons'>
|
||||
<div className='ui--row-buttons'>
|
||||
<Button
|
||||
disabled={!isMatchValid}
|
||||
negative={isRunning}
|
||||
primary
|
||||
primary={!isRunning}
|
||||
onClick={this.toggleStart}
|
||||
>
|
||||
{
|
||||
isRunning
|
||||
? t('vanity.stop', { defaultValue: 'Stop' })
|
||||
: t('vanity.start', { defaultValue: 'Start' })
|
||||
? t('vanity.stop', { defaultValue: 'Stop generation' })
|
||||
: t('vanity.start', { defaultValue: 'Start generation' })
|
||||
}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
@ -15,6 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@polkadot/app-accounts": "^0.3.3",
|
||||
"@polkadot/app-addresses": "^0.3.3",
|
||||
"@polkadot/app-explorer": "^0.3.3",
|
||||
"@polkadot/app-extrinsics": "^0.3.3",
|
||||
"@polkadot/app-storage": "^0.3.3",
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
.apps--Connecting-text {
|
||||
background: red;
|
||||
color: white;
|
||||
display: inline-block;
|
||||
line-height: 1.5em;
|
||||
padding: 1em 2em;
|
||||
}
|
||||
|
||||
@ -29,7 +29,7 @@ function Connecting ({ className, style, value, t }: Props): React$Node {
|
||||
>
|
||||
<div className='apps--Connecting-text'>
|
||||
{t('connecting.disconnected', {
|
||||
defaultValue: 'You are disconnected from the node. Check that your node is running and that the Websocket endpoint is reachable.'
|
||||
defaultValue: 'You are not connected to a node. Ensure that your node is running and that the Websocket endpoint is reachable.'
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
21
packages/apps/src/routing/addresses.js
Normal file
21
packages/apps/src/routing/addresses.js
Normal 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 Addresses from '@polkadot/app-addresses/src';
|
||||
|
||||
export default ([
|
||||
{
|
||||
component: Addresses,
|
||||
i18n: {
|
||||
defaultValue: 'Addresses'
|
||||
},
|
||||
icon: 'address book',
|
||||
isExact: false,
|
||||
isHidden: false,
|
||||
name: 'addresses'
|
||||
}
|
||||
]: Routes);
|
||||
@ -6,7 +6,7 @@
|
||||
import type { Routing } from '../types';
|
||||
|
||||
import accounts from './accounts';
|
||||
// import addresses from './addresses';
|
||||
import addresses from './addresses';
|
||||
import explorer from './explorer';
|
||||
import extrinsics from './extrinsics';
|
||||
// import home from './home';
|
||||
@ -17,6 +17,6 @@ import vanitygen from './vanitygen';
|
||||
export default ({
|
||||
default: 'explorer',
|
||||
routes: [].concat(
|
||||
explorer, extrinsics, storage, null, accounts, vanitygen
|
||||
explorer, extrinsics, storage, null, accounts, addresses, vanitygen
|
||||
)
|
||||
}: Routing);
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
import type { KeyringPair, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
import type { State } from '../types';
|
||||
|
||||
import saveAccount from './account';
|
||||
import saveAccount from './save';
|
||||
|
||||
export default function accountCreate (state: State, seed: Uint8Array, password?: string, meta?: KeyringPair$Meta = {}): KeyringPair {
|
||||
const pair = state.keyring.addFromSeed(seed, meta);
|
||||
16
packages/ui-keyring/src/address/all.js
Normal file
16
packages/ui-keyring/src/address/all.js
Normal file
@ -0,0 +1,16 @@
|
||||
// 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 { KeyringAddress, State } from '../types';
|
||||
|
||||
import get from './get';
|
||||
|
||||
export default function all (state: State): Array<KeyringAddress> {
|
||||
return Object
|
||||
.keys(state.available.address)
|
||||
.map((address) =>
|
||||
get(state, address)
|
||||
);
|
||||
}
|
||||
27
packages/ui-keyring/src/address/get.js
Normal file
27
packages/ui-keyring/src/address/get.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 { KeyringAddress, KeyringJson$Meta, State } from '../types';
|
||||
|
||||
import isString from '@polkadot/util/is/string';
|
||||
import addressDecode from '@polkadot/util-keyring/address/decode';
|
||||
import addressEncode from '@polkadot/util-keyring/address/encode';
|
||||
|
||||
export default function get (state: State, _address: string | Uint8Array): KeyringAddress {
|
||||
const address = isString(_address)
|
||||
// flowlint-next-line unclear-type:off
|
||||
? ((_address: any): string)
|
||||
: addressEncode(_address);
|
||||
const publicKey = addressDecode(address);
|
||||
|
||||
return {
|
||||
address: (): string =>
|
||||
address,
|
||||
publicKey: (): Uint8Array =>
|
||||
publicKey,
|
||||
getMeta: (): KeyringJson$Meta =>
|
||||
state.available.address[address].meta
|
||||
};
|
||||
}
|
||||
33
packages/ui-keyring/src/address/meta.js
Normal file
33
packages/ui-keyring/src/address/meta.js
Normal file
@ -0,0 +1,33 @@
|
||||
// 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, State } from '../types';
|
||||
|
||||
import store from 'store';
|
||||
|
||||
import { addressKey } from '../defaults';
|
||||
import createOptions from '../options';
|
||||
|
||||
export default function saveMeta (state: State, address: string, meta: KeyringPair$Meta): void {
|
||||
const json = state.available[address] || {
|
||||
address,
|
||||
meta: {
|
||||
isRecent: void 0,
|
||||
whenCreated: Date.now()
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(meta).forEach((key) => {
|
||||
json.meta[key] = meta[key];
|
||||
});
|
||||
|
||||
delete json.meta.isRecent;
|
||||
|
||||
store.set(addressKey(address), json);
|
||||
state.available.address[address] = (json: $Shape<KeyringJson>);
|
||||
|
||||
createOptions(state);
|
||||
}
|
||||
@ -3,7 +3,6 @@
|
||||
// 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';
|
||||
@ -4,15 +4,18 @@
|
||||
// @flow
|
||||
|
||||
import type { KeyringPair, KeyringPair$Meta } from '@polkadot/util-keyring/types';
|
||||
import type { KeyringInstance, KeyringOption$Type, KeyringOption, KeyringOptions, State } from './types';
|
||||
import type { KeyringAddress, KeyringInstance, KeyringOption$Type, KeyringOption, KeyringOptions, State } from './types';
|
||||
|
||||
import testKeyring from '@polkadot/util-keyring/testing';
|
||||
|
||||
import loadAll from './loadAll';
|
||||
import createAccount from './save/accountCreate';
|
||||
import saveAccount from './save/account';
|
||||
import saveAccountMeta from './save/accountMeta';
|
||||
import saveRecent from './save/recent';
|
||||
import createAccount from './account/create';
|
||||
import saveAccount from './account/save';
|
||||
import saveAccountMeta from './account/meta';
|
||||
import getAddress from './address/get';
|
||||
import getAddresses from './address/all';
|
||||
import saveAddress from './address/meta';
|
||||
import saveRecent from './address/metaRecent';
|
||||
|
||||
const state: State = {
|
||||
available: {
|
||||
@ -26,20 +29,26 @@ const state: State = {
|
||||
loadAll(state);
|
||||
|
||||
export default ({
|
||||
createAccount: (seed: Uint8Array, password?: string, meta?: KeyringPair$Meta): KeyringPair =>
|
||||
createAccount(state, seed, password, meta),
|
||||
getAddress: (address: string | Uint8Array): KeyringAddress =>
|
||||
getAddress(state, address),
|
||||
getAddresses: (): Array<KeyringAddress> =>
|
||||
getAddresses(state),
|
||||
getOptions: (type: KeyringOption$Type): KeyringOptions =>
|
||||
state.options[type],
|
||||
getPair: (address: string | Uint8Array): KeyringPair =>
|
||||
state.keyring.getPair(address),
|
||||
getPairs: (): Array<KeyringPair> =>
|
||||
state.keyring.getPairs(),
|
||||
getOptions: (type: KeyringOption$Type): KeyringOptions =>
|
||||
state.options[type],
|
||||
loadAll: (): void =>
|
||||
loadAll(state),
|
||||
createAccount: (seed: Uint8Array, password?: string, meta?: KeyringPair$Meta): KeyringPair =>
|
||||
createAccount(state, seed, password, meta),
|
||||
saveAccount: (pair: KeyringPair, password?: string): void =>
|
||||
saveAccount(state, pair, password),
|
||||
saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta): void =>
|
||||
saveAccountMeta(state, pair, meta),
|
||||
saveAddress: (address: string, meta: KeyringPair$Meta): void =>
|
||||
saveAddress(state, address, meta),
|
||||
saveRecent: (address: string): KeyringOption =>
|
||||
saveRecent(state, address)
|
||||
}: KeyringInstance);
|
||||
|
||||
@ -25,18 +25,26 @@ export type KeyringOption = KeyringOption$Data & {
|
||||
|
||||
export type KeyringOptions = Array<KeyringOption>;
|
||||
|
||||
export type KeyringJson$Meta = {
|
||||
isRecent?: boolean,
|
||||
isTesting?: boolean,
|
||||
name?: string,
|
||||
whenCreated?: number,
|
||||
whenEdited?: number,
|
||||
whenUsed?: number
|
||||
};
|
||||
|
||||
export type KeyringJson = {
|
||||
address: string,
|
||||
meta: {
|
||||
isRecent?: boolean,
|
||||
isTesting?: boolean,
|
||||
name?: string,
|
||||
whenCreated?: number,
|
||||
whenEdited?: number,
|
||||
whenUsed?: number
|
||||
}
|
||||
meta: KeyringJson$Meta
|
||||
};
|
||||
|
||||
export type KeyringAddress = {
|
||||
address: () => string,
|
||||
publicKey: () => Uint8Array,
|
||||
getMeta: () => KeyringJson$Meta
|
||||
}
|
||||
|
||||
export type State = {
|
||||
available: {
|
||||
account: { [string]: KeyringJson },
|
||||
@ -49,12 +57,15 @@ export type State = {
|
||||
};
|
||||
|
||||
export type KeyringInstance = {
|
||||
createAccount: (seed: Uint8Array, password?: string, meta?: KeyringPair$Meta) => KeyringPair,
|
||||
getAddress: (address: string | Uint8Array) => KeyringAddress,
|
||||
getAddresses: () => Array<KeyringAddress>,
|
||||
getOptions: (type: KeyringOption$Type) => KeyringOptions,
|
||||
getPair: (address: string | Uint8Array) => KeyringPair,
|
||||
getPairs: () => Array<KeyringPair>,
|
||||
loadAll: () => void,
|
||||
createAccount: (seed: Uint8Array, password?: string, meta?: KeyringPair$Meta) => KeyringPair,
|
||||
saveAccount: (pair: KeyringPair, password?: string) => void,
|
||||
saveAccountMeta: (pair: KeyringPair, meta: KeyringPair$Meta) => void,
|
||||
saveAddress: (address: string, meta: KeyringPair$Meta) => void,
|
||||
saveRecent: (address: string) => KeyringOption
|
||||
};
|
||||
|
||||
@ -4,10 +4,15 @@
|
||||
|
||||
.ui--InputAddress {}
|
||||
|
||||
.ui--InputAddress > .text {
|
||||
.ui--InputAddress .ui.dropdown .text {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui--InputAddress input.search {
|
||||
margin-left: 3rem;
|
||||
}
|
||||
|
||||
.flag--hideAddress .ui--KeyPair .ui--KeyPair-address {
|
||||
flex: 0;
|
||||
max-width: 0;
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ import RxDropdown from '../RxDropdown';
|
||||
|
||||
type Props = BareProps & {
|
||||
defaultValue?: Uint8Array,
|
||||
hideAddress?: boolean;
|
||||
isError?: boolean,
|
||||
isInput?: boolean,
|
||||
label?: string,
|
||||
@ -57,13 +58,13 @@ export default class InputAddress extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
render (): React$Node {
|
||||
const { className, isError, label, onChange, style, type = 'all' } = this.props;
|
||||
const { className, hideAddress = false, isError, label, onChange, style, type = 'all' } = this.props;
|
||||
const { defaultValue } = this.state;
|
||||
const options = keyring.getOptions(type);
|
||||
|
||||
return (
|
||||
<RxDropdown
|
||||
className={['ui--InputAddress', className].join(' ')}
|
||||
className={['ui--InputAddress', hideAddress ? 'flag--hideAddress' : '', className].join(' ')}
|
||||
defaultValue={defaultValue}
|
||||
isError={isError}
|
||||
label={label}
|
||||
|
||||
@ -49,7 +49,7 @@ class InputExtrinsic extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['ui--RxDropdownLinked', 'ui--form', className].join(' ')}
|
||||
className={['ui--RxDropdownLinked', 'ui--row', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='small'>
|
||||
|
||||
@ -47,7 +47,7 @@ class InputStorage extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['ui--RxDropdownLinked', 'ui--form', className].join(' ')}
|
||||
className={['ui--RxDropdownLinked', 'ui--row', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='small'>
|
||||
|
||||
@ -18,7 +18,7 @@ type Props = BareProps & {
|
||||
export default function Base ({ children, className, label, size = 'medium', style }: Props): React$Node {
|
||||
return (
|
||||
<div
|
||||
className={['ui--form', className].join(' ')}
|
||||
className={['ui--row', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className={size}>
|
||||
|
||||
@ -69,7 +69,7 @@ class Params extends React.PureComponent<Props, State> {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['ui--Params', 'ui--form', className].join(' ')}
|
||||
className={['ui--Params', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
<div className='ui--Params-Content'>
|
||||
|
||||
@ -34,17 +34,23 @@ export default function RxDropdown (props: Props): React$Node {
|
||||
delete _props.onChange;
|
||||
delete _props.transform;
|
||||
|
||||
return [
|
||||
<Label key='label'>
|
||||
{props.label || ''}
|
||||
</Label>,
|
||||
<Dropdown
|
||||
key='dropdown'
|
||||
selection
|
||||
{..._props}
|
||||
className={props.className}
|
||||
error={props.isError}
|
||||
onChange={_onChange}
|
||||
/>
|
||||
];
|
||||
return (
|
||||
<div
|
||||
className={['ui--Dropdown', props.className].join(' ')}
|
||||
style={props.style}
|
||||
>
|
||||
<Label key='label'>
|
||||
{props.label || ''}
|
||||
</Label>,
|
||||
<Dropdown
|
||||
key='dropdown'
|
||||
selection
|
||||
{..._props}
|
||||
className={null}
|
||||
error={props.isError}
|
||||
onChange={_onChange}
|
||||
style={{}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -2,11 +2,13 @@
|
||||
/* This software may be modified and distributed under the terms
|
||||
/* of the ISC license. See the LICENSE file for details. */
|
||||
|
||||
.ui--form {
|
||||
.ui--grid,
|
||||
.ui--row {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui--form {
|
||||
.ui--grid,
|
||||
.ui--row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
@ -14,45 +16,59 @@
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.ui--form.embedded {
|
||||
margin-right: -0.5rem;
|
||||
}
|
||||
|
||||
.ui--form > div {
|
||||
.ui--grid > div,
|
||||
.ui--row > div {
|
||||
box-sizing: border-box;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ui--form > div.full:not(:first-child),
|
||||
.ui--form > div.large:not(:first-child),
|
||||
.ui--form > div.medium:not(:first-child),
|
||||
.ui--form > div.small:not(:first-child) {
|
||||
padding-left: 0.5rem;
|
||||
}
|
||||
|
||||
.ui--form > div.full {
|
||||
.ui--grid > div,
|
||||
.ui--grid > div.full,
|
||||
.ui--row > div.full {
|
||||
flex: 0 100%;
|
||||
}
|
||||
|
||||
.ui--form > div.large {
|
||||
.ui--grid > div.large,
|
||||
.ui--row > div.large {
|
||||
flex: 0 75%;
|
||||
}
|
||||
|
||||
.ui--form > div.medium {
|
||||
.ui--grid > div.medium,
|
||||
.ui--row > div.medium {
|
||||
flex: 0 50%;
|
||||
}
|
||||
|
||||
.ui--form > div.small {
|
||||
.ui--grid > div.small,
|
||||
.ui--row > div.small {
|
||||
flex: 0 25%;
|
||||
}
|
||||
|
||||
.ui--form-buttons {
|
||||
margin: 1em 0;
|
||||
text-align: right;
|
||||
.ui--grid > div.sixty6,
|
||||
.ui--row > div.sixty6 {
|
||||
flex: 0 66.66%;
|
||||
}
|
||||
|
||||
.ui--form-buttons .button {
|
||||
margin: 0 0 0 0.5rem;
|
||||
.ui--grid > div.thirty3,
|
||||
.ui--row > div.thirty3 {
|
||||
flex: 0 33.33%;
|
||||
}
|
||||
|
||||
.ui--row > div.full,
|
||||
.ui--row > div.large,
|
||||
.ui--row > div.medium,
|
||||
.ui--row > div.small,
|
||||
.ui--row > div.sixty6,
|
||||
.ui--row > div.thirty3 {
|
||||
padding: 0 0.25rem;
|
||||
}
|
||||
|
||||
.ui--row-buttons {
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ui--row-buttons .button {
|
||||
margin: 0 0.25rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@ -40,6 +40,17 @@
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.ui.modal > .actions {
|
||||
background: transparent;
|
||||
border-top: none;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ui.modal > .actions .button {
|
||||
margin: 0 0.25rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ui.page.modals.transition.visible {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
@ -18,7 +18,7 @@ export type I18nProps = BareProps & {
|
||||
|
||||
export type BaseContext = {
|
||||
api: RxApiInterface,
|
||||
// TODO: Set the correct/full type
|
||||
// TODO: Set the correct type
|
||||
router: {
|
||||
route: {
|
||||
location: Location
|
||||
|
||||
19
yarn.lock
19
yarn.lock
@ -3558,6 +3558,12 @@ copy-descriptor@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
|
||||
|
||||
copy-to-clipboard@^3:
|
||||
version "3.0.8"
|
||||
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
|
||||
dependencies:
|
||||
toggle-selection "^1.0.3"
|
||||
|
||||
core-js@^1.0.0:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||
@ -8631,7 +8637,7 @@ promise@^7.1.1:
|
||||
dependencies:
|
||||
asap "~2.0.3"
|
||||
|
||||
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.6.0:
|
||||
prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0:
|
||||
version "15.6.1"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca"
|
||||
dependencies:
|
||||
@ -8783,6 +8789,13 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.1.7:
|
||||
minimist "^1.2.0"
|
||||
strip-json-comments "~2.0.1"
|
||||
|
||||
react-copy-to-clipboard@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-copy-to-clipboard/-/react-copy-to-clipboard-5.0.1.tgz#8eae107bb400be73132ed3b6a7b4fb156090208e"
|
||||
dependencies:
|
||||
copy-to-clipboard "^3"
|
||||
prop-types "^15.5.8"
|
||||
|
||||
react-dev-utils@6.0.0-next.66cc7a90:
|
||||
version "6.0.0-next.66cc7a90"
|
||||
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-6.0.0-next.66cc7a90.tgz#9d5fb27615454a94b448c679e4a79ea1fe716fba"
|
||||
@ -10452,6 +10465,10 @@ to-regex@^3.0.1, to-regex@^3.0.2:
|
||||
regex-not "^1.0.2"
|
||||
safe-regex "^1.1.0"
|
||||
|
||||
toggle-selection@^1.0.3:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||
|
||||
toposort@^1.0.0:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user