@logicflow/extension 2.0.0-beta.2 → 2.0.0-beta.4
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 +7 -7
- package/dist/index.min.js +2 -2
- 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/index.d.ts +1 -0
- package/es/index.js +1 -0
- 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/node-selection/index.d.ts +2 -1
- package/es/materials/node-selection/index.js +64 -56
- package/es/materials/node-selection/index.js.map +1 -1
- package/es/tools/snapshot/index.d.ts +101 -11
- package/es/tools/snapshot/index.js +331 -147
- 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/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/index.d.ts +1 -0
- package/lib/index.js +1 -0
- 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/node-selection/index.d.ts +2 -1
- package/lib/materials/node-selection/index.js +63 -55
- package/lib/materials/node-selection/index.js.map +1 -1
- package/lib/tools/snapshot/index.d.ts +101 -11
- package/lib/tools/snapshot/index.js +331 -147
- 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 +7 -4
- package/src/components/menu/index.ts +16 -13
- package/src/index.ts +1 -0
- package/src/insert-node-in-polyline/index.ts +3 -3
- package/src/materials/node-selection/index.ts +72 -69
- package/src/tools/snapshot/README.md +130 -5
- package/src/tools/snapshot/index.ts +264 -98
- package/src/tools/snapshot/utils.ts +163 -0
|
@@ -1,9 +1,134 @@
|
|
|
1
|
-
|
|
1
|
+
# 导出 Snapshot
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
我们经常需要将画布内容通过图片的形式导出来,我们提供了一个独立的插件包 `Snapshot` 来使用这个功能。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 使用
|
|
6
|
+
|
|
7
|
+
### 1. 注册
|
|
8
|
+
|
|
9
|
+
两种注册方式,全局注册和局部注册,区别是全局注册每一个`lf`实例都可以使用。
|
|
10
|
+
|
|
11
|
+
```tsx | pure
|
|
12
|
+
import LogicFlow from "@logicflow/core";
|
|
13
|
+
import { Snapshot } from "@logicflow/extension";
|
|
14
|
+
|
|
15
|
+
// 全局注册
|
|
6
16
|
LogicFlow.use(Snapshot);
|
|
7
|
-
|
|
8
|
-
|
|
17
|
+
|
|
18
|
+
// 局部注册
|
|
19
|
+
const lf = new LogicFlow({
|
|
20
|
+
...config,
|
|
21
|
+
plugins: [Snapshot]
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 2. 使用
|
|
27
|
+
|
|
28
|
+
注册后,`lf`实例身上将被挂载`getSnapshot()`方法,通过`lf.getSnapshot()`方法调用。
|
|
29
|
+
|
|
30
|
+
```tsx | pure
|
|
31
|
+
|
|
32
|
+
// 可以使用任意方式触发,然后将绘制的图形下载到本地磁盘上
|
|
33
|
+
document.getElementById("button").addEventListener("click", () => {
|
|
34
|
+
lf.getSnapshot();
|
|
35
|
+
|
|
36
|
+
// 或者 1.1.13版本
|
|
37
|
+
// lf.extension.snapshot.getSnapshot()
|
|
38
|
+
});
|
|
39
|
+
|
|
9
40
|
```
|
|
41
|
+
|
|
42
|
+
值得一提的是:通过此插件截取下载的图片不会因为偏移、缩放受到影响。
|
|
43
|
+
|
|
44
|
+
## 自定义设置 css
|
|
45
|
+
|
|
46
|
+
当自定义元素在导出图片时需要额外添加 css 样式时,可以用如下方式实现:
|
|
47
|
+
|
|
48
|
+
为了保持流程图生成的图片与画布上效果一致,`snapshot`插件默认会将当前页面所有的 `css` 规则都加载到导出图片中, 但是可能会因为 css 文件跨域引起报错,参考 issue575。可以修改useGlobalRules来禁止加载所有 css 规则,然后通过`customCssRules`属性来自定义增加css样式。
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
|
|
52
|
+
// 默认开启css样式
|
|
53
|
+
lf.extension.snapshot.useGlobalRules = true
|
|
54
|
+
// 不会覆盖css样式,会叠加,customCssRules优先级高
|
|
55
|
+
lf.extension.snapshot.customCssRules = `
|
|
56
|
+
.uml-wrapper {
|
|
57
|
+
line-height: 1.2;
|
|
58
|
+
text-align: center;
|
|
59
|
+
color: blue;
|
|
60
|
+
}
|
|
61
|
+
`
|
|
62
|
+
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## API
|
|
66
|
+
|
|
67
|
+
### lf.getSnapshot(...)
|
|
68
|
+
|
|
69
|
+
导出图片。
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
|
|
73
|
+
getSnapshot(fileName?: string, toImageOptions?: ToImageOptions) : Promise<void>
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`fileName` 为文件名称,不填为默认为`logic-flow.当前时间戳`,`ToImageOptions` 描述如下:
|
|
78
|
+
|
|
79
|
+
| 属性名 | 类型 | 默认值 | 必填 | 描述 |
|
|
80
|
+
| --------- | -------- | -------------------------- | -------- | ----------------------------------------------------------------- |
|
|
81
|
+
| fileType | string | png | | 图片类型: 默认不填是png 还可以设置有webp、gif、jpeg、svg |
|
|
82
|
+
| width | number | - | | 自定义导出图片的宽度,不设置即可,设置可能会拉伸图形 |
|
|
83
|
+
| height | numebr | - | | 自定义导出图片的宽度,不设置即可,设置可能会拉伸图形 |
|
|
84
|
+
| backgroundColor | string | - | | 图片背景,不设置背景默认透明 |
|
|
85
|
+
| quality | number | - | | 图片质量,在指定图片格式为 jpeg 或 webp 的情况下,可以从 0 到 1 的区间内选择图片的质量。如果超出取值范围,将会使用默认值 0.92。其他不合法参数会被忽略 |
|
|
86
|
+
| padding | number | 40 | | 图片内边距: 元素内容所在区之外空白空间,不设置默认有40的内边距 |
|
|
87
|
+
| partial | boolean | - | | 导出时是否开启局部渲染,false:将导出画布上所有的元素,true:只导出画面区域内的可见元素,不设置默认为lf实例身上partial值 |
|
|
88
|
+
|
|
89
|
+
注意:
|
|
90
|
+
- `svg`目前暂不支持`width`,`height`, `backgroundColor`, `padding` 属性。
|
|
91
|
+
- 自定义宽高后,可能会拉伸图形,这时候`padding`也会被拉伸导致不准确。
|
|
92
|
+
|
|
93
|
+
### lf.getSnapshotBlob(...)
|
|
94
|
+
|
|
95
|
+
`snapshot` 除了支持图片类型导出,还支持下载<a href="https://developer.mozilla.org/zh-CN/docs/Web/API/Blob" target="_blank"> Blob文件对象 </a> 和 <a href="https://developer.mozilla.org/zh-CN/docs/Glossary/Base64" target="_blank"> Base64文本编码 </a>
|
|
96
|
+
|
|
97
|
+
获取`Blob`对象。
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
|
|
101
|
+
async getSnapshotBlob(backgroundColor?: string, fileType?: string) : Promise<SnapshotResponse>
|
|
102
|
+
|
|
103
|
+
// example
|
|
104
|
+
const { data : blob } = await lf.getSnapshotBlob()
|
|
105
|
+
console.log(blob)
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
`backgroundColor`: 背景,不填默认为透明。
|
|
109
|
+
|
|
110
|
+
`fileType`: 文件类型,不填默认为png。
|
|
111
|
+
|
|
112
|
+
`SnapshotResponse`: 返回对象。
|
|
113
|
+
|
|
114
|
+
```tsx | pure
|
|
115
|
+
|
|
116
|
+
export type SnapshotResponse = {
|
|
117
|
+
data: Blob | string // Blob对象 或 Base64文本编码文本
|
|
118
|
+
width: number // 图片宽度
|
|
119
|
+
height: number // 图片高度
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### lf.getSnapshotBase64(...)
|
|
125
|
+
|
|
126
|
+
获取`Base64文本编码`文本。
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
|
|
130
|
+
async getSnapshotBase64(backgroundColor?: string, fileType?: string) : Promise<SnapshotResponse>
|
|
131
|
+
|
|
132
|
+
// example
|
|
133
|
+
const { data : base64 } = await lf.getSnapshotBlob()
|
|
134
|
+
console.log(base64)
|
|
@@ -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,70 @@ export class Snapshot {
|
|
|
14
60
|
this.lf = lf
|
|
15
61
|
this.customCssRules = ''
|
|
16
62
|
this.useGlobalRules = true
|
|
63
|
+
|
|
17
64
|
/* 下载快照 */
|
|
18
|
-
lf.getSnapshot = (
|
|
19
|
-
|
|
65
|
+
lf.getSnapshot = async (
|
|
66
|
+
fileName?: string,
|
|
67
|
+
toImageOptions?: ToImageOptions,
|
|
68
|
+
) => {
|
|
69
|
+
const curPartial = this.lf.graphModel.getPartial()
|
|
70
|
+
const { partial = curPartial } = toImageOptions ?? {}
|
|
71
|
+
// 画布当前渲染模式和用户导出渲染模式不一致时,需要更新画布
|
|
72
|
+
if (curPartial !== partial) {
|
|
73
|
+
this.lf.graphModel.setPartial(partial)
|
|
74
|
+
this.lf.graphModel.eventCenter.once('graph:updated', async () => {
|
|
75
|
+
await this.getSnapshot(fileName, toImageOptions)
|
|
76
|
+
// 恢复原来渲染模式
|
|
77
|
+
this.lf.graphModel.setPartial(curPartial)
|
|
78
|
+
})
|
|
79
|
+
} else {
|
|
80
|
+
await this.getSnapshot(fileName, toImageOptions)
|
|
81
|
+
}
|
|
20
82
|
}
|
|
83
|
+
|
|
21
84
|
/* 获取Blob对象,用户图片上传 */
|
|
22
|
-
lf.getSnapshotBlob = (backgroundColor
|
|
23
|
-
this.getSnapshotBlob(backgroundColor)
|
|
85
|
+
lf.getSnapshotBlob = async (backgroundColor?: string, fileType?: string) =>
|
|
86
|
+
await this.getSnapshotBlob(backgroundColor, fileType)
|
|
87
|
+
|
|
24
88
|
/* 获取Base64对象,用户图片上传 */
|
|
25
|
-
lf.getSnapshotBase64 = (
|
|
26
|
-
|
|
89
|
+
lf.getSnapshotBase64 = async (
|
|
90
|
+
backgroundColor?: string,
|
|
91
|
+
fileType?: string,
|
|
92
|
+
) => await this.getSnapshotBase64(backgroundColor, fileType)
|
|
27
93
|
}
|
|
28
94
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
95
|
+
/**
|
|
96
|
+
* 获取svgRoot对象dom: 画布元素(不包含grid背景)
|
|
97
|
+
* @param lf LogicFlow
|
|
98
|
+
* @returns
|
|
99
|
+
*/
|
|
100
|
+
getSvgRootElement(lf: LogicFlow) {
|
|
101
|
+
const svgRootElement = lf.container.querySelector('.lf-canvas-overlay')!
|
|
32
102
|
return svgRootElement
|
|
33
103
|
}
|
|
34
104
|
|
|
35
|
-
|
|
105
|
+
/**
|
|
106
|
+
* 通过 imgUrl 下载图片
|
|
107
|
+
* @param imgUrl
|
|
108
|
+
*/
|
|
109
|
+
triggerDownload(imgUrl: string) {
|
|
36
110
|
const evt = new MouseEvent('click', {
|
|
37
111
|
view: document.defaultView,
|
|
38
112
|
bubbles: false,
|
|
39
113
|
cancelable: true,
|
|
40
114
|
})
|
|
41
115
|
const a = document.createElement('a')
|
|
42
|
-
a.setAttribute('download', this.fileName)
|
|
43
|
-
a.setAttribute('href',
|
|
116
|
+
a.setAttribute('download', this.fileName!)
|
|
117
|
+
a.setAttribute('href', imgUrl)
|
|
44
118
|
a.setAttribute('target', '_blank')
|
|
45
119
|
a.dispatchEvent(evt)
|
|
46
120
|
}
|
|
47
121
|
|
|
48
|
-
|
|
122
|
+
/**
|
|
123
|
+
* 删除锚点
|
|
124
|
+
* @param element ChildNode
|
|
125
|
+
*/
|
|
126
|
+
private removeAnchor(element: ChildNode) {
|
|
49
127
|
const { childNodes } = element
|
|
50
128
|
let childLength = element.childNodes && element.childNodes.length
|
|
51
129
|
for (let i = 0; i < childLength; i++) {
|
|
@@ -59,7 +137,11 @@ export class Snapshot {
|
|
|
59
137
|
}
|
|
60
138
|
}
|
|
61
139
|
|
|
62
|
-
|
|
140
|
+
/**
|
|
141
|
+
* 删除旋转按钮
|
|
142
|
+
* @param element
|
|
143
|
+
*/
|
|
144
|
+
private removeRotateControl(element: ChildNode) {
|
|
63
145
|
const { childNodes } = element
|
|
64
146
|
let childLength = element.childNodes && element.childNodes.length
|
|
65
147
|
for (let i = 0; i < childLength; i++) {
|
|
@@ -73,27 +155,56 @@ export class Snapshot {
|
|
|
73
155
|
}
|
|
74
156
|
}
|
|
75
157
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
158
|
+
/**
|
|
159
|
+
* 下载图片
|
|
160
|
+
* @param fileName string
|
|
161
|
+
* @param toImageOptions
|
|
162
|
+
*/
|
|
163
|
+
private async getSnapshot(
|
|
164
|
+
fileName?: string,
|
|
165
|
+
toImageOptions?: ToImageOptions,
|
|
166
|
+
) {
|
|
167
|
+
const { fileType = 'png', quality } = toImageOptions ?? {}
|
|
168
|
+
this.fileName = `${fileName ?? `logic-flow.${Date.now()}`}.${fileType}`
|
|
79
169
|
const svg = this.getSvgRootElement(this.lf)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
}
|
|
87
|
-
|
|
170
|
+
await updateImageSource(svg as SVGElement)
|
|
171
|
+
if (fileType === 'svg') {
|
|
172
|
+
const copy = this.cloneSvg(svg)
|
|
173
|
+
const svgString = new XMLSerializer().serializeToString(copy)
|
|
174
|
+
const blob = new Blob([svgString], {
|
|
175
|
+
type: 'image/svg+xml;charset=utf-8',
|
|
176
|
+
})
|
|
177
|
+
const url = URL.createObjectURL(blob)
|
|
178
|
+
this.triggerDownload(url)
|
|
179
|
+
} else {
|
|
180
|
+
this.getCanvasData(svg, toImageOptions ?? {}).then(
|
|
181
|
+
(canvas: HTMLCanvasElement) => {
|
|
182
|
+
// canvas元素 => url image/octet-stream: 确保所有浏览器都能正常下载
|
|
183
|
+
const imgUrl = canvas
|
|
184
|
+
.toDataURL(`image/${fileType}`, quality)
|
|
185
|
+
.replace(`image/${fileType}`, 'image/octet-stream')
|
|
186
|
+
this.triggerDownload(imgUrl)
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
}
|
|
88
190
|
}
|
|
89
191
|
|
|
90
|
-
|
|
91
|
-
|
|
192
|
+
/**
|
|
193
|
+
* 获取base64对象
|
|
194
|
+
* @param backgroundColor string | undefined
|
|
195
|
+
* @param fileType string | undefined
|
|
196
|
+
* @returns Promise<SnapshotResponse>
|
|
197
|
+
*/
|
|
198
|
+
private async getSnapshotBase64(
|
|
199
|
+
backgroundColor?: string,
|
|
200
|
+
fileType?: string,
|
|
201
|
+
): Promise<SnapshotResponse> {
|
|
92
202
|
const svg = this.getSvgRootElement(this.lf)
|
|
203
|
+
await updateImageSource(svg as SVGElement)
|
|
93
204
|
return new Promise((resolve) => {
|
|
94
|
-
this.getCanvasData(svg, backgroundColor).then(
|
|
205
|
+
this.getCanvasData(svg, { backgroundColor }).then(
|
|
95
206
|
(canvas: HTMLCanvasElement) => {
|
|
96
|
-
const base64 = canvas.toDataURL('
|
|
207
|
+
const base64 = canvas.toDataURL(`image/${fileType ?? 'png'}`)
|
|
97
208
|
// 输出图片数据以及图片宽高
|
|
98
209
|
resolve({
|
|
99
210
|
data: base64,
|
|
@@ -105,26 +216,42 @@ export class Snapshot {
|
|
|
105
216
|
})
|
|
106
217
|
}
|
|
107
218
|
|
|
108
|
-
|
|
109
|
-
|
|
219
|
+
/**
|
|
220
|
+
* 获取Blob对象
|
|
221
|
+
* @param backgroundColor string | undefined
|
|
222
|
+
* @param fileType string | undefined
|
|
223
|
+
* @returns
|
|
224
|
+
*/
|
|
225
|
+
private async getSnapshotBlob(
|
|
226
|
+
backgroundColor?: string,
|
|
227
|
+
fileType?: string,
|
|
228
|
+
): Promise<SnapshotResponse> {
|
|
110
229
|
const svg = this.getSvgRootElement(this.lf)
|
|
230
|
+
await updateImageSource(svg as SVGElement)
|
|
111
231
|
return new Promise((resolve) => {
|
|
112
|
-
this.getCanvasData(svg, backgroundColor).then(
|
|
232
|
+
this.getCanvasData(svg, { backgroundColor }).then(
|
|
113
233
|
(canvas: HTMLCanvasElement) => {
|
|
114
|
-
canvas.toBlob(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
234
|
+
canvas.toBlob(
|
|
235
|
+
(blob) => {
|
|
236
|
+
// 输出图片数据以及图片宽高
|
|
237
|
+
resolve({
|
|
238
|
+
data: blob!,
|
|
239
|
+
width: canvas.width,
|
|
240
|
+
height: canvas.height,
|
|
241
|
+
})
|
|
242
|
+
},
|
|
243
|
+
`image/${fileType ?? 'png'}`,
|
|
244
|
+
)
|
|
122
245
|
},
|
|
123
246
|
)
|
|
124
247
|
})
|
|
125
248
|
}
|
|
126
249
|
|
|
127
|
-
|
|
250
|
+
/**
|
|
251
|
+
* 获取脚本css样式
|
|
252
|
+
* @returns rules string
|
|
253
|
+
*/
|
|
254
|
+
private getClassRules(): string {
|
|
128
255
|
let rules = ''
|
|
129
256
|
if (this.useGlobalRules) {
|
|
130
257
|
const { styleSheets } = document
|
|
@@ -141,37 +268,19 @@ export class Snapshot {
|
|
|
141
268
|
return rules
|
|
142
269
|
}
|
|
143
270
|
|
|
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
|
-
}
|
|
271
|
+
/**
|
|
272
|
+
* 获取图片生成中间产物canvas对象,用户转换为其他需要的格式
|
|
273
|
+
* @param svg Element
|
|
274
|
+
* @param toImageOptions ToImageOptions
|
|
275
|
+
* @returns Promise<HTMLCanvasElement>
|
|
276
|
+
*/
|
|
277
|
+
private async getCanvasData(
|
|
278
|
+
svg: Element,
|
|
279
|
+
toImageOptions: ToImageOptions,
|
|
280
|
+
): Promise<HTMLCanvasElement> {
|
|
281
|
+
const { width, height, backgroundColor, padding = 40 } = toImageOptions
|
|
282
|
+
const copy = this.cloneSvg(svg, false)
|
|
283
|
+
|
|
175
284
|
let dpr = window.devicePixelRatio || 1
|
|
176
285
|
if (dpr < 1) {
|
|
177
286
|
// https://github.com/didi/LogicFlow/issues/1222
|
|
@@ -187,46 +296,50 @@ export class Snapshot {
|
|
|
187
296
|
// 当dpr小于1的时候,我们强制转化为1,并不会产生绘制模糊等问题
|
|
188
297
|
dpr = 1
|
|
189
298
|
}
|
|
190
|
-
const canvas = document.createElement('canvas')
|
|
191
299
|
/*
|
|
192
300
|
为了计算真实宽高需要取图的真实dom
|
|
193
301
|
真实dom存在缩放影响其宽高数值
|
|
194
302
|
在得到真实宽高后除以缩放比例即可得到正常宽高
|
|
195
303
|
*/
|
|
304
|
+
|
|
196
305
|
const base = this.lf.graphModel.rootEl.querySelector('.lf-base')
|
|
197
306
|
const bbox = (base as Element).getBoundingClientRect()
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
.getBoundingClientRect()
|
|
307
|
+
const layoutCanvas = this.lf.container.querySelector('.lf-canvas-overlay')!
|
|
308
|
+
const layout = layoutCanvas.getBoundingClientRect()
|
|
201
309
|
const offsetX = bbox.x - layout.x
|
|
202
310
|
const offsetY = bbox.y - layout.y
|
|
203
311
|
const { graphModel } = this.lf
|
|
204
312
|
const { transformModel } = graphModel
|
|
205
313
|
const { SCALE_X, SCALE_Y, TRANSLATE_X, TRANSLATE_Y } = transformModel
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
314
|
+
;(copy.lastChild as SVGElement).style.transform = `matrix(1, 0, 0, 1, ${
|
|
315
|
+
(-offsetX + TRANSLATE_X) * (1 / SCALE_X)
|
|
316
|
+
}, ${(-offsetY + TRANSLATE_Y) * (1 / SCALE_Y)})`
|
|
317
|
+
// 包含所有元素的最小宽高
|
|
210
318
|
const bboxWidth = Math.ceil(bbox.width / SCALE_X)
|
|
211
319
|
const bboxHeight = Math.ceil(bbox.height / SCALE_Y)
|
|
212
|
-
|
|
320
|
+
const canvas = document.createElement('canvas')
|
|
213
321
|
canvas.style.width = `${bboxWidth}px`
|
|
214
322
|
canvas.style.height = `${bboxHeight}px`
|
|
215
|
-
|
|
216
|
-
canvas.
|
|
323
|
+
// 宽高值 默认加padding 40,保证图形不会紧贴着下载图片
|
|
324
|
+
canvas.width = bboxWidth * dpr + padding * 2
|
|
325
|
+
canvas.height = bboxHeight * dpr + padding * 2
|
|
217
326
|
const ctx = canvas.getContext('2d')
|
|
218
327
|
if (ctx) {
|
|
328
|
+
// 清空canvas
|
|
219
329
|
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
220
330
|
ctx.scale(dpr, dpr)
|
|
221
331
|
// 如果有背景色,设置流程图导出的背景色
|
|
222
332
|
if (backgroundColor) {
|
|
223
333
|
ctx.fillStyle = backgroundColor
|
|
224
|
-
ctx.fillRect(0, 0,
|
|
334
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
|
225
335
|
} else {
|
|
226
|
-
ctx.clearRect(0, 0,
|
|
336
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
227
337
|
}
|
|
228
338
|
}
|
|
339
|
+
|
|
229
340
|
const img = new Image()
|
|
341
|
+
|
|
342
|
+
// 设置css样式
|
|
230
343
|
const style = document.createElement('style')
|
|
231
344
|
style.innerHTML = this.getClassRules()
|
|
232
345
|
const foreignObject = document.createElement('foreignObject')
|
|
@@ -238,26 +351,37 @@ export class Snapshot {
|
|
|
238
351
|
try {
|
|
239
352
|
if (isFirefox) {
|
|
240
353
|
createImageBitmap(img, {
|
|
241
|
-
resizeWidth:
|
|
242
|
-
|
|
354
|
+
resizeWidth:
|
|
355
|
+
width && height
|
|
356
|
+
? copyCanvas(canvas, width, height).width
|
|
357
|
+
: canvas.width,
|
|
358
|
+
resizeHeight:
|
|
359
|
+
width && height
|
|
360
|
+
? copyCanvas(canvas, width, height).height
|
|
361
|
+
: canvas.height,
|
|
243
362
|
}).then((imageBitmap) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
363
|
+
ctx?.drawImage(imageBitmap, padding / dpr, padding / dpr)
|
|
364
|
+
resolve(
|
|
365
|
+
width && height ? copyCanvas(canvas, width, height) : canvas,
|
|
366
|
+
)
|
|
247
367
|
})
|
|
248
368
|
} else {
|
|
249
|
-
ctx?.drawImage(img,
|
|
250
|
-
resolve(
|
|
369
|
+
ctx?.drawImage(img, padding / dpr, padding / dpr)
|
|
370
|
+
resolve(
|
|
371
|
+
width && height ? copyCanvas(canvas, width, height) : canvas,
|
|
372
|
+
)
|
|
251
373
|
}
|
|
374
|
+
// 如果局部渲染本来是开启的,继续开启
|
|
375
|
+
// partial && this.lf.graphModel.setPartial(true)
|
|
252
376
|
} catch (e) {
|
|
253
|
-
ctx?.drawImage(img,
|
|
254
|
-
resolve(canvas)
|
|
377
|
+
ctx?.drawImage(img, padding / dpr, padding / dpr)
|
|
378
|
+
resolve(width && height ? copyCanvas(canvas, width, height) : canvas)
|
|
255
379
|
}
|
|
256
380
|
}
|
|
381
|
+
|
|
257
382
|
/*
|
|
258
383
|
因为svg中存在dom存放在foreignObject元素中
|
|
259
|
-
|
|
260
|
-
todo: 会导致一些清晰度问题这个需要再解决
|
|
384
|
+
svg dom => Base64编码字符串 挂载到img上
|
|
261
385
|
fixme: XMLSerializer的中的css background url不会下载图片
|
|
262
386
|
*/
|
|
263
387
|
const svg2Img = `data:image/svg+xml;charset=utf-8,${new XMLSerializer().serializeToString(
|
|
@@ -270,6 +394,48 @@ export class Snapshot {
|
|
|
270
394
|
img.src = imgSrc
|
|
271
395
|
})
|
|
272
396
|
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* 克隆并处理画布节点
|
|
400
|
+
* @param svg Node
|
|
401
|
+
* @returns
|
|
402
|
+
*/
|
|
403
|
+
private cloneSvg(svg: Element, addStyle: boolean = true): Node {
|
|
404
|
+
const copy = svg.cloneNode(true)
|
|
405
|
+
const graph = copy.lastChild
|
|
406
|
+
let childLength = graph?.childNodes?.length
|
|
407
|
+
if (childLength) {
|
|
408
|
+
for (let i = 0; i < childLength; i++) {
|
|
409
|
+
const lfLayer = graph?.childNodes[i] as SVGGraphicsElement
|
|
410
|
+
// 只保留包含节点和边的基础图层进行下载,其他图层删除
|
|
411
|
+
const layerClassList =
|
|
412
|
+
lfLayer.classList && Array.from(lfLayer.classList)
|
|
413
|
+
if (layerClassList && layerClassList.indexOf('lf-base') < 0) {
|
|
414
|
+
graph?.removeChild(graph.childNodes[i])
|
|
415
|
+
childLength--
|
|
416
|
+
i--
|
|
417
|
+
} else {
|
|
418
|
+
// 删除锚点
|
|
419
|
+
const lfBase = graph?.childNodes[i]
|
|
420
|
+
lfBase &&
|
|
421
|
+
lfBase.childNodes.forEach((item) => {
|
|
422
|
+
const element = item as SVGGraphicsElement
|
|
423
|
+
this.removeAnchor(element.firstChild!)
|
|
424
|
+
this.removeRotateControl(element.firstChild!)
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// 设置css样式
|
|
430
|
+
if (addStyle) {
|
|
431
|
+
const style = document.createElement('style')
|
|
432
|
+
style.innerHTML = this.getClassRules()
|
|
433
|
+
const foreignObject = document.createElement('foreignObject')
|
|
434
|
+
foreignObject.appendChild(style)
|
|
435
|
+
copy.appendChild(foreignObject)
|
|
436
|
+
}
|
|
437
|
+
return copy
|
|
438
|
+
}
|
|
273
439
|
}
|
|
274
440
|
|
|
275
441
|
export default Snapshot
|