@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.
- package/README.md +16 -0
- package/package.json +10 -7
- package/.turbo/turbo-build.log +0 -38
- package/CHANGELOG.md +0 -1829
- package/__test__/bpmn-adapter.test.js +0 -227
- package/es/materials/curved-edge/__test__/curved-edge.test.d.ts +0 -1
- package/es/materials/curved-edge/__test__/curved-edge.test.js +0 -18
- package/jest.config.js +0 -198
- package/lib/materials/curved-edge/__test__/curved-edge.test.d.ts +0 -1
- package/lib/materials/curved-edge/__test__/curved-edge.test.js +0 -20
- package/rollup.config.js +0 -52
- package/src/NodeResize/BasicShape/Ellipse.tsx +0 -22
- package/src/NodeResize/BasicShape/Polygon.tsx +0 -24
- package/src/NodeResize/BasicShape/Rect.tsx +0 -44
- package/src/NodeResize/control/Control.tsx +0 -537
- package/src/NodeResize/control/ControlGroup.tsx +0 -76
- package/src/NodeResize/control/Util.ts +0 -206
- package/src/NodeResize/index.ts +0 -26
- package/src/NodeResize/node/DiamondResize.tsx +0 -149
- package/src/NodeResize/node/EllipseResize.tsx +0 -140
- package/src/NodeResize/node/HtmlResize.tsx +0 -125
- package/src/NodeResize/node/RectResize.tsx +0 -126
- package/src/NodeResize/node/index.ts +0 -4
- package/src/bpmn/constant.ts +0 -56
- package/src/bpmn/events/EndEvent.ts +0 -73
- package/src/bpmn/events/StartEvent.ts +0 -52
- package/src/bpmn/events/index.ts +0 -2
- package/src/bpmn/flow/SequenceFlow.ts +0 -25
- package/src/bpmn/flow/index.ts +0 -1
- package/src/bpmn/gateways/ExclusiveGateway.ts +0 -71
- package/src/bpmn/gateways/index.ts +0 -1
- package/src/bpmn/getBpmnId.ts +0 -31
- package/src/bpmn/index.ts +0 -60
- package/src/bpmn/tasks/ServiceTask.ts +0 -63
- package/src/bpmn/tasks/UserTask.ts +0 -64
- package/src/bpmn/tasks/index.ts +0 -2
- package/src/bpmn-adapter/bpmnIds.ts +0 -31
- package/src/bpmn-adapter/index.ts +0 -835
- package/src/bpmn-adapter/json2xml.ts +0 -127
- package/src/bpmn-adapter/xml2json.ts +0 -544
- package/src/bpmn-elements/README.md +0 -223
- package/src/bpmn-elements/__tests__/definition.test.js +0 -72
- package/src/bpmn-elements/index.d.ts +0 -26
- package/src/bpmn-elements/index.ts +0 -107
- package/src/bpmn-elements/presets/Event/EndEventFactory.ts +0 -114
- package/src/bpmn-elements/presets/Event/IntermediateCatchEvent.ts +0 -108
- package/src/bpmn-elements/presets/Event/IntermediateThrowEvent.ts +0 -109
- package/src/bpmn-elements/presets/Event/StartEventFactory.ts +0 -114
- package/src/bpmn-elements/presets/Event/boundaryEventFactory.ts +0 -117
- package/src/bpmn-elements/presets/Event/index.ts +0 -14
- package/src/bpmn-elements/presets/Flow/flow.d.ts +0 -6
- package/src/bpmn-elements/presets/Flow/index.ts +0 -8
- package/src/bpmn-elements/presets/Flow/manhattan.ts +0 -691
- package/src/bpmn-elements/presets/Flow/sequenceFlow.ts +0 -65
- package/src/bpmn-elements/presets/Gateway/gateway.ts +0 -107
- package/src/bpmn-elements/presets/Gateway/index.ts +0 -23
- package/src/bpmn-elements/presets/Pool/Lane.ts +0 -211
- package/src/bpmn-elements/presets/Pool/Pool.ts +0 -284
- package/src/bpmn-elements/presets/Pool/index.ts +0 -89
- package/src/bpmn-elements/presets/Task/index.ts +0 -122
- package/src/bpmn-elements/presets/Task/subProcess.ts +0 -189
- package/src/bpmn-elements/presets/Task/task.ts +0 -193
- package/src/bpmn-elements/presets/icons.ts +0 -155
- package/src/bpmn-elements/utils.ts +0 -52
- package/src/bpmn-elements-adapter/README.md +0 -293
- package/src/bpmn-elements-adapter/__tests__/adapter_in.test.js +0 -528
- package/src/bpmn-elements-adapter/__tests__/adapter_out.test.js +0 -569
- package/src/bpmn-elements-adapter/constant.ts +0 -76
- package/src/bpmn-elements-adapter/index.ts +0 -1134
- package/src/bpmn-elements-adapter/json2xml.ts +0 -105
- package/src/bpmn-elements-adapter/xml2json.ts +0 -542
- package/src/components/context-menu/index.ts +0 -253
- package/src/components/control/index.ts +0 -155
- package/src/components/dnd-panel/index.ts +0 -137
- package/src/components/highlight/index.ts +0 -227
- package/src/components/menu/index.ts +0 -748
- package/src/components/mini-map/index.ts +0 -686
- package/src/components/selection-select/index.ts +0 -387
- package/src/dynamic-group/index.ts +0 -774
- package/src/dynamic-group/model.ts +0 -580
- package/src/dynamic-group/node.ts +0 -288
- package/src/dynamic-group/utils.ts +0 -46
- package/src/index.less +0 -1
- package/src/index.ts +0 -47
- package/src/insert-node-in-polyline/edge.ts +0 -175
- package/src/insert-node-in-polyline/index.ts +0 -193
- package/src/materials/curved-edge/__test__/curved-edge.test.ts +0 -46
- package/src/materials/curved-edge/index.ts +0 -217
- package/src/materials/group/GroupNode.ts +0 -437
- package/src/materials/group/index.ts +0 -542
- package/src/materials/node-selection/index.ts +0 -380
- package/src/mindmap/fakerRoot.ts +0 -19
- package/src/mindmap/index.ts +0 -328
- package/src/mindmap/markContent.ts +0 -81
- package/src/mindmap/markContentOption.ts +0 -81
- package/src/mindmap/markEntity.ts +0 -82
- package/src/mindmap/markRoot.ts +0 -83
- package/src/mindmap/theme.ts +0 -11
- package/src/pool/LaneModel.ts +0 -226
- package/src/pool/LaneView.ts +0 -220
- package/src/pool/PoolModel.ts +0 -631
- package/src/pool/PoolView.ts +0 -75
- package/src/pool/constant.ts +0 -19
- package/src/pool/index.ts +0 -621
- package/src/pool/utils.ts +0 -46
- package/src/rect-label-node/RectLabelNodeView.ts +0 -33
- package/src/rect-label-node/index.ts +0 -15
- package/src/style/index.less +0 -381
- package/src/style/raw.ts +0 -328
- package/src/tools/auto-layout/index.ts +0 -282
- package/src/tools/flow-path/index.ts +0 -233
- package/src/tools/label/Label.tsx +0 -357
- package/src/tools/label/LabelModel.ts +0 -83
- package/src/tools/label/LabelOverlay.tsx +0 -162
- package/src/tools/label/algorithm.ts +0 -42
- package/src/tools/label/index.ts +0 -479
- package/src/tools/label/mediumEditor.ts +0 -121
- package/src/tools/label/utils.ts +0 -395
- package/src/tools/proximity-connect/index.ts +0 -435
- package/src/tools/snapshot/README.md +0 -145
- package/src/tools/snapshot/index.ts +0 -701
- package/src/tools/snapshot/utils.ts +0 -163
- package/src/turbo-adapter/index.ts +0 -212
- package/stats.html +0 -4842
- 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
|