Add hover help info for status dialogs (#893)

* Add hover help info for status dialogs

* Cleanup intentions -> waiting (actual nextup count)

* Format offline blockNumber
This commit is contained in:
Jaco Greeff 2019-04-05 11:46:28 +02:00 committed by GitHub
parent 9e5818ebdd
commit eee4432a0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 129 additions and 148 deletions

View File

@ -7,7 +7,7 @@ import { AppProps, I18nProps } from '@polkadot/ui-app/types';
import React from 'react';
import store from 'store';
import { getTypeRegistry } from '@polkadot/types';
import { Button, Editor, InputFile } from '@polkadot/ui-app';
import { Button, Editor, InputFile, Labelled } from '@polkadot/ui-app';
import { ActionStatus } from '@polkadot/ui-app/Status/types';
import { isJsonObject, stringToU8a, u8aToString } from '@polkadot/util';
@ -64,17 +64,14 @@ class Developer extends React.PureComponent<Props, State> {
</div>
<div className='ui--row'>
<div className='full'>
<div className='ui--Labelled'>
<label>{t('Manually enter your custom type definitions as valid JSON')}</label>
<div className='ui--Labelled-content'>
<Editor
className='editor'
code={code}
isValid={isJsonValid}
onEdit={this.onEditTypes}
/>
</div>
</div>
<Labelled label={t('Manually enter your custom type definitions as valid JSON')}>
<Editor
className='editor'
code={code}
isValid={isJsonValid}
onEdit={this.onEditTypes}
/>
</Labelled>>
</div>
</div>
<Button.Group>

View File

@ -96,6 +96,7 @@ class Bonding extends React.PureComponent<Props, State> {
<InputAddress
className='medium'
defaultValue={bondedId}
help={t('The controller is the account that will be used to control any nominating or validating actions')}
label={t('controller account')}
onChange={this.onChangeController}
value={controllerId}
@ -110,12 +111,14 @@ class Bonding extends React.PureComponent<Props, State> {
<InputBalance
autoFocus
className='medium'
help={t('The total amount of the stash balance that will be at stake in any forthcoming rounds (should be less than the total amount available)')}
label={t('value bonded')}
onChange={this.onChangeValue}
/>
<Dropdown
className='medium'
defaultValue={0}
help={t('The destination account for any payments as either a nominator or validator')}
label={t('payment destination')}
onChange={this.onChangeDestination}
options={stashOptions}

View File

@ -68,8 +68,9 @@ class Key extends React.PureComponent<Props, State> {
<Modal.Content className='ui--signer-Signer-Content'>
<InputAddress
className='medium'
help={t('Changing the key only takes effect at the start of the next session.')}
isDisabled
label={t('session account')}
label={t('session key')}
value={accountId}
/>
</Modal.Content>

View File

@ -37,8 +37,6 @@ class Staking extends React.PureComponent<Props, State> {
return null;
}
console.error('preferences', props.preferences);
const { unstakeThreshold, validatorPayment } = props.preferences;
return {
@ -103,7 +101,7 @@ class Staking extends React.PureComponent<Props, State> {
return (
<>
<Modal.Header>
{t('Staking')}
{t('Validating')}
</Modal.Header>
<Modal.Content className='ui--signer-Signer-Content'>
<InputAddress
@ -122,16 +120,18 @@ class Staking extends React.PureComponent<Props, State> {
autoFocus
bitLength={32}
className='medium'
help={t('The number of allowed slashes for this validator before being automatically unstaked (maximum of 10 allowed)')}
label={t('unstake threshold')}
onChange={this.onChangeThreshold}
value={
unstakeThreshold
? unstakeThreshold.toString()
: '0'
: '3'
}
/>
<InputBalance
className='medium'
help={t('Reward that validator takes up-front, the remainder is split between themselves and nominators')}
label={t('payment preferences')}
onChange={this.onChangePayment}
value={

View File

@ -11,6 +11,7 @@ import { AccountId, Balance, Option } from '@polkadot/types';
import { withCall, withMulti } from '@polkadot/ui-api/with';
import { AddressMini, AddressRow } from '@polkadot/ui-app';
import keyring from '@polkadot/ui-keyring';
import { formatNumber } from '@polkadot/util';
import translate from '../translate';
@ -100,10 +101,10 @@ class Address extends React.PureComponent<Props, State> {
{instances.toString()}
</div>
<div className='detail'>
{t('Reported offline {{instances}} times since block #{{blockNumber}}', {
{t('Reported offline {{instances}} times, last at block #{{blockNumber}}', {
replace: {
instances: instances.toString(),
blockNumber
blockNumber: formatNumber(blockNumber)
}
})}
</div>

View File

@ -2,14 +2,13 @@
// This software may be modified and distributed under the terms
// of the Apache-2.0 license. See the LICENSE file for details.
import { DerivedBalances, DerivedBalancesMap } from '@polkadot/api-derive/types';
import { DerivedBalancesMap } from '@polkadot/api-derive/types';
import { I18nProps } from '@polkadot/ui-app/types';
import BN from 'bn.js';
import React from 'react';
import SummarySession from '@polkadot/app-explorer/SummarySession';
import { SummaryBox, CardSummary } from '@polkadot/ui-app';
import { formatBalance } from '@polkadot/util';
import { withCall, withMulti } from '@polkadot/ui-api';
import translate from '../translate';
@ -25,6 +24,9 @@ type Props = I18nProps & {
class Summary extends React.PureComponent<Props> {
render () {
const { className, intentions, style, t, staking_validatorCount, validators } = this.props;
const waiting = intentions.length > validators.length
? (intentions.length - validators.length)
: 0;
return (
<SummaryBox
@ -35,94 +37,16 @@ class Summary extends React.PureComponent<Props> {
<CardSummary label={t('validators')}>
{validators.length}/{staking_validatorCount ? staking_validatorCount.toString() : '-'}
</CardSummary>
<CardSummary label={t('intentions')}>
{intentions.length}
<CardSummary label={t('waiting')}>
{waiting}
</CardSummary>
</section>
<section className='ui--media-medium'>
<SummarySession withBroken={false} />
</section>
{this.renderBalances()}
</SummaryBox>
);
}
private renderBalances () {
const { intentions, t } = this.props;
if (!intentions || !intentions.length) {
return null;
}
return (
<section className='ui--media-large'>
<CardSummary label={t('balances')}>
{this.renderBalancesCalculation()}
</CardSummary>
</section>
);
}
private renderBalancesCalculation () {
const { t } = this.props;
const intentionHigh = this.calcIntentionsHigh();
const validatorLow = this.calcValidatorLow();
const nominatedLow = validatorLow && validatorLow.nominatedBalance.gtn(0)
? `(+${formatBalance(validatorLow.nominatedBalance)})`
: '';
const nominatedHigh = intentionHigh && intentionHigh.nominatedBalance.gtn(0)
? `(+${formatBalance(intentionHigh.nominatedBalance)})`
: '';
return (
<div className='staking--Summary-text'>
<div>{t('lowest validator {{validatorLow}}', {
replace: {
validatorLow: validatorLow && validatorLow.stakingBalance
? `${formatBalance(validatorLow.stakingBalance)} ${nominatedLow}`
: '-'
}
})}</div>
<div>{t('highest intention {{intentionHigh}}', {
replace: {
intentionHigh: intentionHigh
? `${formatBalance(intentionHigh.stakingBalance)} ${nominatedHigh}`
: '-'
}
})}</div>
</div>
);
}
private calcIntentionsHigh (): DerivedBalances | null {
const { balances, intentions, validators } = this.props;
return intentions.reduce((high: DerivedBalances | null, addr) => {
const balance = validators.includes(addr) || !balances[addr]
? null
: balances[addr];
if (high === null || (balance && high.stakingBalance.lt(balance.stakingBalance))) {
return balance;
}
return high;
}, null);
}
private calcValidatorLow (): DerivedBalances | null {
const { balances, validators } = this.props;
return validators.reduce((low: DerivedBalances | null, addr) => {
const balance = balances[addr] || null;
if (low === null || (balance && low.stakingBalance.gt(balance.stakingBalance))) {
return balance;
}
return low;
}, null);
}
}
export default withMulti(

View File

@ -75,13 +75,14 @@ type ProgressProps = {
type Props = BareProps & {
children?: React.ReactNode,
help?: React.ReactNode,
label: React.ReactNode,
progress?: ProgressProps
};
export default class CardSummary extends React.PureComponent<Props> {
render () {
const { children, className, label, progress, style } = this.props;
const { children, className, help, label, progress, style } = this.props;
const value = progress && progress.value;
const total = progress && progress.total;
const left = progress && !isUndefined(value) && !isUndefined(total) && value.gten(0) && total.gtn(0)
@ -110,6 +111,7 @@ export default class CardSummary extends React.PureComponent<Props> {
style={style}
>
<Labelled
help={help}
isSmall
label={label}
>

View File

@ -14,6 +14,7 @@ import Labelled from './Labelled';
type Props<Option> = BareProps & {
defaultValue?: any,
help?: React.ReactNode,
isButton?: boolean,
isDisabled?: boolean,
isError?: boolean,
@ -54,7 +55,7 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
}
render () {
const { className, defaultValue, isButton, isDisabled, isError, label, onSearch, options, placeholder, style, withLabel, value } = this.props;
const { className, defaultValue, help, isButton, isDisabled, isError, label, onSearch, options, placeholder, style, withLabel, value } = this.props;
const dropdown = (
<SUIDropdown
button={isButton}
@ -84,6 +85,7 @@ export default class Dropdown<Option> extends React.PureComponent<Props<Option>>
: (
<Labelled
className={classes('ui--Dropdown', className)}
help={help}
label={label}
style={style}
withLabel={withLabel}

View File

@ -16,6 +16,7 @@ type Props = BareProps & {
autoFocus?: boolean,
children?: React.ReactNode,
defaultValue?: any,
help?: React.ReactNode,
icon?: React.ReactNode,
isAction?: boolean,
isDisabled?: boolean,
@ -83,11 +84,12 @@ export default class Input extends React.PureComponent<Props, State> {
};
render () {
const { autoFocus = false, children, className, defaultValue, icon, isEditable = false, isAction = false, isDisabled = false, isError = false, isHidden = false, label, max, maxLength, min, name, placeholder, style, tabIndex, type = 'text', value, withLabel } = this.props;
const { autoFocus = false, children, className, defaultValue, help, icon, isEditable = false, isAction = false, isDisabled = false, isError = false, isHidden = false, label, max, maxLength, min, name, placeholder, style, tabIndex, type = 'text', value, withLabel } = this.props;
return (
<Labelled
className={className}
help={help}
label={label}
style={style}
withLabel={withLabel}

View File

@ -20,6 +20,7 @@ import addressToAddress from '../util/toAddress';
type Props = BareProps & {
defaultValue?: string | null,
help?: React.ReactNode,
hideAddress?: boolean;
isDisabled?: boolean,
isError?: boolean,
@ -105,7 +106,7 @@ class InputAddress extends React.PureComponent<Props, State> {
}
render () {
const { className, defaultValue, hideAddress = false, isDisabled = false, isError, label, optionsAll, type = DEFAULT_TYPE, style, withLabel } = this.props;
const { className, defaultValue, help, hideAddress = false, isDisabled = false, isError, label, optionsAll, type = DEFAULT_TYPE, style, withLabel } = this.props;
const { value } = this.state;
const hasOptions = optionsAll && Object.keys(optionsAll[type]).length !== 0;
@ -131,6 +132,7 @@ class InputAddress extends React.PureComponent<Props, State> {
? undefined
: actualValue
}
help={help}
isDisabled={isDisabled}
isError={isError}
label={label}

View File

@ -12,6 +12,7 @@ import { InputNumber } from '@polkadot/ui-app';
type Props = BareProps & {
autoFocus?: boolean,
defaultValue?: BN | string,
help?: React.ReactNode,
isDisabled?: boolean,
isError?: boolean,
label?: any,
@ -25,7 +26,7 @@ const DEFAULT_BITLENGTH = BitLengthOption.CHAIN_SPEC as BitLength;
export default class InputBalance extends React.PureComponent<Props> {
render () {
const { autoFocus, className, defaultValue, isDisabled, isError, label, onChange, placeholder, style, value, withLabel } = this.props;
const { autoFocus, className, defaultValue, help, isDisabled, isError, label, onChange, placeholder, style, value, withLabel } = this.props;
return (
<InputNumber
@ -33,6 +34,7 @@ export default class InputBalance extends React.PureComponent<Props> {
className={className}
bitLength={DEFAULT_BITLENGTH}
defaultValue={defaultValue}
help={help}
isDisabled={isDisabled}
isError={isError}
isSi

View File

@ -21,6 +21,7 @@ import sectionOptions from './options/section';
type Props = ApiProps & I18nProps & {
defaultValue: MethodFunction,
help?: React.ReactNode,
isDisabled?: boolean,
isError?: boolean,
isPrivate?: boolean,
@ -54,7 +55,7 @@ class InputExtrinsic extends React.PureComponent<Props, State> {
}
render () {
const { api, className, label, style, withLabel } = this.props;
const { api, className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
@ -63,6 +64,7 @@ class InputExtrinsic extends React.PureComponent<Props, State> {
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>

View File

@ -17,6 +17,7 @@ type Props = BareProps & WithTranslation & {
// i.e. MIME types: 'application/json, text/plain', or '.json, .txt'
accept?: string,
clearContent?: boolean,
help?: React.ReactNode,
isDisabled?: boolean,
isError?: boolean,
label: string,
@ -42,11 +43,12 @@ class InputFile extends React.PureComponent<Props, State> {
state: State = {};
render () {
const { accept, className, clearContent, isDisabled, isError = false, label, placeholder, t, withLabel } = this.props;
const { accept, className, clearContent, help, isDisabled, isError = false, label, placeholder, t, withLabel } = this.props;
const { file } = this.state;
return (
<Labelled
help={help}
label={label}
withLabel={withLabel}
>

View File

@ -18,6 +18,7 @@ type Props = BareProps & I18nProps & {
autoFocus?: boolean,
bitLength?: BitLength,
defaultValue?: BN | string,
help?: React.ReactNode,
isDisabled?: boolean,
isError?: boolean,
isSi?: boolean,
@ -83,7 +84,7 @@ class InputNumber extends React.PureComponent<Props, State> {
}
render () {
const { bitLength = DEFAULT_BITLENGTH, className, defaultValue = '0', isSi, isDisabled, maxLength, style, t } = this.props;
const { bitLength = DEFAULT_BITLENGTH, className, defaultValue = '0', help, isSi, isDisabled, maxLength, style, t } = this.props;
const { isValid } = this.state;
const maxValueLength = this.maxValue(bitLength).toString().length - 1;
const value = this.state.defaultValue || defaultValue;
@ -97,6 +98,7 @@ class InputNumber extends React.PureComponent<Props, State> {
? undefined
: value
}
help={help}
isAction={isSi}
isDisabled={isDisabled}
isError={!isValid}

View File

@ -22,6 +22,7 @@ import sectionOptions from './options/section';
type Props = I18nProps & {
defaultValue: RpcMethod,
help?: React.ReactNode,
isError?: boolean,
label: React.ReactNode,
onChange?: (value: RpcMethod) => void,
@ -50,7 +51,7 @@ class InputRpc extends React.PureComponent<Props, State> {
}
render () {
const { className, label, style, withLabel } = this.props;
const { className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
@ -59,6 +60,7 @@ class InputRpc extends React.PureComponent<Props, State> {
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>

View File

@ -23,6 +23,7 @@ import sectionOptions from './options/section';
type Props = ApiProps & I18nProps & {
defaultValue: StorageFunction,
help?: React.ReactNode,
isError?: boolean,
label: React.ReactNode,
onChange?: (value: StorageFunction) => void,
@ -51,7 +52,7 @@ class InputStorage extends React.PureComponent<Props, State> {
}
render () {
const { className, label, style, withLabel } = this.props;
const { className, help, label, style, withLabel } = this.props;
const { optionsMethod, optionsSection, value } = this.state;
return (
@ -60,6 +61,7 @@ class InputStorage extends React.PureComponent<Props, State> {
style={style}
>
<Labelled
help={help}
label={label}
withLabel={withLabel}
>

View File

@ -5,10 +5,13 @@
import { BareProps } from './types';
import React from 'react';
import styled from 'styled-components';
import Icon from './Icon';
import { classes } from './util';
type Props = BareProps & {
help?: React.ReactNode,
isHidden?: boolean,
isSmall?: boolean,
label?: React.ReactNode,
@ -20,9 +23,63 @@ const defaultLabel: any = (// node?
<div>&nbsp;</div>
);
const Wrapper = styled.div`
align-items: center;
display: flex;
flex: 1 1;
text-align: left;
&.label-small {
display: block;
> label {
margin: 0;
min-width: 0;
padding-right: 0;
}
}
> .ui--Labelled-content {
box-sizing: border-box;
flex: 1 1;
min-width: 0;
}
> label {
flex: 0 0 15rem;
min-width: 15rem;
padding-right: 0.5rem;
position: relative;
text-align: right;
z-index: 1;
.help-hover {
background: #4e4e4e;
border-radius: 0.25rem;
color: #eee;
display: none;
padding: 0.5rem 1rem;
position: absolute;
text-align: left;
top: 0.5rem;
left: 2.5rem;
right: -5rem;
z-index: 10;
}
.icon.help {
margin-right: 0;
}
&.with-help:hover .help-hover {
display: block;
}
}
`;
export default class Labelled extends React.PureComponent<Props> {
render () {
const { className, children, isSmall, isHidden, label = defaultLabel, style, withLabel = true } = this.props;
const { className, children, help, isSmall, isHidden, label = defaultLabel, style, withLabel = true } = this.props;
if (isHidden) {
return null;
@ -32,16 +89,20 @@ export default class Labelled extends React.PureComponent<Props> {
);
}
const labelNode = help
? <label className='with-help'>{label} <Icon name='help circle' /><div className='help-hover'>{help}</div></label>
: <label>{label}</label>;
return (
<div
<Wrapper
className={classes('ui--Labelled', isSmall ? 'label-small' : '', className)}
style={style}
>
<label>{label}</label>
{labelNode}
<div className='ui--Labelled-content'>
{children}
</div>
</div>
</Wrapper>
);
}
}

View File

@ -12,6 +12,7 @@ import { classes } from './util';
type Props = BareProps & {
children?: React.ReactNode,
help?: React.ReactNode,
isError?: boolean,
isHidden?: boolean,
label?: any, // node?
@ -22,11 +23,12 @@ type Props = BareProps & {
export default class Output extends React.PureComponent<Props> {
render () {
const { className, children, isError = false, isHidden, label, style, value, withCopy = false, withLabel } = this.props;
const { className, children, help, isError = false, isHidden, label, style, value, withCopy = false, withLabel } = this.props;
return (
<Labelled
className={className}
help={help}
isHidden={isHidden}
label={label}
style={style}

View File

@ -11,6 +11,7 @@ import Labelled from './Labelled';
type Props = BareProps & {
children?: React.ReactNode,
defaultValue?: any,
help?: React.ReactNode,
isDisabled?: boolean,
isError?: boolean,
isHidden?: boolean,
@ -21,11 +22,12 @@ type Props = BareProps & {
export default class Static extends React.PureComponent<Props> {
render () {
const { className, children, defaultValue, isHidden, label, style, value, withLabel } = this.props;
const { className, children, defaultValue, help, isHidden, label, style, value, withLabel } = this.props;
return (
<Labelled
className={className}
help={help}
isHidden={isHidden}
label={label}
style={style}

View File

@ -253,36 +253,6 @@ header .ui--Button-Group {
}
}
.ui--Labelled {
align-items: center;
display: flex;
flex: 1 1;
text-align: left;
&.label-small {
display: block;
> label {
margin: 0;
min-width: 0;
padding-right: 0;
}
}
> .ui--Labelled-content {
box-sizing: border-box;
flex: 1 1;
min-width: 0;
}
> label {
flex: 0 0 15rem;
min-width: 15rem;
padding-right: 0.5rem;
text-align: right;
}
}
.ui--Static {
overflow: hidden;
word-break: break-all;
@ -363,4 +333,4 @@ header .ui--Button-Group {
.ui.tiny.progress {
font-size: .5rem;
}
}