@bpmnkit/core 0.0.8
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 +185 -0
- package/dist/bpmn/auto-layout.d.ts +10 -0
- package/dist/bpmn/auto-layout.js +203 -0
- package/dist/bpmn/bpmn-builder.d.ts +381 -0
- package/dist/bpmn/bpmn-builder.js +1223 -0
- package/dist/bpmn/bpmn-model.d.ts +403 -0
- package/dist/bpmn/bpmn-model.js +2 -0
- package/dist/bpmn/bpmn-parser.d.ts +4 -0
- package/dist/bpmn/bpmn-parser.js +642 -0
- package/dist/bpmn/bpmn-serializer.d.ts +4 -0
- package/dist/bpmn/bpmn-serializer.js +515 -0
- package/dist/bpmn/compact.d.ts +91 -0
- package/dist/bpmn/compact.js +349 -0
- package/dist/bpmn/di-color.d.ts +9 -0
- package/dist/bpmn/di-color.js +32 -0
- package/dist/bpmn/index.d.ts +105 -0
- package/dist/bpmn/index.js +176 -0
- package/dist/bpmn/optimize/feel.d.ts +4 -0
- package/dist/bpmn/optimize/feel.js +186 -0
- package/dist/bpmn/optimize/flow.d.ts +4 -0
- package/dist/bpmn/optimize/flow.js +323 -0
- package/dist/bpmn/optimize/index.d.ts +5 -0
- package/dist/bpmn/optimize/index.js +51 -0
- package/dist/bpmn/optimize/naming.d.ts +8 -0
- package/dist/bpmn/optimize/naming.js +145 -0
- package/dist/bpmn/optimize/tasks.d.ts +4 -0
- package/dist/bpmn/optimize/tasks.js +273 -0
- package/dist/bpmn/optimize/types.d.ts +44 -0
- package/dist/bpmn/optimize/types.js +2 -0
- package/dist/bpmn/optimize/utils.d.ts +23 -0
- package/dist/bpmn/optimize/utils.js +129 -0
- package/dist/bpmn/rest-connector.d.ts +48 -0
- package/dist/bpmn/rest-connector.js +69 -0
- package/dist/bpmn/svg.d.ts +35 -0
- package/dist/bpmn/svg.js +557 -0
- package/dist/bpmn/type-guards.d.ts +136 -0
- package/dist/bpmn/type-guards.js +211 -0
- package/dist/bpmn/utils.d.ts +88 -0
- package/dist/bpmn/utils.js +207 -0
- package/dist/bpmn/zeebe-extensions.d.ts +72 -0
- package/dist/bpmn/zeebe-extensions.js +96 -0
- package/dist/dmn/compact.d.ts +44 -0
- package/dist/dmn/compact.js +125 -0
- package/dist/dmn/dmn-builder.d.ts +59 -0
- package/dist/dmn/dmn-builder.js +144 -0
- package/dist/dmn/dmn-layout.d.ts +57 -0
- package/dist/dmn/dmn-layout.js +256 -0
- package/dist/dmn/dmn-model.d.ts +158 -0
- package/dist/dmn/dmn-model.js +2 -0
- package/dist/dmn/dmn-parser.d.ts +4 -0
- package/dist/dmn/dmn-parser.js +275 -0
- package/dist/dmn/dmn-serializer.d.ts +4 -0
- package/dist/dmn/dmn-serializer.js +296 -0
- package/dist/dmn/index.d.ts +28 -0
- package/dist/dmn/index.js +51 -0
- package/dist/errors.d.ts +77 -0
- package/dist/errors.js +66 -0
- package/dist/form/compact.d.ts +20 -0
- package/dist/form/compact.js +146 -0
- package/dist/form/form-builder.d.ts +111 -0
- package/dist/form/form-builder.js +347 -0
- package/dist/form/form-model.d.ts +221 -0
- package/dist/form/form-model.js +2 -0
- package/dist/form/form-parser.d.ts +4 -0
- package/dist/form/form-parser.js +514 -0
- package/dist/form/form-serializer.d.ts +4 -0
- package/dist/form/form-serializer.js +240 -0
- package/dist/form/index.d.ts +27 -0
- package/dist/form/index.js +43 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +19 -0
- package/dist/layout/bench.d.ts +86 -0
- package/dist/layout/bench.js +225 -0
- package/dist/layout/coordinates.d.ts +62 -0
- package/dist/layout/coordinates.js +868 -0
- package/dist/layout/crossing.d.ts +8 -0
- package/dist/layout/crossing.js +60 -0
- package/dist/layout/graph.d.ts +28 -0
- package/dist/layout/graph.js +126 -0
- package/dist/layout/index.d.ts +7 -0
- package/dist/layout/index.js +5 -0
- package/dist/layout/layers.d.ts +13 -0
- package/dist/layout/layers.js +49 -0
- package/dist/layout/layout-engine.d.ts +21 -0
- package/dist/layout/layout-engine.js +191 -0
- package/dist/layout/overlap.d.ts +10 -0
- package/dist/layout/overlap.js +70 -0
- package/dist/layout/routing.d.ts +26 -0
- package/dist/layout/routing.js +439 -0
- package/dist/layout/subprocess.d.ts +14 -0
- package/dist/layout/subprocess.js +77 -0
- package/dist/layout/types.d.ts +72 -0
- package/dist/layout/types.js +37 -0
- package/dist/types/id-generator.d.ts +8 -0
- package/dist/types/id-generator.js +31 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.js +2 -0
- package/dist/types/xml-element.d.ts +12 -0
- package/dist/types/xml-element.js +2 -0
- package/dist/xml/index.d.ts +2 -0
- package/dist/xml/index.js +2 -0
- package/dist/xml/xml-parser.d.ts +13 -0
- package/dist/xml/xml-parser.js +271 -0
- package/package.json +38 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const REST_CONNECTOR_TYPE = "io.camunda:http-json:1";
|
|
2
|
+
const DEFAULT_TIMEOUT = 20;
|
|
3
|
+
const DEFAULT_RETRIES = "3";
|
|
4
|
+
/** Convert a REST connector config into Zeebe IO mapping inputs. */
|
|
5
|
+
export function restConnectorToIoMappingInputs(config) {
|
|
6
|
+
const inputs = [];
|
|
7
|
+
const auth = config.authentication ?? { type: "noAuth" };
|
|
8
|
+
inputs.push({ source: auth.type, target: "authentication.type" });
|
|
9
|
+
if (auth.type === "bearer") {
|
|
10
|
+
inputs.push({ source: auth.token, target: "authentication.token" });
|
|
11
|
+
}
|
|
12
|
+
inputs.push({ source: config.method, target: "method" });
|
|
13
|
+
inputs.push({ source: config.url, target: "url" });
|
|
14
|
+
if (config.headers !== undefined) {
|
|
15
|
+
const value = typeof config.headers === "string" ? config.headers : serializeFeelObject(config.headers);
|
|
16
|
+
inputs.push({ source: value, target: "headers" });
|
|
17
|
+
}
|
|
18
|
+
if (config.queryParameters !== undefined) {
|
|
19
|
+
const value = typeof config.queryParameters === "string"
|
|
20
|
+
? config.queryParameters
|
|
21
|
+
: serializeFeelObject(config.queryParameters);
|
|
22
|
+
inputs.push({ source: value, target: "queryParameters" });
|
|
23
|
+
}
|
|
24
|
+
if (config.body !== undefined) {
|
|
25
|
+
inputs.push({ source: config.body, target: "body" });
|
|
26
|
+
}
|
|
27
|
+
inputs.push({
|
|
28
|
+
source: String(config.connectionTimeoutInSeconds ?? DEFAULT_TIMEOUT),
|
|
29
|
+
target: "connectionTimeoutInSeconds",
|
|
30
|
+
});
|
|
31
|
+
inputs.push({
|
|
32
|
+
source: String(config.readTimeoutInSeconds ?? DEFAULT_TIMEOUT),
|
|
33
|
+
target: "readTimeoutInSeconds",
|
|
34
|
+
});
|
|
35
|
+
return inputs;
|
|
36
|
+
}
|
|
37
|
+
/** Get the Zeebe task definition type for REST connectors. */
|
|
38
|
+
export function restConnectorTaskType() {
|
|
39
|
+
return REST_CONNECTOR_TYPE;
|
|
40
|
+
}
|
|
41
|
+
/** Get the default retries value for REST connectors. */
|
|
42
|
+
export function restConnectorRetries(config) {
|
|
43
|
+
return config.retries ?? DEFAULT_RETRIES;
|
|
44
|
+
}
|
|
45
|
+
/** Convert a REST connector config into Zeebe task header entries. */
|
|
46
|
+
export function restConnectorToTaskHeaders(config) {
|
|
47
|
+
const headers = [];
|
|
48
|
+
if (config.resultVariable !== undefined) {
|
|
49
|
+
headers.push({ key: "resultVariable", value: config.resultVariable });
|
|
50
|
+
}
|
|
51
|
+
if (config.resultExpression !== undefined) {
|
|
52
|
+
headers.push({
|
|
53
|
+
key: "resultExpression",
|
|
54
|
+
value: config.resultExpression,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
if (config.retryBackoff !== undefined) {
|
|
58
|
+
headers.push({ key: "retryBackoff", value: config.retryBackoff });
|
|
59
|
+
}
|
|
60
|
+
return headers;
|
|
61
|
+
}
|
|
62
|
+
/** Serialize a Record<string, string> into a FEEL context expression. */
|
|
63
|
+
function serializeFeelObject(obj) {
|
|
64
|
+
const entries = Object.entries(obj)
|
|
65
|
+
.map(([key, value]) => `"${key}":"${value}"`)
|
|
66
|
+
.join(", ");
|
|
67
|
+
return `={${entries}}`;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=rest-connector.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { BpmnDefinitions } from "./bpmn-model.js";
|
|
2
|
+
export interface SvgExportOptions {
|
|
3
|
+
/** Visual theme. Default: "light". */
|
|
4
|
+
theme?: "light" | "dark";
|
|
5
|
+
/** Padding around the diagram content in pixels. Default: 20. */
|
|
6
|
+
padding?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generates a self-contained SVG string from a `BpmnDefinitions` object.
|
|
10
|
+
*
|
|
11
|
+
* The definitions must already contain diagram interchange (DI) position data —
|
|
12
|
+
* i.e. they were produced by `.withAutoLayout().build()` or parsed from BPMN
|
|
13
|
+
* XML that includes `<bpmndi:BPMNDiagram>` elements.
|
|
14
|
+
*
|
|
15
|
+
* Works in every environment (Node.js, browsers, Deno, Bun, edge runtimes) with
|
|
16
|
+
* zero external dependencies. Returns a standalone SVG string that can be written
|
|
17
|
+
* to a file, embedded in HTML, or converted to PNG via a canvas API.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* import { Bpmn, exportSvg } from "@bpmnkit/core"
|
|
22
|
+
*
|
|
23
|
+
* const defs = Bpmn.createProcess("order")
|
|
24
|
+
* .startEvent("start")
|
|
25
|
+
* .serviceTask("process", { name: "Process Order", taskType: "process" })
|
|
26
|
+
* .endEvent("end")
|
|
27
|
+
* .withAutoLayout()
|
|
28
|
+
* .build()
|
|
29
|
+
*
|
|
30
|
+
* const svg = exportSvg(defs) // light theme
|
|
31
|
+
* const dark = exportSvg(defs, { theme: "dark" }) // dark theme
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function exportSvg(defs: BpmnDefinitions, options?: SvgExportOptions): string;
|
|
35
|
+
//# sourceMappingURL=svg.d.ts.map
|
package/dist/bpmn/svg.js
ADDED
|
@@ -0,0 +1,557 @@
|
|
|
1
|
+
const LIGHT = {
|
|
2
|
+
bg: "#f8f9fa",
|
|
3
|
+
shapeFill: "#ffffff",
|
|
4
|
+
shapeStroke: "#404040",
|
|
5
|
+
flowStroke: "#404040",
|
|
6
|
+
text: "#333333",
|
|
7
|
+
poolHeader: "rgba(0,0,0,0.04)",
|
|
8
|
+
textHalo: "#f8f9fa",
|
|
9
|
+
};
|
|
10
|
+
const DARK = {
|
|
11
|
+
bg: "#1e1e2e",
|
|
12
|
+
shapeFill: "#2a2a3e",
|
|
13
|
+
shapeStroke: "#8888bb",
|
|
14
|
+
flowStroke: "#7777aa",
|
|
15
|
+
text: "#cdd6f4",
|
|
16
|
+
poolHeader: "rgba(255,255,255,0.06)",
|
|
17
|
+
textHalo: "#1e1e2e",
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Generates a self-contained SVG string from a `BpmnDefinitions` object.
|
|
21
|
+
*
|
|
22
|
+
* The definitions must already contain diagram interchange (DI) position data —
|
|
23
|
+
* i.e. they were produced by `.withAutoLayout().build()` or parsed from BPMN
|
|
24
|
+
* XML that includes `<bpmndi:BPMNDiagram>` elements.
|
|
25
|
+
*
|
|
26
|
+
* Works in every environment (Node.js, browsers, Deno, Bun, edge runtimes) with
|
|
27
|
+
* zero external dependencies. Returns a standalone SVG string that can be written
|
|
28
|
+
* to a file, embedded in HTML, or converted to PNG via a canvas API.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* import { Bpmn, exportSvg } from "@bpmnkit/core"
|
|
33
|
+
*
|
|
34
|
+
* const defs = Bpmn.createProcess("order")
|
|
35
|
+
* .startEvent("start")
|
|
36
|
+
* .serviceTask("process", { name: "Process Order", taskType: "process" })
|
|
37
|
+
* .endEvent("end")
|
|
38
|
+
* .withAutoLayout()
|
|
39
|
+
* .build()
|
|
40
|
+
*
|
|
41
|
+
* const svg = exportSvg(defs) // light theme
|
|
42
|
+
* const dark = exportSvg(defs, { theme: "dark" }) // dark theme
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function exportSvg(defs, options) {
|
|
46
|
+
const t = options?.theme === "dark" ? DARK : LIGHT;
|
|
47
|
+
const padding = options?.padding ?? 20;
|
|
48
|
+
const plane = defs.diagrams[0]?.plane;
|
|
49
|
+
if (!plane)
|
|
50
|
+
return emptySvg(t);
|
|
51
|
+
// Build model index for name/type lookups
|
|
52
|
+
const idx = buildIndex(defs);
|
|
53
|
+
// ── Bounding box ──────────────────────────────────────────────────────────
|
|
54
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
55
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
56
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
57
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
58
|
+
for (const shape of plane.shapes) {
|
|
59
|
+
const { x, y, width, height } = shape.bounds;
|
|
60
|
+
minX = Math.min(minX, x);
|
|
61
|
+
minY = Math.min(minY, y);
|
|
62
|
+
maxX = Math.max(maxX, x + width);
|
|
63
|
+
maxY = Math.max(maxY, y + height);
|
|
64
|
+
}
|
|
65
|
+
for (const edge of plane.edges) {
|
|
66
|
+
for (const wp of edge.waypoints) {
|
|
67
|
+
minX = Math.min(minX, wp.x);
|
|
68
|
+
minY = Math.min(minY, wp.y);
|
|
69
|
+
maxX = Math.max(maxX, wp.x);
|
|
70
|
+
maxY = Math.max(maxY, wp.y);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!Number.isFinite(minX))
|
|
74
|
+
return emptySvg(t);
|
|
75
|
+
const ox = minX - padding;
|
|
76
|
+
const oy = minY - padding;
|
|
77
|
+
const vw = maxX - minX + padding * 2;
|
|
78
|
+
const vh = maxY - minY + padding * 2;
|
|
79
|
+
// ── Edges ─────────────────────────────────────────────────────────────────
|
|
80
|
+
const edgeParts = [];
|
|
81
|
+
for (const edge of plane.edges) {
|
|
82
|
+
if (edge.waypoints.length < 2)
|
|
83
|
+
continue;
|
|
84
|
+
const flow = idx.flows.get(edge.bpmnElement);
|
|
85
|
+
const isMsg = idx.messageFlowIds.has(edge.bpmnElement);
|
|
86
|
+
const isAssoc = !flow && !isMsg;
|
|
87
|
+
const d = waypointsToPath(edge.waypoints);
|
|
88
|
+
const strokeColor = t.flowStroke;
|
|
89
|
+
const sw = 1.5;
|
|
90
|
+
if (isAssoc) {
|
|
91
|
+
edgeParts.push(`<path d="${d}" fill="none" stroke="${strokeColor}" stroke-width="${sw}" stroke-dasharray="5 3"/>`);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const dashArray = isMsg ? ' stroke-dasharray="6 3"' : "";
|
|
95
|
+
edgeParts.push(`<path d="${d}" fill="none" stroke="${strokeColor}" stroke-width="${sw}"${dashArray} marker-end="url(#arr)"/>`);
|
|
96
|
+
// Default-flow slash
|
|
97
|
+
if (flow && idx.defaultFlowIds.has(edge.bpmnElement)) {
|
|
98
|
+
const wp0 = edge.waypoints[0];
|
|
99
|
+
const wp1 = edge.waypoints[1];
|
|
100
|
+
if (wp0 && wp1) {
|
|
101
|
+
const dx = wp1.x - wp0.x;
|
|
102
|
+
const dy = wp1.y - wp0.y;
|
|
103
|
+
const len = Math.sqrt(dx * dx + dy * dy);
|
|
104
|
+
if (len > 0) {
|
|
105
|
+
const nx = dx / len;
|
|
106
|
+
const ny = dy / len;
|
|
107
|
+
const tDist = Math.min(10, len * 0.25);
|
|
108
|
+
const cx = wp0.x + nx * tDist;
|
|
109
|
+
const cy = wp0.y + ny * tDist;
|
|
110
|
+
const s = 5;
|
|
111
|
+
edgeParts.push(`<line x1="${cx - ny * s}" y1="${cy + nx * s}" x2="${cx + ny * s}" y2="${cy - nx * s}" stroke="${strokeColor}" stroke-width="${sw}"/>`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Edge label
|
|
116
|
+
if (flow?.name && edge.label?.bounds) {
|
|
117
|
+
const { x, y, width, height } = edge.label.bounds;
|
|
118
|
+
edgeParts.push(labelSvg(flow.name, x + width / 2, y + height / 2, width - 4, t));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// ── Shapes ────────────────────────────────────────────────────────────────
|
|
122
|
+
const shapeParts = [];
|
|
123
|
+
const labelParts = [];
|
|
124
|
+
for (const shape of plane.shapes) {
|
|
125
|
+
const { x, y, width, height } = shape.bounds;
|
|
126
|
+
const el = idx.elements.get(shape.bpmnElement);
|
|
127
|
+
const type = el?.type ?? "";
|
|
128
|
+
let inner;
|
|
129
|
+
if (isEvent(type)) {
|
|
130
|
+
inner = renderEvent(el, width, height, t);
|
|
131
|
+
}
|
|
132
|
+
else if (isGateway(type)) {
|
|
133
|
+
inner = renderGateway(el, width, height, t);
|
|
134
|
+
}
|
|
135
|
+
else if (type === "" && !el) {
|
|
136
|
+
// Pool, lane, or annotation
|
|
137
|
+
const annotation = idx.annotations.get(shape.bpmnElement);
|
|
138
|
+
if (annotation !== undefined) {
|
|
139
|
+
inner = renderAnnotation(annotation.text, width, height, t);
|
|
140
|
+
}
|
|
141
|
+
else if (idx.participants.has(shape.bpmnElement)) {
|
|
142
|
+
inner = renderPool(idx.participants.get(shape.bpmnElement), width, height, t);
|
|
143
|
+
}
|
|
144
|
+
else if (idx.lanes.has(shape.bpmnElement)) {
|
|
145
|
+
inner = renderLane(idx.lanes.get(shape.bpmnElement), width, height, t);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
inner = "";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
inner = renderTask(el, width, height, t);
|
|
153
|
+
}
|
|
154
|
+
if (inner) {
|
|
155
|
+
shapeParts.push(`<g transform="translate(${x} ${y})">${inner}</g>`);
|
|
156
|
+
}
|
|
157
|
+
// External labels for events and gateways
|
|
158
|
+
const isExtLabel = isEvent(type) || isGateway(type);
|
|
159
|
+
if (el?.name && isExtLabel) {
|
|
160
|
+
const lb = shape.label?.bounds ?? {
|
|
161
|
+
x: shape.bounds.x + shape.bounds.width / 2 - 40,
|
|
162
|
+
y: shape.bounds.y + shape.bounds.height + 6,
|
|
163
|
+
width: 80,
|
|
164
|
+
height: 20,
|
|
165
|
+
};
|
|
166
|
+
labelParts.push(labelSvg(el.name, lb.x + lb.width / 2, lb.y + lb.height / 2, lb.width - 4, t, true));
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
// ── Assemble SVG ──────────────────────────────────────────────────────────
|
|
170
|
+
const bgStyle = `fill="${t.bg}"`;
|
|
171
|
+
const arrowFill = t.flowStroke;
|
|
172
|
+
return [
|
|
173
|
+
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="${ox} ${oy} ${vw} ${vh}" width="${vw}" height="${vh}">`,
|
|
174
|
+
`<rect x="${ox}" y="${oy}" width="${vw}" height="${vh}" ${bgStyle}/>`,
|
|
175
|
+
"<defs>",
|
|
176
|
+
` <marker id="arr" markerWidth="8" markerHeight="6" refX="7" refY="3" orient="auto" markerUnits="strokeWidth">`,
|
|
177
|
+
` <path d="M0,0 L8,3 L0,6 Z" fill="${arrowFill}"/>`,
|
|
178
|
+
" </marker>",
|
|
179
|
+
"</defs>",
|
|
180
|
+
...edgeParts,
|
|
181
|
+
...shapeParts,
|
|
182
|
+
...labelParts,
|
|
183
|
+
"</svg>",
|
|
184
|
+
].join("\n");
|
|
185
|
+
}
|
|
186
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
187
|
+
function emptySvg(t) {
|
|
188
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1 1" width="1" height="1"><rect width="1" height="1" fill="${t.bg}"/></svg>`;
|
|
189
|
+
}
|
|
190
|
+
function isEvent(type) {
|
|
191
|
+
return (type === "startEvent" ||
|
|
192
|
+
type === "endEvent" ||
|
|
193
|
+
type === "intermediateCatchEvent" ||
|
|
194
|
+
type === "intermediateThrowEvent" ||
|
|
195
|
+
type === "boundaryEvent");
|
|
196
|
+
}
|
|
197
|
+
function isGateway(type) {
|
|
198
|
+
return (type === "exclusiveGateway" ||
|
|
199
|
+
type === "parallelGateway" ||
|
|
200
|
+
type === "inclusiveGateway" ||
|
|
201
|
+
type === "eventBasedGateway" ||
|
|
202
|
+
type === "complexGateway");
|
|
203
|
+
}
|
|
204
|
+
// ── Text ──────────────────────────────────────────────────────────────────────
|
|
205
|
+
const AVG_CHAR_PX = 6.5;
|
|
206
|
+
function wrapText(text, maxPx) {
|
|
207
|
+
if (!text.trim())
|
|
208
|
+
return [];
|
|
209
|
+
const words = text.split(/\s+/);
|
|
210
|
+
const lines = [];
|
|
211
|
+
let line = "";
|
|
212
|
+
for (const word of words) {
|
|
213
|
+
const candidate = line ? `${line} ${word}` : word;
|
|
214
|
+
if (candidate.length * AVG_CHAR_PX <= maxPx) {
|
|
215
|
+
line = candidate;
|
|
216
|
+
}
|
|
217
|
+
else if (line) {
|
|
218
|
+
lines.push(line);
|
|
219
|
+
line = word;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
line = word;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (line)
|
|
226
|
+
lines.push(line);
|
|
227
|
+
return lines.length > 0 ? lines : [text];
|
|
228
|
+
}
|
|
229
|
+
function esc(s) {
|
|
230
|
+
return s
|
|
231
|
+
.replace(/&/g, "&")
|
|
232
|
+
.replace(/</g, "<")
|
|
233
|
+
.replace(/>/g, ">")
|
|
234
|
+
.replace(/"/g, """);
|
|
235
|
+
}
|
|
236
|
+
function textStyle(t) {
|
|
237
|
+
return `fill:${t.text};font-family:system-ui,-apple-system,sans-serif;font-size:11px;text-anchor:middle;dominant-baseline:central;paint-order:stroke;stroke:${t.textHalo};stroke-width:4px;stroke-linejoin:round`;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Generates SVG text element(s) for a label centred at (cx, cy).
|
|
241
|
+
* topAlign=true makes multi-line text flow downward from cy rather than
|
|
242
|
+
* being centred around it — prevents long labels from overlapping the shape above.
|
|
243
|
+
*/
|
|
244
|
+
function labelSvg(text, cx, cy, maxWidth, t, topAlign = false) {
|
|
245
|
+
const lines = wrapText(text, maxWidth);
|
|
246
|
+
const lineH = 14;
|
|
247
|
+
const style = textStyle(t);
|
|
248
|
+
if (lines.length === 1) {
|
|
249
|
+
return `<text style="${style}" x="${cx}" y="${cy}">${esc(lines[0] ?? text)}</text>`;
|
|
250
|
+
}
|
|
251
|
+
const totalH = lines.length * lineH;
|
|
252
|
+
const startY = topAlign ? cy + lineH / 2 : cy - totalH / 2 + lineH / 2;
|
|
253
|
+
return lines
|
|
254
|
+
.map((l, i) => `<text style="${style}" x="${cx}" y="${startY + i * lineH}">${esc(l)}</text>`)
|
|
255
|
+
.join("");
|
|
256
|
+
}
|
|
257
|
+
// ── Path ──────────────────────────────────────────────────────────────────────
|
|
258
|
+
function waypointsToPath(wps) {
|
|
259
|
+
if (wps.length < 2)
|
|
260
|
+
return "";
|
|
261
|
+
const r = 4;
|
|
262
|
+
const parts = [];
|
|
263
|
+
for (let i = 0; i < wps.length; i++) {
|
|
264
|
+
const wp = wps[i];
|
|
265
|
+
if (!wp)
|
|
266
|
+
continue;
|
|
267
|
+
if (i === 0) {
|
|
268
|
+
parts.push(`M${wp.x},${wp.y}`);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (i === wps.length - 1) {
|
|
272
|
+
parts.push(`L${wp.x},${wp.y}`);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
const prev = wps[i - 1];
|
|
276
|
+
const next = wps[i + 1];
|
|
277
|
+
if (!prev || !next)
|
|
278
|
+
continue;
|
|
279
|
+
const dx1 = wp.x - prev.x;
|
|
280
|
+
const dy1 = wp.y - prev.y;
|
|
281
|
+
const d1 = Math.sqrt(dx1 * dx1 + dy1 * dy1);
|
|
282
|
+
const dx2 = next.x - wp.x;
|
|
283
|
+
const dy2 = next.y - wp.y;
|
|
284
|
+
const d2 = Math.sqrt(dx2 * dx2 + dy2 * dy2);
|
|
285
|
+
if (d1 < 0.01 || d2 < 0.01) {
|
|
286
|
+
parts.push(`L${wp.x},${wp.y}`);
|
|
287
|
+
continue;
|
|
288
|
+
}
|
|
289
|
+
const radius = Math.min(r, d1 / 2, d2 / 2);
|
|
290
|
+
const ax = wp.x - (dx1 / d1) * radius;
|
|
291
|
+
const ay = wp.y - (dy1 / d1) * radius;
|
|
292
|
+
const bx = wp.x + (dx2 / d2) * radius;
|
|
293
|
+
const by = wp.y + (dy2 / d2) * radius;
|
|
294
|
+
parts.push(`L${ax},${ay}`);
|
|
295
|
+
parts.push(`Q${wp.x},${wp.y} ${bx},${by}`);
|
|
296
|
+
}
|
|
297
|
+
return parts.join(" ");
|
|
298
|
+
}
|
|
299
|
+
// ── Task icons (14×14, origin at 0,0) ────────────────────────────────────────
|
|
300
|
+
function taskIcon(type) {
|
|
301
|
+
switch (type) {
|
|
302
|
+
case "manualTask":
|
|
303
|
+
return `<path d="M3 8V4.5a1 1 0 012 0V8M5 7V3a1 1 0 012 0v4M7 6a1 1 0 012 0v1.5M9 7.5a1 1 0 012 0V9c0 2.5-1.5 4-4.5 4H5c-2.5 0-4-1.5-4-4V8" class="bi"/>`;
|
|
304
|
+
case "serviceTask":
|
|
305
|
+
return `<circle cx="7" cy="7" r="5.5" class="bi"/><circle cx="7" cy="7" r="2" class="bi"/><path d="M7 1v2.5M7 10.5v2.5M1 7h2.5M10.5 7h2.5M2.8 2.8l1.7 1.7M9.5 9.5l1.7 1.7M11.2 2.8l-1.7 1.7M4.5 9.5l-1.7 1.7" class="bi"/>`;
|
|
306
|
+
case "userTask":
|
|
307
|
+
return `<circle cx="7" cy="4.5" r="2.5" class="bi"/><path d="M1.5 14Q1.5 9 7 9Q12.5 9 12.5 14" class="bi"/>`;
|
|
308
|
+
case "scriptTask":
|
|
309
|
+
return `<rect x="2" y="0.5" width="10" height="13" rx="1" class="bi"/><path d="M4 4h6M4 7h6M4 10h4" class="bi"/>`;
|
|
310
|
+
case "sendTask":
|
|
311
|
+
return `<path d="M1.5 3.5h11v8h-11z" class="bis"/><path d="M1.5 3.5l5.5 4.5 5.5-4.5M1.5 11.5l4-3.5M12.5 11.5l-4-3.5" fill="none" stroke="var(--_sf)" stroke-width="1.5"/>`;
|
|
312
|
+
case "receiveTask":
|
|
313
|
+
return `<rect x="1.5" y="3.5" width="11" height="8" class="bi"/><path d="M1.5 3.5l5.5 4.5 5.5-4.5" class="bi"/>`;
|
|
314
|
+
case "businessRuleTask":
|
|
315
|
+
return `<rect x="1" y="1" width="12" height="12" class="bi"/><path d="M1 4.5h12M4 1v12" class="bi"/>`;
|
|
316
|
+
default:
|
|
317
|
+
return "";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// ── Event markers (centred at 0,0) ────────────────────────────────────────────
|
|
321
|
+
function eventMarker(defType, filled) {
|
|
322
|
+
const c = filled ? "bis" : "bi";
|
|
323
|
+
switch (defType) {
|
|
324
|
+
case "timer":
|
|
325
|
+
return `<circle cx="0" cy="0" r="5" class="${c}"/><path d="M0 -3.5v3.5l2 2" class="bi"/>`;
|
|
326
|
+
case "message":
|
|
327
|
+
return `<rect x="-5" y="-3.5" width="10" height="7" class="${c}"/><path d="M-5 -3.5l5 4 5-4" class="${c}" ${filled ? 'fill="none" stroke="var(--_sf)"' : ""}/>`;
|
|
328
|
+
case "signal":
|
|
329
|
+
return `<path d="M0 -5.5l5.5 10h-11z" class="${c}"/>`;
|
|
330
|
+
case "error":
|
|
331
|
+
return `<path d="M-2 -5l2.5 4.5-3.5 0.5L2 5l-2.5-4.5 3.5-0.5z" class="${c}"/>`;
|
|
332
|
+
case "escalation":
|
|
333
|
+
return `<path d="M0 -5.5l3.5 9.5-3.5-3.5-3.5 3.5z" class="${c}"/>`;
|
|
334
|
+
case "compensate":
|
|
335
|
+
return `<path d="M1 -3.5l-5 3.5 5 3.5zM6 -3.5l-5 3.5 5 3.5z" class="${c}"/>`;
|
|
336
|
+
case "conditional":
|
|
337
|
+
return `<rect x="-4.5" y="-5.5" width="9" height="11" rx="1" class="${c}"/><path d="M-2.5 -2.5h5M-2.5 0h5M-2.5 2.5h3" class="bi"/>`;
|
|
338
|
+
case "link":
|
|
339
|
+
return `<path d="M-2 -3.5v7l5.5-3.5z" class="${c}"/><path d="M-6 0h4" class="bi"/>`;
|
|
340
|
+
case "cancel":
|
|
341
|
+
return `<path d="M-4 -4l8 8M4 -4l-8 8" class="${c}"/>`;
|
|
342
|
+
case "terminate":
|
|
343
|
+
return `<circle cx="0" cy="0" r="5" class="bis"/>`;
|
|
344
|
+
default:
|
|
345
|
+
return "";
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
// ── Gateway markers ────────────────────────────────────────────────────────────
|
|
349
|
+
function gatewayMarker(type) {
|
|
350
|
+
switch (type) {
|
|
351
|
+
case "exclusiveGateway":
|
|
352
|
+
return `<path d="M-6 -6l12 12M6 -6l-12 12" class="gms"/>`;
|
|
353
|
+
case "parallelGateway":
|
|
354
|
+
return `<path d="M0 -8v16M-8 0h16" class="gms"/>`;
|
|
355
|
+
case "inclusiveGateway":
|
|
356
|
+
return `<circle cx="0" cy="0" r="6" class="gms"/>`;
|
|
357
|
+
case "eventBasedGateway":
|
|
358
|
+
return `<circle cx="0" cy="0" r="7" class="gms"/><circle cx="0" cy="0" r="5" class="gms"/><path d="M0 -4L3.8 1.8H-3.8Z" class="gm"/>`;
|
|
359
|
+
case "complexGateway":
|
|
360
|
+
return `<path d="M0 -8v16M-8 0h16M-5.7 -5.7l11.4 11.4M5.7 -5.7l-11.4 11.4" class="gms"/>`;
|
|
361
|
+
default:
|
|
362
|
+
return "";
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
// ── Sub-process marker ────────────────────────────────────────────────────────
|
|
366
|
+
function subProcessMarker(type) {
|
|
367
|
+
if (type === "adHocSubProcess") {
|
|
368
|
+
return `<path d="M-7 0Q-4 -4 0 0Q4 4 7 0" class="bi"/>`;
|
|
369
|
+
}
|
|
370
|
+
return `<rect x="-7" y="-7" width="14" height="14" rx="1" class="bi"/><path d="M0 -4v8M-4 0h8" class="bi"/>`;
|
|
371
|
+
}
|
|
372
|
+
// ── Shape renderers ────────────────────────────────────────────────────────────
|
|
373
|
+
function shapeStyle(t, sw = 1.5, dash) {
|
|
374
|
+
return `fill:${t.shapeFill};stroke:${t.shapeStroke};stroke-width:${sw}${dash ? `;stroke-dasharray:${dash}` : ""}`;
|
|
375
|
+
}
|
|
376
|
+
function iconStyle(t) {
|
|
377
|
+
return `fill:none;stroke:${t.shapeStroke};stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round`;
|
|
378
|
+
}
|
|
379
|
+
function iconSolidStyle(t) {
|
|
380
|
+
return `fill:${t.shapeStroke};stroke:none`;
|
|
381
|
+
}
|
|
382
|
+
function gwMarkerStyle(t) {
|
|
383
|
+
return `fill:none;stroke:${t.shapeStroke};stroke-width:2.5;stroke-linecap:round`;
|
|
384
|
+
}
|
|
385
|
+
function gwMarkerFillStyle(t) {
|
|
386
|
+
return `fill:${t.shapeStroke};stroke:none`;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Wraps raw icon SVG (which uses class="bi", "bis", "gm", "gms")
|
|
390
|
+
* in a `<g>` that provides those class definitions as inline style overrides.
|
|
391
|
+
* CSS classes cannot be used in standalone SVGs, so we map them to inline styles
|
|
392
|
+
* via a local `<style>` element scoped to the enclosing `<g>`.
|
|
393
|
+
*/
|
|
394
|
+
function iconGroup(content, x, y, t) {
|
|
395
|
+
const bi = iconStyle(t);
|
|
396
|
+
const bis = iconSolidStyle(t);
|
|
397
|
+
const gm = gwMarkerFillStyle(t);
|
|
398
|
+
const gms = gwMarkerStyle(t);
|
|
399
|
+
// Inline style for sendTask envelope fill colour (matches shape fill)
|
|
400
|
+
const sf = t.shapeFill;
|
|
401
|
+
return `<g transform="translate(${x} ${y})"><style>.bi{${bi}}.bis{${bis}}.gm{${gm}}.gms{${gms}}</style>${content.replace(/var\(--_sf\)/g, sf)}</g>`;
|
|
402
|
+
}
|
|
403
|
+
function renderEvent(el, width, height, t) {
|
|
404
|
+
const cx = width / 2;
|
|
405
|
+
const cy = height / 2;
|
|
406
|
+
const r = Math.min(cx, cy) - 1;
|
|
407
|
+
const isEnd = el?.type === "endEvent";
|
|
408
|
+
const isIntermediate = el?.type === "intermediateCatchEvent" ||
|
|
409
|
+
el?.type === "intermediateThrowEvent" ||
|
|
410
|
+
el?.type === "boundaryEvent";
|
|
411
|
+
const isThrow = el?.type === "intermediateThrowEvent" ||
|
|
412
|
+
(el?.type === "boundaryEvent" && el.cancelActivity === false);
|
|
413
|
+
const outerSw = isEnd ? 3 : 1.5;
|
|
414
|
+
const outerStyle = `fill:${t.shapeFill};stroke:${t.shapeStroke};stroke-width:${outerSw}`;
|
|
415
|
+
let out = `<circle cx="${cx}" cy="${cy}" r="${r}" style="${outerStyle}"/>`;
|
|
416
|
+
if (isIntermediate) {
|
|
417
|
+
const isNonInt = el?.type === "boundaryEvent" && el.cancelActivity === false;
|
|
418
|
+
const innerStyle = `fill:none;stroke:${t.shapeStroke};stroke-width:1.5${isNonInt ? ";stroke-dasharray:4 2" : ""}`;
|
|
419
|
+
out += `<circle cx="${cx}" cy="${cy}" r="${r - 3}" style="${innerStyle}"/>`;
|
|
420
|
+
}
|
|
421
|
+
const eventDef = el && "eventDefinitions" in el && el.eventDefinitions.length > 0
|
|
422
|
+
? el.eventDefinitions[0]
|
|
423
|
+
: undefined;
|
|
424
|
+
if (eventDef) {
|
|
425
|
+
const marker = eventMarker(eventDef.type, isThrow && !isEnd);
|
|
426
|
+
if (marker) {
|
|
427
|
+
out += iconGroup(marker, cx, cy, t);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return out;
|
|
431
|
+
}
|
|
432
|
+
function renderTask(el, width, height, t) {
|
|
433
|
+
const type = el?.type ?? "";
|
|
434
|
+
let sw = 1.5;
|
|
435
|
+
let dash;
|
|
436
|
+
if (type === "callActivity")
|
|
437
|
+
sw = 3;
|
|
438
|
+
else if (type === "eventSubProcess")
|
|
439
|
+
dash = "5 3";
|
|
440
|
+
const bodyStyle = shapeStyle(t, sw, dash);
|
|
441
|
+
let out = `<rect x="0" y="0" width="${width}" height="${height}" rx="10" style="${bodyStyle}"/>`;
|
|
442
|
+
if (type === "transaction") {
|
|
443
|
+
const innerStyle = iconStyle(t);
|
|
444
|
+
out += `<rect x="3" y="3" width="${width - 6}" height="${height - 6}" rx="8" style="${innerStyle}"/>`;
|
|
445
|
+
}
|
|
446
|
+
const icon = taskIcon(type);
|
|
447
|
+
if (icon) {
|
|
448
|
+
out += iconGroup(icon, 4, 4, t);
|
|
449
|
+
}
|
|
450
|
+
if (el?.name) {
|
|
451
|
+
out += labelSvg(el.name, width / 2, height / 2, width - 16, t);
|
|
452
|
+
}
|
|
453
|
+
if (type === "subProcess" ||
|
|
454
|
+
type === "adHocSubProcess" ||
|
|
455
|
+
type === "eventSubProcess" ||
|
|
456
|
+
type === "transaction") {
|
|
457
|
+
out += iconGroup(subProcessMarker(type), width / 2, height - 10, t);
|
|
458
|
+
}
|
|
459
|
+
return out;
|
|
460
|
+
}
|
|
461
|
+
function renderGateway(el, width, height, t) {
|
|
462
|
+
const cx = width / 2;
|
|
463
|
+
const cy = height / 2;
|
|
464
|
+
const bodyStyle = shapeStyle(t);
|
|
465
|
+
const points = `${cx},0 ${width},${cy} ${cx},${height} 0,${cy}`;
|
|
466
|
+
let out = `<polygon points="${points}" style="${bodyStyle}"/>`;
|
|
467
|
+
const marker = el ? gatewayMarker(el.type) : "";
|
|
468
|
+
if (marker) {
|
|
469
|
+
out += iconGroup(marker, cx, cy, t);
|
|
470
|
+
}
|
|
471
|
+
return out;
|
|
472
|
+
}
|
|
473
|
+
function renderPool(participant, width, height, t) {
|
|
474
|
+
const bodyStyle = `fill:${t.shapeFill};stroke:${t.shapeStroke};stroke-width:1.5`;
|
|
475
|
+
const headerStyle = `fill:${t.poolHeader};stroke:${t.shapeStroke};stroke-width:1.5`;
|
|
476
|
+
let out = `<rect x="0" y="0" width="${width}" height="${height}" style="${bodyStyle}"/>` +
|
|
477
|
+
`<rect x="0" y="0" width="30" height="${height}" style="${headerStyle}"/>`;
|
|
478
|
+
if (participant?.name) {
|
|
479
|
+
out += `<text style="${textStyle(t)}" transform="translate(15 ${height / 2}) rotate(-90)">${esc(participant.name)}</text>`;
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
function renderLane(lane, width, height, t) {
|
|
484
|
+
const bodyStyle = `fill:${t.shapeFill};stroke:${t.shapeStroke};stroke-width:1.5`;
|
|
485
|
+
const headerStyle = `fill:${t.poolHeader};stroke:${t.shapeStroke};stroke-width:1.5`;
|
|
486
|
+
let out = `<rect x="0" y="0" width="${width}" height="${height}" style="${bodyStyle}"/>` +
|
|
487
|
+
`<rect x="0" y="0" width="30" height="${height}" style="${headerStyle}"/>`;
|
|
488
|
+
if (lane?.name) {
|
|
489
|
+
out += `<text style="${textStyle(t)}" transform="translate(15 ${height / 2}) rotate(-90)">${esc(lane.name)}</text>`;
|
|
490
|
+
}
|
|
491
|
+
return out;
|
|
492
|
+
}
|
|
493
|
+
function renderAnnotation(text, width, height, t) {
|
|
494
|
+
const pathStyle = `fill:none;stroke:${t.shapeStroke};stroke-width:1.5`;
|
|
495
|
+
let out = `<path d="M${width} 0 L0 0 L0 ${height} L${width} ${height}" style="${pathStyle}"/>`;
|
|
496
|
+
if (text) {
|
|
497
|
+
out += labelSvg(text, width / 2, height / 2, width - 8, t);
|
|
498
|
+
}
|
|
499
|
+
return out;
|
|
500
|
+
}
|
|
501
|
+
function buildIndex(defs) {
|
|
502
|
+
const elements = new Map();
|
|
503
|
+
const flows = new Map();
|
|
504
|
+
const annotations = new Map();
|
|
505
|
+
const participants = new Map();
|
|
506
|
+
const lanes = new Map();
|
|
507
|
+
const messageFlowIds = new Set();
|
|
508
|
+
const defaultFlowIds = new Set();
|
|
509
|
+
function indexElements(flowElements, seqFlows) {
|
|
510
|
+
for (const el of flowElements) {
|
|
511
|
+
elements.set(el.id, el);
|
|
512
|
+
if (el.type === "subProcess" ||
|
|
513
|
+
el.type === "adHocSubProcess" ||
|
|
514
|
+
el.type === "eventSubProcess" ||
|
|
515
|
+
el.type === "transaction") {
|
|
516
|
+
indexElements(el.flowElements, el.sequenceFlows);
|
|
517
|
+
}
|
|
518
|
+
if ((el.type === "exclusiveGateway" ||
|
|
519
|
+
el.type === "inclusiveGateway" ||
|
|
520
|
+
el.type === "complexGateway") &&
|
|
521
|
+
el.default) {
|
|
522
|
+
defaultFlowIds.add(el.default);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
for (const sf of seqFlows) {
|
|
526
|
+
flows.set(sf.id, sf);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
function indexLaneSet(laneSet) {
|
|
530
|
+
for (const lane of laneSet.lanes) {
|
|
531
|
+
lanes.set(lane.id, lane);
|
|
532
|
+
if (lane.childLaneSet)
|
|
533
|
+
indexLaneSet(lane.childLaneSet);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (const proc of defs.processes) {
|
|
537
|
+
indexElements(proc.flowElements, proc.sequenceFlows);
|
|
538
|
+
for (const ta of proc.textAnnotations) {
|
|
539
|
+
annotations.set(ta.id, ta);
|
|
540
|
+
}
|
|
541
|
+
if (proc.laneSet)
|
|
542
|
+
indexLaneSet(proc.laneSet);
|
|
543
|
+
}
|
|
544
|
+
for (const collab of defs.collaborations) {
|
|
545
|
+
for (const p of collab.participants) {
|
|
546
|
+
participants.set(p.id, p);
|
|
547
|
+
}
|
|
548
|
+
for (const ta of collab.textAnnotations) {
|
|
549
|
+
annotations.set(ta.id, ta);
|
|
550
|
+
}
|
|
551
|
+
for (const mf of collab.messageFlows) {
|
|
552
|
+
messageFlowIds.add(mf.id);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
return { elements, flows, annotations, participants, lanes, messageFlowIds, defaultFlowIds };
|
|
556
|
+
}
|
|
557
|
+
//# sourceMappingURL=svg.js.map
|