feat: 新增框选后的右键复制功能,封装复制粘贴函数

feature
钟良源 2 days ago
parent 82d2212a48
commit 67b5deafae

@ -131,6 +131,9 @@ importers:
remark-gfm: remark-gfm:
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1 version: 4.0.1
utif:
specifier: ^3.1.0
version: 3.1.0
devDependencies: devDependencies:
'@svgr/webpack': '@svgr/webpack':
specifier: ^5.5.0 specifier: ^5.5.0
@ -4990,6 +4993,9 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
utif@3.1.0:
resolution: {integrity: sha512-WEo4D/xOvFW53K5f5QTaTbbiORcm2/pCL9P6qmJnup+17eYfKaEhDeX9PeQkuyEoIxlbGklDuGl8xwuXYMrrXQ==}
util-deprecate@1.0.2: util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
@ -11549,6 +11555,10 @@ snapshots:
dependencies: dependencies:
react: 17.0.2 react: 17.0.2
utif@3.1.0:
dependencies:
pako: 1.0.11
util-deprecate@1.0.2: {} util-deprecate@1.0.2: {}
util.promisify@1.0.1: util.promisify@1.0.1:

@ -163,6 +163,54 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
// 流程发布弹窗状态 // 流程发布弹窗状态
const [publishModalVisible, setPublishModalVisible] = React.useState(false); const [publishModalVisible, setPublishModalVisible] = React.useState(false);
const selectedCopyableNodes = useMemo(
() =>
nodes.filter(
(node) =>
node.selected && node.type !== 'start' && node.type !== 'end'
),
[nodes]
);
const selectedEdgeCount = useMemo(
() => edges.filter((edge) => edge.selected).length,
[edges]
);
const hasMultiElementSelection =
selectedCopyableNodes.length > 1 || selectedEdgeCount > 0;
const copySelectedElements = React.useCallback(
(fallbackNode?: Node) => {
const shouldCopySelection =
selectedCopyableNodes.length > 0 &&
(!fallbackNode || fallbackNode.selected);
const nodeToCopy =
(shouldCopySelection ? selectedCopyableNodes[0] : undefined) ||
(fallbackNode?.type !== 'start' && fallbackNode?.type !== 'end'
? fallbackNode
: undefined);
if (!nodeToCopy) {
return;
}
copyNode(nodeToCopy);
setIsEditModalOpen(false);
setMenu(null);
},
[copyNode, selectedCopyableNodes, setIsEditModalOpen, setMenu]
);
const contextNode =
menu?.type === 'node' ? nodes.find((node) => node.id === menu.id) : null;
const contextEdge =
menu?.type === 'edge' ? edges.find((edge) => edge.id === menu.id) : null;
const nodeContextCopiesSelection =
!!contextNode?.selected && hasMultiElementSelection;
const edgeContextCopiesSelection =
!!contextEdge?.selected && selectedCopyableNodes.length > 0;
// 监听自定义事件以隐藏/显示节点 // 监听自定义事件以隐藏/显示节点
useEffect(() => { useEffect(() => {
const handleToggleNodeVisibility = (event: CustomEvent) => { const handleToggleNodeVisibility = (event: CustomEvent) => {
@ -255,12 +303,11 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
// Ctrl/Cmd+C 复制选中的节点 // Ctrl/Cmd+C 复制选中的节点
if (isModifierKey && e.key === 'c' && canEdit) { if (isModifierKey && e.key === 'c' && canEdit) {
// 获取当前选中的节点 // 获取当前选中的节点
const selectedNode = nodes.find((node) => node.selected); const selectedNode = nodes.find(
(node) =>
node.selected && node.type !== 'start' && node.type !== 'end'
);
if (selectedNode) { if (selectedNode) {
// 不允许复制开始和结束节点
if (selectedNode.type === 'start' || selectedNode.type === 'end') {
return;
}
e.preventDefault(); e.preventDefault();
copyNode(selectedNode); copyNode(selectedNode);
} }
@ -650,10 +697,11 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}} }}
> >
<NodeContextMenu <NodeContextMenu
node={nodes.find((n) => n.id === menu.id)!} node={contextNode!}
onDelete={deleteNode} onDelete={deleteNode}
onEdit={editNode} onEdit={editNode}
onCopy={copyNode} onCopy={copySelectedElements}
copyLabel={nodeContextCopiesSelection ? '复制选中内容' : '复制节点'}
onCloseMenu={setMenu} onCloseMenu={setMenu}
onCloseOpenModal={setIsEditModalOpen} onCloseOpenModal={setIsEditModalOpen}
isRunning={currentAppIsRunning} isRunning={currentAppIsRunning}
@ -672,9 +720,13 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
}} }}
> >
<EdgeContextMenu <EdgeContextMenu
edge={edges.find((e) => e.id === menu.id)!} edge={contextEdge!}
onDelete={deleteEdge} onDelete={deleteEdge}
onEdit={editEdge} onEdit={editEdge}
onCopy={
edgeContextCopiesSelection ? () => copySelectedElements() : undefined
}
copyLabel="复制选中内容"
onAddNode={(edge) => { onAddNode={(edge) => {
setEdgeForNodeAdd(edge); setEdgeForNodeAdd(edge);
setIsEditModalOpen(false); setIsEditModalOpen(false);
@ -696,6 +748,15 @@ const FlowEditorMain: React.FC<FlowEditorMainProps> = (props) => {
> >
<PaneContextMenu <PaneContextMenu
position={menu.position!} position={menu.position!}
onCopySelection={
selectedCopyableNodes.length > 0
? () => copySelectedElements()
: undefined
}
onPasteNode={(position) => {
pasteNode(position);
setMenu(null);
}}
onAddNode={( onAddNode={(
nodeType: string, nodeType: string,
position: { x: number; y: number }, position: { x: number; y: number },

@ -6,6 +6,8 @@ interface EdgeContextMenuProps {
edge: Edge; edge: Edge;
onDelete?: (edge: Edge) => void; onDelete?: (edge: Edge) => void;
onEdit?: (edge: Edge) => void; onEdit?: (edge: Edge) => void;
onCopy?: () => void;
copyLabel?: string;
onAddNode?: (edge: Edge) => void; // 添加在边上添加节点的功能 onAddNode?: (edge: Edge) => void; // 添加在边上添加节点的功能
} }
@ -13,6 +15,8 @@ const EdgeContextMenu: React.FC<EdgeContextMenuProps> = ({
edge, edge,
onDelete, onDelete,
onEdit, onEdit,
onCopy,
copyLabel = '复制选中内容',
onAddNode onAddNode
}) => { }) => {
const handleDelete = () => { const handleDelete = () => {
@ -23,6 +27,10 @@ const EdgeContextMenu: React.FC<EdgeContextMenuProps> = ({
onEdit && onEdit(edge); onEdit && onEdit(edge);
}; };
const handleCopy = () => {
onCopy && onCopy();
};
// 添加在边上添加节点的处理函数 // 添加在边上添加节点的处理函数
const handleAddNode = () => { const handleAddNode = () => {
onAddNode && onAddNode(edge); onAddNode && onAddNode(edge);
@ -36,6 +44,11 @@ const EdgeContextMenu: React.FC<EdgeContextMenuProps> = ({
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)' boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)'
}} }}
> >
{onCopy && (
<Menu.Item key="copy" onClick={handleCopy}>
{copyLabel}
</Menu.Item>
)}
<Menu.Item key="add-node" onClick={handleAddNode}> <Menu.Item key="add-node" onClick={handleAddNode}>
</Menu.Item> </Menu.Item>
@ -46,4 +59,4 @@ const EdgeContextMenu: React.FC<EdgeContextMenuProps> = ({
); );
}; };
export default EdgeContextMenu; export default EdgeContextMenu;

@ -9,6 +9,7 @@ interface NodeContextMenuProps {
onRename?: (node: Node) => void; onRename?: (node: Node) => void;
onDelete?: (node: Node) => void; onDelete?: (node: Node) => void;
onCopy?: (node: Node) => void; onCopy?: (node: Node) => void;
copyLabel?: string;
onEdit?: (node: Node) => void; onEdit?: (node: Node) => void;
onCloseMenu?: (data: React.Dispatch<React.SetStateAction<any>>) => void; onCloseMenu?: (data: React.Dispatch<React.SetStateAction<any>>) => void;
onCloseOpenModal?: (boolean: boolean) => void; onCloseOpenModal?: (boolean: boolean) => void;
@ -19,6 +20,7 @@ const NodeContextMenu: React.FC<NodeContextMenuProps> = ({
node, node,
onDelete, onDelete,
onCopy, onCopy,
copyLabel = '复制节点',
onEdit, onEdit,
onCloseMenu, onCloseMenu,
onCloseOpenModal, onCloseOpenModal,
@ -104,7 +106,7 @@ const NodeContextMenu: React.FC<NodeContextMenuProps> = ({
menuItems.push( menuItems.push(
<Menu.Item key="copy" onClick={handleCopy}> <Menu.Item key="copy" onClick={handleCopy}>
{copyLabel}
</Menu.Item> </Menu.Item>
); );
menuItems.push( menuItems.push(

@ -6,11 +6,15 @@ const SubMenu = Menu.SubMenu;
interface PaneContextMenuProps { interface PaneContextMenuProps {
onAddNode: (nodeType: string, position: { x: number; y: number }, node: any) => void; onAddNode: (nodeType: string, position: { x: number; y: number }, node: any) => void;
onCopySelection?: () => void;
onPasteNode?: (position: { x: number; y: number }) => void;
position: { x: number; y: number }; position: { x: number; y: number };
} }
const PaneContextMenu: React.FC<PaneContextMenuProps> = ({ const PaneContextMenu: React.FC<PaneContextMenuProps> = ({
onAddNode, onAddNode,
onCopySelection,
onPasteNode,
position position
}) => { }) => {
// 包装onAddNode函数以适配AddNodeMenu组件的接口 // 包装onAddNode函数以适配AddNodeMenu组件的接口
@ -20,13 +24,8 @@ const PaneContextMenu: React.FC<PaneContextMenuProps> = ({
// 处理粘贴节点 // 处理粘贴节点
const handlePasteNode = useCallback(() => { const handlePasteNode = useCallback(() => {
// 创建自定义粘贴事件 onPasteNode && onPasteNode(position);
const pasteEvent = new ClipboardEvent('paste', { }, [onPasteNode, position]);
clipboardData: new DataTransfer()
});
pasteEvent.clipboardData?.setData('text', 'paste-node');
document.dispatchEvent(pasteEvent);
}, []);
// 检查是否有复制的节点数据 // 检查是否有复制的节点数据
const hasCopiedNode = !!localStorage.getItem('copiedNode') || !!localStorage.getItem('copiedFlowData'); const hasCopiedNode = !!localStorage.getItem('copiedNode') || !!localStorage.getItem('copiedFlowData');
@ -41,6 +40,11 @@ const PaneContextMenu: React.FC<PaneContextMenuProps> = ({
}} }}
triggerProps={{ trigger: 'click' }} triggerProps={{ trigger: 'click' }}
> >
{onCopySelection && (
<Menu.Item key="copy-selection" onClick={onCopySelection}>
</Menu.Item>
)}
<SubMenu <SubMenu
key="add-node" key="add-node"
title="添加节点" title="添加节点"
@ -62,4 +66,4 @@ const PaneContextMenu: React.FC<PaneContextMenuProps> = ({
); );
}; };
export default PaneContextMenu; export default PaneContextMenu;

Loading…
Cancel
Save