Commit 05e389dd authored by tom's avatar tom

tuple type fields

parent a8448fef
import type { Abi } from 'abitype'; import type { Abi } from 'abitype';
export type SmartContractMethodArgType = 'address' | 'uint256' | 'bool' | 'string' | 'bytes' | 'bytes32' | 'bytes32[]'; export type SmartContractMethodArgType = 'address' | 'uint256' | 'bool' | 'string' | 'bytes' | 'bytes32' | 'bytes32[]' | 'tuple';
export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable'; export type SmartContractMethodStateMutability = 'view' | 'nonpayable' | 'payable';
export interface SmartContract { export interface SmartContract {
...@@ -88,6 +88,7 @@ export interface SmartContractMethodInput { ...@@ -88,6 +88,7 @@ export interface SmartContractMethodInput {
internalType?: SmartContractMethodArgType; internalType?: SmartContractMethodArgType;
name: string; name: string;
type: SmartContractMethodArgType; type: SmartContractMethodArgType;
components?: Array<SmartContractMethodInput>;
} }
export interface SmartContractMethodOutput extends SmartContractMethodInput { export interface SmartContractMethodOutput extends SmartContractMethodInput {
......
import { Box, Button, chakra, Flex, Icon, Text } from '@chakra-ui/react'; import { Box, Button, chakra, Flex, Icon, Text } from '@chakra-ui/react';
import _fromPairs from 'lodash/fromPairs'; // import _fromPairs from 'lodash/fromPairs';
import React from 'react'; import React from 'react';
import type { SubmitHandler } from 'react-hook-form'; import type { SubmitHandler } from 'react-hook-form';
import { useForm } from 'react-hook-form'; import { useForm, FormProvider } from 'react-hook-form';
import type { MethodFormFields, ContractMethodCallResult } from './types'; import type { MethodFormFields, ContractMethodCallResult } from './types';
import type { SmartContractMethodInput, SmartContractMethod, SmartContractMethodArgType } from 'types/api/contract'; import type { SmartContractMethodInput, SmartContractMethod } 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 ContractMethodCallableRow from './ContractMethodCallableRow';
import ContractMethodFieldArray from './ContractMethodFieldArray';
import { ARRAY_REGEXP } from './utils';
interface ResultComponentProps<T extends SmartContractMethod> { interface ResultComponentProps<T extends SmartContractMethod> {
item: T; item: T;
...@@ -27,8 +25,11 @@ interface Props<T extends SmartContractMethod> { ...@@ -27,8 +25,11 @@ interface Props<T extends SmartContractMethod> {
isWrite?: boolean; isWrite?: boolean;
} }
// TODO @tom2drum remove this
const getFieldName = (name: string | undefined, index: number): string => name || String(index); const getFieldName = (name: string | undefined, index: number): string => name || String(index);
const getFormFieldName = (inputName: string, inputIndex: number, group?: string) => `${ group ? `${ group }_` : '' }${ inputName || 'input' }-${ inputIndex }}`;
const sortFields = (data: Array<SmartContractMethodInput>) => ( const sortFields = (data: Array<SmartContractMethodInput>) => (
[ a ]: [string, string | Array<string>], [ a ]: [string, string | Array<string>],
[ b ]: [string, string | Array<string>], [ b ]: [string, string | Array<string>],
...@@ -87,8 +88,9 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -87,8 +88,9 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
]; ];
}, [ data ]); }, [ data ]);
const { control, handleSubmit, setValue, getValues } = useForm<MethodFormFields>({ const formApi = useForm<MethodFormFields>({
defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])), // TODO @tom2drum rewrite this
// defaultValues: _fromPairs(inputs.map(({ name }, index) => [ getFieldName(name, index), '' ])),
mode: 'onBlur', mode: 'onBlur',
}); });
...@@ -101,6 +103,10 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -101,6 +103,10 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
}, [ result ]); }, [ result ]);
const onFormSubmit: SubmitHandler<MethodFormFields> = React.useCallback(async(formData) => { const onFormSubmit: SubmitHandler<MethodFormFields> = React.useCallback(async(formData) => {
// console.log('__>__ formData', formData);
// debugger;
const args = Object.entries(formData) const args = Object.entries(formData)
.sort(sortFields(inputs)) .sort(sortFields(inputs))
.map(castFieldValue(inputs)) .map(castFieldValue(inputs))
...@@ -127,69 +133,79 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit, ...@@ -127,69 +133,79 @@ const ContractMethodCallable = <T extends SmartContractMethod>({ data, onSubmit,
return ( return (
<Box> <Box>
<chakra.form <FormProvider { ...formApi }>
noValidate <chakra.form
display="grid" noValidate
columnGap={ 3 } display="grid"
rowGap={{ base: 2, lg: 3 }} columnGap={ 3 }
gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 180px) minmax(0, 1fr)' }} rowGap={{ base: 2, lg: 3 }}
onSubmit={ handleSubmit(onFormSubmit) } gridTemplateColumns={{ base: 'minmax(0, 1fr)', lg: 'minmax(min-content, 250px) minmax(0, 1fr)' }}
onChange={ handleFormChange } onSubmit={ formApi.handleSubmit(onFormSubmit) }
> onChange={ handleFormChange }
{ inputs.map(({ type, name }, index) => {
const fieldName = getFieldName(name, index);
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
name={ fieldName }
valueType={ type }
placeholder={ `${ name }(${ type })` }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isLoading }
onChange={ handleFormChange }
/>
);
return (
<React.Fragment key={ index }>
<Box
fontWeight={ 500 }
lineHeight="20px"
py={{ lg: '6px' }}
fontSize="sm"
>
{ name } ({ type })
</Box>
{ content }
</React.Fragment>
);
}) }
<Button
isLoading={ isLoading }
loadingText={ isWrite ? 'Write' : 'Read' }
variant="outline"
size="sm"
flexShrink={ 0 }
width="min-content"
px={ 4 }
type="submit"
> >
{ isWrite ? 'Write' : 'Read' } { inputs.map((input, index) => {
</Button> const fieldName = getFormFieldName(input.name, index);
</chakra.form>
if (input.type === 'tuple' && input.components) {
return (
<React.Fragment key={ fieldName }>
{ index !== 0 && <><Box h={{ base: 0, lg: 3 }}/><div/></> }
<Box
fontWeight={ 500 }
lineHeight="20px"
py={{ lg: '6px' }}
fontSize="sm"
wordBreak="break-word"
>
{ input.name } ({ input.type })
</Box>
<div/>
{ input.components.map((component, componentIndex) => {
const fieldName = getFormFieldName(component.name, componentIndex, input.name);
return (
<ContractMethodCallableRow
key={ fieldName }
fieldName={ fieldName }
argName={ component.name }
argType={ component.type }
isDisabled={ isLoading }
onChange={ handleFormChange }
isGrouped
/>
);
}) }
{ index !== inputs.length - 1 && <><Box h={{ base: 0, lg: 3 }}/><div/></> }
</React.Fragment>
);
}
return (
<ContractMethodCallableRow
key={ fieldName }
fieldName={ fieldName }
argName={ input.name }
argType={ input.type }
isDisabled={ isLoading }
onChange={ handleFormChange }
/>
);
}) }
<div/>
<Button
isLoading={ isLoading }
loadingText={ isWrite ? 'Write' : 'Read' }
variant="outline"
size="sm"
flexShrink={ 0 }
width="min-content"
px={ 4 }
type="submit"
>
{ isWrite ? 'Write' : 'Read' }
</Button>
</chakra.form>
</FormProvider>
{ 'outputs' in data && !isWrite && data.outputs.length > 0 && ( { 'outputs' in data && !isWrite && data.outputs.length > 0 && (
<Flex mt={ 3 }> <Flex mt={ 3 }>
<Icon as={ arrowIcon } boxSize={ 5 } mr={ 1 }/> <Icon as={ arrowIcon } boxSize={ 5 } mr={ 1 }/>
......
import { Box } from '@chakra-ui/react';
import React from 'react';
import { useFormContext } from 'react-hook-form';
import type { MethodFormFields } from './types';
import type { SmartContractMethodArgType } from 'types/api/contract';
import ContractMethodField from './ContractMethodField';
import ContractMethodFieldArray from './ContractMethodFieldArray';
import { ARRAY_REGEXP } from './utils';
interface Props {
fieldName: string;
argName: string;
argType: SmartContractMethodArgType;
onChange: () => void;
isDisabled: boolean;
isGrouped?: boolean;
}
const ContractMethodCallableRow = ({ argName, fieldName, argType, onChange, isDisabled, isGrouped }: Props) => {
const { control, getValues, setValue } = useFormContext<MethodFormFields>();
const arrayTypeMatch = argType.match(ARRAY_REGEXP);
const content = arrayTypeMatch ? (
<ContractMethodFieldArray
name={ fieldName }
argType={ arrayTypeMatch[1] as SmartContractMethodArgType }
size={ Number(arrayTypeMatch[2] || Infinity) }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isDisabled }
onChange={ onChange }
/>
) : (
<ContractMethodField
name={ fieldName }
argType={ argType }
placeholder={ argType }
control={ control }
setValue={ setValue }
getValues={ getValues }
isDisabled={ isDisabled }
onChange={ onChange }
/>
);
return (
<>
<Box
fontWeight={ 500 }
lineHeight="20px"
py={{ lg: '6px' }}
fontSize="sm"
color={ isGrouped ? 'text_secondary' : 'initial' }
wordBreak="break-word"
>
{ argName } ({ argType })
</Box>
{ content }
</>
);
};
export default React.memo(ContractMethodCallableRow);
...@@ -25,7 +25,7 @@ interface Props { ...@@ -25,7 +25,7 @@ interface Props {
index?: number; index?: number;
groupName?: string; groupName?: string;
placeholder: string; placeholder: string;
valueType: SmartContractMethodArgType; argType: SmartContractMethodArgType;
control: Control<MethodFormFields>; control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>; setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>; getValues: UseFormGetValues<MethodFormFields>;
...@@ -33,7 +33,7 @@ interface Props { ...@@ -33,7 +33,7 @@ interface Props {
onChange: () => void; onChange: () => void;
} }
const ContractMethodField = ({ control, name, groupName, index, valueType, placeholder, setValue, getValues, isDisabled, onChange }: Props) => { const ContractMethodField = ({ control, name, groupName, index, argType, 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(() => {
...@@ -51,7 +51,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place ...@@ -51,7 +51,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
}, [ getValues, groupName, index, 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 = argType.match(INT_REGEXP);
if (!match) { if (!match) {
return null; return null;
} }
...@@ -60,11 +60,11 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place ...@@ -60,11 +60,11 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
const [ min, max ] = getIntBoundaries(Number(power), Boolean(isUnsigned)); const [ min, max ] = getIntBoundaries(Number(power), Boolean(isUnsigned));
return { isUnsigned, power, min, max }; return { isUnsigned, power, min, max };
}, [ valueType ]); }, [ argType ]);
const bytesMatch = React.useMemo(() => { const bytesMatch = React.useMemo(() => {
return valueType.match(BYTES_REGEXP); return argType.match(BYTES_REGEXP);
}, [ valueType ]); }, [ argType ]);
const renderInput = React.useCallback(( const renderInput = React.useCallback((
{ field, formState }: { field: ControllerRenderProps<MethodFormFields>; formState: UseFormStateReturn<MethodFormFields> }, { field, formState }: { field: ControllerRenderProps<MethodFormFields>; formState: UseFormStateReturn<MethodFormFields> },
...@@ -111,11 +111,11 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place ...@@ -111,11 +111,11 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
}, [ index, groupName, name, intMatch, isDisabled, placeholder, handleClear, handleAddZeroesClick ]); }, [ index, groupName, name, intMatch, isDisabled, placeholder, handleClear, handleAddZeroesClick ]);
const validate = React.useCallback((value: string | Array<string>) => { const validate = React.useCallback((value: string | Array<string>) => {
if (typeof value === 'object') { if (typeof value === 'object' || !value) {
return; return;
} }
if (valueType === 'address') { if (argType === 'address') {
return !isAddress(value) ? 'Invalid address format' : true; return !isAddress(value) ? 'Invalid address format' : true;
} }
...@@ -135,7 +135,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place ...@@ -135,7 +135,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
return true; return true;
} }
if (valueType === 'bool') { if (argType === 'bool') {
const formattedValue = formatBooleanValue(value); const formattedValue = formatBooleanValue(value);
if (formattedValue === undefined) { if (formattedValue === undefined) {
return 'Invalid boolean format. Allowed values: 0, 1, true, false'; return 'Invalid boolean format. Allowed values: 0, 1, true, false';
...@@ -160,7 +160,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place ...@@ -160,7 +160,7 @@ const ContractMethodField = ({ control, name, groupName, index, valueType, place
} }
return true; return true;
}, [ bytesMatch, intMatch, valueType ]); }, [ bytesMatch, intMatch, argType ]);
return ( return (
<Controller <Controller
......
...@@ -14,7 +14,7 @@ import ContractMethodField from './ContractMethodField'; ...@@ -14,7 +14,7 @@ import ContractMethodField from './ContractMethodField';
interface Props { interface Props {
name: string; name: string;
size: number; size: number;
valueType: SmartContractMethodArgType; argType: SmartContractMethodArgType;
control: Control<MethodFormFields>; control: Control<MethodFormFields>;
setValue: UseFormSetValue<MethodFormFields>; setValue: UseFormSetValue<MethodFormFields>;
getValues: UseFormGetValues<MethodFormFields>; getValues: UseFormGetValues<MethodFormFields>;
...@@ -22,7 +22,7 @@ interface Props { ...@@ -22,7 +22,7 @@ interface Props {
onChange: () => void; onChange: () => void;
} }
const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabled, valueType, onChange }: Props) => { const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabled, argType, onChange }: Props) => {
const { fields, append, remove } = useFieldArray({ const { fields, append, remove } = useFieldArray({
name: name as never, name: name as never,
control, control,
...@@ -52,8 +52,8 @@ const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabl ...@@ -52,8 +52,8 @@ const ContractMethodFieldArray = ({ control, name, setValue, getValues, isDisabl
name={ `${ name }[${ index }]` } name={ `${ name }[${ index }]` }
groupName={ name } groupName={ name }
index={ index } index={ index }
valueType={ valueType } argType={ argType }
placeholder={ valueType } placeholder={ argType }
control={ control } control={ control }
setValue={ setValue } setValue={ setValue }
getValues={ getValues } getValues={ getValues }
......
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