@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.
Files changed (113) hide show
  1. package/.turbo/turbo-build.log +871 -20
  2. package/dist/index.css +63 -0
  3. package/dist/index.min.js +18 -2
  4. package/es/NodeResize/control/Control.js +3 -2
  5. package/es/NodeResize/control/Control.js.map +1 -1
  6. package/es/components/mini-map/index.js +2 -2
  7. package/es/components/mini-map/index.js.map +1 -1
  8. package/es/components/selection-select/index.js.map +1 -1
  9. package/es/index.css +63 -0
  10. package/es/index.d.ts +1 -0
  11. package/es/index.js +1 -0
  12. package/es/index.js.map +1 -1
  13. package/es/materials/group/GroupNode.d.ts +6 -6
  14. package/es/materials/group/GroupNode.js +7 -6
  15. package/es/materials/group/GroupNode.js.map +1 -1
  16. package/es/materials/group/index.js +20 -25
  17. package/es/materials/group/index.js.map +1 -1
  18. package/es/style/index.css +63 -0
  19. package/es/style/index.less +73 -0
  20. package/es/style/raw.d.ts +1 -1
  21. package/es/style/raw.js +1 -1
  22. package/es/style/raw.js.map +1 -1
  23. package/es/tools/flow-path/index.js +0 -1
  24. package/es/tools/flow-path/index.js.map +1 -1
  25. package/es/tools/label/Label.d.ts +30 -0
  26. package/es/tools/label/Label.js +241 -0
  27. package/es/tools/label/Label.js.map +1 -0
  28. package/es/tools/label/LabelModel.d.ts +26 -0
  29. package/es/tools/label/LabelModel.js +86 -0
  30. package/es/tools/label/LabelModel.js.map +1 -0
  31. package/es/tools/label/LabelOverlay.d.ts +28 -0
  32. package/es/tools/label/LabelOverlay.js +161 -0
  33. package/es/tools/label/LabelOverlay.js.map +1 -0
  34. package/es/tools/label/algorithm.d.ts +16 -0
  35. package/es/tools/label/algorithm.js +27 -0
  36. package/es/tools/label/algorithm.js.map +1 -0
  37. package/es/tools/label/index.d.ts +59 -0
  38. package/es/tools/label/index.js +292 -0
  39. package/es/tools/label/index.js.map +1 -0
  40. package/es/tools/label/mediumEditor.d.ts +16 -0
  41. package/es/tools/label/mediumEditor.js +91 -0
  42. package/es/tools/label/mediumEditor.js.map +1 -0
  43. package/es/tools/label/utils.d.ts +64 -0
  44. package/es/tools/label/utils.js +336 -0
  45. package/es/tools/label/utils.js.map +1 -0
  46. package/es/tools/snapshot/index.d.ts +22 -16
  47. package/es/tools/snapshot/index.js +84 -51
  48. package/es/tools/snapshot/index.js.map +1 -1
  49. package/lib/NodeResize/control/Control.js +3 -2
  50. package/lib/NodeResize/control/Control.js.map +1 -1
  51. package/lib/components/mini-map/index.js +2 -2
  52. package/lib/components/mini-map/index.js.map +1 -1
  53. package/lib/components/selection-select/index.js.map +1 -1
  54. package/lib/index.css +63 -0
  55. package/lib/index.d.ts +1 -0
  56. package/lib/index.js +1 -0
  57. package/lib/index.js.map +1 -1
  58. package/lib/materials/group/GroupNode.d.ts +6 -6
  59. package/lib/materials/group/GroupNode.js +7 -6
  60. package/lib/materials/group/GroupNode.js.map +1 -1
  61. package/lib/materials/group/index.js +19 -24
  62. package/lib/materials/group/index.js.map +1 -1
  63. package/lib/style/index.css +63 -0
  64. package/lib/style/index.less +73 -0
  65. package/lib/style/raw.d.ts +1 -1
  66. package/lib/style/raw.js +1 -1
  67. package/lib/style/raw.js.map +1 -1
  68. package/lib/tools/flow-path/index.js +0 -1
  69. package/lib/tools/flow-path/index.js.map +1 -1
  70. package/lib/tools/label/Label.d.ts +30 -0
  71. package/lib/tools/label/Label.js +247 -0
  72. package/lib/tools/label/Label.js.map +1 -0
  73. package/lib/tools/label/LabelModel.d.ts +26 -0
  74. package/lib/tools/label/LabelModel.js +89 -0
  75. package/lib/tools/label/LabelModel.js.map +1 -0
  76. package/lib/tools/label/LabelOverlay.d.ts +28 -0
  77. package/lib/tools/label/LabelOverlay.js +167 -0
  78. package/lib/tools/label/LabelOverlay.js.map +1 -0
  79. package/lib/tools/label/algorithm.d.ts +16 -0
  80. package/lib/tools/label/algorithm.js +32 -0
  81. package/lib/tools/label/algorithm.js.map +1 -0
  82. package/lib/tools/label/index.d.ts +59 -0
  83. package/lib/tools/label/index.js +298 -0
  84. package/lib/tools/label/index.js.map +1 -0
  85. package/lib/tools/label/mediumEditor.d.ts +16 -0
  86. package/lib/tools/label/mediumEditor.js +97 -0
  87. package/lib/tools/label/mediumEditor.js.map +1 -0
  88. package/lib/tools/label/utils.d.ts +64 -0
  89. package/lib/tools/label/utils.js +349 -0
  90. package/lib/tools/label/utils.js.map +1 -0
  91. package/lib/tools/snapshot/index.d.ts +22 -16
  92. package/lib/tools/snapshot/index.js +84 -51
  93. package/lib/tools/snapshot/index.js.map +1 -1
  94. package/package.json +9 -7
  95. package/rollup.config.js +1 -1
  96. package/src/NodeResize/control/Control.tsx +3 -2
  97. package/src/components/mini-map/index.ts +2 -2
  98. package/src/components/selection-select/index.ts +5 -1
  99. package/src/index.ts +1 -0
  100. package/src/materials/group/GroupNode.ts +11 -8
  101. package/src/materials/group/index.ts +33 -38
  102. package/src/style/index.less +73 -0
  103. package/src/style/raw.ts +64 -1
  104. package/src/tools/flow-path/index.ts +0 -1
  105. package/src/tools/label/Label.tsx +297 -0
  106. package/src/tools/label/LabelModel.ts +82 -0
  107. package/src/tools/label/LabelOverlay.tsx +159 -0
  108. package/src/tools/label/algorithm.ts +42 -0
  109. package/src/tools/label/index.ts +401 -0
  110. package/src/tools/label/mediumEditor.ts +93 -0
  111. package/src/tools/label/utils.ts +395 -0
  112. package/src/tools/snapshot/README.md +27 -16
  113. package/src/tools/snapshot/index.ts +59 -38
@@ -0,0 +1,395 @@
1
+ import LogicFlow, {
2
+ BaseEdgeModel,
3
+ BaseNodeModel,
4
+ getNodeOutline,
5
+ getEdgeOutline,
6
+ Model,
7
+ ModelType,
8
+ BezierEdgeModel,
9
+ isInSegment,
10
+ getBezierPoints,
11
+ points2PointsList,
12
+ getBBoxOfPoints,
13
+ } from '@logicflow/core'
14
+ import { head, isEmpty, last, min } from 'lodash-es'
15
+ import { calcTwoPointsDistance, getPointOnBezier } from './algorithm'
16
+
17
+ import Point = LogicFlow.Point
18
+ import LabelConfig = LogicFlow.LabelConfig
19
+ import OutlineInfo = Model.OutlineInfo
20
+
21
+ export type BBoxInfo = {
22
+ x: number
23
+ y: number
24
+ width: number
25
+ height: number
26
+ }
27
+
28
+ // 工具函数:计算「缩放」后 某坐标点 相对中心位置比例不变的 新坐标点
29
+ // 前提条件: 当缩放一个矩形时,如果你希望矩形中的某个点的位置相对于矩形保持不变
30
+ //
31
+ // 1. 原始矩形的左上角坐标为 (x1, y1),宽度为 w1,高度为 h1。
32
+ // 2. 缩放后的矩形的左上角坐标为 (x2, y2),宽度为 w2,高度为 h2。
33
+ // 3. 矩形中的某个点在原始矩形中的坐标为 (px1, py1)。
34
+ //
35
+ // 目标
36
+ // 计算该点在缩放后矩形中的新坐标 (px2, py2)。
37
+ //
38
+ // 步骤
39
+ // 1. 计算相对位置:首先计算点 (px1, py1) 在原始矩形中的相对位置。
40
+ // relativeX = (px1 - x1) / w1
41
+ // relativeY = (py1 - y1) / h1
42
+ //
43
+ // 2. 计算新坐标:然后,使用相对位置计算该点在缩放后矩形中的新坐标。
44
+ // px2 = x2 + relativeX * w2
45
+ // py2 = y2 + relativeY * h2
46
+ export function calcPointAfterResize(
47
+ origin: BBoxInfo,
48
+ scaled: BBoxInfo,
49
+ point: Point,
50
+ ): Point {
51
+ const { x: x1, y: y1, width: w1, height: h1 } = origin
52
+ const { x: x2, y: y2, width: w2, height: h2 } = scaled
53
+ const { x: px1, y: py1 } = point
54
+
55
+ // 计算点在原始矩形中的相对位置
56
+ const relativeX = (px1 - x1) / w1
57
+ const relativeY = (py1 - y1) / h1
58
+
59
+ // 计算点在缩放后矩形中的新坐标
60
+ const px2 = x2 + relativeX * w2
61
+ const py2 = y2 + relativeY * h2
62
+
63
+ return { x: px2, y: py2 }
64
+ }
65
+
66
+ // 工具函数:计算「旋转」后 某坐标点 相对中心位置比例不变的 新坐标点
67
+ // 要计算以点 x1 = (x1, y1) 为中心,点 x2 = (x2, y2) 旋转 θ 度后的坐标位置,可以使用旋转矩阵进行计算。
68
+ //
69
+ // 旋转公式如下:
70
+ // 1. 首先将点 x2 平移到以 x1 为原点的坐标系:
71
+ // x' = x2 - x1
72
+ // y' = y2 - y1
73
+ // 2. 然后应用旋转矩阵进行旋转:
74
+ // x'' = x' * cos(θ) - y' * sin(θ)
75
+ // y'' = x' * sin(θ) + y' * cos(θ)
76
+ // 3. 最后将结果平移回原来的坐标系:
77
+ // x_new = x'' + x1
78
+ // y_new = y'' + y1
79
+ //
80
+ // 综合起来,旋转后的新坐标 (x_new, y_new) 计算公式如下:
81
+ //
82
+ // x_new = (x2 - x1) * cos(θ) - (y2 - y1) * sin(θ) + x1
83
+ // y_new = (x2 - x1) * sin(θ) + (y2 - y1) * cos(θ) + y1
84
+ //
85
+ // 其中,θ 需要用弧度表示,如果你有的是角度,可以用以下公式转换为弧度:
86
+ //
87
+ // rad = deg * π / 180
88
+ export function rotatePointAroundCenter(
89
+ target: Point,
90
+ center: Point,
91
+ radian: number,
92
+ ): Point {
93
+ // Rotate point (x2, y2) around point (x1, y1) by theta degrees.
94
+ //
95
+ // Parameters:
96
+ // x1, y1: Coordinates of the center point.
97
+ // x2, y2: Coordinates of the point to rotate.
98
+ // theta_degrees: Angle in degrees to rotate the point.
99
+ //
100
+ // Returns:
101
+ // Tuple of new coordinates (x_new, y_new) after rotation.
102
+
103
+ const { x: x1, y: y1 } = center
104
+ const { x: x2, y: y2 } = target
105
+
106
+ // Translate point to origin
107
+ const xPrime = x2 - x1
108
+ const yPrime = y2 - y1
109
+
110
+ // Rotate point
111
+ const xDoublePrime = xPrime * Math.cos(radian) - yPrime * Math.sin(radian)
112
+ const yDoublePrime = xPrime * Math.sin(radian) + yPrime * Math.cos(radian)
113
+
114
+ // Translate point back
115
+ const xNew = xDoublePrime + x1
116
+ const yNew = yDoublePrime + y1
117
+
118
+ return {
119
+ x: xNew,
120
+ y: yNew,
121
+ }
122
+ }
123
+
124
+ /** Edge 相关工具方法 */
125
+
126
+ /**
127
+ * 获取某点在一个矩形图形(节点 or 边的 outline)内的偏移量
128
+ * @param point 目标点(此处即 Label 的坐标信息)
129
+ * @param element 目标元素
130
+ */
131
+ export const getPointOffsetOfElementOutline = (
132
+ point: Point,
133
+ element: BaseNodeModel | BaseEdgeModel,
134
+ ) => {
135
+ const baseType = element.BaseType
136
+ const bboxInfo: OutlineInfo | undefined =
137
+ baseType === 'node' ? getNodeOutline(element) : getEdgeOutline(element)
138
+
139
+ if (bboxInfo) {
140
+ const { x, y } = point
141
+ const { x: minX, y: minY, x1: maxX, y1: maxY } = bboxInfo
142
+ let xDeltaPercent: number = 0.5
143
+ let yDeltaPercent: number = 0.5
144
+ let xDeltaDistance: number = x - maxX
145
+ let yDeltaDistance: number = y - maxY
146
+ /**
147
+ * 文本在由路径点组成的凸包内,就记录偏移比例
148
+ * 文本在凸包外,记录绝对距离
149
+ * 用于边路径变化时计算文本新位置
150
+ */
151
+ if (minX && maxX && minX < x && x < maxX) {
152
+ xDeltaPercent = min([(x - minX) / (maxX - minX), 1]) as number
153
+ } else if (x <= minX) {
154
+ xDeltaDistance = x - minX
155
+ } else {
156
+ xDeltaDistance = x - maxX
157
+ }
158
+ if (minY && maxY && minY < y && y < maxY) {
159
+ yDeltaPercent = min([(y - minY) / (maxY - minY), 1]) as number
160
+ } else if (y <= minY) {
161
+ yDeltaDistance = y - minY
162
+ } else {
163
+ yDeltaDistance = y - maxY
164
+ }
165
+ return {
166
+ xDeltaPercent,
167
+ yDeltaPercent,
168
+ xDeltaDistance,
169
+ yDeltaDistance,
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * 判断节点是否在折线上
176
+ * @param point 目标点坐标
177
+ * @param points 折线上的点坐标
178
+ */
179
+ const isPointOnPolyline = (point: Point, points: Point[]): boolean => {
180
+ for (let i = 0; i < points.length - 1; i++) {
181
+ const start = points[i]
182
+ const end = points[i + 1]
183
+ if (isInSegment(point, start, end)) {
184
+ return true
185
+ }
186
+ }
187
+
188
+ return false
189
+ }
190
+
191
+ /**
192
+ * 给定一个点 P = (x_0, y_0) 和线段的两个端点 A = (x_1, y_1) 和 B = (x_2, y_2) ,可以使用以下步骤计算点到线段的距离:
193
+ * 1. 计算向量 AB 和 AP 。
194
+ * 2. 计算 AB 的平方长度。
195
+ * 3. 计算点 P 在直线 AB 上的投影点 Q 。
196
+ * 4. 判断 Q 是否在线段 AB 上。
197
+ * 5. 根据 Q 是否在线段上,计算点到线段的距离。
198
+ *
199
+ * 计算点到线段质检的距离
200
+ * @param point
201
+ * @param start
202
+ * @param end
203
+ */
204
+ export const pointToSegmentDistance = (
205
+ point: Point,
206
+ start: Point,
207
+ end: Point,
208
+ ): number => {
209
+ const { x: px, y: py } = point
210
+ const { x: sx, y: sy } = start
211
+ const { x: ex, y: ey } = end
212
+
213
+ const SEx = ex - sx
214
+ const SEy = ey - sy
215
+ const SPx = px - sx
216
+ const SPy = py - sy
217
+
218
+ const SE_SE = SEx ** 2 + SEy ** 2
219
+ const SP_SE = SPx * SEx + SPy * SEy
220
+ let t = SP_SE / SE_SE
221
+ if (t < 0) t = 0
222
+ if (t > 1) t = 1
223
+
224
+ const qx = sx + t * SEx
225
+ const qy = sy + t * SEy
226
+ return Math.sqrt((px - qx) ** 2 + (py - qy) ** 2)
227
+ }
228
+
229
+ export const calcPolylineTotalLength = (points: Point[]) => {
230
+ let length = 0
231
+ for (let i = 0; i < points.length - 1; i++) {
232
+ const start = points[i]
233
+ const end = points[i + 1]
234
+ length += calcTwoPointsDistance(start, end)
235
+ }
236
+ return length
237
+ }
238
+
239
+ /**
240
+ * TODO: 确认该函数的意义,写完还是没看懂什么意思
241
+ * @param point
242
+ * @param points
243
+ */
244
+ export const pointPositionRatio = (point: Point, points: Point[]): number => {
245
+ let length = 0
246
+ for (let i = 0; i < points.length - 1; i++) {
247
+ const start = points[i]
248
+ const end = points[i + 1]
249
+ const segmentLength = calcTwoPointsDistance(start, end)
250
+
251
+ if (pointToSegmentDistance(point, start, end) <= 20) {
252
+ const d1 = calcTwoPointsDistance(point, start)
253
+ length += d1
254
+ const totalLength = calcPolylineTotalLength(points)
255
+ // 小数点后保留一位(四舍五入)
256
+ return Math.round((length / totalLength) * 10) / 10
257
+ } else {
258
+ length += segmentLength
259
+ }
260
+ }
261
+ return 0
262
+ }
263
+
264
+ /**
265
+ * 计算一个坐标在贝塞尔曲线上最近的一个点
266
+ * @param point
267
+ * @param edge
268
+ * @param step
269
+ */
270
+ export const calcClosestPointOnBezierEdge = (
271
+ point: Point,
272
+ edge: BezierEdgeModel,
273
+ step: number = 5,
274
+ ): Point => {
275
+ let minDistance = Infinity
276
+ let closestPoint: Point = point
277
+
278
+ const pointsList = getBezierPoints(edge.path)
279
+ if (isEmpty(pointsList)) return closestPoint
280
+
281
+ const [start, sNext, ePre, end] = pointsList
282
+ for (let i = 0; i <= step; i++) {
283
+ const t = i / step
284
+ const bezierPoint = getPointOnBezier(t, start, sNext, ePre, end)
285
+ const distance = calcTwoPointsDistance(point, bezierPoint)
286
+ if (distance < minDistance) {
287
+ minDistance = distance
288
+ closestPoint = bezierPoint
289
+ }
290
+ }
291
+
292
+ return closestPoint
293
+ }
294
+
295
+ export const getNewPointAtDistance = (
296
+ points: Point[],
297
+ ratio: number,
298
+ ): Point | undefined => {
299
+ const totalLength = calcPolylineTotalLength(points)
300
+ const targetLength = totalLength * ratio
301
+
302
+ let length = 0
303
+ for (let i = 0; i < points.length - 1; i++) {
304
+ const start = points[i]
305
+ const end = points[i + 1]
306
+ const segmentLength = calcTwoPointsDistance(start, end)
307
+ if (length + segmentLength >= targetLength) {
308
+ const ratio = (targetLength - length) / segmentLength
309
+ return {
310
+ x: start.x + (end.x - start.x) * ratio,
311
+ y: start.y + (end.y - start.y) * ratio,
312
+ }
313
+ } else {
314
+ length += segmentLength
315
+ }
316
+ }
317
+ return last(points)
318
+ }
319
+
320
+ /**
321
+ * 计算一个坐标离折线(包括 PolylineEdge 和 LineEdge 直线)最近的一个点
322
+ * @param point
323
+ * @param edge
324
+ */
325
+ export const calcLabelPositionOnPolyline = (
326
+ point: Point,
327
+ edge: BaseEdgeModel,
328
+ ): Point => {
329
+ let points = points2PointsList(edge.points)
330
+ if (points.length === 0) {
331
+ points = [edge.startPoint, edge.endPoint]
332
+ }
333
+
334
+ const { xDeltaPercent, yDeltaPercent, yDeltaDistance, xDeltaDistance } =
335
+ getPointOffsetOfElementOutline(point, edge) ?? {}
336
+ const isPointOnEdge = isPointOnPolyline(point, points)
337
+ const ratio = pointPositionRatio(point, points)
338
+
339
+ const start = head(points)
340
+ const end = last(points)
341
+
342
+ // 分别取路径中,x轴 和 y轴上的最大最小坐标值组合成一个矩形
343
+ const { minX, minY, maxX, maxY } = getBBoxOfPoints(points, 10)
344
+
345
+ if (!start || !end) return point
346
+
347
+ if (xDeltaPercent && yDeltaPercent) {
348
+ const positByPercent = {
349
+ x: minX + (maxX - minX) * xDeltaPercent,
350
+ y: minY + (maxY - minY) * yDeltaPercent,
351
+ }
352
+ return isPointOnEdge
353
+ ? getNewPointAtDistance(points, ratio) ?? point // 函数什么意思
354
+ : positByPercent
355
+ }
356
+ // 如果文本在凸包的上方或者下方
357
+ if (xDeltaPercent && yDeltaDistance) {
358
+ return {
359
+ x: minX + (maxX - minX) * xDeltaPercent,
360
+ y: yDeltaDistance < 0 ? minY + yDeltaDistance : maxY + yDeltaDistance,
361
+ }
362
+ }
363
+ // 如果文本在凸包的左边或者右边
364
+ if (yDeltaPercent && xDeltaDistance) {
365
+ return {
366
+ x: xDeltaDistance < 0 ? minX + xDeltaDistance : maxX + xDeltaDistance,
367
+ y: minY + (maxY - minY) * yDeltaPercent,
368
+ }
369
+ }
370
+ // 如果文本在凸包左上/左下/右上/右下
371
+ if (xDeltaDistance && yDeltaDistance) {
372
+ return {
373
+ x: xDeltaDistance < 0 ? minX + xDeltaDistance : maxX + xDeltaDistance,
374
+ y: yDeltaDistance < 0 ? minY + yDeltaDistance : maxY + yDeltaDistance,
375
+ }
376
+ }
377
+ // 兜底
378
+ return point
379
+ }
380
+
381
+ /**
382
+ * 计算 Label 离边最近的点的坐标,用于更新为 Label 的坐标
383
+ * @param label LabelConfig -> 当前 Label 的配置项
384
+ * @param edge
385
+ */
386
+ export const getLabelPositionOfLine = (
387
+ label: LabelConfig,
388
+ edge: BaseEdgeModel,
389
+ ): Point => {
390
+ const { x, y } = label
391
+ if (edge.modelType === ModelType.BEZIER_EDGE) {
392
+ return calcClosestPointOnBezierEdge({ x, y }, edge as BezierEdgeModel)
393
+ }
394
+ return calcLabelPositionOnPolyline({ x, y }, edge)
395
+ }
@@ -45,7 +45,9 @@ document.getElementById("button").addEventListener("click", () => {
45
45
 
46
46
  当自定义元素在导出图片时需要额外添加 css 样式时,可以用如下方式实现:
47
47
 
48
- 为了保持流程图生成的图片与画布上效果一致,`snapshot`插件默认会将当前页面所有的 `css` 规则都加载到导出图片中, 但是可能会因为 css 文件跨域引起报错,参考 issue575。可以修改useGlobalRules来禁止加载所有 css 规则,然后通过`customCssRules`属性来自定义增加css样式。
48
+ 为了保持流程图生成的图片与画布上效果一致,`snapshot`插件默认会将当前页面所有的 `css` 规则都加载到导出图片中,
49
+ 但是可能会因为 css 文件跨域引起报错,参考 issue575。可以修改useGlobalRules来禁止加载所有 css
50
+ 规则,然后通过`customCssRules`属性来自定义增加css样式。
49
51
 
50
52
  ```tsx
51
53
 
@@ -59,7 +61,7 @@ lf.extension.snapshot.customCssRules = `
59
61
  color: blue;
60
62
  }
61
63
  `
62
-
64
+
63
65
  ```
64
66
 
65
67
  ## API
@@ -70,41 +72,50 @@ lf.extension.snapshot.customCssRules = `
70
72
 
71
73
  ```ts
72
74
 
73
- getSnapshot(fileName?: string, toImageOptions?: ToImageOptions) : Promise<void>
75
+ const getSnapshot = (fileName?: string, toImageOptions?: ToImageOptions): Promise<void> => {}
74
76
 
75
77
  ```
76
78
 
77
79
  `fileName` 为文件名称,不填为默认为`logic-flow.当前时间戳`,`ToImageOptions` 描述如下:
78
80
 
79
- | 属性名 | 类型 | 默认值 | 必填 | 描述 |
80
- | --------- | -------- | -------------------------- | -------- | ----------------------------------------------------------------- |
81
- | fileType | string | png | | 图片类型: 默认不填是png 还可以设置有webp、gif、jpeg、svg |
82
- | width | number | - | | 自定义导出图片的宽度,不设置即可,设置可能会拉伸图形 |
83
- | height | numebr | - | | 自定义导出图片的宽度,不设置即可,设置可能会拉伸图形 |
84
- | backgroundColor | string | - | | 图片背景,不设置背景默认透明 |
85
- | quality | number | - | | 图片质量,在指定图片格式为 jpeg 或 webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他不合法参数会被忽略 |
86
- | padding | number | 40 | | 图片内边距: 元素内容所在区之外空白空间,不设置默认有40的内边距 |
87
- | partial | boolean | - | | 导出时是否开启局部渲染,false:将导出画布上所有的元素,true:只导出画面区域内的可见元素,不设置默认为lf实例身上partial值 |
81
+ | 属性名 | 类型 | 默认值 | 必填 | 描述 |
82
+ |-----------------|---------|-----|----|----------------------------------------------------------------------------------------|
83
+ | fileType | string | png | | 图片类型: 默认不填是png 还可以设置有webp、gif、jpeg、svg |
84
+ | width | number | - | | 自定义导出图片的宽度,不设置即可,设置可能会拉伸图形 |
85
+ | height | number | - | | 自定义导出图片的宽度,不设置即可,设置可能会拉伸图形 |
86
+ | backgroundColor | string | - | | 图片背景,不设置背景默认透明 |
87
+ | quality | number | - | | 图片质量,在指定图片格式为 jpeg 或 webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他不合法参数会被忽略 |
88
+ | padding | number | 40 | | 图片内边距: 元素内容所在区之外空白空间,不设置默认有40的内边距 |
89
+ | partial | boolean | - | | 导出时是否开启局部渲染,false:将导出画布上所有的元素,true:只导出画面区域内的可见元素,不设置默认为lf实例身上partial值 |
88
90
 
89
91
  注意:
90
- - `svg`目前暂不支持`width`,`height`, `backgroundColor`, `padding` 属性。
92
+
93
+ - `svg`目前暂不支持`width`,`height`, `backgroundColor`, `padding` 属性。
91
94
  - 自定义宽高后,可能会拉伸图形,这时候`padding`也会被拉伸导致不准确。
92
95
 
93
96
  ### lf.getSnapshotBlob(...)
94
97
 
95
- `snapshot` 除了支持图片类型导出,还支持下载<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Blob" target="_blank"> Blob文件对象 </a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Base64" target="_blank"> Base64文本编码 </a>
98
+ `snapshot`
99
+ 除了支持图片类型导出,还支持下载<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Blob" target="_blank">
100
+ Blob文件对象 </a>
101
+ 和 <a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Base64" target="_blank">
102
+ Base64文本编码 </a>
96
103
 
97
104
  获取`Blob`对象。
98
105
 
99
106
  ```ts
100
107
 
101
- async getSnapshotBlob(backgroundColor?: string, fileType?: string) : Promise<SnapshotResponse>
108
+ const getSnapshotBlob = async (
109
+ backgroundColor?: string,
110
+ fileType?: string
111
+ ): Promise<SnapshotResponse> => {}
102
112
 
103
113
  // example
104
- const { data : blob } = await lf.getSnapshotBlob()
114
+ const { data: blob } = await lf.getSnapshotBlob()
105
115
  console.log(blob)
106
116
 
107
117
  ```
118
+
108
119
  `backgroundColor`: 背景,不填默认为透明。
109
120
 
110
121
  `fileType`: 文件类型,不填默认为png。
@@ -61,25 +61,13 @@ export class Snapshot {
61
61
  this.customCssRules = ''
62
62
  this.useGlobalRules = true
63
63
 
64
+ // TODO: 设置fileType为gif但是下载下来的还是png
65
+ // TODO: 完善静默模式不允许添加、操作元素能力
64
66
  /* 下载快照 */
65
67
  lf.getSnapshot = async (
66
68
  fileName?: string,
67
69
  toImageOptions?: ToImageOptions,
68
- ) => {
69
- const curPartial = this.lf.graphModel.getPartial()
70
- const { partial = curPartial } = toImageOptions ?? {}
71
- // 画布当前渲染模式和用户导出渲染模式不一致时,需要更新画布
72
- if (curPartial !== partial) {
73
- this.lf.graphModel.setPartial(partial)
74
- this.lf.graphModel.eventCenter.once('graph:updated', async () => {
75
- await this.getSnapshot(fileName, toImageOptions)
76
- // 恢复原来渲染模式
77
- this.lf.graphModel.setPartial(curPartial)
78
- })
79
- } else {
80
- await this.getSnapshot(fileName, toImageOptions)
81
- }
82
- }
70
+ ) => await this.getSnapshot(fileName, toImageOptions)
83
71
 
84
72
  /* 获取Blob对象,用户图片上传 */
85
73
  lf.getSnapshotBlob = async (backgroundColor?: string, fileType?: string) =>
@@ -94,10 +82,10 @@ export class Snapshot {
94
82
 
95
83
  /**
96
84
  * 获取svgRoot对象dom: 画布元素(不包含grid背景)
97
- * @param lf LogicFlow
85
+ * @param lf
98
86
  * @returns
99
87
  */
100
- getSvgRootElement(lf: LogicFlow) {
88
+ private getSvgRootElement(lf: LogicFlow) {
101
89
  const svgRootElement = lf.container.querySelector('.lf-canvas-overlay')!
102
90
  return svgRootElement
103
91
  }
@@ -106,7 +94,7 @@ export class Snapshot {
106
94
  * 通过 imgUrl 下载图片
107
95
  * @param imgUrl
108
96
  */
109
- triggerDownload(imgUrl: string) {
97
+ private triggerDownload(imgUrl: string) {
110
98
  const evt = new MouseEvent('click', {
111
99
  view: document.defaultView,
112
100
  bubbles: false,
@@ -121,7 +109,7 @@ export class Snapshot {
121
109
 
122
110
  /**
123
111
  * 删除锚点
124
- * @param element ChildNode
112
+ * @param element
125
113
  */
126
114
  private removeAnchor(element: ChildNode) {
127
115
  const { childNodes } = element
@@ -155,15 +143,43 @@ export class Snapshot {
155
143
  }
156
144
  }
157
145
 
146
+ /**
147
+ * 下载前的处理画布工作:局部渲染模式处理、静默模式处理
148
+ * @param fileName
149
+ * @param toImageOptions
150
+ */
151
+ async getSnapshot(fileName?: string, toImageOptions?: ToImageOptions) {
152
+ const curPartial = this.lf.graphModel.getPartial()
153
+ const { partial = curPartial } = toImageOptions ?? {}
154
+ // 获取流程图配置
155
+ const editConfig = this.lf.getEditConfig()
156
+ // 开启静默模式:如果元素多的话 避免用户交互 感知卡顿
157
+ this.lf.updateEditConfig({
158
+ isSilentMode: true,
159
+ stopScrollGraph: true,
160
+ stopMoveGraph: true,
161
+ })
162
+ // 画布当前渲染模式和用户导出渲染模式不一致时,需要更新画布
163
+ if (curPartial !== partial) {
164
+ this.lf.graphModel.setPartial(partial)
165
+ this.lf.graphModel.eventCenter.once('graph:updated', async () => {
166
+ await this.snapshot(fileName, toImageOptions)
167
+ // 恢复原来渲染模式
168
+ this.lf.graphModel.setPartial(curPartial)
169
+ })
170
+ } else {
171
+ await this.snapshot(fileName, toImageOptions)
172
+ }
173
+ // 恢复原来配置
174
+ this.lf.updateEditConfig(editConfig)
175
+ }
176
+
158
177
  /**
159
178
  * 下载图片
160
- * @param fileName string
179
+ * @param fileName
161
180
  * @param toImageOptions
162
181
  */
163
- private async getSnapshot(
164
- fileName?: string,
165
- toImageOptions?: ToImageOptions,
166
- ) {
182
+ private async snapshot(fileName?: string, toImageOptions?: ToImageOptions) {
167
183
  const { fileType = 'png', quality } = toImageOptions ?? {}
168
184
  this.fileName = `${fileName ?? `logic-flow.${Date.now()}`}.${fileType}`
169
185
  const svg = this.getSvgRootElement(this.lf)
@@ -191,9 +207,9 @@ export class Snapshot {
191
207
 
192
208
  /**
193
209
  * 获取base64对象
194
- * @param backgroundColor string | undefined
195
- * @param fileType string | undefined
196
- * @returns Promise<SnapshotResponse>
210
+ * @param backgroundColor
211
+ * @param fileType
212
+ * @returns
197
213
  */
198
214
  private async getSnapshotBase64(
199
215
  backgroundColor?: string,
@@ -218,8 +234,8 @@ export class Snapshot {
218
234
 
219
235
  /**
220
236
  * 获取Blob对象
221
- * @param backgroundColor string | undefined
222
- * @param fileType string | undefined
237
+ * @param backgroundColor
238
+ * @param fileType
223
239
  * @returns
224
240
  */
225
241
  private async getSnapshotBlob(
@@ -249,7 +265,7 @@ export class Snapshot {
249
265
 
250
266
  /**
251
267
  * 获取脚本css样式
252
- * @returns rules string
268
+ * @returns
253
269
  */
254
270
  private getClassRules(): string {
255
271
  let rules = ''
@@ -257,8 +273,15 @@ export class Snapshot {
257
273
  const { styleSheets } = document
258
274
  for (let i = 0; i < styleSheets.length; i++) {
259
275
  const sheet = styleSheets[i]
260
- for (let j = 0; j < sheet.cssRules.length; j++) {
261
- rules += sheet.cssRules[j].cssText
276
+ // 这里是为了过滤掉不同源css脚本
277
+ try {
278
+ for (let j = 0; j < sheet.cssRules.length; j++) {
279
+ rules += sheet.cssRules[j].cssText
280
+ }
281
+ } catch (error) {
282
+ console.log(
283
+ 'CSS scripts from different sources have been filtered out',
284
+ )
262
285
  }
263
286
  }
264
287
  }
@@ -270,9 +293,9 @@ export class Snapshot {
270
293
 
271
294
  /**
272
295
  * 获取图片生成中间产物canvas对象,用户转换为其他需要的格式
273
- * @param svg Element
274
- * @param toImageOptions ToImageOptions
275
- * @returns Promise<HTMLCanvasElement>
296
+ * @param svg
297
+ * @param toImageOptions
298
+ * @returns
276
299
  */
277
300
  private async getCanvasData(
278
301
  svg: Element,
@@ -371,8 +394,6 @@ export class Snapshot {
371
394
  width && height ? copyCanvas(canvas, width, height) : canvas,
372
395
  )
373
396
  }
374
- // 如果局部渲染本来是开启的,继续开启
375
- // partial && this.lf.graphModel.setPartial(true)
376
397
  } catch (e) {
377
398
  ctx?.drawImage(img, padding / dpr, padding / dpr)
378
399
  resolve(width && height ? copyCanvas(canvas, width, height) : canvas)
@@ -397,7 +418,7 @@ export class Snapshot {
397
418
 
398
419
  /**
399
420
  * 克隆并处理画布节点
400
- * @param svg Node
421
+ * @param svg
401
422
  * @returns
402
423
  */
403
424
  private cloneSvg(svg: Element, addStyle: boolean = true): Node {