Skip to content

Commit 4b01254

Browse files
authored
Merge pull request #4 from xinxin93/master
feat(core): v0.2.0 1. Add draggable and editable value in custom node & edge 2. Some bugfix
2 parents cdc19dd + f11d143 commit 4b01254

35 files changed

+383
-769
lines changed

packages/core/src/LogicFlow.tsx

Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ import {
3939
EdgeFilter,
4040
NodeConfig,
4141
NodeAttribute,
42-
ExtensionLike,
42+
Extension,
43+
ComponentRender,
4344
FocusOnArgs,
4445
RegisterElementFn,
4546
RegisterParam,
@@ -64,7 +65,7 @@ export default class LogicFlow {
6465
width: number;
6566
height: number;
6667
graphModel: GraphModel;
67-
history: History = new History();
68+
history: History;
6869
viewMap = new Map();
6970
tool: Tool;
7071
keyboard: Keyboard;
@@ -73,15 +74,16 @@ export default class LogicFlow {
7374
getSnapshot: () => void;
7475
eventCenter: EventEmitter;
7576
snaplineModel: SnaplineModel;
76-
static extensions: ExtensionLike[] = [];
77+
static extensions: Extension[] = [];
78+
components: ComponentRender[] = [];
7779
adapterIn: (data: unknown) => GraphConfigData;
7880
adapterOut: (data: GraphConfigData) => unknown;
81+
[propName: string]: any;
7982
constructor(options: Options.Definition) {
8083
const {
8184
container,
8285
width,
8386
height,
84-
tool = {},
8587
dndOptions,
8688
keyboard,
8789
isSilentMode,
@@ -97,8 +99,9 @@ export default class LogicFlow {
9799
if (!this.height) {
98100
this.height = container.getBoundingClientRect().height;
99101
}
100-
this.tool = new Tool(tool, this);
102+
this.tool = new Tool(this);
101103
this.eventCenter = new EventEmitter();
104+
this.history = new History(this.eventCenter);
102105
this.dnd = new Dnd({ options: dndOptions, lf: this });
103106
this.keyboard = new Keyboard({ lf: this, keyboard });
104107
// model 初始化
@@ -132,12 +135,16 @@ export default class LogicFlow {
132135
* 添加扩展, 待讨论,这里是不是静态方法好一些?
133136
* @param plugin 插件
134137
*/
135-
static use(extension: ExtensionLike) {
138+
static use(extension: Extension) {
136139
this.extensions.push(extension);
137140
}
138141
installPlugins() {
139142
LogicFlow.extensions.forEach((extension) => {
140-
extension.install(this);
143+
const { install, render: renderComponent } = extension;
144+
install.call(extension, this);
145+
if (renderComponent) {
146+
this.components.push(renderComponent.bind(extension));
147+
}
141148
});
142149
}
143150

@@ -260,6 +267,14 @@ export default class LogicFlow {
260267
const { transformMatrix } = this.graphModel;
261268
transformMatrix.translate(x, y);
262269
}
270+
/**
271+
* 还原图形为初始位置
272+
*/
273+
resetTranslate(): void {
274+
const { transformMatrix } = this.graphModel;
275+
const { TRANSLATE_X, TRANSLATE_Y } = transformMatrix;
276+
this.translate(-TRANSLATE_X, -TRANSLATE_Y);
277+
}
263278
/**
264279
* 将图形定位到画布中心
265280
* @param focusOnArgs 支持用户传入图形当前的坐标或id,可以通过type来区分是节点还是连线的id,也可以不传(兜底)
@@ -297,8 +312,13 @@ export default class LogicFlow {
297312
* @param {string} nodeId 节点Id
298313
*/
299314
deleteNode(nodeId: string): void {
300-
// todo: 1) before after钩子; 2) 删除后的回调
301-
this.graphModel.deleteNode(nodeId);
315+
const Model = this.graphModel.getNodeModel(nodeId);
316+
const data = Model.getData();
317+
const { guards } = this.options;
318+
const enabledDelete = guards && guards.beforeDelete ? guards.beforeDelete(data) : true;
319+
if (enabledDelete) {
320+
this.graphModel.deleteNode(nodeId);
321+
}
302322
}
303323
/**
304324
* 添加节点
@@ -307,6 +327,14 @@ export default class LogicFlow {
307327
addNode(nodeConfig: NodeConfig): BaseNodeModel {
308328
return this.graphModel.addNode(nodeConfig);
309329
}
330+
331+
setProperties(id: string, properties: Object): void {
332+
this.graphModel.getElement(id)?.setProperties(properties);
333+
}
334+
335+
getProperties(id: string): Object {
336+
return this.graphModel.getElement(id)?.getProperties();
337+
}
310338
/**
311339
* 显示节点文本编辑框
312340
* @param nodeId 节点id
@@ -319,7 +347,13 @@ export default class LogicFlow {
319347
* @param nodeId 节点Id
320348
*/
321349
cloneNode(nodeId: string): BaseNodeModel {
322-
return this.graphModel.cloneNode(nodeId);
350+
const Model = this.graphModel.getNodeModel(nodeId);
351+
const data = Model.getData();
352+
const { guards } = this.options;
353+
const enabledClone = guards && guards.beforeClone ? guards.beforeClone(data) : true;
354+
if (enabledClone) {
355+
return this.graphModel.cloneNode(nodeId);
356+
}
323357
}
324358
/**
325359
* 获取节点对象
@@ -351,14 +385,25 @@ export default class LogicFlow {
351385
createEdge(edgeConfig: EdgeConfig): void {
352386
this.graphModel.createEdge(edgeConfig);
353387
}
354-
/* 删除边 */
355-
removeEdge(config: EdgeFilter): void {
388+
/**
389+
* 删除边
390+
* @param {string} edgeId 边Id
391+
*/
392+
deleteEdge(edgeId: string): void {
393+
// 待讨论,这种钩子在这里覆盖不到removeEdge, 是否需要在graphModel中实现
394+
const { guards } = this.options;
395+
const edgeData = this.graphModel.edgesMap[edgeId].model.getData();
396+
const enabledDelete = guards && guards.beforeDelete
397+
? guards.beforeDelete(edgeData) : true;
398+
if (enabledDelete) {
399+
this.graphModel.removeEdgeById(edgeId);
400+
}
401+
}
402+
/* 删除指定类型的边 */
403+
removeEdge(config: { sourceNodeId: string, targetNodeId: string }): void {
356404
const {
357-
id, sourceNodeId, targetNodeId,
405+
sourceNodeId, targetNodeId,
358406
} = config;
359-
if (id) {
360-
this.graphModel.removeEdgeById(id);
361-
}
362407
if (sourceNodeId && targetNodeId) {
363408
this.graphModel.removeEdge(sourceNodeId, targetNodeId);
364409
} else if (sourceNodeId) {
@@ -501,6 +546,7 @@ export default class LogicFlow {
501546
options={this.options}
502547
dnd={this.dnd}
503548
snaplineModel={this.snaplineModel}
549+
components={this.components}
504550
/>
505551
</Provider>
506552
), this.container);

packages/core/src/constant/constant.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ export enum ModelType {
3030
export enum ElementType {
3131
NODE = 'node',
3232
EDGE = 'edge',
33+
GRAPH = 'graph',
3334
}
3435

3536
export enum EventType {
@@ -57,6 +58,7 @@ export enum EventType {
5758
BLANK_CLICK = 'blank:click',
5859
BLANK_CONTEXTMENU = 'blank:contextmenu',
5960
CONNECTION_NOT_ALLOWED = 'connection:not-allowed',
61+
HISTORY_CHANGE = 'history:change',
6062
}
6163

6264
export enum InnerEventType {

packages/core/src/event/event.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,8 @@ emit 的第二个参数。
3737
emit('namespace:eventName', {
3838
id, // 节点或连线的id 必传,blank非必需
3939
e, // 非必需,事件对象 e
40+
position: { // 非必须,表示触发事件时,鼠标所处画布层的位置和dom层的位置
41+
domOverlayPostion,
42+
canvasOverlayPostion,
43+
}
4044
})

packages/core/src/history/History.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { debounce, isEqual, last } from 'lodash-es';
22
import { deepObserve } from 'mobx-utils';
33
import EventEmitter from '../event/eventEmitter';
4-
import { InnerEventType } from '../constant/constant';
4+
import { EventType } from '../constant/constant';
55

6-
class History extends EventEmitter {
6+
class History {
77
undos = [];
88
redos = [];
99
callbacks = [];
@@ -13,6 +13,11 @@ class History extends EventEmitter {
1313
// 发生数据变化后,最多再等500ms,把距离上次的数据变更存储起来。
1414
// 所以waitTime值越小,History对数据变化越敏感,存的undos就越细。
1515
waitTime = 100;
16+
eventCenter: EventEmitter;
17+
18+
constructor(eventCenter) {
19+
this.eventCenter = eventCenter;
20+
}
1621

1722
add(data) {
1823
if (isEqual(last(this.undos), data)) return;
@@ -23,7 +28,15 @@ class History extends EventEmitter {
2328
if (!isEqual(this.curData, data)) {
2429
this.redos = [];
2530
}
26-
this.emit(InnerEventType.HISTORY_CHANGE, data);
31+
this.eventCenter.emit(EventType.HISTORY_CHANGE,
32+
{
33+
data: {
34+
undos: this.undos,
35+
redos: this.redos,
36+
undoAble: this.undos.length > 1,
37+
redoAble: this.redos.length > 0,
38+
},
39+
});
2740
if (this.undos.length > this.maxSize) {
2841
this.undos.shift();
2942
}

packages/core/src/keyboard/shortcut.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import LogicFlow from '../LogicFlow';
22
import GraphModel from '../model/GraphModel';
3+
import { ElementType } from '../constant/constant';
34

45
let selected = null;
56

@@ -11,16 +12,19 @@ export function initShortcut(lf: LogicFlow, graph: GraphModel) {
1112
keyboard.on(['cmd + c', 'ctrl + c'], () => {
1213
if (!keyboardOptions.enabled) return;
1314
if (graph.textEditElement) return;
14-
const node = graph.getSelected();
15-
selected = node;
15+
const element = graph.selectElement;
16+
if (element.BaseType === ElementType.NODE) {
17+
selected = element;
18+
}
1619
return false;
1720
});
1821
// 粘贴
1922
keyboard.on(['cmd + v', 'ctrl + v'], () => {
2023
if (!keyboardOptions.enabled) return;
2124
if (graph.textEditElement) return;
2225
if (selected) {
23-
selected = graph.cloneNode(selected.id);
26+
const cloned = lf.cloneNode(selected.id);
27+
selected = cloned || selected;
2428
}
2529
return false;
2630
});
@@ -42,13 +46,12 @@ export function initShortcut(lf: LogicFlow, graph: GraphModel) {
4246
keyboard.on(['backspace'], () => {
4347
if (!keyboardOptions.enabled) return;
4448
if (graph.textEditElement) return;
45-
const node = graph.getSelected();
46-
if (node) {
47-
lf.deleteNode(node.id);
49+
const element = graph.selectElement;
50+
if (element.BaseType === ElementType.NODE) {
51+
lf.deleteNode(element.id);
4852
}
49-
const edge = graph.getSelectedEdge();
50-
if (edge) {
51-
lf.removeEdge({ id: edge.id });
53+
if (element.BaseType === ElementType.EDGE) {
54+
lf.deleteEdge(element.id);
5255
}
5356
return false;
5457
});

packages/core/src/model/BaseModel.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,16 @@ interface IBaseModel {
1616
moveText(deltaX: number, deltaY: number): void;
1717
updateText(value: string): void;
1818
setSelected(flag: boolean): void;
19+
setZIndex(zindex?: number): void;
1920
/**
2021
* 设置Node|Edge等model的状态
2122
* @param state 状态
2223
*/
2324
setElementState(state: ElementState, additionStateData?: AdditionData): void;
24-
// TODO: 补充model的通用函数
25+
26+
getProperties(): Object;
27+
28+
setProperties(properties: Object): void;
2529
}
2630

2731
export {

0 commit comments

Comments
 (0)