@logicflow/extension 2.2.0-alpha.7 → 2.2.1

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 (125) hide show
  1. package/README.md +16 -0
  2. package/package.json +10 -7
  3. package/.turbo/turbo-build.log +0 -38
  4. package/CHANGELOG.md +0 -1829
  5. package/__test__/bpmn-adapter.test.js +0 -227
  6. package/es/materials/curved-edge/__test__/curved-edge.test.d.ts +0 -1
  7. package/es/materials/curved-edge/__test__/curved-edge.test.js +0 -18
  8. package/jest.config.js +0 -198
  9. package/lib/materials/curved-edge/__test__/curved-edge.test.d.ts +0 -1
  10. package/lib/materials/curved-edge/__test__/curved-edge.test.js +0 -20
  11. package/rollup.config.js +0 -52
  12. package/src/NodeResize/BasicShape/Ellipse.tsx +0 -22
  13. package/src/NodeResize/BasicShape/Polygon.tsx +0 -24
  14. package/src/NodeResize/BasicShape/Rect.tsx +0 -44
  15. package/src/NodeResize/control/Control.tsx +0 -537
  16. package/src/NodeResize/control/ControlGroup.tsx +0 -76
  17. package/src/NodeResize/control/Util.ts +0 -206
  18. package/src/NodeResize/index.ts +0 -26
  19. package/src/NodeResize/node/DiamondResize.tsx +0 -149
  20. package/src/NodeResize/node/EllipseResize.tsx +0 -140
  21. package/src/NodeResize/node/HtmlResize.tsx +0 -125
  22. package/src/NodeResize/node/RectResize.tsx +0 -126
  23. package/src/NodeResize/node/index.ts +0 -4
  24. package/src/bpmn/constant.ts +0 -56
  25. package/src/bpmn/events/EndEvent.ts +0 -73
  26. package/src/bpmn/events/StartEvent.ts +0 -52
  27. package/src/bpmn/events/index.ts +0 -2
  28. package/src/bpmn/flow/SequenceFlow.ts +0 -25
  29. package/src/bpmn/flow/index.ts +0 -1
  30. package/src/bpmn/gateways/ExclusiveGateway.ts +0 -71
  31. package/src/bpmn/gateways/index.ts +0 -1
  32. package/src/bpmn/getBpmnId.ts +0 -31
  33. package/src/bpmn/index.ts +0 -60
  34. package/src/bpmn/tasks/ServiceTask.ts +0 -63
  35. package/src/bpmn/tasks/UserTask.ts +0 -64
  36. package/src/bpmn/tasks/index.ts +0 -2
  37. package/src/bpmn-adapter/bpmnIds.ts +0 -31
  38. package/src/bpmn-adapter/index.ts +0 -835
  39. package/src/bpmn-adapter/json2xml.ts +0 -127
  40. package/src/bpmn-adapter/xml2json.ts +0 -544
  41. package/src/bpmn-elements/README.md +0 -223
  42. package/src/bpmn-elements/__tests__/definition.test.js +0 -72
  43. package/src/bpmn-elements/index.d.ts +0 -26
  44. package/src/bpmn-elements/index.ts +0 -107
  45. package/src/bpmn-elements/presets/Event/EndEventFactory.ts +0 -114
  46. package/src/bpmn-elements/presets/Event/IntermediateCatchEvent.ts +0 -108
  47. package/src/bpmn-elements/presets/Event/IntermediateThrowEvent.ts +0 -109
  48. package/src/bpmn-elements/presets/Event/StartEventFactory.ts +0 -114
  49. package/src/bpmn-elements/presets/Event/boundaryEventFactory.ts +0 -117
  50. package/src/bpmn-elements/presets/Event/index.ts +0 -14
  51. package/src/bpmn-elements/presets/Flow/flow.d.ts +0 -6
  52. package/src/bpmn-elements/presets/Flow/index.ts +0 -8
  53. package/src/bpmn-elements/presets/Flow/manhattan.ts +0 -691
  54. package/src/bpmn-elements/presets/Flow/sequenceFlow.ts +0 -65
  55. package/src/bpmn-elements/presets/Gateway/gateway.ts +0 -107
  56. package/src/bpmn-elements/presets/Gateway/index.ts +0 -23
  57. package/src/bpmn-elements/presets/Pool/Lane.ts +0 -211
  58. package/src/bpmn-elements/presets/Pool/Pool.ts +0 -284
  59. package/src/bpmn-elements/presets/Pool/index.ts +0 -89
  60. package/src/bpmn-elements/presets/Task/index.ts +0 -122
  61. package/src/bpmn-elements/presets/Task/subProcess.ts +0 -189
  62. package/src/bpmn-elements/presets/Task/task.ts +0 -193
  63. package/src/bpmn-elements/presets/icons.ts +0 -155
  64. package/src/bpmn-elements/utils.ts +0 -52
  65. package/src/bpmn-elements-adapter/README.md +0 -293
  66. package/src/bpmn-elements-adapter/__tests__/adapter_in.test.js +0 -528
  67. package/src/bpmn-elements-adapter/__tests__/adapter_out.test.js +0 -569
  68. package/src/bpmn-elements-adapter/constant.ts +0 -76
  69. package/src/bpmn-elements-adapter/index.ts +0 -1134
  70. package/src/bpmn-elements-adapter/json2xml.ts +0 -105
  71. package/src/bpmn-elements-adapter/xml2json.ts +0 -542
  72. package/src/components/context-menu/index.ts +0 -253
  73. package/src/components/control/index.ts +0 -155
  74. package/src/components/dnd-panel/index.ts +0 -137
  75. package/src/components/highlight/index.ts +0 -227
  76. package/src/components/menu/index.ts +0 -748
  77. package/src/components/mini-map/index.ts +0 -686
  78. package/src/components/selection-select/index.ts +0 -387
  79. package/src/dynamic-group/index.ts +0 -774
  80. package/src/dynamic-group/model.ts +0 -580
  81. package/src/dynamic-group/node.ts +0 -288
  82. package/src/dynamic-group/utils.ts +0 -46
  83. package/src/index.less +0 -1
  84. package/src/index.ts +0 -47
  85. package/src/insert-node-in-polyline/edge.ts +0 -175
  86. package/src/insert-node-in-polyline/index.ts +0 -193
  87. package/src/materials/curved-edge/__test__/curved-edge.test.ts +0 -46
  88. package/src/materials/curved-edge/index.ts +0 -217
  89. package/src/materials/group/GroupNode.ts +0 -437
  90. package/src/materials/group/index.ts +0 -542
  91. package/src/materials/node-selection/index.ts +0 -380
  92. package/src/mindmap/fakerRoot.ts +0 -19
  93. package/src/mindmap/index.ts +0 -328
  94. package/src/mindmap/markContent.ts +0 -81
  95. package/src/mindmap/markContentOption.ts +0 -81
  96. package/src/mindmap/markEntity.ts +0 -82
  97. package/src/mindmap/markRoot.ts +0 -83
  98. package/src/mindmap/theme.ts +0 -11
  99. package/src/pool/LaneModel.ts +0 -226
  100. package/src/pool/LaneView.ts +0 -220
  101. package/src/pool/PoolModel.ts +0 -631
  102. package/src/pool/PoolView.ts +0 -75
  103. package/src/pool/constant.ts +0 -19
  104. package/src/pool/index.ts +0 -621
  105. package/src/pool/utils.ts +0 -46
  106. package/src/rect-label-node/RectLabelNodeView.ts +0 -33
  107. package/src/rect-label-node/index.ts +0 -15
  108. package/src/style/index.less +0 -381
  109. package/src/style/raw.ts +0 -328
  110. package/src/tools/auto-layout/index.ts +0 -282
  111. package/src/tools/flow-path/index.ts +0 -233
  112. package/src/tools/label/Label.tsx +0 -357
  113. package/src/tools/label/LabelModel.ts +0 -83
  114. package/src/tools/label/LabelOverlay.tsx +0 -162
  115. package/src/tools/label/algorithm.ts +0 -42
  116. package/src/tools/label/index.ts +0 -479
  117. package/src/tools/label/mediumEditor.ts +0 -121
  118. package/src/tools/label/utils.ts +0 -395
  119. package/src/tools/proximity-connect/index.ts +0 -435
  120. package/src/tools/snapshot/README.md +0 -145
  121. package/src/tools/snapshot/index.ts +0 -701
  122. package/src/tools/snapshot/utils.ts +0 -163
  123. package/src/turbo-adapter/index.ts +0 -212
  124. package/stats.html +0 -4842
  125. package/tsconfig.json +0 -18
@@ -1,701 +0,0 @@
1
- import LogicFlow from '@logicflow/core'
2
- import { updateImageSource, copyCanvas } from './utils'
3
-
4
- // 导出图片
5
- export type ToImageOptions = {
6
- /**
7
- * 导出图片的格式,可选值为:`png`、`webp`、`jpeg`、`svg`,默认值为 `png`
8
- */
9
- fileType?: string
10
- /**
11
- * 导出图片的宽度,通常无需设置,设置后可能会拉伸图形
12
- */
13
- width?: number
14
- /**
15
- * 导出图片的高度,通常无需设置,设置后可能会拉伸图形
16
- */
17
- height?: number
18
- /**
19
- * 导出图片的背景色,默认为透明
20
- */
21
- backgroundColor?: string
22
- /**
23
- * 导出图片的质量。
24
- *
25
- * 在指定图片格式为 `jpeg` 或 `webp` 的情况下,可以从 0 到 1 的区间内选择图片的质量,如果超出取值范围,将会使用默认值 0.92。导出为其他格式的图片时,该参数会被忽略。
26
- */
27
- quality?: number
28
- /**
29
- * 导出图片的内边距,即元素内容所在区域边界与图片边界的距离,单位为像素,默认为 40
30
- */
31
- padding?: number
32
- /**
33
- * 导出图片时是否开启局部渲染
34
- * - `false`:将导出画布上所有的元素
35
- * - `true`:只导出画面区域内的可见元素
36
- */
37
- partial?: boolean
38
- /**
39
- * 导出图片时的安全系数,用于确保导出的图片能够容纳所有元素,默认值为 1.1
40
- */
41
- safetyFactor?: number
42
- /**
43
- * 导出图片时的安全边距,用于确保导出的图片能够容纳所有元素,默认值为 40
44
- */
45
- safetyMargin?: number
46
- }
47
-
48
- // Blob | base64
49
- export type SnapshotResponse = {
50
- data: Blob | string
51
- width: number
52
- height: number
53
- }
54
-
55
- /**
56
- * 快照插件,生成视图
57
- */
58
- export class Snapshot {
59
- static pluginName = 'snapshot'
60
- lf: LogicFlow
61
- offsetX?: number
62
- offsetY?: number
63
- fileName?: string // 默认是 logic-flow.当前时间戳
64
- customCssRules: string
65
- useGlobalRules: boolean
66
-
67
- constructor({ lf }) {
68
- this.lf = lf
69
- this.customCssRules = ''
70
- this.useGlobalRules = true
71
-
72
- // TODO: 设置fileType为gif但是下载下来的还是png
73
- // TODO: 完善静默模式不允许添加、操作元素能力
74
- /* 导出画布快照 */
75
- lf.getSnapshot = async (
76
- fileName?: string,
77
- toImageOptions?: ToImageOptions,
78
- ) => await this.getSnapshot(fileName, toImageOptions)
79
-
80
- /* 获取Blob对象 */
81
- lf.getSnapshotBlob = async (
82
- backgroundColor?: string, // 兼容老的使用方式
83
- fileType?: string,
84
- toImageOptions?: ToImageOptions,
85
- ) => await this.getSnapshotBlob(backgroundColor, fileType, toImageOptions)
86
-
87
- /* 获取Base64对象 */
88
- lf.getSnapshotBase64 = async (
89
- backgroundColor?: string, // 兼容老的使用方式
90
- fileType?: string,
91
- toImageOptions?: ToImageOptions,
92
- ) => await this.getSnapshotBase64(backgroundColor, fileType, toImageOptions)
93
- }
94
-
95
- /**
96
- * 获取svgRoot对象dom: 画布元素(不包含grid背景)
97
- * @param lf
98
- * @returns
99
- */
100
- private getSvgRootElement(lf: LogicFlow) {
101
- const svgRootElement = lf.container.querySelector('.lf-canvas-overlay')!
102
- return svgRootElement
103
- }
104
-
105
- /**
106
- * 通过 imgUrl 下载图片
107
- * @param imgUrl
108
- */
109
- private triggerDownload(imgUrl: string) {
110
- const evt = new MouseEvent('click', {
111
- view: document.defaultView,
112
- bubbles: false,
113
- cancelable: true,
114
- })
115
- const a = document.createElement('a')
116
- a.setAttribute('download', this.fileName!)
117
- a.setAttribute('href', imgUrl)
118
- a.setAttribute('target', '_blank')
119
- a.dispatchEvent(evt)
120
- }
121
-
122
- /**
123
- * 删除锚点
124
- * @param element
125
- */
126
- private removeAnchor(element: ChildNode) {
127
- const { childNodes } = element
128
- let childLength = element.childNodes && element.childNodes.length
129
- for (let i = 0; i < childLength; i++) {
130
- const child = childNodes[i] as SVGGraphicsElement
131
- const classList = (child.classList && Array.from(child.classList)) || []
132
- if (classList.indexOf('lf-anchor') > -1) {
133
- element.removeChild(element.childNodes[i])
134
- childLength--
135
- i--
136
- }
137
- }
138
- }
139
-
140
- /**
141
- * 删除旋转按钮
142
- * @param element
143
- */
144
- private removeRotateControl(element: ChildNode) {
145
- const { childNodes } = element
146
- let childLength = element.childNodes && element.childNodes.length
147
- for (let i = 0; i < childLength; i++) {
148
- const child = childNodes[i] as SVGGraphicsElement
149
- const classList = (child.classList && Array.from(child.classList)) || []
150
- if (classList.indexOf('lf-rotate-control') > -1) {
151
- element.removeChild(element.childNodes[i])
152
- childLength--
153
- i--
154
- }
155
- }
156
- }
157
-
158
- /**
159
- * 将图片转换为base64格式
160
- * @param url - 图片URL
161
- * @returns Promise<string> - base64字符串
162
- */
163
- private async convertImageToBase64(url: string): Promise<string> {
164
- return new Promise((resolve, reject) => {
165
- const img = new Image()
166
- img.crossOrigin = 'anonymous' // 处理跨域问题
167
- img.onload = () => {
168
- const canvas = document.createElement('canvas')
169
- canvas.width = img.width
170
- canvas.height = img.height
171
- const ctx = canvas.getContext('2d')
172
- ctx?.drawImage(img, 0, 0)
173
- const base64 = canvas.toDataURL('image/png')
174
- resolve(base64)
175
- }
176
- img.onerror = () => {
177
- reject(new Error(`Failed to load image: ${url}`))
178
- }
179
- img.src = url
180
- })
181
- }
182
-
183
- /**
184
- * 检查URL是否为相对路径
185
- * @param url - 要检查的URL
186
- * @returns boolean - 是否为相对路径
187
- */
188
- private isRelativePath(url: string): boolean {
189
- return (
190
- !url.startsWith('data:') &&
191
- !url.startsWith('http://') &&
192
- !url.startsWith('https://') &&
193
- !url.startsWith('//')
194
- )
195
- }
196
-
197
- /**
198
- * 处理SVG中的图片元素
199
- * @param element - SVG元素
200
- */
201
- private async processImages(element: Element): Promise<void> {
202
- // 处理image元素
203
- const images = element.getElementsByTagName('image')
204
- for (let i = 0; i < images.length; i++) {
205
- const image = images[i]
206
- const href =
207
- image.getAttributeNS('http://www.w3.org/1999/xlink', 'href') ||
208
- image.getAttribute('href')
209
- if (href && this.isRelativePath(href)) {
210
- try {
211
- const base64 = await this.convertImageToBase64(href)
212
- image.setAttributeNS('http://www.w3.org/1999/xlink', 'href', base64)
213
- image.setAttribute('href', base64)
214
- } catch (error) {
215
- console.warn(`Failed to convert image to base64: ${href}`, error)
216
- }
217
- }
218
- }
219
-
220
- // 处理foreignObject中的img元素
221
- const foreignObjects = element.getElementsByTagName('foreignObject')
222
- for (let i = 0; i < foreignObjects.length; i++) {
223
- const foreignObject = foreignObjects[i]
224
- const images = foreignObject.getElementsByTagName('img')
225
- for (let j = 0; j < images.length; j++) {
226
- const image = images[j]
227
- const src = image.getAttribute('src')
228
- if (src && this.isRelativePath(src)) {
229
- try {
230
- const base64 = await this.convertImageToBase64(src)
231
- image.setAttribute('src', base64)
232
- } catch (error) {
233
- console.warn(`Failed to convert image to base64: ${src}`, error)
234
- }
235
- }
236
- }
237
- }
238
- }
239
-
240
- /**
241
- * 克隆并处理画布节点
242
- * @param svg
243
- * @returns
244
- */
245
- private async cloneSvg(
246
- svg: Element,
247
- addStyle: boolean = true,
248
- ): Promise<Node> {
249
- const copy = svg.cloneNode(true) as Element
250
- const graph = copy.lastChild as Element
251
- let childLength = graph?.childNodes?.length
252
- if (childLength) {
253
- for (let i = 0; i < childLength; i++) {
254
- const lfLayer = graph?.childNodes[i] as SVGGraphicsElement
255
- // 只保留包含节点和边的基础图层进行下载,其他图层删除
256
- const layerClassList =
257
- lfLayer.classList && Array.from(lfLayer.classList)
258
- if (layerClassList && layerClassList.indexOf('lf-base') < 0) {
259
- graph?.removeChild(graph.childNodes[i])
260
- childLength--
261
- i--
262
- } else {
263
- // 删除锚点
264
- const lfBase = graph?.childNodes[i]
265
- lfBase &&
266
- lfBase.childNodes.forEach((item) => {
267
- const element = item as SVGGraphicsElement
268
- this.removeAnchor(element.firstChild!)
269
- this.removeRotateControl(element.firstChild!)
270
- })
271
- }
272
- }
273
- }
274
-
275
- // 处理图片路径
276
- await this.processImages(copy)
277
-
278
- // 设置css样式
279
- if (addStyle) {
280
- const style = document.createElement('style')
281
- style.innerHTML = this.getClassRules()
282
- const foreignObject = document.createElement('foreignObject')
283
- foreignObject.appendChild(style)
284
- copy.appendChild(foreignObject)
285
- }
286
- return copy
287
- }
288
-
289
- /**
290
- * 获取脚本 css 样式
291
- * @returns
292
- */
293
- private getClassRules(): string {
294
- let rules = ''
295
- if (this.useGlobalRules) {
296
- const { styleSheets } = document
297
- for (let i = 0; i < styleSheets.length; i++) {
298
- const sheet = styleSheets[i]
299
- // 这里是为了过滤掉不同源 css 脚本,防止报错终止导出
300
- try {
301
- for (let j = 0; j < sheet.cssRules.length; j++) {
302
- rules += sheet.cssRules[j].cssText
303
- }
304
- } catch (error) {
305
- console.log(
306
- 'CSS scripts from different sources have been filtered out',
307
- )
308
- }
309
- }
310
- }
311
- if (this.customCssRules) {
312
- rules += this.customCssRules
313
- }
314
- return rules
315
- }
316
-
317
- /**
318
- * 根据浏览器对 canvas 的大小限制,计算等比缩放比例
319
- * - 参考 MDN 最大的画布尺寸:
320
- * https://developer.mozilla.org/zh-CN/docs/Web/HTML/Reference/Elements/canvas#最大的画布尺寸
321
- *
322
- * @param width
323
- * @param height
324
- */
325
- private getCanvasScaleRatio(width: number, height: number): number {
326
- /* 单边最大像素 */
327
- const maxCanvasSide = 32767
328
- /* 最大像素面积 */
329
- const maxCanvasArea = 268435456
330
-
331
- const area = width * height
332
-
333
- if (
334
- width <= maxCanvasSide &&
335
- height <= maxCanvasSide &&
336
- area <= maxCanvasArea
337
- ) {
338
- return 1
339
- }
340
-
341
- const widthScale = maxCanvasSide / width
342
- const heightScale = maxCanvasSide / height
343
- const areaScale = maxCanvasArea / area
344
-
345
- return Math.min(widthScale, heightScale, areaScale)
346
- }
347
-
348
- /**
349
- * 将 svg 转化为 canvas
350
- * @param svg - svg 元素
351
- * @param toImageOptions - 图像选项
352
- * @returns Promise<canvas> - 返回 canvas 对象
353
- */
354
- private async getCanvasData(
355
- svg: Element,
356
- toImageOptions: ToImageOptions,
357
- ): Promise<HTMLCanvasElement> {
358
- const { width, height, backgroundColor, padding = 40 } = toImageOptions
359
- const copy = await this.cloneSvg(svg, false)
360
-
361
- let dpr = window.devicePixelRatio || 1
362
- if (dpr < 1) {
363
- // https://github.com/didi/LogicFlow/issues/1222
364
- // canvas.width = bboxWidth * dpr配合ctx.scale(dpr, dpr)是为了解决绘制模糊
365
- // 比如dpr=2,先让canvas.width放大到等同于屏幕的物理像素宽高,然后自适应缩放适配canvas.style.width
366
- // 由于所有元素都缩放了一半,因此需要ctx.scale(dpr, dpr)放大2倍整体绘制的内容
367
- // 当用户缩放浏览器时,window.devicePixelRatio会随着变小
368
- // 当window.devicePixelRatio变小到一定程度,会导致canvas.width<canvas.style.width
369
- // 由于导出图片的svg的大小是canvas.style.width+canvas.style.height
370
- // 因此会导致导出的svg图片无法完整绘制到canvas(因为canvas.width小于svg的宽)
371
- // 从而导致canvas导出图片是缺失的svg
372
- // 而dpr>=1就能保证canvas.width>=canvas.style.width
373
- // 当dpr小于1的时候,我们强制转化为1,并不会产生绘制模糊等问题
374
- dpr = 1
375
- }
376
- /*
377
- 为了计算真实宽高需要取图的真实dom
378
- 真实dom存在缩放影响其宽高数值
379
- 在得到真实宽高后除以缩放比例即可得到正常宽高
380
- */
381
-
382
- const base = this.lf.graphModel.rootEl.querySelector('.lf-base')
383
- const bbox = (base as Element).getBoundingClientRect()
384
- const layoutCanvas = this.lf.container.querySelector('.lf-canvas-overlay')!
385
- const layout = layoutCanvas.getBoundingClientRect()
386
- const offsetX = bbox.x - layout.x
387
- const offsetY = bbox.y - layout.y
388
- const { graphModel } = this.lf
389
- const { transformModel } = graphModel
390
- const { SCALE_X, SCALE_Y, TRANSLATE_X, TRANSLATE_Y } = transformModel
391
-
392
- // 计算实际宽高,考虑缩放因素
393
- // 在宽画布情况下,getBoundingClientRect可能无法获取到所有元素的边界
394
- // 因此我们添加一个安全系数来确保能够容纳所有元素
395
- const safetyFactor = toImageOptions.safetyFactor || 1 // 安全系数,增加10%的空间
396
- const actualWidth = bbox.width / SCALE_X
397
- const actualHeight = bbox.height / SCALE_Y
398
- const factorWidth = actualWidth * (safetyFactor - 1)
399
- const factorHeight = actualHeight * (safetyFactor - 1)
400
-
401
- // 包含所有元素的最小宽高,确保足够大以容纳所有元素
402
- const bboxWidth = Math.ceil(actualWidth + factorWidth)
403
- const bboxHeight = Math.ceil(actualHeight + factorHeight)
404
- const canvas = document.createElement('canvas')
405
- canvas.style.width = `${bboxWidth}px`
406
- canvas.style.height = `${bboxHeight}px`
407
-
408
- // 宽高值 默认加padding 40,保证图形不会紧贴着下载图片
409
- // 为宽画布添加额外的安全边距,确保不会裁剪
410
- const safetyMargin = toImageOptions.safetyMargin || 0 // 额外的安全边距
411
-
412
- let targetWidth = bboxWidth * dpr
413
- let targetHeight = bboxHeight * dpr
414
-
415
- // 超出 canvas 大小限制时,进行等比缩放
416
- const scaleRatio = this.getCanvasScaleRatio(targetWidth, targetHeight)
417
- if (scaleRatio < 1) {
418
- targetWidth = Math.floor(targetWidth * scaleRatio)
419
- targetHeight = Math.floor(targetHeight * scaleRatio)
420
- }
421
-
422
- // 将导出区域移动到左上角,canvas 绘制的时候是从左上角开始绘制的
423
- // 在transform矩阵中加入padding值,确保左侧元素不会被截断
424
- // 对这个矩阵进行缩放,否则会导致截断
425
- ;(copy.lastChild as SVGElement).style.transform =
426
- `matrix(${scaleRatio}, 0, 0, ${scaleRatio}, ${
427
- (-offsetX + TRANSLATE_X) * (1 / SCALE_X) * scaleRatio +
428
- padding +
429
- factorWidth / 2 +
430
- safetyMargin
431
- }, ${(-offsetY + TRANSLATE_Y) * (1 / SCALE_Y) * scaleRatio + padding + factorHeight / 2 + safetyMargin})`
432
- canvas.width = targetWidth + (padding + safetyMargin) * 2 * dpr
433
- canvas.height = targetHeight + (padding + safetyMargin) * 2 * dpr
434
- const ctx = canvas.getContext('2d')
435
- if (ctx) {
436
- // 清空canvas
437
- ctx.clearRect(0, 0, canvas.width, canvas.height)
438
- ctx.scale(dpr, dpr)
439
- // 如果有背景色,设置流程图导出的背景色
440
- if (backgroundColor) {
441
- ctx.fillStyle = backgroundColor
442
- ctx.fillRect(0, 0, canvas.width, canvas.height)
443
- } else {
444
- ctx.clearRect(0, 0, canvas.width, canvas.height)
445
- }
446
- }
447
-
448
- const img = new Image()
449
-
450
- // 注入 css 样式
451
- const style = document.createElement('style')
452
- style.innerHTML = this.getClassRules()
453
- const foreignObject = document.createElement('foreignObject')
454
- foreignObject.appendChild(style)
455
- copy.appendChild(foreignObject)
456
- return new Promise((resolve) => {
457
- img.onload = () => {
458
- const isFirefox = navigator.userAgent.indexOf('Firefox') > -1
459
- try {
460
- if (isFirefox) {
461
- createImageBitmap(img, {
462
- resizeWidth:
463
- width && height
464
- ? copyCanvas(canvas, width, height).width
465
- : canvas.width,
466
- resizeHeight:
467
- width && height
468
- ? copyCanvas(canvas, width, height).height
469
- : canvas.height,
470
- }).then((imageBitmap) => {
471
- // 由于在transform矩阵中已经考虑了padding,这里不再需要额外的padding偏移
472
- ctx?.drawImage(imageBitmap, 0, 0)
473
- resolve(
474
- width && height ? copyCanvas(canvas, width, height) : canvas,
475
- )
476
- })
477
- } else {
478
- // 由于在transform矩阵中已经考虑了padding,这里不再需要额外的padding偏移
479
- ctx?.drawImage(img, 0, 0)
480
- resolve(
481
- width && height ? copyCanvas(canvas, width, height) : canvas,
482
- )
483
- }
484
- } catch (e) {
485
- // 由于在transform矩阵中已经考虑了padding,这里不再需要额外的padding偏移
486
- ctx?.drawImage(img, 0, 0)
487
- resolve(width && height ? copyCanvas(canvas, width, height) : canvas)
488
- }
489
- }
490
-
491
- /*
492
- 因为svg中存在dom存放在foreignObject元素中
493
- svg dom => Base64编码字符串 挂载到img上
494
- fixme: XMLSerializer的中的css background url不会下载图片
495
- */
496
- const svg2Img = `data:image/svg+xml;charset=utf-8,${new XMLSerializer().serializeToString(
497
- copy,
498
- )}`
499
- const imgSrc = svg2Img
500
- .replace(/\n/g, '')
501
- .replace(/\t/g, '')
502
- .replace(/#/g, '%23')
503
- img.src = imgSrc
504
- })
505
- }
506
-
507
- /**
508
- * 封装导出前的通用处理逻辑:局部渲染模式处理、静默模式处理
509
- * @param callback 实际执行的导出操作回调函数
510
- * @param toImageOptions 导出图片选项
511
- * @returns 返回回调函数的执行结果
512
- */
513
- private async withExportPreparation<T>(
514
- callback: () => Promise<T>,
515
- toImageOptions?: ToImageOptions,
516
- ): Promise<T> {
517
- // 获取当前局部渲染状态
518
- const curPartial = this.lf.graphModel.getPartial()
519
- const { partial = curPartial } = toImageOptions ?? {}
520
- // 获取流程图配置
521
- const editConfig = this.lf.getEditConfig()
522
-
523
- // 开启静默模式:如果元素多的话 避免用户交互 感知卡顿
524
- this.lf.updateEditConfig({
525
- isSilentMode: true,
526
- stopScrollGraph: true,
527
- stopMoveGraph: true,
528
- })
529
-
530
- let result: T
531
-
532
- try {
533
- // 如果画布的渲染模式与导出渲染模式不一致,则切换渲染模式
534
- if (curPartial !== partial) {
535
- this.lf.graphModel.setPartial(partial)
536
- // 等待画布更新完成
537
- result = await new Promise<T>((resolve) => {
538
- this.lf.graphModel.eventCenter.once('graph:updated', async () => {
539
- const callbackResult = await callback()
540
- // 恢复原来渲染模式
541
- this.lf.graphModel.setPartial(curPartial)
542
- resolve(callbackResult)
543
- })
544
- })
545
- } else {
546
- // 直接执行回调
547
- result = await callback()
548
- }
549
- } finally {
550
- // 恢复原来配置
551
- this.lf.updateEditConfig(editConfig)
552
- }
553
-
554
- return result
555
- }
556
-
557
- /**
558
- * 导出画布:导出前的处理画布工作,局部渲染模式处理、静默模式处理
559
- * @param fileName
560
- * @param toImageOptions
561
- */
562
- async getSnapshot(fileName?: string, toImageOptions?: ToImageOptions) {
563
- await this.withExportPreparation(
564
- () => this.snapshot(fileName, toImageOptions),
565
- toImageOptions,
566
- )
567
- }
568
-
569
- /**
570
- * 下载图片
571
- * @param fileName
572
- * @param toImageOptions
573
- */
574
- private async snapshot(fileName?: string, toImageOptions?: ToImageOptions) {
575
- const { fileType = 'png', quality } = toImageOptions ?? {}
576
- this.fileName = `${fileName ?? `logic-flow.${Date.now()}`}.${fileType}`
577
- const svg = this.getSvgRootElement(this.lf)
578
- await updateImageSource(svg as SVGElement)
579
- if (fileType === 'svg') {
580
- const copy = await this.cloneSvg(svg)
581
- const svgString = new XMLSerializer().serializeToString(copy)
582
- const blob = new Blob([svgString], {
583
- type: 'image/svg+xml;charset=utf-8',
584
- })
585
- const url = URL.createObjectURL(blob)
586
- this.triggerDownload(url)
587
- } else {
588
- this.getCanvasData(svg, toImageOptions ?? {}).then(
589
- (canvas: HTMLCanvasElement) => {
590
- // canvas元素 => base64 url image/octet-stream: 确保所有浏览器都能正常下载
591
- const imgUrl = canvas
592
- .toDataURL(`image/${fileType}`, quality)
593
- .replace(`image/${fileType}`, 'image/octet-stream')
594
- this.triggerDownload(imgUrl)
595
- },
596
- )
597
- }
598
- }
599
-
600
- /**
601
- * 获取Blob对象
602
- * @param fileType
603
- * @param toImageOptions
604
- * @returns
605
- */
606
- async getSnapshotBlob(
607
- backgroundColor?: string,
608
- fileType?: string,
609
- toImageOptions?: ToImageOptions,
610
- ): Promise<SnapshotResponse> {
611
- return await this.withExportPreparation(
612
- () => this.snapshotBlob(toImageOptions, fileType, backgroundColor),
613
- toImageOptions,
614
- )
615
- }
616
-
617
- // 内部方法处理blob转换
618
- private async snapshotBlob(
619
- toImageOptions?: ToImageOptions,
620
- baseFileType?: string,
621
- backgroundColor?: string,
622
- ): Promise<SnapshotResponse> {
623
- const { fileType = baseFileType } = toImageOptions ?? {}
624
- const svg = this.getSvgRootElement(this.lf)
625
- await updateImageSource(svg as SVGElement)
626
- if (fileType === 'svg') {
627
- const copy = await this.cloneSvg(svg)
628
- const svgString = new XMLSerializer().serializeToString(copy)
629
- const blob = new Blob([svgString], {
630
- type: 'image/svg+xml;charset=utf-8',
631
- })
632
- return {
633
- data: blob,
634
- width: 0,
635
- height: 0,
636
- }
637
- }
638
- return new Promise((resolve) => {
639
- this.getCanvasData(svg, {
640
- backgroundColor,
641
- ...(toImageOptions ?? {}),
642
- }).then((canvas: HTMLCanvasElement) => {
643
- canvas.toBlob(
644
- (blob) => {
645
- // 输出图片数据以及图片宽高
646
- resolve({
647
- data: blob!,
648
- width: canvas.width,
649
- height: canvas.height,
650
- })
651
- },
652
- `image/${fileType ?? 'png'}`,
653
- )
654
- })
655
- })
656
- }
657
-
658
- /**
659
- * 获取base64对象
660
- * @param backgroundColor
661
- * @param fileType
662
- * @param toImageOptions
663
- * @returns
664
- */
665
- async getSnapshotBase64(
666
- backgroundColor?: string,
667
- fileType?: string,
668
- toImageOptions?: ToImageOptions,
669
- ): Promise<SnapshotResponse> {
670
- return await this.withExportPreparation(
671
- () => this._getSnapshotBase64(backgroundColor, fileType, toImageOptions),
672
- toImageOptions,
673
- )
674
- }
675
-
676
- // 内部方法处理实际的base64转换
677
- private async _getSnapshotBase64(
678
- backgroundColor?: string,
679
- baseFileType?: string,
680
- toImageOptions?: ToImageOptions,
681
- ): Promise<SnapshotResponse> {
682
- const { fileType = baseFileType } = toImageOptions ?? {}
683
- const svg = this.getSvgRootElement(this.lf)
684
- await updateImageSource(svg as SVGElement)
685
- return new Promise((resolve) => {
686
- this.getCanvasData(svg, {
687
- backgroundColor,
688
- ...(toImageOptions ?? {}),
689
- }).then((canvas: HTMLCanvasElement) => {
690
- const base64 = canvas.toDataURL(`image/${fileType ?? 'png'}`)
691
- resolve({
692
- data: base64,
693
- width: canvas.width,
694
- height: canvas.height,
695
- })
696
- })
697
- })
698
- }
699
- }
700
-
701
- export default Snapshot