Browse Source

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
Alexander Popiak 5 years ago
parent
commit
7a159976f4

+ 76 - 0
packages/react-components/src/InputFloat.tsx

@@ -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;
+  }
+`;

+ 21 - 11
packages/react-components/src/InputNumber.tsx

@@ -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

+ 1 - 0
packages/react-components/src/index.tsx

@@ -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';

+ 44 - 0
packages/react-params/src/Param/Float.tsx

@@ -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
+};

+ 3 - 1
packages/react-params/src/Param/findComponent.ts

@@ -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`);