JSON v3 support (#3256)

* JSON v3 support

* Update deps

* isBusy adjustments

* Cleanup import

* Linting

* trigger

* Derive fixed, remove unused styles

* Backup adjustments

* Overlay & spinner ordering
This commit is contained in:
Jaco Greeff 2020-07-24 10:17:14 +02:00 committed by GitHub
parent 9eea9c759c
commit 51bceafb03
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 360 additions and 299 deletions

View File

@ -1,2 +1,2 @@
10
0.45.2
11
0.45.2

View File

@ -35,10 +35,10 @@
"resolutions": {
"@polkadot/api": "^1.26.0-beta.1",
"@polkadot/api-contract": "^1.26.0-beta.1",
"@polkadot/keyring": "^2.18.1",
"@polkadot/keyring": "^3.0.0-beta.3",
"@polkadot/types": "^1.26.0-beta.1",
"@polkadot/util": "^2.18.1",
"@polkadot/util-crypto": "^2.18.1",
"@polkadot/util": "^3.0.0-beta.3",
"@polkadot/util-crypto": "^3.0.0-beta.3",
"@polkadot/wasm-crypto": "^1.2.1",
"babel-core": "^7.0.0-bridge.0"
},

View File

@ -149,7 +149,7 @@ function createWebpack (ENV, context) {
...mapChunks('polkadot', [
/* 00 */ /node_modules\/@polkadot\/(wasm)/,
/* 01 */ /node_modules\/(@polkadot\/(api|metadata|rpc|types))/,
/* 02 */ /node_modules\/(@polkadot\/(extension|keyring|react|ui|util|vanitygen)|@edgeware|@ledgerhq|@zondax|edgeware)/
/* 02 */ /node_modules\/(@polkadot\/(extension|keyring|react|ui|util|vanitygen)|@acala-network|@edgeware|@ledgerhq|@open-web3|@zondax|edgeware)/
]),
...mapChunks('react', [
/* 00 */ /node_modules\/(@fortawesome)/,

View File

@ -3,7 +3,7 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import FileSaver from 'file-saver';
import React, { useCallback, useMemo, useState } from 'react';
import React, { useCallback, useState } from 'react';
import { AddressRow, Button, Modal, Password } from '@polkadot/react-components';
import keyring from '@polkadot/ui-keyring';
@ -14,52 +14,42 @@ interface Props {
address: string;
}
interface ContentProps {
address: string;
doBackup: () => void;
isPassTouched: boolean;
isPassValid: boolean;
onChangePass: (password: string) => void;
password: string;
}
function Backup ({ address, onClose }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [password, setPassword] = useState('');
const [isPassTouched, setIsPassTouched] = useState(false);
const [isBusy, setIsBusy] = useState(false);
const [{ isPassTouched, password }, setPassword] = useState({ isPassTouched: false, password: '' });
const [backupFailed, setBackupFailed] = useState(false);
const isPassValid = useMemo(() =>
keyring.isPassValid(password) && !backupFailed,
[password, backupFailed]);
const isPassValid = !backupFailed && keyring.isPassValid(password);
const _onChangePass = useCallback(
(value: string): void => {
if (!isPassTouched) {
setIsPassTouched(true);
}
(password: string): void => {
setBackupFailed(false);
setPassword(value);
setPassword({ isPassTouched: true, password });
},
[isPassTouched]
[]
);
const _doBackup = useCallback(
(): void => {
try {
const addressKeyring = address && keyring.getPair(address);
const json = addressKeyring && keyring.backupAccount(addressKeyring, password);
const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
setIsBusy(true);
setTimeout((): void => {
try {
const addressKeyring = address && keyring.getPair(address);
const json = addressKeyring && keyring.backupAccount(addressKeyring, password);
const blob = new Blob([JSON.stringify(json)], { type: 'application/json; charset=utf-8' });
FileSaver.saveAs(blob, `${address}.json`);
} catch (error) {
setBackupFailed(true);
console.error(error);
FileSaver.saveAs(blob, `${address}.json`);
} catch (error) {
setBackupFailed(true);
setIsBusy(false);
console.error(error);
return;
}
return;
}
onClose();
setIsBusy(false);
onClose();
}, 0);
},
[address, onClose, password]
);
@ -69,18 +59,33 @@ function Backup ({ address, onClose }: Props): React.ReactElement<Props> {
className='app--accounts-Modal'
header={t<string>('Backup account')}
>
<Content
address={address}
doBackup={_doBackup}
isPassTouched={isPassTouched}
isPassValid={isPassValid}
onChangePass={_onChangePass}
password={password}
/>
<Modal.Content>
<AddressRow
isInline
value={address}
>
<p>{t<string>('An encrypted backup file will be created once you have pressed the "Download" button. This can be used to re-import your account on any other machine.')}</p>
<p>{t<string>('Save this backup file in a secure location. Additionally, the password associated with this account is needed together with this backup file in order to restore your account.')}</p>
<div>
<Password
autoFocus
help={t<string>('The account password as specified when creating the account. This is used to encrypt the backup file and subsequently decrypt it when restoring the account.')}
isError={isPassTouched && !isPassValid}
label={t<string>('password')}
onChange={_onChangePass}
onEnter={_doBackup}
tabIndex={0}
value={password}
/>
</div>
</AddressRow>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<Button
icon='download'
isBusy={isBusy}
isDisabled={!isPassValid}
isPrimary
label={t<string>('Download')}
onClick={_doBackup}
/>
@ -89,32 +94,4 @@ function Backup ({ address, onClose }: Props): React.ReactElement<Props> {
);
}
function Content ({ address, doBackup, isPassTouched, isPassValid, onChangePass, password }: ContentProps): React.ReactElement<ContentProps> {
const { t } = useTranslation();
return (
<Modal.Content>
<AddressRow
isInline
value={address}
>
<p>{t<string>('An encrypted backup file will be created once you have pressed the "Download" button. This can be used to re-import your account on any other machine.')}</p>
<p>{t<string>('Save this backup file in a secure location. Additionally, the password associated with this account is needed together with this backup file in order to restore your account.')}</p>
<div>
<Password
autoFocus
help={t<string>('The account password as specified when creating the account. This is used to encrypt the backup file and subsequently decrypt it when restoring the account.')}
isError={isPassTouched && !isPassValid}
label={t<string>('password')}
onChange={onChangePass}
onEnter={doBackup}
tabIndex={0}
value={password}
/>
</div>
</AddressRow>
</Modal.Content>
);
}
export default React.memo(Backup);

View File

@ -26,6 +26,7 @@ interface OldPass {
function ChangePass ({ address, className = '', onClose }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isBusy, setIsBusy] = useState(false);
const [newPass1, setNewPass1] = useState<NewPass>({ isValid: false, password: '' });
const [newPass2, setNewPass2] = useState<NewPass>({ isValid: false, password: '' });
const [{ isOldValid, oldPass }, setOldPass] = useState<OldPass>({ isOldValid: false, oldPass: '' });
@ -43,8 +44,7 @@ function ChangePass ({ address, className = '', onClose }: Props): React.ReactEl
);
const _onChangeOld = useCallback(
(oldPass: string) =>
setOldPass({ isOldValid: keyring.isPassValid(oldPass), oldPass }),
(oldPass: string) => setOldPass({ isOldValid: keyring.isPassValid(oldPass), oldPass }),
[]
);
@ -56,27 +56,33 @@ function ChangePass ({ address, className = '', onClose }: Props): React.ReactEl
return;
}
try {
if (!account.isLocked) {
account.lock();
setIsBusy(true);
setTimeout((): void => {
try {
if (!account.isLocked) {
account.lock();
}
account.decodePkcs8(oldPass);
} catch (error) {
setOldPass((state: OldPass) => ({ ...state, isOldValid: false }));
setIsBusy(false);
return;
}
account.decodePkcs8(oldPass);
} catch (error) {
setOldPass((state: OldPass) => ({ ...state, isOldValid: false }));
try {
keyring.encryptAccount(account, newPass1.password);
} catch (error) {
setNewPass2((state: NewPass) => ({ ...state, isValid: false }));
setIsBusy(false);
return;
}
return;
}
try {
keyring.encryptAccount(account, newPass1.password);
} catch (error) {
setNewPass2((state: NewPass) => ({ ...state, isValid: false }));
return;
}
onClose();
setIsBusy(false);
onClose();
}, 0);
},
[address, newPass1, oldPass, onClose]
);
@ -126,6 +132,7 @@ function ChangePass ({ address, className = '', onClose }: Props): React.ReactEl
<Modal.Actions onCancel={onClose}>
<Button
icon='sign-in-alt'
isBusy={isBusy}
isDisabled={!newPass1.isValid || !newPass2.isValid || !isOldValid}
isPrimary
label={t<string>('Change')}

View File

@ -168,6 +168,7 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
const { api, isDevelopment } = useApi();
const [{ address, deriveError, derivePath, isSeedValid, pairType, seed, seedType }, setAddress] = useState<AddressState>(generateSeed(propsSeed, '', propsSeed ? 'raw' : 'bip', propsType));
const [isConfirmationOpen, toggleConfirmation] = useToggle();
const [isBusy, setIsBusy] = useState(false);
const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
const [{ isPasswordValid, password }, setPassword] = useState({ isPasswordValid: false, password: '' });
const isValid = !!address && !deriveError && isNameValid && isPasswordValid && isSeedValid;
@ -220,12 +221,16 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
return;
}
const options = { genesisHash: isDevelopment ? undefined : api.genesisHash.toString(), name: name.trim() };
const status = createAccount(`${seed}${derivePath}`, pairType, options, password, t<string>('created account'));
setIsBusy(true);
setTimeout((): void => {
const options = { genesisHash: isDevelopment ? undefined : api.genesisHash.toString(), name: name.trim() };
const status = createAccount(`${seed}${derivePath}`, pairType, options, password, t<string>('created account'));
toggleConfirmation();
onStatusChange(status);
onClose();
toggleConfirmation();
onStatusChange(status);
setIsBusy(false);
onClose();
}, 0);
},
[api, derivePath, isDevelopment, isValid, name, onClose, onStatusChange, pairType, password, seed, t, toggleConfirmation]
);
@ -239,6 +244,7 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
{address && isConfirmationOpen && (
<CreateConfirmation
address={address}
isBusy={isBusy}
name={name}
onClose={toggleConfirmation}
onCommit={_onCommit}
@ -355,6 +361,7 @@ function Create ({ className = '', onClose, onStatusChange, seed: propsSeed, typ
<Modal.Actions onCancel={onClose}>
<Button
icon='plus'
isBusy={isBusy}
isDisabled={!isValid}
isPrimary
label={t<string>('Save')}

View File

@ -9,12 +9,13 @@ import { useTranslation } from '../../translate';
interface Props {
address: string;
isBusy: boolean;
name: string;
onClose: () => void;
onCommit: () => void;
}
function CreateConfirmation ({ address, name, onClose, onCommit }: Props): React.ReactElement<Props> | null {
function CreateConfirmation ({ address, isBusy, name, onClose, onCommit }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
return (
@ -33,6 +34,7 @@ function CreateConfirmation ({ address, name, onClose, onCommit }: Props): React
<Modal.Actions onCancel={onClose}>
<Button
icon='plus'
isBusy={isBusy}
isPrimary
label={t<string>('Create and backup account')}
onClick={onCommit}

View File

@ -78,13 +78,14 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
const { api, isDevelopment } = useApi();
const { queueAction } = useContext(StatusContext);
const [source] = useState(keyring.getPair(from));
const [isBusy, setIsBusy] = useState(false);
const [{ address, deriveError }, setDerive] = useState<DeriveAddress>({ address: null, deriveError: null });
const [isConfirmationOpen, toggleConfirmation] = useToggle();
const [{ isLocked, lockedError }, setIsLocked] = useState<LockState>({ isLocked: source.isLocked, lockedError: null });
const [{ isNameValid, name }, setName] = useState({ isNameValid: false, name: '' });
const [{ isPassValid, password }, setPassword] = useState({ isPassValid: false, password: '' });
const [{ isPass2Valid, password2 }, setPassword2] = useState({ isPass2Valid: false, password2: '' });
const [rootPass, setRootPass] = useState('');
const [{ isRootValid, rootPass }, setRootPass] = useState({ isRootValid: false, rootPass: '' });
const [suri, setSuri] = useState('');
const debouncedSuri = useDebounce(suri);
const isValid = !!address && !deriveError && isNameValid && isPassValid && isPass2Valid;
@ -123,15 +124,28 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
[password]
);
const _onChangeRootPass = useCallback(
(rootPass: string): void => {
setRootPass({ isRootValid: !!rootPass, rootPass });
setIsLocked(({ isLocked }) => ({ isLocked, lockedError: null }));
},
[]
);
const _onUnlock = useCallback(
(): void => {
try {
source.decodePkcs8(rootPass);
setIsLocked({ isLocked: source.isLocked, lockedError: null });
} catch (error) {
console.error(error);
setIsLocked({ isLocked: true, lockedError: (error as Error).message });
}
setIsBusy(true);
setTimeout((): void => {
try {
source.decodePkcs8(rootPass);
setIsLocked({ isLocked: source.isLocked, lockedError: null });
} catch (error) {
console.error(error);
setIsLocked({ isLocked: true, lockedError: (error as Error).message });
}
setIsBusy(false);
}, 0);
},
[rootPass, source]
);
@ -142,11 +156,15 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
return;
}
const status = createAccount(source, suri, name, password, t<string>('created account'), isDevelopment ? undefined : api.genesisHash.toString());
setIsBusy(true);
setTimeout((): void => {
const status = createAccount(source, suri, name, password, t<string>('created account'), isDevelopment ? undefined : api.genesisHash.toString());
toggleConfirmation();
queueAction(status);
onClose();
toggleConfirmation();
queueAction(status);
setIsBusy(false);
onClose();
}, 0);
},
[api, isDevelopment, isValid, name, onClose, password, queueAction, source, suri, t, toggleConfirmation]
);
@ -168,6 +186,7 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
{address && isConfirmationOpen && (
<CreateConfirmation
address={address}
isBusy={isBusy}
name={name}
onClose={toggleConfirmation}
onCommit={_onCommit}
@ -182,7 +201,7 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
help={t<string>('The password to unlock the selected account.')}
isError={!!lockedError}
label={t<string>('password')}
onChange={setRootPass}
onChange={_onChangeRootPass}
value={rootPass}
/>
</>
@ -237,7 +256,8 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
? (
<Button
icon='lock'
isDisabled={!rootPass}
isBusy={isBusy}
isDisabled={!isRootValid}
isPrimary
label={t<string>('Unlock')}
onClick={_onUnlock}
@ -246,6 +266,7 @@ function Derive ({ className = '', from, onClose }: Props): React.ReactElement {
: (
<Button
icon='plus'
isBusy={isBusy}
isDisabled={!isValid}
isPrimary
label={t<string>('Save')}

View File

@ -8,7 +8,7 @@ import { ModalProps } from '../../types';
import React, { useCallback, useState } from 'react';
import { AddressRow, Button, InputAddress, InputFile, Modal, Password } from '@polkadot/react-components';
import { isHex, isObject, u8aToString } from '@polkadot/util';
import { isObject, u8aToString } from '@polkadot/util';
import keyring from '@polkadot/ui-keyring';
import { useTranslation } from '../../translate';
@ -37,7 +37,7 @@ function parseFile (file: Uint8Array): FileState {
const json = JSON.parse(u8aToString(file)) as KeyringPair$Json;
const publicKey = keyring.decodeAddress(json.address, true);
const address = keyring.encodeAddress(publicKey);
const isFileValid = publicKey.length === 32 && isHex(json.encoded) && isObject(json.meta) && (
const isFileValid = publicKey.length === 32 && !!json.encoded && isObject(json.meta) && (
Array.isArray(json.encoding.content)
? json.encoding.content[0] === 'pkcs8'
: json.encoding.content === 'pkcs8'
@ -53,18 +53,17 @@ function parseFile (file: Uint8Array): FileState {
function Import ({ className = '', onClose, onStatusChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const [isBusy, setIsBusy] = useState(false);
const [{ address, isFileValid, json }, setFile] = useState<FileState>({ address: null, isFileValid: false, json: null });
const [{ isPassValid, password }, setPass] = useState<PassState>({ isPassValid: false, password: '' });
const _onChangeFile = useCallback(
(file: Uint8Array): void =>
setFile(parseFile(file)),
(file: Uint8Array) => setFile(parseFile(file)),
[]
);
const _onChangePass = useCallback(
(password: string): void =>
setPass({ isPassValid: keyring.isPassValid(password), password }),
(password: string) => setPass({ isPassValid: keyring.isPassValid(password), password }),
[]
);
@ -74,30 +73,34 @@ function Import ({ className = '', onClose, onStatusChange }: Props): React.Reac
return;
}
const status: Partial<ActionStatus> = { action: 'restore' };
setIsBusy(true);
setTimeout((): void => {
const status: Partial<ActionStatus> = { action: 'restore' };
try {
const pair = keyring.restoreAccount(json, password);
const { address } = pair;
try {
const pair = keyring.restoreAccount(json, password);
const { address } = pair;
status.status = pair ? 'success' : 'error';
status.account = address;
status.message = t<string>('account restored');
status.status = pair ? 'success' : 'error';
status.account = address;
status.message = t<string>('account restored');
InputAddress.setLastValue('account', address);
} catch (error) {
setPass((state: PassState) => ({ ...state, isPassValid: false }));
InputAddress.setLastValue('account', address);
} catch (error) {
setPass((state: PassState) => ({ ...state, isPassValid: false }));
status.status = 'error';
status.message = (error as Error).message;
console.error(error);
}
status.status = 'error';
status.message = (error as Error).message;
console.error(error);
}
onStatusChange(status as ActionStatus);
setIsBusy(false);
onStatusChange(status as ActionStatus);
if (status.status !== 'error') {
onClose();
}
if (status.status !== 'error') {
onClose();
}
}, 0);
},
[json, onClose, onStatusChange, password, t]
);
@ -155,6 +158,7 @@ function Import ({ className = '', onClose, onStatusChange }: Props): React.Reac
<Modal.Actions onCancel={onClose}>
<Button
icon='sync'
isBusy={isBusy}
isDisabled={!isFileValid || !isPassValid}
isPrimary
label={t<string>('Restore')}

View File

@ -136,7 +136,6 @@ function Sidebar ({ address, className = '', onClose, onUpdateName }: Props): Re
<Button
className='ui--AddressMenu-button'
icon='ban'
isAnimated
isNegative={isHoveringButton}
isPositive={!isHoveringButton}
label={t<string>('Remove')}

View File

@ -108,7 +108,7 @@ function VanityApp ({ className = '', onStatusChange }: Props): React.ReactEleme
return _checkMatches();
}
setImmediate((): void => {
setTimeout((): void => {
if (mountedRef.current) {
if (results.current.length === 25) {
_checkMatches();
@ -120,7 +120,7 @@ function VanityApp ({ className = '', onStatusChange }: Props): React.ReactEleme
_executeGeneration();
}
});
}, 0);
},
[_checkMatches, match, mountedRef, runningRef, type, withCase]
);

View File

@ -17,6 +17,7 @@ interface Props {
function Unlock ({ onClose, onUnlock, pair }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const [isBusy, setIsBusy] = useState(false);
const [address, setAddress] = useState('');
const [password, setPassword] = useState('');
const [unlockError, setUnlockError] = useState<string | null>(null);
@ -35,13 +36,19 @@ function Unlock ({ onClose, onUnlock, pair }: Props): React.ReactElement<Props>
return;
}
try {
pair.decodePkcs8(password);
} catch (error) {
return setUnlockError((error as Error).message);
}
setIsBusy(true);
setTimeout((): void => {
try {
pair.decodePkcs8(password);
} catch (error) {
setIsBusy(false);
onUnlock();
return setUnlockError((error as Error).message);
}
setIsBusy(false);
onUnlock();
}, 0);
},
[onUnlock, pair, password]
);
@ -90,6 +97,7 @@ function Unlock ({ onClose, onUnlock, pair }: Props): React.ReactElement<Props>
<Modal.Actions onCancel={onClose}>
<Button
icon='unlock'
isBusy={isBusy}
isPrimary
label={t<string>('Unlock')}
onClick={_onUnlock}

View File

@ -32,7 +32,7 @@
"dependencies": {
"@babel/runtime": "^7.10.5",
"@polkadot/api": "^1.26.0-beta.1",
"@polkadot/extension-dapp": "^0.32.0-beta.8",
"@polkadot/extension-dapp": "^0.32.0-beta.9",
"rxjs-compat": "^6.6.0"
}
}

View File

@ -106,12 +106,12 @@ export default function withCall<P extends ApiProps> (endpoint: string, {
// The attachment takes time when a lot is available, set a timeout
// to first handle the current queue before subscribing
setImmediate((): void => {
setTimeout((): void => {
this
.subscribe(this.getParams(this.props))
.then(NOOP)
.catch(NOOP);
});
}, 0);
}
public componentWillUnmount (): void {

View File

@ -14,15 +14,15 @@
"@fortawesome/fontawesome-svg-core": "^1.2.30",
"@fortawesome/free-solid-svg-icons": "^5.14.0",
"@fortawesome/react-fontawesome": "^0.1.11",
"@polkadot/keyring": "^2.18.1",
"@polkadot/keyring": "^3.0.0-beta.3",
"@polkadot/react-api": "0.51.0-beta.17",
"@polkadot/react-identicon": "^0.56.1",
"@polkadot/react-qr": "^0.56.1",
"@polkadot/react-identicon": "^0.57.0-beta.1",
"@polkadot/react-qr": "^0.57.0-beta.1",
"@polkadot/react-query": "0.51.0-beta.17",
"@polkadot/ui-keyring": "^0.56.1",
"@polkadot/ui-settings": "^0.56.1",
"@polkadot/util": "^2.18.1",
"@polkadot/util-crypto": "^2.18.1",
"@polkadot/ui-keyring": "^0.57.0-beta.1",
"@polkadot/ui-settings": "^0.57.0-beta.1",
"@polkadot/util": "^3.0.0-beta.3",
"@polkadot/util-crypto": "^3.0.0-beta.3",
"chart.js": "^2.9.3",
"codeflask": "^1.4.1",
"i18next": "^19.6.2",

View File

@ -8,40 +8,42 @@ import React, { useCallback } from 'react';
import styled from 'styled-components';
import Icon from '../Icon';
import Spinner from '../Spinner';
function Button ({ children, className = '', icon, isBasic = false, isCircular = false, isDisabled = false, isFull = false, isIcon, isNegative = false, isPositive = false, isPrimary = false, label, onClick, onMouseEnter, onMouseLeave, tabIndex }: ButtonProps): React.ReactElement<ButtonProps> {
function Button ({ children, className = '', icon, isBasic, isBusy, isCircular, isDisabled, isFull, isIcon, isNegative, isPositive, isPrimary, label, onClick, onMouseEnter, onMouseLeave, tabIndex }: ButtonProps): React.ReactElement<ButtonProps> {
const _onClick = useCallback(
(): void => {
!isDisabled && onClick && onClick();
},
[isDisabled, onClick]
() => !(isBusy || isDisabled) && onClick && onClick(),
[isBusy, isDisabled, onClick]
);
return (
<button
className={`ui--Button${label ? ' hasLabel' : ''}${isBasic ? ' isBasic' : ''}${isCircular ? ' isCircular' : ''}${isFull ? ' isFull' : ''}${isIcon ? ' isIcon' : ''}${isNegative ? ' isNegative' : ''}${isPositive ? ' isPositive' : ''}${isPrimary ? (isBasic ? ' ui--highlight--border' : ' ui--highlight--button') : ''} ${className}`}
disabled={isDisabled}
className={`ui--Button${label ? ' hasLabel' : ''}${isBasic ? ' isBasic' : ''}${isCircular ? ' isCircular' : ''}${isFull ? ' isFull' : ''}${isIcon ? ' isIcon' : ''}${isNegative ? ' isNegative' : ''}${isPositive ? ' isPositive' : ''}${isPrimary ? (isBasic ? ' ui--highlight--border' : ' ui--highlight--button') : ''}${isDisabled ? ' isDisabled' : ''}${isBusy ? ' isBusy' : ''} ${className}`}
onClick={_onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
tabIndex={tabIndex}
>
{icon && <Icon icon={icon} />}
<Icon icon={icon} />
{label}
{children}
<div className='ui--Button-overlay'>
<Spinner
className='ui--Button-spinner'
variant='cover'
/>
</div>
</button>
);
}
export default React.memo(styled(Button)`
border: none;
cursor: pointer;
font-size: 0.92857142857rem; // 13/14px
position: relative;
text-align: center;
&:not(:disabled) {
cursor: pointer;
}
&:not(.hasLabel) {
padding: 0.75em;
@ -55,11 +57,6 @@ export default React.memo(styled(Button)`
border-radius: 0.25rem;
}
&:disabled {
cursor: not-allowed;
opacity: 0.15;
}
&:focus {
outline:0;
}
@ -78,10 +75,18 @@ export default React.memo(styled(Button)`
color: inherit !important;
}
&.isBusy {
cursor: wait;
}
&.isCircular {
border-radius: 10rem;
}
&.isDisabled {
cursor: not-allowed;
}
&.isFull {
display: block;
width: 100%;
@ -90,4 +95,31 @@ export default React.memo(styled(Button)`
&.isIcon {
background: transparent;
}
.ui--Button-spinner {
visibility: hidden;
}
.ui--Button-overlay {
background: rgba(255, 255, 255, 0.75);
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
visibility: hidden;
}
&.isBusy {
.ui--Button-spinner {
visibility: visible;
}
}
&.isBusy,
&.isDisabled {
.ui--Button-overlay {
visibility: visible;
}
}
`);

View File

@ -3,7 +3,6 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { IconName } from '@fortawesome/fontawesome-svg-core';
import { ButtonProps as SUIButtonProps } from 'semantic-ui-react/dist/commonjs/elements/Button/Button';
import { BareProps } from '../types';
export type Button$Callback = () => void | Promise<void>;
@ -11,9 +10,9 @@ export type Button$Callback = () => void | Promise<void>;
export interface ButtonProps {
children?: React.ReactNode;
className?: string;
icon?: IconName;
isAnimated?: SUIButtonProps['animated'];
icon: IconName;
isBasic?: boolean;
isBusy?: boolean;
isCircular?: boolean;
isDisabled?: boolean;
isFull?: boolean;

View File

@ -3,7 +3,6 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import { MAX_PASS_LEN } from '@polkadot/ui-keyring/defaults';
import Input from './Input';
@ -39,7 +38,6 @@ function Password ({ autoFocus, children, className = '', defaultValue, help, is
isFull={isFull}
label={label}
labelExtra={labelExtra}
maxLength={MAX_PASS_LEN}
name={name}
onChange={onChange}
onEnter={onEnter}

View File

@ -11,17 +11,22 @@ import spinnerSrc from './Spinner-2.png';
interface Props {
className?: string;
label?: React.ReactNode;
variant?: 'app' | 'push' | 'mini';
variant?: 'app' | 'cover' | 'push' | 'mini';
}
// prefetch
const img = new Image();
img.src = spinnerSrc as string;
function Spinner ({ className = '', label, variant = 'app' }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
return (
<div className={`${className} ui--Spinner`}>
<div className={`${className} ui--Spinner${variant === 'cover' ? ' isCover' : ''}`}>
<img
className={variant === 'push' ? '' : 'ui--highlight--bg'}
src={spinnerSrc as unknown as string}
className={variant === 'push' ? '' : 'ui--highlight--bg ui--highlight--border'}
src={spinnerSrc as string}
/>
{variant === 'app' && <div className='text'>{label || t('Retrieving data')}</div>}
</div>
@ -34,7 +39,20 @@ export default React.memo(styled(Spinner)`
margin: 0 auto;
text-align: center;
&.isCover {
bottom: 0;
left: 0;
position: absolute;
right: 0;
img {
margin: 0 auto;
opacity: 1;
}
}
img {
border: 1px solid transparent;
border-radius: 10rem;
opacity: 0.75;
}

View File

@ -14,7 +14,7 @@ import Button from './Button';
import { StatusContext } from './Status';
import { useTranslation } from './translate';
function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, isBasic, isDisabled, isIcon, isNegative, isPrimary, isUnsigned, label, onClick, onFailed, onSendRef, onStart, onSuccess, onUpdate, params, tooltip, tx, withSpinner }: Props): React.ReactElement<Props> {
function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon, isBasic, isBusy, isDisabled, isIcon, isNegative, isPrimary, isUnsigned, label, onClick, onFailed, onSendRef, onStart, onSuccess, onUpdate, params, tooltip, tx, withSpinner }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { queueExtrinsic } = useContext(StatusContext);
@ -87,6 +87,7 @@ function TxButton ({ accountId, className = '', extrinsic: propsExtrinsic, icon,
className={className}
icon={icon || 'check'}
isBasic={isBasic}
isBusy={isBusy}
isDisabled={isSending || isDisabled || needsAccount}
isIcon={isIcon}
isNegative={isNegative}

View File

@ -121,17 +121,6 @@ export default createGlobalStyle<Props>`
position: relative;
text-align: left;
&:not(:hover):not(.keepAlive) {
.ui.button:not(.disabled) {
background: #eee !important;
color: #555 !important;
}
.ui.button.show-on-hover {
visibility: hidden;
}
}
> ul {
margin: 0;
padding: 0;

View File

@ -149,14 +149,6 @@ export default css`
opacity: 1;
}
.ui.button:disabled,
.ui.buttons .disabled.button,
.ui.disabled.active.button,
.ui.disabled.button,
.ui.disabled.button:hover {
opacity: 0.2 !important;
}
/* modals aligned to top, not center */
.ui.dimmer {
background-color: rgba(0,0,0,0.5);

View File

@ -57,6 +57,7 @@ export interface TxButtonProps extends TxProps {
className?: string;
icon?: IconName;
isBasic?: boolean;
isBusy?: boolean;
isDisabled?: boolean;
isIcon?: boolean;
isNegative?: boolean;

View File

@ -29,7 +29,7 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re
useEffect((): void => {
// eslint-disable-next-line @typescript-eslint/unbound-method
accountId && extrinsic && isFunction(extrinsic.paymentInfo) && isFunction(api.rpc.payment?.queryInfo) &&
setImmediate((): void => {
setTimeout((): void => {
try {
extrinsic
.paymentInfo(accountId)
@ -38,7 +38,7 @@ function PaymentInfo ({ accountId, className = '', extrinsic }: Props): React.Re
} catch (error) {
console.error((error as Error).message);
}
});
}, 0);
}, [api, accountId, extrinsic, mountedRef]);
if (!dispatchInfo) {

View File

@ -264,8 +264,7 @@ function TxSigned ({ className, currentItem, requestAddress }: Props): React.Rea
const _doStart = useCallback(
(): void => {
setBusy(true);
setImmediate((): void => {
setTimeout((): void => {
if (_unlock()) {
isSubmit
? currentItem.payload
@ -275,7 +274,7 @@ function TxSigned ({ className, currentItem, requestAddress }: Props): React.Rea
} else {
setBusy(false);
}
});
}, 0);
},
[_onSend, _onSendPayload, _onSign, _unlock, currentItem, isSubmit, queueSetTxStatus, senderInfo]
);
@ -338,38 +337,37 @@ function TxSigned ({ className, currentItem, requestAddress }: Props): React.Rea
</ErrorBoundary>
</Modal.Content>
<Modal.Actions onCancel={_onCancel}>
<Button
icon={
flags.isQr
? 'qrcode'
: 'sign-in-alt'
}
isBusy={isBusy}
isDisabled={!senderInfo.signAddress || isRenderError}
isPrimary
label={
flags.isQr
? t<string>('Sign via Qr')
: isSubmit
? t<string>('Sign and Submit')
: t<string>('Sign (no submission)')
}
onClick={_doStart}
tabIndex={2}
/>
{!isBusy && (
<>
<Button
icon={
flags.isQr
? 'qrcode'
: 'sign-in-alt'
}
isDisabled={!senderInfo.signAddress || isRenderError}
isPrimary
label={
flags.isQr
? t<string>('Sign via Qr')
: isSubmit
? t<string>('Sign and Submit')
: t<string>('Sign (no submission)')
}
onClick={_doStart}
tabIndex={2}
/>
<Toggle
className='signToggle'
isDisabled={!!currentItem.payload}
label={
isSubmit
? t<string>('Sign and Submit')
: t<string>('Sign (no submission)')
}
onChange={setIsSubmit}
value={isSubmit}
/>
</>
<Toggle
className='signToggle'
isDisabled={!!currentItem.payload}
label={
isSubmit
? t<string>('Sign and Submit')
: t<string>('Sign (no submission)')
}
onChange={setIsSubmit}
value={isSubmit}
/>
)}
</Modal.Actions>
</>

128
yarn.lock
View File

@ -1354,7 +1354,7 @@ __metadata:
languageName: node
linkType: hard
"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.10.4, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.6":
"@babel/runtime@npm:^7.1.2, @babel/runtime@npm:^7.10.1, @babel/runtime@npm:^7.10.3, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.3.1, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.6":
version: 7.10.5
resolution: "@babel/runtime@npm:7.10.5"
dependencies:
@ -3363,39 +3363,39 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/extension-dapp@npm:^0.32.0-beta.8":
version: 0.32.0-beta.8
resolution: "@polkadot/extension-dapp@npm:0.32.0-beta.8"
"@polkadot/extension-dapp@npm:^0.32.0-beta.9":
version: 0.32.0-beta.9
resolution: "@polkadot/extension-dapp@npm:0.32.0-beta.9"
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/extension-inject": 0.32.0-beta.8
"@polkadot/util": ^2.18.1
"@polkadot/util-crypto": ^2.18.1
"@polkadot/extension-inject": 0.32.0-beta.9
"@polkadot/util": ^3.0.0-beta.2
"@polkadot/util-crypto": ^3.0.0-beta.2
peerDependencies:
"@polkadot/api": "*"
checksum: 3/3391648f0a010ae4cacf5e6c6e32bf4683f1f0aa0b57014e6cc30745a1b11d970f794d82c436fd492ad0f637aef30dd86eb8847b4d9e5324b947c5828bb8ebaf
checksum: 3/70ef177238eff0bcfcb2a15f570fa83646a1684aec25ba20cdf91d84ba888b9c375a7ebac3f3c2a7a20dc60afff54d54108fd8586fe6579fd53b8cd1158d291f
languageName: node
linkType: hard
"@polkadot/extension-inject@npm:0.32.0-beta.8":
version: 0.32.0-beta.8
resolution: "@polkadot/extension-inject@npm:0.32.0-beta.8"
"@polkadot/extension-inject@npm:0.32.0-beta.9":
version: 0.32.0-beta.9
resolution: "@polkadot/extension-inject@npm:0.32.0-beta.9"
dependencies:
"@babel/runtime": ^7.10.5
peerDependencies:
"@polkadot/api": "*"
checksum: 3/3dd34a291c24151f9f2e0f755f59ce3525b4d43d81d2b928a9596c00a713204380eacc2e1bf90e1a1d4ec8b639ec5a3639d5cd960621b237711b119b6c99bb5e
checksum: 3/8536f3cbc74702ee468644767f87c43d6872d26b17c3a363bc2e96d2f775df0292565e7c2378f99760ba5df3535d442d67dfc1aa0ca8236fed4754bdf434bca1
languageName: node
linkType: hard
"@polkadot/keyring@npm:^2.18.1":
version: 2.18.1
resolution: "@polkadot/keyring@npm:2.18.1"
"@polkadot/keyring@npm:^3.0.0-beta.3":
version: 3.0.0-beta.3
resolution: "@polkadot/keyring@npm:3.0.0-beta.3"
dependencies:
"@babel/runtime": ^7.10.4
"@polkadot/util": 2.18.1
"@polkadot/util-crypto": 2.18.1
checksum: 3/293c9a25c33234a85710b10a6a253b76930ba0be7b42825af6bfc19d47579c185133d7b793c3e34ac0de9189563f65949cf08bf8dabf04c02661c675ee87e41d
"@babel/runtime": ^7.10.5
"@polkadot/util": 3.0.0-beta.3
"@polkadot/util-crypto": 3.0.0-beta.3
checksum: 3/38e19ebe85db6a03828481b6302e985a49070ebb934c269e5e0962862015f1aa7db5b8d6573dcfd586b08f36ea7dba86c58a0d763896d87c26a501bb82d9f774
languageName: node
linkType: hard
@ -3419,7 +3419,7 @@ __metadata:
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/api": ^1.26.0-beta.1
"@polkadot/extension-dapp": ^0.32.0-beta.8
"@polkadot/extension-dapp": ^0.32.0-beta.9
rxjs-compat: ^6.6.0
languageName: unknown
linkType: soft
@ -3432,15 +3432,15 @@ __metadata:
"@fortawesome/fontawesome-svg-core": ^1.2.30
"@fortawesome/free-solid-svg-icons": ^5.14.0
"@fortawesome/react-fontawesome": ^0.1.11
"@polkadot/keyring": ^2.18.1
"@polkadot/keyring": ^3.0.0-beta.3
"@polkadot/react-api": 0.51.0-beta.17
"@polkadot/react-identicon": ^0.56.1
"@polkadot/react-qr": ^0.56.1
"@polkadot/react-identicon": ^0.57.0-beta.1
"@polkadot/react-qr": ^0.57.0-beta.1
"@polkadot/react-query": 0.51.0-beta.17
"@polkadot/ui-keyring": ^0.56.1
"@polkadot/ui-settings": ^0.56.1
"@polkadot/util": ^2.18.1
"@polkadot/util-crypto": ^2.18.1
"@polkadot/ui-keyring": ^0.57.0-beta.1
"@polkadot/ui-settings": ^0.57.0-beta.1
"@polkadot/util": ^3.0.0-beta.3
"@polkadot/util-crypto": ^3.0.0-beta.3
"@types/codeflask": ^1.4.1
chart.js: ^2.9.3
codeflask: ^1.4.1
@ -3476,13 +3476,13 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/react-identicon@npm:^0.56.1":
version: 0.56.1
resolution: "@polkadot/react-identicon@npm:0.56.1"
"@polkadot/react-identicon@npm:^0.57.0-beta.1":
version: 0.57.0-beta.1
resolution: "@polkadot/react-identicon@npm:0.57.0-beta.1"
dependencies:
"@babel/runtime": ^7.10.5
"@polkadot/ui-settings": 0.56.1
"@polkadot/ui-shared": 0.56.1
"@polkadot/ui-settings": 0.57.0-beta.1
"@polkadot/ui-shared": 0.57.0-beta.1
color: ^3.1.2
jdenticon: 2.2.0
react-copy-to-clipboard: ^5.0.2
@ -3494,7 +3494,7 @@ __metadata:
react-dom: "*"
react-is: "*"
styled-components: "*"
checksum: 3/6e1ff0c9e3dcf3b588b486cc308e0ec7b33fc41459ef52c126cc286e99df0789cf712563213c7cebf13e880efe9cbea06ddeaf0a2ed91a8c897df53caec269b7
checksum: 3/03ee88220f6f40433d80cda02af636c411cb67faac6256d55ba4144b1b2b473527276d245dfab6307b5b9aa2a2e15359a6c9ca3477534264df67f774f99ebd9b
languageName: node
linkType: hard
@ -3507,9 +3507,9 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/react-qr@npm:^0.56.1":
version: 0.56.1
resolution: "@polkadot/react-qr@npm:0.56.1"
"@polkadot/react-qr@npm:^0.57.0-beta.1":
version: 0.57.0-beta.1
resolution: "@polkadot/react-qr@npm:0.57.0-beta.1"
dependencies:
"@babel/runtime": ^7.10.5
qrcode-generator: ^1.4.4
@ -3520,7 +3520,7 @@ __metadata:
react: "*"
react-dom: "*"
styled-components: "*"
checksum: 3/612ad6a250d3d2aeb93c471f5fadb4972673dafd07e90ca6e56ae70713d71c115bbf1f40837ed18e896bc4e36fb0da9fa1c2000c835a31dadb0eeee1a791b5b5
checksum: 3/3dff1f6993acb7d1ba82c086f39858b3fa4337c1c6dc1470b2f1b5537c5a01fa0d269d852e0862c870c521665a09ea38819886e1d31d2fc0d6c864a73304a9d6
languageName: node
linkType: hard
@ -3610,9 +3610,9 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/ui-keyring@npm:^0.56.1":
version: 0.56.1
resolution: "@polkadot/ui-keyring@npm:0.56.1"
"@polkadot/ui-keyring@npm:^0.57.0-beta.1":
version: 0.57.0-beta.1
resolution: "@polkadot/ui-keyring@npm:0.57.0-beta.1"
dependencies:
"@babel/runtime": ^7.10.5
"@ledgerhq/hw-transport-node-hid": ^5.19.1
@ -3628,42 +3628,42 @@ __metadata:
dependenciesMeta:
"@ledgerhq/hw-transport-node-hid":
optional: true
checksum: 3/96f28f61310e0c23d9534246aa16917d98000eb9abd2c9d95b1ef9b91198d328960f4c1ab8917478e7ba9059be4d24e7c918448710ad72a7796b1e48d7c4f5b6
checksum: 3/55962fac3fbc07d85dcede5fb478d942a31f6dab231c4181f75b5b5f01047fff54d9c6dfa5aee01481d83a74bebb65da0285bfb88e063c542a38a75c29ce17b6
languageName: node
linkType: hard
"@polkadot/ui-settings@npm:0.56.1, @polkadot/ui-settings@npm:^0.56.1":
version: 0.56.1
resolution: "@polkadot/ui-settings@npm:0.56.1"
"@polkadot/ui-settings@npm:0.57.0-beta.1, @polkadot/ui-settings@npm:^0.57.0-beta.1":
version: 0.57.0-beta.1
resolution: "@polkadot/ui-settings@npm:0.57.0-beta.1"
dependencies:
"@babel/runtime": ^7.10.5
eventemitter3: ^4.0.4
store: ^2.0.12
peerDependencies:
"@polkadot/util": "*"
checksum: 3/a40d42823b712cd430386291647475b4b1184d1423431383c2cf2b9570b85ece8af005b72d209bf5851c96e6bb374f51ca6099508ae8c7e66f5e677cbb445e40
checksum: 3/710aedfda14af1dbc7602fdc68e0c121cf6646a27e788d690acec29bd899263acc76dff4fbd1909abe85aabcaf2997e42095445e3ac56f300eda23734a3df1a2
languageName: node
linkType: hard
"@polkadot/ui-shared@npm:0.56.1":
version: 0.56.1
resolution: "@polkadot/ui-shared@npm:0.56.1"
"@polkadot/ui-shared@npm:0.57.0-beta.1":
version: 0.57.0-beta.1
resolution: "@polkadot/ui-shared@npm:0.57.0-beta.1"
dependencies:
"@babel/runtime": ^7.10.5
color: ^3.1.2
peerDependencies:
"@polkadot/util": "*"
"@polkadot/util-crypto": "*"
checksum: 3/ad4f0cd5243f16aa35fdbedb0ba536784c7237ccb5baea4a1e5fd5c615d362efc9feae6e0c0805cd3571ee9609213820716a66340b9dfee287c664e300c9146a
checksum: 3/5c3abd8a6bf6cb8c6fc07f9478e15334c9a243ab63e8ceb527558fdca5fa7749ec764f4721b992de9d3eb4b09d3d6f6535d924c742d9989c84108a8a25871da7
languageName: node
linkType: hard
"@polkadot/util-crypto@npm:^2.18.1":
version: 2.18.1
resolution: "@polkadot/util-crypto@npm:2.18.1"
"@polkadot/util-crypto@npm:^3.0.0-beta.3":
version: 3.0.0-beta.3
resolution: "@polkadot/util-crypto@npm:3.0.0-beta.3"
dependencies:
"@babel/runtime": ^7.10.4
"@polkadot/util": 2.18.1
"@babel/runtime": ^7.10.5
"@polkadot/util": 3.0.0-beta.3
"@polkadot/wasm-crypto": ^1.2.1
base-x: ^3.0.8
bip39: ^3.0.2
@ -3672,23 +3672,24 @@ __metadata:
elliptic: ^6.5.3
js-sha3: ^0.8.0
pbkdf2: ^3.1.1
scryptsy: ^2.1.0
tweetnacl: ^1.0.3
xxhashjs: ^0.2.2
checksum: 3/412d3f1c0d0c318130808cdb6d0097fc0be915412e505d86e38ea7f2e131f0a7277b371bd4a9f41608d5220fc04f71de29409781f75f2a40399490827603f6fd
checksum: 3/cc39d6fc7c693b0a278f6fa8042d9054fffd49931437bc974ba7f1646b212c48b84966b918e0595c851014dd35466815ac886b65f0fea33a032e559086f0cb03
languageName: node
linkType: hard
"@polkadot/util@npm:^2.18.1":
version: 2.18.1
resolution: "@polkadot/util@npm:2.18.1"
"@polkadot/util@npm:^3.0.0-beta.3":
version: 3.0.0-beta.3
resolution: "@polkadot/util@npm:3.0.0-beta.3"
dependencies:
"@babel/runtime": ^7.10.4
"@babel/runtime": ^7.10.5
"@types/bn.js": ^4.11.6
bn.js: ^5.1.2
camelcase: ^5.3.1
chalk: ^4.1.0
ip-regex: ^4.1.0
checksum: 3/df6cb63e7d5cff1f0075e7807b76c6ea26c1a52f887dc20558d8717cde81713fa391cfd17dd82792f4c6271be30f143d3a11c816b7c00eaeea1d411fe5208e71
checksum: 3/93156add0469b8013a424ea25813c2c4f4ab16c1ab6fb20e85a66963f31a77b4a581bde69e711a7e25d167980fddba11ef5e2bd15a041eb7c592961112659789
languageName: node
linkType: hard
@ -20582,6 +20583,13 @@ __metadata:
languageName: node
linkType: hard
"scryptsy@npm:^2.1.0":
version: 2.1.0
resolution: "scryptsy@npm:2.1.0"
checksum: 3/9fd847955caa9d13a6d58a0360ba8fb31f8e7e7da9250b4c09c67db784a0f6dda757bc837313b0860c0e35f84a56ba068394c682dd4fba81c5899d82832aa7d6
languageName: node
linkType: hard
"sdp@npm:^2.12.0, sdp@npm:^2.6.0":
version: 2.12.0
resolution: "sdp@npm:2.12.0"