diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index 804b25c5b..5803c3fcd 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -24,6 +24,10 @@ }, "common": { "Add": "Add", + "Cancel": "Cancel", + "Collect": "Collect", + "Copy": "Copy", + "Delete": "Delete", "Filed is repeat": "Filed is repeated", "Filed is repeated": "", "Input": "Input", diff --git a/client/public/locales/zh/common.json b/client/public/locales/zh/common.json index 12d4551f5..0a0069e7d 100644 --- a/client/public/locales/zh/common.json +++ b/client/public/locales/zh/common.json @@ -24,6 +24,10 @@ }, "common": { "Add": "添加", + "Cancel": "取消", + "Collect": "收藏", + "Copy": "复制", + "Delete": "删除", "Filed is repeat": "", "Filed is repeated": "字段重复了", "Input": "输入", diff --git a/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx b/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx index 89fc3e099..0dd4fcdaf 100644 --- a/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx +++ b/client/src/pages/app/detail/components/AdEdit/components/modules/NodeCard.tsx @@ -1,10 +1,11 @@ -import React from 'react'; -import { Box, Flex, useTheme } from '@chakra-ui/react'; +import React, { useMemo } from 'react'; +import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react'; import MyIcon from '@/components/Icon'; import Avatar from '@/components/Avatar'; import type { FlowModuleItemType } from '@/types/flow'; import MyTooltip from '@/components/MyTooltip'; import { QuestionOutlineIcon } from '@chakra-ui/icons'; +import { useTranslation } from 'react-i18next'; type Props = { children?: React.ReactNode | React.ReactNode[] | string; @@ -15,6 +16,7 @@ type Props = { minW?: string | number; moduleId: string; onDelNode: FlowModuleItemType['onDelNode']; + onCopyNode: FlowModuleItemType['onCopyNode']; }; const NodeCard = ({ @@ -23,11 +25,39 @@ const NodeCard = ({ name = '未知模块', description, minW = '300px', + onCopyNode, onDelNode, moduleId }: Props) => { + const { t } = useTranslation(); const theme = useTheme(); + const menuList = useMemo( + () => [ + { + icon: 'copy', + label: t('common.Copy'), + onClick: () => onCopyNode(moduleId) + }, + { + icon: 'delete', + label: t('common.Delete'), + onClick: () => onDelNode(moduleId) + }, + // { + // icon: 'collectionLight', + // label: t('common.Collect'), + // onClick: () => {} + // }, + { + icon: 'back', + label: t('common.Cancel'), + onClick: () => {} + } + ], + [moduleId, onCopyNode, onDelNode, t] + ); + return ( )} - onDelNode(moduleId)} - > - - + + { + e.stopPropagation(); + }} + > + + + + {menuList.map((item) => ( + + + {item.label} + + ))} + + {children} diff --git a/client/src/pages/app/detail/components/AdEdit/index.tsx b/client/src/pages/app/detail/components/AdEdit/index.tsx index ac60798f5..81b2c9353 100644 --- a/client/src/pages/app/detail/components/AdEdit/index.tsx +++ b/client/src/pages/app/detail/components/AdEdit/index.tsx @@ -10,7 +10,7 @@ import ReactFlow, { Connection, useViewport } from 'reactflow'; -import { Box, Flex, IconButton, useTheme, useDisclosure } from '@chakra-ui/react'; +import { Box, Flex, IconButton, useTheme, useDisclosure, position } from '@chakra-ui/react'; import { SmallCloseIcon } from '@chakra-ui/icons'; import { edgeOptions, @@ -126,6 +126,32 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { }, 100); }, []); + const onAddNode = useCallback( + ({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => { + if (!reactFlowWrapper.current) return; + const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); + const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100; + const mouseY = (position.y - reactFlowBounds.top - y) / zoom; + + setNodes((state) => + state.concat( + appModule2FlowNode({ + item: { + ...template, + moduleId: nanoid(), + position: { x: mouseX, y: mouseY } + }, + onChangeNode, + onDelNode, + onDelEdge, + onCopyNode, + onCollectionNode + }) + ) + ); + }, + [x, zoom, y] + ); const onDelNode = useCallback( (nodeId: string) => { setNodes((state) => state.filter((item) => item.id !== nodeId)); @@ -155,6 +181,45 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { }, [setEdges] ); + const onCopyNode = useCallback( + (nodeId: string) => { + setNodes((nodes) => { + const node = nodes.find((node) => node.id === nodeId); + if (!node) return nodes; + const template = { + logo: node.data.logo, + name: node.data.name, + intro: node.data.intro, + description: node.data.description, + flowType: node.data.flowType, + inputs: node.data.inputs, + outputs: node.data.outputs, + showStatus: node.data.showStatus + }; + return nodes.concat( + appModule2FlowNode({ + item: { + ...template, + moduleId: nanoid(), + position: { x: node.position.x + 200, y: node.position.y + 50 } + }, + onChangeNode, + onDelNode, + onDelEdge, + onCopyNode, + onCollectionNode + }) + ); + }); + }, + [setNodes] + ); + const onCollectionNode = useCallback( + (nodeId: string) => { + console.log(nodes.find((node) => node.id === nodeId)); + }, + [nodes] + ); const flow2AppModules = useCallback(() => { const modules: AppModuleItemType[] = nodes.map((item) => ({ @@ -264,39 +329,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { }) ); }, - [setNodes] + [] ); - const onAddNode = useCallback( - ({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => { - if (!reactFlowWrapper.current) return; - const reactFlowBounds = reactFlowWrapper.current.getBoundingClientRect(); - const mouseX = (position.x - reactFlowBounds.left - x) / zoom - 100; - const mouseY = (position.y - reactFlowBounds.top - y) / zoom; - - setNodes((state) => - state.concat( - appModule2FlowNode({ - item: { - ...template, - moduleId: nanoid(), - position: { x: mouseX, y: mouseY } - }, - onChangeNode, - onDelNode, - onDelEdge - }) - ) - ); - }, - [onDelEdge, onChangeNode, onDelNode, setNodes, x, y, zoom] - ); - const onDelConnect = useCallback( - (id: string) => { - setEdges((state) => state.filter((item) => item.id !== id)); - }, - [setEdges] - ); + const onDelConnect = useCallback((id: string) => { + setEdges((state) => state.filter((item) => item.id !== id)); + }, []); const onConnect = useCallback( ({ connect }: { connect: Connection }) => { const source = nodes.find((node) => node.id === connect.source)?.data; @@ -342,7 +380,7 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { ) ); }, - [onDelConnect, setEdges, nodes] + [nodes] ); const { mutate: onclickSave, isLoading } = useRequest({ @@ -373,19 +411,31 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => { item, onChangeNode, onDelNode, - onDelEdge + onDelEdge, + onCopyNode, + onCollectionNode }) ) ); onFixView(); }, - [onDelConnect, setEdges, setNodes, onFixView, onChangeNode, onDelNode, onDelEdge] + [ + onDelConnect, + setEdges, + setNodes, + onFixView, + onChangeNode, + onDelNode, + onDelEdge, + onCopyNode, + onCollectionNode + ] ); useEffect(() => { initData(JSON.parse(JSON.stringify(app))); - }, [app, initData]); + }, [app]); return ( <> diff --git a/client/src/types/flow.d.ts b/client/src/types/flow.d.ts index 3cb39f3fa..6c428e485 100644 --- a/client/src/types/flow.d.ts +++ b/client/src/types/flow.d.ts @@ -61,6 +61,8 @@ export type FlowModuleItemType = FlowModuleTemplateType & { moduleId: string; onChangeNode: (e: FlowModuleItemChangeProps) => void; onDelNode: (id: string) => void; + onCopyNode: (id: string) => void; + onCollectionNode: (id: string) => void; onDelEdge: ({ moduleId, sourceHandle, diff --git a/client/src/utils/adapt.ts b/client/src/utils/adapt.ts index fa6ace9c3..8c1f178de 100644 --- a/client/src/utils/adapt.ts +++ b/client/src/utils/adapt.ts @@ -64,12 +64,16 @@ export const appModule2FlowNode = ({ item, onChangeNode, onDelNode, - onDelEdge + onDelEdge, + onCopyNode, + onCollectionNode }: { item: AppModuleItemType; onChangeNode: FlowModuleItemType['onChangeNode']; onDelNode: FlowModuleItemType['onDelNode']; onDelEdge: FlowModuleItemType['onDelEdge']; + onCopyNode: FlowModuleItemType['onCopyNode']; + onCollectionNode: FlowModuleItemType['onCollectionNode']; }): Node => { // init some static data const template = @@ -109,7 +113,9 @@ export const appModule2FlowNode = ({ }), onChangeNode, onDelNode, - onDelEdge + onDelEdge, + onCopyNode, + onCollectionNode }; return {