Skip to content

Commit 76dd46c

Browse files
committed
feat: 增加冒泡
2 parents 5588035 + ab3600b commit 76dd46c

File tree

9 files changed

+92
-21
lines changed

9 files changed

+92
-21
lines changed

docs/xflow/api.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,14 @@ Handle 配置继承自 React Flow 的 Handle 配置,用于控制节点连接
202202
| key | 节点菜单项的key | `string` | |
203203
| nodeId | 节点ID | `string` | |
204204
| sourceHandle | 连接头ID | `string` | |
205+
206+
207+
## 画布快捷键
208+
209+
| 快捷键 | 功能描述 |
210+
|----------------------|------------------|
211+
| Ctrl/Cmd + C | 复制选中节点 |
212+
| Ctrl/Cmd + V | 粘贴节点 |
213+
| Delete/Backspace | 删除选中节点或边 |
214+
| Ctrl/Cmd + Z | 撤销 |
215+
| Ctrl/Cmd + Y | 重做 |

docs/xflow/demo/nodeWidget/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const HTTPNodeWidget = ({ data }) => {
7474

7575
// 问题分类器节点
7676
const ClassifierNodeWidget = ({ data }) => {
77-
const { categories, rules, defaultCategory } = data;
77+
const { categories = [], rules, defaultCategory } = data;
7878
return (
7979
<Card
8080
size="small"
@@ -85,7 +85,7 @@ const ClassifierNodeWidget = ({ data }) => {
8585
<Typography.Text type="secondary" style={{ fontSize: 12 }}>分类</Typography.Text>
8686
<div style={{ marginTop: 4 }}>
8787
<Space wrap>
88-
{categories.map(cat => (
88+
{(categories || [])?.map(cat => (
8989
<Tag
9090
key={cat}
9191
color={cat === defaultCategory ? 'purple' : 'default'}

docs/xflow/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ group:
243243
<div class="feature-name">画布操作</div>
244244
<div class="feature-content">
245245
<div class="feature-desc">
246-
支持画布缩放、撤销/重置、节点添加、鼠标模式切换、节点整理、全屏展示等功能。提供丰富的画布操作工具,提升用户体验。
246+
支持画布缩放、撤销/重置、节点添加、鼠标模式切换、节点整理、全屏展示、画布快捷键等功能。提供丰富的画布操作工具,提升用户体验。
247247
</div>
248248
<div class="feature-image node-show-img">
249249
<img src="https://img.alicdn.com/imgextra/i3/O1CN01Pcb1wd1RKe0hVF7nG_!!6000000002093-0-tps-3172-1234.jpg" alt="画布操作" />

packages/x-flow/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@xrenders/xflow",
3-
"version": "1.0.8-beta.4",
3+
"version": "1.0.8-beta.6",
44
"description": "一款功能强大、易用灵活的流程编辑器框架,帮助你轻松构建复杂的工作流和流程产品",
55
"keywords": [
66
"xflow"

packages/x-flow/src/XFlow.tsx

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { useEventEmitterContextContext } from './models/event-emitter';
2626

2727
import CustomNodeComponent from './components/CustomNode';
2828
import { useStore, useStoreApi } from './hooks/useStore';
29+
import { useFlow } from './hooks/useFlow';
2930

3031
import Operator from './operator';
3132
import FlowProps from './types';
@@ -89,6 +90,8 @@ const XFlow: FC<FlowProps> = memo(props => {
8990
const [openLogPanel, setOpenLogPanel] = useState<boolean>(true);
9091
const { onNodeClick, onEdgeClick, zoomOnScroll = true, panOnScroll = false, preventScrolling = true } = props;
9192
const nodeEditorRef = useRef(null);
93+
const { copyNode, pasteNodeSimple } = useFlow();
94+
const { undo, redo } = useTemporalStore();
9295

9396
useEffect(() => {
9497
zoomTo(0.8);
@@ -101,12 +104,27 @@ const XFlow: FC<FlowProps> = memo(props => {
101104
useEventListener('keydown', e => {
102105
if ((e.key === 'd' || e.key === 'D') && (e.ctrlKey || e.metaKey))
103106
e.preventDefault();
104-
if ((e.key === 'z' || e.key === 'Z') && (e.ctrlKey || e.metaKey))
107+
if ((e.key === 'z' || e.key === 'Z') && (e.ctrlKey || e.metaKey)) {
105108
e.preventDefault();
106-
if ((e.key === 'y' || e.key === 'Y') && (e.ctrlKey || e.metaKey))
109+
undo();
110+
}
111+
if ((e.key === 'y' || e.key === 'Y') && (e.ctrlKey || e.metaKey)) {
107112
e.preventDefault();
113+
redo();
114+
}
108115
if ((e.key === 's' || e.key === 'S') && (e.ctrlKey || e.metaKey))
109116
e.preventDefault();
117+
if ((e.key === 'c' || e.key === 'C') && (e.ctrlKey || e.metaKey)) {
118+
const selectedNode = nodes?.find(node => node.selected);
119+
if (selectedNode) {
120+
copyNode(selectedNode.id);
121+
e.preventDefault();
122+
}
123+
}
124+
else if ((e.key === 'v' || e.key === 'V') && (e.ctrlKey || e.metaKey)) {
125+
pasteNodeSimple();
126+
e.preventDefault();
127+
}
110128
});
111129

112130
useEventListener(
@@ -125,7 +143,8 @@ const XFlow: FC<FlowProps> = memo(props => {
125143
},
126144
{
127145
target: workflowContainerRef.current,
128-
enable: isAddingNode,
146+
// enable: isAddingNode,
147+
enable: true, // 复制粘贴的时候需要监听鼠标位置
129148
}
130149
);
131150

@@ -262,6 +281,9 @@ const XFlow: FC<FlowProps> = memo(props => {
262281
}
263282
setOpenLogPanel(true);
264283
}}
284+
onDelete={(delId)=>{
285+
setActiveNode(null);// 删除节点并关闭弹窗
286+
}}
265287
/>
266288
);
267289
},
@@ -366,7 +388,7 @@ const XFlow: FC<FlowProps> = memo(props => {
366388
}
367389
}}
368390
onNodesDelete={() => {
369-
// setActiveNode(null);
391+
setActiveNode(null);
370392
}}
371393
onNodeClick={(event, node) => {
372394
onNodeClick && onNodeClick(event, node);

packages/x-flow/src/components/CustomEdge/index.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,8 @@ export default memo((edge: any) => {
129129
{!hideEdgeDelBtn && !readOnly && (
130130
<div
131131
className="line-icon-box"
132-
onClick={() => {
132+
onClick={(ev: any) => {
133+
ev.stopPropagation();
133134
if (readOnly) {
134135
return;
135136
}

packages/x-flow/src/components/CustomNode/index.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import SourceHandle from './sourceHandle';
2020
import { useFlow } from '../../hooks/useFlow';
2121

2222
export default memo((props: any) => {
23-
const { id, type, data, layout, isConnectable, selected, onClick, status } =
23+
const { id, type, data, layout, isConnectable, selected, onClick, status, onDelete } =
2424
props;
2525
const {
2626
widgets,
@@ -136,6 +136,7 @@ export default memo((props: any) => {
136136

137137
const handleDeleteNode = useCallback(() => {
138138
deleteNode(id);
139+
onDelete(id)
139140
}, [deleteNode, id]);
140141

141142
const defaultAction = (e, sourceHandle) => {
@@ -160,6 +161,7 @@ export default memo((props: any) => {
160161
if (!e.key) {
161162
return;
162163
}
164+
163165
const sourceHandle = e.item.props?.sourcehandle;
164166
if (isFunction(onMenuItemClick)) {
165167
const data: Record<string, string> = {
@@ -368,4 +370,4 @@ export default memo((props: any) => {
368370
}
369371
</div>
370372
);
371-
});
373+
});

packages/x-flow/src/hooks/useFlow.ts

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export const useFlow = () => {
3333
screenToFlowPosition,
3434
flowToScreenPosition
3535
} = useReactFlow();
36-
36+
3737
const { record } = useTemporalStore();
3838

3939
const toObject = useMemoizedFn(() => {
@@ -123,21 +123,55 @@ export const useFlow = () => {
123123
}
124124
});
125125

126-
const deleteNode = useMemoizedFn((nodeId) => {
126+
const pasteNodeSimple = useMemoizedFn(() => {
127+
const mousePosition = storeApi.getState().mousePosition;
128+
if (storeApi.getState().copyNodes.length > 0) {
129+
const flowPos = screenToFlowPosition({
130+
x: mousePosition.elementX,
131+
y: mousePosition.elementY,
132+
});
133+
const copyNodes = storeApi.getState().copyNodes.map(node => ({
134+
...node,
135+
position: {
136+
x: flowPos.x,
137+
y: flowPos.y,
138+
},
139+
}));
140+
record(() => {
141+
storeApi.getState().addNodes(copyNodes, false);
142+
// 将清空剪贴板的操作也加入记录,使其可撤销
143+
storeApi.setState({
144+
copyNodes: [],
145+
});
146+
});
147+
} else {
148+
// message.warning('请先复制节点!');
149+
}
150+
});
151+
152+
const deleteNode = useMemoizedFn(nodeId => {
127153
record(() => {
128154
storeApi.setState({
129-
edges: storeApi.getState().edges.filter((edge) => edge.source !== nodeId && edge.target !== nodeId),
155+
edges: storeApi
156+
.getState()
157+
.edges.filter(
158+
edge => edge.source !== nodeId && edge.target !== nodeId
159+
),
130160
});
131-
})
161+
});
132162
record(() => {
133163
storeApi.setState({
134-
nodes: storeApi.getState().nodes.filter((node) => node.id !== nodeId),
164+
nodes: storeApi.getState().nodes.filter(node => node.id !== nodeId),
135165
});
136-
})
166+
});
137167
});
138168

139169
const runAutoLayout = useMemoizedFn(() => {
140-
const newNodes: any = autoLayoutNodes(storeApi.getState().nodes, storeApi.getState().edges, storeApi.getState().layout);
170+
const newNodes: any = autoLayoutNodes(
171+
storeApi.getState().nodes,
172+
storeApi.getState().edges,
173+
storeApi.getState().layout
174+
);
141175
setNodes(newNodes);
142176
});
143177

@@ -166,8 +200,9 @@ export const useFlow = () => {
166200
runAutoLayout,
167201
copyNode,
168202
pasteNode,
169-
deleteNode
203+
pasteNodeSimple,
204+
deleteNode,
170205
}),
171206
[instance]
172207
);
173-
}
208+
}

packages/x-flow/src/models/store.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ const createStore = (initProps?: Partial<FlowProps>) => {
125125
};
126126
},
127127
onSave(pastState, currentState) {
128-
console.log('onSave', pastState, currentState);
128+
// console.log('onSave', pastState, currentState);
129129
},
130130
}
131131
),

0 commit comments

Comments
 (0)