diff --git a/src/components/ui/icon/index.tsx b/src/components/ui/icon/index.tsx index efb6962..d1e5029 100644 --- a/src/components/ui/icon/index.tsx +++ b/src/components/ui/icon/index.tsx @@ -10,7 +10,7 @@ import { import { BsFillPlayFill } from 'react-icons/bs'; import { FaRegClone } from 'react-icons/fa'; import { FiEdit2, FiEye } from 'react-icons/fi'; -import { GoTriangleRight } from 'react-icons/go'; +import { GoTriangleDown, GoTriangleRight, GoTriangleUp } from 'react-icons/go'; import { GrClear } from 'react-icons/gr'; import { HiDocumentText } from 'react-icons/hi'; import { MdFeedback } from 'react-icons/md'; @@ -42,6 +42,8 @@ export type AppIconType = | 'Beaker' | 'Plus' | 'Home' + | 'AngleUp' + | 'AngleDown' | 'AngleRight' | 'Test' | 'Google' @@ -77,6 +79,8 @@ const Components = { Home: AiOutlineHome, Code, Beaker, + AngleUp: GoTriangleUp, + AngleDown: GoTriangleDown, AngleRight: GoTriangleRight, Test: Test, Google: AiOutlineGoogle, diff --git a/src/components/workspace/BuildProject/BuildProject.module.scss b/src/components/workspace/BuildProject/BuildProject.module.scss index e514ced..0606c02 100644 --- a/src/components/workspace/BuildProject/BuildProject.module.scss +++ b/src/components/workspace/BuildProject/BuildProject.module.scss @@ -54,4 +54,37 @@ margin-top: 1rem; font-size: 0.9rem; } + .cellActions { + width: 100%; + display: flex; + justify-content: flex-end; + button { + display: inline-flex; + justify-content: center; + align-items: center; + border-radius: 50%; + } + } + .addMoreField { + width: 100%; + display: flex; + align-items: center; + justify-content: center; + gap: 0.3rem; + } + .fieldGroup { + width: 100%; + margin: 1rem 0 0 0; + + [class*='ant-form-item-control-input-content'] { + display: flex; + flex-direction: column; + gap: 0.2rem; + } + } + [class*='ant-form-item-explain-error'] { + position: relative; + top: -8px; + pointer-events: none; + } } diff --git a/src/components/workspace/BuildProject/BuildProject.tsx b/src/components/workspace/BuildProject/BuildProject.tsx index 0134313..9913c12 100644 --- a/src/components/workspace/BuildProject/BuildProject.tsx +++ b/src/components/workspace/BuildProject/BuildProject.tsx @@ -19,10 +19,10 @@ import React, { FC, useEffect, useRef, useState } from 'react'; import { Address, Cell } from 'ton-core'; import ContractInteraction from '../ContractInteraction'; import ExecuteFile from '../ExecuteFile/ExecuteFile'; -import OpenFile from '../OpenFile/OpenFile'; import s from './BuildProject.module.scss'; import AppIcon from '@/components/ui/icon'; +import { useForm } from 'antd/lib/form/Form'; import { AddressInput, AmountInput, @@ -33,6 +33,7 @@ import { StringInput, } from '../abiInputs'; import { globalWorkspace } from '../globalWorkspace'; +import CellBuilder, { generateCellCode } from './CellBuilder'; const blankABI = { getters: [], @@ -106,6 +107,7 @@ const BuildProject: FC = ({ const { sandboxBlockchain } = globalWorkspace; const { Option } = Select; + const [deployForm] = useForm(); const { projectFiles, @@ -142,6 +144,19 @@ const BuildProject: FC = ({ }); }; + const cellBuilder = (info: string) => { + if (!activeProject?.language || activeProject?.language !== 'func') + return <>; + return ( + + ); + }; + const deployView = () => { const _contractsToDeploy = contractsToDeploy(); @@ -151,8 +166,10 @@ const BuildProject: FC = ({ return ( <> +
{ if (Object.hasOwn(changedValues, 'contract')) { @@ -177,6 +194,7 @@ const BuildProject: FC = ({ ))} + {cellBuilder('Update initial contract state in ')} {contractABI?.initParams && (
{contractABI?.initParams?.map((item, index) => { @@ -218,6 +236,9 @@ const BuildProject: FC = ({ if (_temp.queryId) { delete _temp.queryId; } + if (_temp.cell) { + delete _temp.cell; + } const initParamsData = contractABI?.initParams; let parametrsType: any = {}; if (initParamsData) { @@ -254,6 +275,9 @@ const BuildProject: FC = ({ // } } initParams = initParams?.slice(0, -1); + if (formValues.cell) { + initParams = formValues.cell; + } try { if (!tonConnector.connected && environment !== 'SANDBOX') { @@ -371,17 +395,29 @@ const BuildProject: FC = ({ 'tact.ts' ); } else { - const stateInitContent = await getFileByPath( + let stateInitContent = await getFileByPath( 'stateInit.cell.ts', projectId ); - if (stateInitContent && !stateInitContent.content) { + let cellCode = ''; + if (stateInitContent && !stateInitContent.content && !initParams) { throw 'State init data is missing in file stateInit.cell.ts'; } + if (initParams) { + cellCode = generateCellCode(initParams as any); + updateProjectById( + { + cellABI: { deploy: initParams }, + }, + projectId + ); + } else { + cellCode = stateInitContent?.content || ''; + } jsOutout = await buildTs( { - 'stateInit.cell.ts': stateInitContent?.content, + 'stateInit.cell.ts': cellCode, 'cell.ts': 'import cell from "./stateInit.cell.ts"; cell;', }, 'cell.ts' @@ -590,17 +626,6 @@ const BuildProject: FC = ({ {environment !== 'SANDBOX' && } - {activeProject?.language !== 'tact' && ( -

- - Update initial contract state in{' '} - {' '} -

- )} -
= ({ ? 'Build' : 'Build' } - description="- Select a contract file to build and deploy" + description="- Select a contract to build" allowedFile={['fc', 'tact']} onCompile={() => { if ( diff --git a/src/components/workspace/BuildProject/CellBuilder.tsx b/src/components/workspace/BuildProject/CellBuilder.tsx new file mode 100644 index 0000000..703a64b --- /dev/null +++ b/src/components/workspace/BuildProject/CellBuilder.tsx @@ -0,0 +1,198 @@ +import AppIcon from '@/components/ui/icon'; +import { useWorkspaceActions } from '@/hooks/workspace.hooks'; +import { MinusCircleOutlined } from '@ant-design/icons'; +import { Button, Form, Input, Select, Switch } from 'antd'; +import { FormInstance } from 'antd/lib/form/Form'; +import { FC, useEffect, useState } from 'react'; +import OpenFile from '../OpenFile'; +import s from './BuildProject.module.scss'; + +interface Props { + info: string; + projectId: string; + type: 'deploy' | 'setter'; + form: FormInstance; +} + +const { Option } = Select; + +export const CellBuilderDataTypes = { + Boolean: { + type: 'Boolean', + cellValue: 'storeBit(__)', + value: 'true/false', + }, + Uint: { + type: 'Uint', + cellValue: 'storeUint(__)', + value: 'value, bits', + }, + Int: { + type: 'Int', + cellValue: 'storeInt(__)', + value: 'value, bits', + }, + Coins: { + type: 'Coins', + cellValue: 'storeCoins(toNano(__))', + value: 'in ton', + }, + Address: { + type: 'Address', + cellValue: 'storeAddress(address("__"))', + value: 'string', + }, +}; + +interface CellValues { + type: string; + value: string; +} + +export const generateCellCode = (cellValues: CellValues[]) => { + const cellCode = cellValues.reduce((code: string, cellValue: CellValues) => { + const cellType = (CellBuilderDataTypes as any)[cellValue.type]; + const cellValueCode = cellType.cellValue.replace('__', cellValue.value); + return code + '.' + cellValueCode; + }, ''); + + return `import { beginCell, address, toNano } from "ton-core"; + + const cell = beginCell() + ${cellCode} + .endCell(); + + export default cell;`; +}; + +const CellBuilder: FC = ({ info, projectId, type, form }) => { + const { project } = useWorkspaceActions(); + const [isCellBuilder, setIsCellBuilder] = useState(false); + + const activeProject = project(projectId); + + const getPlaceHolder = (key: number) => { + return ( + (CellBuilderDataTypes as any)[form.getFieldValue('cell')[key]?.type] + ?.value || 'value' + ); + }; + + useEffect(() => { + form.setFieldsValue({ + cell: activeProject?.cellABI?.[type], + }); + }, [isCellBuilder]); + + return ( +
+

+ - {info} + {' '} + or use {`"Cell Builder"`}{' '} + {type === 'deploy' && ' to send message'} +

+ + { + setIsCellBuilder(e); + }} + /> + + {isCellBuilder && ( + + {(fields, { add, remove, move }, { errors }) => { + return ( + <> + {fields.map(({ key, name, ...restField }, index) => ( + + + + + + + +
+
+
+ ))} + + + + + + + ); + }} +
+ )} +
+ ); +}; + +export default CellBuilder; diff --git a/src/components/workspace/ContractInteraction/ContractInteraction.tsx b/src/components/workspace/ContractInteraction/ContractInteraction.tsx index d5c3a4c..8e03ebc 100644 --- a/src/components/workspace/ContractInteraction/ContractInteraction.tsx +++ b/src/components/workspace/ContractInteraction/ContractInteraction.tsx @@ -10,9 +10,10 @@ import { buildTs } from '@/utility/typescriptHelper'; import { SandboxContract } from '@ton-community/sandbox'; import { useTonConnectUI } from '@tonconnect/ui-react'; import { Button, Form, message } from 'antd'; +import { useForm } from 'antd/lib/form/Form'; import { FC, useEffect, useRef, useState } from 'react'; import ABIUi from '../ABIUi'; -import OpenFile from '../OpenFile/OpenFile'; +import CellBuilder, { generateCellCode } from '../BuildProject/CellBuilder'; import { globalWorkspace } from '../globalWorkspace'; import s from './ContractInteraction.module.scss'; @@ -35,28 +36,39 @@ const ContractInteraction: FC = ({ const [tonConnector] = useTonConnectUI(); const [isLoading, setIsLoading] = useState(''); const { sendMessage } = useContractAction(); - const { getFileByPath } = useWorkspaceActions(); + const { getFileByPath, updateProjectById } = useWorkspaceActions(); const { createLog } = useLogActivity(); const { sandboxWallet: wallet } = globalWorkspace; + const [messageForm] = useForm(); const cellBuilderRef = useRef(null); - const createCell = async () => { + const createCell = async (cell: any) => { if (!cellBuilderRef.current?.contentWindow) return; + let cellCode = ''; + const contractCellContent = await getFileByPath( 'message.cell.ts', projectId ); - if (contractCellContent && !contractCellContent.content) { + if (contractCellContent && !contractCellContent.content && !cell) { throw 'Cell data is missing in file message.cell.ts'; } - if (!contractCellContent?.content?.includes('cell')) { - throw 'cell variable is missing in file message.cell.ts'; + if (cell) { + cellCode = generateCellCode(cell as any); + updateProjectById( + { + cellABI: { setter: cell }, + }, + projectId + ); + } else { + cellCode = contractCellContent?.content || ''; } try { const jsOutout = await buildTs( { - 'message.cell.ts': contractCellContent?.content, + 'message.cell.ts': cellCode, 'cell.ts': 'import cell from "./message.cell.ts"; cell;', }, 'cell.ts' @@ -94,7 +106,7 @@ const ContractInteraction: FC = ({ try { setIsLoading('setter'); - await createCell(); + await createCell(formValues?.cell); } catch (error: any) { setIsLoading(''); console.log(error); @@ -110,6 +122,18 @@ const ContractInteraction: FC = ({ } }; + const cellBuilder = (info: string) => { + if (!language || language !== 'func') return <>; + return ( + + ); + }; + useEffect(() => { const handler = async ( event: MessageEvent<{ name: string; type: string; data: any }> @@ -191,16 +215,8 @@ const ContractInteraction: FC = ({ {language !== 'tact' && ( <> -

- Update cell data in{' '} - {' '} - and then send message -

- + + {cellBuilder('Update cell in ')}