Skip to content

Commit 6a62355

Browse files
committed
feat: 新增 common 文件夹用于存放通用方法
1 parent 4be8d12 commit 6a62355

File tree

6 files changed

+642
-0
lines changed

6 files changed

+642
-0
lines changed

packages/core/src/common/drag.ts

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
import { noop } from 'lodash-es'
2+
import { Model } from '../model'
3+
import { EventType } from '../constant'
4+
import EventEmitter from '../event/eventEmitter'
5+
6+
// TODO:这种方式在同构项目中,会报错,该如何解决(是否要求用户控制在浏览器环境时才初始化)
7+
// const DOC: any = window?.document
8+
const LEFT_MOUSE_BUTTON_CODE = 0
9+
10+
export type IDragParams = {
11+
deltaX: number
12+
deltaY: number
13+
event: MouseEvent | null
14+
[key: string]: unknown
15+
}
16+
17+
export type ICreateDragParams = {
18+
onDragStart?: (params: Partial<IDragParams>) => void
19+
onDragging?: (param: IDragParams) => void
20+
onDragEnd?: (param: Partial<IDragParams>) => void
21+
step?: number
22+
isStopPropagation?: boolean
23+
}
24+
25+
export type IStepperDragProps = {
26+
eventType?: 'NODE' | 'BLANK' | 'SELECTION' | 'ADJUST_POINT' | ''
27+
eventCenter?: EventEmitter
28+
model?: Model.BaseModel
29+
data?: Record<string, unknown>
30+
[key: string]: unknown
31+
} & Partial<ICreateDragParams>
32+
/**
33+
* 支持拖拽时按步长进行移动
34+
* REMIND:在绘制的过程中因为放大缩小,移动的真实 step 是变化的
35+
*/
36+
export class StepperDrag {
37+
// 初始化
38+
onDragStart: (params: Partial<IDragParams>) => void
39+
onDragging: (params: IDragParams) => void
40+
onDragEnd: (params: Partial<IDragParams>) => void
41+
42+
step: number
43+
isStopPropagation: boolean
44+
eventType: 'NODE' | 'BLANK' | 'SELECTION' | 'ADJUST_POINT' | ''
45+
eventCenter?: EventEmitter
46+
model?: Model.BaseModel
47+
data?: Record<string, unknown>
48+
49+
// 运行时
50+
isDragging: boolean = false
51+
isStartDrag: boolean = false
52+
53+
startX: number = 0
54+
startY: number = 0
55+
totalDeltaX: number = 0
56+
totalDeltaY: number = 0
57+
58+
startTime?: number
59+
constructor({
60+
onDragStart = noop,
61+
onDragging = noop,
62+
onDragEnd = noop,
63+
step = 1,
64+
eventType = '',
65+
isStopPropagation = true,
66+
eventCenter,
67+
model,
68+
data,
69+
}: IStepperDragProps) {
70+
this.onDragStart = onDragStart
71+
this.onDragging = onDragging
72+
this.onDragEnd = onDragEnd
73+
this.step = step
74+
this.eventType = eventType
75+
this.isStopPropagation = isStopPropagation
76+
this.eventCenter = eventCenter
77+
this.model = model
78+
this.data = data
79+
}
80+
81+
setStep(step: number) {
82+
this.step = step
83+
}
84+
85+
handleMouseMove = (e: MouseEvent) => {
86+
if (this.isStopPropagation) e.stopPropagation()
87+
if (!this.isStartDrag) return
88+
89+
this.totalDeltaX += e.clientX - this.startX
90+
this.totalDeltaY += e.clientY - this.startY
91+
this.startX = e.clientX
92+
this.startY = e.clientY
93+
94+
if (
95+
this.step <= 1 ||
96+
Math.abs(this.totalDeltaX) > this.step ||
97+
Math.abs(this.totalDeltaY) > this.step
98+
) {
99+
const remainderX = this.totalDeltaX % this.step
100+
const remainderY = this.totalDeltaY % this.step
101+
102+
const deltaX = this.totalDeltaX - remainderX
103+
const deltaY = this.totalDeltaY - remainderY
104+
105+
this.totalDeltaX = remainderX
106+
this.totalDeltaY = remainderY
107+
108+
const elementData = this.model?.getData()
109+
// REMIND: 为了区分点击和拖动,在鼠标没有拖动时,不触发 dragstart。
110+
if (!this.isDragging) {
111+
if (this.eventType) {
112+
this.eventCenter?.emit(EventType[`${this.eventType}_DRAGSTART`], {
113+
e,
114+
data: this.data || elementData,
115+
})
116+
}
117+
this.onDragStart({ event: e })
118+
}
119+
120+
this.isDragging = true
121+
// REMIND: 为了让 dragstart 和 drag 不在同一个事件循环中,将 drag 事件放在下一个任务队列中。
122+
// TODO: 测试用例是否可覆盖???
123+
Promise.resolve().then(() => {
124+
this.onDragging({ deltaX, deltaY, event: e })
125+
if (this.eventType) {
126+
this.eventCenter?.emit(EventType[`${this.eventType}_MOUSEMOVE`], {
127+
e,
128+
data: this.data || elementData,
129+
})
130+
this.eventCenter?.emit(EventType[`${this.eventType}_DRAG`], {
131+
e,
132+
data: this.data || elementData,
133+
})
134+
}
135+
})
136+
}
137+
}
138+
139+
handleMouseUp = (e: MouseEvent) => {
140+
const DOC: any = window?.document
141+
142+
this.isStartDrag = false
143+
if (this.isStopPropagation) e.stopPropagation()
144+
145+
// fix: issue#568, 如果 onDragging 在下一个时间循环中触发,而 drop 在当前事件循环,会出现问题
146+
Promise.resolve().then(() => {
147+
DOC?.removeEventListener('mousemove', this.handleMouseMove, true)
148+
DOC?.removeEventListener('mouseup', this.handleMouseUp, true)
149+
150+
const elementData = this.model?.getData()
151+
if (this.eventType) {
152+
this.eventCenter?.emit(EventType[`${this.eventType}_MOUSEUP`], {
153+
e,
154+
data: this.data || elementData,
155+
})
156+
}
157+
158+
if (!this.isDragging) return
159+
this.isDragging = false
160+
this.onDragEnd({ event: e })
161+
if (this.eventType) {
162+
this.eventCenter?.emit(EventType[`${this.eventType}_DROP`], {
163+
e,
164+
data: this.data || elementData,
165+
})
166+
}
167+
})
168+
}
169+
170+
handleMouseDown = (e: MouseEvent) => {
171+
const DOC: any = window?.document
172+
173+
// issue: LogicFlow交流群-3群 8.10 号抛出的事件相关的问题,是否是这引起的???
174+
if (e.button !== LEFT_MOUSE_BUTTON_CODE) return
175+
if (this.isStopPropagation) e.stopPropagation()
176+
177+
this.isStartDrag = true
178+
this.startX = e.clientX
179+
this.startY = e.clientY
180+
181+
DOC?.addEventListener('mousemove', this.handleMouseMove, true)
182+
DOC?.addEventListener('mouseup', this.handleMouseUp, true)
183+
184+
const elementData = this.model?.getData()
185+
if (this.eventType) {
186+
this.eventCenter?.emit(EventType[`${this.eventType}_MOUSEDOWN`], {
187+
e,
188+
data: this.data || elementData,
189+
})
190+
}
191+
this.startTime = new Date().getTime()
192+
}
193+
194+
cancelDrag = () => {
195+
const DOC: any = window?.document
196+
197+
DOC?.removeEventListener('mousemove', this.handleMouseMove, true)
198+
DOC?.removeEventListener('mouseup', this.handleMouseUp, true)
199+
200+
this.onDragEnd({ event: null })
201+
this.isDragging = false
202+
}
203+
}
204+
205+
export default StepperDrag

packages/core/src/common/history.ts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { cloneDeep, debounce, isEqual, last } from 'lodash-es'
2+
import { deepObserve, IDisposer } from 'mobx-utils'
3+
import { LogicFlow } from '../LogicFlow'
4+
// import { EventType } from '../constant'
5+
import { GraphModel } from '../model'
6+
import EventEmitter from '../event/eventEmitter'
7+
8+
export type HistoryData = LogicFlow.GraphConfigData
9+
10+
export class History {
11+
undos: HistoryData[] = []
12+
redos: HistoryData[] = []
13+
stopWatch: IDisposer | null = null
14+
curData: HistoryData | null = null
15+
maxSize: number = 50
16+
// 发生数据变化后,最多再等 500ms,把距离上次的数据变更存储起来。
17+
// 所以 waitTime 值越小,History 对数据变化越敏感,存的 undos 数据就越细
18+
waitTime: number = 100
19+
eventCenter: EventEmitter
20+
21+
constructor(eventCenter: EventEmitter) {
22+
this.eventCenter = eventCenter
23+
}
24+
25+
add(data: HistoryData) {
26+
if (isEqual(last(this.undos), data)) return
27+
this.undos.push(data)
28+
29+
// 因为 undo 的时候会触发 add.
30+
// 所以需要区分这个 add 是 undo 触发的还是用户正常操作触发的
31+
// 如果是用户正常操作触发的,需要清空 redos
32+
if (!isEqual(this.curData, data)) {
33+
this.redos = []
34+
}
35+
// this.eventCenter.emit(EventType.HISTORY_CHANGE, {
36+
// data: {
37+
// undos: this.undos,
38+
// redos: this.redos,
39+
// undoAble: this.undos.length > 1,
40+
// redoAble: this.redos.length > 0,
41+
// },
42+
// })
43+
44+
if (this.undos.length > this.maxSize) {
45+
this.undos.shift()
46+
}
47+
}
48+
49+
undoAble() {
50+
return this.undos.length > 1
51+
}
52+
53+
/**
54+
* undo 方法触发:
55+
* graphModel 重新渲染 nodes 和 edges
56+
* graphModel 发生变化,触发 watch
57+
* watch 触发 add
58+
*/
59+
undo() {
60+
if (!this.undoAble()) return
61+
const preData = this.undos.pop()
62+
if (preData) {
63+
this.redos.push(preData)
64+
}
65+
const curData = this.undos.pop()
66+
if (curData) {
67+
this.curData = cloneDeep(curData)
68+
}
69+
return curData
70+
}
71+
72+
redoAble() {
73+
return this.redos.length > 0
74+
}
75+
76+
redo() {
77+
if (!this.redoAble()) return
78+
const curData = this.redos.pop()
79+
if (curData) {
80+
this.curData = cloneDeep(curData)
81+
}
82+
return curData
83+
}
84+
85+
watch(model: GraphModel) {
86+
this.stopWatch && this.stopWatch()
87+
88+
// 把当前 watch 的 model 转换一下数据存起来,无需清空 redos
89+
const historyData = model.modelToHistoryData()
90+
if (historyData) {
91+
this.undos.push(historyData)
92+
}
93+
94+
this.stopWatch = deepObserve(
95+
model,
96+
debounce(() => {
97+
// 数据变更后,把最新的当前 model 数据存起来,并清空 redos
98+
// 因为这个回调函数的触发,一般是用户交互而引起的,所以按照正常逻辑需要清空 redos
99+
const data = model.modelToHistoryData()
100+
if (data) {
101+
this.add(data)
102+
}
103+
}, this.waitTime),
104+
)
105+
}
106+
}
107+
108+
export default History

packages/core/src/common/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export * from './drag'
2+
export * from './history'
3+
export * from './keyboard'
4+
5+
export * from './matrix'
6+
export * from './vector'

0 commit comments

Comments
 (0)