@logicflow/extension 2.0.0-beta.4 → 2.0.0-beta.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +871 -20
- package/dist/index.css +63 -0
- package/dist/index.min.js +18 -2
- package/es/NodeResize/control/Control.js +3 -2
- package/es/NodeResize/control/Control.js.map +1 -1
- package/es/components/mini-map/index.js +2 -2
- package/es/components/mini-map/index.js.map +1 -1
- package/es/components/selection-select/index.js.map +1 -1
- package/es/index.css +63 -0
- package/es/index.d.ts +1 -0
- package/es/index.js +1 -0
- package/es/index.js.map +1 -1
- package/es/materials/group/GroupNode.d.ts +6 -6
- package/es/materials/group/GroupNode.js +7 -6
- package/es/materials/group/GroupNode.js.map +1 -1
- package/es/materials/group/index.js +20 -25
- package/es/materials/group/index.js.map +1 -1
- package/es/style/index.css +63 -0
- package/es/style/index.less +73 -0
- package/es/style/raw.d.ts +1 -1
- package/es/style/raw.js +1 -1
- package/es/style/raw.js.map +1 -1
- package/es/tools/flow-path/index.js +0 -1
- package/es/tools/flow-path/index.js.map +1 -1
- package/es/tools/label/Label.d.ts +30 -0
- package/es/tools/label/Label.js +241 -0
- package/es/tools/label/Label.js.map +1 -0
- package/es/tools/label/LabelModel.d.ts +26 -0
- package/es/tools/label/LabelModel.js +86 -0
- package/es/tools/label/LabelModel.js.map +1 -0
- package/es/tools/label/LabelOverlay.d.ts +28 -0
- package/es/tools/label/LabelOverlay.js +161 -0
- package/es/tools/label/LabelOverlay.js.map +1 -0
- package/es/tools/label/algorithm.d.ts +16 -0
- package/es/tools/label/algorithm.js +27 -0
- package/es/tools/label/algorithm.js.map +1 -0
- package/es/tools/label/index.d.ts +59 -0
- package/es/tools/label/index.js +292 -0
- package/es/tools/label/index.js.map +1 -0
- package/es/tools/label/mediumEditor.d.ts +16 -0
- package/es/tools/label/mediumEditor.js +91 -0
- package/es/tools/label/mediumEditor.js.map +1 -0
- package/es/tools/label/utils.d.ts +64 -0
- package/es/tools/label/utils.js +336 -0
- package/es/tools/label/utils.js.map +1 -0
- package/es/tools/snapshot/index.d.ts +22 -16
- package/es/tools/snapshot/index.js +84 -51
- package/es/tools/snapshot/index.js.map +1 -1
- package/lib/NodeResize/control/Control.js +3 -2
- package/lib/NodeResize/control/Control.js.map +1 -1
- package/lib/components/mini-map/index.js +2 -2
- package/lib/components/mini-map/index.js.map +1 -1
- package/lib/components/selection-select/index.js.map +1 -1
- package/lib/index.css +63 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/materials/group/GroupNode.d.ts +6 -6
- package/lib/materials/group/GroupNode.js +7 -6
- package/lib/materials/group/GroupNode.js.map +1 -1
- package/lib/materials/group/index.js +19 -24
- package/lib/materials/group/index.js.map +1 -1
- package/lib/style/index.css +63 -0
- package/lib/style/index.less +73 -0
- package/lib/style/raw.d.ts +1 -1
- package/lib/style/raw.js +1 -1
- package/lib/style/raw.js.map +1 -1
- package/lib/tools/flow-path/index.js +0 -1
- package/lib/tools/flow-path/index.js.map +1 -1
- package/lib/tools/label/Label.d.ts +30 -0
- package/lib/tools/label/Label.js +247 -0
- package/lib/tools/label/Label.js.map +1 -0
- package/lib/tools/label/LabelModel.d.ts +26 -0
- package/lib/tools/label/LabelModel.js +89 -0
- package/lib/tools/label/LabelModel.js.map +1 -0
- package/lib/tools/label/LabelOverlay.d.ts +28 -0
- package/lib/tools/label/LabelOverlay.js +167 -0
- package/lib/tools/label/LabelOverlay.js.map +1 -0
- package/lib/tools/label/algorithm.d.ts +16 -0
- package/lib/tools/label/algorithm.js +32 -0
- package/lib/tools/label/algorithm.js.map +1 -0
- package/lib/tools/label/index.d.ts +59 -0
- package/lib/tools/label/index.js +298 -0
- package/lib/tools/label/index.js.map +1 -0
- package/lib/tools/label/mediumEditor.d.ts +16 -0
- package/lib/tools/label/mediumEditor.js +97 -0
- package/lib/tools/label/mediumEditor.js.map +1 -0
- package/lib/tools/label/utils.d.ts +64 -0
- package/lib/tools/label/utils.js +349 -0
- package/lib/tools/label/utils.js.map +1 -0
- package/lib/tools/snapshot/index.d.ts +22 -16
- package/lib/tools/snapshot/index.js +84 -51
- package/lib/tools/snapshot/index.js.map +1 -1
- package/package.json +9 -7
- package/rollup.config.js +1 -1
- package/src/NodeResize/control/Control.tsx +3 -2
- package/src/components/mini-map/index.ts +2 -2
- package/src/components/selection-select/index.ts +5 -1
- package/src/index.ts +1 -0
- package/src/materials/group/GroupNode.ts +11 -8
- package/src/materials/group/index.ts +33 -38
- package/src/style/index.less +73 -0
- package/src/style/raw.ts +64 -1
- package/src/tools/flow-path/index.ts +0 -1
- package/src/tools/label/Label.tsx +297 -0
- package/src/tools/label/LabelModel.ts +82 -0
- package/src/tools/label/LabelOverlay.tsx +159 -0
- package/src/tools/label/algorithm.ts +42 -0
- package/src/tools/label/index.ts +401 -0
- package/src/tools/label/mediumEditor.ts +93 -0
- package/src/tools/label/utils.ts +395 -0
- package/src/tools/snapshot/README.md +27 -16
- package/src/tools/snapshot/index.ts +59 -38
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import LogicFlow, {
|
|
2
|
+
BaseEdgeModel,
|
|
3
|
+
BaseNodeModel,
|
|
4
|
+
Component,
|
|
5
|
+
createRef,
|
|
6
|
+
ElementState,
|
|
7
|
+
GraphModel,
|
|
8
|
+
IDragParams,
|
|
9
|
+
observer,
|
|
10
|
+
StepDrag,
|
|
11
|
+
} from '@logicflow/core'
|
|
12
|
+
import classNames from 'classnames'
|
|
13
|
+
import LabelModel from './LabelModel'
|
|
14
|
+
import { findIndex } from 'lodash-es'
|
|
15
|
+
|
|
16
|
+
import LabelConfig = LogicFlow.LabelConfig
|
|
17
|
+
|
|
18
|
+
export interface ILabelProps {
|
|
19
|
+
label: LabelModel
|
|
20
|
+
element: BaseNodeModel | BaseEdgeModel
|
|
21
|
+
graphModel: GraphModel
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface ILabelState {
|
|
25
|
+
isEditing: boolean
|
|
26
|
+
isHovered: boolean
|
|
27
|
+
isDragging: boolean
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@observer
|
|
31
|
+
export class Label extends Component<ILabelProps, ILabelState> {
|
|
32
|
+
textRef = createRef<HTMLDivElement>()
|
|
33
|
+
stepDrag: StepDrag
|
|
34
|
+
|
|
35
|
+
constructor(props: ILabelProps) {
|
|
36
|
+
super(props)
|
|
37
|
+
const {
|
|
38
|
+
label,
|
|
39
|
+
graphModel: { gridSize, eventCenter },
|
|
40
|
+
} = props
|
|
41
|
+
|
|
42
|
+
this.stepDrag = new StepDrag({
|
|
43
|
+
onDragging: this.handleDragging,
|
|
44
|
+
onDragEnd: this.handleDragEnd,
|
|
45
|
+
step: gridSize,
|
|
46
|
+
eventType: 'LABEL',
|
|
47
|
+
model: label,
|
|
48
|
+
eventCenter,
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
this.state = {
|
|
52
|
+
isEditing: false,
|
|
53
|
+
isHovered: false,
|
|
54
|
+
isDragging: false,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
setHoverOn = () => {
|
|
59
|
+
const { element } = this.props
|
|
60
|
+
if (element.isDragging || this.state.isHovered) return // 当节点或边在拖拽中时,不触发 hover 态
|
|
61
|
+
|
|
62
|
+
this.setState({ isHovered: true })
|
|
63
|
+
element.setHovered(true)
|
|
64
|
+
}
|
|
65
|
+
setHoverOff = () => {
|
|
66
|
+
const { element } = this.props
|
|
67
|
+
if (!this.state.isHovered) return
|
|
68
|
+
|
|
69
|
+
this.setState({ isHovered: false })
|
|
70
|
+
element.setHovered(false)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
handleMouseDown = (e: MouseEvent) => {
|
|
74
|
+
const { label, graphModel } = this.props
|
|
75
|
+
const {
|
|
76
|
+
editConfigModel: { nodeTextDraggable },
|
|
77
|
+
} = graphModel
|
|
78
|
+
|
|
79
|
+
// 当 label 允许拖拽 且不处于拖拽状态时, StepDrag 开启拖拽
|
|
80
|
+
if ((label.draggable ?? nodeTextDraggable) && !this.state.isDragging) {
|
|
81
|
+
this.setState({ isDragging: true })
|
|
82
|
+
this.stepDrag.handleMouseDown(e)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
handleDragging = ({ deltaX, deltaY }: IDragParams) => {
|
|
86
|
+
const { label, element, graphModel } = this.props
|
|
87
|
+
|
|
88
|
+
// DONE: 添加缩放时拖拽的逻辑,对 deltaX 和 deltaY 进行按比例缩放
|
|
89
|
+
const { transformModel } = graphModel
|
|
90
|
+
const [curDeltaX, curDeltaY] = transformModel.fixDeltaXY(deltaX, deltaY)
|
|
91
|
+
|
|
92
|
+
// DONE:更新 label 位置,触发 LABEL:DRAG 事件,并抛出相关的数据
|
|
93
|
+
const {
|
|
94
|
+
properties: { _label },
|
|
95
|
+
} = element
|
|
96
|
+
const elementLabel = _label as LabelConfig[]
|
|
97
|
+
const idx = findIndex(elementLabel, (cur) => cur.id === label.id)
|
|
98
|
+
|
|
99
|
+
const target = elementLabel[idx]
|
|
100
|
+
elementLabel[idx] = {
|
|
101
|
+
...target,
|
|
102
|
+
x: target.x + curDeltaX,
|
|
103
|
+
y: target.y + curDeltaY,
|
|
104
|
+
}
|
|
105
|
+
const targetElem = graphModel.getElement(element.id)
|
|
106
|
+
targetElem?.setProperty('_label', elementLabel)
|
|
107
|
+
|
|
108
|
+
graphModel.eventCenter.emit('label:drag', {
|
|
109
|
+
data: label.getData(),
|
|
110
|
+
model: label,
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
handleDragEnd = () => {
|
|
114
|
+
this.setState({ isDragging: false })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
handleDbClick = (e: MouseEvent) => {
|
|
118
|
+
const { label, element, graphModel } = this.props
|
|
119
|
+
graphModel.eventCenter.emit('label:dblclick', {
|
|
120
|
+
data: label.getData(),
|
|
121
|
+
e,
|
|
122
|
+
model: element,
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
if (!label.editable) {
|
|
126
|
+
element.setSelected(true)
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
element.setSelected()
|
|
130
|
+
element.setElementState(ElementState.TEXT_EDIT)
|
|
131
|
+
|
|
132
|
+
this.setState({ isEditing: true })
|
|
133
|
+
|
|
134
|
+
// DONE: 触发当前 label 的 focus 事件,设置内容可编辑,且在文本最后添加光标
|
|
135
|
+
if (this.textRef.current) {
|
|
136
|
+
this.textRef.current.contentEditable = 'true'
|
|
137
|
+
this.textRef.current.focus()
|
|
138
|
+
|
|
139
|
+
const range = document.createRange()
|
|
140
|
+
const selection = window.getSelection()
|
|
141
|
+
range.selectNodeContents(this.textRef.current)
|
|
142
|
+
range.collapse(false)
|
|
143
|
+
selection?.removeAllRanges()
|
|
144
|
+
selection?.addRange(range)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
handleBlur = (e: FocusEvent) => {
|
|
148
|
+
const {
|
|
149
|
+
label,
|
|
150
|
+
element,
|
|
151
|
+
graphModel: { eventCenter },
|
|
152
|
+
} = this.props
|
|
153
|
+
|
|
154
|
+
// DONE: 触发 LABEL:BLUR 事件,并抛出相关的事件
|
|
155
|
+
eventCenter.emit('label:blur', {
|
|
156
|
+
e,
|
|
157
|
+
model: element,
|
|
158
|
+
data: label.getData(),
|
|
159
|
+
element: this.textRef.current,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
this.setState({
|
|
163
|
+
isDragging: false,
|
|
164
|
+
isHovered: false,
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 重新计算 Label 大小
|
|
169
|
+
reCalcLabelSize = () => {}
|
|
170
|
+
|
|
171
|
+
// TODO:如何处理 Label zIndex 的问题, Label 永远会比节点层级高
|
|
172
|
+
// 当 Label 被元素遮盖时,隐藏它
|
|
173
|
+
|
|
174
|
+
componentDidMount() {
|
|
175
|
+
const { label, element, graphModel } = this.props
|
|
176
|
+
|
|
177
|
+
// 在点击元素、边或者画布 时,结束 Label 的编辑态
|
|
178
|
+
graphModel.eventCenter.on('blank:click,node:click,edge:click', () => {
|
|
179
|
+
// 如果当前 label 处于编辑态,则结束编辑态
|
|
180
|
+
if (this.state.isEditing) {
|
|
181
|
+
this.setState({ isEditing: false })
|
|
182
|
+
|
|
183
|
+
const value = this.textRef.current?.innerText ?? ''
|
|
184
|
+
const content = this.textRef.current?.innerHTML ?? ''
|
|
185
|
+
|
|
186
|
+
const {
|
|
187
|
+
properties: { _label },
|
|
188
|
+
} = element
|
|
189
|
+
const elementLabel = _label as LabelConfig[]
|
|
190
|
+
const idx = findIndex(elementLabel, (cur) => cur.id === label.id)
|
|
191
|
+
|
|
192
|
+
const target = elementLabel[idx]
|
|
193
|
+
elementLabel[idx] = {
|
|
194
|
+
...target,
|
|
195
|
+
value,
|
|
196
|
+
content,
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const targetElem = graphModel.getElement(element.id)
|
|
200
|
+
targetElem?.setProperty('_label', elementLabel)
|
|
201
|
+
|
|
202
|
+
element.setElementState(ElementState.DEFAULT)
|
|
203
|
+
}
|
|
204
|
+
if (this.textRef.current) {
|
|
205
|
+
this.textRef.current.contentEditable = 'false'
|
|
206
|
+
}
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
// TODO: 节点拖拽结束后,更新 Label 的位置
|
|
210
|
+
// eventCenter.on('node:drag', () => {})
|
|
211
|
+
// eventCenter.on('node:drop', () => {})
|
|
212
|
+
// eventCenter.on('node:mousemove', () => {})
|
|
213
|
+
//
|
|
214
|
+
// eventCenter.on('node:properties-change,node:properties-delete', () => {})
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
componentDidUpdate() {
|
|
218
|
+
// snapshot: any, // previousState: Readonly<ILabelState>, // previousProps: Readonly<ILabelProps>,
|
|
219
|
+
console.log('Label componentDidUpdate')
|
|
220
|
+
// console.log('previousProps', previousProps)
|
|
221
|
+
// console.log('previousState', previousState)
|
|
222
|
+
// console.log('snapshot', snapshot)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
componentWillUnmount() {
|
|
226
|
+
const { graphModel } = this.props
|
|
227
|
+
graphModel.eventCenter.off('blank:click,node:click,edge:click')
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// TODO: 当某一个标签更新时,如何避免其它标签的更新
|
|
231
|
+
// shouldComponentUpdate or memo
|
|
232
|
+
|
|
233
|
+
render() {
|
|
234
|
+
const { label, element, graphModel } = this.props
|
|
235
|
+
const { isDragging, isHovered, isEditing } = this.state
|
|
236
|
+
const { transformModel } = graphModel
|
|
237
|
+
const { transform } = transformModel.getTransformStyle()
|
|
238
|
+
const {
|
|
239
|
+
id,
|
|
240
|
+
x,
|
|
241
|
+
y,
|
|
242
|
+
zIndex,
|
|
243
|
+
vertical,
|
|
244
|
+
style,
|
|
245
|
+
rotate,
|
|
246
|
+
content,
|
|
247
|
+
labelWidth,
|
|
248
|
+
textOverflowMode,
|
|
249
|
+
} = label
|
|
250
|
+
|
|
251
|
+
const maxLabelWidth: number =
|
|
252
|
+
labelWidth ?? (element.BaseType === 'node' ? element.width - 20 : 80)
|
|
253
|
+
const containerStyle = {
|
|
254
|
+
left: `${x - maxLabelWidth / 2}px`,
|
|
255
|
+
top: `${y - 10}px`,
|
|
256
|
+
width: `${maxLabelWidth}px`,
|
|
257
|
+
height: '20px',
|
|
258
|
+
zIndex: zIndex ?? 1,
|
|
259
|
+
transform: rotate
|
|
260
|
+
? `${transform} rotate(${rotate}deg)`
|
|
261
|
+
: `${transform} rotate(${vertical ? -0.25 : 0}turn)`,
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return (
|
|
265
|
+
<div
|
|
266
|
+
id={`element-container-${id}`}
|
|
267
|
+
className={classNames('lf-label-editor-container')}
|
|
268
|
+
style={containerStyle}
|
|
269
|
+
onMouseDown={this.handleMouseDown}
|
|
270
|
+
onDblClick={this.handleDbClick}
|
|
271
|
+
onBlur={this.handleBlur}
|
|
272
|
+
onMouseEnter={this.setHoverOn}
|
|
273
|
+
onMouseOver={this.setHoverOn}
|
|
274
|
+
onMouseLeave={this.setHoverOff}
|
|
275
|
+
>
|
|
276
|
+
<div
|
|
277
|
+
ref={this.textRef}
|
|
278
|
+
id={`editor-container-${id}`}
|
|
279
|
+
className={classNames('lf-label-editor', {
|
|
280
|
+
'lf-label-editor-dragging': isDragging,
|
|
281
|
+
'lf-label-editor-editing': isEditing,
|
|
282
|
+
'lf-label-editor-hover': !isEditing && isHovered,
|
|
283
|
+
[`lf-label-editor-${textOverflowMode}`]: !isEditing,
|
|
284
|
+
})}
|
|
285
|
+
style={{
|
|
286
|
+
maxWidth: `${maxLabelWidth}px`,
|
|
287
|
+
width: `${maxLabelWidth}px`,
|
|
288
|
+
...style,
|
|
289
|
+
}}
|
|
290
|
+
dangerouslySetInnerHTML={{ __html: content }}
|
|
291
|
+
></div>
|
|
292
|
+
</div>
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export default Label
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import LogicFlow, {
|
|
2
|
+
BaseEdgeModel,
|
|
3
|
+
BaseNodeModel,
|
|
4
|
+
GraphModel,
|
|
5
|
+
LogicFlowUtil,
|
|
6
|
+
h,
|
|
7
|
+
observable,
|
|
8
|
+
toJS,
|
|
9
|
+
} from '@logicflow/core'
|
|
10
|
+
import { assign } from 'lodash-es'
|
|
11
|
+
|
|
12
|
+
import LabelData = LogicFlow.LabelData
|
|
13
|
+
import LabelConfig = LogicFlow.LabelConfig
|
|
14
|
+
import GraphElement = LogicFlow.GraphElement
|
|
15
|
+
|
|
16
|
+
// export type ILabelConfig = {}
|
|
17
|
+
const { createUuid } = LogicFlowUtil
|
|
18
|
+
|
|
19
|
+
export class LabelModel {
|
|
20
|
+
id: string
|
|
21
|
+
type: string = 'label' // 目前写死,后续可以根据业务需求进行扩展
|
|
22
|
+
|
|
23
|
+
@observable x!: number
|
|
24
|
+
@observable y!: number
|
|
25
|
+
@observable content: string = ''
|
|
26
|
+
@observable value: string = ''
|
|
27
|
+
@observable rotate?: number
|
|
28
|
+
@observable style: h.JSX.CSSProperties = {}
|
|
29
|
+
|
|
30
|
+
@observable zIndex?: number
|
|
31
|
+
@observable vertical: boolean = false // 文字是否垂直显示
|
|
32
|
+
@observable editable: boolean = true // label 是否可编辑
|
|
33
|
+
@observable draggable: boolean = true // label 是否可拖拽
|
|
34
|
+
@observable labelWidth?: number
|
|
35
|
+
@observable textOverflowMode:
|
|
36
|
+
| 'ellipsis'
|
|
37
|
+
| 'wrap'
|
|
38
|
+
| 'clip'
|
|
39
|
+
| 'nowrap'
|
|
40
|
+
| 'default' = 'default' // Label 节点的文本溢出模式
|
|
41
|
+
|
|
42
|
+
// TODO: 后续看 label 是否可以独立存在,不依赖节点 or 边
|
|
43
|
+
element: BaseNodeModel | BaseEdgeModel // 当前节点关联的元素 Model
|
|
44
|
+
graphModel: GraphModel
|
|
45
|
+
|
|
46
|
+
constructor(
|
|
47
|
+
config: LabelConfig,
|
|
48
|
+
element: GraphElement,
|
|
49
|
+
graphModel: GraphModel,
|
|
50
|
+
) {
|
|
51
|
+
this.element = element
|
|
52
|
+
this.graphModel = graphModel
|
|
53
|
+
this.id = config.id ?? createUuid()
|
|
54
|
+
|
|
55
|
+
this.initLabelData(config)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
initLabelData(config: LabelConfig): void {
|
|
59
|
+
assign(this, config)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getData(): LabelData {
|
|
63
|
+
return {
|
|
64
|
+
id: this.id,
|
|
65
|
+
x: this.x,
|
|
66
|
+
y: this.y,
|
|
67
|
+
content: this.content,
|
|
68
|
+
value: this.value,
|
|
69
|
+
rotate: this.rotate,
|
|
70
|
+
style: toJS(this.style),
|
|
71
|
+
|
|
72
|
+
draggable: this.draggable,
|
|
73
|
+
editable: this.editable,
|
|
74
|
+
labelWidth: this.labelWidth,
|
|
75
|
+
textOverflowMode: this.textOverflowMode,
|
|
76
|
+
|
|
77
|
+
vertical: this.vertical,
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export default LabelModel
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import LogicFlow, { Component, GraphModel, h, observer } from '@logicflow/core'
|
|
2
|
+
import type { IToolProps } from '@logicflow/core'
|
|
3
|
+
import { forEach, merge } from 'lodash-es'
|
|
4
|
+
import LabelPlugin from '.'
|
|
5
|
+
import Label from './Label'
|
|
6
|
+
import LabelModel from './LabelModel'
|
|
7
|
+
import { MediumEditor, defaultOptions, ColorPickerButton } from './mediumEditor'
|
|
8
|
+
|
|
9
|
+
import LabelConfig = LogicFlow.LabelConfig
|
|
10
|
+
|
|
11
|
+
export type LabelConfigType = string | LabelConfig | LabelConfig[]
|
|
12
|
+
|
|
13
|
+
export interface ILabelOverlayState {
|
|
14
|
+
tick: number
|
|
15
|
+
}
|
|
16
|
+
// const { createUuid } = LogicFlowUtil
|
|
17
|
+
|
|
18
|
+
// DONE: 解决问题,如果 LabelOverlay 设置为 Observer,拖拽 Label 时会导致 LabelOverlay 组件重新渲染,不知道为何
|
|
19
|
+
// 目前解决了。流程是 拖动 -> 更新 label 的数据信息到 element model -> 重新渲染 LabelOverlay
|
|
20
|
+
// 这种目前存在的问题,会全量重新渲染,是否会影响性能 ❓❓❓
|
|
21
|
+
// 另一种解决方案是,在此组件中监听一些事件,当这些事件触发时,再重新渲染 LabelOverlay
|
|
22
|
+
// 讨论一下
|
|
23
|
+
@observer
|
|
24
|
+
export class LabelOverlay extends Component<IToolProps, ILabelOverlayState> {
|
|
25
|
+
static toolName = 'label-edit-tool'
|
|
26
|
+
|
|
27
|
+
lf: LogicFlow
|
|
28
|
+
editor?: MediumEditor
|
|
29
|
+
graphModel: GraphModel
|
|
30
|
+
labelMap: Map<string, LabelModel> = new Map()
|
|
31
|
+
|
|
32
|
+
constructor(props: IToolProps) {
|
|
33
|
+
super(props)
|
|
34
|
+
|
|
35
|
+
const { lf, graphModel } = props
|
|
36
|
+
this.lf = lf
|
|
37
|
+
this.graphModel = graphModel
|
|
38
|
+
|
|
39
|
+
this.state = { tick: 0 }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
componentDidMount() {
|
|
43
|
+
const { graphModel } = this.props
|
|
44
|
+
// 1. 直接在此处初始化 RichTextEdit 工具
|
|
45
|
+
this.editor = new MediumEditor(
|
|
46
|
+
'.lf-label-editor',
|
|
47
|
+
merge(defaultOptions, {
|
|
48
|
+
autoLink: true,
|
|
49
|
+
extensions: {
|
|
50
|
+
colorPicker: new ColorPickerButton(),
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
// TODO: 2. 在此处监听一些事件,当 node、edge 数据变化时,主动触发重新渲染,保证同步更新
|
|
56
|
+
// TODO: 3. 整理哪些事件应该触发 Label 的更新
|
|
57
|
+
// 不需要了,将当前组件设置成 @observer 后,graphModel 更新会自动触发更新
|
|
58
|
+
graphModel.eventCenter.on(
|
|
59
|
+
'text:update,node:mousemove,node:resize,node:rotate,node:drag,label:drop,node:drop',
|
|
60
|
+
() => {
|
|
61
|
+
// this.setState({ tick: this.state.tick + 1 })
|
|
62
|
+
},
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
graphModel.eventCenter.on(
|
|
66
|
+
'node:properties-change,node:properties-delete',
|
|
67
|
+
() => {
|
|
68
|
+
this.setState({ tick: this.state.tick + 1 })
|
|
69
|
+
},
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
componentDidUpdate() {
|
|
74
|
+
// 在组件更新后,将新增的 label 元素添加到富文本编辑器中
|
|
75
|
+
if (this.editor && this.editor.elements.length > 0) {
|
|
76
|
+
this.editor.addElements('.lf-label-editor')
|
|
77
|
+
} else {
|
|
78
|
+
// FIX: 如果初始化的数据中没有 properties._label,需要重新初始化富文本编辑器
|
|
79
|
+
this.editor?.destroy()
|
|
80
|
+
this.editor = new MediumEditor(
|
|
81
|
+
'.lf-label-editor',
|
|
82
|
+
merge(defaultOptions, {
|
|
83
|
+
autoLink: true,
|
|
84
|
+
extensions: {
|
|
85
|
+
colorPicker: new ColorPickerButton(),
|
|
86
|
+
},
|
|
87
|
+
}),
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
componentWillUnmount() {
|
|
93
|
+
// 组件销毁时,同时将富文本编辑器注销
|
|
94
|
+
this.editor?.destroy()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* 获取当前画布上所有的 label
|
|
99
|
+
* 1. 第一步,先把当前所有的 text 转换为 label 进行展示
|
|
100
|
+
* 2. 数据同步,text 编辑后,同步更新 label,并重新渲染
|
|
101
|
+
*/
|
|
102
|
+
getLabels(): h.JSX.Element[] | null {
|
|
103
|
+
// 1. 通过 graphModel 获取当前画布上所有的 label 配置数据
|
|
104
|
+
const {
|
|
105
|
+
lf: { extension },
|
|
106
|
+
graphModel,
|
|
107
|
+
} = this.props
|
|
108
|
+
|
|
109
|
+
const elements = [...graphModel.nodes, ...graphModel.edges]
|
|
110
|
+
const curExtension = extension['Label'] as LabelPlugin
|
|
111
|
+
|
|
112
|
+
if (curExtension) {
|
|
113
|
+
const labels: h.JSX.Element[] = [] // 保存所有的 Label 元素
|
|
114
|
+
|
|
115
|
+
// TODO: 筛选出当前画布上,textMode 为 TextMode.LABEL 的元素(在支持元素级别的 textMode 时,需要做这个筛选)
|
|
116
|
+
// REMIND: 本期先只支持全局配置,所以判断全局的 textMode 即可
|
|
117
|
+
forEach(elements, (element) => {
|
|
118
|
+
const elementData = element.getData()
|
|
119
|
+
const labelConfig = elementData.properties?._label ?? []
|
|
120
|
+
|
|
121
|
+
forEach(labelConfig, (config) => {
|
|
122
|
+
const { labelMap } = this
|
|
123
|
+
// 查找 labelModel 实例,如果是实例化过的,直接复用;如果是新的,创建实例
|
|
124
|
+
// let label: LabelModel
|
|
125
|
+
// if (config.id && labelMap.has(config.id)) {
|
|
126
|
+
// label = labelMap.get(config.id)!
|
|
127
|
+
// } else {
|
|
128
|
+
// label = new LabelModel(config, element, graphModel)
|
|
129
|
+
// labelMap.set(label.id, label)
|
|
130
|
+
// }
|
|
131
|
+
const label = new LabelModel(config, element, graphModel)
|
|
132
|
+
labelMap.set(label.id, label)
|
|
133
|
+
|
|
134
|
+
labels.push(
|
|
135
|
+
<Label
|
|
136
|
+
key={label.id}
|
|
137
|
+
label={label}
|
|
138
|
+
element={element}
|
|
139
|
+
graphModel={graphModel}
|
|
140
|
+
/>,
|
|
141
|
+
)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
return labels
|
|
146
|
+
}
|
|
147
|
+
return null
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
render() {
|
|
151
|
+
return (
|
|
152
|
+
<foreignObject id="lf-label-overlay" class="lf-label-overlay">
|
|
153
|
+
{this.getLabels()}
|
|
154
|
+
</foreignObject>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export default LabelOverlay
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import LogicFlow from '@logicflow/core'
|
|
2
|
+
import Point = LogicFlow.Point
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 三次贝塞尔曲线公式
|
|
6
|
+
*/
|
|
7
|
+
export const getPointOnBezier = (
|
|
8
|
+
t: number,
|
|
9
|
+
P0: Point,
|
|
10
|
+
P1: Point,
|
|
11
|
+
P2: Point,
|
|
12
|
+
P3: Point,
|
|
13
|
+
) => {
|
|
14
|
+
const x =
|
|
15
|
+
(1 - t) ** 3 * P0.x +
|
|
16
|
+
3 * (1 - t) ** 2 * t * P1.x +
|
|
17
|
+
3 * (1 - t) * t ** 2 * P2.x +
|
|
18
|
+
t ** 3 * P3.x
|
|
19
|
+
const y =
|
|
20
|
+
(1 - t) ** 3 * P0.y +
|
|
21
|
+
3 * (1 - t) ** 2 * t * P1.y +
|
|
22
|
+
3 * (1 - t) * t ** 2 * P2.y +
|
|
23
|
+
t ** 3 * P3.y
|
|
24
|
+
|
|
25
|
+
return { x: x, y: y }
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 计算两个节点间的距离
|
|
30
|
+
* @param point1
|
|
31
|
+
* @param point2
|
|
32
|
+
* @param gridSize
|
|
33
|
+
*/
|
|
34
|
+
export const calcTwoPointsDistance = (
|
|
35
|
+
point1: Point,
|
|
36
|
+
point2: Point,
|
|
37
|
+
gridSize: number = 1,
|
|
38
|
+
): number => {
|
|
39
|
+
const dx = (point1.x - point2.x) / gridSize
|
|
40
|
+
const dy = (point1.y - point2.y) / gridSize
|
|
41
|
+
return Math.sqrt(dx ** 2 + dy ** 2)
|
|
42
|
+
}
|