feat: copy module

This commit is contained in:
archer
2023-08-07 14:45:49 +08:00
parent 90406fce9e
commit 206eb81bb4
6 changed files with 157 additions and 53 deletions

View File

@@ -24,6 +24,10 @@
}, },
"common": { "common": {
"Add": "Add", "Add": "Add",
"Cancel": "Cancel",
"Collect": "Collect",
"Copy": "Copy",
"Delete": "Delete",
"Filed is repeat": "Filed is repeated", "Filed is repeat": "Filed is repeated",
"Filed is repeated": "", "Filed is repeated": "",
"Input": "Input", "Input": "Input",

View File

@@ -24,6 +24,10 @@
}, },
"common": { "common": {
"Add": "添加", "Add": "添加",
"Cancel": "取消",
"Collect": "收藏",
"Copy": "复制",
"Delete": "删除",
"Filed is repeat": "", "Filed is repeat": "",
"Filed is repeated": "字段重复了", "Filed is repeated": "字段重复了",
"Input": "输入", "Input": "输入",

View File

@@ -1,10 +1,11 @@
import React from 'react'; import React, { useMemo } from 'react';
import { Box, Flex, useTheme } from '@chakra-ui/react'; import { Box, Flex, useTheme, Menu, MenuButton, MenuList, MenuItem } from '@chakra-ui/react';
import MyIcon from '@/components/Icon'; import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar'; import Avatar from '@/components/Avatar';
import type { FlowModuleItemType } from '@/types/flow'; import type { FlowModuleItemType } from '@/types/flow';
import MyTooltip from '@/components/MyTooltip'; import MyTooltip from '@/components/MyTooltip';
import { QuestionOutlineIcon } from '@chakra-ui/icons'; import { QuestionOutlineIcon } from '@chakra-ui/icons';
import { useTranslation } from 'react-i18next';
type Props = { type Props = {
children?: React.ReactNode | React.ReactNode[] | string; children?: React.ReactNode | React.ReactNode[] | string;
@@ -15,6 +16,7 @@ type Props = {
minW?: string | number; minW?: string | number;
moduleId: string; moduleId: string;
onDelNode: FlowModuleItemType['onDelNode']; onDelNode: FlowModuleItemType['onDelNode'];
onCopyNode: FlowModuleItemType['onCopyNode'];
}; };
const NodeCard = ({ const NodeCard = ({
@@ -23,11 +25,39 @@ const NodeCard = ({
name = '未知模块', name = '未知模块',
description, description,
minW = '300px', minW = '300px',
onCopyNode,
onDelNode, onDelNode,
moduleId moduleId
}: Props) => { }: Props) => {
const { t } = useTranslation();
const theme = useTheme(); 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 ( return (
<Box <Box
minW={minW} minW={minW}
@@ -52,19 +82,27 @@ const NodeCard = ({
</MyTooltip> </MyTooltip>
)} )}
<Box flex={1} /> <Box flex={1} />
<Flex <Menu autoSelect={false} isLazy>
className={'nodrag'} <MenuButton
w={'22px'} className={'nodrag'}
h={'22px'} _hover={{ bg: 'myWhite.600' }}
alignItems={'center'} cursor={'pointer'}
justifyContent={'center'} borderRadius={'md'}
color={'myGray.600'} onClick={(e) => {
_hover={{ color: 'red.600' }} e.stopPropagation();
cursor={'pointer'} }}
onClick={() => onDelNode(moduleId)} >
> <MyIcon name={'more'} w={'14px'} p={2} />
<MyIcon name="delete" w={'16px'} /> </MenuButton>
</Flex> <MenuList color={'myGray.700'} minW={`120px !important`} zIndex={10}>
{menuList.map((item) => (
<MenuItem key={item.label} onClick={item.onClick} py={[2, 3]}>
<MyIcon name={item.icon as any} w={['14px', '16px']} />
<Box ml={[1, 2]}>{item.label}</Box>
</MenuItem>
))}
</MenuList>
</Menu>
</Flex> </Flex>
{children} {children}
</Box> </Box>

View File

@@ -10,7 +10,7 @@ import ReactFlow, {
Connection, Connection,
useViewport useViewport
} from 'reactflow'; } 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 { SmallCloseIcon } from '@chakra-ui/icons';
import { import {
edgeOptions, edgeOptions,
@@ -126,6 +126,32 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
}, 100); }, 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( const onDelNode = useCallback(
(nodeId: string) => { (nodeId: string) => {
setNodes((state) => state.filter((item) => item.id !== nodeId)); setNodes((state) => state.filter((item) => item.id !== nodeId));
@@ -155,6 +181,45 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
}, },
[setEdges] [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 flow2AppModules = useCallback(() => {
const modules: AppModuleItemType[] = nodes.map((item) => ({ const modules: AppModuleItemType[] = nodes.map((item) => ({
@@ -264,39 +329,12 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
}) })
); );
}, },
[setNodes] []
); );
const onAddNode = useCallback( const onDelConnect = useCallback((id: string) => {
({ template, position }: { template: FlowModuleTemplateType; position: XYPosition }) => { setEdges((state) => state.filter((item) => item.id !== id));
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 onConnect = useCallback( const onConnect = useCallback(
({ connect }: { connect: Connection }) => { ({ connect }: { connect: Connection }) => {
const source = nodes.find((node) => node.id === connect.source)?.data; 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({ const { mutate: onclickSave, isLoading } = useRequest({
@@ -373,19 +411,31 @@ const AppEdit = ({ app, fullScreen, onFullScreen }: Props) => {
item, item,
onChangeNode, onChangeNode,
onDelNode, onDelNode,
onDelEdge onDelEdge,
onCopyNode,
onCollectionNode
}) })
) )
); );
onFixView(); onFixView();
}, },
[onDelConnect, setEdges, setNodes, onFixView, onChangeNode, onDelNode, onDelEdge] [
onDelConnect,
setEdges,
setNodes,
onFixView,
onChangeNode,
onDelNode,
onDelEdge,
onCopyNode,
onCollectionNode
]
); );
useEffect(() => { useEffect(() => {
initData(JSON.parse(JSON.stringify(app))); initData(JSON.parse(JSON.stringify(app)));
}, [app, initData]); }, [app]);
return ( return (
<> <>

View File

@@ -61,6 +61,8 @@ export type FlowModuleItemType = FlowModuleTemplateType & {
moduleId: string; moduleId: string;
onChangeNode: (e: FlowModuleItemChangeProps) => void; onChangeNode: (e: FlowModuleItemChangeProps) => void;
onDelNode: (id: string) => void; onDelNode: (id: string) => void;
onCopyNode: (id: string) => void;
onCollectionNode: (id: string) => void;
onDelEdge: ({ onDelEdge: ({
moduleId, moduleId,
sourceHandle, sourceHandle,

View File

@@ -64,12 +64,16 @@ export const appModule2FlowNode = ({
item, item,
onChangeNode, onChangeNode,
onDelNode, onDelNode,
onDelEdge onDelEdge,
onCopyNode,
onCollectionNode
}: { }: {
item: AppModuleItemType; item: AppModuleItemType;
onChangeNode: FlowModuleItemType['onChangeNode']; onChangeNode: FlowModuleItemType['onChangeNode'];
onDelNode: FlowModuleItemType['onDelNode']; onDelNode: FlowModuleItemType['onDelNode'];
onDelEdge: FlowModuleItemType['onDelEdge']; onDelEdge: FlowModuleItemType['onDelEdge'];
onCopyNode: FlowModuleItemType['onCopyNode'];
onCollectionNode: FlowModuleItemType['onCollectionNode'];
}): Node<FlowModuleItemType> => { }): Node<FlowModuleItemType> => {
// init some static data // init some static data
const template = const template =
@@ -109,7 +113,9 @@ export const appModule2FlowNode = ({
}), }),
onChangeNode, onChangeNode,
onDelNode, onDelNode,
onDelEdge onDelEdge,
onCopyNode,
onCollectionNode
}; };
return { return {