Add extra Input types to the UI (#2324)

* fix typo

* WIP introduce float input

* remove si dropdown for float input

* assume floats to have si 1 billion

* use correct SI name and parse decimal

* fix linting issues

* remove VecDeque from Vector UI component mapping
This commit is contained in:
Alexander Popiak 2020-03-30 07:31:36 +02:00 committed by GitHub
parent 30d25e69a8
commit 7a159976f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 145 additions and 12 deletions

View File

@ -0,0 +1,76 @@
// Copyright 2017-2020 @polkadot/react-components 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 { BareProps, BitLength } from './types';
import BN from 'bn.js';
import React from 'react';
import styled from 'styled-components';
import { BitLengthOption } from '@polkadot/react-components/constants';
import { InputNumber } from '@polkadot/react-components';
import { formatBalance } from '@polkadot/util';
interface Props extends BareProps {
autoFocus?: boolean;
defaultValue?: BN | string;
help?: React.ReactNode;
isDisabled?: boolean;
isError?: boolean;
isFull?: boolean;
isZeroable?: boolean;
label?: React.ReactNode;
labelExtra?: React.ReactNode;
maxValue?: BN;
onChange?: (value?: BN) => void;
onEnter?: () => void;
onEscape?: () => void;
placeholder?: string;
value?: BN | string;
withEllipsis?: boolean;
withLabel?: boolean;
withMax?: boolean;
}
const DEFAULT_BITLENGTH = BitLengthOption.CHAIN_SPEC as BitLength;
function InputFloat ({ autoFocus, className, defaultValue: inDefault, help, isDisabled, isError, isFull, isZeroable = true, label, labelExtra, maxValue, onChange, onEnter, onEscape, placeholder, style, value, withEllipsis, withLabel, withMax }: Props): React.ReactElement<Props> {
const defaultValue = inDefault;
const multiplier = formatBalance.findSi('G');
return (
<InputNumber
autoFocus={autoFocus}
className={`ui--InputFloat ${className}`}
bitLength={DEFAULT_BITLENGTH}
defaultValue={defaultValue}
defaultSi={multiplier}
help={help}
isDecimal={true}
isDisabled={isDisabled}
isError={isError}
isFull={isFull}
isZeroable={isZeroable}
isSi={false}
label={label}
labelExtra={labelExtra}
maxValue={maxValue}
onChange={onChange}
onEnter={onEnter}
onEscape={onEscape}
placeholder={placeholder}
style={style}
value={value}
withEllipsis={withEllipsis}
withLabel={withLabel}
withMax={withMax}
/>
);
}
export default styled(InputFloat)`
&&:not(.label-small) .labelExtra {
right: 6.5rem;
}
`;

View File

@ -22,6 +22,7 @@ interface Props extends BareProps {
autoFocus?: boolean;
bitLength?: BitLength;
defaultValue?: BN | string;
defaultSi?: SiDef;
help?: React.ReactNode;
isDisabled?: boolean;
isError?: boolean;
@ -110,22 +111,26 @@ function inputToBn (input: string, si: SiDef | null, props: Props): [BN, boolean
const [siPower, basePower, siUnitPower] = getSiPowers(si);
// eslint-disable-next-line @typescript-eslint/prefer-regexp-exec
const isDecimalValue = input.match(/^(\d+)\.(\d+)$/);
const decimalMatch = input.match(/^(\d+)[.,](\d+)$/);
let result;
if (isDecimalValue) {
if (siUnitPower - isDecimalValue[2].length < -basePower) {
if (decimalMatch) {
const intString = decimalMatch[1];
const fractionString = decimalMatch[2];
if (siUnitPower - fractionString.length < -basePower) {
result = new BN(-1);
}
const div = new BN(input.replace(/\.\d*$/, ''));
const modString = input.replace(/^\d+\./, '');
const mod = new BN(modString);
const div = new BN(intString);
const mod = new BN(fractionString);
result = div
.mul(TEN.pow(siPower))
.add(mod.mul(TEN.pow(new BN(basePower + siUnitPower - modString.length))));
// multiply whole number part with 10^SI
const divMul = div.mul(TEN.pow(new BN(siUnitPower)));
// multiply the fractional part with 10^(SI-length)
const modMul = mod.mul(TEN.pow(new BN(siUnitPower - fractionString.length)));
result = divMul.add(modMul);
} else {
result = new BN(input.replace(/[^\d]/g, ''))
.mul(TEN.pow(siPower));
@ -195,7 +200,7 @@ function isNewPropsValue (propsValue: BN | string, value: string, valueBn: BN):
function InputNumber (props: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { bitLength = DEFAULT_BITLENGTH, className, defaultValue = ZERO, help, isDecimal, isFull, isSi, isDisabled, isError = false, maxLength, maxValue, onChange, onEnter, onEscape, placeholder, style, value: propsValue } = props;
const { bitLength = DEFAULT_BITLENGTH, className, defaultValue = ZERO, defaultSi, help, isDecimal, isFull, isSi, isDisabled, isError = false, maxLength, maxValue, onChange, onEnter, onEscape, placeholder, style, value: propsValue } = props;
const [si, setSi] = useState<SiDef | null>(isSi ? formatBalance.findSi('-') : null);
const [isPreKeyDown, setIsPreKeyDown] = useState(false);
@ -254,6 +259,11 @@ function InputNumber (props: Props): React.ReactElement<Props> {
}
};
useEffect((): void => {
if (defaultSi) {
setSi(defaultSi);
}
}, [defaultSi]);
const _onSelectSiUnit = (siUnit: string): void => {
setSi(formatBalance.findSi(siUnit));
};
@ -294,7 +304,7 @@ function InputNumber (props: Props): React.ReactElement<Props> {
{t('Max')}
</Button>
) */}
{!!si && (
{isSi && !!si && (
<Dropdown
dropdownClassName='ui--SiDropdown'
isButton

View File

@ -51,6 +51,7 @@ export { default as InputConsts } from './InputConsts';
export { default as InputError } from './InputError';
export { default as InputExtrinsic } from './InputExtrinsic';
export { default as InputFile } from './InputFile';
export { default as InputFloat } from './InputFloat';
export { default as InputNumber } from './InputNumber';
export { default as InputRpc } from './InputRpc';
export { default as InputStorage } from './InputStorage';

View File

@ -0,0 +1,44 @@
// Copyright 2017-2020 @polkadot/react-components 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 { Props } from '../types';
import BN from 'bn.js';
import React from 'react';
import { InputFloat } from '@polkadot/react-components';
import Bare from './Bare';
export default function Float ({ className, defaultValue: { value }, isDisabled, isError, label, onChange, onEnter, onEscape, style, withLabel }: Props): React.ReactElement<Props> {
const defaultValue = new BN((value as BN || '0').toString()).toString(10);
const _onChange = (value?: BN): void =>
onChange && onChange({
isValid: !isError && !!value,
value
});
return (
<Bare
className={className}
style={style}
>
<InputFloat
className='full'
defaultValue={defaultValue}
isDisabled={isDisabled}
isError={isError}
label={label}
onChange={_onChange}
withEllipsis
onEnter={onEnter}
onEscape={onEscape}
withLabel={withLabel}
/>
</Bare>
);
}
export {
Float
};

View File

@ -18,6 +18,7 @@ import Call from './Call';
import Code from './Code';
import DispatchError from './DispatchError';
import Enum from './Enum';
import Float from './Float';
import Hash256 from './Hash256';
import Hash512 from './Hash512';
import KeyValue from './KeyValue';
@ -50,6 +51,7 @@ const componentDef: TypeToComponent[] = [
{ c: DispatchError, t: ['DispatchError'] },
{ c: Raw, t: ['Raw', 'Keys'] },
{ c: Enum, t: ['Enum'] },
{ c: Float, t: ['Perbill', 'Fixed64'] },
{ c: Hash256, t: ['BlockHash', 'CodeHash', 'Hash', 'H256', 'SeedOf'] },
{ c: Hash512, t: ['H512', 'Signature'] },
{ c: KeyValue, t: ['KeyValue'] },
@ -137,7 +139,7 @@ export default function findComponent (def: TypeDef, overrides: ComponentMap = {
// console.error(error.message);
}
// we only want to want once, not spam
// we only want to warn once, not spam
if (!warnList.includes(type)) {
warnList.push(type);
console.info(`params: No pre-defined component for type ${type} from ${JSON.stringify(def)}, using defaults`);