Skip to content

Commit eac40c3

Browse files
author
昔梦
committed
fix:快捷键复制粘贴优化
1 parent 0770393 commit eac40c3

File tree

1 file changed

+62
-125
lines changed

1 file changed

+62
-125
lines changed

packages/x-flow/src/XFlow.tsx

Lines changed: 62 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
import '@xyflow/react/dist/style.css';
99
import { useEventListener, useMemoizedFn } from 'ahooks';
1010
import { produce, setAutoFreeze } from 'immer';
11-
import { debounce, isFunction } from 'lodash';
11+
import { isFunction, isString } from 'lodash';
1212
import type { FC } from 'react';
1313
import React, {
1414
memo,
@@ -87,7 +87,8 @@ const XFlow: FC<FlowProps> = memo(props => {
8787
);
8888
const { record } = useTemporalStore();
8989
const [activeNode, setActiveNode] = useState<any>(null);
90-
const { settingMap, globalConfig, readOnly, logPanel } = useContext(ConfigContext);
90+
const { settingMap, globalConfig, readOnly, logPanel } =
91+
useContext(ConfigContext);
9192
const [openPanel, setOpenPanel] = useState<boolean>(true);
9293
const [openLogPanel, setOpenLogPanel] = useState<boolean>(true);
9394
const {
@@ -101,8 +102,6 @@ const XFlow: FC<FlowProps> = memo(props => {
101102
const nodeEditorRef = useRef(null);
102103
const { copyNode, pasteNodeSimple } = useFlow();
103104
const { undo, redo } = useTemporalStore();
104-
const isNodeCopyingRef = useRef(false); // 是否正在进行节点复制
105-
const lastClickedNodeRef = useRef(false); // 最后一次点击是否是节点
106105

107106
useEffect(() => {
108107
zoomTo(0.8);
@@ -116,7 +115,7 @@ const XFlow: FC<FlowProps> = memo(props => {
116115
};
117116
}, []);
118117

119-
useEventListener('keydown', e => {
118+
const handleKeyDown = useMemoizedFn((e: KeyboardEvent) => {
120119
if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey))
121120
e.preventDefault();
122121
if ((e.key === 'z' || e.key === 'Z') && (e.ctrlKey || e.metaKey)) {
@@ -130,74 +129,49 @@ const XFlow: FC<FlowProps> = memo(props => {
130129
if ((e.key === 's' || e.key === 'S') && (e.ctrlKey || e.metaKey))
131130
e.preventDefault();
132131
if ((e.key === 'c' || e.key === 'C') && (e.ctrlKey || e.metaKey)) {
133-
const selectedNode = nodes?.find(node => node.selected);
134-
// 获取当前选中的文本(非节点的内容)
135-
const selectedText = window.getSelection()?.toString();
136-
// 如果最后点击的是节点 且 有节点被选中 则 复制节点
137-
if (selectedNode && lastClickedNodeRef.current) {
132+
const latestNodes = storeApi.getState().nodes;
133+
let isNodeCopyEvent = false;
134+
if (e.target instanceof HTMLElement) {
135+
const target = e.target as HTMLElement;
136+
if (
137+
(isString(target.tagName) &&
138+
target.tagName.toLowerCase() === 'body') ||
139+
(target.tagName.toLowerCase() === 'div' &&
140+
target.classList &&
141+
isFunction(target.classList.contains) &&
142+
(target.classList.contains('ant-drawer') ||
143+
target.classList.contains('react-flow__node') ||
144+
target.id === 'xflow-container'))
145+
) {
146+
isNodeCopyEvent = true;
147+
}
148+
}
149+
const selectedNode = latestNodes?.find(node => node.selected);
150+
const targetElement =
151+
e.target instanceof HTMLElement ? e.target : undefined;
152+
const targetTagName = targetElement?.tagName;
153+
const targetClassList = targetElement?.classList;
154+
155+
if (isNodeCopyEvent && selectedNode?.id) {
138156
// 复制节点
139-
isNodeCopyingRef.current = true; // 标记为节点复制
140-
copyNode(selectedNode.id);
141157
e.preventDefault();
142-
143-
// 清空系统剪贴板,确保粘贴时使用节点而非之前的文本
144-
try {
145-
navigator.clipboard.writeText('').catch(() => {});
146-
} catch (err) {}
147-
} else if (selectedText) {
148-
// 复制文本
149-
// 清除之前的节点复制状态
150-
const { copyNodes, copyTimeoutId } = storeApi.getState();
151-
if (copyNodes?.length > 0) {
152-
if (copyTimeoutId) {
153-
clearTimeout(copyTimeoutId);
154-
}
155-
storeApi.setState({
156-
copyNodes: [],
157-
copyTimeoutId: null,
158-
isAddingNode: false,
159-
});
160-
}
158+
copyNode(selectedNode.id);
161159
}
160+
161+
162162
} else if ((e.key === 'v' || e.key === 'V') && (e.ctrlKey || e.metaKey)) {
163163
const { copyNodes } = storeApi.getState();
164-
// 只有在有节点复制状态时才拦截粘贴操作
165164
if (copyNodes?.length > 0) {
166-
pasteNodeSimple();
167165
e.preventDefault();
166+
pasteNodeSimple();
168167
}
169-
} else if (copyNodes.length > 0) {
170-
// 只在有复制节点时才检查其他操作
171-
const { copyTimeoutId } = storeApi.getState();
172-
if (copyTimeoutId) {
173-
clearTimeout(copyTimeoutId);
174-
storeApi.setState({
175-
copyTimeoutId: null,
176-
isAddingNode: false,
177-
});
178-
}
179-
} else if (e.key === 'Escape'){
180-
setOpenPanel(false)
168+
} else if (e.key === 'Escape') {
169+
setOpenPanel(false);
170+
workflowContainerRef.current?.focus();
181171
}
182172
});
183-
184-
// 添加 copy 事件监听,获取实际复制的内容
185-
useEventListener('copy', (e: ClipboardEvent) => {
186-
if (!isNodeCopyingRef.current) {
187-
// 清除节点复制状态,因为用户复制了其他内容
188-
const { copyNodes, copyTimeoutId } = storeApi.getState();
189-
if (copyNodes?.length > 0) {
190-
if (copyTimeoutId) {
191-
clearTimeout(copyTimeoutId);
192-
}
193-
storeApi.setState({
194-
copyNodes: [],
195-
copyTimeoutId: null,
196-
isAddingNode: false,
197-
});
198-
}
199-
}
200-
isNodeCopyingRef.current = false;
173+
useEventListener('keydown', handleKeyDown, {
174+
target: workflowContainerRef,
201175
});
202176

203177
useEventListener(
@@ -221,28 +195,6 @@ const XFlow: FC<FlowProps> = memo(props => {
221195
}
222196
);
223197

224-
// 当点击非节点区域时重置标记
225-
useEventListener('mousedown', (e: MouseEvent) => {
226-
const target = e.target as HTMLElement;
227-
const isClickingNode =
228-
target.closest('.xflow-node-container') ||
229-
target.closest('.candidate-node');
230-
// 如果点击的不是节点,重置标记
231-
if (!isClickingNode) {
232-
lastClickedNodeRef.current = false;
233-
234-
// 清除复制状态
235-
const { copyTimeoutId, copyNodes } = storeApi.getState();
236-
if (copyTimeoutId && copyNodes?.length > 0) {
237-
clearTimeout(copyTimeoutId);
238-
storeApi.setState({
239-
copyTimeoutId: null,
240-
isAddingNode: false,
241-
});
242-
}
243-
}
244-
});
245-
246198
const { eventEmitter } = useEventEmitterContextContext();
247199
eventEmitter?.useSubscription((v: any) => {
248200
// 整理画布
@@ -278,28 +230,6 @@ const XFlow: FC<FlowProps> = memo(props => {
278230
setCandidateNode(newNode);
279231
};
280232

281-
// 插入节点
282-
// const handleInsertNode = () => {
283-
// const newNode = {
284-
// id: uuid(),
285-
// data: { label: 'new node' },
286-
// position: {
287-
// x: 0,
288-
// y: 0,
289-
// },
290-
// };
291-
// addNodes(newNode);
292-
// addEdges({
293-
// id: uuid(),
294-
// source: '2',
295-
// target: newNode.id,
296-
// });
297-
// const targetEdge = edges.find(edge => edge.source === '2');
298-
// updateEdge(targetEdge?.id as string, {
299-
// source: newNode.id,
300-
// });
301-
// };
302-
303233
// edge 移入/移出效果
304234
const getUpdateEdgeConfig = useMemoizedFn((edge: any, color: string) => {
305235
const newEdges = produce(edges, draft => {
@@ -323,7 +253,6 @@ const XFlow: FC<FlowProps> = memo(props => {
323253
const { _nodeType, _status, ...restData } = data || {};
324254
const nodeSetting = settingMap[_nodeType] || {};
325255
const showPanel = nodeSetting?.nodePanel?.showPanel ?? true;
326-
327256
return (
328257
<CustomNode
329258
{...rest}
@@ -333,9 +262,6 @@ const XFlow: FC<FlowProps> = memo(props => {
333262
layout={layout}
334263
status={_status}
335264
onClick={async e => {
336-
// 记录用户点击了节点
337-
lastClickedNodeRef.current = true;
338-
339265
if (nodeEditorRef?.current?.validateForm) {
340266
const result = await nodeEditorRef?.current?.validateForm();
341267
if (!result) {
@@ -400,7 +326,14 @@ const XFlow: FC<FlowProps> = memo(props => {
400326
const panelonClose = globalConfig?.nodePanel?.onClose;
401327

402328
return (
403-
<div id="xflow-container" ref={workflowContainerRef}>
329+
<div
330+
id="xflow-container"
331+
ref={workflowContainerRef}
332+
tabIndex={0}
333+
onMouseDown={() => {
334+
workflowContainerRef.current?.focus();
335+
}}
336+
>
404337
<ReactFlow
405338
panOnDrag={panOnDrag}
406339
nodeTypes={nodeTypes}
@@ -504,6 +437,7 @@ const XFlow: FC<FlowProps> = memo(props => {
504437
return;
505438
}
506439
setOpenPanel(false);
440+
workflowContainerRef.current?.focus();
507441

508442
// 如果日志面板关闭
509443
if (!isTruthy(activeNode?._status) || !openLogPanel) {
@@ -520,19 +454,22 @@ const XFlow: FC<FlowProps> = memo(props => {
520454
{NodeEditorWrap}
521455
</PanelContainer>
522456
)}
523-
{isTruthy(activeNode?._status) && openLogPanel && Boolean(logPanel?.enable ?? true) && (
524-
<PanelStatusLogContainer
525-
id={activeNode?.id}
526-
nodeType={activeNode?._nodeType}
527-
onClose={() => {
528-
setOpenLogPanel(false);
529-
!openPanel && setActiveNode(null);
530-
}}
531-
data={activeNode?.values}
532-
>
533-
{NodeLogWrap}
534-
</PanelStatusLogContainer>
535-
)}
457+
{isTruthy(activeNode?._status) &&
458+
openLogPanel &&
459+
Boolean(logPanel?.enable ?? true) && (
460+
<PanelStatusLogContainer
461+
id={activeNode?.id}
462+
nodeType={activeNode?._nodeType}
463+
onClose={() => {
464+
setOpenLogPanel(false);
465+
!openPanel && setActiveNode(null);
466+
workflowContainerRef.current?.focus();
467+
}}
468+
data={activeNode?.values}
469+
>
470+
{NodeLogWrap}
471+
</PanelStatusLogContainer>
472+
)}
536473
</ReactFlow>
537474
</div>
538475
);

0 commit comments

Comments
 (0)