Commit a8448fef authored by tom's avatar tom

array fields, first iteration

parent 5ffd2a62
...@@ -5,12 +5,14 @@ import type { SubmitHandler } from 'react-hook-form'; ...@@ -5,12 +5,14 @@ import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import type { MethodFormFields, ContractMethodCallResult } from './types'; import type { MethodFormFields, ContractMethodCallResult } from './types';
import type { SmartContractMethodInput, SmartContractMethod } from 'types/api/contract'; import type { SmartContractMethodInput, SmartContractMethod, SmartContractMethodArgType } from 'types/api/contract';
import arrowIcon from 'icons/arrows/down-right.svg'; import arrowIcon from 'icons/arrows/down-right.svg';
import * as mixpanel from 'lib/mixpanel/index'; import * as mixpanel from 'lib/mixpanel/index';
import ContractMethodField from './ContractMethodField'; import ContractMethodField from './ContractMethodField';
import ContractMethodFieldArray from './ContractMethodFieldArray';
import { ARRAY_REGEXP } from './utils';
interface ResultComponentProps<T extends SmartContractMethod> { interface ResultComponentProps<T extends SmartContractMethod> {
item: T; item: T;
...@@ -27,7 +29,10 @@ interface Props<T extends SmartContractMethod> { ...@@ -27,7 +29,10 @@ interface Props<T extends SmartContractMethod> {
const getFieldName = (name: string | undefined, index: number): string => name || String(index); const getFieldName = (name: string | undefined, index: number): string => name || String(index);
const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, string], [ b ]: [string, string]): 1 | -1 | 0 => { const sortFields = (data: Array<SmartContractMethodInput>) => (
[ a ]: [string, string | Array<string>],
[ b ]: [string, string | Array<string>],
): 1 | -1 | 0 => {
const fieldNames = data.map(({ name }, index) => getFieldName(name, index)); const fieldNames = data.map(({ name }, index) => getFieldName(name, index));
const indexA = fieldNames.indexOf(a); const indexA = fieldNames.indexOf(a);
const indexB = fieldNames.indexOf(b); const indexB = fieldNames.indexOf(b);
...@@ -43,15 +48,19 @@ const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, s ...@@ -43,15 +48,19 @@ const sortFields = (data: Array<SmartContractMethodInput>) => ([ a ]: [string, s
return 0; return 0;
}; };
const castFieldValue = (data: Array<SmartContractMethodInput>) => ([ key, value ]: [ string, string ], index: number) => { const castFieldValue = (data: Array<SmartContractMethodInput>) => ([ key, value ]: [ string, string | Array<string> ], index: number) => {
if (data[index].type.includes('[')) { if (data[index].type.includes('[')) {
return [ key, parseArrayValue(value) ]; return [ key, parseArrayValue(value) ];
} }
return [ key, value ]; return [ key, value ];
}; };
const parseArrayValue = (value: string) => { const parseArrayValue = (value: string | Array<string>) => {
try { try {
if (Array.isArray(value)) {
return value;
}
const parsedResult = JSON.parse(value); const parsedResult = JSON.parse(value);
if (Array.isArray(parsedResult)) { if (Array.isArray(parsedResult)) {
return parsedResult; return parsedResult;
...@@ -97,8 +106,6 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -97,8 +106,6 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
.map(castFieldValue(inputs)) .map(castFieldValue(inputs))
.map(([ , value ]) => value); .map(([ , value ]) => value);
return;
setResult(undefined); setResult(undefined);
setLoading(true); setLoading(true);
...@@ -131,9 +138,20 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -131,9 +138,20 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
> >
{ inputs.map(({ type, name }, index) => { { inputs.map(({ type, name }, index) => {
const fieldName = getFieldName(name, index); const fieldName = getFieldName(name, index);
return ( const arrayTypeMatch = type.match(ARRAY_REGEXP);
const content = arrayTypeMatch ? (
<ContractMethodFieldArray
name={ fieldName }
valueType={ arrayTypeMatch[1] as SmartContractMethodArgType }
size={ Number(arrayTypeMatch[2] || Infinity) }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isLoading }
onChange={ handleFormChange }
/>
) : (
<ContractMethodField <ContractMethodField
key={ index }
name={ fieldName } name={ fieldName }
valueType={ type } valueType={ type }
placeholder={ `${ name }(${ type })` } placeholder={ `${ name }(${ type })` }
...@@ -144,6 +162,20 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -144,6 +162,20 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
onChange={ handleFormChange } onChange={ handleFormChange }
/> />
); );
return (
<React.Fragment key={ index }>
<Box
fontWeight={ 500 }
lineHeight="20px"
py={{ lg: '6px' }}
fontSize="sm"
>
{ name } ({ type })
</Box>
{ content }
</React.Fragment>
);
}) } }) }
<Button <Button
isLoading={ isLoading } isLoading={ isLoading }
......
...@@ -6,7 +6,7 @@ import { ...@@ -6,7 +6,7 @@ import {
InputRightElement, InputRightElement,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { Control, ControllerRenderProps, UseFormGetValues, UseFormSetValue, UseFormStateReturn } from 'react-hook-form'; import type { Control, ControllerRenderProps, FieldError, UseFormGetValues, UseFormSetValue, UseFormStateReturn } from 'react-hook-form';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import { NumericFormat } from 'react-number-format'; import { NumericFormat } from 'react-number-format';
import { isAddress } from 'viem'; import { isAddress } from 'viem';
...@@ -21,17 +21,19 @@ import ContractMethodFieldZeroes from './ContractMethodFieldZeroes'; ...@@ -21,17 +21,19 @@ import ContractMethodFieldZeroes from './ContractMethodFieldZeroes';
import { INT_REGEXP, BYTES_REGEXP, getIntBoundaries, formatBooleanValue } from './utils'; import { INT_REGEXP, BYTES_REGEXP, getIntBoundaries, formatBooleanValue } from './utils';
interface Props { interface Props {
name: string;
index?: number;
groupName?: string;
placeholder: string;
valueType: SmartContractMethodArgType;
control: Control<MethodFormFields>; control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>; setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>; getValues: UseFormGetValues<MethodFormFields>;
placeholder: string;
name: string;
valueType: SmartContractMethodArgType;
isDisabled: boolean; isDisabled: boolean;
onChange: () => void; onChange: () => void;
} }
const ContractMethodField = ({ control, name, valueType, placeholder, setValue, getValues, isDisabled, onChange }: Props) => { const ContractMethodField = ({ control, name, groupName, index, valueType, placeholder, setValue, getValues, isDisabled, onChange }: Props) => {
const ref = React.useRef<HTMLInputElement>(null); const ref = React.useRef<HTMLInputElement>(null);
const handleClear = React.useCallback(() => { const handleClear = React.useCallback(() => {
...@@ -41,12 +43,12 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue, ...@@ -41,12 +43,12 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue,
}, [ name, onChange, setValue ]); }, [ name, onChange, setValue ]);
const handleAddZeroesClick = React.useCallback((power: number) => { const handleAddZeroesClick = React.useCallback((power: number) => {
const value = getValues()[name]; const value = groupName && index !== undefined ? getValues()[groupName][index] : getValues()[name];
const zeroes = Array(power).fill('0').join(''); const zeroes = Array(power).fill('0').join('');
const newValue = value ? value + zeroes : '1' + zeroes; const newValue = value ? value + zeroes : '1' + zeroes;
setValue(name, newValue); setValue(name, newValue);
onChange(); onChange();
}, [ getValues, name, onChange, setValue ]); }, [ getValues, groupName, index, name, onChange, setValue ]);
const intMatch = React.useMemo(() => { const intMatch = React.useMemo(() => {
const match = valueType.match(INT_REGEXP); const match = valueType.match(INT_REGEXP);
...@@ -67,15 +69,17 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue, ...@@ -67,15 +69,17 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue,
const renderInput = React.useCallback(( const renderInput = React.useCallback((
{ field, formState }: { field: ControllerRenderProps<MethodFormFields>; formState: UseFormStateReturn<MethodFormFields> }, { field, formState }: { field: ControllerRenderProps<MethodFormFields>; formState: UseFormStateReturn<MethodFormFields> },
) => { ) => {
const error = formState.errors[name]; const error: FieldError | undefined = index !== undefined && groupName !== undefined ?
(formState.errors[groupName] as unknown as Array<FieldError>)?.[index] :
formState.errors[name];
// show control for all inputs which allows to insert 10^18 or greater numbers // show control for all inputs which allows to insert 10^18 or greater numbers
const hasZerosControl = intMatch && Number(intMatch.power) >= 64; const hasZerosControl = intMatch && Number(intMatch.power) >= 64;
return ( return (
<Box> <Box w="100%">
<FormControl <FormControl
id={ name } id={ name }
w="100%"
mb={{ base: 1, lg: 0 }} mb={{ base: 1, lg: 0 }}
isDisabled={ isDisabled } isDisabled={ isDisabled }
> >
...@@ -104,9 +108,13 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue, ...@@ -104,9 +108,13 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue,
{ error && <Box color="error" fontSize="sm" mt={ 1 }>{ error.message }</Box> } { error && <Box color="error" fontSize="sm" mt={ 1 }>{ error.message }</Box> }
</Box> </Box>
); );
}, [ name, intMatch, isDisabled, placeholder, handleClear, handleAddZeroesClick ]); }, [ index, groupName, name, intMatch, isDisabled, placeholder, handleClear, handleAddZeroesClick ]);
const validate = React.useCallback((value: string | Array<string>) => {
if (typeof value === 'object') {
return;
}
const validate = React.useCallback((value: string) => {
if (valueType === 'address') { if (valueType === 'address') {
return !isAddress(value) ? 'Invalid address format' : true; return !isAddress(value) ? 'Invalid address format' : true;
} }
...@@ -155,22 +163,12 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue, ...@@ -155,22 +163,12 @@ const ContractMethodField = ({ control, name, valueType, placeholder, setValue,
}, [ bytesMatch, intMatch, valueType ]); }, [ bytesMatch, intMatch, valueType ]);
return ( return (
<> <Controller
<Box name={ name }
fontWeight={ 500 } control={ control }
lineHeight="20px" render={ renderInput }
py={{ lg: '6px' }} rules={{ required: 'Field is required', validate }}
fontSize="sm" />
>
{ name } ({ valueType })
</Box>
<Controller
name={ name }
control={ control }
render={ renderInput }
rules={{ required: 'Field is required', validate }}
/>
</>
); );
}; };
......
import { Flex, Icon, IconButton } from '@chakra-ui/react';
import React from 'react';
import type { Control, UseFormGetValues, UseFormSetValue } from 'react-hook-form';
import { useFieldArray } from 'react-hook-form';
import type { MethodFormFields } from './types';
import type { SmartContractMethodArgType } from 'types/api/contract';
import minusIcon from 'icons/minus.svg';
import plusIcon from 'icons/plus.svg';
import ContractMethodField from './ContractMethodField';
interface Props {
name: string;
size: number;
valueType: SmartContractMethodArgType;
control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>;
isDisabled: boolean;
onChange: () => void;
}
const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabled, valueType, onChange }: Props) => {
const { fields, append, remove } = useFieldArray({
name: name as never,
control,
});
React.useEffect(() => {
fields.length === 0 && append('');
}, [ append, fields.length ]);
const handleAddButtonClick = React.useCallback(() => {
append('');
}, [ append ]);
const handleRemoveButtonClick = React.useCallback((event: React.MouseEvent<HTMLButtonElement>) => {
const itemIndex = event.currentTarget.getAttribute('data-index');
if (itemIndex) {
remove(Number(itemIndex));
}
}, [ remove ]);
return (
<Flex flexDir="column" rowGap={ 3 }>
{ fields.map((field, index, array) => {
return (
<Flex key={ field.id } columnGap={ 3 }>
<ContractMethodField
name={ `${ name }[${ index }]` }
groupName={ name }
index={ index }
valueType={ valueType }
placeholder={ valueType }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isDisabled }
onChange={ onChange }
/>
{ array.length > 1 && (
<IconButton
aria-label="remove"
data-index={ index }
variant="outline"
w="30px"
h="30px"
flexShrink={ 0 }
onClick={ handleRemoveButtonClick }
icon={ <Icon as={ minusIcon } boxSize={ 4 }/> }
isDisabled={ isDisabled }
/>
) }
{ index === array.length - 1 && (
<IconButton
aria-label="add"
data-index={ index }
variant="outline"
w="30px"
h="30px"
flexShrink={ 0 }
onClick={ handleAddButtonClick }
icon={ <Icon as={ plusIcon } boxSize={ 4 }/> }
isDisabled={ isDisabled }
/>
) }
</Flex>
);
}) }
</Flex>
);
};
export default React.memo(ContractMethodFieldArray);
...@@ -2,7 +2,7 @@ import type { SmartContractQueryMethodRead, SmartContractMethod } from 'types/ap ...@@ -2,7 +2,7 @@ import type { SmartContractQueryMethodRead, SmartContractMethod } from 'types/ap
import type { ResourceError } from 'lib/api/resources'; import type { ResourceError } from 'lib/api/resources';
export type MethodFormFields = Record<string, string>; export type MethodFormFields = Record<string, string | Array<string>>;
export type ContractMethodReadResult = SmartContractQueryMethodRead | ResourceError; export type ContractMethodReadResult = SmartContractQueryMethodRead | ResourceError;
......
...@@ -6,6 +6,8 @@ export const INT_REGEXP = /^(u)?int(\d+)?$/i; ...@@ -6,6 +6,8 @@ export const INT_REGEXP = /^(u)?int(\d+)?$/i;
export const BYTES_REGEXP = /^bytes(\d+)?$/i; export const BYTES_REGEXP = /^bytes(\d+)?$/i;
export const ARRAY_REGEXP = /^(.*)\[(\d*)\]$/;
export const getIntBoundaries = (power: number, isUnsigned: boolean) => { export const getIntBoundaries = (power: number, isUnsigned: boolean) => {
const maxUnsigned = 2 ** power; const maxUnsigned = 2 ** power;
const max = isUnsigned ? maxUnsigned - 1 : maxUnsigned / 2 - 1; const max = isUnsigned ? maxUnsigned - 1 : maxUnsigned / 2 - 1;
......
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