Skip to content

Commit

Permalink
Merge pull request #524 from alleslabs/feat/abi-form
Browse files Browse the repository at this point in the history
feat: initial abi form
  • Loading branch information
songwongtp authored Sep 22, 2023
2 parents 799b16b + 373eea5 commit 57f58dd
Show file tree
Hide file tree
Showing 50 changed files with 932 additions and 95 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#524](https://github.com/alleslabs/celatone-frontend/pull/524) Add initia abi form
- [#530](https://github.com/alleslabs/celatone-frontend/pull/530) Publish module component state wireup and add leaflet
- [#521](https://github.com/alleslabs/celatone-frontend/pull/521) Initia module interaction function panel and selected function info accordion
- [#515](https://github.com/alleslabs/celatone-frontend/pull/515) Initia select module drawer wireup
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"@emotion/styled": "^11",
"@graphql-codegen/cli": "^2.13.12",
"@graphql-codegen/client-preset": "^1.1.4",
"@initia/initia.js": "^0.1.8",
"@rjsf/chakra-ui": "v5.0.0-beta.10",
"@rjsf/core": "v5.0.0-beta.10",
"@rjsf/utils": "v5.0.0-beta.10",
Expand Down
2 changes: 1 addition & 1 deletion src/lib/app-provider/hooks/useAddress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const validateAddress = (
getAddressTypeByLength: GetAddressTypeByLengthFn
) => {
if (!bech32Prefix)
return "Can not retrieve bech32 prefix of the current network.";
return "Cannot retrieve bech32 prefix of the current network.";

const prefix = getPrefix(bech32Prefix, addressType);

Expand Down
2 changes: 1 addition & 1 deletion src/lib/components/AddressInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const AddressInput = <T extends FieldValues>({
label={label}
placeholder={placeholder ?? exampleUserAddress}
type="text"
variant="floating"
variant="fixed-floating"
status={status}
labelBgColor={labelBgColor}
helperText={helperText}
Expand Down
4 changes: 2 additions & 2 deletions src/lib/components/OffChainForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export const OffChainForm = <T extends OffchainDetail>({
label="Name"
placeholder={contractLabel}
helperText="Set name for your contract"
variant="floating"
variant="fixed-floating"
rules={{
maxLength: constants.maxContractNameLength,
}}
Expand All @@ -60,7 +60,7 @@ export const OffChainForm = <T extends OffchainDetail>({
control={control}
label="Description"
placeholder="Help understanding what this contract do and how it works ..."
variant="floating"
variant="fixed-floating"
rules={{
maxLength: constants.maxContractDescriptionLength,
}}
Expand Down
53 changes: 53 additions & 0 deletions src/lib/components/abi/AbiForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Flex } from "@chakra-ui/react";
import { useForm } from "react-hook-form";

import type { AbiFormData, ExposedFunction } from "lib/types";

import { ArgsForm } from "./args-form";
import { TypesForm } from "./types-form";

interface AbiFormProps {
fn: ExposedFunction;
initialData: AbiFormData;
propsOnChange?: (data: AbiFormData) => void;
propsOnErrors?: (errors: [string, string][]) => void;
}

export const AbiForm = ({
fn,
initialData,
propsOnChange,
propsOnErrors,
}: AbiFormProps) => {
const { setValue, watch, getValues } = useForm<AbiFormData>({
defaultValues: initialData,
mode: "all",
});
const { typeArgs, args } = watch();

return (
<Flex direction="column" gap={4}>
{Object.keys(typeArgs).length > 0 && (
<TypesForm
genericTypeParams={fn.generic_type_params}
initialData={typeArgs}
propsOnChange={(value) => {
setValue("typeArgs", value);
propsOnChange?.(getValues());
}}
/>
)}
{Object.keys(args).length > 0 && (
<ArgsForm
params={fn.params}
initialData={args}
propsOnChange={(value) => {
setValue("args", value);
propsOnChange?.(getValues());
}}
propsOnErrors={propsOnErrors}
/>
)}
</Flex>
);
};
91 changes: 91 additions & 0 deletions src/lib/components/abi/args-form/field/ArgFieldWidget.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { Input, Textarea } from "@chakra-ui/react";
import { Select } from "chakra-react-select";
import type { ControllerRenderProps } from "react-hook-form";

import type { Option } from "lib/types";

import { UintTypes } from "./utils";

const getInputPlaceholder = (type: string, isNull: boolean) => {
if (type === "0x1::string::String" && !isNull)
return "Left blank to send as empty string";
if (type === "&signer") return "Signer is auto-filled when signing a tx";
return " ";
};

const boolOptions = [
{ label: "True", value: "true" },
{ label: "False", value: "false" },
];

interface ArgFieldWidgetProps {
type: string;
value: Option<string>;
onChange: ControllerRenderProps["onChange"];
}

export const ArgFieldWidget = ({
type,
value,
onChange,
}: ArgFieldWidgetProps) => {
if (
UintTypes.includes(type) ||
type === "address" ||
type === "0x1::string::String" ||
type === "&signer"
)
return (
<Input
size="md"
placeholder={getInputPlaceholder(type, value === undefined)}
value={value ?? ""}
onChange={onChange}
/>
);

if (type === "bool")
return (
<Select
classNamePrefix="chakra-react-select"
size="md"
options={boolOptions}
placeholder={" "}
value={boolOptions.find(
({ value: optionValue }) => optionValue === value
)}
onChange={(e) => onChange(e?.value)}
menuPosition="fixed"
chakraStyles={{
control: (provided) => ({
...provided,
_disabled: {
color: "text.main",
},
}),
dropdownIndicator: (provided, state) => ({
...provided,
color: state.isDisabled ? "gray.700" : undefined,
}),
option: (provided, state) => ({
...provided,
bg: state.isSelected ? "gray.800" : undefined,
color: "text.main",
_hover: {
bg: "gray.700",
},
}),
}}
/>
);

return (
<Textarea
minH="112px"
h="fit-content"
placeholder={" "}
value={value ?? ""}
onChange={onChange}
/>
);
};
100 changes: 100 additions & 0 deletions src/lib/components/abi/args-form/field/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/* eslint-disable sonarjs/cognitive-complexity */
import {
Box,
Checkbox,
FormControl,
FormErrorMessage,
FormLabel,
Text,
} from "@chakra-ui/react";
import { useCallback } from "react";
import { type Control, useController } from "react-hook-form";

import { useValidateAddress } from "lib/app-provider";
import type { AbiFormData } from "lib/types";

import { ArgFieldWidget } from "./ArgFieldWidget";
import { getRules } from "./utils";

interface ArgFieldTemplateProps {
index: number;
param: string;
control: Control<AbiFormData["args"]>;
error?: string;
isReadOnly?: boolean;
}

export const ArgFieldTemplate = ({
index,
param,
control,
error,
isReadOnly = false,
}: ArgFieldTemplateProps) => {
const { validateUserAddress, validateContractAddress, validateHexAddress } =
useValidateAddress();

const isValidArgAddress = useCallback(
(input: string) =>
validateUserAddress(input) === null ||
validateContractAddress(input) === null ||
validateHexAddress(input),
[validateContractAddress, validateHexAddress, validateUserAddress]
);

const isOptional = param.startsWith("0x1::option::Option");
const type = isOptional ? param.split(/<(.*)>/)[1] : param;
const rules = getRules(type, isOptional, isReadOnly, isValidArgAddress);

const {
field: { value, onChange, ...fieldProps },
fieldState: { isTouched },
} = useController({
name: `${index}`,
control,
rules,
});
const isError = isTouched && !!error;

const size = "md";
const isSigner = type === "&signer";
const isNull = value === undefined;
return (
<Box>
<FormControl
className={`${size}-form`}
variant={
isSigner || (type === "0x1::string::String" && !isNull)
? "fixed-floating"
: "floating"
}
size={size}
isInvalid={isError}
isReadOnly={isReadOnly}
isDisabled={(isSigner && !isReadOnly) || isNull}
{...fieldProps}
>
<ArgFieldWidget type={type} value={value} onChange={onChange} />
<FormLabel className={`${size}-label`} bgColor="background.main">
{param}
</FormLabel>

{isError && (
<FormErrorMessage className="error-text" mt={1}>
{error}
</FormErrorMessage>
)}
</FormControl>
{isOptional && (
<Checkbox
pt="2px"
pl={2}
isChecked={value === undefined}
onChange={(e) => onChange(e.target.checked ? undefined : "")}
>
<Text variant="body3">Send as null</Text>
</Checkbox>
)}
</Box>
);
};
Loading

0 comments on commit 57f58dd

Please sign in to comment.