Store/use parentAddress alongside derivation (#2526)

* Store/use parentAddress alongside derivation

* Show actual parent relationships

* Tooltip for address
This commit is contained in:
Jaco Greeff 2020-04-07 15:04:02 +02:00 committed by GitHub
parent 6bd5c9d817
commit c906794397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 397 additions and 292 deletions

View File

@ -5,10 +5,11 @@
import { DeriveAccountInfo, DeriveBalancesAll } from '@polkadot/api-derive/types';
import { ActionStatus } from '@polkadot/react-components/Status/types';
import { RecoveryConfig } from '@polkadot/types/interfaces';
import { SortedAccount } from './types';
import React, { useCallback, useState, useEffect } from 'react';
import styled from 'styled-components';
import { AddressInfo, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, Input, InputTags, LinkExternal, Menu, Popup, Tag } from '@polkadot/react-components';
import { AddressInfo, AddressMini, AddressSmall, Badge, Button, ChainLock, CryptoType, Forget, Icon, IdentityIcon, Input, InputTags, LinkExternal, Menu, Popup, Tag } from '@polkadot/react-components';
import { useApi, useCall, useToggle } from '@polkadot/react-hooks';
import { Option } from '@polkadot/types';
import keyring from '@polkadot/ui-keyring';
@ -23,22 +24,31 @@ import RecoverAccount from './modals/RecoverAccount';
import RecoverSetup from './modals/RecoverSetup';
import Transfer from './modals/Transfer';
interface Props {
address: string;
interface Props extends SortedAccount {
className?: string;
filter: string;
isFavorite: boolean;
toggleFavorite: (address: string) => void;
}
function Account ({ address, className, filter, isFavorite, toggleFavorite }: Props): React.ReactElement<Props> | null {
function calcVisible (filter: string, name: string, tags: string[]): boolean {
if (filter.length === 0) {
return true;
}
const _filter = filter.toLowerCase();
return tags.reduce((result: boolean, tag: string): boolean => {
return result || tag.toLowerCase().includes(_filter);
}, name.toLowerCase().includes(_filter));
}
function Account ({ account: { address, meta }, className, filter, isFavorite, toggleFavorite }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const api = useApi();
const info = useCall<DeriveAccountInfo>(api.api.derive.accounts.info as any, [address]);
const balancesAll = useCall<DeriveBalancesAll>(api.api.derive.balances.all as any, [address]);
const recoveryInfo = useCall<RecoveryConfig | null>(api.api.query.recovery?.recoverable, [address], {
transform: (opt: Option<RecoveryConfig>): RecoveryConfig | null =>
opt.unwrapOr(null)
transform: (opt: Option<RecoveryConfig>) => opt.unwrapOr(null)
});
const [tags, setTags] = useState<string[]>([]);
const [accName, setAccName] = useState('');
@ -88,23 +98,16 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
}, [address, _setTags]);
useEffect((): void => {
if (filter.length === 0) {
setIsVisible(true);
} else {
const _filter = filter.toLowerCase();
setIsVisible(
tags.reduce((result: boolean, tag: string): boolean => {
return result || tag.toLowerCase().includes(_filter);
}, accName.toLowerCase().includes(_filter))
);
}
setIsVisible(
calcVisible(filter, accName, tags)
);
}, [accName, filter, tags]);
const _onFavorite = useCallback(
(): void => toggleFavorite(address),
[address, toggleFavorite]
);
const _onGenesisChange = useCallback(
(genesisHash: string | null): void => {
const account = keyring.getPair(address);
@ -115,6 +118,7 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
},
[address]
);
const _saveName = useCallback(
(): void => {
toggleEditName();
@ -133,6 +137,7 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
},
[accName, address, toggleEditName]
);
const _saveTags = useCallback(
(): void => {
toggleEditTags();
@ -151,6 +156,7 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
},
[address, tags, toggleEditTags]
);
const _onForget = useCallback(
(): void => {
if (!address) {
@ -306,6 +312,11 @@ function Account ({ address, className, filter, isFavorite, toggleFavorite }: Pr
/>
)}
</td>
<td className='address'>
{meta.parentAddress && (
<AddressMini value={meta.parentAddress} />
)}
</td>
<td className='number'>
<CryptoType accountId={address} />
</td>

View File

@ -4,6 +4,7 @@
import { KeyringAddress } from '@polkadot/ui-keyring/types';
import { ComponentProps as Props } from '../types';
import { SortedAccount } from './types';
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
@ -19,8 +20,6 @@ import QrModal from './modals/Qr';
import Account from './Account';
import Banner from './Banner';
type SortedAccount = { address: string; isFavorite: boolean };
const STORE_FAVS = 'accounts:favorites';
// query the ledger for the address, adding it to the keyring
@ -36,6 +35,53 @@ async function queryLedger (): Promise<void> {
}
}
function expandList (mapped: SortedAccount[], entry: SortedAccount): SortedAccount[] {
mapped.push(entry);
entry.children.forEach((entry): void => {
expandList(mapped, entry);
});
return mapped;
}
function sortAccounts (addresses: string[], favorites: string[]): SortedAccount[] {
const mapped = addresses
.map((address) => keyring.getAccount(address))
.filter((account): account is KeyringAddress => !!account)
.map((account): SortedAccount => ({
account,
children: [],
isFavorite: favorites.includes(account.address)
}))
.sort((a, b) => (a.account.meta.whenCreated || 0) - (b.account.meta.whenCreated || 0));
return mapped
.filter((entry): boolean => {
const parentAddress = entry.account.meta.parentAddress;
if (parentAddress) {
const parent = mapped.find(({ account: { address } }) => address === parentAddress);
if (parent) {
parent.children.push(entry);
return false;
}
}
return true;
})
.reduce(expandList, [])
.sort((a, b): number =>
a.isFavorite === b.isFavorite
? 0
: b.isFavorite
? 1
: -1
);
}
function Overview ({ className, onStatusChange }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { allAccounts } = useAccounts();
@ -48,21 +94,7 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
useEffect((): void => {
setSortedAccounts(
allAccounts
.map((address): SortedAccount => ({ address, isFavorite: favorites.includes(address) }))
.sort((a, b): number => {
const accA = keyring.getAccount(a.address) as KeyringAddress;
const accB = keyring.getAccount(b.address) as KeyringAddress;
return (accA.meta.whenCreated || 0) - (accB.meta.whenCreated || 0);
})
.sort((a, b): number =>
a.isFavorite === b.isFavorite
? 0
: b.isFavorite
? 1
: -1
)
sortAccounts(allAccounts, favorites)
);
}, [allAccounts, favorites]);
@ -131,6 +163,7 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
}
header={[
[t('accounts'), 'start', 3],
[t('parent'), 'address'],
[t('type')],
[t('tags'), 'start'],
[t('transactions')],
@ -138,12 +171,12 @@ function Overview ({ className, onStatusChange }: Props): React.ReactElement<Pro
[undefined, undefined, 2]
]}
>
{sortedAccounts.map(({ address, isFavorite }): React.ReactNode => (
{sortedAccounts.map(({ account, isFavorite }): React.ReactNode => (
<Account
address={address}
account={account}
filter={filter}
isFavorite={isFavorite}
key={address}
key={account.address}
toggleFavorite={toggleFavorite}
/>
))}

View File

@ -49,7 +49,7 @@ function createAccount (source: KeyringPair, suri: string, name: string, passwor
try {
const derived = source.derive(suri);
derived.setMeta({ ...derived.meta, name, tags: [] });
derived.setMeta({ ...derived.meta, name, parentAddress: source.address, tags: [] });
const result = keyring.addPair(derived, password || '');
const { address } = result.pair;

View File

@ -0,0 +1,11 @@
// Copyright 2017-2020 @polkadot/app-staking authors & contributors
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { KeyringAddress } from '@polkadot/ui-keyring/types';
export interface SortedAccount {
account: KeyringAddress;
children: SortedAccount[];
isFavorite: boolean;
}

View File

@ -4,7 +4,7 @@
import { DeriveAccountInfo } from '@polkadot/api-derive/types';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { useApi, useCall } from '@polkadot/react-hooks';
@ -54,7 +54,10 @@ function AddressToggle ({ address, className, filter, isHidden, noToggle, onChan
setIsFiltered(getIsFiltered(address, filter, info));
}, [address, filter, info]);
const _onClick = (): void => onChange && onChange(!value);
const _onClick = useCallback(
(): void => onChange && onChange(!value),
[onChange, value]
);
return (
<div

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { useApi } from '@polkadot/react-hooks';
import chains from '@polkadot/ui-settings/defaults/chains';
@ -39,17 +39,20 @@ function ChainLock ({ className, genesisHash, isDisabled, onChange, preventDefau
setTied(calcLock(api.genesisHash.toHex(), genesisHash));
}, [api, genesisHash]);
const _onChange = useCallback(
(isTiedToChain: boolean) =>
onChange(
isTiedToChain
? api.genesisHash.toHex()
: null
),
[api, onChange]
);
if (isDevelopment) {
return null;
}
const _onChange = (isTiedToChain: boolean): void =>
onChange(
isTiedToChain
? api.genesisHash.toHex()
: null
);
return (
<Toggle
className={className}

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React, { useContext } from 'react';
import React, { useCallback, useContext } from 'react';
import CopyToClipboard from 'react-copy-to-clipboard';
import StatusContext from './Status/Context';
@ -24,14 +24,17 @@ function CopyButton ({ children, className, icon = 'copy', isAddress = false, va
const { t } = useTranslation();
const { queueAction } = useContext(StatusContext);
const _onCopy = (): void => {
isAddress && queueAction && queueAction({
account: value,
action: t('clipboard'),
message: t('address copied'),
status: 'queued'
});
};
const _onCopy = useCallback(
(): void => {
isAddress && queueAction && queueAction({
account: value,
action: t('clipboard'),
message: t('address copied'),
status: 'queued'
});
},
[isAddress, queueAction, t, value]
);
return (
<div className={className}>

View File

@ -71,11 +71,17 @@ function BaseDropdown<Option> ({ allowAdd = false, className, defaultValue, drop
_setStored(isUndefined(value) ? defaultValue : value);
}, [_setStored, defaultValue, value]);
const _onAdd = (_: React.SyntheticEvent<HTMLElement>, { value }: DropdownProps): void =>
onAdd && onAdd(value);
const _onAdd = useCallback(
(_: React.SyntheticEvent<HTMLElement>, { value }: DropdownProps): void =>
onAdd && onAdd(value),
[onAdd]
);
const _onChange = (_: React.SyntheticEvent<HTMLElement> | null, { value }: DropdownProps): void =>
_setStored(value);
const _onChange = useCallback(
(_: React.SyntheticEvent<HTMLElement> | null, { value }: DropdownProps): void =>
_setStored(value),
[_setStored]
);
const dropdown = (
<SUIDropdown

View File

@ -4,9 +4,10 @@
import { BareProps } from './types';
import React, { useState } from 'react';
import React from 'react';
import ReactMd from 'react-markdown';
import styled from 'styled-components';
import { useToggle } from '@polkadot/react-hooks';
import Icon from './Icon';
@ -15,23 +16,21 @@ interface Props extends BareProps {
}
function HelpOverlay ({ className, md }: Props): React.ReactElement<Props> {
const [isVisible, setIsVisible] = useState(false);
const _toggleVisible = (): void => setIsVisible(!isVisible);
const [isVisible, toggleVisible] = useToggle();
return (
<div className={className}>
<div className='help-button'>
<Icon
name='help circle'
onClick={_toggleVisible}
onClick={toggleVisible}
/>
</div>
<div className={`help-slideout ${isVisible ? 'open' : 'closed'}`}>
<div className='help-button'>
<Icon
name='close'
onClick={_toggleVisible}
onClick={toggleVisible}
/>
</div>
<ReactMd

View File

@ -5,7 +5,7 @@
import { DeriveAccountInfo } from '@polkadot/api-derive/types';
import { IdentityProps as Props } from '@polkadot/react-identicon/types';
import React, { useContext, useEffect, useState } from 'react';
import React, { useCallback, useContext, useEffect, useState } from 'react';
import { getSystemIcon } from '@polkadot/apps-config/ui';
import { useApi, useCall } from '@polkadot/react-hooks';
import BaseIdentityIcon from '@polkadot/react-identicon';
@ -13,13 +13,17 @@ import uiSettings from '@polkadot/ui-settings';
import { ValidatorsContext } from '@polkadot/react-query';
import StatusContext from './Status/Context';
import Tooltip from './Tooltip';
import { useTranslation } from './translate';
export function getIdentityTheme (systemName: string): 'substrate' {
return ((uiSettings.icon === 'default' && getSystemIcon(systemName)) || uiSettings.icon) as 'substrate';
}
function IdentityIcon ({ className, onCopy, prefix, size, style, theme, value }: Props): React.ReactElement<Props> {
let id = 0;
function IdentityIcon ({ className, onCopy, prefix, size, theme, value }: Props): React.ReactElement<Props> {
const [trigger] = useState(`tooltip-identicon-${++id}`);
const { api, isApiReady, systemName } = useApi();
const { t } = useTranslation();
const info = useCall<DeriveAccountInfo>(isApiReady && api.derive.accounts.info as any, [value]);
@ -30,34 +34,50 @@ function IdentityIcon ({ className, onCopy, prefix, size, style, theme, value }:
const thisTheme = theme || getIdentityTheme(systemName);
useEffect((): void => {
value && setIsValidator(validators.includes(value.toString()));
value && setIsValidator(
validators.includes(value.toString())
);
}, [value, validators]);
useEffect((): void => {
info && setAddress(info.accountId?.toString());
info && setAddress(
info.accountId?.toString()
);
}, [info]);
const _onCopy = (account: string): void => {
onCopy && onCopy(account);
queueAction && queueAction({
account,
action: t('clipboard'),
message: t('address copied'),
status: 'queued'
});
};
const _onCopy = useCallback(
(account: string): void => {
onCopy && onCopy(account);
queueAction && queueAction({
account,
action: t('clipboard'),
message: t('address copied'),
status: 'queued'
});
},
[onCopy, queueAction, t]
);
return (
<BaseIdentityIcon
<span
className={className}
isHighlight={isValidator}
onCopy={_onCopy}
prefix={prefix}
size={size}
style={style}
theme={thisTheme as 'substrate'}
value={address}
/>
data-for={trigger}
data-tip={true}
>
<BaseIdentityIcon
isHighlight={isValidator}
onCopy={_onCopy}
prefix={prefix}
size={size}
theme={thisTheme as 'substrate'}
value={address}
/>
<Tooltip
className='address'
text={address}
trigger={trigger}
/>
</span>
);
}

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import SUIInput from 'semantic-ui-react/dist/commonjs/elements/Input/Input';
import { isUndefined } from '@polkadot/util';
@ -93,35 +93,45 @@ let counter = 0;
function Input ({ autoFocus = false, children, className, defaultValue, help, icon, inputClassName, isAction = false, isDisabled = false, isDisabledError = false, isEditable = false, isError = false, isFull = false, isHidden = false, isReadOnly = false, label, labelExtra, max, maxLength, min, name, onBlur, onChange, onEnter, onEscape, onKeyDown, onKeyUp, onPaste, placeholder, style, tabIndex, type = 'text', value, withEllipsis, withLabel }: Props): React.ReactElement<Props> {
const [stateName] = useState(`in_${counter++}_at_${Date.now()}`);
const _onBlur = (): void => {
onBlur && onBlur();
};
const _onBlur = useCallback(
() => onBlur && onBlur(),
[onBlur]
);
const _onChange = ({ target }: React.SyntheticEvent<HTMLInputElement>): void => {
onChange && onChange((target as HTMLInputElement).value);
};
const _onChange = useCallback(
({ target }: React.SyntheticEvent<HTMLInputElement>): void =>
onChange && onChange((target as HTMLInputElement).value),
[onChange]
);
const _onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
onKeyDown && onKeyDown(event);
};
const _onKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>): void =>
onKeyDown && onKeyDown(event),
[onKeyDown]
);
const _onKeyUp = (event: React.KeyboardEvent<HTMLInputElement>): void => {
onKeyUp && onKeyUp(event);
const _onKeyUp = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>): void => {
onKeyUp && onKeyUp(event);
if (onEnter && event.keyCode === 13) {
(event.target as any).blur();
onEnter();
}
if (onEnter && event.keyCode === 13) {
(event.target as any).blur();
onEnter();
}
if (onEscape && event.keyCode === 27) {
(event.target as any).blur();
onEscape();
}
};
if (onEscape && event.keyCode === 27) {
(event.target as any).blur();
onEscape();
}
},
[onEnter, onEscape, onKeyUp]
);
const _onPaste = (event: React.ClipboardEvent<HTMLInputElement>): void => {
onPaste && onPaste(event);
};
const _onPaste = useCallback(
(event: React.ClipboardEvent<HTMLInputElement>): void =>
onPaste && onPaste(event),
[onPaste]
);
return (
<Labelled

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React, { useState } from 'react';
import React, { useCallback, useState } from 'react';
import styled from 'styled-components';
import addressToAddress from './util/toAddress';
@ -25,13 +25,16 @@ interface Props extends BareProps {
function InputAddressSimple ({ children, className, defaultValue, help, isFull, label, onChange, onEnter, onEscape }: Props): React.ReactElement<Props> {
const [address, setAddress] = useState<string | null>(defaultValue || null);
const _onChange = (_address: string): void => {
const address = addressToAddress(_address) || null;
const _onChange = useCallback(
(_address: string): void => {
const address = addressToAddress(_address) || null;
setAddress(address);
setAddress(address);
onChange && onChange(address);
};
onChange && onChange(address);
},
[onChange]
);
return (
<div className={className}>

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React, { useState, createRef } from 'react';
import React, { useCallback, useState, createRef } from 'react';
import Dropzone, { DropzoneRef } from 'react-dropzone';
import styled from 'styled-components';
import { formatNumber, isHex, u8aToString, hexToU8a } from '@polkadot/util';
@ -58,29 +58,32 @@ function InputFile ({ accept, className, clearContent, convertHex, help, isDisab
const dropRef = createRef<DropzoneRef>();
const [file, setFile] = useState<FileState | undefined>();
const _onDrop = (files: File[]): void => {
files.forEach((file): void => {
const reader = new FileReader();
const _onDrop = useCallback(
(files: File[]): void => {
files.forEach((file): void => {
const reader = new FileReader();
reader.onabort = NOOP;
reader.onerror = NOOP;
reader.onabort = NOOP;
reader.onerror = NOOP;
reader.onload = ({ target }: ProgressEvent<FileReader>): void => {
if (target && target.result) {
const name = file.name;
const data = convertResult(target.result as ArrayBuffer, convertHex);
reader.onload = ({ target }: ProgressEvent<FileReader>): void => {
if (target && target.result) {
const name = file.name;
const data = convertResult(target.result as ArrayBuffer, convertHex);
onChange && onChange(data, name);
dropRef && setFile({
name,
size: data.length
});
}
};
onChange && onChange(data, name);
dropRef && setFile({
name,
size: data.length
});
}
};
reader.readAsArrayBuffer(file);
});
};
reader.readAsArrayBuffer(file);
});
},
[convertHex, dropRef, onChange]
);
const dropZone = (
<Dropzone

View File

@ -6,7 +6,7 @@ import { SiDef } from '@polkadot/util/types';
import { BareProps, BitLength } from './types';
import BN from 'bn.js';
import React, { useEffect, useState } from 'react';
import React, { useCallback, useEffect, useState } from 'react';
import { formatBalance } from '@polkadot/util';
import { classes } from './util';
@ -137,30 +137,6 @@ function inputToBn (input: string, si: SiDef | null, props: Props): [BN, boolean
];
}
// function bnToInput (bn: BN, si: SiDef | null): string {
// const [siPower] = getSiPowers(si);
//
// const base = TEN.pow(siPower);
// const div = bn.div(base);
// const mod = bn.mod(base);
//
// return `${
// div.gt(ZERO) ? div.toString() : '0'
// }${
// mod.gt(ZERO)
// ? ((): string => {
// const padding = Math.max(
// mod.toString().length,
// base.toString().length - div.toString().length,
// bn.toString().length - div.toString().length
// );
//
// return `.${mod.toString(10, padding).replace(/0*$/, '')}`;
// })()
// : ''
// }`;
// }
function getValuesFromString (value: string, si: SiDef | null, props: Props): [string, BN, boolean] {
const [valueBn, isValid] = inputToBn(value, si, props);
@ -220,48 +196,55 @@ function InputNumber (props: Props): React.ReactElement<Props> {
onChange && onChange(valueBn);
}, [onChange, valueBn]);
const _onChange = (input: string): void => {
setValues(getValuesFromString(input, si, props));
};
const _onChange = useCallback(
(input: string) => setValues(getValuesFromString(input, si, props)),
[props, si]
);
const _onKeyDown = (event: React.KeyboardEvent<Element>): void => {
if (KEYS_PRE.includes(event.key)) {
setIsPreKeyDown(true);
const _onKeyDown = useCallback(
(event: React.KeyboardEvent<Element>): void => {
if (KEYS_PRE.includes(event.key)) {
setIsPreKeyDown(true);
return;
}
return;
}
if (event.key.length === 1 && !isPreKeyDown) {
const { selectionEnd: j, selectionStart: i, value } = event.target as HTMLInputElement;
const newValue = `${value.substring(0, i || 0)}${event.key}${value.substring(j || 0)}`;
if (event.key.length === 1 && !isPreKeyDown) {
const { selectionEnd: j, selectionStart: i, value } = event.target as HTMLInputElement;
const newValue = `${value.substring(0, i || 0)}${event.key}${value.substring(j || 0)}`;
if (!getRegex(isDecimal || !!si).test(newValue)) {
event.preventDefault();
}
}
},
[isDecimal, isPreKeyDown, si]
);
const _onKeyUp = useCallback(
(event: React.KeyboardEvent<Element>): void => {
if (KEYS_PRE.includes(event.key)) {
setIsPreKeyDown(false);
}
},
[]
);
const _onPaste = useCallback(
(event: React.ClipboardEvent<Element>): void => {
const { value: newValue } = event.target as HTMLInputElement;
if (!getRegex(isDecimal || !!si).test(newValue)) {
event.preventDefault();
}
}
};
},
[isDecimal, si]
);
const _onKeyUp = (event: React.KeyboardEvent<Element>): void => {
if (KEYS_PRE.includes(event.key)) {
setIsPreKeyDown(false);
}
};
const _onPaste = (event: React.ClipboardEvent<Element>): void => {
const { value: newValue } = event.target as HTMLInputElement;
if (!getRegex(isDecimal || !!si).test(newValue)) {
event.preventDefault();
}
};
const _onSelectSiUnit = (siUnit: string): void => {
setSi(formatBalance.findSi(siUnit));
};
// const _onClickMaxButton = (): void => {
// !!maxValue && setValue(bnToInput(maxValue, si));
// };
const _onSelectSiUnit = useCallback(
(siUnit: string) => setSi(formatBalance.findSi(siUnit)),
[]
);
const maxValueLength = getGlobalMaxValue(bitLength).toString().length - 1;

View File

@ -2,7 +2,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React from 'react';
import React, { useCallback } from 'react';
import { compactAddLength } from '@polkadot/util';
import InputFile, { InputFileProps } from './InputFile';
@ -12,12 +12,15 @@ interface Props extends Omit<InputFileProps, 'accept'> {
}
function InputWasm ({ isValidRef, onChange, ...props }: Props): React.ReactElement<Props> {
const _onChange = (wasm: Uint8Array, name: string): void => {
const isWasmValid = wasm.subarray(0, 4).toString() === '0,97,115,109'; // '\0asm'
const _onChange = useCallback(
(wasm: Uint8Array, name: string): void => {
const isWasmValid = wasm.subarray(0, 4).toString() === '0,97,115,109'; // '\0asm'
isValidRef.current = isWasmValid;
onChange(compactAddLength(wasm), name);
};
isValidRef.current = isWasmValid;
onChange(compactAddLength(wasm), name);
},
[isValidRef, onChange]
);
return (
<InputFile

View File

@ -2,9 +2,10 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import React, { useState } from 'react';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import styled from 'styled-components';
import { useToggle } from '@polkadot/react-hooks';
import Icon from './Icon';
import { classes } from './util';
@ -23,18 +24,19 @@ export interface InsetProps {
function Inset ({ children, className, header, href, isCollapsible, isError, isSuccess, withBottomMargin, withTopMargin }: InsetProps): React.ReactElement<InsetProps> | null {
const history = useHistory();
const [isCollapsed, setIsCollapsed] = useState(true);
const [isCollapsed, toggleCollapsed] = useToggle();
const _onClick = useCallback(
(): void => {
href && history.push(href);
},
[history, href]
);
if (!children) {
return null;
}
const _onClick = (): void => {
href && history.push(href);
};
const _toggleCollapsed = (): void => setIsCollapsed(!isCollapsed);
return (
<div
className={
@ -53,7 +55,7 @@ function Inset ({ children, className, header, href, isCollapsible, isError, isS
{isCollapsible && (
<div
className='header'
onClick={_toggleCollapsed}
onClick={toggleCollapsed}
>
<h3>{header}</h3>
<Icon

View File

@ -6,6 +6,7 @@ import { BareProps } from './types';
import React, { useState } from 'react';
import styled from 'styled-components';
import { useToggle } from '@polkadot/react-hooks';
import Icon from './Icon';
import { classes } from './util';
@ -15,10 +16,11 @@ interface Props extends BareProps {
help: React.ReactNode;
}
function LabelHelp ({ className, help, style }: Props): React.ReactElement<Props> {
const [isTooltipOpen, setIsTooltipOpen] = useState(false);
let id = 0;
const _toggleTooltip = (): void => setIsTooltipOpen(!isTooltipOpen);
function LabelHelp ({ className, help, style }: Props): React.ReactElement<Props> {
const [trigger] = useState(`label-help-${++id}`);
const [isTooltipOpen, toggleTooltip] = useToggle();
return (
<div
@ -26,16 +28,16 @@ function LabelHelp ({ className, help, style }: Props): React.ReactElement<Props
style={style}
>
<Icon
data-for='controlled-trigger'
data-for={trigger}
data-tip
name='help circle'
onMouseOut={_toggleTooltip}
onMouseOver={_toggleTooltip}
onMouseOut={toggleTooltip}
onMouseOver={toggleTooltip}
/>
{isTooltipOpen && (
<Tooltip
text={help}
trigger='controlled-trigger'
trigger={trigger}
/>
)}
</div>

View File

@ -4,8 +4,9 @@
import { BareProps } from './types';
import React, { useState } from 'react';
import React from 'react';
import { MAX_PASS_LEN } from '@polkadot/ui-keyring/defaults';
import { useToggle } from '@polkadot/react-hooks';
import { classes } from './util';
import Button from './Button';
@ -31,9 +32,7 @@ interface Props extends BareProps {
}
function Password ({ autoFocus, children, className, defaultValue, help, isDisabled, isError, isFull, label, labelExtra, name, onChange, onEnter, onEscape, style, tabIndex, value, withLabel }: Props): React.ReactElement<Props> {
const [isVisible, setIsVisible] = useState(false);
const _toggleVisible = (): void => setIsVisible(!isVisible);
const [isVisible, toggleVisible] = useToggle();
return (
<Input
@ -68,7 +67,7 @@ function Password ({ autoFocus, children, className, defaultValue, help, isDisab
? 'hide'
: 'unhide'
}
onClick={_toggleVisible}
onClick={toggleVisible}
/>
{children}
</Input>

View File

@ -23,33 +23,27 @@ interface Props extends BareProps {
}
function Progress ({ className, color = 'blue', percent, style, total, value }: Props): React.ReactElement<Props> | null {
let calculated: number | undefined;
const _total = bnToBn(total);
const _value = bnToBn(value);
if (_total.gtn(0)) {
calculated = 100.0 * _value.toNumber() / _total.toNumber();
} else {
calculated = isBn(percent) ? percent.toNumber() : percent;
}
const calculated = _total.gtn(0)
? 100.0 * _value.toNumber() / _total.toNumber()
: isBn(percent) ? percent.toNumber() : percent;
if (isUndefined(calculated) || calculated < 0) {
return null;
}
let rainbow: BaseColors;
if (color === 'auto' || color === 'autoReverse') {
if (calculated > 66.6) {
rainbow = color === 'auto' ? 'green' : 'red';
} else if (calculated > 33.3) {
rainbow = 'orange';
} else {
rainbow = color === 'auto' ? 'red' : 'green';
}
} else {
rainbow = color;
}
const rainbow = (color === 'auto' || color === 'autoReverse')
? (calculated > 66.6)
? color === 'auto'
? 'green'
: 'red'
: (calculated > 33.3)
? 'orange'
: color === 'auto'
? 'red'
: 'green'
: color;
return (
<SUIProgress

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React from 'react';
import React, { useCallback } from 'react';
import SUICheckbox from 'semantic-ui-react/dist/commonjs/modules/Checkbox';
import styled from 'styled-components';
@ -19,14 +19,17 @@ interface Props extends BareProps {
}
function Toggle ({ asSwitch = true, className, defaultValue, isDisabled, label, onChange, preventDefault, value }: Props): React.ReactElement<Props> {
const _onChange = (event: React.FormEvent<HTMLInputElement>, { checked }: any): void => {
if (preventDefault) {
event.preventDefault();
event.stopPropagation();
}
const _onChange = useCallback(
(event: React.FormEvent<HTMLInputElement>, { checked }: any): void => {
if (preventDefault) {
event.preventDefault();
event.stopPropagation();
}
onChange && onChange(checked);
};
onChange && onChange(checked);
},
[onChange, preventDefault]
);
return (
<div className={className}>

View File

@ -52,13 +52,18 @@ function Tooltip ({ className, effect = 'solid', offset, place = 'top', text, tr
offset={offset}
place={place}
>
{text}
{className?.includes('address') ? <div>{text}</div> : text}
</ReactTooltip>,
tooltipContainer
);
}
export default React.memo(styled(Tooltip)`
&.address div {
overflow: hidden;
text-overflow: ellipsis;
}
table {
border: 0;

View File

@ -4,7 +4,7 @@
import { TxButtonProps as Props } from './types';
import React, { useContext } from 'react';
import React, { useCallback, useContext } from 'react';
import { SubmittableResult } from '@polkadot/api';
import { useApi, useToggle } from '@polkadot/react-hooks';
import { assert, isFunction, isUndefined } from '@polkadot/util';
@ -20,53 +20,62 @@ function TxButton ({ accountId, className, extrinsic: propsExtrinsic, icon, isBa
const [isSending, , setIsSending] = useToggle(false);
const needsAccount = !isUnsigned && !accountId;
const _onFailed = (result: SubmittableResult | null): void => {
setIsSending(false);
const _onFailed = useCallback(
(result: SubmittableResult | null): void => {
setIsSending(false);
onFailed && onFailed(result);
};
onFailed && onFailed(result);
},
[onFailed, setIsSending]
);
const _onSuccess = (result: SubmittableResult): void => {
setIsSending(false);
const _onSuccess = useCallback(
(result: SubmittableResult): void => {
setIsSending(false);
onSuccess && onSuccess(result);
};
onSuccess && onSuccess(result);
},
[onSuccess, setIsSending]
);
const _onSend = (): void => {
let extrinsic: any;
const _onSend = useCallback(
(): void => {
let extrinsic: any;
if (propsExtrinsic) {
extrinsic = propsExtrinsic;
} else {
const [section, method] = (tx || '').split('.');
if (propsExtrinsic) {
extrinsic = propsExtrinsic;
} else {
const [section, method] = (tx || '').split('.');
assert(api.tx[section] && api.tx[section][method], `Unable to find api.tx.${section}.${method}`);
assert(api.tx[section] && api.tx[section][method], `Unable to find api.tx.${section}.${method}`);
extrinsic = api.tx[section][method](...(
isFunction(params)
? params()
: (params || [])
));
}
extrinsic = api.tx[section][method](...(
isFunction(params)
? params()
: (params || [])
));
}
assert(extrinsic, 'Expected generated extrinsic passed to TxButton');
assert(extrinsic, 'Expected generated extrinsic passed to TxButton');
if (withSpinner) {
setIsSending(true);
}
if (withSpinner) {
setIsSending(true);
}
queueExtrinsic({
accountId: accountId && accountId.toString(),
extrinsic,
isUnsigned,
txFailedCb: withSpinner ? _onFailed : onFailed,
txStartCb: onStart,
txSuccessCb: withSpinner ? _onSuccess : onSuccess,
txUpdateCb: onUpdate
});
queueExtrinsic({
accountId: accountId && accountId.toString(),
extrinsic,
isUnsigned,
txFailedCb: withSpinner ? _onFailed : onFailed,
txStartCb: onStart,
txSuccessCb: withSpinner ? _onSuccess : onSuccess,
txUpdateCb: onUpdate
});
onClick && onClick();
};
onClick && onClick();
},
[_onFailed, _onSuccess, accountId, api.tx, isUnsigned, onClick, onFailed, onStart, onSuccess, onUpdate, params, propsExtrinsic, queueExtrinsic, setIsSending, tx, withSpinner]
);
if (onSendRef) {
onSendRef.current = _onSend;