Collapse unbonding, hover expands all at once (#1589)

* Collapse unbonding, hover expands all at once

* Return null (not 0)

* Linting
This commit is contained in:
Jaco Greeff 2019-09-06 15:53:21 +02:00 committed by GitHub
parent b125c379fd
commit 65a9d91ad0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 245 additions and 354 deletions

View File

@ -5,7 +5,7 @@
import { ActionStatus } from '@polkadot/react-components/Status/types';
import { I18nProps } from '@polkadot/react-components/types';
import React from 'react';
import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { AddressCard, AddressInfo, Button, ChainLock, Forget, Icon } from '@polkadot/react-components';
import keyring from '@polkadot/ui-keyring';
@ -20,145 +20,28 @@ interface Props extends I18nProps {
className?: string;
}
interface State {
genesisHash: string | null;
isBackupOpen: boolean;
isEditable: boolean;
isExternal: boolean;
isForgetOpen: boolean;
isPasswordOpen: boolean;
isTransferOpen: boolean;
}
function Account ({ address, className, t }: Props): React.ReactElement<Props> {
const [genesisHash, setGenesisHash] = useState<string | null>(null);
const [isBackupOpen, setIsBackupOpen] = useState(false);
const [isEditable, setIsEditable] = useState(false);
const [isExternal, setIsExternal] = useState(false);
const [isForgetOpen, setIsForgetOpen] = useState(false);
const [isPasswordOpen, setIsPasswordOpen] = useState(false);
const [isTransferOpen, setIsTransferOpen] = useState(false);
class Account extends React.PureComponent<Props, State> {
public state: State;
useEffect((): void => {
const account = keyring.getAccount(address);
public constructor (props: Props) {
super(props);
const account = keyring.getAccount(props.address);
this.state = {
genesisHash: (account && account.meta.genesisHash) || null,
isBackupOpen: false,
isEditable: account
? !(account.meta.isInjected)
: false,
isExternal: (account && account.meta.isExternal) || false,
isForgetOpen: false,
isPasswordOpen: false,
isTransferOpen: false
};
}
public render (): React.ReactNode {
const { address, className } = this.props;
const { isEditable } = this.state;
// FIXME It is a bit heavy-handled switching of being editable here completely
// (and removing the tags, however the keyring cannot save these)
return (
<AddressCard
buttons={this.renderButtons()}
className={className}
isEditable={isEditable}
type='account'
value={address}
withExplorer
withIndex
withTags
>
{this.renderModals()}
<AddressInfo
address={address}
withBalance
withExtended
/>
</AddressCard>
);
}
private renderModals (): React.ReactNode {
const { address } = this.props;
const { isBackupOpen, isForgetOpen, isPasswordOpen, isTransferOpen } = this.state;
if (!address) {
return null;
}
const modals = [];
if (isBackupOpen) {
modals.push(
<Backup
key='modal-backup-account'
onClose={this.toggleBackup}
address={address}
/>
);
}
if (isForgetOpen) {
modals.push(
<Forget
address={address}
onForget={this.onForget}
key='modal-forget-account'
onClose={this.toggleForget}
/>
);
}
if (isPasswordOpen) {
modals.push(
<ChangePass
address={address}
key='modal-change-pass'
onClose={this.togglePass}
/>
);
}
if (isTransferOpen) {
modals.push(
<Transfer
key='modal-transfer'
onClose={this.toggleTransfer}
senderId={address}
/>
);
}
return modals;
}
private toggleBackup = (): void => {
this.setState(({ isBackupOpen }: State): Pick<State, never> => ({
isBackupOpen: !isBackupOpen
}));
}
private toggleForget = (): void => {
this.setState(({ isForgetOpen }: State): Pick<State, never> => ({
isForgetOpen: !isForgetOpen
}));
}
private togglePass = (): void => {
this.setState(({ isPasswordOpen }: State): Pick<State, never> => ({
isPasswordOpen: !isPasswordOpen
}));
}
private toggleTransfer = (): void => {
this.setState(({ isTransferOpen }: State): Pick<State, never> => ({
isTransferOpen: !isTransferOpen
}));
}
private onForget = (): void => {
const { address, t } = this.props;
setGenesisHash((account && account.meta.genesisHash) || null);
setIsEditable((account && !(account.meta.isInjected)) || false);
setIsExternal((account && account.meta.isExternal) || false);
}, [address]);
const _toggleBackup = (): void => setIsBackupOpen(!isBackupOpen);
const _toggleForget = (): void => setIsForgetOpen(!isForgetOpen);
const _togglePass = (): void => setIsPasswordOpen(!isPasswordOpen);
const _toggleTransfer = (): void => setIsTransferOpen(!isTransferOpen);
const _onForget = (): void => {
if (!address) {
return;
}
@ -176,71 +59,113 @@ class Account extends React.PureComponent<Props, State> {
status.status = 'error';
status.message = error.message;
}
}
};
const _onGenesisChange = (genesisHash: string | null): void => {
const account = keyring.getPair(address);
private onGenesisChange = (genesisHash: string | null): void => {
const { address } = this.props;
account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });
};
this.setState({ genesisHash }, (): void => {
const account = keyring.getPair(address);
account && keyring.saveAccountMeta(account, { ...account.meta, genesisHash });
});
}
private renderButtons (): React.ReactNode {
const { t } = this.props;
const { genesisHash, isEditable, isExternal } = this.state;
return (
<div className='accounts--Account-buttons buttons'>
<div className='actions'>
{isEditable && (
// FIXME It is a bit heavy-handled switching of being editable here completely
// (and removing the tags, however the keyring cannot save these)
return (
<AddressCard
buttons={
<div className='accounts--Account-buttons buttons'>
<div className='actions'>
{isEditable && (
<Button
isNegative
onClick={_toggleForget}
icon='trash'
size='small'
tooltip={t('Forget this account')}
/>
)}
{isEditable && !isExternal && (
<>
<Button
icon='cloud download'
isPrimary
onClick={_toggleBackup}
size='small'
tooltip={t('Create a backup file for this account')}
/>
<Button
icon='key'
isPrimary
onClick={_togglePass}
size='small'
tooltip={t("Change this account's password")}
/>
</>
)}
<Button
isNegative
onClick={this.toggleForget}
icon='trash'
isPrimary
label={<><Icon name='paper plane' /> {t('send')}</>}
onClick={_toggleTransfer}
size='small'
tooltip={t('Forget this account')}
/>
)}
{isEditable && !isExternal && (
<>
<Button
icon='cloud download'
isPrimary
onClick={this.toggleBackup}
size='small'
tooltip={t('Create a backup file for this account')}
/>
<Button
icon='key'
isPrimary
onClick={this.togglePass}
size='small'
tooltip={t("Change this account's password")}
/>
</>
)}
<Button
isPrimary
label={<><Icon name='paper plane' /> {t('send')}</>}
onClick={this.toggleTransfer}
size='small'
tooltip={t('Send funds from this account')}
/>
</div>
{isEditable && !isExternal && (
<div className='others'>
<ChainLock
genesisHash={genesisHash}
onChange={this.onGenesisChange}
tooltip={t('Send funds from this account')}
/>
</div>
)}
</div>
);
}
{isEditable && !isExternal && (
<div className='others'>
<ChainLock
genesisHash={genesisHash}
onChange={_onGenesisChange}
/>
</div>
)}
</div>
}
className={className}
isEditable={isEditable}
type='account'
value={address}
withExplorer
withIndex
withTags
>
{address && (
<>
{isBackupOpen && (
<Backup
key='modal-backup-account'
onClose={_toggleBackup}
address={address}
/>
)}
{isForgetOpen && (
<Forget
address={address}
onForget={_onForget}
key='modal-forget-account'
onClose={_toggleForget}
/>
)}
{isPasswordOpen && (
<ChangePass
address={address}
key='modal-change-pass'
onClose={_togglePass}
/>
)}
{isTransferOpen && (
<Transfer
key='modal-transfer'
onClose={_toggleTransfer}
senderId={address}
/>
)}
</>
)}
<AddressInfo
address={address}
withBalance
withExtended
/>
</AddressCard>
);
}
export default translate(

View File

@ -6,7 +6,7 @@ import { I18nProps } from '@polkadot/react-components/types';
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
import { ComponentProps } from './types';
import React from 'react';
import React, { useState } from 'react';
import accountObservable from '@polkadot/ui-keyring/observable/accounts';
import { withMulti, withObservable } from '@polkadot/react-api';
import { Button, CardGrid } from '@polkadot/react-components';
@ -22,100 +22,69 @@ interface Props extends ComponentProps, I18nProps {
accounts?: SubjectInfo[];
}
interface State {
isCreateOpen: boolean;
isImportOpen: boolean;
isQrOpen: boolean;
}
function Overview ({ accounts = [], onStatusChange, t }: Props): React.ReactElement<Props> {
const [isCreateOpen, setIsCreateOpen] = useState(false);
const [isImportOpen, setIsImportOpen] = useState(false);
const [isQrOpen, setIsQrOpen] = useState(false);
const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && (Object.keys(accounts).length === 0);
class Overview extends React.PureComponent<Props, State> {
public constructor (props: Props) {
super(props);
const _toggleCreate = (): void => setIsCreateOpen(!isCreateOpen);
const _toggleImport = (): void => setIsImportOpen(!setIsImportOpen);
const _toggleQr = (): void => setIsQrOpen(!isQrOpen);
this.state = {
isCreateOpen: false,
isImportOpen: false,
isQrOpen: false
};
}
public render (): React.ReactNode {
const { accounts = [], onStatusChange, t } = this.props;
const { isCreateOpen, isImportOpen, isQrOpen } = this.state;
const emptyScreen = !(isCreateOpen || isImportOpen || isQrOpen) && (Object.keys(accounts).length === 0);
return (
<CardGrid
banner={<Banner />}
buttons={
<Button.Group>
<Button
isPrimary
label={t('Add account')}
onClick={this.toggleCreate}
/>
<Button.Or />
<Button
isPrimary
label={t('Restore JSON')}
onClick={this.toggleImport}
/>
<Button.Or />
<Button
isPrimary
label={t('Add via Qr')}
onClick={this.toggleQr}
/>
</Button.Group>
}
isEmpty={emptyScreen}
emptyText={t('No account yet?')}
>
{isCreateOpen && (
<CreateModal
onClose={this.toggleCreate}
onStatusChange={onStatusChange}
return (
<CardGrid
banner={<Banner />}
buttons={
<Button.Group>
<Button
isPrimary
label={t('Add account')}
onClick={_toggleCreate}
/>
)}
{isImportOpen && (
<ImportModal
onClose={this.toggleImport}
onStatusChange={onStatusChange}
<Button.Or />
<Button
isPrimary
label={t('Restore JSON')}
onClick={_toggleImport}
/>
)}
{isQrOpen && (
<QrModal
onClose={this.toggleQr}
onStatusChange={onStatusChange}
<Button.Or />
<Button
isPrimary
label={t('Add via Qr')}
onClick={_toggleQr}
/>
)}
{Object.keys(accounts).map((address): React.ReactNode => (
<Account
address={address}
key={address}
/>
))}
</CardGrid>
);
}
private toggleCreate = (): void => {
this.setState(({ isCreateOpen }): Pick<State, never> => ({
isCreateOpen: !isCreateOpen
}));
}
private toggleImport = (): void => {
this.setState(({ isImportOpen }): Pick<State, never> => ({
isImportOpen: !isImportOpen
}));
}
private toggleQr = (): void => {
this.setState(({ isQrOpen }): Pick<State, never> => ({
isQrOpen: !isQrOpen
}));
}
</Button.Group>
}
isEmpty={emptyScreen}
emptyText={t('No account yet?')}
>
{isCreateOpen && (
<CreateModal
onClose={_toggleCreate}
onStatusChange={onStatusChange}
/>
)}
{isImportOpen && (
<ImportModal
onClose={_toggleImport}
onStatusChange={onStatusChange}
/>
)}
{isQrOpen && (
<QrModal
onClose={_toggleQr}
onStatusChange={onStatusChange}
/>
)}
{Object.keys(accounts).map((address): React.ReactNode => (
<Account
address={address}
key={address}
/>
))}
</CardGrid>
);
}
export default withMulti(

View File

@ -216,7 +216,7 @@ export default function withCall<P extends ApiProps> (
// don't flood the console with the same errors each time, just do it once, then
// ignore it going forward
if (!errorred[error.message]) {
console.error(endpoint, '::', error);
console.warn(endpoint, '::', error);
errorred[error.message] = true;
}
@ -247,7 +247,7 @@ export default function withCall<P extends ApiProps> (
);
}
} catch (error) {
// console.error(endpoint, '::', error);
// console.warn(endpoint, '::', error);
}
}
@ -275,7 +275,7 @@ export default function withCall<P extends ApiProps> (
callUpdatedAt: Date.now()
});
} catch (error) {
// console.error(endpoint, '::', error.message);
// console.warn(endpoint, '::', error.message);
}
}

View File

@ -105,68 +105,43 @@ function renderExtended ({ balances_all, t, address, withExtended }: Props): Rea
);
}
function renderHexSessionId ({ t, withHexSessionId = null }: Props): React.ReactNode {
if (!withHexSessionId) {
return null;
}
return (
<>
<Label label={t('session keys')} />
<div className='result'>{withHexSessionId}</div>
</>
);
}
function renderRedeemButton ({ staking_info, t }: Props): React.ReactNode {
return (staking_info && staking_info.controllerId && (
<TxButton
accountId={staking_info.controllerId.toString()}
className='iconButton'
icon='lock'
size='small'
isPrimary
key='unlock'
params={[]}
tooltip={t('Redeem these funds')}
tx='staking.withdrawUnbonded'
/>
));
}
function renderRewardDestination ({ staking_info, withRewardDestination, t }: Props): React.ReactNode {
if (!withRewardDestination) {
return null;
}
return (
staking_info &&
staking_info.rewardDestination &&
<>
<Label label={t('reward destination')} />
<div className='result'>{staking_info.rewardDestination.toString().toLowerCase()}</div>
</>
);
}
function renderUnlocking ({ staking_info, t }: Props): React.ReactNode {
if (!staking_info || !staking_info.unlocking || !staking_info.unlocking.length) {
return null;
}
const total = staking_info.unlocking.reduce((total, { value }): BN => total.add(value), new BN(0));
if (total.eqn(0)) {
return null;
}
return (
staking_info &&
staking_info.unlocking &&
staking_info.unlocking.map(({ remainingBlocks, value }, index): React.ReactNode => (
<div key={index}>
{formatBalance(value)}
<Icon
name='info circle'
data-tip
data-for={`unlocking-trigger-${index}`}
/>
<Tooltip
text={t('{{remainingBlocks}} blocks left', { replace: { remainingBlocks } })}
trigger={`unlocking-trigger-${index}`}
/>
</div>
))
<div>
{formatBalance(total)}
<Icon
name='info circle'
data-tip
data-for='unlocking-trigger'
/>
<Tooltip
text={
<>
{staking_info.unlocking.map(({ remainingBlocks, value }, index): React.ReactNode => (
<div key={index}>
{t('{{value}}, {{remaining}} blocks left', {
replace: {
remaining: formatNumber(remainingBlocks),
value: formatBalance(value)
}
})}
</div>
))}
</>
}
trigger='unlocking-trigger'
/>
</div>
);
}
@ -233,7 +208,19 @@ function renderBalances (props: Props): React.ReactNode {
<Label label={t('redeemable')} />
<div className='result'>
{formatBalance(staking_info.redeemable)}
{renderRedeemButton(props)}
{staking_info.controllerId && (
<TxButton
accountId={staking_info.controllerId.toString()}
className='iconButton'
icon='lock'
size='small'
isPrimary
key='unlock'
params={[]}
tooltip={t('Redeem these funds')}
tx='staking.withdrawUnbonded'
/>
)}
</div>
</>
)}
@ -250,16 +237,26 @@ function renderBalances (props: Props): React.ReactNode {
}
function AddressInfo (props: Props): React.ReactElement<Props> {
const { className, children } = props;
const { className, children, staking_info, t, withHexSessionId, withRewardDestination } = props;
return (
<div className={className}>
<div className='column'>
{renderBalances(props)}
{renderHexSessionId(props)}
{withHexSessionId && (
<>
<Label label={t('session keys')} />
<div className='result'>{withHexSessionId}</div>
</>
)}
{renderValidatorPrefs(props)}
{renderRewardDestination(props)}
{withRewardDestination && staking_info && staking_info.rewardDestination && (
<>
<Label label={t('reward destination')} />
<div className='result'>{staking_info.rewardDestination.toString().toLowerCase()}</div>
</>
)}
</div>
{renderExtended(props)}
{children && (
<div className='column'>