diff --git a/.changeset/thin-colts-own.md b/.changeset/thin-colts-own.md new file mode 100644 index 0000000..2c3993b --- /dev/null +++ b/.changeset/thin-colts-own.md @@ -0,0 +1,5 @@ +--- +"solive-core": patch +--- + +refactor log output diff --git a/.eslintrc.js b/.eslintrc.js index fb157e3..3ce29b7 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -61,6 +61,7 @@ module.exports = { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/no-non-null-asserted-optional-chain': 'off', '@typescript-eslint/no-non-null-assertion': 'off', + 'no-unused-expressions': 'off', // React 相关 'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }], @@ -68,10 +69,12 @@ module.exports = { 'react/jsx-props-no-spreading': 'off', 'react-hooks/rules-of-hooks': 'error', 'react-hooks/exhaustive-deps': 'warn', - 'react/require-default-props': 'warn', 'react/no-unused-prop-types': 'warn', 'jsx-a11y/click-events-have-key-events': 'warn', 'react/jsx-no-bind': 'off', + 'react/require-default-props': 'off', + 'react/no-array-index-key': 'off', + 'jsx-a11y/no-static-element-interactions': 'off', // import 相关 'import/prefer-default-export': 'off', diff --git a/apps/doc/docs/extension/docusaurus-plugins/props.mdx b/apps/doc/docs/extension/docusaurus-plugins/props.mdx index 8040072..1c51055 100644 --- a/apps/doc/docs/extension/docusaurus-plugins/props.mdx +++ b/apps/doc/docs/extension/docusaurus-plugins/props.mdx @@ -8,15 +8,15 @@ hide_title: true > Note: properties may change the default values and new properties will be added in the future. -| Property | Type | Default Value | Description | -| --- | --- | --- | --- | -| width | string | 90% | Container width | -| height | string | 500px | Container height | -| simple | boolean | false | Is it minimal mode? | -| consoleOpen | boolean | true | Display console? | -| consoleTriggerControl | boolean | false | Show console trigger button? | -| consoleDefaultVisible | boolean | false | Is console visible by default? | -| deployOpen | boolean | true | Display deploy module? | -| deployDefaultVisible | boolean | false | Is deploy module visible by default? | -| fileNavOpen | boolean | true | Display file navigation bar? | -| fileNavDefaultVisible | boolean | false | Is file navigation bar visible by default? | \ No newline at end of file +| Property | Type | Default Value | Description | +|-----------------------|---------|---------------|--------------------------------------------| +| width | string | 90% | Container width | +| height | string | 500px | Container height | +| simple | boolean | false | Is it minimal mode? | +| consoleOpen | boolean | true | Display console? | +| consoleTriggerControl | boolean | false | Show console trigger button? | +| consoleDefaultVisible | boolean | false | Is console visible by default? | +| deployOpen | boolean | true | Display deploy module? | +| deployDefaultVisible | boolean | false | Is deploy module visible by default? | +| fileNavOpen | boolean | true | Display file navigation bar? | +| fileNavDefaultVisible | boolean | false | Is file navigation bar visible by default? | diff --git a/apps/doc/docs/solive-props.mdx b/apps/doc/docs/solive-props.mdx index 759b59e..54c6158 100644 --- a/apps/doc/docs/solive-props.mdx +++ b/apps/doc/docs/solive-props.mdx @@ -8,46 +8,46 @@ hide_title: true ## Property Table -| Property Name | Description | Type | Default Value | Required | -| ------------- | ----------- | ---- | ------------- | -------- | -| id | Unique form identifier | string | - | ✅ | -| modelInfos | File information | array | [] | ✅ | -| height | Height | string | - | ✅ | -| rounded | Window corner radius | string | 12px | - | -| disableValidation | Whether to disable validation | boolean | false | - | -| monacoEditorOptions | Monaco editor configuration | (Refer to [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html)) | - | - | -| fileNav | Top file navigation | TFileNav | (Refer to [TFileNav](/docs/SoLive-props#tfilenav)) | - | -| console | Log block | TConsole | (Refer to [TConsole](/docs/SoLive-props#tconsole)) | - | -| deploy | Deployment block | TDeploy | (Refer to [TDeploy](/docs/SoLive-props#tdeploy)) | - | +| Property Name | Description | Type | Default Value | Required | +|---------------------|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------|----------| +| id | Unique form identifier | string | - | ✅ | +| modelInfos | File information | array | [] | ✅ | +| height | Height | string | - | ✅ | +| rounded | Window corner radius | string | 12px | - | +| disableValidation | Whether to disable validation | boolean | false | - | +| monacoEditorOptions | Monaco editor configuration | (Refer to [IStandaloneEditorConstructionOptions](https://microsoft.github.io/monaco-editor/typedoc/interfaces/editor.IStandaloneEditorConstructionOptions.html)) | - | - | +| fileNav | Top file navigation | TFileNav | (Refer to [TFileNav](/docs/solive-props#tfilenav)) | - | +| console | Log block | TConsole | (Refer to [TConsole](/docs/solive-props#tconsole)) | - | +| deploy | Deployment block | TDeploy | (Refer to [TDeploy](/docs/solive-props#tdeploy)) | - | ## TFileNav Top file navigation property table -| Property Name | Description | Type | Default Value | Required | -| ------------- | ----------- | ---- | ------------- | -------- | -| open | Whether to enable the feature | boolean | true | - | +| Property Name | Description | Type | Default Value | Required | +|---------------|-------------------------------|---------|---------------|----------| +| open | Whether to enable the feature | boolean | true | - | ## TConsole Log block property table -| Property Name | Description | Type | Default Value | Required | -| ------------- | ----------- | ---- | ------------- | -------- | -| defaultVisible | Default visibility | boolean | true | - | -| defaultHeight | Default height | string | "30%" | - | -| minHeight | Minimum height | number | 78 | - | -| triggerControl | Whether to enable control | boolean | true | - | -| open | Whether to enable the feature | boolean | true | - | +| Property Name | Description | Type | Default Value | Required | +|----------------|-------------------------------|---------|---------------|----------| +| defaultVisible | Default visibility | boolean | true | - | +| defaultHeight | Default height | string | "30%" | - | +| minHeight | Minimum height | number | 78 | - | +| triggerControl | Whether to enable control | boolean | true | - | +| open | Whether to enable the feature | boolean | true | - | ## TDeploy Deployment block property table -| Property Name | Description | Type | Default Value | Required | -| ------------- | ----------- | ---- | ------------- | -------- | -| defaultVisible | Default visibility | boolean | true | - | -| defaultWidth | Default width | string | "200px" | - | -| minWidth | Minimum width | number | 140 | - | -| maxWidth | Maximum width | number | 240 | - | -| open | Whether to enable the feature | boolean | true | - | \ No newline at end of file +| Property Name | Description | Type | Default Value | Required | +|----------------|-------------------------------|---------|---------------|----------| +| defaultVisible | Default visibility | boolean | true | - | +| defaultWidth | Default width | string | "200px" | - | +| minWidth | Minimum width | number | 140 | - | +| maxWidth | Maximum width | number | 240 | - | +| open | Whether to enable the feature | boolean | true | - | diff --git a/packages/core/src/components/Console/index.tsx b/packages/core/src/components/Console/index.tsx index 16190f7..a927e94 100644 --- a/packages/core/src/components/Console/index.tsx +++ b/packages/core/src/components/Console/index.tsx @@ -1,59 +1,66 @@ -import ReactJson from "react-json-view"; +import { useEffect, useRef } from 'react'; -import NavBar from "../NavBar"; -import {useEditor} from "../../editor/contexts/editorContext"; -import {generateConsoleMessageToShow} from "../../types/console"; -import {useConsole} from "../../editor/contexts/consoleContext"; -import {useEffect, useRef} from "react"; +import ReactJson from 'react-json-view'; + +import NavBar from '../NavBar'; +import { useEditor } from '../../editor/contexts/editorContext'; +import { generateConsoleMessageToShow } from '../../types/console'; +import { useConsole } from '../../editor/contexts/consoleContext'; const NAVS = [ - {label: "Console", id: "console"}, - {label: "", id: "empty"}, + { label: 'Console', id: 'console' }, + { label: '', id: 'empty' }, ]; type TProps = { onDeleteClick?: () => void; } -const Console = (props: TProps) => { +function Console(props: TProps) { + const { + onDeleteClick, + } = props; const { id } = useEditor(); - const {consoles} = useConsole(); - const consoleRef = useRef(null) + const { consoles } = useConsole(); + const consoleRef = useRef(null); const consoleMessages = consoles || []; - const handleDeleteClick = () => props.onDeleteClick && props.onDeleteClick(); + const handleDeleteClick = () => onDeleteClick && onDeleteClick(); useEffect(() => { - consoleRef.current?.scrollTo(0, consoleRef.current.scrollHeight) - }, [consoleMessages]) + consoleRef.current?.scrollTo(0, consoleRef.current.scrollHeight); + }, [consoleMessages]); return ( -
+
{consoleMessages.map((item, index) => { - let data + let data; try { - data = JSON.parse(item.message) + data = JSON.parse(item.message); } catch (e) { /* empty */ } if (data instanceof Object) { return (
- [{new Date(item.timestamp).toLocaleTimeString()}]: - -
- ) - } else - return ( -
- {generateConsoleMessageToShow(item).toString()} + + [ + {new Date(item.timestamp).toLocaleTimeString()} + ]: + +
- ) + ); + } return ( +
+ {generateConsoleMessageToShow(item)} +
+ ); })}
- ) + ); } export default Console; diff --git a/packages/core/src/components/DeployAndCall/AbiForm.tsx b/packages/core/src/components/DeployAndCall/AbiForm.tsx index 2cb07ea..9d602b7 100644 --- a/packages/core/src/components/DeployAndCall/AbiForm.tsx +++ b/packages/core/src/components/DeployAndCall/AbiForm.tsx @@ -1,100 +1,128 @@ -import {useMemo, useRef, useState} from "react"; -import {useForm} from "react-hook-form"; -import {ContractInterface, ethers} from "ethers"; +import { useMemo, useRef, useState } from 'react'; -import RHFInput from "../HookForm/RHFInput"; -import {FormProvider} from "../HookForm"; -import RHFSelect from "../HookForm/RHFSelect"; -import Button from "../Button"; -import {useRelayNetwork} from "../../editor/contexts/relayNetworkContext"; -import {useConsole} from "../../editor/contexts/consoleContext"; +import { useForm } from 'react-hook-form'; +import { ethers } from 'ethers'; +import { ABIDescription, FunctionDescription } from 'solive-compiler-utils'; -import AbiInput from "./AbiInput"; +import RHFInput from '../HookForm/RHFInput'; +import { FormProvider } from '../HookForm'; +import RHFSelect from '../HookForm/RHFSelect'; +import Button from '../Button'; +import { useRelayNetwork } from '../../editor/contexts/relayNetworkContext'; +import { useConsole } from '../../editor/contexts/consoleContext'; -const AbiForm = (props: any) => { - const [loading, setLoading] = useState(false); - const {providerRef} = useRelayNetwork(); - const {addConsole} = useConsole(); - const abiOptions = (abi: ContractInterface) => { - if (Array.isArray(abi)) { - return abi.filter(v => { - return v.type !== 'constructor' && v.type === 'function' - }).map(v => { - return { - label: v.name, - value: v.name, - }; - }) - } else { - return []; - } - } +import AbiInput from './AbiInput'; +import { transformAbiOutputs } from './utils/utils'; + +type TProps = { + signerAddress: string; + abi: ABIDescription[]; + address: string; + name: string; +} +function AbiForm(props: TProps) { + const { + signerAddress, + abi, + address, + } = props; + const [loading, setLoading] = useState(false); + const { providerRef } = useRelayNetwork(); + const { addConsole } = useConsole(); const methods = useForm({ defaultValues: { callFunction: '', - ethAmount: '' - } + ethAmount: '', + }, }); - const {watch} = methods; + const { watch } = methods; const callFunction = watch('callFunction'); const ethAmount = watch('ethAmount'); - const abiInputRef = useRef<{ getValues: () => any; watch: (v: string) => any; }>(); + const abiInputRef = useRef<{ getValues:() => any; watch: (v: string) => any; }>(); + + const abiOptions = useMemo(() => { + if (Array.isArray(abi)) { + return abi + .filter((v) => v.type !== 'constructor' && v.type === 'function') + .map((v) => ({ + label: v.name || '', + value: v.name || '', + })); + } + return []; + }, [abi]); const selectedFunction = useMemo(() => { - if (Array.isArray(props.abi)) { - return props.abi.filter((v: { name: string; }) => v.name === callFunction)[0]; - } else { - return false + if (Array.isArray(abi)) { + return abi.filter((v) => v.type === 'function' && v.name === callFunction)[0] as FunctionDescription; } + return {} as FunctionDescription; }, [callFunction]); const handleSubmit = async () => { const submitParams = abiInputRef.current?.getValues(); - setLoading(true); - try{ - const signer = await providerRef.current.getSigner(props.signerAddress) - const contract = new ethers.Contract(props.address, props.abi, signer); - let result - if(selectedFunction?.stateMutability === "payable") { + try { + const signer = await providerRef.current.getSigner(signerAddress); + const contract = new ethers.Contract(address, abi, signer); + let result; + if (selectedFunction?.stateMutability === 'payable') { const overrides = { value: ethers.utils.parseEther(ethAmount), - } + }; result = await contract[callFunction](...Object.values(submitParams || []), overrides); - }else{ + } else { result = await contract[callFunction](...Object.values(submitParams || [])); } - console.log(result) - addConsole([ - { - type: "success", - message: JSON.stringify(result) - } - ]); - }catch (e) { + + if (selectedFunction.stateMutability === 'view') { + addConsole([ + { + type: 'success', + message: `Result: ${transformAbiOutputs(selectedFunction.outputs || [], result)}`, + }, + ]); + } else { + addConsole([ + { + type: 'success', + message: JSON.stringify(result), + }, + ]); + const txResult = await result.wait(); + addConsole([ + { + type: 'success', + message: JSON.stringify(txResult), + }, + ]); + } + } catch (e: any) { addConsole([ { - type: "error", - message: e - } + type: 'error', + message: e.message, + }, ]); } setLoading(false); - } + }; - return - - {selectedFunction?.stateMutability === "payable" - ? - : ''} - {selectedFunction?.inputs?.length > 0 && ( - - )} - {selectedFunction ? : ''} - + return ( + + + {selectedFunction?.stateMutability === 'payable' + ? + : ''} + {selectedFunction?.inputs && selectedFunction?.inputs.length > 0 && ( + + )} + {selectedFunction && } + + ); } export default AbiForm; diff --git a/packages/core/src/components/DeployAndCall/AbiInput.tsx b/packages/core/src/components/DeployAndCall/AbiInput.tsx index 11469d7..3aac3b4 100644 --- a/packages/core/src/components/DeployAndCall/AbiInput.tsx +++ b/packages/core/src/components/DeployAndCall/AbiInput.tsx @@ -5,7 +5,7 @@ import {forwardRef, useImperativeHandle} from "react"; import RHFInput from "../HookForm/RHFInput"; import {FormProvider} from "../HookForm"; -import {transformAbiParams} from "./utils"; +import {transformAbiParams} from "./utils/utils"; type TProps = { inputs: ABIParameter[]; diff --git a/packages/core/src/components/DeployAndCall/Call.tsx b/packages/core/src/components/DeployAndCall/Call.tsx index 184b3b8..e4b5e92 100644 --- a/packages/core/src/components/DeployAndCall/Call.tsx +++ b/packages/core/src/components/DeployAndCall/Call.tsx @@ -1,12 +1,12 @@ -import {Disclosure} from '@headlessui/react' -import {ChevronUpIcon} from "@heroicons/react/24/outline"; +import { Disclosure } from '@headlessui/react'; +import { ChevronUpIcon } from '@heroicons/react/24/outline'; -import {useDeployed} from "../../editor/contexts/deployedContext"; +import { useDeployed } from '../../editor/contexts/deployedContext'; -import AbiForm from "./AbiForm"; +import AbiForm from './AbiForm'; -const Call = () => { - const {compiledContracts} = useDeployed() +function Call() { + const { compiledContracts } = useDeployed(); return (
@@ -14,16 +14,22 @@ const Call = () => { Call
- {compiledContracts.length === 0 ? - No contract has been deployed yet. : ''} - {compiledContracts.map((v) => { - return
+ {compiledContracts.length === 0 + ? No contract has been deployed yet. : ''} + {compiledContracts.map((v) => ( +
- {({open}) => ( + {({ open }) => ( <> - {v.name} + className="flex items-center justify-between box-border w-full py-2 px-2 rounded border-none text-white placeholder:text-[#878E95] text-left bg-[#36384A] focus:outline-none focus:shadow-outline" + > + + {v.name} + {' ('} + {v.address} + ) + { /> - + )}
- })} + ))}
); diff --git a/packages/core/src/components/DeployAndCall/Deploy.tsx b/packages/core/src/components/DeployAndCall/Deploy.tsx index 31f580c..21bf3bf 100644 --- a/packages/core/src/components/DeployAndCall/Deploy.tsx +++ b/packages/core/src/components/DeployAndCall/Deploy.tsx @@ -19,7 +19,7 @@ import { useConsole } from '../../editor/contexts/consoleContext'; import { useRelayNetwork } from '../../editor/contexts/relayNetworkContext'; import AbiInput from './AbiInput'; -import { getAccountOptions } from './accounts'; +import { getAccountOptions } from './utils/accounts'; const ENVIRONMENT_OPTIONS = [ { label: 'London', value: Hardfork.London }, @@ -38,7 +38,7 @@ const resolveConstructor = (abi: any = []) => { }; const useCompile = () => { - const { state, actions, id } = useEditor(); + const { state } = useEditor(); const { addConsole } = useConsole(); const models = state.models || []; const modelIndex = state.modelIndex || 0; @@ -57,8 +57,7 @@ const useCompile = () => { const compileResult: any = await state.codeParser.compilerService.compile(); const hasError = compileResult.output.errors?.filter((item: any) => item.severity === 'error').length > 0; if (hasError) { - console.log(compileResult.errors); - throw new Error('编译失败'); + throw new Error(compileResult.output.errors.map((item: any) => item.formattedMessage).join('\n')); } setError(false); @@ -67,8 +66,11 @@ const useCompile = () => { label: key, value: key, }))); + addConsole([{ + type: 'success', + message: 'Compile success!', + }]); } catch (e: any) { - console.log(e); setError(true); addConsole([{ type: 'error', @@ -109,7 +111,7 @@ const useDeploy = () => { try { setLoading(true); if (!abi) { - throw new Error('Please select the deployed contract first.'); + throw new Error('Please compile or select contract first!'); } const signer = await provider.provider.getSigner(signerAddress); const [contract, tx] = await deploy(abi, bytecode, signer, callOptions, Object.values(params || {})); @@ -124,15 +126,12 @@ const useDeploy = () => { type: 'success', message: JSON.stringify(tx), }, - ]); - addConsole([ { type: 'success', message: `${name} - Contract deployment succeeded: ${contract.address}`, }, ]); } catch (e: any) { - console.log(e); addConsole([ { type: 'error', diff --git a/packages/core/src/components/DeployAndCall/index.tsx b/packages/core/src/components/DeployAndCall/index.tsx index f29e37a..51efdc9 100644 --- a/packages/core/src/components/DeployAndCall/index.tsx +++ b/packages/core/src/components/DeployAndCall/index.tsx @@ -1,20 +1,19 @@ -import {Allotment} from "allotment"; +import { Allotment } from 'allotment'; -import NavBar from "../NavBar"; +import NavBar from '../NavBar'; -import Deploy from "./Deploy"; -import Call from "./Call"; +import Deploy from './Deploy'; +import Call from './Call'; -const PlaceholderElement = () => { +function PlaceholderElement() { return (
Deploy & Run Contract
- ) + ); } -const DeployAndCall = () => { - +function DeployAndCall() { return (
} /> @@ -27,15 +26,9 @@ const DeployAndCall = () => { - {/**/} - {/**/} - {/**/} - {/*