Commit 75013a42 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #702 from blockscout/contracts-add-zeros

contracts: add zeros control
parents 02a02732 cb84a290
......@@ -68,7 +68,7 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
];
}, [ data ]);
const { control, handleSubmit, setValue } = useForm<MethodFormFields>({
const { control, handleSubmit, setValue, getValues } = useForm<MethodFormFields>({
defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])),
});
......@@ -118,11 +118,13 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
<ContractMethodField
key={ fieldName }
name={ fieldName }
valueType={ type }
placeholder={ `${ name }(${ type })` }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isLoading }
onClear={ handleFormChange }
onChange={ handleFormChange }
/>
);
}) }
......
import { FormControl, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
import {
FormControl,
Input,
InputGroup,
InputRightElement,
} from '@chakra-ui/react';
import React from 'react';
import type { Control, ControllerRenderProps, UseFormSetValue } from 'react-hook-form';
import type { Control, ControllerRenderProps, UseFormGetValues, UseFormSetValue } from 'react-hook-form';
import { Controller } from 'react-hook-form';
import type { MethodFormFields } from './types';
import type { SmartContractMethodArgType } from 'types/api/contract';
import InputClearButton from 'ui/shared/InputClearButton';
import ContractMethodFieldZeroes from './ContractMethodFieldZeroes';
import { addZeroesAllowed } from './utils';
interface Props {
control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>;
placeholder: string;
name: string;
valueType: SmartContractMethodArgType;
isDisabled: boolean;
onClear: () => void;
onChange: () => void;
}
const ContractMethodField = ({ control, name, placeholder, setValue, isDisabled, onClear }: Props) => {
const ContractMethodField = ({ control, name, valueType, placeholder, setValue, getValues, isDisabled, onChange }: Props) => {
const ref = React.useRef<HTMLInputElement>(null);
const handleClear = React.useCallback(() => {
setValue(name, '');
onClear();
onChange();
ref.current?.focus();
}, [ name, onClear, setValue ]);
}, [ name, onChange, setValue ]);
const handleAddZeroesClick = React.useCallback((power: number) => {
const value = getValues()[name];
const zeroes = Array(power).fill('0').join('');
const newValue = value ? value + zeroes : '1' + zeroes;
setValue(name, newValue);
onChange();
}, [ getValues, name, onChange, setValue ]);
const hasZerosControl = addZeroesAllowed(valueType);
const renderInput = React.useCallback(({ field }: { field: ControllerRenderProps<MethodFormFields> }) => {
return (
......@@ -32,23 +53,23 @@ const ContractMethodField = ({ control, name, placeholder, setValue, isDisabled,
flexBasis={{ base: '100%', lg: 'calc((100% - 24px) / 3 - 65px)' }}
w={{ base: '100%', lg: 'auto' }}
flexGrow={ 1 }
isDisabled={ isDisabled
}>
isDisabled={ isDisabled }
>
<InputGroup size="xs">
<Input
{ ...field }
ref={ ref }
placeholder={ placeholder }
paddingRight={ hasZerosControl ? '120px' : '40px' }
/>
{ field.value && (
<InputRightElement>
<InputClearButton onClick={ handleClear } isDisabled={ isDisabled }/>
</InputRightElement>
) }
<InputRightElement w="auto" right={ 1 }>
{ field.value && <InputClearButton onClick={ handleClear } isDisabled={ isDisabled }/> }
{ hasZerosControl && <ContractMethodFieldZeroes onClick={ handleAddZeroesClick }/> }
</InputRightElement>
</InputGroup>
</FormControl>
);
}, [ handleClear, isDisabled, name, placeholder ]);
}, [ name, isDisabled, placeholder, hasZerosControl, handleClear, handleAddZeroesClick ]);
return (
<Controller
......
import {
chakra,
Popover,
PopoverBody,
PopoverContent,
PopoverTrigger,
Portal,
Button,
List,
ListItem,
Icon,
useDisclosure,
Input,
} from '@chakra-ui/react';
import React from 'react';
import iconEastMini from 'icons/arrows/east-mini.svg';
import iconCheck from 'icons/check.svg';
import { times } from 'lib/html-entities';
interface Props {
onClick: (power: number) => void;
}
const ContractMethodFieldZeroes = ({ onClick }: Props) => {
const [ selectedOption, setSelectedOption ] = React.useState<number | undefined>(18);
const [ customValue, setCustomValue ] = React.useState<number>();
const { isOpen, onToggle, onClose } = useDisclosure();
const handleOptionClick = React.useCallback((event: React.MouseEvent) => {
const id = Number((event.currentTarget as HTMLDivElement).getAttribute('data-id'));
if (!Object.is(id, NaN)) {
setSelectedOption((prev) => prev === id ? undefined : id);
setCustomValue(undefined);
onClose();
}
}, [ onClose ]);
const handleInputChange = React.useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
setCustomValue(Number(event.target.value));
setSelectedOption(undefined);
}, []);
const value = selectedOption || customValue;
const handleButtonClick = React.useCallback(() => {
value && onClick(value);
}, [ onClick, value ]);
return (
<>
{ Boolean(value) && (
<Button
px={ 1 }
lineHeight={ 6 }
h={ 6 }
fontWeight={ 500 }
ml={ 1 }
variant="subtle"
colorScheme="gray"
display="inline"
onClick={ handleButtonClick }
>
{ times }
<chakra.span>10</chakra.span>
<chakra.span fontSize="xs" lineHeight={ 4 } verticalAlign="super">{ value }</chakra.span>
</Button>
) }
<Popover placement="bottom-end" isLazy isOpen={ isOpen } onClose={ onClose }>
<PopoverTrigger>
<Button
variant="subtle"
colorScheme="gray"
size="xs"
cursor="pointer"
ml={ 1 }
p={ 0 }
onClick={ onToggle }
>
<Icon as={ iconEastMini } transform={ isOpen ? 'rotate(90deg)' : 'rotate(-90deg)' } boxSize={ 6 }/>
</Button>
</PopoverTrigger>
<Portal>
<PopoverContent w="110px">
<PopoverBody py={ 2 }>
<List>
{ [ 8, 12, 16, 18, 20 ].map((id) => (
<ListItem
key={ id }
py={ 2 }
data-id={ id }
onClick={ handleOptionClick }
display="flex"
justifyContent="space-between"
alignItems="center"
cursor="pointer"
>
<span>10*{ id }</span>
{ selectedOption === id && <Icon as={ iconCheck } boxSize={ 6 } color="blue.600"/> }
</ListItem>
)) }
<ListItem
py={ 2 }
display="flex"
justifyContent="space-between"
alignItems="center"
>
<span>10*</span>
<Input
type="number"
min={ 0 }
max={ 100 }
ml={ 3 }
size="xs"
onChange={ handleInputChange }
value={ customValue || '' }
/>
</ListItem>
</List>
</PopoverBody>
</PopoverContent>
</Portal>
</Popover>
</>
);
};
export default React.memo(ContractMethodFieldZeroes);
......@@ -7,6 +7,24 @@ export const getNativeCoinValue = (value: string | Array<string>) => {
return BigNumber(_value).times(10 ** config.network.currency.decimals).toString();
};
export const addZeroesAllowed = (valueType: string) => {
if (valueType.includes('[]')) {
return false;
}
const REGEXP = /u?int(\d+)/i;
const match = valueType.match(REGEXP);
const power = match?.[1];
if (power) {
// show control for all inputs which allows to insert 10^18 or greater numbers
return Number(power) >= 64;
}
return false;
};
interface ExtendedError extends Error {
detectedNetwork?: {
chain: number;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment