More functional component conversions (#1633)
* More functional component conversions * Cleanups * functional Elapsed * Status -> functional * Couple more * One more tiny one
This commit is contained in:
parent
2489521187
commit
a66577eda4
@ -21,6 +21,7 @@ import { isHex, u8aToHex } from '@polkadot/util';
|
||||
import { keyExtractSuri, mnemonicGenerate, mnemonicValidate, randomAsU8a } from '@polkadot/util-crypto';
|
||||
|
||||
import translate from '../translate';
|
||||
import CreateConfirmation from './CreateConfirmation';
|
||||
|
||||
interface Props extends ModalProps, ApiProps, I18nProps {
|
||||
seed?: string;
|
||||
@ -34,20 +35,23 @@ interface SeedOption {
|
||||
value: SeedType;
|
||||
}
|
||||
|
||||
interface State {
|
||||
interface AddressState {
|
||||
address: string;
|
||||
deriveError: string | null;
|
||||
derivePath: string;
|
||||
isNameValid: boolean;
|
||||
isSeedValid: boolean;
|
||||
pairType: KeypairType;
|
||||
seed: string;
|
||||
seedType: SeedType;
|
||||
}
|
||||
|
||||
interface State extends AddressState {
|
||||
isNameValid: boolean;
|
||||
isPassValid: boolean;
|
||||
isValid: boolean;
|
||||
name: string;
|
||||
pairType: KeypairType;
|
||||
password: string;
|
||||
seed: string;
|
||||
seedOptions: SeedOption[];
|
||||
seedType: SeedType;
|
||||
showWarning: boolean;
|
||||
tags: string[];
|
||||
}
|
||||
@ -87,6 +91,30 @@ function addressFromSeed (phrase: string, derivePath: string, pairType: KeypairT
|
||||
.address;
|
||||
}
|
||||
|
||||
function generateSeed (_seed: string | null, derivePath: string, seedType: SeedType, pairType: KeypairType): AddressState {
|
||||
const seed = ((): string => {
|
||||
switch (seedType) {
|
||||
case 'bip':
|
||||
return mnemonicGenerate();
|
||||
case 'dev':
|
||||
return DEV_PHRASE;
|
||||
default:
|
||||
return _seed || u8aToHex(randomAsU8a());
|
||||
}
|
||||
})();
|
||||
const address = addressFromSeed(seed, derivePath, pairType);
|
||||
|
||||
return {
|
||||
address,
|
||||
deriveError: null,
|
||||
derivePath,
|
||||
isSeedValid: true,
|
||||
pairType,
|
||||
seedType,
|
||||
seed
|
||||
};
|
||||
}
|
||||
|
||||
class Create extends React.PureComponent<Props, State> {
|
||||
public state: State;
|
||||
|
||||
@ -103,14 +131,25 @@ class Create extends React.PureComponent<Props, State> {
|
||||
seedOptions.push({ value: 'dev', text: t('Development') });
|
||||
}
|
||||
|
||||
const pairType = type || DEFAULT_TYPE;
|
||||
const seedType = seed ? 'raw' : 'bip';
|
||||
|
||||
this.state = {
|
||||
...(this.emptyState(seed || null, '', type || DEFAULT_TYPE) as State),
|
||||
seedOptions
|
||||
...generateSeed(seed || null, '', seedType, pairType),
|
||||
isNameValid: true,
|
||||
isPassValid: false,
|
||||
isValid: false,
|
||||
name: 'new account',
|
||||
password: '',
|
||||
seedOptions,
|
||||
showWarning: false,
|
||||
tags: []
|
||||
};
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, t } = this.props;
|
||||
const { address, isValid, showWarning } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@ -119,39 +158,34 @@ class Create extends React.PureComponent<Props, State> {
|
||||
open
|
||||
>
|
||||
<Modal.Header>{t('Add an account via seed')}</Modal.Header>
|
||||
{this.renderModal()}
|
||||
{showWarning && (
|
||||
<CreateConfirmation
|
||||
address={address}
|
||||
name={name}
|
||||
onCommit={this.onCommit}
|
||||
onHideWarning={this.onHideWarning}
|
||||
/>
|
||||
)}
|
||||
{this.renderInput()}
|
||||
{this.renderButtons()}
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
label={t('Cancel')}
|
||||
onClick={this.onDiscard}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isDisabled={!isValid}
|
||||
isPrimary
|
||||
label={t('Save')}
|
||||
onClick={this.onShowWarning}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private renderButtons (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { isValid } = this.state;
|
||||
|
||||
return (
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
label={t('Cancel')}
|
||||
labelIcon='cancel'
|
||||
onClick={this.onDiscard}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isDisabled={!isValid}
|
||||
isPrimary
|
||||
label={t('Save')}
|
||||
labelIcon='save'
|
||||
onClick={this.onShowWarning}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
);
|
||||
}
|
||||
|
||||
private renderInput (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { address, deriveError, derivePath, isNameValid, isPassValid, isSeedValid, name, pairType, password, seed, seedOptions, seedType } = this.state;
|
||||
@ -231,101 +265,15 @@ class Create extends React.PureComponent<Props, State> {
|
||||
onEnter={this.onCommit}
|
||||
value={derivePath}
|
||||
/>
|
||||
{
|
||||
deriveError
|
||||
? <Labelled label=''><article className='error'>{deriveError}</article></Labelled>
|
||||
: null
|
||||
}
|
||||
{deriveError && (
|
||||
<Labelled label=''><article className='error'>{deriveError}</article></Labelled>
|
||||
)}
|
||||
</details>
|
||||
</AddressRow>
|
||||
</Modal.Content>
|
||||
);
|
||||
}
|
||||
|
||||
private renderModal (): React.ReactNode {
|
||||
const { t } = this.props;
|
||||
const { address, name, showWarning } = this.state;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
dimmer='inverted'
|
||||
open={showWarning}
|
||||
>
|
||||
<Modal.Header>
|
||||
{t('Important notice')}
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<AddressRow
|
||||
defaultName={name}
|
||||
isInline
|
||||
value={address}
|
||||
>
|
||||
<p>{t('We will provide you with a generated backup file after your account is created. As long as you have access to your account you can always download this file later by clicking on "Backup" button from the Accounts section.')}</p>
|
||||
<p>{t('Please make sure to save this file in a secure location as it is required, together with your password, to restore your account.')}</p>
|
||||
</AddressRow>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
label={t('Cancel')}
|
||||
labelIcon='cancel'
|
||||
onClick={this.onHideWarning}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isPrimary
|
||||
label={t('Create and backup account')}
|
||||
labelIcon='sign-in'
|
||||
onClick={this.onCommit}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
private generateSeed (_seed: string | null, derivePath: string, seedType: SeedType, pairType: KeypairType): Partial<State> {
|
||||
const seed = ((): string => {
|
||||
switch (seedType) {
|
||||
case 'bip':
|
||||
return mnemonicGenerate();
|
||||
case 'dev':
|
||||
return DEV_PHRASE;
|
||||
default:
|
||||
return _seed || u8aToHex(randomAsU8a());
|
||||
}
|
||||
})();
|
||||
const address = addressFromSeed(seed, derivePath, pairType);
|
||||
|
||||
return {
|
||||
address,
|
||||
deriveError: null,
|
||||
derivePath,
|
||||
isSeedValid: true,
|
||||
seed
|
||||
};
|
||||
}
|
||||
|
||||
private emptyState (seed: string | null, derivePath: string, pairType: KeypairType): Partial<State> {
|
||||
const seedType = seed
|
||||
? 'raw'
|
||||
: this.state ? this.state.seedType : 'bip';
|
||||
|
||||
return {
|
||||
...this.generateSeed(seed, derivePath, seedType, pairType),
|
||||
isNameValid: true,
|
||||
isPassValid: false,
|
||||
isValid: false,
|
||||
name: 'new account',
|
||||
password: '',
|
||||
pairType,
|
||||
seedType,
|
||||
showWarning: false,
|
||||
tags: []
|
||||
};
|
||||
}
|
||||
|
||||
private nextState (newState: Partial<State>): void {
|
||||
this.setState(
|
||||
(prevState: State): State => {
|
||||
@ -439,7 +387,7 @@ class Create extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
this.setState(({ derivePath, pairType }: State): State => ({
|
||||
...(this.generateSeed(null, derivePath, seedType, pairType) as State),
|
||||
...(generateSeed(null, derivePath, seedType, pairType) as State),
|
||||
seedType
|
||||
}));
|
||||
}
|
||||
|
||||
57
packages/app-accounts/src/modals/CreateConfirmation.tsx
Normal file
57
packages/app-accounts/src/modals/CreateConfirmation.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
// Copyright 2017-2019 @polkadot/app-accounts 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 { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import { AddressRow, Button, Modal } from '@polkadot/react-components';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
address: string;
|
||||
name: string;
|
||||
onCommit: () => void;
|
||||
onHideWarning: () => void;
|
||||
}
|
||||
|
||||
function CreateConfirmation ({ address, name, onCommit, onHideWarning, t }: Props): React.ReactElement<Props> | null {
|
||||
return (
|
||||
<Modal
|
||||
dimmer='inverted'
|
||||
open
|
||||
>
|
||||
<Modal.Header>
|
||||
{t('Important notice')}
|
||||
</Modal.Header>
|
||||
<Modal.Content>
|
||||
<AddressRow
|
||||
defaultName={name}
|
||||
isInline
|
||||
value={address}
|
||||
>
|
||||
<p>{t('We will provide you with a generated backup file after your account is created. As long as you have access to your account you can always download this file later by clicking on "Backup" button from the Accounts section.')}</p>
|
||||
<p>{t('Please make sure to save this file in a secure location as it is required, together with your password, to restore your account.')}</p>
|
||||
</AddressRow>
|
||||
</Modal.Content>
|
||||
<Modal.Actions>
|
||||
<Button.Group>
|
||||
<Button
|
||||
isNegative
|
||||
label={t('Cancel')}
|
||||
onClick={onHideWarning}
|
||||
/>
|
||||
<Button.Or />
|
||||
<Button
|
||||
isPrimary
|
||||
label={t('Create and backup account')}
|
||||
onClick={onCommit}
|
||||
/>
|
||||
</Button.Group>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default translate(CreateConfirmation);
|
||||
@ -6,12 +6,12 @@ import React from 'react';
|
||||
import { Redirect } from 'react-router';
|
||||
import routing from '@polkadot/apps-routing';
|
||||
|
||||
type Props = {};
|
||||
|
||||
const defaultTo = `/${routing.default}`;
|
||||
|
||||
export default class NotFound extends React.PureComponent<{}> {
|
||||
public render (): React.ReactNode {
|
||||
return (
|
||||
<Redirect to={defaultTo} />
|
||||
);
|
||||
}
|
||||
export default function NotFound (): React.ReactElement<Props> {
|
||||
return (
|
||||
<Redirect to={defaultTo} />
|
||||
);
|
||||
}
|
||||
|
||||
@ -5,10 +5,10 @@
|
||||
|
||||
import { EventRecord } from '@polkadot/types/interfaces';
|
||||
import { KeyringOptions } from '@polkadot/ui-keyring/options/types';
|
||||
import { QueueStatus, QueueTx, QueueAction$Add } from '@polkadot/react-components/Status/types';
|
||||
import { ActionStatus, QueueStatus, QueueTx, QueueAction$Add } from '@polkadot/react-components/Status/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import keyringOption from '@polkadot/ui-keyring/options';
|
||||
import { Status as StatusDisplay } from '@polkadot/react-components';
|
||||
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
|
||||
@ -27,9 +27,9 @@ interface Props extends I18nProps {
|
||||
|
||||
let prevEventHash: string;
|
||||
|
||||
class Status extends React.PureComponent<Props> {
|
||||
public componentDidUpdate ({ optionsAll, queueAction, system_events, t }: Props): void {
|
||||
const eventHash = xxhashAsHex(stringToU8a(JSON.stringify(system_events || [])));
|
||||
function Status ({ optionsAll, queueAction, stqueue, system_events = [], t, txqueue }: Props): React.ReactElement<Props> {
|
||||
useEffect((): void => {
|
||||
const eventHash = xxhashAsHex(stringToU8a(JSON.stringify(system_events)));
|
||||
|
||||
if (!optionsAll || eventHash === prevEventHash) {
|
||||
return;
|
||||
@ -38,45 +38,46 @@ class Status extends React.PureComponent<Props> {
|
||||
prevEventHash = eventHash;
|
||||
|
||||
const addresses = optionsAll.account.map((account): string | null => account.value);
|
||||
const statusses = system_events
|
||||
.map(({ event: { data, method, section } }): ActionStatus | null => {
|
||||
if (section === 'balances' && method === 'Transfer') {
|
||||
const account = data[1].toString();
|
||||
|
||||
(system_events || []).forEach(({ event: { data, method, section } }): void => {
|
||||
if (section === 'balances' && method === 'Transfer') {
|
||||
const account = data[1].toString();
|
||||
if (addresses.includes(account)) {
|
||||
return {
|
||||
account,
|
||||
action: `${section}.${method}`,
|
||||
status: 'event',
|
||||
message: t('transfer received')
|
||||
};
|
||||
}
|
||||
} else if (section === 'democracy') {
|
||||
const index = data[0].toString();
|
||||
|
||||
if (addresses.includes(account)) {
|
||||
queueAction({
|
||||
account,
|
||||
return {
|
||||
action: `${section}.${method}`,
|
||||
status: 'event',
|
||||
message: t('transfer received')
|
||||
});
|
||||
message: t('update on #{{index}}', {
|
||||
replace: {
|
||||
index
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
} else if (section === 'democracy') {
|
||||
const index = data[0].toString();
|
||||
|
||||
queueAction({
|
||||
action: `${section}.${method}`,
|
||||
status: 'event',
|
||||
message: t('update on #{{index}}', {
|
||||
replace: {
|
||||
index
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter((item): boolean => !!item) as ActionStatus[];
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { stqueue, txqueue } = this.props;
|
||||
statusses.length && queueAction(statusses);
|
||||
}, [system_events]);
|
||||
|
||||
return (
|
||||
<StatusDisplay
|
||||
stqueue={stqueue}
|
||||
txqueue={txqueue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<StatusDisplay
|
||||
stqueue={stqueue}
|
||||
txqueue={txqueue}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
|
||||
@ -19,27 +19,6 @@ import NotFound from './NotFound';
|
||||
|
||||
interface Props extends I18nProps, ApiProps, RouteComponentProps {}
|
||||
|
||||
const Wrapper = styled.div`
|
||||
background: #fafafa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
padding: 0 2rem;
|
||||
|
||||
@media(max-width: 768px) {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Connecting = styled.div`
|
||||
padding: 1rem 0;
|
||||
`;
|
||||
|
||||
const unknown = {
|
||||
display: {
|
||||
needsApi: undefined
|
||||
@ -48,48 +27,63 @@ const unknown = {
|
||||
name: ''
|
||||
};
|
||||
|
||||
class Content extends React.Component<Props> {
|
||||
public render (): React.ReactNode {
|
||||
const { isApiConnected, isApiReady, location, t } = this.props;
|
||||
const app = location.pathname.slice(1) || '';
|
||||
const { Component, display: { needsApi }, name } = routing.routes.find((route): boolean =>
|
||||
!!(route && app.startsWith(route.name))
|
||||
) || unknown;
|
||||
function Content ({ isApiConnected, isApiReady, className, location, t }: Props): React.ReactElement<Props> {
|
||||
const app = location.pathname.slice(1) || '';
|
||||
const { Component, display: { needsApi }, name } = routing.routes.find((route): boolean =>
|
||||
!!(route && app.startsWith(route.name))
|
||||
) || unknown;
|
||||
|
||||
if (needsApi && (!isApiReady || !isApiConnected)) {
|
||||
return (
|
||||
<Wrapper>
|
||||
<Connecting>{t('Waiting for API to be connected and ready.')}</Connecting>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<QueueConsumer>
|
||||
{({ queueAction, stqueue, txqueue }: QueueProps): React.ReactNode => (
|
||||
<>
|
||||
<Component
|
||||
basePath={`/${name}`}
|
||||
location={location}
|
||||
onStatusChange={queueAction}
|
||||
/>
|
||||
<Status
|
||||
queueAction={queueAction}
|
||||
stqueue={stqueue}
|
||||
txqueue={txqueue}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</QueueConsumer>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={className}>
|
||||
{needsApi && (!isApiReady || !isApiConnected)
|
||||
? <div className='connecting'>{t('Waiting for API to be connected and ready.')}</div>
|
||||
: (
|
||||
<QueueConsumer>
|
||||
{({ queueAction, stqueue, txqueue }: QueueProps): React.ReactNode => (
|
||||
<>
|
||||
<Component
|
||||
basePath={`/${name}`}
|
||||
location={location}
|
||||
onStatusChange={queueAction}
|
||||
/>
|
||||
<Status
|
||||
queueAction={queueAction}
|
||||
stqueue={stqueue}
|
||||
txqueue={txqueue}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</QueueConsumer>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// React-router needs to be first, otherwise we have blocked updates
|
||||
export default withMulti(
|
||||
withRouter(Content),
|
||||
withRouter(
|
||||
styled(Content)`
|
||||
background: #fafafa;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
padding: 0 2rem;
|
||||
|
||||
@media(max-width: 768px) {
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.connecting {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
`
|
||||
),
|
||||
translate,
|
||||
// These API queries are used in a number of places, warm them up
|
||||
// to avoid constant un-/re-subscribe on these
|
||||
|
||||
@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
import { withApi, withMulti, withObservable } from '@polkadot/react-api';
|
||||
@ -19,66 +19,48 @@ interface Props extends I18nProps, ApiProps {
|
||||
allAccounts?: SubjectInfo;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasAccounts: boolean;
|
||||
isHidden: boolean;
|
||||
}
|
||||
function Accounts ({ allAccounts, isApiReady, className, t }: Props): React.ReactElement<Props> | null {
|
||||
const [hasAccounts, setHasAccounts] = useState(false);
|
||||
const [isHidden, setIsHidden] = useState(false);
|
||||
|
||||
class Accounts extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
hasAccounts: false,
|
||||
isHidden: false
|
||||
};
|
||||
|
||||
public static getDerivedStateFromProps ({ allAccounts }: Props, prevState: State): Pick<State, never> | null {
|
||||
useEffect((): void => {
|
||||
if (!allAccounts) {
|
||||
return null;
|
||||
return;
|
||||
}
|
||||
|
||||
const hasAccounts = Object.keys(allAccounts).length !== 0;
|
||||
const newHas = Object.keys(allAccounts).length !== 0;
|
||||
|
||||
if (hasAccounts === prevState.hasAccounts) {
|
||||
return null;
|
||||
if (newHas !== hasAccounts) {
|
||||
setHasAccounts(newHas);
|
||||
}
|
||||
}, [allAccounts]);
|
||||
|
||||
return {
|
||||
hasAccounts
|
||||
};
|
||||
const _onClose = (): void => setIsHidden(true);
|
||||
|
||||
if (!isApiReady || hasAccounts || isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { isApiReady, className, t } = this.props;
|
||||
const { hasAccounts, isHidden } = this.state;
|
||||
|
||||
if (!isApiReady || hasAccounts || isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseOverlay
|
||||
className={className}
|
||||
icon='users'
|
||||
>
|
||||
<p>{t("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")}</p>
|
||||
<p>
|
||||
<Link
|
||||
to='/accounts'
|
||||
onClick={this.onClose}
|
||||
>
|
||||
{t('Create an account now.')}
|
||||
</Link>
|
||||
</p>
|
||||
</BaseOverlay>
|
||||
);
|
||||
}
|
||||
|
||||
private onClose = (): void => {
|
||||
this.setState({ isHidden: true });
|
||||
}
|
||||
return (
|
||||
<BaseOverlay
|
||||
className={className}
|
||||
icon='users'
|
||||
>
|
||||
<p>{t("You don't have any accounts. Some features are currently hidden and will only become available once you have accounts.")}</p>
|
||||
<p>
|
||||
<Link
|
||||
to='/accounts'
|
||||
onClick={_onClose}
|
||||
>
|
||||
{t('Create an account now.')}
|
||||
</Link>
|
||||
</p>
|
||||
</BaseOverlay>
|
||||
);
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
styled(Accounts as React.ComponentClass<Props>)`
|
||||
styled(Accounts)`
|
||||
background: #fff6cb;
|
||||
border-color: #e7c000;
|
||||
color: #6b5900;
|
||||
|
||||
@ -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 styled from 'styled-components';
|
||||
import { Icon } from '@polkadot/react-components';
|
||||
|
||||
@ -12,47 +12,34 @@ interface Props {
|
||||
icon: string;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isHidden: boolean;
|
||||
}
|
||||
function BaseOverlay ({ children, className, icon }: Props): React.ReactElement<Props> | null {
|
||||
const [isHidden, setIsHidden] = useState(false);
|
||||
|
||||
class BaseOverlay extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isHidden: false
|
||||
};
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { children, className, icon } = this.props;
|
||||
const { isHidden } = this.state;
|
||||
const _onClose = (): void => setIsHidden(true);
|
||||
|
||||
if (isHidden) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='content'>
|
||||
<Icon
|
||||
className='contentIcon'
|
||||
name={icon as any}
|
||||
size='big'
|
||||
/>
|
||||
<div className='contentItem'>
|
||||
{children}
|
||||
</div>
|
||||
<Icon
|
||||
className='closeIcon'
|
||||
name='close'
|
||||
onClick={this.onClose}
|
||||
/>
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className='content'>
|
||||
<Icon
|
||||
className='contentIcon'
|
||||
name={icon as any}
|
||||
size='big'
|
||||
/>
|
||||
<div className='contentItem'>
|
||||
{children}
|
||||
</div>
|
||||
<Icon
|
||||
className='closeIcon'
|
||||
name='close'
|
||||
onClick={_onClose}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private onClose = (): void => {
|
||||
this.setState({ isHidden: true });
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default styled(BaseOverlay)`
|
||||
|
||||
@ -70,24 +70,28 @@ export default class Queue extends React.Component<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
public queueAction = (status: ActionStatus): number => {
|
||||
const id = ++nextId;
|
||||
const removeItem = this.clearAction(id);
|
||||
public queueAction = (status: ActionStatus | ActionStatus[]): number => {
|
||||
const todos = (Array.isArray(status) ? status : [status]).map((item: ActionStatus) => {
|
||||
const id = ++nextId;
|
||||
const removeItem = this.clearAction(id);
|
||||
|
||||
setTimeout(removeItem, REMOVE_TIMEOUT);
|
||||
|
||||
return {
|
||||
...item,
|
||||
id,
|
||||
isCompleted: false,
|
||||
removeItem
|
||||
};
|
||||
});
|
||||
|
||||
this.setState(
|
||||
(prevState: State): Pick<State, never> => ({
|
||||
stqueue: prevState.stqueue.concat({
|
||||
...status,
|
||||
id,
|
||||
isCompleted: false,
|
||||
removeItem
|
||||
})
|
||||
stqueue: prevState.stqueue.concat(...todos)
|
||||
})
|
||||
);
|
||||
|
||||
setTimeout(removeItem, REMOVE_TIMEOUT);
|
||||
|
||||
return id;
|
||||
return todos[0].id;
|
||||
}
|
||||
|
||||
private clearStatus (id: number): () => void {
|
||||
|
||||
@ -97,7 +97,7 @@ export type QueueTxPayloadAdd = (payload: SignerPayloadJSON, signerCb: SignerCal
|
||||
|
||||
export type QueueTxMessageSetStatus = (id: number, status: QueueTxStatus, result?: any, error?: Error) => void;
|
||||
|
||||
export type QueueAction$Add = (status: ActionStatus) => number;
|
||||
export type QueueAction$Add = (status: ActionStatus | ActionStatus[]) => number;
|
||||
|
||||
export interface QueueProps {
|
||||
stqueue: QueueStatus[];
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
import { BareProps } from '@polkadot/react-api/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { bnToBn } from '@polkadot/util';
|
||||
|
||||
type Ticker = (now: number) => void;
|
||||
@ -14,74 +14,68 @@ interface Props extends BareProps {
|
||||
value?: BN | Date | number;
|
||||
}
|
||||
|
||||
interface State {
|
||||
now?: number;
|
||||
}
|
||||
|
||||
const TICK_TIMEOUT = 100;
|
||||
const tickers = new Map<Elapsed, Ticker>();
|
||||
const tickers = new Map<number, Ticker>();
|
||||
|
||||
let lastNow = Date.now();
|
||||
let lastId = 0;
|
||||
|
||||
function tick (): void {
|
||||
const now = Date.now();
|
||||
lastNow = Date.now();
|
||||
|
||||
for (const ticker of tickers.values()) {
|
||||
ticker(now);
|
||||
ticker(lastNow);
|
||||
}
|
||||
|
||||
setTimeout(tick, TICK_TIMEOUT);
|
||||
}
|
||||
|
||||
function getDisplayValue (now = 0, value: BN | Date | number = 0): string {
|
||||
const tsValue = (
|
||||
value && (value as Date).getTime
|
||||
? (value as Date).getTime()
|
||||
: bnToBn(value as number).toNumber()
|
||||
) || 0;
|
||||
let display = '0.0s';
|
||||
|
||||
if (now && tsValue) {
|
||||
const elapsed = Math.max(Math.abs(now - tsValue), 0) / 1000;
|
||||
|
||||
if (elapsed < 15) {
|
||||
display = `${elapsed.toFixed(1)}s`;
|
||||
} else if (elapsed < 60) {
|
||||
display = `${elapsed | 0}s`;
|
||||
} else if (elapsed < 3600) {
|
||||
display = `${elapsed / 60 | 0}m`;
|
||||
} else {
|
||||
display = `${elapsed / 3600 | 0}h`;
|
||||
}
|
||||
}
|
||||
|
||||
return display;
|
||||
}
|
||||
|
||||
tick();
|
||||
|
||||
export default class Elapsed extends React.PureComponent<Props, State> {
|
||||
public state: State = {};
|
||||
export default function Elapsed ({ className, style, value }: Props): React.ReactElement<Props> {
|
||||
const [now, setNow] = useState(lastNow);
|
||||
|
||||
public componentDidMount (): void {
|
||||
tickers.set(this, (now: number): void => {
|
||||
this.setState({ now });
|
||||
});
|
||||
}
|
||||
useEffect((): () => void => {
|
||||
const id = lastId++;
|
||||
|
||||
public componentWillUnmount (): void {
|
||||
tickers.delete(this);
|
||||
}
|
||||
tickers.set(id, setNow);
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { className, style, value } = this.props;
|
||||
const { now } = this.state;
|
||||
return (): void => {
|
||||
tickers.delete(id);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={['ui--Elapsed', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{this.getDisplayValue(now, value)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private getDisplayValue (now = 0, value: BN | Date | number = 0): string {
|
||||
const tsValue = (
|
||||
value && (value as Date).getTime
|
||||
? (value as Date).getTime()
|
||||
: bnToBn(value as number).toNumber()
|
||||
) || 0;
|
||||
let display = '0.0s';
|
||||
|
||||
if (now && tsValue) {
|
||||
const elapsed = Math.max(Math.abs(now - tsValue), 0) / 1000;
|
||||
|
||||
if (elapsed < 15) {
|
||||
display = `${elapsed.toFixed(1)}s`;
|
||||
} else if (elapsed < 60) {
|
||||
display = `${elapsed | 0}s`;
|
||||
} else if (elapsed < 3600) {
|
||||
display = `${elapsed / 60 | 0}m`;
|
||||
} else {
|
||||
display = `${elapsed / 3600 | 0}h`;
|
||||
}
|
||||
}
|
||||
|
||||
return display;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={['ui--Elapsed', className].join(' ')}
|
||||
style={style}
|
||||
>
|
||||
{getDisplayValue(now, value)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user