@logicflow/extension 2.2.0 → 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 (201) hide show
  1. package/README.md +16 -0
  2. package/dist/index.css +1 -1
  3. package/dist/index.min.js +1 -1
  4. package/dist/index.min.js.map +1 -1
  5. package/es/NodeResize/node/RectResize.d.ts +0 -6
  6. package/es/NodeResize/node/RectResize.js +2 -11
  7. package/es/bpmn-elements-adapter/json2xml.d.ts +2 -1
  8. package/es/bpmn-elements-adapter/json2xml.js +18 -4
  9. package/es/bpmn-elements-adapter/xml2json.js +2 -7
  10. package/es/components/control/index.d.ts +1 -0
  11. package/es/components/control/index.js +24 -11
  12. package/es/components/mini-map/index.js +1 -1
  13. package/es/dynamic-group/model.d.ts +2 -1
  14. package/es/dynamic-group/model.js +28 -11
  15. package/es/dynamic-group/node.js +1 -0
  16. package/es/index.css +1 -1
  17. package/es/index.d.ts +1 -0
  18. package/es/index.js +2 -0
  19. package/es/insert-node-in-polyline/index.js +11 -35
  20. package/es/materials/curved-edge/index.js +49 -21
  21. package/es/materials/group/GroupNode.d.ts +0 -6
  22. package/es/materials/group/GroupNode.js +1 -6
  23. package/es/pool/LaneModel.d.ts +90 -0
  24. package/es/pool/LaneModel.js +252 -0
  25. package/es/pool/LaneView.d.ts +40 -0
  26. package/es/pool/LaneView.js +202 -0
  27. package/es/pool/PoolModel.d.ts +120 -0
  28. package/es/pool/PoolModel.js +586 -0
  29. package/es/pool/PoolView.d.ts +17 -0
  30. package/es/pool/PoolView.js +76 -0
  31. package/es/pool/constant.d.ts +15 -0
  32. package/es/pool/constant.js +17 -0
  33. package/es/pool/index.d.ts +89 -0
  34. package/es/pool/index.js +524 -0
  35. package/es/pool/utils.d.ts +19 -0
  36. package/es/pool/utils.js +33 -0
  37. package/es/style/index.css +1 -1
  38. package/es/style/raw.d.ts +1 -1
  39. package/es/style/raw.js +1 -1
  40. package/es/tools/label/LabelOverlay.js +3 -3
  41. package/es/tools/label/mediumEditor.d.ts +1 -1
  42. package/es/tools/label/mediumEditor.js +89 -52
  43. package/es/tools/snapshot/index.d.ts +7 -3
  44. package/es/tools/snapshot/index.js +72 -77
  45. package/lib/NodeResize/node/RectResize.d.ts +0 -6
  46. package/lib/NodeResize/node/RectResize.js +1 -10
  47. package/lib/bpmn-elements-adapter/json2xml.d.ts +2 -1
  48. package/lib/bpmn-elements-adapter/json2xml.js +19 -4
  49. package/lib/bpmn-elements-adapter/xml2json.js +2 -7
  50. package/lib/components/control/index.d.ts +1 -0
  51. package/lib/components/control/index.js +24 -11
  52. package/lib/components/mini-map/index.js +1 -1
  53. package/lib/dynamic-group/model.d.ts +2 -1
  54. package/lib/dynamic-group/model.js +28 -11
  55. package/lib/dynamic-group/node.js +1 -0
  56. package/lib/index.css +1 -1
  57. package/lib/index.d.ts +1 -0
  58. package/lib/index.js +2 -0
  59. package/lib/insert-node-in-polyline/index.js +10 -34
  60. package/lib/materials/curved-edge/index.js +49 -21
  61. package/lib/materials/group/GroupNode.d.ts +0 -6
  62. package/lib/materials/group/GroupNode.js +1 -6
  63. package/lib/pool/LaneModel.d.ts +90 -0
  64. package/lib/pool/LaneModel.js +255 -0
  65. package/lib/pool/LaneView.d.ts +40 -0
  66. package/lib/pool/LaneView.js +205 -0
  67. package/lib/pool/PoolModel.d.ts +120 -0
  68. package/lib/pool/PoolModel.js +589 -0
  69. package/lib/pool/PoolView.d.ts +17 -0
  70. package/lib/pool/PoolView.js +79 -0
  71. package/lib/pool/constant.d.ts +15 -0
  72. package/lib/pool/constant.js +20 -0
  73. package/lib/pool/index.d.ts +89 -0
  74. package/lib/pool/index.js +527 -0
  75. package/lib/pool/utils.d.ts +19 -0
  76. package/lib/pool/utils.js +38 -0
  77. package/lib/style/index.css +1 -1
  78. package/lib/style/raw.d.ts +1 -1
  79. package/lib/style/raw.js +1 -1
  80. package/lib/tools/label/LabelOverlay.js +2 -2
  81. package/lib/tools/label/mediumEditor.d.ts +1 -1
  82. package/lib/tools/label/mediumEditor.js +91 -53
  83. package/lib/tools/snapshot/index.d.ts +7 -3
  84. package/lib/tools/snapshot/index.js +72 -77
  85. package/package.json +10 -7
  86. package/.turbo/turbo-build.log +0 -38
  87. package/CHANGELOG.md +0 -1766
  88. package/__test__/bpmn-adapter.test.js +0 -227
  89. package/es/materials/curved-edge/__test__/curved-edge.test.d.ts +0 -1
  90. package/es/materials/curved-edge/__test__/curved-edge.test.js +0 -18
  91. package/jest.config.js +0 -198
  92. package/lib/materials/curved-edge/__test__/curved-edge.test.d.ts +0 -1
  93. package/lib/materials/curved-edge/__test__/curved-edge.test.js +0 -20
  94. package/rollup.config.js +0 -52
  95. package/src/NodeResize/BasicShape/Ellipse.tsx +0 -22
  96. package/src/NodeResize/BasicShape/Polygon.tsx +0 -24
  97. package/src/NodeResize/BasicShape/Rect.tsx +0 -44
  98. package/src/NodeResize/control/Control.tsx +0 -537
  99. package/src/NodeResize/control/ControlGroup.tsx +0 -76
  100. package/src/NodeResize/control/Util.ts +0 -206
  101. package/src/NodeResize/index.ts +0 -26
  102. package/src/NodeResize/node/DiamondResize.tsx +0 -149
  103. package/src/NodeResize/node/EllipseResize.tsx +0 -140
  104. package/src/NodeResize/node/HtmlResize.tsx +0 -125
  105. package/src/NodeResize/node/RectResize.tsx +0 -146
  106. package/src/NodeResize/node/index.ts +0 -4
  107. package/src/bpmn/constant.ts +0 -56
  108. package/src/bpmn/events/EndEvent.ts +0 -73
  109. package/src/bpmn/events/StartEvent.ts +0 -52
  110. package/src/bpmn/events/index.ts +0 -2
  111. package/src/bpmn/flow/SequenceFlow.ts +0 -25
  112. package/src/bpmn/flow/index.ts +0 -1
  113. package/src/bpmn/gateways/ExclusiveGateway.ts +0 -71
  114. package/src/bpmn/gateways/index.ts +0 -1
  115. package/src/bpmn/getBpmnId.ts +0 -31
  116. package/src/bpmn/index.ts +0 -60
  117. package/src/bpmn/tasks/ServiceTask.ts +0 -63
  118. package/src/bpmn/tasks/UserTask.ts +0 -64
  119. package/src/bpmn/tasks/index.ts +0 -2
  120. package/src/bpmn-adapter/bpmnIds.ts +0 -31
  121. package/src/bpmn-adapter/index.ts +0 -835
  122. package/src/bpmn-adapter/json2xml.ts +0 -127
  123. package/src/bpmn-adapter/xml2json.ts +0 -544
  124. package/src/bpmn-elements/README.md +0 -223
  125. package/src/bpmn-elements/__tests__/definition.test.js +0 -72
  126. package/src/bpmn-elements/index.d.ts +0 -26
  127. package/src/bpmn-elements/index.ts +0 -107
  128. package/src/bpmn-elements/presets/Event/EndEventFactory.ts +0 -114
  129. package/src/bpmn-elements/presets/Event/IntermediateCatchEvent.ts +0 -108
  130. package/src/bpmn-elements/presets/Event/IntermediateThrowEvent.ts +0 -109
  131. package/src/bpmn-elements/presets/Event/StartEventFactory.ts +0 -114
  132. package/src/bpmn-elements/presets/Event/boundaryEventFactory.ts +0 -117
  133. package/src/bpmn-elements/presets/Event/index.ts +0 -14
  134. package/src/bpmn-elements/presets/Flow/flow.d.ts +0 -6
  135. package/src/bpmn-elements/presets/Flow/index.ts +0 -8
  136. package/src/bpmn-elements/presets/Flow/manhattan.ts +0 -691
  137. package/src/bpmn-elements/presets/Flow/sequenceFlow.ts +0 -65
  138. package/src/bpmn-elements/presets/Gateway/gateway.ts +0 -107
  139. package/src/bpmn-elements/presets/Gateway/index.ts +0 -23
  140. package/src/bpmn-elements/presets/Pool/Lane.ts +0 -211
  141. package/src/bpmn-elements/presets/Pool/Pool.ts +0 -284
  142. package/src/bpmn-elements/presets/Pool/index.ts +0 -89
  143. package/src/bpmn-elements/presets/Task/index.ts +0 -122
  144. package/src/bpmn-elements/presets/Task/subProcess.ts +0 -189
  145. package/src/bpmn-elements/presets/Task/task.ts +0 -193
  146. package/src/bpmn-elements/presets/icons.ts +0 -155
  147. package/src/bpmn-elements/utils.ts +0 -52
  148. package/src/bpmn-elements-adapter/README.md +0 -293
  149. package/src/bpmn-elements-adapter/__tests__/adapter_in.test.js +0 -528
  150. package/src/bpmn-elements-adapter/__tests__/adapter_out.test.js +0 -569
  151. package/src/bpmn-elements-adapter/constant.ts +0 -76
  152. package/src/bpmn-elements-adapter/index.ts +0 -1134
  153. package/src/bpmn-elements-adapter/json2xml.ts +0 -91
  154. package/src/bpmn-elements-adapter/xml2json.ts +0 -548
  155. package/src/components/context-menu/index.ts +0 -253
  156. package/src/components/control/index.ts +0 -141
  157. package/src/components/dnd-panel/index.ts +0 -137
  158. package/src/components/highlight/index.ts +0 -227
  159. package/src/components/menu/index.ts +0 -748
  160. package/src/components/mini-map/index.ts +0 -686
  161. package/src/components/selection-select/index.ts +0 -387
  162. package/src/dynamic-group/index.ts +0 -775
  163. package/src/dynamic-group/model.ts +0 -562
  164. package/src/dynamic-group/node.ts +0 -288
  165. package/src/dynamic-group/utils.ts +0 -46
  166. package/src/index.less +0 -1
  167. package/src/index.ts +0 -45
  168. package/src/insert-node-in-polyline/edge.ts +0 -175
  169. package/src/insert-node-in-polyline/index.ts +0 -187
  170. package/src/materials/curved-edge/__test__/curved-edge.test.ts +0 -46
  171. package/src/materials/curved-edge/index.ts +0 -185
  172. package/src/materials/group/GroupNode.ts +0 -442
  173. package/src/materials/group/index.ts +0 -542
  174. package/src/materials/node-selection/index.ts +0 -380
  175. package/src/mindmap/fakerRoot.ts +0 -19
  176. package/src/mindmap/index.ts +0 -328
  177. package/src/mindmap/markContent.ts +0 -81
  178. package/src/mindmap/markContentOption.ts +0 -81
  179. package/src/mindmap/markEntity.ts +0 -82
  180. package/src/mindmap/markRoot.ts +0 -83
  181. package/src/mindmap/theme.ts +0 -11
  182. package/src/rect-label-node/RectLabelNodeView.ts +0 -33
  183. package/src/rect-label-node/index.ts +0 -15
  184. package/src/style/index.less +0 -342
  185. package/src/style/raw.ts +0 -295
  186. package/src/tools/auto-layout/index.ts +0 -282
  187. package/src/tools/flow-path/index.ts +0 -233
  188. package/src/tools/label/Label.tsx +0 -357
  189. package/src/tools/label/LabelModel.ts +0 -83
  190. package/src/tools/label/LabelOverlay.tsx +0 -158
  191. package/src/tools/label/algorithm.ts +0 -42
  192. package/src/tools/label/index.ts +0 -479
  193. package/src/tools/label/mediumEditor.ts +0 -94
  194. package/src/tools/label/utils.ts +0 -395
  195. package/src/tools/proximity-connect/index.ts +0 -435
  196. package/src/tools/snapshot/README.md +0 -145
  197. package/src/tools/snapshot/index.ts +0 -718
  198. package/src/tools/snapshot/utils.ts +0 -163
  199. package/src/turbo-adapter/index.ts +0 -212
  200. package/stats.html +0 -4842
  201. package/tsconfig.json +0 -18
@@ -1,718 +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
- * @returns 包含最大主维度和次维度的对象
320
- */
321
- private getCanvasDimensionsByBrowser(): {
322
- maxCanvasDimension: number
323
- otherMaxCanvasDimension: number
324
- } {
325
- const userAgent = navigator.userAgent
326
-
327
- // 默认值
328
- let maxCanvasDimension = 65535
329
- let otherMaxCanvasDimension = 4096
330
-
331
- if (
332
- userAgent.indexOf('Chrome') !== -1 ||
333
- userAgent.indexOf('Edge') !== -1
334
- ) {
335
- maxCanvasDimension = 65535
336
- otherMaxCanvasDimension = 4096
337
- } else if (userAgent.indexOf('Firefox') !== -1) {
338
- maxCanvasDimension = 32767
339
- otherMaxCanvasDimension = 3814
340
- }
341
-
342
- return { maxCanvasDimension, otherMaxCanvasDimension }
343
- }
344
-
345
- /**
346
- * 将 svg 转化为 canvas
347
- * @param svg - svg 元素
348
- * @param toImageOptions - 图像选项
349
- * @returns Promise<canvas> - 返回 canvas 对象
350
- */
351
- private async getCanvasData(
352
- svg: Element,
353
- toImageOptions: ToImageOptions,
354
- ): Promise<HTMLCanvasElement> {
355
- const { width, height, backgroundColor, padding = 40 } = toImageOptions
356
- const copy = await this.cloneSvg(svg, false)
357
-
358
- let dpr = window.devicePixelRatio || 1
359
- if (dpr < 1) {
360
- // https://github.com/didi/LogicFlow/issues/1222
361
- // canvas.width = bboxWidth * dpr配合ctx.scale(dpr, dpr)是为了解决绘制模糊
362
- // 比如dpr=2,先让canvas.width放大到等同于屏幕的物理像素宽高,然后自适应缩放适配canvas.style.width
363
- // 由于所有元素都缩放了一半,因此需要ctx.scale(dpr, dpr)放大2倍整体绘制的内容
364
- // 当用户缩放浏览器时,window.devicePixelRatio会随着变小
365
- // 当window.devicePixelRatio变小到一定程度,会导致canvas.width<canvas.style.width
366
- // 由于导出图片的svg的大小是canvas.style.width+canvas.style.height
367
- // 因此会导致导出的svg图片无法完整绘制到canvas(因为canvas.width小于svg的宽)
368
- // 从而导致canvas导出图片是缺失的svg
369
- // 而dpr>=1就能保证canvas.width>=canvas.style.width
370
- // 当dpr小于1的时候,我们强制转化为1,并不会产生绘制模糊等问题
371
- dpr = 1
372
- }
373
- /*
374
- 为了计算真实宽高需要取图的真实dom
375
- 真实dom存在缩放影响其宽高数值
376
- 在得到真实宽高后除以缩放比例即可得到正常宽高
377
- */
378
-
379
- const base = this.lf.graphModel.rootEl.querySelector('.lf-base')
380
- const bbox = (base as Element).getBoundingClientRect()
381
- const layoutCanvas = this.lf.container.querySelector('.lf-canvas-overlay')!
382
- const layout = layoutCanvas.getBoundingClientRect()
383
- const offsetX = bbox.x - layout.x
384
- const offsetY = bbox.y - layout.y
385
- const { graphModel } = this.lf
386
- const { transformModel } = graphModel
387
- const { SCALE_X, SCALE_Y, TRANSLATE_X, TRANSLATE_Y } = transformModel
388
-
389
- // 计算实际宽高,考虑缩放因素
390
- // 在宽画布情况下,getBoundingClientRect可能无法获取到所有元素的边界
391
- // 因此我们添加一个安全系数来确保能够容纳所有元素
392
- const safetyFactor = toImageOptions.safetyFactor || 1.1 // 安全系数,增加10%的空间
393
- const actualWidth = (bbox.width / SCALE_X) * safetyFactor
394
- const actualHeight = (bbox.height / SCALE_Y) * safetyFactor
395
-
396
- // 包含所有元素的最小宽高,确保足够大以容纳所有元素
397
- const bboxWidth = Math.ceil(actualWidth)
398
- const bboxHeight = Math.ceil(actualHeight)
399
- const canvas = document.createElement('canvas')
400
- canvas.style.width = `${bboxWidth}px`
401
- canvas.style.height = `${bboxHeight}px`
402
-
403
- // 宽高值 默认加padding 40,保证图形不会紧贴着下载图片
404
- // 为宽画布添加额外的安全边距,确保不会裁剪
405
- const safetyMargin = toImageOptions.safetyMargin || 40 // 额外的安全边距
406
-
407
- // 获取当前浏览器类型,不同浏览器对canvas的限制不同
408
- const { maxCanvasDimension, otherMaxCanvasDimension } =
409
- this.getCanvasDimensionsByBrowser()
410
- const MAX_CANVAS_DIMENSION = maxCanvasDimension
411
- const OTHER_MAX_CANVAS_DIMENSION = otherMaxCanvasDimension
412
-
413
- let targetWidth = bboxWidth * dpr + padding * 2 + safetyMargin
414
- let targetHeight = bboxHeight * dpr + padding * 2 + safetyMargin
415
- let scaleWidth = 1 //宽 缩放
416
- let scaleHeight = 1 //高 缩放
417
- // 对宽和高分别进行缩放,如chrome,矩形单边最大宽度不超过65535,如宽超过65535,那么高不能超过4096,否则像素会超,也会显示不出。
418
- if (
419
- targetWidth > MAX_CANVAS_DIMENSION &&
420
- targetHeight > OTHER_MAX_CANVAS_DIMENSION
421
- ) {
422
- scaleWidth = MAX_CANVAS_DIMENSION / targetWidth
423
- scaleHeight = OTHER_MAX_CANVAS_DIMENSION / targetHeight
424
- } else if (
425
- targetWidth > OTHER_MAX_CANVAS_DIMENSION &&
426
- targetHeight > MAX_CANVAS_DIMENSION
427
- ) {
428
- scaleWidth = OTHER_MAX_CANVAS_DIMENSION / targetWidth
429
- scaleHeight = MAX_CANVAS_DIMENSION / targetHeight
430
- } else if (
431
- targetWidth > MAX_CANVAS_DIMENSION &&
432
- targetHeight < OTHER_MAX_CANVAS_DIMENSION
433
- ) {
434
- scaleHeight = scaleWidth = MAX_CANVAS_DIMENSION / targetWidth
435
- } else if (
436
- targetWidth < OTHER_MAX_CANVAS_DIMENSION &&
437
- targetHeight > MAX_CANVAS_DIMENSION
438
- ) {
439
- scaleHeight = scaleWidth = MAX_CANVAS_DIMENSION / targetHeight
440
- }
441
-
442
- if (scaleWidth < 1 || scaleHeight < 1) {
443
- targetWidth = Math.floor(targetWidth * scaleWidth)
444
- targetHeight = Math.floor(targetHeight * scaleHeight)
445
- }
446
- // 将导出区域移动到左上角,canvas 绘制的时候是从左上角开始绘制的
447
- // 在transform矩阵中加入padding值,确保左侧元素不会被截断
448
- // 对这个矩阵进行缩放,否则会导致截断
449
- ;(copy.lastChild as SVGElement).style.transform =
450
- `matrix(${scaleWidth}, 0, 0, ${scaleHeight}, ${
451
- (-offsetX + TRANSLATE_X) * (1 / SCALE_X) * scaleWidth + padding / dpr
452
- }, ${
453
- (-offsetY + TRANSLATE_Y) * (1 / SCALE_Y) * scaleHeight + padding / dpr
454
- })`
455
- canvas.width = targetWidth
456
- canvas.height = targetHeight
457
- const ctx = canvas.getContext('2d')
458
- if (ctx) {
459
- // 清空canvas
460
- ctx.clearRect(0, 0, canvas.width, canvas.height)
461
- ctx.scale(dpr * scaleWidth, dpr * scaleHeight)
462
- // 如果有背景色,设置流程图导出的背景色
463
- if (backgroundColor) {
464
- ctx.fillStyle = backgroundColor
465
- ctx.fillRect(0, 0, canvas.width, canvas.height)
466
- } else {
467
- ctx.clearRect(0, 0, canvas.width, canvas.height)
468
- }
469
- }
470
-
471
- const img = new Image()
472
-
473
- // 注入 css 样式
474
- const style = document.createElement('style')
475
- style.innerHTML = this.getClassRules()
476
- const foreignObject = document.createElement('foreignObject')
477
- foreignObject.appendChild(style)
478
- copy.appendChild(foreignObject)
479
- return new Promise((resolve) => {
480
- img.onload = () => {
481
- const isFirefox = navigator.userAgent.indexOf('Firefox') > -1
482
- try {
483
- if (isFirefox) {
484
- createImageBitmap(img, {
485
- resizeWidth:
486
- width && height
487
- ? copyCanvas(canvas, width, height).width
488
- : canvas.width,
489
- resizeHeight:
490
- width && height
491
- ? copyCanvas(canvas, width, height).height
492
- : canvas.height,
493
- }).then((imageBitmap) => {
494
- // 由于在transform矩阵中已经考虑了padding,这里不再需要额外的padding偏移
495
- ctx?.drawImage(imageBitmap, 0, 0)
496
- resolve(
497
- width && height ? copyCanvas(canvas, width, height) : canvas,
498
- )
499
- })
500
- } else {
501
- // 由于在transform矩阵中已经考虑了padding,这里不再需要额外的padding偏移
502
- ctx?.drawImage(img, 0, 0)
503
- resolve(
504
- width && height ? copyCanvas(canvas, width, height) : canvas,
505
- )
506
- }
507
- } catch (e) {
508
- // 由于在transform矩阵中已经考虑了padding,这里不再需要额外的padding偏移
509
- ctx?.drawImage(img, 0, 0)
510
- resolve(width && height ? copyCanvas(canvas, width, height) : canvas)
511
- }
512
- }
513
-
514
- /*
515
- 因为svg中存在dom存放在foreignObject元素中
516
- svg dom => Base64编码字符串 挂载到img上
517
- fixme: XMLSerializer的中的css background url不会下载图片
518
- */
519
- const svg2Img = `data:image/svg+xml;charset=utf-8,${new XMLSerializer().serializeToString(
520
- copy,
521
- )}`
522
- const imgSrc = svg2Img
523
- .replace(/\n/g, '')
524
- .replace(/\t/g, '')
525
- .replace(/#/g, '%23')
526
- img.src = imgSrc
527
- })
528
- }
529
-
530
- /**
531
- * 封装导出前的通用处理逻辑:局部渲染模式处理、静默模式处理
532
- * @param callback 实际执行的导出操作回调函数
533
- * @param toImageOptions 导出图片选项
534
- * @returns 返回回调函数的执行结果
535
- */
536
- private async withExportPreparation<T>(
537
- callback: () => Promise<T>,
538
- toImageOptions?: ToImageOptions,
539
- ): Promise<T> {
540
- // 获取当前局部渲染状态
541
- const curPartial = this.lf.graphModel.getPartial()
542
- const { partial = curPartial } = toImageOptions ?? {}
543
- // 获取流程图配置
544
- const editConfig = this.lf.getEditConfig()
545
-
546
- // 开启静默模式:如果元素多的话 避免用户交互 感知卡顿
547
- this.lf.updateEditConfig({
548
- isSilentMode: true,
549
- stopScrollGraph: true,
550
- stopMoveGraph: true,
551
- })
552
-
553
- let result: T
554
-
555
- try {
556
- // 如果画布的渲染模式与导出渲染模式不一致,则切换渲染模式
557
- if (curPartial !== partial) {
558
- this.lf.graphModel.setPartial(partial)
559
- // 等待画布更新完成
560
- result = await new Promise<T>((resolve) => {
561
- this.lf.graphModel.eventCenter.once('graph:updated', async () => {
562
- const callbackResult = await callback()
563
- // 恢复原来渲染模式
564
- this.lf.graphModel.setPartial(curPartial)
565
- resolve(callbackResult)
566
- })
567
- })
568
- } else {
569
- // 直接执行回调
570
- result = await callback()
571
- }
572
- } finally {
573
- // 恢复原来配置
574
- this.lf.updateEditConfig(editConfig)
575
- }
576
-
577
- return result
578
- }
579
-
580
- /**
581
- * 导出画布:导出前的处理画布工作,局部渲染模式处理、静默模式处理
582
- * @param fileName
583
- * @param toImageOptions
584
- */
585
- async getSnapshot(fileName?: string, toImageOptions?: ToImageOptions) {
586
- await this.withExportPreparation(
587
- () => this.snapshot(fileName, toImageOptions),
588
- toImageOptions,
589
- )
590
- }
591
-
592
- /**
593
- * 下载图片
594
- * @param fileName
595
- * @param toImageOptions
596
- */
597
- private async snapshot(fileName?: string, toImageOptions?: ToImageOptions) {
598
- const { fileType = 'png', quality } = toImageOptions ?? {}
599
- this.fileName = `${fileName ?? `logic-flow.${Date.now()}`}.${fileType}`
600
- const svg = this.getSvgRootElement(this.lf)
601
- await updateImageSource(svg as SVGElement)
602
- if (fileType === 'svg') {
603
- const copy = await this.cloneSvg(svg)
604
- const svgString = new XMLSerializer().serializeToString(copy)
605
- const blob = new Blob([svgString], {
606
- type: 'image/svg+xml;charset=utf-8',
607
- })
608
- const url = URL.createObjectURL(blob)
609
- this.triggerDownload(url)
610
- } else {
611
- this.getCanvasData(svg, toImageOptions ?? {}).then(
612
- (canvas: HTMLCanvasElement) => {
613
- // canvas元素 => base64 url image/octet-stream: 确保所有浏览器都能正常下载
614
- const imgUrl = canvas
615
- .toDataURL(`image/${fileType}`, quality)
616
- .replace(`image/${fileType}`, 'image/octet-stream')
617
- this.triggerDownload(imgUrl)
618
- },
619
- )
620
- }
621
- }
622
-
623
- /**
624
- * 获取Blob对象
625
- * @param fileType
626
- * @param toImageOptions
627
- * @returns
628
- */
629
- async getSnapshotBlob(
630
- backgroundColor?: string,
631
- fileType?: string,
632
- toImageOptions?: ToImageOptions,
633
- ): Promise<SnapshotResponse> {
634
- return await this.withExportPreparation(
635
- () => this.snapshotBlob(toImageOptions, fileType, backgroundColor),
636
- toImageOptions,
637
- )
638
- }
639
-
640
- // 内部方法处理blob转换
641
- private async snapshotBlob(
642
- toImageOptions?: ToImageOptions,
643
- baseFileType?: string,
644
- backgroundColor?: string,
645
- ): Promise<SnapshotResponse> {
646
- const { fileType = baseFileType } = toImageOptions ?? {}
647
- const svg = this.getSvgRootElement(this.lf)
648
- await updateImageSource(svg as SVGElement)
649
- return new Promise((resolve) => {
650
- this.getCanvasData(svg, {
651
- backgroundColor,
652
- ...(toImageOptions ?? {}),
653
- }).then((canvas: HTMLCanvasElement) => {
654
- canvas.toBlob(
655
- (blob) => {
656
- // 输出图片数据以及图片宽高
657
- resolve({
658
- data: blob!,
659
- width: canvas.width,
660
- height: canvas.height,
661
- })
662
- },
663
- `image/${fileType ?? 'png'}`,
664
- )
665
- })
666
- })
667
- }
668
-
669
- /**
670
- * 获取base64对象
671
- * @param backgroundColor
672
- * @param fileType
673
- * @param toImageOptions
674
- * @returns
675
- */
676
- async getSnapshotBase64(
677
- backgroundColor?: string,
678
- fileType?: string,
679
- toImageOptions?: ToImageOptions,
680
- ): Promise<SnapshotResponse> {
681
- console.log(
682
- 'getSnapshotBase64---------------',
683
- backgroundColor,
684
- fileType,
685
- toImageOptions,
686
- )
687
- return await this.withExportPreparation(
688
- () => this._getSnapshotBase64(backgroundColor, fileType, toImageOptions),
689
- toImageOptions,
690
- )
691
- }
692
-
693
- // 内部方法处理实际的base64转换
694
- private async _getSnapshotBase64(
695
- backgroundColor?: string,
696
- baseFileType?: string,
697
- toImageOptions?: ToImageOptions,
698
- ): Promise<SnapshotResponse> {
699
- const { fileType = baseFileType } = toImageOptions ?? {}
700
- const svg = this.getSvgRootElement(this.lf)
701
- await updateImageSource(svg as SVGElement)
702
- return new Promise((resolve) => {
703
- this.getCanvasData(svg, {
704
- backgroundColor,
705
- ...(toImageOptions ?? {}),
706
- }).then((canvas: HTMLCanvasElement) => {
707
- const base64 = canvas.toDataURL(`image/${fileType ?? 'png'}`)
708
- resolve({
709
- data: base64,
710
- width: canvas.width,
711
- height: canvas.height,
712
- })
713
- })
714
- })
715
- }
716
- }
717
-
718
- export default Snapshot