prince-ui-bpmn 1.0.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/LICENSE +21 -0
- package/dist/index.css +434 -0
- package/dist/index.css.map +1 -0
- package/dist/index.d.ts +284 -0
- package/dist/index.js +1173 -0
- package/dist/index.js.map +1 -0
- package/package.json +75 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1173 @@
|
|
|
1
|
+
import { useRef, useState, useEffect, useCallback, useMemo } from 'react';
|
|
2
|
+
import { Button, Badge, SearchField, AnalyticalTable, SegmentedControl, Segment, Notice, Link } from 'prince-ui';
|
|
3
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/theme/diagram-theme.ts
|
|
6
|
+
function readToken(name, fallback) {
|
|
7
|
+
if (typeof window === "undefined" || !document?.documentElement) return fallback;
|
|
8
|
+
const value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
9
|
+
return value || fallback;
|
|
10
|
+
}
|
|
11
|
+
function isDarkMode(scheme = "auto") {
|
|
12
|
+
if (scheme === "dark") return true;
|
|
13
|
+
if (scheme === "light") return false;
|
|
14
|
+
if (typeof document !== "undefined") {
|
|
15
|
+
const attr = document.documentElement.getAttribute("data-theme");
|
|
16
|
+
if (attr === "dark" || attr === "cu") return true;
|
|
17
|
+
if (attr === "light") return false;
|
|
18
|
+
}
|
|
19
|
+
if (typeof window !== "undefined" && window.matchMedia) {
|
|
20
|
+
return window.matchMedia("(prefers-color-scheme: dark)").matches;
|
|
21
|
+
}
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
function getDiagramColors(scheme = "auto") {
|
|
25
|
+
const dark = isDarkMode(scheme);
|
|
26
|
+
return {
|
|
27
|
+
defaultFillColor: readToken("--prn-bg-elevated", dark ? "#1c1c1e" : "#ffffff"),
|
|
28
|
+
defaultStrokeColor: readToken("--prn-label", dark ? "#e4e4e6" : "#1d1d1f"),
|
|
29
|
+
defaultLabelColor: readToken("--prn-label", dark ? "#e4e4e6" : "#1d1d1f"),
|
|
30
|
+
canvasBackground: readToken("--prn-bg", dark ? "#000000" : "#f5f5f7"),
|
|
31
|
+
accent: readToken("--prn-accent", "#a0d22b")
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
function onThemeChange(onChange) {
|
|
35
|
+
if (typeof window === "undefined" || !window.MutationObserver) return () => {
|
|
36
|
+
};
|
|
37
|
+
const observer = new MutationObserver((mutations) => {
|
|
38
|
+
for (const m of mutations) {
|
|
39
|
+
if (m.type === "attributes" && m.attributeName === "data-theme") {
|
|
40
|
+
onChange();
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
observer.observe(document.documentElement, {
|
|
46
|
+
attributes: true,
|
|
47
|
+
attributeFilter: ["data-theme"]
|
|
48
|
+
});
|
|
49
|
+
let media;
|
|
50
|
+
if (window.matchMedia) {
|
|
51
|
+
media = window.matchMedia("(prefers-color-scheme: dark)");
|
|
52
|
+
media.addEventListener?.("change", onChange);
|
|
53
|
+
}
|
|
54
|
+
return () => {
|
|
55
|
+
observer.disconnect();
|
|
56
|
+
media?.removeEventListener?.("change", onChange);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// src/theme/apple-renderer.ts
|
|
61
|
+
var APPLE_FONT = '-apple-system, BlinkMacSystemFont, "SF Pro Text", system-ui, "Helvetica Neue", sans-serif';
|
|
62
|
+
function buildRendererConfig(colors) {
|
|
63
|
+
const labelStyle = {
|
|
64
|
+
fill: colors.defaultLabelColor,
|
|
65
|
+
fontFamily: APPLE_FONT,
|
|
66
|
+
fontSize: 12,
|
|
67
|
+
fontWeight: 500
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
bpmnRenderer: {
|
|
71
|
+
defaultFillColor: colors.defaultFillColor,
|
|
72
|
+
defaultStrokeColor: colors.defaultStrokeColor,
|
|
73
|
+
defaultLabelColor: colors.defaultLabelColor
|
|
74
|
+
},
|
|
75
|
+
textRenderer: {
|
|
76
|
+
defaultStyle: labelStyle,
|
|
77
|
+
externalStyle: labelStyle
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function createAppleRendererModule(BpmnRenderer) {
|
|
82
|
+
function AppleRenderer(...args) {
|
|
83
|
+
Reflect.apply(BpmnRenderer, this, args);
|
|
84
|
+
}
|
|
85
|
+
AppleRenderer.prototype = Object.create(BpmnRenderer.prototype);
|
|
86
|
+
AppleRenderer.prototype.constructor = AppleRenderer;
|
|
87
|
+
AppleRenderer.prototype.canRender = function canRender(element) {
|
|
88
|
+
const parentProto = Object.getPrototypeOf(AppleRenderer.prototype);
|
|
89
|
+
return parentProto.canRender.call(this, element);
|
|
90
|
+
};
|
|
91
|
+
AppleRenderer.prototype.drawShape = function drawShape(parentNode, element) {
|
|
92
|
+
const parentProto = Object.getPrototypeOf(AppleRenderer.prototype);
|
|
93
|
+
const gfx = parentProto.drawShape.call(this, parentNode, element);
|
|
94
|
+
try {
|
|
95
|
+
const type = element.type ?? "";
|
|
96
|
+
if (type.includes("Task") || type.includes("SubProcess") || type.includes("CallActivity")) {
|
|
97
|
+
const rect = parentNode.querySelector("rect");
|
|
98
|
+
if (rect) {
|
|
99
|
+
rect.setAttribute("rx", "12");
|
|
100
|
+
rect.setAttribute("ry", "12");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const main = gfx ?? parentNode;
|
|
104
|
+
const stroke = main.querySelector?.("rect, circle, polygon, path");
|
|
105
|
+
if (stroke && stroke.getAttribute("stroke-width") === "2") {
|
|
106
|
+
stroke.setAttribute("stroke-width", "1.5");
|
|
107
|
+
}
|
|
108
|
+
const isLabel = type === "label";
|
|
109
|
+
if (!isLabel && main?.style) {
|
|
110
|
+
main.style.filter = "drop-shadow(0 1px 2px rgba(0, 0, 0, 0.12))";
|
|
111
|
+
}
|
|
112
|
+
} catch {
|
|
113
|
+
}
|
|
114
|
+
return gfx;
|
|
115
|
+
};
|
|
116
|
+
AppleRenderer.prototype.drawConnection = function drawConnection(parentNode, element) {
|
|
117
|
+
const parentProto = Object.getPrototypeOf(AppleRenderer.prototype);
|
|
118
|
+
const gfx = parentProto.drawConnection.call(this, parentNode, element);
|
|
119
|
+
try {
|
|
120
|
+
const main = gfx ?? parentNode;
|
|
121
|
+
const path = main.querySelector?.("path");
|
|
122
|
+
if (path && path.getAttribute("stroke-width") === "2") {
|
|
123
|
+
path.setAttribute("stroke-width", "1.5");
|
|
124
|
+
}
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
return gfx;
|
|
128
|
+
};
|
|
129
|
+
AppleRenderer.$inject = BpmnRenderer.$inject ?? [
|
|
130
|
+
"config.bpmnRenderer",
|
|
131
|
+
"eventBus",
|
|
132
|
+
"styles",
|
|
133
|
+
"pathMap",
|
|
134
|
+
"canvas",
|
|
135
|
+
"textRenderer"
|
|
136
|
+
];
|
|
137
|
+
return {
|
|
138
|
+
__init__: ["appleRenderer"],
|
|
139
|
+
appleRenderer: ["type", AppleRenderer]
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// src/viewer/status.ts
|
|
144
|
+
var STATUS_TOKEN = {
|
|
145
|
+
active: "--prn-blue",
|
|
146
|
+
completed: "--prn-green",
|
|
147
|
+
incident: "--prn-red",
|
|
148
|
+
canceled: "--prn-orange"
|
|
149
|
+
};
|
|
150
|
+
var STATUS_LABEL = {
|
|
151
|
+
active: "Aktiv",
|
|
152
|
+
completed: "Abgeschlossen",
|
|
153
|
+
incident: "Fehler",
|
|
154
|
+
canceled: "Abgebrochen"
|
|
155
|
+
};
|
|
156
|
+
function buildActivityMap(activities) {
|
|
157
|
+
const map = /* @__PURE__ */ new Map();
|
|
158
|
+
for (const activity of activities) {
|
|
159
|
+
if (activity.activityId) {
|
|
160
|
+
map.set(activity.activityId, activity);
|
|
161
|
+
if (activity.id && activity.id !== activity.activityId) {
|
|
162
|
+
map.set(activity.id, activity);
|
|
163
|
+
}
|
|
164
|
+
} else if (activity.id) {
|
|
165
|
+
map.set(activity.id, activity);
|
|
166
|
+
}
|
|
167
|
+
if (activity.activityName) {
|
|
168
|
+
const key = activity.activityName.toLowerCase().trim();
|
|
169
|
+
if (key && !map.has(key)) map.set(key, activity);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return map;
|
|
173
|
+
}
|
|
174
|
+
function findMultiExecutionIds(activities) {
|
|
175
|
+
const counts = /* @__PURE__ */ new Map();
|
|
176
|
+
for (const a of activities) {
|
|
177
|
+
if (a.activityId) counts.set(a.activityId, (counts.get(a.activityId) ?? 0) + 1);
|
|
178
|
+
}
|
|
179
|
+
const multi = /* @__PURE__ */ new Set();
|
|
180
|
+
for (const [id, count] of counts) if (count > 1) multi.add(id);
|
|
181
|
+
return multi;
|
|
182
|
+
}
|
|
183
|
+
function isFlowNode(element) {
|
|
184
|
+
const type = element.type ?? element.businessObject?.$type ?? "";
|
|
185
|
+
return type.includes("Task") || type.includes("Event") || type.includes("Gateway") || type.includes("CallActivity") || type.includes("SubProcess");
|
|
186
|
+
}
|
|
187
|
+
function isSequenceFlow(element) {
|
|
188
|
+
const type = element.type ?? element.businessObject?.$type ?? "";
|
|
189
|
+
return type === "bpmn:SequenceFlow";
|
|
190
|
+
}
|
|
191
|
+
function matchActivity(element, map) {
|
|
192
|
+
const elementId = element.id ?? element.businessObject?.id;
|
|
193
|
+
const boId = element.businessObject?.id ?? element.id;
|
|
194
|
+
if (!elementId) return null;
|
|
195
|
+
if (map.has(elementId)) return map.get(elementId);
|
|
196
|
+
if (boId && map.has(boId)) return map.get(boId);
|
|
197
|
+
const lowerId = elementId.toLowerCase();
|
|
198
|
+
for (const [key, activity] of map) {
|
|
199
|
+
if (key.toLowerCase() === lowerId || activity.activityId?.toLowerCase() === lowerId) {
|
|
200
|
+
return activity;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
const type = element.type ?? element.businessObject?.$type ?? "";
|
|
204
|
+
if (type.includes("CallActivity") || type.includes("Call")) {
|
|
205
|
+
const name = element.businessObject?.name?.toLowerCase().trim();
|
|
206
|
+
if (name) {
|
|
207
|
+
if (map.has(name)) return map.get(name);
|
|
208
|
+
for (const activity of map.values()) {
|
|
209
|
+
if (activity.activityName?.toLowerCase().trim() === name || activity.activityId?.toLowerCase().trim() === name) {
|
|
210
|
+
return activity;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
function hasIncident(ids, incidents) {
|
|
218
|
+
const set = new Set(ids.filter(Boolean));
|
|
219
|
+
if (set.size === 0) return false;
|
|
220
|
+
return incidents.some(
|
|
221
|
+
(i) => i.activityId != null && set.has(i.activityId) || i.failedActivityId != null && set.has(i.failedActivityId)
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
function deriveStatus(element, activity, ctx) {
|
|
225
|
+
const elementId = element.id ?? element.businessObject?.id ?? "";
|
|
226
|
+
const activityId = activity?.activityId ?? void 0;
|
|
227
|
+
const ids = [elementId, activityId];
|
|
228
|
+
if (hasIncident(ids, ctx.incidents)) {
|
|
229
|
+
return { status: "incident", isMultiExecution: false };
|
|
230
|
+
}
|
|
231
|
+
const runtimeActive = ctx.runtimeActiveActivityIds != null && ids.some((id) => id != null && ctx.runtimeActiveActivityIds.has(id));
|
|
232
|
+
if (runtimeActive) return { status: "active", isMultiExecution: false };
|
|
233
|
+
if (!activity) return null;
|
|
234
|
+
const isCanceled = activity.cancelTime != null || activity.canceled === true;
|
|
235
|
+
const isMultiExecution = ctx.multiExecutionIds?.has(activity.activityId ?? elementId) ?? false;
|
|
236
|
+
if (activity.startTime && activity.endTime && !isCanceled) {
|
|
237
|
+
return { status: "completed", isMultiExecution };
|
|
238
|
+
}
|
|
239
|
+
if (activity.startTime && !activity.endTime && !isCanceled) {
|
|
240
|
+
return { status: "active", isMultiExecution: false };
|
|
241
|
+
}
|
|
242
|
+
if (activity.startTime && isCanceled) {
|
|
243
|
+
return { status: "canceled", isMultiExecution: false };
|
|
244
|
+
}
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
function isSequenceFlowExecuted(element, map) {
|
|
248
|
+
const sourceId = element.businessObject?.sourceRef?.id;
|
|
249
|
+
const targetId = element.businessObject?.targetRef?.id;
|
|
250
|
+
if (!sourceId || !targetId) return false;
|
|
251
|
+
const source = matchActivity({ id: sourceId, businessObject: { id: sourceId } }, map);
|
|
252
|
+
const target = matchActivity({ id: targetId, businessObject: { id: targetId } }, map);
|
|
253
|
+
return Boolean(
|
|
254
|
+
source?.endTime && !source.cancelTime && target?.startTime
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
function computeElementStatuses(elements, activities, options = {}) {
|
|
258
|
+
const incidents = options.incidents ?? [];
|
|
259
|
+
const runtimeActive = options.runtimeActiveActivityIds ? new Set(options.runtimeActiveActivityIds) : void 0;
|
|
260
|
+
const map = buildActivityMap(activities);
|
|
261
|
+
const multiExecutionIds = findMultiExecutionIds(activities);
|
|
262
|
+
const ctx = { incidents, runtimeActiveActivityIds: runtimeActive, multiExecutionIds };
|
|
263
|
+
const result = [];
|
|
264
|
+
for (const element of elements) {
|
|
265
|
+
if (!isFlowNode(element)) continue;
|
|
266
|
+
const elementId = element.id ?? element.businessObject?.id;
|
|
267
|
+
if (!elementId) continue;
|
|
268
|
+
const activity = matchActivity(element, map);
|
|
269
|
+
const derived = deriveStatus(element, activity, ctx);
|
|
270
|
+
if (!derived) continue;
|
|
271
|
+
result.push({
|
|
272
|
+
elementId,
|
|
273
|
+
status: derived.status,
|
|
274
|
+
token: STATUS_TOKEN[derived.status],
|
|
275
|
+
label: STATUS_LABEL[derived.status],
|
|
276
|
+
isMultiExecution: derived.isMultiExecution,
|
|
277
|
+
activity
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return result;
|
|
281
|
+
}
|
|
282
|
+
function computeExecutedFlows(elements, activities) {
|
|
283
|
+
const map = buildActivityMap(activities);
|
|
284
|
+
const result = [];
|
|
285
|
+
for (const element of elements) {
|
|
286
|
+
if (!isSequenceFlow(element)) continue;
|
|
287
|
+
const id = element.id ?? element.businessObject?.id;
|
|
288
|
+
if (id && isSequenceFlowExecuted(element, map)) result.push(id);
|
|
289
|
+
}
|
|
290
|
+
return result;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/viewer/overlays.ts
|
|
294
|
+
function statusMarker(status) {
|
|
295
|
+
return `prn-bpmn-status-${status}`;
|
|
296
|
+
}
|
|
297
|
+
var MULTI_EXECUTION_MARKER = "prn-bpmn-status-multi";
|
|
298
|
+
var FLOW_EXECUTED_MARKER = "prn-bpmn-flow-executed";
|
|
299
|
+
function allStatusMarkers() {
|
|
300
|
+
const statuses = ["active", "completed", "incident", "canceled"];
|
|
301
|
+
return [...statuses.map(statusMarker), MULTI_EXECUTION_MARKER];
|
|
302
|
+
}
|
|
303
|
+
function buildStatusOverlay(status, doc = document) {
|
|
304
|
+
const el = doc.createElement("div");
|
|
305
|
+
el.className = `prn-bpmn-overlay prn-bpmn-overlay-${status}`;
|
|
306
|
+
el.setAttribute("role", "img");
|
|
307
|
+
el.setAttribute("aria-label", STATUS_LABEL[status]);
|
|
308
|
+
el.title = STATUS_LABEL[status];
|
|
309
|
+
return el;
|
|
310
|
+
}
|
|
311
|
+
var STATUS_OVERLAY_POSITION = { top: -6, right: -6 };
|
|
312
|
+
function BpmnViewer({
|
|
313
|
+
xml,
|
|
314
|
+
activityHistory,
|
|
315
|
+
incidents,
|
|
316
|
+
runtimeActiveActivityIds,
|
|
317
|
+
colorScheme = "auto",
|
|
318
|
+
onElementClick,
|
|
319
|
+
fitOnResize = true,
|
|
320
|
+
showZoomControls = true,
|
|
321
|
+
height = "100%",
|
|
322
|
+
className
|
|
323
|
+
}) {
|
|
324
|
+
const containerRef = useRef(null);
|
|
325
|
+
const viewerRef = useRef(null);
|
|
326
|
+
const overlayIdsRef = useRef([]);
|
|
327
|
+
const [error, setError] = useState(null);
|
|
328
|
+
const [themeTick, setThemeTick] = useState(0);
|
|
329
|
+
const onElementClickRef = useRef(onElementClick);
|
|
330
|
+
onElementClickRef.current = onElementClick;
|
|
331
|
+
useEffect(() => onThemeChange(() => setThemeTick((t) => t + 1)), []);
|
|
332
|
+
const applyHighlighting = useCallback(() => {
|
|
333
|
+
const viewer = viewerRef.current;
|
|
334
|
+
if (!viewer) return;
|
|
335
|
+
try {
|
|
336
|
+
const elementRegistry = viewer.get("elementRegistry");
|
|
337
|
+
const canvas = viewer.get("canvas");
|
|
338
|
+
const overlays = viewer.get("overlays");
|
|
339
|
+
const all = elementRegistry.getAll();
|
|
340
|
+
const markers = [...allStatusMarkers(), FLOW_EXECUTED_MARKER];
|
|
341
|
+
for (const el of all) {
|
|
342
|
+
if (!el.id) continue;
|
|
343
|
+
for (const m of markers) canvas.removeMarker(el.id, m);
|
|
344
|
+
}
|
|
345
|
+
for (const oid of overlayIdsRef.current) {
|
|
346
|
+
try {
|
|
347
|
+
overlays.remove(oid);
|
|
348
|
+
} catch {
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
overlayIdsRef.current = [];
|
|
352
|
+
const statuses = computeElementStatuses(all, activityHistory ?? [], {
|
|
353
|
+
incidents,
|
|
354
|
+
runtimeActiveActivityIds
|
|
355
|
+
});
|
|
356
|
+
const doc = containerRef.current?.ownerDocument ?? document;
|
|
357
|
+
for (const entry of statuses) {
|
|
358
|
+
canvas.addMarker(entry.elementId, statusMarker(entry.status));
|
|
359
|
+
if (entry.isMultiExecution) {
|
|
360
|
+
canvas.addMarker(entry.elementId, MULTI_EXECUTION_MARKER);
|
|
361
|
+
}
|
|
362
|
+
try {
|
|
363
|
+
const id = overlays.add(entry.elementId, {
|
|
364
|
+
position: STATUS_OVERLAY_POSITION,
|
|
365
|
+
html: buildStatusOverlay(entry.status, doc)
|
|
366
|
+
});
|
|
367
|
+
overlayIdsRef.current.push(id);
|
|
368
|
+
} catch {
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const flows = computeExecutedFlows(all, activityHistory ?? []);
|
|
372
|
+
for (const id of flows) canvas.addMarker(id, FLOW_EXECUTED_MARKER);
|
|
373
|
+
} catch {
|
|
374
|
+
}
|
|
375
|
+
}, [activityHistory, incidents, runtimeActiveActivityIds]);
|
|
376
|
+
useEffect(() => {
|
|
377
|
+
let cancelled = false;
|
|
378
|
+
setError(null);
|
|
379
|
+
const init = async () => {
|
|
380
|
+
if (!containerRef.current || !xml || !xml.trim()) return;
|
|
381
|
+
if (viewerRef.current) {
|
|
382
|
+
try {
|
|
383
|
+
viewerRef.current.destroy();
|
|
384
|
+
} catch {
|
|
385
|
+
}
|
|
386
|
+
viewerRef.current = null;
|
|
387
|
+
}
|
|
388
|
+
overlayIdsRef.current = [];
|
|
389
|
+
const [{ default: NavigatedViewer }, { default: BpmnRenderer }] = await Promise.all([
|
|
390
|
+
import(
|
|
391
|
+
/* @vite-ignore */
|
|
392
|
+
'bpmn-js/lib/NavigatedViewer'
|
|
393
|
+
),
|
|
394
|
+
import(
|
|
395
|
+
/* @vite-ignore */
|
|
396
|
+
'bpmn-js/lib/draw/BpmnRenderer'
|
|
397
|
+
)
|
|
398
|
+
]);
|
|
399
|
+
if (cancelled || !containerRef.current) return;
|
|
400
|
+
const colors = getDiagramColors(colorScheme);
|
|
401
|
+
const viewer = new NavigatedViewer({
|
|
402
|
+
container: containerRef.current,
|
|
403
|
+
additionalModules: [
|
|
404
|
+
createAppleRendererModule(BpmnRenderer)
|
|
405
|
+
],
|
|
406
|
+
...buildRendererConfig(colors)
|
|
407
|
+
});
|
|
408
|
+
viewerRef.current = viewer;
|
|
409
|
+
try {
|
|
410
|
+
await viewer.importXML(xml);
|
|
411
|
+
if (cancelled) return;
|
|
412
|
+
const eventBus = viewer.get("eventBus");
|
|
413
|
+
eventBus.on("element.click", (e) => {
|
|
414
|
+
const element = e.element;
|
|
415
|
+
const original = e.originalEvent;
|
|
416
|
+
if (!element) return;
|
|
417
|
+
const handler = onElementClickRef.current;
|
|
418
|
+
if (!handler) return;
|
|
419
|
+
handler({
|
|
420
|
+
elementId: element.id ?? element.businessObject?.id ?? "",
|
|
421
|
+
elementType: element.type ?? element.businessObject?.$type ?? "",
|
|
422
|
+
businessObject: element.businessObject,
|
|
423
|
+
screenPosition: original ? { x: original.clientX, y: original.clientY } : void 0
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
const canvas = viewer.get("canvas");
|
|
427
|
+
if (containerRef.current && containerRef.current.clientWidth > 0 && containerRef.current.clientHeight > 0) {
|
|
428
|
+
canvas.zoom("fit-viewport");
|
|
429
|
+
}
|
|
430
|
+
applyHighlighting();
|
|
431
|
+
} catch (e) {
|
|
432
|
+
if (!cancelled) setError(e.message);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
void init();
|
|
436
|
+
return () => {
|
|
437
|
+
cancelled = true;
|
|
438
|
+
if (viewerRef.current) {
|
|
439
|
+
try {
|
|
440
|
+
viewerRef.current.destroy();
|
|
441
|
+
} catch {
|
|
442
|
+
}
|
|
443
|
+
viewerRef.current = null;
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
}, [xml, colorScheme, themeTick]);
|
|
447
|
+
useEffect(() => {
|
|
448
|
+
applyHighlighting();
|
|
449
|
+
}, [applyHighlighting]);
|
|
450
|
+
useEffect(() => {
|
|
451
|
+
if (!fitOnResize || !containerRef.current) return;
|
|
452
|
+
let timer = null;
|
|
453
|
+
const ro = new ResizeObserver(() => {
|
|
454
|
+
if (timer) clearTimeout(timer);
|
|
455
|
+
timer = setTimeout(() => {
|
|
456
|
+
try {
|
|
457
|
+
viewerRef.current?.get("canvas")?.zoom(
|
|
458
|
+
"fit-viewport"
|
|
459
|
+
);
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
}, 150);
|
|
463
|
+
});
|
|
464
|
+
ro.observe(containerRef.current);
|
|
465
|
+
return () => {
|
|
466
|
+
if (timer) clearTimeout(timer);
|
|
467
|
+
ro.disconnect();
|
|
468
|
+
};
|
|
469
|
+
}, [fitOnResize]);
|
|
470
|
+
const zoom = useCallback((factor) => {
|
|
471
|
+
const canvas = viewerRef.current?.get("canvas");
|
|
472
|
+
if (!canvas) return;
|
|
473
|
+
try {
|
|
474
|
+
if (factor === "fit") canvas.zoom("fit-viewport");
|
|
475
|
+
else canvas.zoom(canvas.zoom() * factor);
|
|
476
|
+
} catch {
|
|
477
|
+
}
|
|
478
|
+
}, []);
|
|
479
|
+
return /* @__PURE__ */ jsxs(
|
|
480
|
+
"div",
|
|
481
|
+
{
|
|
482
|
+
className: ["prn-bpmn", "prn-bpmn-viewer", className].filter(Boolean).join(" "),
|
|
483
|
+
"data-color-scheme": colorScheme,
|
|
484
|
+
style: { height },
|
|
485
|
+
children: [
|
|
486
|
+
showZoomControls && /* @__PURE__ */ jsxs("div", { className: "prn-bpmn-zoom", role: "group", "aria-label": "Zoom", children: [
|
|
487
|
+
/* @__PURE__ */ jsx(Button, { variant: "tinted", "aria-label": "Hineinzoomen", onPress: () => zoom(1.2), children: /* @__PURE__ */ jsx("span", { className: "bpmn-icon-screen-fullscreen", "aria-hidden": true, children: "+" }) }),
|
|
488
|
+
/* @__PURE__ */ jsx(Button, { variant: "tinted", "aria-label": "Herauszoomen", onPress: () => zoom(0.8), children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2212" }) }),
|
|
489
|
+
/* @__PURE__ */ jsx(Button, { variant: "tinted", "aria-label": "Einpassen", onPress: () => zoom("fit"), children: /* @__PURE__ */ jsx("span", { "aria-hidden": true, children: "\u2922" }) })
|
|
490
|
+
] }),
|
|
491
|
+
/* @__PURE__ */ jsx("div", { className: "prn-bpmn-canvas", ref: containerRef }),
|
|
492
|
+
error && /* @__PURE__ */ jsxs("div", { className: "prn-bpmn-error", role: "alert", children: [
|
|
493
|
+
"BPMN konnte nicht geladen werden: ",
|
|
494
|
+
error
|
|
495
|
+
] })
|
|
496
|
+
]
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
var BpmnViewer_default = BpmnViewer;
|
|
501
|
+
|
|
502
|
+
// src/editor/lintConfig.ts
|
|
503
|
+
var DEFAULT_LINT_RULES = {
|
|
504
|
+
"bpmnlint/label-required": "error",
|
|
505
|
+
"bpmnlint/no-disconnected": "error",
|
|
506
|
+
"bpmnlint/no-implicit-split": "warn",
|
|
507
|
+
"bpmnlint/no-complex-gateway": "warn"
|
|
508
|
+
};
|
|
509
|
+
function buildLintConfig(ruleModules, rules = DEFAULT_LINT_RULES) {
|
|
510
|
+
const ruleCache = {};
|
|
511
|
+
for (const [name, mod] of Object.entries(ruleModules)) {
|
|
512
|
+
ruleCache[`rule:${name}`] = mod;
|
|
513
|
+
}
|
|
514
|
+
return {
|
|
515
|
+
config: { rules },
|
|
516
|
+
resolver: {
|
|
517
|
+
resolveRule(pkg, ruleName) {
|
|
518
|
+
const key = `rule:${pkg}/${ruleName}`;
|
|
519
|
+
const resolved = ruleCache[key];
|
|
520
|
+
if (!resolved) throw new Error(`unknown rule <${pkg}/${ruleName}>`);
|
|
521
|
+
return resolved;
|
|
522
|
+
},
|
|
523
|
+
resolveConfig(_pkg, configName) {
|
|
524
|
+
throw new Error(`unknown config <${configName}>`);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// src/editor/lintHints.ts
|
|
531
|
+
var HINTS = {
|
|
532
|
+
"label-required": {
|
|
533
|
+
title: "Beschriftung fehlt",
|
|
534
|
+
explanation: "Aufgaben, Ereignisse und Gateways brauchen einen sprechenden Namen, damit der Prozess lesbar bleibt.",
|
|
535
|
+
howto: "Element anklicken und im Properties-Panel unter \u201EName\u201C eine Beschriftung eintragen."
|
|
536
|
+
},
|
|
537
|
+
"no-disconnected": {
|
|
538
|
+
title: "Element nicht verbunden",
|
|
539
|
+
explanation: "Ein Element ist \xFCber keine Sequenzfluss-Kante erreichbar \u2014 der Prozessfluss bricht ab.",
|
|
540
|
+
howto: "Eine Verbindung (Pfeil) vom vorherigen Element hierher und/oder weiter zum n\xE4chsten ziehen."
|
|
541
|
+
},
|
|
542
|
+
"no-implicit-split": {
|
|
543
|
+
title: "Impliziter Split",
|
|
544
|
+
explanation: "Ein Element hat mehrere ausgehende Sequenzfl\xFCsse ohne explizites Gateway \u2014 das Verzweigungsverhalten ist mehrdeutig.",
|
|
545
|
+
howto: "Ein Gateway (exklusiv/parallel) einf\xFCgen und die ausgehenden Fl\xFCsse dar\xFCber f\xFChren."
|
|
546
|
+
},
|
|
547
|
+
"no-complex-gateway": {
|
|
548
|
+
title: "Komplexes Gateway",
|
|
549
|
+
explanation: "Komplexe Gateways sind schwer verst\xE4ndlich und in den MaKo-Prozessen unerw\xFCnscht.",
|
|
550
|
+
howto: "Durch exklusive (XOR) oder parallele (AND) Gateways ersetzen."
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
var FALLBACK = {
|
|
554
|
+
title: "Validierungshinweis",
|
|
555
|
+
explanation: "Der Linter hat ein Problem an diesem Element gemeldet.",
|
|
556
|
+
howto: "Element pr\xFCfen und gem\xE4\xDF der Meldung korrigieren. Bei Unsicherheit den KI-Fix nutzen."
|
|
557
|
+
};
|
|
558
|
+
function lintHint(ruleId) {
|
|
559
|
+
return HINTS[ruleId] ?? FALLBACK;
|
|
560
|
+
}
|
|
561
|
+
function ErrorPanel({
|
|
562
|
+
issues,
|
|
563
|
+
elementName,
|
|
564
|
+
onSelectElement,
|
|
565
|
+
onKiFix,
|
|
566
|
+
onClose,
|
|
567
|
+
actionsSlot
|
|
568
|
+
}) {
|
|
569
|
+
return /* @__PURE__ */ jsxs("div", { className: "prn-bpmn-errorpanel", role: "region", "aria-label": "Validierung", children: [
|
|
570
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-errorpanel-head", children: [
|
|
571
|
+
/* @__PURE__ */ jsxs("strong", { children: [
|
|
572
|
+
"Validierung \u2014 ",
|
|
573
|
+
issues.length,
|
|
574
|
+
" Befund(e)"
|
|
575
|
+
] }),
|
|
576
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-errorpanel-actions", children: [
|
|
577
|
+
actionsSlot,
|
|
578
|
+
onKiFix && /* @__PURE__ */ jsx(Button, { variant: "tinted", onPress: onKiFix, "data-testid": "ki-fix-all", children: "KI-Fix" }),
|
|
579
|
+
/* @__PURE__ */ jsx(Button, { variant: "plain", "aria-label": "Schlie\xDFen", onPress: onClose, children: "\u2715" })
|
|
580
|
+
] })
|
|
581
|
+
] }),
|
|
582
|
+
/* @__PURE__ */ jsx("ul", { className: "prn-bpmn-errorpanel-list", children: issues.map((issue, idx) => {
|
|
583
|
+
const hint = lintHint(issue.id);
|
|
584
|
+
const name = elementName(issue.element);
|
|
585
|
+
return /* @__PURE__ */ jsxs(
|
|
586
|
+
"li",
|
|
587
|
+
{
|
|
588
|
+
className: "prn-bpmn-errorpanel-item",
|
|
589
|
+
"data-category": issue.category,
|
|
590
|
+
children: [
|
|
591
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-errorpanel-title", children: [
|
|
592
|
+
/* @__PURE__ */ jsx("span", { className: "prn-bpmn-errorpanel-icon", "aria-hidden": true, children: issue.category === "error" ? "\u2715" : "\u26A0" }),
|
|
593
|
+
/* @__PURE__ */ jsx("span", { children: hint.title }),
|
|
594
|
+
issue.element && /* @__PURE__ */ jsx(Link, { onPress: () => onSelectElement(issue.element), children: name })
|
|
595
|
+
] }),
|
|
596
|
+
/* @__PURE__ */ jsx("p", { className: "prn-bpmn-muted prn-bpmn-errorpanel-text", children: hint.explanation }),
|
|
597
|
+
/* @__PURE__ */ jsxs("p", { className: "prn-bpmn-errorpanel-text", children: [
|
|
598
|
+
"\u279C ",
|
|
599
|
+
hint.howto
|
|
600
|
+
] }),
|
|
601
|
+
/* @__PURE__ */ jsxs("p", { className: "prn-bpmn-muted prn-bpmn-errorpanel-meta", children: [
|
|
602
|
+
issue.message,
|
|
603
|
+
" (",
|
|
604
|
+
issue.id,
|
|
605
|
+
")"
|
|
606
|
+
] })
|
|
607
|
+
]
|
|
608
|
+
},
|
|
609
|
+
`${issue.id}-${issue.element}-${idx}`
|
|
610
|
+
);
|
|
611
|
+
}) })
|
|
612
|
+
] });
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/editor/parseBpmnElements.ts
|
|
616
|
+
var BPMN_NS = "http://www.omg.org/spec/BPMN/20100524/MODEL";
|
|
617
|
+
var BPMN_ELEMENT_TYPES = [
|
|
618
|
+
"startEvent",
|
|
619
|
+
"endEvent",
|
|
620
|
+
"serviceTask",
|
|
621
|
+
"userTask",
|
|
622
|
+
"sendTask",
|
|
623
|
+
"receiveTask",
|
|
624
|
+
"manualTask",
|
|
625
|
+
"businessRuleTask",
|
|
626
|
+
"scriptTask",
|
|
627
|
+
"task",
|
|
628
|
+
"exclusiveGateway",
|
|
629
|
+
"parallelGateway",
|
|
630
|
+
"inclusiveGateway",
|
|
631
|
+
"eventBasedGateway",
|
|
632
|
+
"complexGateway",
|
|
633
|
+
"intermediateCatchEvent",
|
|
634
|
+
"intermediateThrowEvent",
|
|
635
|
+
"boundaryEvent",
|
|
636
|
+
"subProcess",
|
|
637
|
+
"callActivity"
|
|
638
|
+
];
|
|
639
|
+
var BPMN_TYPE_LABEL = {
|
|
640
|
+
startEvent: "Start",
|
|
641
|
+
endEvent: "Ende",
|
|
642
|
+
serviceTask: "Service Task",
|
|
643
|
+
userTask: "User Task",
|
|
644
|
+
sendTask: "Send Task",
|
|
645
|
+
receiveTask: "Receive Task",
|
|
646
|
+
manualTask: "Manuell",
|
|
647
|
+
businessRuleTask: "Business Rule",
|
|
648
|
+
scriptTask: "Script",
|
|
649
|
+
task: "Task",
|
|
650
|
+
exclusiveGateway: "Gateway (XOR)",
|
|
651
|
+
parallelGateway: "Gateway (AND)",
|
|
652
|
+
inclusiveGateway: "Gateway (OR)",
|
|
653
|
+
eventBasedGateway: "Gateway (Event)",
|
|
654
|
+
complexGateway: "Gateway (komplex)",
|
|
655
|
+
intermediateCatchEvent: "Ereignis (catch)",
|
|
656
|
+
intermediateThrowEvent: "Ereignis (throw)",
|
|
657
|
+
boundaryEvent: "Grenzereignis",
|
|
658
|
+
subProcess: "Teilprozess",
|
|
659
|
+
callActivity: "Call Activity"
|
|
660
|
+
};
|
|
661
|
+
function typeTone(type) {
|
|
662
|
+
if (type.includes("Gateway")) return "orange";
|
|
663
|
+
if (type === "startEvent") return "green";
|
|
664
|
+
if (type === "endEvent") return "red";
|
|
665
|
+
if (type.includes("Task")) return "blue";
|
|
666
|
+
return "neutral";
|
|
667
|
+
}
|
|
668
|
+
function parseBpmnElements(xml) {
|
|
669
|
+
const doc = new DOMParser().parseFromString(xml, "application/xml");
|
|
670
|
+
const laneMap = {};
|
|
671
|
+
const lanes = doc.getElementsByTagNameNS(BPMN_NS, "lane");
|
|
672
|
+
for (let i = 0; i < lanes.length; i++) {
|
|
673
|
+
const lane = lanes[i];
|
|
674
|
+
if (!lane) continue;
|
|
675
|
+
const laneName = lane.getAttribute("name") ?? "";
|
|
676
|
+
const refs = lane.getElementsByTagNameNS(BPMN_NS, "flowNodeRef");
|
|
677
|
+
for (let j = 0; j < refs.length; j++) {
|
|
678
|
+
const refId = refs[j]?.textContent?.trim() ?? "";
|
|
679
|
+
if (refId) laneMap[refId] = laneName;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
const elements = [];
|
|
683
|
+
for (const type of BPMN_ELEMENT_TYPES) {
|
|
684
|
+
const nodes = doc.getElementsByTagNameNS(BPMN_NS, type);
|
|
685
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
686
|
+
const el = nodes[i];
|
|
687
|
+
if (!el) continue;
|
|
688
|
+
const id = el.getAttribute("id") ?? "";
|
|
689
|
+
if (id) {
|
|
690
|
+
elements.push({
|
|
691
|
+
id,
|
|
692
|
+
type,
|
|
693
|
+
name: el.getAttribute("name") ?? "",
|
|
694
|
+
lane: laneMap[id] ?? ""
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return elements;
|
|
700
|
+
}
|
|
701
|
+
function BpmnTableView({ xml, visibleRows = 30, className }) {
|
|
702
|
+
const [search, setSearch] = useState("");
|
|
703
|
+
const elements = useMemo(() => parseBpmnElements(xml), [xml]);
|
|
704
|
+
const filtered = useMemo(() => {
|
|
705
|
+
if (!search.trim()) return elements;
|
|
706
|
+
const q = search.toLowerCase();
|
|
707
|
+
return elements.filter(
|
|
708
|
+
(e) => e.name.toLowerCase().includes(q) || (BPMN_TYPE_LABEL[e.type] ?? e.type).toLowerCase().includes(q) || e.lane.toLowerCase().includes(q) || e.id.toLowerCase().includes(q)
|
|
709
|
+
);
|
|
710
|
+
}, [elements, search]);
|
|
711
|
+
const columns = useMemo(
|
|
712
|
+
() => [
|
|
713
|
+
{
|
|
714
|
+
header: "#",
|
|
715
|
+
accessorKey: "idx",
|
|
716
|
+
width: "56px",
|
|
717
|
+
disableFilters: true,
|
|
718
|
+
cellRender: (row) => /* @__PURE__ */ jsx("span", { className: "prn-bpmn-muted", children: row.idx })
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
header: "Typ",
|
|
722
|
+
accessorKey: "type",
|
|
723
|
+
width: "168px",
|
|
724
|
+
cellRender: (row) => /* @__PURE__ */ jsx(Badge, { tone: typeTone(row.type), children: BPMN_TYPE_LABEL[row.type] ?? row.type })
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
header: "Name / Bezeichnung",
|
|
728
|
+
accessorKey: "name",
|
|
729
|
+
cellRender: (row) => row.name ? /* @__PURE__ */ jsx("span", { children: row.name }) : /* @__PURE__ */ jsx("span", { className: "prn-bpmn-muted", children: "\u2014" })
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
header: "Lane / Rolle",
|
|
733
|
+
accessorKey: "lane",
|
|
734
|
+
cellRender: (row) => /* @__PURE__ */ jsx("span", { className: "prn-bpmn-muted", children: row.lane })
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
header: "ID",
|
|
738
|
+
accessorKey: "id",
|
|
739
|
+
cellRender: (row) => /* @__PURE__ */ jsx("span", { className: "prn-bpmn-mono", children: row.id })
|
|
740
|
+
}
|
|
741
|
+
],
|
|
742
|
+
[]
|
|
743
|
+
);
|
|
744
|
+
const tableData = useMemo(
|
|
745
|
+
() => filtered.map((el, idx) => ({ ...el, idx: idx + 1 })),
|
|
746
|
+
[filtered]
|
|
747
|
+
);
|
|
748
|
+
return /* @__PURE__ */ jsxs("div", { className: ["prn-bpmn-table", className].filter(Boolean).join(" "), children: [
|
|
749
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-table-bar", children: [
|
|
750
|
+
/* @__PURE__ */ jsx(
|
|
751
|
+
SearchField,
|
|
752
|
+
{
|
|
753
|
+
"aria-label": "Elemente durchsuchen",
|
|
754
|
+
placeholder: "Suche nach Name, Typ, Lane, ID\u2026",
|
|
755
|
+
value: search,
|
|
756
|
+
onChange: setSearch
|
|
757
|
+
}
|
|
758
|
+
),
|
|
759
|
+
/* @__PURE__ */ jsxs("span", { className: "prn-bpmn-muted prn-bpmn-table-count", children: [
|
|
760
|
+
filtered.length,
|
|
761
|
+
" / ",
|
|
762
|
+
elements.length,
|
|
763
|
+
" Elemente"
|
|
764
|
+
] })
|
|
765
|
+
] }),
|
|
766
|
+
/* @__PURE__ */ jsx("div", { className: "prn-bpmn-table-body", children: /* @__PURE__ */ jsx(
|
|
767
|
+
AnalyticalTable,
|
|
768
|
+
{
|
|
769
|
+
data: tableData,
|
|
770
|
+
columns,
|
|
771
|
+
visibleRows,
|
|
772
|
+
alternateRowColor: true,
|
|
773
|
+
selectionMode: "none"
|
|
774
|
+
}
|
|
775
|
+
) })
|
|
776
|
+
] });
|
|
777
|
+
}
|
|
778
|
+
function BpmnEditor({
|
|
779
|
+
value,
|
|
780
|
+
defaultValue,
|
|
781
|
+
onChange,
|
|
782
|
+
onSave,
|
|
783
|
+
lintRules = DEFAULT_LINT_RULES,
|
|
784
|
+
onElementSelect,
|
|
785
|
+
onDirtyChange,
|
|
786
|
+
actionsSlot,
|
|
787
|
+
onKiFix,
|
|
788
|
+
colorScheme = "auto",
|
|
789
|
+
propertiesPanel = true,
|
|
790
|
+
minimap = true,
|
|
791
|
+
elementTemplates,
|
|
792
|
+
height = "100%",
|
|
793
|
+
className
|
|
794
|
+
}) {
|
|
795
|
+
const canvasRef = useRef(null);
|
|
796
|
+
const propsRef = useRef(null);
|
|
797
|
+
const modelerRef = useRef(null);
|
|
798
|
+
const isControlled = value !== void 0;
|
|
799
|
+
const [error, setError] = useState(null);
|
|
800
|
+
const [lintIssues, setLintIssues] = useState([]);
|
|
801
|
+
const [errorPanelOpen, setErrorPanelOpen] = useState(false);
|
|
802
|
+
const [view, setView] = useState("diagram");
|
|
803
|
+
const [tableXml, setTableXml] = useState("");
|
|
804
|
+
const [saving, setSaving] = useState(false);
|
|
805
|
+
const [themeTick, setThemeTick] = useState(0);
|
|
806
|
+
const cb = useRef({ onChange, onElementSelect, onDirtyChange });
|
|
807
|
+
cb.current = { onChange, onElementSelect, onDirtyChange };
|
|
808
|
+
const templatesRef = useRef(elementTemplates);
|
|
809
|
+
templatesRef.current = elementTemplates;
|
|
810
|
+
useEffect(() => onThemeChange(() => setThemeTick((t) => t + 1)), []);
|
|
811
|
+
const getXml = useCallback(async () => {
|
|
812
|
+
if (!modelerRef.current) return null;
|
|
813
|
+
try {
|
|
814
|
+
const { xml } = await modelerRef.current.saveXML({ format: true });
|
|
815
|
+
return xml;
|
|
816
|
+
} catch {
|
|
817
|
+
return null;
|
|
818
|
+
}
|
|
819
|
+
}, []);
|
|
820
|
+
const fitWithPadding = useCallback(() => {
|
|
821
|
+
const canvas = modelerRef.current?.get("canvas");
|
|
822
|
+
if (!canvas) return;
|
|
823
|
+
try {
|
|
824
|
+
canvas.zoom("fit-viewport", "auto");
|
|
825
|
+
const vb = canvas.viewbox();
|
|
826
|
+
const padded = Math.min(vb.scale * 0.92, 1.5);
|
|
827
|
+
canvas.zoom(padded, "auto");
|
|
828
|
+
canvas.scroll({ dx: 90, dy: 24 });
|
|
829
|
+
} catch {
|
|
830
|
+
}
|
|
831
|
+
}, []);
|
|
832
|
+
const initialXml = isControlled ? value : defaultValue;
|
|
833
|
+
const lastImportedRef = useRef(void 0);
|
|
834
|
+
useEffect(() => {
|
|
835
|
+
let cancelled = false;
|
|
836
|
+
setError(null);
|
|
837
|
+
const init = async () => {
|
|
838
|
+
if (!canvasRef.current) return;
|
|
839
|
+
if (modelerRef.current) {
|
|
840
|
+
try {
|
|
841
|
+
modelerRef.current.destroy();
|
|
842
|
+
} catch {
|
|
843
|
+
}
|
|
844
|
+
modelerRef.current = null;
|
|
845
|
+
}
|
|
846
|
+
const [
|
|
847
|
+
{ default: BpmnModeler },
|
|
848
|
+
propsPanel,
|
|
849
|
+
{ default: CamundaModdle },
|
|
850
|
+
{ default: lintModule },
|
|
851
|
+
{ default: BpmnRenderer },
|
|
852
|
+
{ default: MinimapModule },
|
|
853
|
+
elementTemplatesMod,
|
|
854
|
+
labelRequired,
|
|
855
|
+
noComplexGateway,
|
|
856
|
+
noDisconnected,
|
|
857
|
+
noImplicitSplit
|
|
858
|
+
] = await Promise.all([
|
|
859
|
+
import(
|
|
860
|
+
/* @vite-ignore */
|
|
861
|
+
'bpmn-js/lib/Modeler'
|
|
862
|
+
),
|
|
863
|
+
import(
|
|
864
|
+
/* @vite-ignore */
|
|
865
|
+
'bpmn-js-properties-panel'
|
|
866
|
+
),
|
|
867
|
+
import(
|
|
868
|
+
/* @vite-ignore */
|
|
869
|
+
'camunda-bpmn-moddle/resources/camunda.json'
|
|
870
|
+
),
|
|
871
|
+
import(
|
|
872
|
+
/* @vite-ignore */
|
|
873
|
+
'bpmn-js-bpmnlint'
|
|
874
|
+
),
|
|
875
|
+
import(
|
|
876
|
+
/* @vite-ignore */
|
|
877
|
+
'bpmn-js/lib/draw/BpmnRenderer'
|
|
878
|
+
),
|
|
879
|
+
import(
|
|
880
|
+
/* @vite-ignore */
|
|
881
|
+
'diagram-js-minimap'
|
|
882
|
+
),
|
|
883
|
+
import(
|
|
884
|
+
/* @vite-ignore */
|
|
885
|
+
'bpmn-js-element-templates'
|
|
886
|
+
),
|
|
887
|
+
import(
|
|
888
|
+
/* @vite-ignore */
|
|
889
|
+
'bpmnlint/rules/label-required'
|
|
890
|
+
),
|
|
891
|
+
import(
|
|
892
|
+
/* @vite-ignore */
|
|
893
|
+
'bpmnlint/rules/no-complex-gateway'
|
|
894
|
+
),
|
|
895
|
+
import(
|
|
896
|
+
/* @vite-ignore */
|
|
897
|
+
'bpmnlint/rules/no-disconnected'
|
|
898
|
+
),
|
|
899
|
+
import(
|
|
900
|
+
/* @vite-ignore */
|
|
901
|
+
'bpmnlint/rules/no-implicit-split'
|
|
902
|
+
)
|
|
903
|
+
]);
|
|
904
|
+
if (cancelled || !canvasRef.current) return;
|
|
905
|
+
const {
|
|
906
|
+
BpmnPropertiesPanelModule,
|
|
907
|
+
BpmnPropertiesProviderModule,
|
|
908
|
+
CamundaPlatformPropertiesProviderModule
|
|
909
|
+
} = propsPanel;
|
|
910
|
+
const lintConfig = buildLintConfig(
|
|
911
|
+
{
|
|
912
|
+
"bpmnlint/label-required": labelRequired.default,
|
|
913
|
+
"bpmnlint/no-complex-gateway": noComplexGateway.default,
|
|
914
|
+
"bpmnlint/no-disconnected": noDisconnected.default,
|
|
915
|
+
"bpmnlint/no-implicit-split": noImplicitSplit.default
|
|
916
|
+
},
|
|
917
|
+
lintRules
|
|
918
|
+
);
|
|
919
|
+
const { ElementTemplatesPropertiesProviderModule } = elementTemplatesMod;
|
|
920
|
+
const colors = getDiagramColors(colorScheme);
|
|
921
|
+
const additionalModules = [
|
|
922
|
+
lintModule,
|
|
923
|
+
createAppleRendererModule(BpmnRenderer)
|
|
924
|
+
];
|
|
925
|
+
if (minimap) additionalModules.push(MinimapModule);
|
|
926
|
+
if (propertiesPanel) {
|
|
927
|
+
additionalModules.unshift(
|
|
928
|
+
BpmnPropertiesPanelModule,
|
|
929
|
+
BpmnPropertiesProviderModule,
|
|
930
|
+
CamundaPlatformPropertiesProviderModule,
|
|
931
|
+
ElementTemplatesPropertiesProviderModule
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
const modeler = new BpmnModeler({
|
|
935
|
+
container: canvasRef.current,
|
|
936
|
+
...propertiesPanel && propsRef.current ? { propertiesPanel: { parent: propsRef.current } } : {},
|
|
937
|
+
linting: { bpmnlint: lintConfig, active: true },
|
|
938
|
+
...buildRendererConfig(colors),
|
|
939
|
+
additionalModules,
|
|
940
|
+
moddleExtensions: { camunda: CamundaModdle }
|
|
941
|
+
});
|
|
942
|
+
modelerRef.current = modeler;
|
|
943
|
+
if (Array.isArray(templatesRef.current)) {
|
|
944
|
+
try {
|
|
945
|
+
modeler.get("elementTemplates").set(
|
|
946
|
+
templatesRef.current
|
|
947
|
+
);
|
|
948
|
+
} catch {
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
if (minimap) {
|
|
952
|
+
try {
|
|
953
|
+
modeler.get("minimap").open();
|
|
954
|
+
} catch {
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
const eventBus = modeler.get("eventBus");
|
|
958
|
+
eventBus.on("linting.completed", (e) => {
|
|
959
|
+
const results = e.issues ?? {};
|
|
960
|
+
const flat = [];
|
|
961
|
+
for (const [elId, issues] of Object.entries(results)) {
|
|
962
|
+
for (const issue of issues) {
|
|
963
|
+
flat.push({
|
|
964
|
+
id: issue.rule ?? issue.id ?? elId,
|
|
965
|
+
message: issue.message,
|
|
966
|
+
category: issue.category ?? "warning",
|
|
967
|
+
element: elId
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
setLintIssues(flat);
|
|
972
|
+
});
|
|
973
|
+
eventBus.on("selection.changed", (e) => {
|
|
974
|
+
const sel = e.newSelection ?? [];
|
|
975
|
+
const only = sel.length === 1 ? sel[0] : void 0;
|
|
976
|
+
if (only?.id) {
|
|
977
|
+
cb.current.onElementSelect?.({ id: only.id, name: only.businessObject?.name });
|
|
978
|
+
} else {
|
|
979
|
+
cb.current.onElementSelect?.(null);
|
|
980
|
+
}
|
|
981
|
+
});
|
|
982
|
+
eventBus.on("commandStack.changed", () => {
|
|
983
|
+
cb.current.onDirtyChange?.(true);
|
|
984
|
+
if (cb.current.onChange) {
|
|
985
|
+
void modeler.saveXML({ format: true }).then(({ xml }) => {
|
|
986
|
+
lastImportedRef.current = xml;
|
|
987
|
+
cb.current.onChange?.(xml);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
if (initialXml && initialXml.trim()) {
|
|
992
|
+
try {
|
|
993
|
+
await modeler.importXML(initialXml);
|
|
994
|
+
lastImportedRef.current = initialXml;
|
|
995
|
+
fitWithPadding();
|
|
996
|
+
} catch (err) {
|
|
997
|
+
if (!cancelled) setError(err.message);
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
};
|
|
1001
|
+
void init();
|
|
1002
|
+
return () => {
|
|
1003
|
+
cancelled = true;
|
|
1004
|
+
if (modelerRef.current) {
|
|
1005
|
+
try {
|
|
1006
|
+
modelerRef.current.destroy();
|
|
1007
|
+
} catch {
|
|
1008
|
+
}
|
|
1009
|
+
modelerRef.current = null;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
1012
|
+
}, [themeTick, colorScheme, propertiesPanel, minimap]);
|
|
1013
|
+
useEffect(() => {
|
|
1014
|
+
if (!isControlled || value === void 0) return;
|
|
1015
|
+
if (value === lastImportedRef.current) return;
|
|
1016
|
+
const modeler = modelerRef.current;
|
|
1017
|
+
if (!modeler || !value.trim()) return;
|
|
1018
|
+
let cancelled = false;
|
|
1019
|
+
void modeler.importXML(value).then(() => {
|
|
1020
|
+
if (cancelled) return;
|
|
1021
|
+
lastImportedRef.current = value;
|
|
1022
|
+
fitWithPadding();
|
|
1023
|
+
});
|
|
1024
|
+
return () => {
|
|
1025
|
+
cancelled = true;
|
|
1026
|
+
};
|
|
1027
|
+
}, [value, isControlled, fitWithPadding]);
|
|
1028
|
+
const elementName = useCallback((elementId) => {
|
|
1029
|
+
if (!elementId) return "(unbenannt)";
|
|
1030
|
+
const reg = modelerRef.current?.get("elementRegistry");
|
|
1031
|
+
return reg?.get(elementId)?.businessObject?.name || elementId;
|
|
1032
|
+
}, []);
|
|
1033
|
+
const selectElement = useCallback((elementId) => {
|
|
1034
|
+
const reg = modelerRef.current?.get("elementRegistry");
|
|
1035
|
+
const el = reg?.get(elementId);
|
|
1036
|
+
if (!el) return;
|
|
1037
|
+
try {
|
|
1038
|
+
modelerRef.current?.get("selection")?.select(el);
|
|
1039
|
+
modelerRef.current?.get("canvas")?.scrollToElement(el);
|
|
1040
|
+
} catch {
|
|
1041
|
+
}
|
|
1042
|
+
}, []);
|
|
1043
|
+
const handleSave = useCallback(async () => {
|
|
1044
|
+
if (!onSave) return;
|
|
1045
|
+
setSaving(true);
|
|
1046
|
+
try {
|
|
1047
|
+
const xml = await getXml();
|
|
1048
|
+
if (xml != null) {
|
|
1049
|
+
await onSave(xml);
|
|
1050
|
+
cb.current.onDirtyChange?.(false);
|
|
1051
|
+
}
|
|
1052
|
+
} finally {
|
|
1053
|
+
setSaving(false);
|
|
1054
|
+
}
|
|
1055
|
+
}, [onSave, getXml]);
|
|
1056
|
+
const handleSearch = useCallback(() => {
|
|
1057
|
+
const m = modelerRef.current;
|
|
1058
|
+
if (!m) return;
|
|
1059
|
+
try {
|
|
1060
|
+
m.get("editorActions").trigger("find");
|
|
1061
|
+
} catch {
|
|
1062
|
+
try {
|
|
1063
|
+
m.get("searchPad").toggle();
|
|
1064
|
+
} catch {
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}, []);
|
|
1068
|
+
useEffect(() => {
|
|
1069
|
+
const m = modelerRef.current;
|
|
1070
|
+
if (!m) return;
|
|
1071
|
+
try {
|
|
1072
|
+
m.get("elementTemplates").set(
|
|
1073
|
+
Array.isArray(elementTemplates) ? elementTemplates : []
|
|
1074
|
+
);
|
|
1075
|
+
} catch {
|
|
1076
|
+
}
|
|
1077
|
+
}, [elementTemplates]);
|
|
1078
|
+
const handleToggleView = useCallback(
|
|
1079
|
+
async (next) => {
|
|
1080
|
+
if (next === "table") {
|
|
1081
|
+
const xml = await getXml();
|
|
1082
|
+
setTableXml(xml ?? initialXml ?? "");
|
|
1083
|
+
}
|
|
1084
|
+
setView(next);
|
|
1085
|
+
},
|
|
1086
|
+
[getXml, initialXml]
|
|
1087
|
+
);
|
|
1088
|
+
const errors = lintIssues.filter((i) => i.category === "error");
|
|
1089
|
+
const warnings = lintIssues.filter((i) => i.category === "warning");
|
|
1090
|
+
return /* @__PURE__ */ jsxs(
|
|
1091
|
+
"div",
|
|
1092
|
+
{
|
|
1093
|
+
className: ["prn-bpmn", "prn-bpmn-editor", className].filter(Boolean).join(" "),
|
|
1094
|
+
"data-color-scheme": colorScheme,
|
|
1095
|
+
style: { height },
|
|
1096
|
+
children: [
|
|
1097
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-editor-toolbar", role: "toolbar", "aria-label": "BPMN-Editor-Aktionen", children: [
|
|
1098
|
+
/* @__PURE__ */ jsxs(
|
|
1099
|
+
SegmentedControl,
|
|
1100
|
+
{
|
|
1101
|
+
"aria-label": "Ansicht",
|
|
1102
|
+
selectedKeys: /* @__PURE__ */ new Set([view]),
|
|
1103
|
+
onSelectionChange: (keys) => {
|
|
1104
|
+
const next = [...keys][0];
|
|
1105
|
+
if (next) void handleToggleView(next);
|
|
1106
|
+
},
|
|
1107
|
+
children: [
|
|
1108
|
+
/* @__PURE__ */ jsx(Segment, { id: "diagram", children: "Diagramm" }),
|
|
1109
|
+
/* @__PURE__ */ jsx(Segment, { id: "table", children: "Tabelle" })
|
|
1110
|
+
]
|
|
1111
|
+
}
|
|
1112
|
+
),
|
|
1113
|
+
view === "diagram" && /* @__PURE__ */ jsx(Button, { variant: "plain", onPress: handleSearch, "aria-label": "Element suchen", children: "\u{1F50D} Suchen" }),
|
|
1114
|
+
/* @__PURE__ */ jsx("div", { className: "prn-bpmn-editor-toolbar-spacer" }),
|
|
1115
|
+
actionsSlot,
|
|
1116
|
+
onSave && /* @__PURE__ */ jsx(Button, { variant: "filled", onPress: () => void handleSave(), isDisabled: saving, children: saving ? "Speichern\u2026" : "Speichern" })
|
|
1117
|
+
] }),
|
|
1118
|
+
error && /* @__PURE__ */ jsx(Notice, { tone: "negative", title: "Fehler", children: error }),
|
|
1119
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-editor-main", children: [
|
|
1120
|
+
/* @__PURE__ */ jsxs("div", { className: "prn-bpmn-editor-stage", children: [
|
|
1121
|
+
/* @__PURE__ */ jsx(
|
|
1122
|
+
"div",
|
|
1123
|
+
{
|
|
1124
|
+
ref: canvasRef,
|
|
1125
|
+
className: "prn-bpmn-canvas",
|
|
1126
|
+
style: { display: view === "table" ? "none" : "block" }
|
|
1127
|
+
}
|
|
1128
|
+
),
|
|
1129
|
+
view === "table" && /* @__PURE__ */ jsx("div", { className: "prn-bpmn-editor-table", children: /* @__PURE__ */ jsx(BpmnTableView, { xml: tableXml }) })
|
|
1130
|
+
] }),
|
|
1131
|
+
propertiesPanel && view === "diagram" && /* @__PURE__ */ jsx("div", { ref: propsRef, className: "prn-bpmn-editor-properties" })
|
|
1132
|
+
] }),
|
|
1133
|
+
lintIssues.length > 0 && !errorPanelOpen && /* @__PURE__ */ jsxs(
|
|
1134
|
+
"button",
|
|
1135
|
+
{
|
|
1136
|
+
type: "button",
|
|
1137
|
+
className: "prn-bpmn-editor-lintbar",
|
|
1138
|
+
onClick: () => setErrorPanelOpen(true),
|
|
1139
|
+
"data-testid": "lint-footer-toggle",
|
|
1140
|
+
children: [
|
|
1141
|
+
errors.length > 0 && /* @__PURE__ */ jsxs("span", { className: "prn-bpmn-lint-error", children: [
|
|
1142
|
+
"\u2715 ",
|
|
1143
|
+
errors.length,
|
|
1144
|
+
" Fehler"
|
|
1145
|
+
] }),
|
|
1146
|
+
warnings.length > 0 && /* @__PURE__ */ jsxs("span", { className: "prn-bpmn-lint-warning", children: [
|
|
1147
|
+
"\u26A0 ",
|
|
1148
|
+
warnings.length,
|
|
1149
|
+
" Warnungen"
|
|
1150
|
+
] }),
|
|
1151
|
+
/* @__PURE__ */ jsx("span", { className: "prn-bpmn-muted", children: "\u2014 Details anzeigen" })
|
|
1152
|
+
]
|
|
1153
|
+
}
|
|
1154
|
+
),
|
|
1155
|
+
lintIssues.length > 0 && errorPanelOpen && /* @__PURE__ */ jsx(
|
|
1156
|
+
ErrorPanel,
|
|
1157
|
+
{
|
|
1158
|
+
issues: lintIssues,
|
|
1159
|
+
elementName,
|
|
1160
|
+
onSelectElement: selectElement,
|
|
1161
|
+
onKiFix: onKiFix ? () => onKiFix(lintIssues) : void 0,
|
|
1162
|
+
onClose: () => setErrorPanelOpen(false)
|
|
1163
|
+
}
|
|
1164
|
+
)
|
|
1165
|
+
]
|
|
1166
|
+
}
|
|
1167
|
+
);
|
|
1168
|
+
}
|
|
1169
|
+
var BpmnEditor_default = BpmnEditor;
|
|
1170
|
+
|
|
1171
|
+
export { BpmnEditor, BpmnEditor_default as BpmnEditorDefault, BpmnTableView, BpmnViewer, BpmnViewer_default as BpmnViewerDefault, STATUS_LABEL, STATUS_TOKEN, buildActivityMap, computeElementStatuses, computeExecutedFlows, deriveStatus, getDiagramColors, isDarkMode, lintHint, matchActivity, onThemeChange, readToken };
|
|
1172
|
+
//# sourceMappingURL=index.js.map
|
|
1173
|
+
//# sourceMappingURL=index.js.map
|