More functional component conversions (#1640)

* More functional component conversions

* Expose systemVersion on api

* Signer checks

* KeyRing uses local IdentityIcon
This commit is contained in:
Jaco Greeff 2019-09-20 16:08:37 +02:00 committed by GitHub
parent 3a9d67f398
commit 6fb8916e6f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 621 additions and 946 deletions

View File

@ -3,12 +3,12 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId, RuntimeVersion } from '@polkadot/types/interfaces';
import { AccountId } from '@polkadot/types/interfaces';
import { BareProps, I18nProps } from '@polkadot/react-components/types';
import BN from 'bn.js';
import React, { useState, useEffect } from 'react';
import { withCalls } from '@polkadot/react-api/with';
import React, { useContext, useState, useEffect } from 'react';
import { ApiContext, withCalls } from '@polkadot/react-api';
import { Bubble, IdentityIcon } from '@polkadot/react-components';
import { formatBalance, formatNumber } from '@polkadot/util';
@ -20,13 +20,10 @@ interface Props extends BareProps, I18nProps {
chain_bestNumberLag?: BN;
session_validators?: AccountId[];
staking_intentions?: AccountId[];
state_getRuntimeVersion?: RuntimeVersion;
system_chain?: string;
system_name?: string;
system_version?: string;
}
function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_intentions = [], session_validators = [], state_getRuntimeVersion, system_chain, system_name, system_version }: Props): React.ReactElement<Props> {
function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumberLag, staking_intentions = [], session_validators = [] }: Props): React.ReactElement<Props> {
const { api, systemChain, systemName, systemVersion } = useContext(ApiContext);
const [nextUp, setNextUp] = useState<AccountId[]>([]);
useEffect((): void => {
@ -43,15 +40,14 @@ function SummaryBar ({ balances_totalIssuance, chain_bestNumber, chain_bestNumbe
<summary>
<div>
<Bubble icon='tty' label='node'>
{system_name} v{system_version}
{systemName} v{systemVersion}
</Bubble>
<Bubble icon='chain' label='chain'>
{system_chain}
{systemChain}
</Bubble>
<Bubble icon='code' label='runtime'>
{api.runtimeVersion.implName} v{api.runtimeVersion.implVersion}
</Bubble>
<Bubble icon='code' label='runtime'>{
state_getRuntimeVersion &&
`${state_getRuntimeVersion.implName} v${state_getRuntimeVersion.implVersion}`
}</Bubble>
<Bubble icon='bullseye' label='best #'>
{formatNumber(chain_bestNumber)} ({formatNumber(chain_bestNumberLag)} lag)
</Bubble>
@ -79,10 +75,6 @@ export default translate(
'derive.chain.bestNumber',
'derive.chain.bestNumberLag',
'query.balances.totalIssuance',
'query.session.validators',
'rpc.state.getRuntimeVersion',
'rpc.system.chain',
'rpc.system.name',
'rpc.system.version'
'query.session.validators'
)(SummaryBar)
);

View File

@ -3,7 +3,7 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { Compact, Text } from '@polkadot/types';
import { Compact } from '@polkadot/types';
import { Balance, EcdsaSignature, EthereumAddress } from '@polkadot/types/interfaces';
import { AppProps, I18nProps } from '@polkadot/react-components/types';
import { ApiProps } from '@polkadot/react-api/types';
@ -12,7 +12,7 @@ import React from 'react';
import { Trans } from 'react-i18next';
import styled from 'styled-components';
import CopyToClipboard from 'react-copy-to-clipboard';
import { withCalls, withMulti } from '@polkadot/react-api';
import { withApi, withMulti } from '@polkadot/react-api';
import { Button, Card, Columar, Column, InputAddress, Tooltip } from '@polkadot/react-components';
import { InputNumber } from '@polkadot/react-components/InputNumber';
import TxModal, { TxModalState, TxModalProps } from '@polkadot/react-components/TxModal';
@ -30,9 +30,7 @@ enum Step {
Claim = 2,
}
interface Props extends AppProps, ApiProps, I18nProps, TxModalProps {
system_chain?: Text;
}
interface Props extends AppProps, ApiProps, I18nProps, TxModalProps {}
interface State extends TxModalState {
didCopy: boolean;
@ -100,7 +98,7 @@ class App extends TxModal<Props, State> {
}
public render (): React.ReactNode {
const { api, system_chain = '', t } = this.props;
const { api, systemChain = '', t } = this.props;
const { accountId, didCopy, ethereumAddress, signature, step } = this.state;
const payload = accountId
@ -121,7 +119,7 @@ class App extends TxModal<Props, State> {
<Card withBottomMargin>
<h3>{t('1. Select your {{chain}} account', {
replace: {
chain: system_chain.toString()
chain: systemChain
}
})}</h3>
<InputAddress
@ -257,5 +255,5 @@ class App extends TxModal<Props, State> {
export default withMulti(
App,
translate,
withCalls<Props>('rpc.system.chain')
withApi
);

View File

@ -25,8 +25,8 @@ function renderEntry (route: Route): React.ReactNode {
);
}
// NOTE: This _looks_ weird and it looks weird, because it is weird. Basically we want all
// the entries of an equal width. So here we add a non-content spacers at the end that just
// NOTE: This _looks_ weird, because it is weird. Basically we want all the entries
// of an equal width. So here we add a non-content spacers at the end that just
// ensures flex has enough items to render something usable to the user. Since we don't
// quite know how many items per row, we just render a bunch, n === routes.length
function renderSpacer (route: Route, index: number): React.ReactNode {

View File

@ -22,7 +22,6 @@ import detectUnsafe from '../../unsafeChains';
interface Props extends I18nProps, ApiProps, CalculateBalanceProps {
controllerId: string;
isOpen: boolean;
isUnsafeChain: boolean;
onClose: () => void;
stashId: string;
}
@ -96,9 +95,10 @@ class BondExtra extends TxComponent<Props, State> {
}
private renderContent (): React.ReactNode {
const { isUnsafeChain, stashId, t } = this.props;
const { stashId, systemChain, t } = this.props;
const { maxBalance } = this.state;
const available = <span className='label'>{t('available ')}</span>;
const isUnsafeChain = detectUnsafe(systemChain);
return (
<>
@ -186,10 +186,6 @@ export default withMulti(
withCalls<Props>(
'derive.balances.fees',
['derive.balances.all', { paramName: 'stashId' }],
['query.system.accountNonce', { paramName: 'stashId' }],
['rpc.system.chain', {
propName: 'isUnsafeChain',
transform: detectUnsafe
}]
['query.system.accountNonce', { paramName: 'stashId' }]
)
);

View File

@ -2,19 +2,19 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import { Button, Icon, InputAddress, Modal, TxButton, TxComponent } from '@polkadot/react-components';
import { withCalls, withMulti } from '@polkadot/react-api';
import { withApi, withMulti } from '@polkadot/react-api';
import translate from '../../translate';
import detectUnsafe from '../../unsafeChains';
import InputValidationController from '../Account/InputValidationController';
interface Props extends I18nProps {
interface Props extends ApiProps, I18nProps {
defaultControllerId: string;
isUnsafeChain?: boolean;
isValidating?: boolean;
onClose: () => void;
stashId: string;
@ -36,8 +36,9 @@ class SetControllerAccount extends TxComponent<Props, State> {
}
public render (): React.ReactNode {
const { defaultControllerId, isUnsafeChain, onClose, stashId, t } = this.props;
const { defaultControllerId, isValidating, onClose, stashId, systemChain, t } = this.props;
const { controllerError, controllerId } = this.state;
const isUnsafeChain = detectUnsafe(systemChain);
const canSubmit = isUnsafeChain || (!controllerError && !!controllerId && (defaultControllerId !== controllerId));
return (
@ -47,7 +48,42 @@ class SetControllerAccount extends TxComponent<Props, State> {
open
size='small'
>
{this.renderContent()}
<Modal.Header>
{t('Change controller account')}
</Modal.Header>
<Modal.Content className='ui--signer-Signer-Content'>
{isValidating && (
<article className='warning'>
<div className='warning'>
<Icon name='warning sign' />
{t('Warning - Changing the controller while validating will modify the associated session account. It is advised to stop validating before changing the controller account.')}
</div>
</article>
)}
<InputAddress
className='medium'
isDisabled
label={t('stash account')}
value={stashId}
/>
<InputAddress
className='medium'
defaultValue={defaultControllerId}
help={t('The controller is the account that will be used to control any nominating or validating actions. Should not match another stash or controller.')}
isError={!isUnsafeChain && !!controllerError}
label={t('controller account')}
onChange={this.onChangeController}
type='account'
value={controllerId}
/>
<InputValidationController
accountId={stashId}
defaultController={defaultControllerId}
controllerId={controllerId}
isUnsafeChain={isUnsafeChain}
onError={this.onControllerError}
/>
</Modal.Content>
<Modal.Actions>
<Button.Group>
<Button
@ -74,62 +110,6 @@ class SetControllerAccount extends TxComponent<Props, State> {
);
}
private renderContent (): React.ReactNode {
const { defaultControllerId, isUnsafeChain, stashId, t } = this.props;
const { controllerId, controllerError } = this.state;
return (
<>
<Modal.Header>
{t('Change controller account')}
</Modal.Header>
<Modal.Content className='ui--signer-Signer-Content'>
{this.renderSessionAccountWarning()}
<InputAddress
className='medium'
isDisabled
label={t('stash account')}
value={stashId}
/>
<InputAddress
className='medium'
defaultValue={defaultControllerId}
help={t('The controller is the account that will be used to control any nominating or validating actions. Should not match another stash or controller.')}
isError={!isUnsafeChain && !!controllerError}
label={t('controller account')}
onChange={this.onChangeController}
type='account'
value={controllerId}
/>
<InputValidationController
accountId={stashId}
defaultController={defaultControllerId}
controllerId={controllerId}
isUnsafeChain={isUnsafeChain}
onError={this.onControllerError}
/>
</Modal.Content>
</>
);
}
private renderSessionAccountWarning (): React.ReactNode {
const { isValidating = false, t } = this.props;
if (!isValidating) {
return null;
}
return (
<article className='warning'>
<div className='warning'>
<Icon name='warning sign' />
{t('Warning - Changing the controller while validating will modify the associated session account. It is advised to stop validating before changing the controller account.')}
</div>
</article>
);
}
private onChangeController = (controllerId: string): void => {
this.setState({ controllerId });
}
@ -142,10 +122,5 @@ class SetControllerAccount extends TxComponent<Props, State> {
export default withMulti(
SetControllerAccount,
translate,
withCalls<Props>(
['rpc.system.chain', {
propName: 'isUnsafeChain',
transform: detectUnsafe
}]
)
withApi
);

View File

@ -10,7 +10,7 @@ import BN from 'bn.js';
import React from 'react';
import { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import { Button, Dropdown, InputAddress, InputBalanceBonded, Modal, TxButton, TxComponent } from '@polkadot/react-components';
import { withCalls, withMulti } from '@polkadot/react-api';
import { withApi, withMulti } from '@polkadot/react-api';
import translate from '../translate';
import detectUnsafe from '../unsafeChains';
@ -18,7 +18,6 @@ import InputValidationController from './Account/InputValidationController';
import { rewardDestinationOptions } from './constants';
interface Props extends ApiProps, I18nProps, CalculateBalanceProps {
isUnsafeChain?: boolean;
onClose: () => void;
}
@ -47,9 +46,10 @@ class NewStake extends TxComponent<Props, State> {
}
public render (): React.ReactNode {
const { isUnsafeChain, onClose, t } = this.props;
const { bondValue, controllerError, controllerId, extrinsic, stashId } = this.state;
const { onClose, systemChain, t } = this.props;
const { bondValue, controllerError, controllerId, destination, extrinsic, stashId } = this.state;
const hasValue = !!bondValue && bondValue.gtn(0);
const isUnsafeChain = detectUnsafe(systemChain);
const canSubmit = (hasValue && (isUnsafeChain || (!controllerError && !!controllerId)));
return (
@ -59,39 +59,6 @@ class NewStake extends TxComponent<Props, State> {
open
size='small'
>
{this.renderContent()}
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={onClose}
label={t('Cancel')}
labelIcon='cancel'
/>
<Button.Or />
<TxButton
accountId={stashId}
isDisabled={!canSubmit}
isPrimary
label={t('Bond')}
labelIcon='sign-in'
onClick={onClose}
extrinsic={extrinsic}
ref={this.button}
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
private renderContent (): React.ReactNode {
const { isUnsafeChain, t } = this.props;
const { controllerId, controllerError, bondValue, destination, stashId } = this.state;
const hasValue = !!bondValue && bondValue.gtn(0);
return (
<>
<Modal.Header>
{t('Bonding Preferences')}
</Modal.Header>
@ -142,7 +109,28 @@ class NewStake extends TxComponent<Props, State> {
value={destination}
/>
</Modal.Content>
</>
<Modal.Actions>
<Button.Group>
<Button
isNegative
onClick={onClose}
label={t('Cancel')}
labelIcon='cancel'
/>
<Button.Or />
<TxButton
accountId={stashId}
isDisabled={!canSubmit}
isPrimary
label={t('Bond')}
labelIcon='sign-in'
onClick={onClose}
extrinsic={extrinsic}
ref={this.button}
/>
</Button.Group>
</Modal.Actions>
</Modal>
);
}
@ -189,10 +177,5 @@ class NewStake extends TxComponent<Props, State> {
export default withMulti(
NewStake,
translate,
withCalls<Props>(
['rpc.system.chain', {
propName: 'isUnsafeChain',
transform: detectUnsafe
}]
)
withApi
);

View File

@ -14,7 +14,7 @@ import React from 'react';
import styled from 'styled-components';
import { Responsive } from 'semantic-ui-react';
import routing from '@polkadot/apps-routing';
import { withCalls, withMulti } from '@polkadot/react-api';
import { withApi, withMulti } from '@polkadot/react-api';
import { Button, ChainImg, Icon, Menu, media } from '@polkadot/react-components';
import { classes } from '@polkadot/react-components/util';
import { BestNumber, Chain } from '@polkadot/react-query';
@ -29,7 +29,6 @@ interface Props extends ApiProps, I18nProps {
handleResize: () => void;
isCollapsed: boolean;
menuOpen: boolean;
system_chain?: string;
toggleMenu: () => void;
}
@ -257,7 +256,5 @@ export default withMulti(
}
`,
translate,
withCalls<Props>(
'rpc.system.chain'
)
withApi
);

View File

@ -3,7 +3,6 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { ProviderInterface } from '@polkadot/rpc-provider/types';
import { ChainProperties } from '@polkadot/types/interfaces';
import { QueueTxPayloadAdd, QueueTxMessageSetStatus } from '@polkadot/react-components/Status/types';
import { Prefix } from '@polkadot/util-crypto/address/types';
import { ApiProps } from './types';
@ -17,7 +16,7 @@ import { InputNumber } from '@polkadot/react-components/InputNumber';
import keyring from '@polkadot/ui-keyring';
import uiSettings from '@polkadot/ui-settings';
import ApiSigner from '@polkadot/react-signer/ApiSigner';
import { Text, u32 as U32 } from '@polkadot/types';
import { u32 as U32 } from '@polkadot/types';
import { formatBalance, isTestChain } from '@polkadot/util';
import addressDefaults from '@polkadot/util-crypto/address/defaults';
@ -122,9 +121,11 @@ export default class Api extends React.PureComponent<Props, State> {
}
private async loadOnReady (api: ApiPromise): Promise<void> {
const [properties, value, injectedAccounts] = await Promise.all([
api.rpc.system.properties<ChainProperties>(),
api.rpc.system.chain<Text>(),
const [properties, _systemChain, _systemName, _systemVersion, injectedAccounts] = await Promise.all([
api.rpc.system.properties(),
api.rpc.system.chain(),
api.rpc.system.name(),
api.rpc.system.version(),
web3Accounts().then((accounts): InjectedAccountExt[] =>
accounts.map(({ address, meta }): InjectedAccountExt => ({
address,
@ -142,12 +143,12 @@ export default class Api extends React.PureComponent<Props, State> {
) as Prefix;
const tokenSymbol = properties.tokenSymbol.unwrapOr('DEV').toString();
const tokenDecimals = properties.tokenDecimals.unwrapOr(DEFAULT_DECIMALS).toNumber();
const chain = value
? value.toString()
: null;
const isDevelopment = isTestChain(chain);
const systemChain = _systemChain
? _systemChain.toString()
: '<unknown>';
const isDevelopment = isTestChain(systemChain);
console.log('api: found chain', chain, JSON.stringify(properties));
console.log('api: found chain', systemChain, JSON.stringify(properties));
// first setup the UI helpers
formatBalance.setDefaults({
@ -177,15 +178,17 @@ export default class Api extends React.PureComponent<Props, State> {
this.setState({
apiDefaultTx,
apiDefaultTxSudo,
chain,
isApiReady: true,
isDevelopment,
isSubstrateV2
isSubstrateV2,
systemChain,
systemName: _systemName.toString(),
systemVersion: _systemVersion.toString()
});
}
public render (): React.ReactNode {
const { api, apiDefaultTx, apiDefaultTxSudo, chain, isApiConnected, isApiReady, isDevelopment, isSubstrateV2, isWaitingInjected, setApiUrl } = this.state;
const { api, apiDefaultTx, apiDefaultTxSudo, isApiConnected, isApiReady, isDevelopment, isSubstrateV2, isWaitingInjected, setApiUrl, systemChain, systemName, systemVersion } = this.state;
return (
<ApiContext.Provider
@ -193,13 +196,15 @@ export default class Api extends React.PureComponent<Props, State> {
api,
apiDefaultTx,
apiDefaultTxSudo,
currentChain: chain || '<unknown>',
isApiConnected,
isApiReady: isApiReady && !!chain,
isApiReady: isApiReady && !!systemChain,
isDevelopment,
isSubstrateV2,
isWaitingInjected,
setApiUrl
setApiUrl,
systemChain,
systemName,
systemVersion
}}
>
{this.props.children}

View File

@ -19,13 +19,15 @@ export interface ApiProps {
api: ApiPromise;
apiDefaultTx: SubmittableExtrinsicFunction;
apiDefaultTxSudo: SubmittableExtrinsicFunction;
currentChain: string;
isApiConnected: boolean;
isApiReady: boolean;
isDevelopment: boolean;
isSubstrateV2: boolean;
isWaitingInjected: boolean;
setApiUrl: (url?: string) => void;
systemChain: string;
systemName: string;
systemVersion: string;
}
export interface OnChangeCbObs {

View File

@ -3,8 +3,9 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
import { ApiProps } from '@polkadot/react-api/types';
import { I18nProps } from '@polkadot/react-components/types';
import { AccountId, AccountIndex, Address } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React from 'react';
@ -16,7 +17,7 @@ import keyring from '@polkadot/ui-keyring';
import AddressInfo, { BalanceActiveType } from './AddressInfo';
import { classes, getAddressName, getAddressTags, toShortAddress } from './util';
import CopyButton from './CopyButton';
import IdentityIcon from './IdentityIcon';
import IdentityIcon, { getIdentityTheme } from './IdentityIcon';
import Row, { RowProps, RowState as State, styles } from './Row';
import translate from './translate';
@ -34,10 +35,10 @@ export interface Props extends I18nProps, RowProps {
const DEFAULT_ADDR = '5'.padEnd(16, 'x');
const ICON_SIZE = 48;
class AddressRow extends Row<Props, State> {
class AddressRow extends Row<ApiProps & Props, State> {
public state: State;
public constructor (props: Props) {
public constructor (props: ApiProps & Props) {
super(props);
this.state = this.createState();
@ -177,7 +178,7 @@ class AddressRow extends Row<Props, State> {
}
private renderIcon (): React.ReactNode {
const { accounts_idAndIndex = [], iconInfo, withIcon = true } = this.props;
const { accounts_idAndIndex = [], iconInfo, systemName, withIcon = true } = this.props;
const { address } = this.state;
const [accountId] = accounts_idAndIndex;
@ -190,11 +191,13 @@ class AddressRow extends Row<Props, State> {
const Component = accountId
? IdentityIcon
: BaseIdentityIcon;
const theme = getIdentityTheme(systemName);
return (
<div className='ui--Row-icon'>
<Component
size={ICON_SIZE}
theme={theme}
value={address}
/>
{iconInfo && (
@ -267,7 +270,7 @@ export {
};
export default withMulti(
styled(AddressRow as React.ComponentClass<Props, State>)`
styled(AddressRow as React.ComponentClass<Props & ApiProps, State>)`
${styles}
`,
translate,

View File

@ -1,12 +1,10 @@
/* eslint-disable @typescript-eslint/camelcase */
// Copyright 2017-2019 @polkadot/apps 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 React from 'react';
import React, { useContext } from 'react';
import styled from 'styled-components';
import { withCalls, withMulti } from '@polkadot/react-api';
import { Text } from '@polkadot/types';
import { ApiContext } from '@polkadot/react-api';
// the imports here as a bit all-over, non-aphabetical - since we expect this to grow,
// rather organise based on type, grouping chains and nodes as opposed to location
@ -51,14 +49,13 @@ const LOGOS: Record<string, any> = {
interface Props {
className?: string;
injectedLogoChain?: any;
injectedLogoNode?: any;
logo?: keyof typeof LOGOS;
onClick?: () => any;
}
function ChainImg ({ className, injectedLogoChain, injectedLogoNode, logo = '', onClick }: Props): React.ReactElement<Props> {
const img = LOGOS[logo] || injectedLogoChain || injectedLogoNode || EMPTY;
function ChainImg ({ className, logo = '', onClick }: Props): React.ReactElement<Props> {
const { systemChain, systemName } = useContext(ApiContext);
const img = LOGOS[logo] || CHAINS[systemChain] || NODES[systemName] || EMPTY;
return (
<img
@ -70,19 +67,7 @@ function ChainImg ({ className, injectedLogoChain, injectedLogoNode, logo = '',
);
}
export default withMulti(
styled(ChainImg)`
border-radius: 50%;
box-sizing: border-box;
`,
withCalls<Props>(
['rpc.system.chain', {
propName: 'injectedLogoChain',
transform: (chain: Text): any | null => CHAINS[chain.toString()]
}],
['rpc.system.name', {
propName: 'injectedLogoNode',
transform: (node: Text): any | null => NODES[node.toString()]
}]
)
);
export default styled(ChainImg)`
border-radius: 50%;
box-sizing: border-box;
`;

View File

@ -5,35 +5,23 @@
import { AccountId } from '@polkadot/types/interfaces';
import { IdentityProps } from '@polkadot/react-identicon/types';
import { QueueAction$Add } from './Status/types';
import { I18nProps } from './types';
import React from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { Option } from '@polkadot/types';
import { withCalls } from '@polkadot/react-api/with';
import { ApiContext, withCalls, withMulti } from '@polkadot/react-api';
import BaseIdentityIcon from '@polkadot/react-identicon';
import uiSettings, { ICON_DEFAULT_HOST } from '@polkadot/ui-settings';
import uiSettings from '@polkadot/ui-settings';
import { QueueConsumer } from './Status/Context';
import StatusContext from './Status/Context';
import translate from './translate';
interface CopyProps extends IdentityProps, I18nProps {
queueAction?: QueueAction$Add;
}
interface IconProps extends IdentityProps {
interface Props extends IdentityProps, I18nProps {
session_validators?: AccountId[];
staking_bonded?: string | null;
system_name?: string;
}
interface Props extends IconProps, IdentityProps {}
interface State {
isValidator: boolean;
theme: string;
}
// overrides based on the actual software node type
const NODES: Record<string, string> = {
'edgeware-node': 'substrate',
@ -49,83 +37,63 @@ const NULL_TOSTRING = {
toString: (): null => null
};
class CopyIcon extends React.PureComponent<CopyProps> {
public render (): React.ReactNode {
return (
<BaseIdentityIcon
{...this.props}
onCopy={this.onCopy}
/>
);
}
private onCopy = (account: string): void => {
const { onCopy, queueAction, t } = this.props;
if (onCopy) {
onCopy(account);
}
if (queueAction) {
queueAction({
account,
action: t('clipboard'),
status: 'queued',
message: t('address copied')
});
}
}
export function getIdentityTheme (systemName: string): 'empty' {
return ((uiSettings.icon === 'default' && NODES[systemName]) || uiSettings.icon) as 'empty';
}
const CopyIconI18N = translate(CopyIcon);
function IdentityIcon ({ className, onCopy, prefix, session_validators, size, staking_bonded, style, t, theme, value }: Props): React.ReactElement<Props> {
const { systemName } = useContext(ApiContext);
const { queueAction } = useContext(StatusContext);
const [isValidator, setIsValidator] = useState(false);
const thisTheme = theme || getIdentityTheme(systemName);
class IdentityIcon extends React.PureComponent<Props, State> {
public state: State = {
isValidator: false,
theme: ICON_DEFAULT_HOST
};
public static getDerivedStateFromProps ({ session_validators = [], staking_bonded, system_name, value }: Props): State {
useEffect((): void => {
const address = value
? value.toString()
: null;
const isValidator = !!session_validators.find((validator): boolean =>
[address, staking_bonded].includes(validator.toString())
setIsValidator(
session_validators
? session_validators.some((validator): boolean =>
[address, staking_bonded].includes(validator.toString())
)
: false
);
}, [session_validators, value]);
return {
isValidator,
theme: (system_name && uiSettings.icon === 'default' && NODES[system_name]) || uiSettings.icon
};
}
const _onCopy = (account: string): void => {
onCopy && onCopy(account);
queueAction && queueAction({
account,
action: t('clipboard'),
status: 'queued',
message: t('address copied')
});
};
public render (): React.ReactNode {
const { isValidator, theme } = this.state;
return (
<QueueConsumer>
{({ queueAction }): React.ReactNode =>
<CopyIconI18N
isHighlight={isValidator}
theme={theme as 'substrate'}
{...this.props}
queueAction={queueAction}
/>
}
</QueueConsumer>
);
}
return (
<BaseIdentityIcon
className={className}
isHighlight={isValidator}
onCopy={_onCopy}
prefix={prefix}
size={size}
style={style}
theme={thisTheme as 'substrate'}
value={value}
/>
);
}
export default withCalls<Props>(
'query.session.validators',
['query.staking.bonded', {
paramName: 'value',
transform: (bonded: Option<AccountId>): string | null =>
bonded.unwrapOr(NULL_TOSTRING).toString()
}],
['rpc.system.name', {
transform: (node: Text): string =>
node.toString()
}]
)(IdentityIcon);
export default withMulti(
IdentityIcon,
translate,
withCalls<Props>(
'query.session.validators',
['query.staking.bonded', {
paramName: 'value',
transform: (bonded: Option<AccountId>): string | null =>
bonded.unwrapOr(NULL_TOSTRING).toString()
}]
)
);

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React from 'react';
import React, { useState } from 'react';
import SUIInput from 'semantic-ui-react/dist/commonjs/elements/Input/Input';
import { isUndefined } from '@polkadot/util';
@ -44,10 +44,6 @@ interface Props extends BareProps {
withEllipsis?: boolean;
}
interface State {
name: string;
}
// Find decimal separator used in current locale
const getDecimalSeparator = (): string => 1.1
.toLocaleString()
@ -89,122 +85,91 @@ const isSelectAll = (key: string, isPreKeyDown: boolean): boolean =>
let counter = 0;
export default class Input extends React.PureComponent<Props, State> {
public state: State = {
name: `in_${counter++}_at_${Date.now()}`
export default function Input ({ autoFocus = false, children, className, defaultValue, help, icon, isEditable = false, isAction = false, isDisabled = false, isError = false, isHidden = false, isReadOnly = false, label, max, maxLength, min, name, onBlur, onChange, onEnter, 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();
};
public render (): React.ReactNode {
const { autoFocus = false, children, className, defaultValue, help, icon, isEditable = false, isAction = false, isDisabled = false, isError = false, isHidden = false, isReadOnly = false, label, max, maxLength, min, name, placeholder, style, tabIndex, type = 'text', value, withEllipsis, withLabel } = this.props;
return (
<Labelled
className={className}
help={help}
label={label}
style={style}
withEllipsis={withEllipsis}
withLabel={withLabel}
>
<SUIInput
action={isAction}
autoFocus={autoFocus}
className={
isEditable
? 'ui--Input edit icon'
: 'ui--Input'
}
defaultValue={
isUndefined(value)
? (defaultValue || '')
: undefined
}
disabled={isDisabled}
error={!isDisabled && isError}
hidden={isHidden}
id={name}
iconPosition={
isUndefined(icon)
? undefined
: 'left'
}
max={max}
maxLength={maxLength}
min={min}
name={name || this.state.name}
onBlur={this.onBlur}
onChange={this.onChange}
onKeyDown={this.onKeyDown}
onKeyUp={this.onKeyUp}
placeholder={placeholder}
readOnly={isReadOnly}
tabIndex={tabIndex}
type={type}
value={value}
>
<input
autoComplete={
type === 'password'
? 'new-password'
: 'off'
}
onPaste={this.onPaste}
/>
{
isEditable
? <i className='edit icon' />
: undefined
}
{icon}
{children}
</SUIInput>
</Labelled>
);
}
private onChange = (event: React.SyntheticEvent<Element>): void => {
const { onChange } = this.props;
const { value } = event.target as HTMLInputElement;
onChange && onChange(value);
}
private onKeyDown = (event: React.KeyboardEvent<Element>): void => {
const { onKeyDown } = this.props;
if (onKeyDown) {
onKeyDown(event);
}
}
private onBlur = (): void => {
const { onBlur } = this.props;
if (onBlur) {
onBlur();
}
}
private onKeyUp = (event: React.KeyboardEvent<Element>): void => {
const { onEnter, onKeyUp } = this.props;
if (onKeyUp) {
onKeyUp(event);
}
const _onChange = ({ target }: React.SyntheticEvent<HTMLInputElement>): void => {
onChange && onChange((target as HTMLInputElement).value);
};
const _onKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
onKeyDown && onKeyDown(event);
};
const _onKeyUp = (event: React.KeyboardEvent<HTMLInputElement>): void => {
onKeyUp && onKeyUp(event);
if (onEnter && event.keyCode === 13) {
(event.target as any).blur();
onEnter();
}
}
};
const _onPaste = (event: React.ClipboardEvent<HTMLInputElement>): void => {
onPaste && onPaste(event);
};
private onPaste = (event: React.ClipboardEvent<Element>): void => {
const { onPaste } = this.props;
if (onPaste) {
onPaste(event);
}
}
return (
<Labelled
className={className}
help={help}
label={label}
style={style}
withEllipsis={withEllipsis}
withLabel={withLabel}
>
<SUIInput
action={isAction}
autoFocus={autoFocus}
className={
isEditable
? 'ui--Input edit icon'
: 'ui--Input'
}
defaultValue={
isUndefined(value)
? (defaultValue || '')
: undefined
}
disabled={isDisabled}
error={!isDisabled && isError}
hidden={isHidden}
id={name}
iconPosition={
isUndefined(icon)
? undefined
: 'left'
}
max={max}
maxLength={maxLength}
min={min}
name={name || stateName}
onBlur={_onBlur}
onChange={_onChange}
onKeyDown={_onKeyDown}
onKeyUp={_onKeyUp}
placeholder={placeholder}
readOnly={isReadOnly}
tabIndex={tabIndex}
type={type}
value={value}
>
<input
autoComplete={
type === 'password'
? 'new-password'
: 'off'
}
onPaste={_onPaste}
/>
{isEditable && (
<i className='edit icon' />
)}
{icon}
{children}
</SUIInput>
</Labelled>
);
}
export {

View File

@ -4,7 +4,8 @@
import React from 'react';
import styled from 'styled-components';
import IdentityIcon from '@polkadot/react-identicon';
import IdentityIcon from '../IdentityIcon';
interface Props {
address: string;

View File

@ -4,7 +4,7 @@
import { BareProps } from './types';
import React from 'react';
import React, { useState } from 'react';
import styled from 'styled-components';
import addressToAddress from './util/toAddress';
@ -17,46 +17,32 @@ interface Props extends BareProps {
onChange?: (address: string | null) => void;
}
interface State {
address: string | null;
isError: boolean;
}
function InputAddressSimple ({ className, help, label, onChange }: Props): React.ReactElement<Props> {
const [address, setAddress] = useState<string | null>(null);
class InputAddressSimple extends React.PureComponent<Props, State> {
public state: State = {
address: null,
isError: true
};
public render (): React.ReactNode {
const { className, help, label } = this.props;
const { address, isError } = this.state;
return (
<div className={className}>
<Input
help={help}
isError={isError}
label={label}
onChange={this.onChange}
/>
<IdentityIcon
className='ui--InputAddressSimpleIcon'
size={32}
value={address}
/>
</div>
);
}
private onChange = (_address: string): void => {
const { onChange } = this.props;
const _onChange = (_address: string): void => {
const address = addressToAddress(_address) || null;
this.setState({ address, isError: !address }, (): void => {
onChange && onChange(address);
});
}
setAddress(address);
onChange && onChange(address);
};
return (
<div className={className}>
<Input
help={help}
isError={!address}
label={label}
onChange={_onChange}
/>
<IdentityIcon
className='ui--InputAddressSimpleIcon'
size={32}
value={address}
/>
</div>
);
}
export default styled(InputAddressSimple)`

View File

@ -32,60 +32,48 @@ interface Props extends BareProps {
withLabel?: boolean;
}
interface State {
options: Option[];
}
function loadTags (): string[] {
return store.get('tags') || ['Default'];
}
function valueToOption (value: string): Option {
return { key: value, text: value, value };
}
const tags = loadTags();
const options = tags.map(valueToOption);
function saveTags (tags: string[]): void {
store.set('tags', tags);
}
const tags = loadTags();
function onAddTag (value: string): void {
tags.push(value);
options.push(valueToOption(value));
export default class InputTags extends React.PureComponent<Props, State> {
public state: State = {
options: tags.map((value): { key: string; text: string; value: string } => ({
key: value, text: value, value
}))
};
public render (): React.ReactNode {
const { className, defaultValue, help, isDisabled, isError, label, onBlur, onChange, onClose, placeholder, searchInput, value, withLabel } = this.props;
const { options } = this.state;
return (
<Dropdown
allowAdd={!isDisabled}
className={className}
defaultValue={defaultValue}
help={help}
isDisabled={isDisabled}
isError={isError}
isMultiple
label={label}
onAdd={this.onAdd}
onBlur={onBlur}
onChange={onChange}
onClose={onClose}
options={options}
placeholder={placeholder}
searchInput={searchInput}
value={value}
withLabel={withLabel}
/>
);
}
private onAdd = (value: string): void => {
tags.push(value);
saveTags(tags);
this.setState(({ options }: State): Pick<State, never> => ({
options: [...options, { key: value, text: value, value }]
}));
}
saveTags(tags);
}
export default function InputTags ({ className, defaultValue, help, isDisabled, isError, label, onBlur, onChange, onClose, placeholder, searchInput, value, withLabel }: Props): React.ReactElement<Props> {
return (
<Dropdown
allowAdd={!isDisabled}
className={className}
defaultValue={defaultValue}
help={help}
isDisabled={isDisabled}
isError={isError}
isMultiple
label={label}
onAdd={onAddTag}
onBlur={onBlur}
onChange={onChange}
onClose={onClose}
options={options}
placeholder={placeholder}
searchInput={searchInput}
value={value}
withLabel={withLabel}
/>
);
}

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, { useState } from 'react';
import { RouteComponentProps } from 'react-router';
import { withRouter } from 'react-router-dom';
import styled from 'styled-components';
@ -22,77 +22,57 @@ export interface InsetProps extends RouteComponentProps {
withBottomMargin?: boolean;
}
interface State {
isCollapsed: boolean;
}
function Inset ({ children, className, header, history, href, isCollapsible, isError, isSuccess, withBottomMargin, withTopMargin }: InsetProps): React.ReactElement<InsetProps> | null {
const [isCollapsed, setIsCollapsed] = useState(true);
class Inset extends React.PureComponent<InsetProps, State> {
public state: State = {
isCollapsed: true
};
public render (): React.ReactNode {
const { children, className, isCollapsible, isError, isSuccess, header, href, withBottomMargin, withTopMargin } = this.props;
const { isCollapsed } = this.state;
if (!children) {
return null;
}
return (
<div
className={
classes(
'ui--Inset',
href && 'as-link',
isCollapsible && 'collapsible',
(isError && !isSuccess) && 'error',
(!isError && isSuccess) && 'success',
withBottomMargin && 'bottom-margin',
withTopMargin && 'top-margin',
className
)
}
>
{isCollapsible && (
<div
className='header'
onClick={this.toggleCollapsed}
>
<h3>{header}</h3>
<Icon
className={classes(isCollapsed && 'collapsed')}
name='angle up'
/>
</div>
)}
<div
className={classes('children', (isCollapsible && isCollapsed) && 'collapsed')}
onClick={href ? this.onClick : undefined}
>
{children}
</div>
</div>
);
if (!children) {
return null;
}
private onClick = (): void => {
const { history, href } = this.props;
const _onClick = (): void => {
href && history.push(href);
}
};
const _toggleCollapsed = (): void => setIsCollapsed(!isCollapsed);
private toggleCollapsed = (): void => {
this.setState(({ isCollapsed }: State): Pick<State, never> => {
return {
isCollapsed: !isCollapsed
};
});
}
return (
<div
className={
classes(
'ui--Inset',
href && 'as-link',
isCollapsible && 'collapsible',
(isError && !isSuccess) && 'error',
(!isError && isSuccess) && 'success',
withBottomMargin && 'bottom-margin',
withTopMargin && 'top-margin',
className
)
}
>
{isCollapsible && (
<div
className='header'
onClick={_toggleCollapsed}
>
<h3>{header}</h3>
<Icon
className={classes(isCollapsed && 'collapsed')}
name='angle up'
/>
</div>
)}
<div
className={classes('children', (isCollapsible && isCollapsed) && 'collapsed')}
onClick={_onClick}
>
{children}
</div>
</div>
);
}
export default withRouter(
styled(Inset as React.ComponentClass<InsetProps, State>)`
styled(Inset)`
& {
box-shadow: 0 3px 3px rgba(0,0,0,.2);
position: relative;

View File

@ -34,8 +34,8 @@ const TYPES: Record<string, string> = {
};
function LinkPolkascan ({ className, data, t, type }: Props): React.ReactElement<Props> | null {
const { currentChain } = useContext(ApiContext);
const extChain = CHAINS[currentChain];
const { systemChain } = useContext(ApiContext);
const extChain = CHAINS[systemChain];
const extType = TYPES[type];
if (!extChain || !extType) {

View File

@ -4,132 +4,94 @@
import { TreasuryProposal as TreasuryProposalType } from '@polkadot/types/interfaces';
import { I18nProps } from '@polkadot/react-components/types';
import { ApiProps } from '@polkadot/react-api/types';
import React from 'react';
import { Option } from '@polkadot/types';
import React, { useContext, useEffect, useState } from 'react';
import { InputAddress, Labelled, Static } from '@polkadot/react-components';
import { withMulti, withApi } from '@polkadot/react-api';
import { ApiContext } from '@polkadot/react-api';
import { Option } from '@polkadot/types';
import { formatBalance } from '@polkadot/util';
import Inset, { InsetProps } from './Inset';
import translate from './translate';
interface Props extends I18nProps, ApiProps {
interface Props extends I18nProps {
className?: string;
asInset?: boolean;
insetProps?: Partial<InsetProps>;
onClick?: () => void;
proposalId: string;
proposalId?: string;
proposal?: TreasuryProposalType | null;
withLink?: boolean;
}
interface State {
proposal?: TreasuryProposalType | null;
}
function TreasuryProposal ({ className, asInset, insetProps, onClick, proposal, proposalId, t }: Props): React.ReactElement<Props> | null {
const [stateProposal, setProposal] = useState<TreasuryProposalType | null>(null);
const { api } = useContext(ApiContext);
class TreasuryProposal extends React.PureComponent<Props, State> {
public state: State = {};
public constructor (props: Props) {
super(props);
if (!props.proposal) {
this.fetchProposal();
}
}
public static getDerivedStateFromProps ({ proposal }: Props, state: State): State | null {
if (!state.proposal && proposal) {
return { proposal };
useEffect((): void => {
if (!proposal && proposalId) {
api.query.treasury
.proposals<Option<TreasuryProposalType>>(proposalId)
.then((proposal): TreasuryProposalType | null => proposal.unwrapOr(null))
.catch((): null => null)
.then(setProposal);
} else {
setProposal(proposal || null);
}
}, [proposal, proposalId]);
if (!stateProposal) {
return null;
}
public componentDidUpdate (): void {
this.fetchProposal();
}
const { bond, beneficiary, proposer, value } = stateProposal;
public render (): React.ReactNode {
const { className, asInset, insetProps, onClick, t } = this.props;
const { proposal } = this.state;
if (!proposal) {
return null;
}
const { bond, beneficiary, proposer, value } = proposal;
const inner = (
<>
<Labelled label={t('proposed by')}>
<InputAddress
isDisabled
defaultValue={proposer}
value={proposer}
withLabel={false}
/>
</Labelled>
<Labelled label={t('beneficiary')}>
<InputAddress
isDisabled
defaultValue={beneficiary}
value={beneficiary}
withLabel={false}
/>
</Labelled>
<Static label={t('value')}>
{formatBalance(value)}
</Static>
<Static label={t('bond')}>
{formatBalance(bond)}
</Static>
</>
);
if (asInset) {
return (
<Inset
className={className}
{...insetProps}
>
{inner}
</Inset>
);
}
const inner = (
<>
<Labelled label={t('proposed by')}>
<InputAddress
isDisabled
defaultValue={proposer}
value={proposer}
withLabel={false}
/>
</Labelled>
<Labelled label={t('beneficiary')}>
<InputAddress
isDisabled
defaultValue={beneficiary}
value={beneficiary}
withLabel={false}
/>
</Labelled>
<Static label={t('value')}>
{formatBalance(value)}
</Static>
<Static label={t('bond')}>
{formatBalance(bond)}
</Static>
</>
);
if (asInset) {
return (
<div
<Inset
className={className}
onClick={onClick && onClick}
{...insetProps}
>
{inner}
</div>
</Inset>
);
}
private fetchProposal = (): void => {
const { api, proposalId } = this.props;
if (!this.state.proposal && proposalId) {
api.query.treasury
.proposals<Option<TreasuryProposalType>>(proposalId)
.then((proposal): void => {
this.setState({
proposal: proposal.unwrapOr(null)
});
})
.catch((): void => {
console.error('Error fetching proposal');
});
}
}
return (
<div
className={className}
onClick={onClick && onClick}
>
{inner}
</div>
);
}
export default withMulti(
TreasuryProposal,
translate,
withApi
);
export default translate(TreasuryProposal);

View File

@ -4,35 +4,37 @@
import { Props } from '../types';
import React from 'react';
import React, { useState } from 'react';
import { Compact } from '@polkadot/types';
import { Button } from '@polkadot/react-components';
import BaseBytes from './BaseBytes';
import File from './File';
interface State {
isFileDrop: boolean;
}
export default function Bytes ({ className, defaultValue, isDisabled, isOptional, isError, label, name, onChange, onEnter, style, type, withLabel }: Props): React.ReactElement<Props> {
const [isFileDrop, setIsFileDrop] = useState(false);
export default class Bytes extends React.PureComponent<Props, State> {
public state: State = {
isFileDrop: false
const _toggleFile = (): void => setIsFileDrop(true);
const _onChangeFile = (value: Uint8Array): void => {
onChange && onChange({
isValid: value.length !== 0,
value: Compact.addLengthPrefix(value)
});
};
public render (): React.ReactNode {
const { isDisabled } = this.props;
const { isFileDrop } = this.state;
return !isDisabled && isFileDrop
? this.renderFile()
: this.renderInput();
}
private renderInput (): React.ReactNode {
const { className, defaultValue, isDisabled, isOptional, isError, label, name, onChange, onEnter, style, type, withLabel } = this.props;
return (
return !isDisabled && isFileDrop
? (
<File
className={className}
isDisabled={isDisabled}
isError={isError}
label={label}
onChange={_onChangeFile}
style={style}
withLabel={withLabel}
/>
)
: (
<BaseBytes
className={className}
defaultValue={defaultValue}
@ -49,54 +51,12 @@ export default class Bytes extends React.PureComponent<Props, State> {
withLabel={withLabel}
withLength
>
{this.renderFileButton()}
{!isDisabled && (
<Button
icon='file'
onClick={_toggleFile}
/>
)}
</BaseBytes>
);
}
private renderFileButton (): React.ReactNode {
const { isDisabled } = this.props;
if (isDisabled) {
return null;
}
return (
<Button
icon='file'
onClick={this.toggleFile}
/>
);
}
private renderFile (): React.ReactNode {
const { className, isDisabled, isError, label, style, withLabel } = this.props;
return (
<File
className={className}
isDisabled={isDisabled}
isError={isError}
label={label}
onChange={this.onChangeFile}
style={style}
withLabel={withLabel}
/>
);
}
private toggleFile = (): void => {
this.setState(({ isFileDrop }: State): State => ({
isFileDrop: !isFileDrop
} as unknown as State));
}
private onChangeFile = (value: Uint8Array): void => {
const { onChange } = this.props;
onChange && onChange({
isValid: value.length !== 0,
value: Compact.addLengthPrefix(value)
});
}
}

View File

@ -4,19 +4,15 @@
import { Props } from '../types';
import React from 'react';
export default class Null extends React.PureComponent<Props> {
public componentDidMount (): void {
const { onChange } = this.props;
import React, { useEffect } from 'react';
export default function Null ({ onChange }: Props): React.ReactElement<Props> | null {
useEffect((): void => {
onChange && onChange({
isValid: true,
value: null
});
}
}, []);
public render (): React.ReactNode {
return null;
}
return null;
}

View File

@ -3,9 +3,9 @@
// of the Apache-2.0 license. See the LICENSE file for details.
import { I18nProps } from '@polkadot/react-components/types';
import { BaseProps, Props as ComponentProps, ComponentMap } from '../types';
import { BaseProps, Props as CProps, ComponentMap } from '../types';
import React from 'react';
import React, { useRef } from 'react';
import { classes } from '@polkadot/react-components/util';
import translate from '@polkadot/react-components/translate';
import { isUndefined } from '@polkadot/util';
@ -17,52 +17,32 @@ interface Props extends I18nProps, BaseProps {
overrides?: ComponentMap;
}
interface State {
Component: React.ComponentType<ComponentProps> | null;
}
function Param ({ className, defaultValue, isDisabled, isOptional, name, onChange, onEnter, overrides, style, type }: Props): React.ReactElement<Props> | null {
const compRef = useRef<React.ComponentType<CProps> | null>(findComponent(type, overrides));
class ParamComponent extends React.PureComponent<Props, State> {
public state: State = {
Component: null
};
public static getDerivedStateFromProps ({ overrides, type }: Props): State {
return {
Component: !type
? null
: findComponent(type, overrides)
};
if (!compRef.current) {
return null;
}
public render (): React.ReactNode {
const { Component } = this.state;
if (Component === null) {
return null;
}
const { className, defaultValue, isDisabled, isOptional, name, onChange, onEnter, style, type } = this.props;
return (
<Component
className={classes('ui--Param', className)}
defaultValue={defaultValue}
key={`${name}:${type}`}
isDisabled={isDisabled}
isOptional={isOptional}
label={
isUndefined(name)
? type.type
: `${name}: ${type.type}`
}
name={name}
onChange={onChange}
onEnter={onEnter}
style={style}
type={type}
/>
);
}
return (
<compRef.current
className={classes('ui--Param', className)}
defaultValue={defaultValue}
key={`${name}:${type}`}
isDisabled={isDisabled}
isOptional={isOptional}
label={
isUndefined(name)
? type.type
: `${name}: ${type.type}`
}
name={name}
onChange={onChange}
onEnter={onEnter}
style={style}
type={type}
/>
);
}
export default translate(ParamComponent);
export default translate(Param);

View File

@ -3,27 +3,25 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { BareProps, CallProps } from '@polkadot/react-api/types';
import { BareProps } from '@polkadot/react-api/types';
import React from 'react';
import { Text } from '@polkadot/types';
import { withCalls } from '@polkadot/react-api';
import React, { useContext } from 'react';
import { ApiContext } from '@polkadot/react-api';
interface Props extends BareProps, CallProps {
interface Props extends BareProps {
children?: React.ReactNode;
label?: React.ReactNode;
system_chain?: Text;
}
export function Chain ({ children, className, label = '', style, system_chain = new Text('unknown') }: Props): React.ReactElement<Props> {
export default function Chain ({ children, className, label = '', style }: Props): React.ReactElement<Props> {
const { systemChain } = useContext(ApiContext);
return (
<div
className={className}
style={style}
>
{label}{system_chain.toString()}{children}
{label}{systemChain}{children}
</div>
);
}
export default withCalls<Props>('rpc.system.chain')(Chain);

View File

@ -3,27 +3,25 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { BareProps, CallProps } from '@polkadot/react-api/types';
import { BareProps } from '@polkadot/react-api/types';
import React from 'react';
import { Text } from '@polkadot/types';
import { withCalls } from '@polkadot/react-api';
import React, { useContext } from 'react';
import { ApiContext } from '@polkadot/react-api';
interface Props extends BareProps, CallProps {
interface Props extends BareProps {
children?: React.ReactNode;
label?: React.ReactNode;
system_name?: Text;
}
export function NodeName ({ children, className, label = '', style, system_name = new Text('unknown') }: Props): React.ReactElement<Props> {
export default function NodeName ({ children, className, label = '', style }: Props): React.ReactElement<Props> {
const { systemName } = useContext(ApiContext);
return (
<div
className={className}
style={style}
>
{label}{system_name.toString()}{children}
{label}{systemName}{children}
</div>
);
}
export default withCalls<Props>('rpc.system.name')(NodeName);

View File

@ -3,27 +3,25 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { BareProps, CallProps } from '@polkadot/react-api/types';
import { BareProps } from '@polkadot/react-api/types';
import React from 'react';
import { Text } from '@polkadot/types';
import { withCalls } from '@polkadot/react-api';
import React, { useContext } from 'react';
import { ApiContext } from '@polkadot/react-api';
interface Props extends BareProps, CallProps {
interface Props extends BareProps {
children?: React.ReactNode;
label?: React.ReactNode;
system_version?: Text;
}
export function NodeVersion ({ children, className, label = '', style, system_version = new Text('-') }: Props): React.ReactElement<Props> {
export default function NodeVersion ({ children, className, label = '', style }: Props): React.ReactElement<Props> {
const { systemVersion } = useContext(ApiContext);
return (
<div
className={className}
style={style}
>
{label}{system_version.toString()}{children}
{label}{systemVersion}{children}
</div>
);
}
export default withCalls<Props>('rpc.system.version')(NodeVersion);

View File

@ -8,7 +8,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { ExtraFees } from './types';
import BN from 'bn.js';
import React from 'react';
import React, { useState, useEffect } from 'react';
import { Compact, UInt } from '@polkadot/types';
import { withCalls, withMulti } from '@polkadot/react-api';
import { Icon } from '@polkadot/react-components';
@ -27,65 +27,59 @@ interface State extends ExtraFees {
isBelowMinimum: boolean;
}
export class Proposal extends React.PureComponent<Props, State> {
public state: State = {
extraFees: new BN(0),
extraAmount: new BN(0),
const ZERO = new BN(0);
export function Proposal ({ deposit, democracy_minimumDeposit = ZERO, onChange, t }: Props): React.ReactElement<Props> {
const [{ extraAmount, isBelowMinimum }, setState] = useState<State>({
extraFees: ZERO,
extraAmount: ZERO,
extraWarn: false,
isBelowMinimum: false
};
});
public static getDerivedStateFromProps ({
deposit,
democracy_minimumDeposit = new BN(0),
onChange
}: Props): State {
useEffect((): void => {
const extraAmount = deposit instanceof Compact
? deposit.toBn()
: deposit;
const isBelowMinimum = extraAmount.lt(democracy_minimumDeposit);
const update = {
extraAmount,
extraFees: new BN(0),
extraFees: ZERO,
extraWarn: isBelowMinimum
};
onChange(update);
return {
setState({
...update,
isBelowMinimum
};
}
});
}, [democracy_minimumDeposit]);
public render (): React.ReactNode {
const { democracy_minimumDeposit = new BN(0), t } = this.props;
const { extraAmount, isBelowMinimum } = this.state;
return (
<>
{
isBelowMinimum
? <div><Icon name='warning sign' />{t('The deposit is below the {{minimum}} minimum required for the proposal to be evaluated', {
replace: {
minimum: formatBalance(democracy_minimumDeposit)
}
})}</div>
: undefined
}
{
extraAmount.isZero()
? undefined
: <div><Icon name='arrow right' />{t('The deposit of {{deposit}} will be reserved until the proposal is completed', {
replace: {
deposit: formatBalance(extraAmount)
}
})}</div>
}
</>
);
}
return (
<>
{isBelowMinimum && (
<div>
<Icon name='warning sign' />
{t('The deposit is below the {{minimum}} minimum required for the proposal to be evaluated', {
replace: {
minimum: formatBalance(democracy_minimumDeposit)
}
})}
</div>
)}
{!extraAmount.isZero() && (
<div>
<Icon name='arrow right' />
{t('The deposit of {{deposit}} will be reserved until the proposal is completed', {
replace: {
deposit: formatBalance(extraAmount)
}
})}
</div>
)}
</>
);
}
export default withMulti(

View File

@ -8,7 +8,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { ExtraFees } from './types';
import BN from 'bn.js';
import React from 'react';
import React, { useEffect, useState } from 'react';
import { Compact, UInt } from '@polkadot/types';
import { withCalls, withMulti } from '@polkadot/react-api';
import { Icon } from '@polkadot/react-components';
@ -30,16 +30,16 @@ interface State extends ExtraFees {
isNoEffect: boolean;
}
export class Transfer extends React.PureComponent<Props, State> {
public state: State = {
export function Transfer ({ amount, balances_all = ZERO_BALANCE, fees, onChange, t }: Props): React.ReactElement<Props> {
const [{ isCreation, isNoEffect }, setState] = useState<State>({
extraFees: new BN(0),
extraAmount: new BN(0),
extraWarn: false,
isCreation: false,
isNoEffect: false
};
});
public static getDerivedStateFromProps ({ amount, balances_all = ZERO_BALANCE, fees, onChange }: Props): State {
useEffect((): void => {
let extraFees = new BN(fees.transferFee);
if (balances_all.votingBalance.isZero()) {
@ -58,47 +58,37 @@ export class Transfer extends React.PureComponent<Props, State> {
onChange(update);
return {
setState({
...update,
isCreation,
isNoEffect
};
}
});
}, [balances_all, fees]);
public render (): React.ReactNode {
const { fees, t } = this.props;
const { isCreation, isNoEffect } = this.state;
return (
<>
{
isNoEffect
? (
<div>
<Icon name='warning sign' />
{t('The final recipient balance is less or equal to {{existentialDeposit}} (the existential amount) and will not be reflected',
{
replace: {
existentialDeposit: formatBalance(fees.existentialDeposit)
}
}
)}
</div>
)
: undefined
}
{
isCreation
? <div><Icon name='warning sign' />{t('A fee of {{creationFee}} will be deducted from the sender since the destination account does not exist', {
replace: {
creationFee: formatBalance(fees.creationFee)
}
})}</div>
: undefined
}
</>
);
}
return (
<>
{isNoEffect && (
<div>
<Icon name='warning sign' />
{t('The final recipient balance is less or equal to {{existentialDeposit}} (the existential amount) and will not be reflected', {
replace: {
existentialDeposit: formatBalance(fees.existentialDeposit)
}
})}
</div>
)}
{isCreation && (
<div>
<Icon name='warning sign' />
{t('A fee of {{creationFee}} will be deducted from the sender since the destination account does not exist', {
replace: {
creationFee: formatBalance(fees.creationFee)
}
})}
</div>
)}
</>
);
}
export default withMulti(

View File

@ -51,6 +51,7 @@ const LENGTH_ADDRESS = 32 + 1; // publicKey + prefix
const LENGTH_ERA = 2; // assuming mortals
const LENGTH_SIGNATURE = 64; // assuming ed25519 or sr25519
const LENGTH_VERSION = 1; // 0x80 & version
const ZERO = new BN(0);
export const calcTxLength = (extrinsic?: IExtrinsic | null, nonce?: BN, tip?: BN): BN => {
return new BN(
@ -66,11 +67,11 @@ export const calcTxLength = (extrinsic?: IExtrinsic | null, nonce?: BN, tip?: BN
export class FeeDisplay extends React.PureComponent<Props, State> {
public state: State = {
allFees: new BN(0),
allTotal: new BN(0),
allFees: ZERO,
allTotal: ZERO,
allWarn: false,
extraAmount: new BN(0),
extraFees: new BN(0),
extraAmount: ZERO,
extraFees: ZERO,
extraWarn: false,
hasAvailable: false,
isRemovable: false,
@ -78,7 +79,7 @@ export class FeeDisplay extends React.PureComponent<Props, State> {
overLimit: false
};
public static getDerivedStateFromProps ({ accountId, balances_all = ZERO_BALANCE, api, extrinsic, balances_fees = ZERO_FEES_BALANCES, system_accountNonce = new BN(0), tip }: Props, prevState: State): State | null {
public static getDerivedStateFromProps ({ accountId, balances_all = ZERO_BALANCE, api, extrinsic, balances_fees = ZERO_FEES_BALANCES, system_accountNonce = ZERO, tip }: Props, prevState: State): State | null {
if (!accountId || !extrinsic) {
return null;
}
@ -91,10 +92,10 @@ export class FeeDisplay extends React.PureComponent<Props, State> {
const isSameExtrinsic = prevState.extMethod === extMethod && prevState.extSection === extSection;
const extraAmount = isSameExtrinsic
? prevState.extraAmount
: new BN(0);
: ZERO;
const extraFees = isSameExtrinsic
? prevState.extraFees
: new BN(0);
: ZERO;
const extraWarn = isSameExtrinsic
? prevState.extraWarn
: false;
@ -142,11 +143,9 @@ export class FeeDisplay extends React.PureComponent<Props, State> {
const feeClass = !hasAvailable || overLimit || isRemovable
? 'error'
: (
allWarn
? 'warning'
: 'normal'
);
: allWarn
? 'warning'
: 'normal';
// display all the errors, warning and information messages (in that order)
return (

View File

@ -5,7 +5,7 @@
import { I18nProps } from '@polkadot/react-components/types';
import { KeyringPair } from '@polkadot/keyring/types';
import React from 'react';
import React, { useState } from 'react';
import { Password } from '@polkadot/react-components';
import keyring from '@polkadot/ui-keyring';
@ -21,58 +21,34 @@ interface Props extends I18nProps {
value?: string | null;
}
interface State {
isError: boolean;
isInjected?: boolean;
isLocked: boolean;
pair?: KeyringPair;
function getPair (address?: string | null): KeyringPair | null {
try {
return keyring.getPair(address as string);
} catch (error) {
return null;
}
}
class Unlock extends React.PureComponent<Props, State> {
public state: State = {
isError: false,
isLocked: false
};
function Unlock ({ autoFocus, error, onChange, onEnter, password, t, tabIndex, value }: Props): React.ReactElement<Props> | null {
const [pair] = useState<KeyringPair | null>(getPair(value));
public static getDerivedStateFromProps ({ error, value }: Props): State | null {
const pair = keyring.getPair(value as string);
if (!pair) {
return null;
}
const { isLocked, meta: { isInjected = false } } = pair;
return {
isError: !!error,
isInjected,
isLocked,
pair
};
if (!pair || !(pair.isLocked) || pair.meta.isInjected) {
return null;
}
public render (): React.ReactNode {
const { autoFocus, onChange, onEnter, password, t, tabIndex } = this.props;
const { isError, isInjected, isLocked } = this.state;
if (isInjected || !isLocked) {
return null;
}
return (
<div className='ui--signer-Signer-Unlock'>
<Password
autoFocus={autoFocus}
isError={isError}
label={t('unlock account with password')}
onChange={onChange}
onEnter={onEnter}
tabIndex={tabIndex}
value={password}
/>
</div>
);
}
return (
<div className='ui--signer-Signer-Unlock'>
<Password
autoFocus={autoFocus}
isError={!!error}
label={t('unlock account with password')}
onChange={onChange}
onEnter={onEnter}
tabIndex={tabIndex}
value={password}
/>
</div>
);
}
export default translate(Unlock);