* 0.32

* Speed improvements for derivation (#397)

* Speed improvements for derivation

* Cleanups

* Remove obsolete test

* Menu adjustments
This commit is contained in:
Jaco Greeff 2020-07-28 10:48:46 +02:00 committed by GitHub
parent d7ea176469
commit bb4f355ef6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 136 additions and 117 deletions

View File

@ -1,6 +1,6 @@
# CHANGELOG
## 0.32.0-beta.x
## 0.32.1 Jul 27, 2020
- Add Kulupu to the chain lock dropdown (Thanks to https://github.com/carumusan)
- Minor README updates (Thanks to https://github.com/marceljay)

View File

@ -9,5 +9,5 @@
"packages": [
"packages/*"
],
"version": "0.32.0-beta.10"
"version": "0.32.0"
}

View File

@ -2,7 +2,7 @@
"repository": "https://github.com/polkadot-js/extension",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"private": true,
"workspaces": [
"packages/*"

View File

@ -1,13 +1,13 @@
{
"name": "@polkadot/extension-base",
"description": "Functions, classes and other utilities used in @polkadot/extension",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/api": "^1.26.1",
"@polkadot/extension-inject": "0.32.0-beta.10",
"@polkadot/extension-inject": "0.32.0",
"@polkadot/keyring": "^3.0.1",
"@polkadot/ui-keyring": "^0.57.3"
}

View File

@ -1,7 +1,7 @@
{
"name": "@polkadot/extension-chains",
"description": "Definitions for all known chains as exposed by the extension.",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"dependencies": {

View File

@ -1,12 +1,12 @@
{
"name": "@polkadot/extension-dapp",
"description": "Provides an interfaces around the injected globals for ease of access by dapp developers.",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/extension-inject": "0.32.0-beta.10",
"@polkadot/extension-inject": "0.32.0",
"@polkadot/util": "^3.0.1",
"@polkadot/util-crypto": "^3.0.1"
},

View File

@ -1,7 +1,7 @@
{
"name": "@polkadot/extension-inject",
"description": "A generic injector (usable to any extension), that populates the base exposed interfaces to be used by dapps.",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"dependencies": {

View File

@ -1,13 +1,13 @@
{
"name": "@polkadot/extension-ui",
"description": "A sample signer extension for the @polkadot/api",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/extension-base": "0.32.0-beta.10",
"@polkadot/extension-inject": "0.32.0-beta.10",
"@polkadot/extension-base": "0.32.0",
"@polkadot/extension-inject": "0.32.0",
"@polkadot/keyring": "^3.0.1",
"@polkadot/react-identicon": "^0.57.3",
"@polkadot/react-qr": "^0.57.3",

View File

@ -26,7 +26,6 @@ export default function CreateAccount (): React.ReactElement {
// this should always be the case
if (name && password && account) {
setIsBusy(true);
createAccountSuri(name, password, account.seed)
.then((): void => onAction('/'))
.catch((error: Error): void => {

View File

@ -21,16 +21,17 @@ interface Props {
function AddressDropdown ({ allAddresses, className, onSelect, selectedAddress }: Props): React.ReactElement<Props> {
const [isDropdownVisible, setDropdownVisible] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const hideDropdown = useCallback(() => setDropdownVisible(false), []);
const toggleDropdown = useCallback(() => setDropdownVisible(!isDropdownVisible), [isDropdownVisible]);
const selectParent = (newParent: string) => (): void => onSelect(newParent);
useOutsideClick(ref, hideDropdown);
const _hideDropdown = useCallback(() => setDropdownVisible(false), []);
const _toggleDropdown = useCallback(() => setDropdownVisible(!isDropdownVisible), [isDropdownVisible]);
const _selectParent = useCallback((newParent: string) => () => onSelect(newParent), [onSelect]);
useOutsideClick(ref, _hideDropdown);
return (
<div className={className}>
<div
onClick={toggleDropdown}
onClick={_toggleDropdown}
ref={ref}
>
<Address address={selectedAddress} />
@ -40,7 +41,7 @@ function AddressDropdown ({ allAddresses, className, onSelect, selectedAddress }
<div
data-parent-option
key={address}
onClick={selectParent(address)}
onClick={_selectParent(address)}
>
<Address address={address} />
</div>

View File

@ -2,63 +2,49 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ThemeProps } from '../types';
import { ThemeProps } from '../../types';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import lockIcon from '../assets/lock.svg';
import unlockIcon from '../assets/unlock.svg';
import { InputWithLabel, Svg, ValidatedInput, Button } from '../components';
import { validateDerivationPath } from '../messaging';
import { Result } from '../util/validators';
import lockIcon from '../../assets/lock.svg';
import unlockIcon from '../../assets/unlock.svg';
import { InputWithLabel, Svg, Button } from '../../components';
interface Props {
className?: string;
onChange: (derivedAccount: { address: string; suri: string } | null) => void;
defaultPath: string;
isError: boolean;
onChange: (suri: string) => void;
parentAddress: string;
parentPassword: string;
defaultPath: string;
}
function DerivationPath ({ className, defaultPath, onChange, parentAddress, parentPassword }: Props): React.ReactElement<Props> {
function DerivationPath ({ className, defaultPath, isError, onChange }: Props): React.ReactElement<Props> {
const [path, setPath] = useState<string>(defaultPath);
const [isDisabled, setIsDisabled] = useState(false);
const isPathValid = useCallback(async (newPath: string): Promise<Result<string>> => {
try {
await validateDerivationPath(parentAddress, newPath, parentPassword);
return Result.ok(newPath);
} catch (error) {
return Result.error(newPath === path ? '' : 'Invalid derivation path');
}
}, [path, parentAddress, parentPassword]);
const _onExpand = useCallback(() => setIsDisabled(!isDisabled), [isDisabled]);
const _onChange = useCallback(async (newPath: string | null) => {
newPath !== null && setPath(newPath);
onChange(newPath === null ? null : await validateDerivationPath(parentAddress, newPath, parentPassword));
}, [parentAddress, onChange, parentPassword]);
const _onChange = useCallback((newPath: string): void => {
setPath(newPath);
onChange(newPath);
}, [onChange]);
return (
<div className={className}>
<div className='container'>
<PathInput isUnlocked={isDisabled}>
<ValidatedInput
component={InputWithLabel}
<InputWithLabel
data-input-suri
defaultValue={defaultPath}
disabled={!isDisabled}
isError={isError || !path}
label={
isDisabled
? 'Derivation Path'
: 'Derivation Path (unlock to edit)'
}
onValidatedChange={_onChange}
onChange={_onChange}
placeholder='//hard/soft'
validator={isPathValid}
value={path}
/>
</PathInput>

View File

@ -120,16 +120,6 @@ describe('Derive', () => {
expect(button.prop('disabled')).not.toBe(true);
});
it('derivation path input is not present until password is correct', async () => {
const { wrapper } = await mountComponent();
expect(wrapper.exists('[data-input-suri]')).toBe(false);
await type(wrapper.find('input[type="password"]'), 'pass');
expect(wrapper.exists('[data-input-suri]')).toBe(true);
await type(wrapper.find('input[type="password"]'), 'pass2');
expect(wrapper.exists('[data-input-suri]')).toBe(false);
});
it('"Create derived account" is disabled when suri is incorrect', async () => {
const { wrapper } = await mountComponent();

View File

@ -4,12 +4,13 @@
import React, { useCallback, useContext, useEffect, useRef, useState } from 'react';
import styled from 'styled-components';
import { assert } from '@polkadot/util';
import { AccountContext, ActionContext, Address, ButtonArea, Checkbox, InputWithLabel, Label, NextStepButton, VerticalSpace } from '../../components';
import { validateAccount } from '../../messaging';
import { DerivationPath } from '../../partials';
import { validateAccount, validateDerivationPath } from '../../messaging';
import { nextDerivationPath } from '../../util/nextDerivationPath';
import AddressDropdown from './AddressDropdown';
import DerivationPath from './DerivationPath';
interface Props {
isLocked?: boolean;
@ -19,8 +20,10 @@ interface Props {
export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress }: Props): React.ReactElement<Props> {
const onAction = useContext(ActionContext);
const [isBusy, setIsBusy] = useState(false);
const { accounts, hierarchy } = useContext(AccountContext);
const [account, setAccount] = useState<null | { address: string; suri: string }>(null);
const [defaultPath] = useState(nextDerivationPath(accounts, parentAddress));
const [suriPath, setSuriPath] = useState<null | string>(defaultPath);
const [parentPassword, setParentPassword] = useState<string>('');
const [isProperParentPassword, setIsProperParentPassword] = useState(false);
const [shouldAccountBeDerived, setShouldAccountBeDerived] = useState(true);
@ -33,11 +36,11 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress }
);
const _onParentPasswordEnter = useCallback(
async (enteredPassword: string) => {
setParentPassword(enteredPassword);
setIsProperParentPassword(await validateAccount(parentAddress, enteredPassword));
(parentPassword: string): void => {
setParentPassword(parentPassword);
setIsProperParentPassword(!!parentPassword);
},
[parentAddress]
[]
);
const _onParentChange = useCallback(
@ -46,8 +49,30 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress }
);
const _onSubmit = useCallback(
() => parentPassword && account && onDerivationConfirmed({ account, parentPassword }),
[account, parentPassword, onDerivationConfirmed]
async (): Promise<void> => {
if (suriPath && parentAddress && parentPassword) {
setIsBusy(true);
const isUnlockable = await validateAccount(parentAddress, parentPassword);
if (isUnlockable) {
try {
const account = await validateDerivationPath(parentAddress, suriPath, parentPassword);
assert(account, 'Unable to derive');
console.log(account);
onDerivationConfirmed({ account, parentPassword });
} catch (error) {
setSuriPath(null);
}
} else {
setIsProperParentPassword(false);
}
}
},
[parentAddress, parentPassword, onDerivationConfirmed, suriPath]
);
useEffect(() => {
@ -68,17 +93,18 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress }
/>
)}
<DisableableArea isDisabled={!shouldAccountBeDerived}>
{isLocked ? (
<Address address={parentAddress} />
) : (
<Label label='Choose Parent Account:'>
<AddressDropdown
allAddresses={hierarchy.filter(({ isExternal }) => !isExternal).map(({ address }) => address)}
onSelect={_onParentChange}
selectedAddress={parentAddress}
/>
</Label>
)}
{isLocked
? <Address address={parentAddress} />
: (
<Label label='Choose Parent Account:'>
<AddressDropdown
allAddresses={hierarchy.filter(({ isExternal }) => !isExternal).map(({ address }) => address)}
onSelect={_onParentChange}
selectedAddress={parentAddress}
/>
</Label>
)
}
<div ref={passwordInputRef}>
<InputWithLabel
data-export-password
@ -92,8 +118,8 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress }
</div>
{isProperParentPassword && (
<DerivationPath
defaultPath={nextDerivationPath(accounts, parentAddress)}
onChange={setAccount}
defaultPath={defaultPath}
onChange={setSuriPath}
parentAddress={parentAddress}
parentPassword={parentPassword}
/>
@ -105,7 +131,8 @@ export function SelectParent ({ isLocked, onDerivationConfirmed, parentAddress }
? (
<NextStepButton
data-button-action='create derived account'
isDisabled={!isProperParentPassword || account === null}
isBusy={isBusy}
isDisabled={!isProperParentPassword || !suriPath}
onClick={_onSubmit}
>
Create a derived account

View File

@ -42,7 +42,6 @@ function Derive ({ isLocked }: Props): React.ReactElement<Props> {
}
setIsBusy(true);
deriveAccount(parentAddress, account.suri, parentPassword, name, password)
.then(() => onAction('/'))
.catch((error): void => {

View File

@ -258,7 +258,8 @@ const ArrowLabel = styled(Svg).attrs(() => ({ src: parentArrow }))`
`;
const MovableMenu = styled(Menu)<{ isMoved: boolean }>`
${({ isMoved }): string => isMoved ? 'bottom: 0;' : ''}
${({ isMoved }): string => isMoved ? 'bottom: 0;' : 'top: 0;'}
margin-top: -20px;
right: 28px;
`;

View File

@ -19,12 +19,7 @@ interface Props extends ThemeProps {
to?: string;
}
const STYLE: { position: 'relative'; width: string } = {
position: 'relative',
width: '100%'
};
function Button ({ children, className, isBusy, isDisabled, onClick, to }: Props): React.ReactElement<Props> {
function Button ({ children, className = '', isBusy, isDisabled, onClick, to }: Props): React.ReactElement<Props> {
const _onClick = (): void => {
if (isBusy || isDisabled) {
return;
@ -38,20 +33,21 @@ function Button ({ children, className, isBusy, isDisabled, onClick, to }: Props
};
return (
<div style={STYLE}>
<button
className={className}
disabled={isDisabled || isBusy}
onClick={_onClick}
>
{children}
</button>
{isBusy && <Spinner />}
</div>
<button
className={`${className}${(isDisabled || isBusy) ? ' isDisabled' : ''}${isBusy ? ' isBusy' : ''}`}
disabled={isDisabled || isBusy}
onClick={_onClick}
>
{children}
<div className='disabledOverlay' />
<Spinner className='busyOverlay' />
</button>
);
}
export default styled(Button)(({ isDanger, theme }: Props) => `
background: ${isDanger ? theme.buttonBackgroundDanger : theme.buttonBackground};
cursor: pointer;
display: block;
width: 100%;
height: ${isDanger ? '40px' : '48px'};
@ -63,16 +59,37 @@ export default styled(Button)(({ isDanger, theme }: Props) => `
font-weight: 800;
line-height: 20px;
padding: 0 1rem;
position: relative;
text-align: center;
background: ${isDanger ? theme.buttonBackgroundDanger : theme.buttonBackground};
cursor: pointer;
&:disabled {
opacity: 0.3;
cursor: default;
}
&:not(:disabled):hover {
background: ${isDanger ? theme.buttonBackgroundDangerHover : theme.buttonBackgroundHover};
}
.busyOverlay,
.disabledOverlay {
visibility: hidden;
}
.disabledOverlay {
background: rgba(96,96,96,0.75);
border-radius: ${theme.borderRadius};
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
&.isBusy .busyOverlay {
visibility: visible;
}
&.isDisabled .disabledOverlay {
visibility: visible;
}
`);

View File

@ -3,7 +3,6 @@
// of the Apache-2.0 license. See the LICENSE file for details.
export { default as Back } from './Back';
export { default as DerivationPath } from './DerivationPath';
export { default as Header } from './Header';
export { default as MenuSettings } from './MenuSettings';
export { default as Name } from './Name';

View File

@ -1,14 +1,14 @@
{
"name": "@polkadot/extension",
"description": "A sample signer extension for the @polkadot/api",
"version": "0.32.0-beta.10",
"version": "0.32.0",
"author": "Jaco Greeff <jacogr@gmail.com>",
"license": "Apache-2",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/extension-base": "0.32.0-beta.10",
"@polkadot/extension-inject": "0.32.0-beta.10",
"@polkadot/extension-ui": "0.32.0-beta.10"
"@polkadot/extension-base": "0.32.0",
"@polkadot/extension-inject": "0.32.0",
"@polkadot/extension-ui": "0.32.0"
},
"devDependencies": {
"@polkadot/dev": "^0.55.28",

View File

@ -2956,13 +2956,13 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/extension-base@0.32.0-beta.10, @polkadot/extension-base@workspace:packages/extension-base":
"@polkadot/extension-base@0.32.0, @polkadot/extension-base@workspace:packages/extension-base":
version: 0.0.0-use.local
resolution: "@polkadot/extension-base@workspace:packages/extension-base"
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/api": ^1.26.1
"@polkadot/extension-inject": 0.32.0-beta.10
"@polkadot/extension-inject": 0.32.0
"@polkadot/keyring": ^3.0.1
"@polkadot/ui-keyring": ^0.57.3
languageName: unknown
@ -2983,7 +2983,7 @@ __metadata:
resolution: "@polkadot/extension-dapp@workspace:packages/extension-dapp"
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/extension-inject": 0.32.0-beta.10
"@polkadot/extension-inject": 0.32.0
"@polkadot/util": ^3.0.1
"@polkadot/util-crypto": ^3.0.1
peerDependencies:
@ -2991,7 +2991,7 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/extension-inject@0.32.0-beta.10, @polkadot/extension-inject@workspace:packages/extension-inject":
"@polkadot/extension-inject@0.32.0, @polkadot/extension-inject@workspace:packages/extension-inject":
version: 0.0.0-use.local
resolution: "@polkadot/extension-inject@workspace:packages/extension-inject"
dependencies:
@ -3003,13 +3003,13 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/extension-ui@0.32.0-beta.10, @polkadot/extension-ui@workspace:packages/extension-ui":
"@polkadot/extension-ui@0.32.0, @polkadot/extension-ui@workspace:packages/extension-ui":
version: 0.0.0-use.local
resolution: "@polkadot/extension-ui@workspace:packages/extension-ui"
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/extension-base": 0.32.0-beta.10
"@polkadot/extension-inject": 0.32.0-beta.10
"@polkadot/extension-base": 0.32.0
"@polkadot/extension-inject": 0.32.0
"@polkadot/keyring": ^3.0.1
"@polkadot/react-identicon": ^0.57.3
"@polkadot/react-qr": ^0.57.3
@ -3043,9 +3043,9 @@ __metadata:
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/dev": ^0.55.28
"@polkadot/extension-base": 0.32.0-beta.10
"@polkadot/extension-inject": 0.32.0-beta.10
"@polkadot/extension-ui": 0.32.0-beta.10
"@polkadot/extension-base": 0.32.0
"@polkadot/extension-inject": 0.32.0
"@polkadot/extension-ui": 0.32.0
babel-loader: ^8.1.0
extract-loader: ^5.1.0
file-loader: ^6.0.0