Skip to content

Commit

Permalink
Merge branch 'main' into ui/heimdallrs-steps
Browse files Browse the repository at this point in the history
  • Loading branch information
portdeveloper committed Mar 13, 2024
2 parents ce5d6e8 + 5bc6506 commit 30f3d9e
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 59 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AugmentedAbiFunction } from "./ContractUI";
import { ReadOnlyFunctionForm } from "./ReadOnlyFunctionForm";
import { Abi, AbiFunction } from "abitype";
import { Abi } from "abitype";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";

Expand All @@ -15,7 +16,7 @@ export const ContractReadMethods = ({
}

const functionsToDisplay = (
((deployedContractData.abi || []) as Abi).filter(part => part.type === "function") as AbiFunction[]
((deployedContractData.abi || []) as Abi).filter(part => part.type === "function") as AugmentedAbiFunction[]
)
.filter(fn => {
const isQueryableWithParams =
Expand All @@ -41,15 +42,15 @@ export const ContractReadMethods = ({
return (
<>
{functionsToDisplay.map(({ fn, inheritedFrom }) => (
<div key={fn.name} className="relative mb-4 pt-5">
<div key={fn.uid} className="relative mb-4 pt-5">
<ReadOnlyFunctionForm
abi={deployedContractData.abi as Abi}
contractAddress={deployedContractData.address}
abiFunction={fn}
inheritedFrom={inheritedFrom}
/>
<button
onClick={() => removeMethod(fn.name)}
onClick={() => removeMethod(fn.uid)}
className="absolute top-0 right-0 btn btn-ghost btn-xs mt-[21px]"
aria-label="Remove method"
>
Expand Down
64 changes: 36 additions & 28 deletions packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ type ContractUIProps = {
initialContractData: { address: string; abi: Abi };
};

export interface AugmentedAbiFunction extends AbiFunction {
uid: string;
}

const mainNetworks = getTargetNetworks();

/**
Expand All @@ -43,49 +47,53 @@ export const ContractUI = ({ className = "", initialContractData }: ContractUIPr
router.push({ pathname: newPath, query: currentQuery.toString() }, undefined, { shallow: true });
};

const readMethodsWithInputsAndWriteMethods = initialContractData.abi.filter((method): method is AbiFunction => {
if (method.type !== "function") return false;
const augmentMethodsWithUid = (methods: AbiFunction[]): AugmentedAbiFunction[] => {
return methods.map((method, index) => ({
...method,
uid: `${method.name}_${index}`, // Simple UID based on index
}));
};

// Check for read functions
if (method.stateMutability === "view" || method.stateMutability === "pure") {
// Check for read inputs length
if (method.inputs.length > 0) {
return true;
} else return false;
} else {
// Else condition defines write methods
return true;
}
});
const readMethodsWithInputsAndWriteMethods = useMemo(() => {
return augmentMethodsWithUid(
initialContractData.abi.filter((method): method is AbiFunction => {
if (method.type !== "function") return false;
if (method.stateMutability === "view" || method.stateMutability === "pure") {
return method.inputs.length > 0;
} else {
return true;
}
}),
);
}, [initialContractData.abi]);

// local abi state for for dispalying selected methods
const [abi, setAbi] = useState<AbiFunction[]>([]);
const [abi, setAbi] = useState<AugmentedAbiFunction[]>([]);

const handleMethodSelect = (methodName: string) => {
const methodToAdd = initialContractData.abi.find(
method => method.type === "function" && "name" in method && method.name === methodName,
) as AbiFunction | undefined; // Cast it to AbiFunction | undefined
const handleMethodSelect = (uid: string) => {
const methodToAdd = readMethodsWithInputsAndWriteMethods.find(method => method.uid === uid);

if (methodToAdd && !abi.some(method => method.name === methodName)) {
if (methodToAdd && !abi.some(method => method.uid === uid)) {
const updatedAbi = [...abi, methodToAdd];
setAbi(updatedAbi);
updateUrlWithSelectedMethods(updatedAbi.map(m => m.name));
updateUrlWithSelectedMethods(updatedAbi.map(m => m.uid));
}
};

const removeMethod = (methodName: string) => {
const updatedAbi = abi.filter(fn => fn.name !== methodName);
const removeMethod = (uid: string) => {
const updatedAbi = abi.filter(method => method.uid !== uid);

setAbi(updatedAbi);
updateUrlWithSelectedMethods(updatedAbi.map(m => m.name));
updateUrlWithSelectedMethods(updatedAbi.map(m => m.uid));
};

useEffect(() => {
const selectedMethodNames = (router.query.methods as string)?.split(",") || [];
const selectedMethods = initialContractData.abi.filter(
method => method.type === "function" && "name" in method && selectedMethodNames.includes(method.name),
) as AbiFunction[]; // Cast it to AbiFunction[]
setAbi(selectedMethods);
}, [initialContractData.abi, router?.query?.methods]);
const selectedMethods = readMethodsWithInputsAndWriteMethods.filter(method =>
selectedMethodNames.includes(method.uid),
);
setAbi(selectedMethods as AugmentedAbiFunction[]);
}, [router.query.methods, readMethodsWithInputsAndWriteMethods]);

const { data: contractNameData, isLoading: isContractNameLoading } = useContractRead({
address: initialContractData.address,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AugmentedAbiFunction } from "./ContractUI";
import { WriteOnlyFunctionForm } from "./WriteOnlyFunctionForm";
import { Abi, AbiFunction } from "abitype";
import { Abi } from "abitype";
import { XMarkIcon } from "@heroicons/react/24/outline";
import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";

Expand All @@ -17,7 +18,7 @@ export const ContractWriteMethods = ({
}

const functionsToDisplay = (
(deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]
(deployedContractData.abi as Abi).filter(part => part.type === "function") as AugmentedAbiFunction[]
)
.filter(fn => {
const isWriteableFunction = fn.stateMutability !== "view" && fn.stateMutability !== "pure";
Expand Down Expand Up @@ -51,7 +52,7 @@ export const ContractWriteMethods = ({
inheritedFrom={inheritedFrom}
/>
<button
onClick={() => removeMethod(fn.name)}
onClick={() => removeMethod(fn.uid)}
className="absolute top-0 right-0 btn btn-ghost btn-xs mt-[21px]"
aria-label="Remove method"
>
Expand Down
43 changes: 19 additions & 24 deletions packages/nextjs/components/scaffold-eth/Contract/MethodSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { useState } from "react";
import { AbiFunction } from "abitype";
import { Abi } from "viem";
import { AugmentedAbiFunction } from "./ContractUI";
import { ChevronDownIcon, ChevronRightIcon, XMarkIcon } from "@heroicons/react/24/outline";

interface MethodSelectorProps {
readMethodsWithInputsAndWriteMethods: AbiFunction[];
abi: Abi;
onMethodSelect: (selectedMethods: string) => void;
removeMethod: (methodName: string) => void;
readMethodsWithInputsAndWriteMethods: AugmentedAbiFunction[];
abi: AugmentedAbiFunction[];
onMethodSelect: (uid: string) => void;
removeMethod: (uid: string) => void;
}

export const MethodSelector = ({
Expand All @@ -20,19 +19,15 @@ export const MethodSelector = ({
const [isWriteCollapsed, setIsWriteCollapsed] = useState(false);

const readMethods = readMethodsWithInputsAndWriteMethods.filter(
(method): method is AbiFunction => method.stateMutability === "view" || method.stateMutability === "pure",
method => method.stateMutability === "view" || method.stateMutability === "pure",
);

const writeMethods = readMethodsWithInputsAndWriteMethods.filter(
(method): method is AbiFunction => method.stateMutability !== "view" && method.stateMutability !== "pure",
method => method.stateMutability !== "view" && method.stateMutability !== "pure",
);

const handleMethodSelect = (methodName: string) => {
onMethodSelect(methodName);
};

const isMethodSelected = (methodName: string) => {
return abi.some(method => "name" in method && method.name === methodName);
const isMethodSelected = (uid: string) => {
return abi.some(method => method.uid === uid);
};

return (
Expand All @@ -57,19 +52,19 @@ export const MethodSelector = ({
</h3>
{!isReadCollapsed && (
<div className="flex flex-col items-start gap-1 pb-4">
{readMethods.map((method, index) => (
<div key={index} className="flex items-center gap-2 w-full pr-4">
{readMethods.map(method => (
<div key={method.uid} className="flex items-center gap-2 w-full pr-4">
<button
className={`btn btn-sm btn-ghost font-normal pr-1 w-full justify-between ${
isMethodSelected(method.name) ? "bg-purple-100 pointer-events-none" : ""
isMethodSelected(method.uid) ? "bg-purple-100 pointer-events-none" : ""
}`}
onClick={() => handleMethodSelect(method.name)}
onClick={() => onMethodSelect(method.uid)}
>
{method.name}
{isMethodSelected(method.name) && (
{isMethodSelected(method.uid) && (
<button
className="ml-4 text-xs hover:bg-gray-100 rounded-md p-1 pointer-events-auto"
onClick={() => removeMethod(method.name)}
onClick={() => removeMethod(method.uid)}
>
<XMarkIcon className="h-4 w-4" />
</button>
Expand Down Expand Up @@ -100,15 +95,15 @@ export const MethodSelector = ({
<div key={index} className="flex items-center gap-2 w-full pr-4">
<button
className={`btn btn-sm btn-ghost font-normal pr-1 w-full justify-between ${
isMethodSelected(method.name) ? "bg-purple-100 pointer-events-none" : ""
isMethodSelected(method.uid) ? "bg-purple-100 pointer-events-none" : ""
}`}
onClick={() => handleMethodSelect(method.name)}
onClick={() => onMethodSelect(method.uid)}
>
{method.name}
{isMethodSelected(method.name) && (
{isMethodSelected(method.uid) && (
<button
className="ml-4 text-xs hover:bg-gray-100 rounded-md p-1 pointer-events-auto"
onClick={() => removeMethod(method.name)}
onClick={() => removeMethod(method.uid)}
>
<XMarkIcon className="h-4 w-4" />
</button>
Expand Down

0 comments on commit 30f3d9e

Please sign in to comment.