@logicflow/extension 2.0.0-beta.0 → 2.0.0-beta.10
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 +146 -491
- package/dist/index.css +63 -0
- package/dist/index.min.js +42 -0
- package/dist/index.min.js.map +1 -0
- package/es/NodeResize/control/Control.d.ts +3 -3
- package/es/NodeResize/control/Control.js +13 -3
- package/es/NodeResize/control/Control.js.map +1 -1
- package/es/NodeResize/index.d.ts +4 -0
- package/es/NodeResize/index.js.map +1 -1
- package/es/NodeResize/node/DiamondResize.d.ts +1 -0
- package/es/NodeResize/node/DiamondResize.js +2 -1
- package/es/NodeResize/node/DiamondResize.js.map +1 -1
- package/es/NodeResize/node/EllipseResize.d.ts +1 -0
- package/es/NodeResize/node/EllipseResize.js +2 -1
- package/es/NodeResize/node/EllipseResize.js.map +1 -1
- package/es/NodeResize/node/HtmlResize.d.ts +1 -0
- package/es/NodeResize/node/HtmlResize.js +2 -1
- package/es/NodeResize/node/HtmlResize.js.map +1 -1
- package/es/bpmn/constant.d.ts +1 -1
- package/es/bpmn/constant.js +3 -0
- package/es/bpmn/constant.js.map +1 -1
- package/es/bpmn/index.d.ts +3 -6
- package/es/bpmn/index.js +5 -7
- package/es/bpmn/index.js.map +1 -1
- package/es/bpmn-elements/presets/Pool/Pool.d.ts +21 -1
- package/es/components/control/index.d.ts +4 -4
- package/es/components/control/index.js.map +1 -1
- package/es/components/highlight/index.d.ts +6 -4
- package/es/components/highlight/index.js +32 -5
- package/es/components/highlight/index.js.map +1 -1
- package/es/components/menu/index.d.ts +1 -1
- package/es/components/menu/index.js +9 -10
- package/es/components/menu/index.js.map +1 -1
- package/es/components/mini-map/index.d.ts +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.d.ts +1 -1
- package/es/components/selection-select/index.js.map +1 -1
- package/es/dynamic-group/index.d.ts +106 -0
- package/es/dynamic-group/index.js +536 -0
- package/es/dynamic-group/index.js.map +1 -0
- package/es/dynamic-group/model.d.ts +135 -0
- package/es/dynamic-group/model.js +413 -0
- package/es/dynamic-group/model.js.map +1 -0
- package/es/dynamic-group/node.d.ts +16 -0
- package/es/dynamic-group/node.js +143 -0
- package/es/dynamic-group/node.js.map +1 -0
- package/es/dynamic-group/utils.d.ts +17 -0
- package/es/dynamic-group/utils.js +27 -0
- package/es/dynamic-group/utils.js.map +1 -0
- package/es/index.css +63 -0
- package/es/index.d.ts +16 -8
- package/es/index.js +24 -8
- package/es/index.js.map +1 -1
- package/es/insert-node-in-polyline/index.js +3 -3
- package/es/insert-node-in-polyline/index.js.map +1 -1
- package/es/materials/group/GroupNode.d.ts +6 -10
- package/es/materials/group/GroupNode.js +8 -6
- package/es/materials/group/GroupNode.js.map +1 -1
- package/es/materials/group/index.d.ts +5 -5
- package/es/materials/group/index.js +25 -26
- package/es/materials/group/index.js.map +1 -1
- package/es/materials/node-selection/index.d.ts +6 -1
- package/es/materials/node-selection/index.js +64 -56
- package/es/materials/node-selection/index.js.map +1 -1
- package/es/mindmap/index.d.ts +2 -2
- 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 +17 -0
- package/es/tools/label/mediumEditor.js +92 -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 +107 -11
- package/es/tools/snapshot/index.js +366 -149
- package/es/tools/snapshot/index.js.map +1 -1
- package/es/tools/snapshot/utils.d.ts +35 -0
- package/es/tools/snapshot/utils.js +238 -0
- package/es/tools/snapshot/utils.js.map +1 -0
- package/lib/NodeResize/control/Control.d.ts +3 -3
- package/lib/NodeResize/control/Control.js +13 -3
- package/lib/NodeResize/control/Control.js.map +1 -1
- package/lib/NodeResize/index.d.ts +4 -0
- package/lib/NodeResize/index.js.map +1 -1
- package/lib/NodeResize/node/DiamondResize.d.ts +1 -0
- package/lib/NodeResize/node/DiamondResize.js +2 -1
- package/lib/NodeResize/node/DiamondResize.js.map +1 -1
- package/lib/NodeResize/node/EllipseResize.d.ts +1 -0
- package/lib/NodeResize/node/EllipseResize.js +2 -1
- package/lib/NodeResize/node/EllipseResize.js.map +1 -1
- package/lib/NodeResize/node/HtmlResize.d.ts +1 -0
- package/lib/NodeResize/node/HtmlResize.js +2 -1
- package/lib/NodeResize/node/HtmlResize.js.map +1 -1
- package/lib/bpmn/constant.d.ts +1 -1
- package/lib/bpmn/constant.js +3 -0
- package/lib/bpmn/constant.js.map +1 -1
- package/lib/bpmn/index.d.ts +3 -6
- package/lib/bpmn/index.js +5 -7
- package/lib/bpmn/index.js.map +1 -1
- package/lib/bpmn-elements/presets/Pool/Pool.d.ts +21 -1
- package/lib/components/control/index.d.ts +4 -4
- package/lib/components/control/index.js.map +1 -1
- package/lib/components/highlight/index.d.ts +6 -4
- package/lib/components/highlight/index.js +32 -5
- package/lib/components/highlight/index.js.map +1 -1
- package/lib/components/menu/index.d.ts +1 -1
- package/lib/components/menu/index.js +9 -10
- package/lib/components/menu/index.js.map +1 -1
- package/lib/components/mini-map/index.d.ts +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.d.ts +1 -1
- package/lib/components/selection-select/index.js.map +1 -1
- package/lib/dynamic-group/index.d.ts +106 -0
- package/lib/dynamic-group/index.js +553 -0
- package/lib/dynamic-group/index.js.map +1 -0
- package/lib/dynamic-group/model.d.ts +135 -0
- package/lib/dynamic-group/model.js +416 -0
- package/lib/dynamic-group/model.js.map +1 -0
- package/lib/dynamic-group/node.d.ts +16 -0
- package/lib/dynamic-group/node.js +146 -0
- package/lib/dynamic-group/node.js.map +1 -0
- package/lib/dynamic-group/utils.d.ts +17 -0
- package/lib/dynamic-group/utils.js +32 -0
- package/lib/dynamic-group/utils.js.map +1 -0
- package/lib/index.css +63 -0
- package/lib/index.d.ts +16 -8
- package/lib/index.js +24 -8
- package/lib/index.js.map +1 -1
- package/lib/insert-node-in-polyline/index.js +2 -2
- package/lib/insert-node-in-polyline/index.js.map +1 -1
- package/lib/materials/group/GroupNode.d.ts +6 -10
- package/lib/materials/group/GroupNode.js +8 -6
- package/lib/materials/group/GroupNode.js.map +1 -1
- package/lib/materials/group/index.d.ts +5 -5
- package/lib/materials/group/index.js +24 -25
- package/lib/materials/group/index.js.map +1 -1
- package/lib/materials/node-selection/index.d.ts +6 -1
- package/lib/materials/node-selection/index.js +63 -55
- package/lib/materials/node-selection/index.js.map +1 -1
- package/lib/mindmap/index.d.ts +2 -2
- 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 +17 -0
- package/lib/tools/label/mediumEditor.js +98 -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 +107 -11
- package/lib/tools/snapshot/index.js +366 -149
- package/lib/tools/snapshot/index.js.map +1 -1
- package/lib/tools/snapshot/utils.d.ts +35 -0
- package/lib/tools/snapshot/utils.js +247 -0
- package/lib/tools/snapshot/utils.js.map +1 -0
- package/package.json +20 -3
- package/rollup.config.js +1 -1
- package/src/NodeResize/control/Control.tsx +13 -3
- package/src/NodeResize/index.ts +4 -0
- package/src/NodeResize/node/DiamondResize.tsx +2 -1
- package/src/NodeResize/node/EllipseResize.tsx +2 -1
- package/src/NodeResize/node/HtmlResize.tsx +2 -1
- package/src/bpmn/constant.ts +4 -1
- package/src/bpmn/index.ts +7 -4
- package/src/bpmn-elements-adapter/README.md +1 -3
- package/src/components/control/index.ts +4 -4
- package/src/components/highlight/index.ts +33 -6
- package/src/components/menu/index.ts +16 -13
- package/src/components/mini-map/index.ts +3 -3
- package/src/components/selection-select/index.ts +6 -2
- package/src/dynamic-group/index.ts +609 -0
- package/src/dynamic-group/model.ts +503 -0
- package/src/dynamic-group/node.ts +140 -0
- package/src/dynamic-group/utils.ts +33 -0
- package/src/index.ts +30 -8
- package/src/insert-node-in-polyline/index.ts +3 -3
- package/src/materials/group/GroupNode.ts +12 -12
- package/src/materials/group/index.ts +40 -40
- package/src/materials/node-selection/index.ts +78 -70
- 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 +94 -0
- package/src/tools/label/utils.ts +395 -0
- package/src/tools/snapshot/README.md +141 -5
- package/src/tools/snapshot/index.ts +288 -101
- package/src/tools/snapshot/utils.ts +163 -0
- package/dist/index.js +0 -26
- package/dist/index.js.map +0 -1
|
@@ -1,12 +1,58 @@
|
|
|
1
|
+
import LogicFlow from '@logicflow/core'
|
|
2
|
+
import { updateImageSource, copyCanvas } from './utils'
|
|
3
|
+
|
|
4
|
+
// 导出图片
|
|
5
|
+
export type ToImageOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* 导出图片的格式,可选值为:`png`、`webp`、`gif`、`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
|
+
|
|
40
|
+
// Blob | base64
|
|
41
|
+
export type SnapshotResponse = {
|
|
42
|
+
data: Blob | string
|
|
43
|
+
width: number
|
|
44
|
+
height: number
|
|
45
|
+
}
|
|
46
|
+
|
|
1
47
|
/**
|
|
2
48
|
* 快照插件,生成视图
|
|
3
49
|
*/
|
|
4
50
|
export class Snapshot {
|
|
5
51
|
static pluginName = 'snapshot'
|
|
6
|
-
lf:
|
|
52
|
+
lf: LogicFlow
|
|
7
53
|
offsetX?: number
|
|
8
54
|
offsetY?: number
|
|
9
|
-
fileName
|
|
55
|
+
fileName?: string // 默认是 logic-flow.当前时间戳
|
|
10
56
|
customCssRules: string
|
|
11
57
|
useGlobalRules: boolean
|
|
12
58
|
|
|
@@ -14,38 +60,58 @@ export class Snapshot {
|
|
|
14
60
|
this.lf = lf
|
|
15
61
|
this.customCssRules = ''
|
|
16
62
|
this.useGlobalRules = true
|
|
63
|
+
|
|
64
|
+
// TODO: 设置fileType为gif但是下载下来的还是png
|
|
65
|
+
// TODO: 完善静默模式不允许添加、操作元素能力
|
|
17
66
|
/* 下载快照 */
|
|
18
|
-
lf.getSnapshot = (
|
|
19
|
-
|
|
20
|
-
|
|
67
|
+
lf.getSnapshot = async (
|
|
68
|
+
fileName?: string,
|
|
69
|
+
toImageOptions?: ToImageOptions,
|
|
70
|
+
) => await this.getSnapshot(fileName, toImageOptions)
|
|
71
|
+
|
|
21
72
|
/* 获取Blob对象,用户图片上传 */
|
|
22
|
-
lf.getSnapshotBlob = (backgroundColor
|
|
23
|
-
this.getSnapshotBlob(backgroundColor)
|
|
73
|
+
lf.getSnapshotBlob = async (backgroundColor?: string, fileType?: string) =>
|
|
74
|
+
await this.getSnapshotBlob(backgroundColor, fileType)
|
|
75
|
+
|
|
24
76
|
/* 获取Base64对象,用户图片上传 */
|
|
25
|
-
lf.getSnapshotBase64 = (
|
|
26
|
-
|
|
77
|
+
lf.getSnapshotBase64 = async (
|
|
78
|
+
backgroundColor?: string,
|
|
79
|
+
fileType?: string,
|
|
80
|
+
) => await this.getSnapshotBase64(backgroundColor, fileType)
|
|
27
81
|
}
|
|
28
82
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
83
|
+
/**
|
|
84
|
+
* 获取svgRoot对象dom: 画布元素(不包含grid背景)
|
|
85
|
+
* @param lf
|
|
86
|
+
* @returns
|
|
87
|
+
*/
|
|
88
|
+
private getSvgRootElement(lf: LogicFlow) {
|
|
89
|
+
const svgRootElement = lf.container.querySelector('.lf-canvas-overlay')!
|
|
32
90
|
return svgRootElement
|
|
33
91
|
}
|
|
34
92
|
|
|
35
|
-
|
|
93
|
+
/**
|
|
94
|
+
* 通过 imgUrl 下载图片
|
|
95
|
+
* @param imgUrl
|
|
96
|
+
*/
|
|
97
|
+
private triggerDownload(imgUrl: string) {
|
|
36
98
|
const evt = new MouseEvent('click', {
|
|
37
99
|
view: document.defaultView,
|
|
38
100
|
bubbles: false,
|
|
39
101
|
cancelable: true,
|
|
40
102
|
})
|
|
41
103
|
const a = document.createElement('a')
|
|
42
|
-
a.setAttribute('download', this.fileName)
|
|
43
|
-
a.setAttribute('href',
|
|
104
|
+
a.setAttribute('download', this.fileName!)
|
|
105
|
+
a.setAttribute('href', imgUrl)
|
|
44
106
|
a.setAttribute('target', '_blank')
|
|
45
107
|
a.dispatchEvent(evt)
|
|
46
108
|
}
|
|
47
109
|
|
|
48
|
-
|
|
110
|
+
/**
|
|
111
|
+
* 删除锚点
|
|
112
|
+
* @param element
|
|
113
|
+
*/
|
|
114
|
+
private removeAnchor(element: ChildNode) {
|
|
49
115
|
const { childNodes } = element
|
|
50
116
|
let childLength = element.childNodes && element.childNodes.length
|
|
51
117
|
for (let i = 0; i < childLength; i++) {
|
|
@@ -59,7 +125,11 @@ export class Snapshot {
|
|
|
59
125
|
}
|
|
60
126
|
}
|
|
61
127
|
|
|
62
|
-
|
|
128
|
+
/**
|
|
129
|
+
* 删除旋转按钮
|
|
130
|
+
* @param element
|
|
131
|
+
*/
|
|
132
|
+
private removeRotateControl(element: ChildNode) {
|
|
63
133
|
const { childNodes } = element
|
|
64
134
|
let childLength = element.childNodes && element.childNodes.length
|
|
65
135
|
for (let i = 0; i < childLength; i++) {
|
|
@@ -73,27 +143,84 @@ export class Snapshot {
|
|
|
73
143
|
}
|
|
74
144
|
}
|
|
75
145
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 下载图片
|
|
179
|
+
* @param fileName
|
|
180
|
+
* @param toImageOptions
|
|
181
|
+
*/
|
|
182
|
+
private async snapshot(fileName?: string, toImageOptions?: ToImageOptions) {
|
|
183
|
+
const { fileType = 'png', quality } = toImageOptions ?? {}
|
|
184
|
+
this.fileName = `${fileName ?? `logic-flow.${Date.now()}`}.${fileType}`
|
|
79
185
|
const svg = this.getSvgRootElement(this.lf)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
186
|
+
await updateImageSource(svg as SVGElement)
|
|
187
|
+
if (fileType === 'svg') {
|
|
188
|
+
const copy = this.cloneSvg(svg)
|
|
189
|
+
const svgString = new XMLSerializer().serializeToString(copy)
|
|
190
|
+
const blob = new Blob([svgString], {
|
|
191
|
+
type: 'image/svg+xml;charset=utf-8',
|
|
192
|
+
})
|
|
193
|
+
const url = URL.createObjectURL(blob)
|
|
194
|
+
this.triggerDownload(url)
|
|
195
|
+
} else {
|
|
196
|
+
this.getCanvasData(svg, toImageOptions ?? {}).then(
|
|
197
|
+
(canvas: HTMLCanvasElement) => {
|
|
198
|
+
// canvas元素 => url image/octet-stream: 确保所有浏览器都能正常下载
|
|
199
|
+
const imgUrl = canvas
|
|
200
|
+
.toDataURL(`image/${fileType}`, quality)
|
|
201
|
+
.replace(`image/${fileType}`, 'image/octet-stream')
|
|
202
|
+
this.triggerDownload(imgUrl)
|
|
203
|
+
},
|
|
204
|
+
)
|
|
205
|
+
}
|
|
88
206
|
}
|
|
89
207
|
|
|
90
|
-
|
|
91
|
-
|
|
208
|
+
/**
|
|
209
|
+
* 获取base64对象
|
|
210
|
+
* @param backgroundColor
|
|
211
|
+
* @param fileType
|
|
212
|
+
* @returns
|
|
213
|
+
*/
|
|
214
|
+
private async getSnapshotBase64(
|
|
215
|
+
backgroundColor?: string,
|
|
216
|
+
fileType?: string,
|
|
217
|
+
): Promise<SnapshotResponse> {
|
|
92
218
|
const svg = this.getSvgRootElement(this.lf)
|
|
219
|
+
await updateImageSource(svg as SVGElement)
|
|
93
220
|
return new Promise((resolve) => {
|
|
94
|
-
this.getCanvasData(svg, backgroundColor).then(
|
|
221
|
+
this.getCanvasData(svg, { backgroundColor }).then(
|
|
95
222
|
(canvas: HTMLCanvasElement) => {
|
|
96
|
-
const base64 = canvas.toDataURL('
|
|
223
|
+
const base64 = canvas.toDataURL(`image/${fileType ?? 'png'}`)
|
|
97
224
|
// 输出图片数据以及图片宽高
|
|
98
225
|
resolve({
|
|
99
226
|
data: base64,
|
|
@@ -105,33 +232,56 @@ export class Snapshot {
|
|
|
105
232
|
})
|
|
106
233
|
}
|
|
107
234
|
|
|
108
|
-
|
|
109
|
-
|
|
235
|
+
/**
|
|
236
|
+
* 获取Blob对象
|
|
237
|
+
* @param backgroundColor
|
|
238
|
+
* @param fileType
|
|
239
|
+
* @returns
|
|
240
|
+
*/
|
|
241
|
+
private async getSnapshotBlob(
|
|
242
|
+
backgroundColor?: string,
|
|
243
|
+
fileType?: string,
|
|
244
|
+
): Promise<SnapshotResponse> {
|
|
110
245
|
const svg = this.getSvgRootElement(this.lf)
|
|
246
|
+
await updateImageSource(svg as SVGElement)
|
|
111
247
|
return new Promise((resolve) => {
|
|
112
|
-
this.getCanvasData(svg, backgroundColor).then(
|
|
248
|
+
this.getCanvasData(svg, { backgroundColor }).then(
|
|
113
249
|
(canvas: HTMLCanvasElement) => {
|
|
114
|
-
canvas.toBlob(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
250
|
+
canvas.toBlob(
|
|
251
|
+
(blob) => {
|
|
252
|
+
// 输出图片数据以及图片宽高
|
|
253
|
+
resolve({
|
|
254
|
+
data: blob!,
|
|
255
|
+
width: canvas.width,
|
|
256
|
+
height: canvas.height,
|
|
257
|
+
})
|
|
258
|
+
},
|
|
259
|
+
`image/${fileType ?? 'png'}`,
|
|
260
|
+
)
|
|
122
261
|
},
|
|
123
262
|
)
|
|
124
263
|
})
|
|
125
264
|
}
|
|
126
265
|
|
|
127
|
-
|
|
266
|
+
/**
|
|
267
|
+
* 获取脚本css样式
|
|
268
|
+
* @returns
|
|
269
|
+
*/
|
|
270
|
+
private getClassRules(): string {
|
|
128
271
|
let rules = ''
|
|
129
272
|
if (this.useGlobalRules) {
|
|
130
273
|
const { styleSheets } = document
|
|
131
274
|
for (let i = 0; i < styleSheets.length; i++) {
|
|
132
275
|
const sheet = styleSheets[i]
|
|
133
|
-
|
|
134
|
-
|
|
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
|
+
)
|
|
135
285
|
}
|
|
136
286
|
}
|
|
137
287
|
}
|
|
@@ -141,37 +291,19 @@ export class Snapshot {
|
|
|
141
291
|
return rules
|
|
142
292
|
}
|
|
143
293
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const layerClassList =
|
|
158
|
-
lfLayer.classList && Array.from(lfLayer.classList)
|
|
159
|
-
if (layerClassList && layerClassList.indexOf('lf-base') < 0) {
|
|
160
|
-
graph?.removeChild(graph.childNodes[i])
|
|
161
|
-
childLength--
|
|
162
|
-
i--
|
|
163
|
-
} else {
|
|
164
|
-
// 删除锚点
|
|
165
|
-
const lfBase = graph?.childNodes[i]
|
|
166
|
-
lfBase &&
|
|
167
|
-
lfBase.childNodes.forEach((item) => {
|
|
168
|
-
const element = item as SVGGraphicsElement
|
|
169
|
-
this.removeAnchor(element.firstChild)
|
|
170
|
-
this.removeRotateControl(element.firstChild)
|
|
171
|
-
})
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
294
|
+
/**
|
|
295
|
+
* 获取图片生成中间产物canvas对象,用户转换为其他需要的格式
|
|
296
|
+
* @param svg
|
|
297
|
+
* @param toImageOptions
|
|
298
|
+
* @returns
|
|
299
|
+
*/
|
|
300
|
+
private async getCanvasData(
|
|
301
|
+
svg: Element,
|
|
302
|
+
toImageOptions: ToImageOptions,
|
|
303
|
+
): Promise<HTMLCanvasElement> {
|
|
304
|
+
const { width, height, backgroundColor, padding = 40 } = toImageOptions
|
|
305
|
+
const copy = this.cloneSvg(svg, false)
|
|
306
|
+
|
|
175
307
|
let dpr = window.devicePixelRatio || 1
|
|
176
308
|
if (dpr < 1) {
|
|
177
309
|
// https://github.com/didi/LogicFlow/issues/1222
|
|
@@ -187,46 +319,50 @@ export class Snapshot {
|
|
|
187
319
|
// 当dpr小于1的时候,我们强制转化为1,并不会产生绘制模糊等问题
|
|
188
320
|
dpr = 1
|
|
189
321
|
}
|
|
190
|
-
const canvas = document.createElement('canvas')
|
|
191
322
|
/*
|
|
192
323
|
为了计算真实宽高需要取图的真实dom
|
|
193
324
|
真实dom存在缩放影响其宽高数值
|
|
194
325
|
在得到真实宽高后除以缩放比例即可得到正常宽高
|
|
195
326
|
*/
|
|
327
|
+
|
|
196
328
|
const base = this.lf.graphModel.rootEl.querySelector('.lf-base')
|
|
197
329
|
const bbox = (base as Element).getBoundingClientRect()
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
.getBoundingClientRect()
|
|
330
|
+
const layoutCanvas = this.lf.container.querySelector('.lf-canvas-overlay')!
|
|
331
|
+
const layout = layoutCanvas.getBoundingClientRect()
|
|
201
332
|
const offsetX = bbox.x - layout.x
|
|
202
333
|
const offsetY = bbox.y - layout.y
|
|
203
334
|
const { graphModel } = this.lf
|
|
204
335
|
const { transformModel } = graphModel
|
|
205
336
|
const { SCALE_X, SCALE_Y, TRANSLATE_X, TRANSLATE_Y } = transformModel
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
337
|
+
;(copy.lastChild as SVGElement).style.transform = `matrix(1, 0, 0, 1, ${
|
|
338
|
+
(-offsetX + TRANSLATE_X) * (1 / SCALE_X)
|
|
339
|
+
}, ${(-offsetY + TRANSLATE_Y) * (1 / SCALE_Y)})`
|
|
340
|
+
// 包含所有元素的最小宽高
|
|
210
341
|
const bboxWidth = Math.ceil(bbox.width / SCALE_X)
|
|
211
342
|
const bboxHeight = Math.ceil(bbox.height / SCALE_Y)
|
|
212
|
-
|
|
343
|
+
const canvas = document.createElement('canvas')
|
|
213
344
|
canvas.style.width = `${bboxWidth}px`
|
|
214
345
|
canvas.style.height = `${bboxHeight}px`
|
|
215
|
-
|
|
216
|
-
canvas.
|
|
346
|
+
// 宽高值 默认加padding 40,保证图形不会紧贴着下载图片
|
|
347
|
+
canvas.width = bboxWidth * dpr + padding * 2
|
|
348
|
+
canvas.height = bboxHeight * dpr + padding * 2
|
|
217
349
|
const ctx = canvas.getContext('2d')
|
|
218
350
|
if (ctx) {
|
|
351
|
+
// 清空canvas
|
|
219
352
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
220
353
|
ctx.scale(dpr, dpr)
|
|
221
354
|
// 如果有背景色,设置流程图导出的背景色
|
|
222
355
|
if (backgroundColor) {
|
|
223
356
|
ctx.fillStyle = backgroundColor
|
|
224
|
-
ctx.fillRect(0, 0,
|
|
357
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
225
358
|
} else {
|
|
226
|
-
ctx.clearRect(0, 0,
|
|
359
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
227
360
|
}
|
|
228
361
|
}
|
|
362
|
+
|
|
229
363
|
const img = new Image()
|
|
364
|
+
|
|
365
|
+
// 设置css样式
|
|
230
366
|
const style = document.createElement('style')
|
|
231
367
|
style.innerHTML = this.getClassRules()
|
|
232
368
|
const foreignObject = document.createElement('foreignObject')
|
|
@@ -238,26 +374,35 @@ export class Snapshot {
|
|
|
238
374
|
try {
|
|
239
375
|
if (isFirefox) {
|
|
240
376
|
createImageBitmap(img, {
|
|
241
|
-
resizeWidth:
|
|
242
|
-
|
|
377
|
+
resizeWidth:
|
|
378
|
+
width && height
|
|
379
|
+
? copyCanvas(canvas, width, height).width
|
|
380
|
+
: canvas.width,
|
|
381
|
+
resizeHeight:
|
|
382
|
+
width && height
|
|
383
|
+
? copyCanvas(canvas, width, height).height
|
|
384
|
+
: canvas.height,
|
|
243
385
|
}).then((imageBitmap) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
386
|
+
ctx?.drawImage(imageBitmap, padding / dpr, padding / dpr)
|
|
387
|
+
resolve(
|
|
388
|
+
width && height ? copyCanvas(canvas, width, height) : canvas,
|
|
389
|
+
)
|
|
247
390
|
})
|
|
248
391
|
} else {
|
|
249
|
-
ctx?.drawImage(img,
|
|
250
|
-
resolve(
|
|
392
|
+
ctx?.drawImage(img, padding / dpr, padding / dpr)
|
|
393
|
+
resolve(
|
|
394
|
+
width && height ? copyCanvas(canvas, width, height) : canvas,
|
|
395
|
+
)
|
|
251
396
|
}
|
|
252
397
|
} catch (e) {
|
|
253
|
-
ctx?.drawImage(img,
|
|
254
|
-
resolve(canvas)
|
|
398
|
+
ctx?.drawImage(img, padding / dpr, padding / dpr)
|
|
399
|
+
resolve(width && height ? copyCanvas(canvas, width, height) : canvas)
|
|
255
400
|
}
|
|
256
401
|
}
|
|
402
|
+
|
|
257
403
|
/*
|
|
258
404
|
因为svg中存在dom存放在foreignObject元素中
|
|
259
|
-
|
|
260
|
-
todo: 会导致一些清晰度问题这个需要再解决
|
|
405
|
+
svg dom => Base64编码字符串 挂载到img上
|
|
261
406
|
fixme: XMLSerializer的中的css background url不会下载图片
|
|
262
407
|
*/
|
|
263
408
|
const svg2Img = `data:image/svg+xml;charset=utf-8,${new XMLSerializer().serializeToString(
|
|
@@ -270,6 +415,48 @@ export class Snapshot {
|
|
|
270
415
|
img.src = imgSrc
|
|
271
416
|
})
|
|
272
417
|
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* 克隆并处理画布节点
|
|
421
|
+
* @param svg
|
|
422
|
+
* @returns
|
|
423
|
+
*/
|
|
424
|
+
private cloneSvg(svg: Element, addStyle: boolean = true): Node {
|
|
425
|
+
const copy = svg.cloneNode(true)
|
|
426
|
+
const graph = copy.lastChild
|
|
427
|
+
let childLength = graph?.childNodes?.length
|
|
428
|
+
if (childLength) {
|
|
429
|
+
for (let i = 0; i < childLength; i++) {
|
|
430
|
+
const lfLayer = graph?.childNodes[i] as SVGGraphicsElement
|
|
431
|
+
// 只保留包含节点和边的基础图层进行下载,其他图层删除
|
|
432
|
+
const layerClassList =
|
|
433
|
+
lfLayer.classList && Array.from(lfLayer.classList)
|
|
434
|
+
if (layerClassList && layerClassList.indexOf('lf-base') < 0) {
|
|
435
|
+
graph?.removeChild(graph.childNodes[i])
|
|
436
|
+
childLength--
|
|
437
|
+
i--
|
|
438
|
+
} else {
|
|
439
|
+
// 删除锚点
|
|
440
|
+
const lfBase = graph?.childNodes[i]
|
|
441
|
+
lfBase &&
|
|
442
|
+
lfBase.childNodes.forEach((item) => {
|
|
443
|
+
const element = item as SVGGraphicsElement
|
|
444
|
+
this.removeAnchor(element.firstChild!)
|
|
445
|
+
this.removeRotateControl(element.firstChild!)
|
|
446
|
+
})
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// 设置css样式
|
|
451
|
+
if (addStyle) {
|
|
452
|
+
const style = document.createElement('style')
|
|
453
|
+
style.innerHTML = this.getClassRules()
|
|
454
|
+
const foreignObject = document.createElement('foreignObject')
|
|
455
|
+
foreignObject.appendChild(style)
|
|
456
|
+
copy.appendChild(foreignObject)
|
|
457
|
+
}
|
|
458
|
+
return copy
|
|
459
|
+
}
|
|
273
460
|
}
|
|
274
461
|
|
|
275
462
|
export default Snapshot
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 图片缓存, 已请求过的图片直接从缓存中获取
|
|
3
|
+
*/
|
|
4
|
+
const imageCache: Record<string, string> = {}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 当获取图片失败时会返回失败信息,是 text/plain 类型的数据
|
|
8
|
+
* @param str - 图片内容
|
|
9
|
+
* @returns
|
|
10
|
+
*/
|
|
11
|
+
export function isTextPlainBase64(str: string) {
|
|
12
|
+
return str.startsWith('data:text/plain')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 将网络图片转为 base64
|
|
17
|
+
* @param url - 图片地址
|
|
18
|
+
* @returns
|
|
19
|
+
*/
|
|
20
|
+
export async function convertImageToBase64(url: string): Promise<string> {
|
|
21
|
+
if (imageCache[url]) {
|
|
22
|
+
return imageCache[url]
|
|
23
|
+
}
|
|
24
|
+
return new Promise((resolve, reject) => {
|
|
25
|
+
try {
|
|
26
|
+
fetch(url)
|
|
27
|
+
.then((response) => response.blob())
|
|
28
|
+
.then((blob) => {
|
|
29
|
+
const reader = new FileReader()
|
|
30
|
+
reader.onloadend = () => {
|
|
31
|
+
resolve((imageCache[url] = reader.result as string))
|
|
32
|
+
}
|
|
33
|
+
reader.onerror = reject
|
|
34
|
+
reader.readAsDataURL(blob)
|
|
35
|
+
})
|
|
36
|
+
.catch(() => {
|
|
37
|
+
resolve((imageCache[url] = url))
|
|
38
|
+
})
|
|
39
|
+
} catch (error) {
|
|
40
|
+
// 如果转换失败,后续大概率仍然会失败,因此直接缓存
|
|
41
|
+
return (imageCache[url] = url)
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 使用 base64 的图片替换 img 标签的 src 或 image 标签的 href
|
|
48
|
+
* @param node - html 节点或 svg 节点
|
|
49
|
+
*/
|
|
50
|
+
export async function updateImageSrcOrHrefWithBase64Image(
|
|
51
|
+
node: HTMLImageElement | SVGImageElement,
|
|
52
|
+
attrName: 'src' | 'href',
|
|
53
|
+
) {
|
|
54
|
+
try {
|
|
55
|
+
const url = node.getAttribute(attrName) || ''
|
|
56
|
+
// 已经是 base64 图片,不需要处理
|
|
57
|
+
if (url.startsWith('data:')) {
|
|
58
|
+
return
|
|
59
|
+
}
|
|
60
|
+
const base64Image = await convertImageToBase64(url)
|
|
61
|
+
if (isTextPlainBase64(base64Image)) {
|
|
62
|
+
return
|
|
63
|
+
}
|
|
64
|
+
node.setAttribute(attrName, base64Image)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(error)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 使用 base64 的图片替换背景图片
|
|
72
|
+
* @param node - html 节点
|
|
73
|
+
* @param styleAttr - 样式属性名称
|
|
74
|
+
*/
|
|
75
|
+
export async function updateBackgroundImageWithBase64Image(
|
|
76
|
+
node: HTMLElement,
|
|
77
|
+
url: string,
|
|
78
|
+
) {
|
|
79
|
+
try {
|
|
80
|
+
// 已经是 base64 图片,不需要处理
|
|
81
|
+
if (url.startsWith('data:')) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
const base64Image = await convertImageToBase64(url)
|
|
85
|
+
if (isTextPlainBase64(base64Image)) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
node.style.backgroundImage = `url(${base64Image})`
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error(error)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* 更新图片数据
|
|
96
|
+
* @param node - 节点
|
|
97
|
+
*/
|
|
98
|
+
export async function updateImageSource(node: HTMLElement | SVGElement) {
|
|
99
|
+
const nodes = [node]
|
|
100
|
+
let nodePtr
|
|
101
|
+
const promises: any[] = []
|
|
102
|
+
while (nodes.length) {
|
|
103
|
+
nodePtr = nodes.shift()
|
|
104
|
+
if (nodePtr.children.length) {
|
|
105
|
+
nodes.push(...nodePtr.children)
|
|
106
|
+
}
|
|
107
|
+
if (nodePtr instanceof HTMLElement) {
|
|
108
|
+
// 如果有 style 的 background, backgroundImage 属性中有 url(xxx), 尝试替换为 base64 图片
|
|
109
|
+
const { background, backgroundImage } = nodePtr.style
|
|
110
|
+
const backgroundUrlMatch = background.match(/url\(["']?(.*?)["']?\)/)
|
|
111
|
+
if (backgroundUrlMatch && backgroundUrlMatch[1]) {
|
|
112
|
+
const imageUrl = backgroundUrlMatch[1]
|
|
113
|
+
promises.push(updateBackgroundImageWithBase64Image(nodePtr, imageUrl))
|
|
114
|
+
}
|
|
115
|
+
const backgroundImageUrlMatch = backgroundImage.match(
|
|
116
|
+
/url\(["']?(.*?)["']?\)/,
|
|
117
|
+
)
|
|
118
|
+
if (backgroundImageUrlMatch && backgroundImageUrlMatch[1]) {
|
|
119
|
+
const imageUrl = backgroundImageUrlMatch[1]
|
|
120
|
+
promises.push(updateBackgroundImageWithBase64Image(nodePtr, imageUrl))
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// 如果有 img 和 image 标签,尝试将 src 和 href 替换为 base64 图片
|
|
124
|
+
if (nodePtr instanceof HTMLImageElement) {
|
|
125
|
+
promises.push(updateImageSrcOrHrefWithBase64Image(nodePtr, 'src'))
|
|
126
|
+
} else if (nodePtr instanceof SVGImageElement) {
|
|
127
|
+
promises.push(updateImageSrcOrHrefWithBase64Image(nodePtr, 'href'))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
await Promise.all(promises)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 重新复制canvas 用于在不裁剪原canvas的基础上通过拉伸方式达到自定义宽高目的
|
|
135
|
+
* @param originCanvas HTMLCanvasElement
|
|
136
|
+
* @param targetWidth number
|
|
137
|
+
* @param targetHeight number
|
|
138
|
+
*/
|
|
139
|
+
export function copyCanvas(
|
|
140
|
+
originCanvas: HTMLCanvasElement,
|
|
141
|
+
targetWidth: number,
|
|
142
|
+
targetHeight: number,
|
|
143
|
+
): HTMLCanvasElement {
|
|
144
|
+
const newCanvas = document.createElement('canvas')
|
|
145
|
+
newCanvas.width = targetWidth
|
|
146
|
+
newCanvas.height = targetHeight
|
|
147
|
+
const newCtx = newCanvas.getContext('2d')
|
|
148
|
+
if (newCtx) {
|
|
149
|
+
// 注意: 自定义宽高时,可能会拉伸图形,这时候padding也会被拉伸导致不准确
|
|
150
|
+
newCtx.drawImage(
|
|
151
|
+
originCanvas,
|
|
152
|
+
0,
|
|
153
|
+
0,
|
|
154
|
+
originCanvas.width,
|
|
155
|
+
originCanvas.height,
|
|
156
|
+
0,
|
|
157
|
+
0,
|
|
158
|
+
targetWidth,
|
|
159
|
+
targetHeight,
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
return newCanvas
|
|
163
|
+
}
|