@bpmnkit/core 0.0.22 → 0.0.24
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/dist/bpmn/auto-layout.js +190 -21
- package/dist/bpmn/bpmn-builder.d.ts +104 -16
- package/dist/bpmn/bpmn-builder.js +561 -374
- package/dist/bpmn/bpmn-model.d.ts +6 -0
- package/dist/bpmn/bpmn-parser.js +10 -0
- package/dist/bpmn/bpmn-serializer.js +10 -1
- package/dist/bpmn/compact.js +1 -0
- package/dist/bpmn/index.d.ts +15 -1
- package/dist/bpmn/index.js +17 -1
- package/dist/bpmn/optimize/tasks.js +1 -0
- package/dist/bpmn/zeebe-extensions.d.ts +2 -0
- package/dist/bpmn/zeebe-extensions.js +3 -0
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/layout/astar.d.ts +15 -0
- package/dist/layout/astar.js +191 -0
- package/dist/layout/block-builder.d.ts +37 -0
- package/dist/layout/block-builder.js +154 -0
- package/dist/layout/block-layout.d.ts +9 -0
- package/dist/layout/block-layout.js +163 -0
- package/dist/layout/coordinates.d.ts +23 -0
- package/dist/layout/coordinates.js +612 -88
- package/dist/layout/index.d.ts +5 -0
- package/dist/layout/index.js +4 -0
- package/dist/layout/layout-engine.d.ts +2 -1
- package/dist/layout/layout-engine.js +209 -29
- package/dist/layout/overlap.js +3 -0
- package/dist/layout/routing.d.ts +10 -3
- package/dist/layout/routing.js +254 -71
- package/dist/layout/types.d.ts +6 -0
- package/package.json +4 -3
|
@@ -336,6 +336,11 @@ export interface BpmnMessage {
|
|
|
336
336
|
name?: string;
|
|
337
337
|
unknownAttributes: Record<string, string>;
|
|
338
338
|
}
|
|
339
|
+
/** A root-level BPMN signal definition referenced by signal catch/throw events. */
|
|
340
|
+
export interface BpmnSignal {
|
|
341
|
+
id: string;
|
|
342
|
+
name?: string;
|
|
343
|
+
}
|
|
339
344
|
/** Optional label positioning information for a BPMNDi shape or edge. */
|
|
340
345
|
export interface BpmnDiLabel {
|
|
341
346
|
bounds?: BpmnBounds;
|
|
@@ -397,6 +402,7 @@ export interface BpmnDefinitions {
|
|
|
397
402
|
errors: BpmnError[];
|
|
398
403
|
escalations: BpmnEscalation[];
|
|
399
404
|
messages: BpmnMessage[];
|
|
405
|
+
signals: BpmnSignal[];
|
|
400
406
|
collaborations: BpmnCollaboration[];
|
|
401
407
|
processes: BpmnProcess[];
|
|
402
408
|
diagrams: BpmnDiagram[];
|
package/dist/bpmn/bpmn-parser.js
CHANGED
|
@@ -505,6 +505,12 @@ function parseMessage(element) {
|
|
|
505
505
|
unknownAttributes: unknownAttrs(element),
|
|
506
506
|
};
|
|
507
507
|
}
|
|
508
|
+
function parseSignal(element) {
|
|
509
|
+
return {
|
|
510
|
+
id: requiredAttr(element, "id"),
|
|
511
|
+
name: attr(element, "name"),
|
|
512
|
+
};
|
|
513
|
+
}
|
|
508
514
|
// ---------------------------------------------------------------------------
|
|
509
515
|
// Diagram interchange
|
|
510
516
|
// ---------------------------------------------------------------------------
|
|
@@ -607,6 +613,7 @@ export function parseBpmn(xml) {
|
|
|
607
613
|
const errors = [];
|
|
608
614
|
const escalations = [];
|
|
609
615
|
const messages = [];
|
|
616
|
+
const signals = [];
|
|
610
617
|
const collaborations = [];
|
|
611
618
|
const processes = [];
|
|
612
619
|
const diagrams = [];
|
|
@@ -618,6 +625,8 @@ export function parseBpmn(xml) {
|
|
|
618
625
|
escalations.push(parseEscalation(child));
|
|
619
626
|
else if (ln === "message")
|
|
620
627
|
messages.push(parseMessage(child));
|
|
628
|
+
else if (ln === "signal")
|
|
629
|
+
signals.push(parseSignal(child));
|
|
621
630
|
else if (ln === "collaboration")
|
|
622
631
|
collaborations.push(parseCollaboration(child));
|
|
623
632
|
else if (ln === "process")
|
|
@@ -635,6 +644,7 @@ export function parseBpmn(xml) {
|
|
|
635
644
|
errors,
|
|
636
645
|
escalations,
|
|
637
646
|
messages,
|
|
647
|
+
signals,
|
|
638
648
|
collaborations,
|
|
639
649
|
processes,
|
|
640
650
|
diagrams,
|
|
@@ -387,6 +387,12 @@ function serializeMessage(m, bp) {
|
|
|
387
387
|
attrs.name = m.name;
|
|
388
388
|
return el(`${bp}:message`, attrs, []);
|
|
389
389
|
}
|
|
390
|
+
function serializeSignal(s, bp) {
|
|
391
|
+
const attrs = { id: s.id };
|
|
392
|
+
if (s.name !== undefined)
|
|
393
|
+
attrs.name = s.name;
|
|
394
|
+
return el(`${bp}:signal`, attrs, []);
|
|
395
|
+
}
|
|
390
396
|
// ---------------------------------------------------------------------------
|
|
391
397
|
// Diagram interchange
|
|
392
398
|
// ---------------------------------------------------------------------------
|
|
@@ -490,7 +496,7 @@ export function serializeBpmn(definitions) {
|
|
|
490
496
|
attrs[key] = value;
|
|
491
497
|
}
|
|
492
498
|
const children = [];
|
|
493
|
-
// Root elements: errors, escalations, messages first
|
|
499
|
+
// Root elements: errors, escalations, messages, signals first
|
|
494
500
|
for (const e of definitions.escalations) {
|
|
495
501
|
children.push(serializeEscalation(e, bp));
|
|
496
502
|
}
|
|
@@ -500,6 +506,9 @@ export function serializeBpmn(definitions) {
|
|
|
500
506
|
for (const m of definitions.messages) {
|
|
501
507
|
children.push(serializeMessage(m, bp));
|
|
502
508
|
}
|
|
509
|
+
for (const s of definitions.signals ?? []) {
|
|
510
|
+
children.push(serializeSignal(s, bp));
|
|
511
|
+
}
|
|
503
512
|
// Collaborations
|
|
504
513
|
for (const c of definitions.collaborations) {
|
|
505
514
|
children.push(serializeCollaboration(c, ns));
|
package/dist/bpmn/compact.js
CHANGED
package/dist/bpmn/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ProcessBuilder } from "./bpmn-builder.js";
|
|
1
|
+
import { DiagramBuilder, ProcessBuilder } from "./bpmn-builder.js";
|
|
2
2
|
import type { BpmnDefinitions } from "./bpmn-model.js";
|
|
3
3
|
/** A minimal 3-element BPMN diagram useful for first-launch or "New Diagram" defaults. */
|
|
4
4
|
export declare const SAMPLE_BPMN_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<bpmn:definitions xmlns:bpmn=\"http://www.omg.org/spec/BPMN/20100524/MODEL\"\n xmlns:bpmndi=\"http://www.omg.org/spec/BPMN/20100524/DI\"\n xmlns:dc=\"http://www.omg.org/spec/DD/20100524/DC\"\n xmlns:di=\"http://www.omg.org/spec/DD/20100524/DI\"\n id=\"Definitions_1\" targetNamespace=\"http://bpmn.io/schema/bpmn\">\n <bpmn:process id=\"proc\" isExecutable=\"true\">\n <bpmn:startEvent id=\"start\" name=\"Start\">\n <bpmn:outgoing>flow1</bpmn:outgoing>\n </bpmn:startEvent>\n <bpmn:serviceTask id=\"task1\" name=\"Process Order\">\n <bpmn:incoming>flow1</bpmn:incoming>\n <bpmn:outgoing>flow2</bpmn:outgoing>\n </bpmn:serviceTask>\n <bpmn:endEvent id=\"end\" name=\"End\">\n <bpmn:incoming>flow2</bpmn:incoming>\n </bpmn:endEvent>\n <bpmn:sequenceFlow id=\"flow1\" sourceRef=\"start\" targetRef=\"task1\"/>\n <bpmn:sequenceFlow id=\"flow2\" sourceRef=\"task1\" targetRef=\"end\"/>\n </bpmn:process>\n <bpmndi:BPMNDiagram id=\"diagram1\">\n <bpmndi:BPMNPlane id=\"plane1\" bpmnElement=\"proc\">\n <bpmndi:BPMNShape id=\"start_di\" bpmnElement=\"start\">\n <dc:Bounds x=\"152\" y=\"202\" width=\"36\" height=\"36\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNShape id=\"task1_di\" bpmnElement=\"task1\">\n <dc:Bounds x=\"260\" y=\"180\" width=\"100\" height=\"80\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNShape id=\"end_di\" bpmnElement=\"end\">\n <dc:Bounds x=\"432\" y=\"202\" width=\"36\" height=\"36\"/>\n </bpmndi:BPMNShape>\n <bpmndi:BPMNEdge id=\"flow1_di\" bpmnElement=\"flow1\">\n <di:waypoint x=\"188\" y=\"220\"/>\n <di:waypoint x=\"260\" y=\"220\"/>\n </bpmndi:BPMNEdge>\n <bpmndi:BPMNEdge id=\"flow2_di\" bpmnElement=\"flow2\">\n <di:waypoint x=\"360\" y=\"220\"/>\n <di:waypoint x=\"432\" y=\"220\"/>\n </bpmndi:BPMNEdge>\n </bpmndi:BPMNPlane>\n </bpmndi:BPMNDiagram>\n</bpmn:definitions>";
|
|
@@ -41,6 +41,20 @@ export declare const Bpmn: {
|
|
|
41
41
|
* ```
|
|
42
42
|
*/
|
|
43
43
|
readonly createProcess: (processId: string) => ProcessBuilder;
|
|
44
|
+
/**
|
|
45
|
+
* Create a multi-process BPMN definitions document using the fluent builder API.
|
|
46
|
+
*
|
|
47
|
+
* @param id - Unique identifier for the definitions element (defaults to `"Definitions_1"`).
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const defs = Bpmn.createDiagram("OrderSystem")
|
|
52
|
+
* .process("caller", (p) => p.startEvent("s").callActivity("call", { processId: "callee" }).endEvent("e"))
|
|
53
|
+
* .process("callee", (p) => p.startEvent("s2").serviceTask("work", { name: "Work", taskType: "work" }).endEvent("e2"))
|
|
54
|
+
* .build()
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
readonly createDiagram: (id?: string) => DiagramBuilder;
|
|
44
58
|
/**
|
|
45
59
|
* Parse a BPMN 2.0 XML string into a typed {@link BpmnDefinitions} model.
|
|
46
60
|
*
|
package/dist/bpmn/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { applyAutoLayout } from "./auto-layout.js";
|
|
2
|
-
import { ProcessBuilder } from "./bpmn-builder.js";
|
|
2
|
+
import { DiagramBuilder, ProcessBuilder } from "./bpmn-builder.js";
|
|
3
3
|
import { parseBpmn } from "./bpmn-parser.js";
|
|
4
4
|
import { serializeBpmn } from "./bpmn-serializer.js";
|
|
5
5
|
/** A minimal 3-element BPMN diagram useful for first-launch or "New Diagram" defaults. */
|
|
@@ -86,6 +86,22 @@ export const Bpmn = {
|
|
|
86
86
|
createProcess(processId) {
|
|
87
87
|
return new ProcessBuilder(processId);
|
|
88
88
|
},
|
|
89
|
+
/**
|
|
90
|
+
* Create a multi-process BPMN definitions document using the fluent builder API.
|
|
91
|
+
*
|
|
92
|
+
* @param id - Unique identifier for the definitions element (defaults to `"Definitions_1"`).
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const defs = Bpmn.createDiagram("OrderSystem")
|
|
97
|
+
* .process("caller", (p) => p.startEvent("s").callActivity("call", { processId: "callee" }).endEvent("e"))
|
|
98
|
+
* .process("callee", (p) => p.startEvent("s2").serviceTask("work", { name: "Work", taskType: "work" }).endEvent("e2"))
|
|
99
|
+
* .build()
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
createDiagram(id = "Definitions_1") {
|
|
103
|
+
return new DiagramBuilder(id);
|
|
104
|
+
},
|
|
89
105
|
/**
|
|
90
106
|
* Parse a BPMN 2.0 XML string into a typed {@link BpmnDefinitions} model.
|
|
91
107
|
*
|
|
@@ -64,6 +64,8 @@ export interface ZeebeExtensions {
|
|
|
64
64
|
formDefinition?: ZeebeFormDefinition;
|
|
65
65
|
/** DMN decision invoked by a business rule task (zeebe:calledDecision). */
|
|
66
66
|
calledDecision?: ZeebeCalledDecision;
|
|
67
|
+
/** Marks this as a Camunda 8 native user task (zeebe:userTask). */
|
|
68
|
+
userTask?: true;
|
|
67
69
|
/** Unrecognized extension elements preserved for roundtrip. */
|
|
68
70
|
unknownElements?: XmlElement[];
|
|
69
71
|
}
|
|
@@ -71,6 +71,9 @@ export function zeebeExtensionsToXmlElements(extensions) {
|
|
|
71
71
|
attrs.activeElementsCollection = activeElementsCollection;
|
|
72
72
|
elements.push({ name: "zeebe:adHoc", attributes: attrs, children: [] });
|
|
73
73
|
}
|
|
74
|
+
if (extensions.userTask) {
|
|
75
|
+
elements.push({ name: "zeebe:userTask", attributes: {}, children: [] });
|
|
76
|
+
}
|
|
74
77
|
if (extensions.formDefinition) {
|
|
75
78
|
elements.push({
|
|
76
79
|
name: "zeebe:formDefinition",
|
package/dist/index.d.ts
CHANGED
|
@@ -4,8 +4,9 @@ export { isBpmnActivity, isBpmnAdHocSubProcess, isBpmnBoundaryEvent, isBpmnBusin
|
|
|
4
4
|
export { findElement, findElementInProcess, findProcess, findSequenceFlow, getAllElements, getElementType, getZeebeExtensions, } from "./bpmn/utils.js";
|
|
5
5
|
export { Bpmn, SAMPLE_BPMN_XML } from "./bpmn/index.js";
|
|
6
6
|
export { applyAutoLayout } from "./bpmn/auto-layout.js";
|
|
7
|
-
export
|
|
8
|
-
export type {
|
|
7
|
+
export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
|
|
8
|
+
export type { ProcessBuilder, BranchBuilder, SubProcessContentBuilder, ServiceTaskOptions, ScriptTaskOptions, UserTaskOptions, CallActivityOptions, BusinessRuleTaskOptions, ElementOptions, GatewayOptions, MultiInstanceOptions, SubProcessOptions, StartEventOptions, IntermediateCatchEventOptions, IntermediateThrowEventOptions, EndEventOptions, BoundaryEventOptions, AdHocSubProcessOptions, } from "./bpmn/bpmn-builder.js";
|
|
9
|
+
export type { BpmnDefinitions, BpmnProcess, BpmnFlowNode, BpmnFlowElement, BpmnSequenceFlow, BpmnBoundaryEvent, BpmnElementType, BpmnStartEvent, BpmnEndEvent, BpmnIntermediateCatchEvent, BpmnIntermediateThrowEvent, BpmnTask, BpmnServiceTask, BpmnScriptTask, BpmnUserTask, BpmnSendTask, BpmnReceiveTask, BpmnBusinessRuleTask, BpmnManualTask, BpmnCallActivity, BpmnSubProcess, BpmnAdHocSubProcess, BpmnEventSubProcess, BpmnTransaction, BpmnExclusiveGateway, BpmnParallelGateway, BpmnInclusiveGateway, BpmnEventBasedGateway, BpmnComplexGateway, BpmnCollaboration, BpmnParticipant, BpmnMessageFlow, BpmnLane, BpmnLaneSet, BpmnError, BpmnEscalation, BpmnMessage, BpmnSignal, BpmnTextAnnotation, BpmnAssociation, BpmnConditionExpression, BpmnEventDefinition, BpmnTimerEventDefinition, BpmnErrorEventDefinition, BpmnEscalationEventDefinition, BpmnMessageEventDefinition, BpmnSignalEventDefinition, BpmnConditionalEventDefinition, BpmnLinkEventDefinition, BpmnCancelEventDefinition, BpmnTerminateEventDefinition, BpmnCompensateEventDefinition, BpmnMultiInstanceLoopCharacteristics, BpmnDiagram, BpmnDiPlane, BpmnDiShape, BpmnDiEdge, BpmnDiLabel, BpmnBounds, BpmnWaypoint, } from "./bpmn/bpmn-model.js";
|
|
9
10
|
export type { RestConnectorConfig, RestAuthentication, HttpMethod, } from "./bpmn/rest-connector.js";
|
|
10
11
|
export type { ZeebeExtensions, ZeebeTaskDefinition, ZeebeIoMapping, ZeebeIoMappingEntry, ZeebeTaskHeaders, ZeebeTaskHeaderEntry, ZeebeProperties, ZeebePropertyEntry, ZeebeFormDefinition, ZeebeCalledDecision, } from "./bpmn/zeebe-extensions.js";
|
|
11
12
|
export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
|
|
@@ -32,7 +33,7 @@ export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
|
|
|
32
33
|
export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
|
|
33
34
|
export type { BenchmarkResult, BoundingBox, ElementComparison, ElementPosition, FlowOrderViolation, } from "./layout/index.js";
|
|
34
35
|
export type { Bounds, LayoutEdge, LayoutNode, LayoutResult, Waypoint } from "./layout/index.js";
|
|
35
|
-
export { ELEMENT_SIZES, GRID_CELL_HEIGHT } from "./layout/index.js";
|
|
36
|
+
export { ELEMENT_SIZES, GRID_CELL_HEIGHT, GRID_CELL_WIDTH } from "./layout/index.js";
|
|
36
37
|
export { compactify, expand } from "./bpmn/compact.js";
|
|
37
38
|
export { applyOperations } from "./bpmn/operations.js";
|
|
38
39
|
export type { BpmnOperation } from "./bpmn/operations.js";
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ export { isBpmnActivity, isBpmnAdHocSubProcess, isBpmnBoundaryEvent, isBpmnBusin
|
|
|
3
3
|
export { findElement, findElementInProcess, findProcess, findSequenceFlow, getAllElements, getElementType, getZeebeExtensions, } from "./bpmn/utils.js";
|
|
4
4
|
export { Bpmn, SAMPLE_BPMN_XML } from "./bpmn/index.js";
|
|
5
5
|
export { applyAutoLayout } from "./bpmn/auto-layout.js";
|
|
6
|
+
export { DiagramBuilder } from "./bpmn/bpmn-builder.js";
|
|
6
7
|
export { zeebeExtensionsToXmlElements } from "./bpmn/zeebe-extensions.js";
|
|
7
8
|
export { Dmn, layoutDmn, benchmarkDmnLayout, compactifyDmn, expandDmn } from "./dmn/index.js";
|
|
8
9
|
export { Form, compactifyForm, expandForm } from "./form/index.js";
|
|
@@ -15,7 +16,7 @@ export { renderStoryHtml } from "./bpmn/story.js";
|
|
|
15
16
|
export { analyzeVariableFlow, extractFeelIdentifiers } from "./bpmn/optimize/variable-flow.js";
|
|
16
17
|
export { layoutProcess, layoutFlowNodes } from "./layout/index.js";
|
|
17
18
|
export { benchmarkLayout, compareLayouts, formatBenchmarkResult, generateAutoLayout, parseReferenceLayout, } from "./layout/index.js";
|
|
18
|
-
export { ELEMENT_SIZES, GRID_CELL_HEIGHT } from "./layout/index.js";
|
|
19
|
+
export { ELEMENT_SIZES, GRID_CELL_HEIGHT, GRID_CELL_WIDTH } from "./layout/index.js";
|
|
19
20
|
export { compactify, expand } from "./bpmn/compact.js";
|
|
20
21
|
export { applyOperations } from "./bpmn/operations.js";
|
|
21
22
|
export { buildValidationDmn, findValidationStructure, getValidationInputNames, insertValidationStructure, removeValidationStructure, validationDecisionId, } from "./bpmn/input-validation.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Bounds, Waypoint } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Route a single edge using A* on a 10px grid.
|
|
4
|
+
* source/target are center points of source/target nodes.
|
|
5
|
+
* obstacles are node bounding boxes to avoid (inflated by 6px margin).
|
|
6
|
+
* Returns simplified orthogonal waypoints.
|
|
7
|
+
*/
|
|
8
|
+
export declare function routeEdgeAstar(source: {
|
|
9
|
+
x: number;
|
|
10
|
+
y: number;
|
|
11
|
+
}, target: {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
}, obstacles: Bounds[], canvasWidth: number, canvasHeight: number): Waypoint[];
|
|
15
|
+
//# sourceMappingURL=astar.d.ts.map
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
const GRID_RES = 10;
|
|
2
|
+
const OBSTACLE_MARGIN = 6;
|
|
3
|
+
const TURN_PENALTY = 5;
|
|
4
|
+
const CANVAS_EXTEND = 80;
|
|
5
|
+
const DX = [1, 0, -1, 0];
|
|
6
|
+
const DY = [0, 1, 0, -1];
|
|
7
|
+
/** Minimal binary min-heap keyed by f-score. */
|
|
8
|
+
class MinHeap {
|
|
9
|
+
data = [];
|
|
10
|
+
push(f, key) {
|
|
11
|
+
this.data.push({ f, key });
|
|
12
|
+
this.bubbleUp(this.data.length - 1);
|
|
13
|
+
}
|
|
14
|
+
pop() {
|
|
15
|
+
const top = this.data[0];
|
|
16
|
+
const last = this.data.pop();
|
|
17
|
+
if (this.data.length > 0 && last !== undefined) {
|
|
18
|
+
this.data[0] = last;
|
|
19
|
+
this.sinkDown(0);
|
|
20
|
+
}
|
|
21
|
+
return top;
|
|
22
|
+
}
|
|
23
|
+
get size() {
|
|
24
|
+
return this.data.length;
|
|
25
|
+
}
|
|
26
|
+
bubbleUp(startIdx) {
|
|
27
|
+
let i = startIdx;
|
|
28
|
+
while (i > 0) {
|
|
29
|
+
const parent = (i - 1) >> 1;
|
|
30
|
+
const d = this.data[i];
|
|
31
|
+
const p = this.data[parent];
|
|
32
|
+
if (!d || !p || p.f <= d.f)
|
|
33
|
+
break;
|
|
34
|
+
this.data[i] = p;
|
|
35
|
+
this.data[parent] = d;
|
|
36
|
+
i = parent;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
sinkDown(startIdx) {
|
|
40
|
+
let i = startIdx;
|
|
41
|
+
for (;;) {
|
|
42
|
+
const left = 2 * i + 1;
|
|
43
|
+
const right = 2 * i + 2;
|
|
44
|
+
let smallest = i;
|
|
45
|
+
const d = this.data[smallest];
|
|
46
|
+
const l = this.data[left];
|
|
47
|
+
const r = this.data[right];
|
|
48
|
+
if (l && l.f < (d?.f ?? Number.POSITIVE_INFINITY))
|
|
49
|
+
smallest = left;
|
|
50
|
+
const s = this.data[smallest];
|
|
51
|
+
if (r && r.f < (s?.f ?? Number.POSITIVE_INFINITY))
|
|
52
|
+
smallest = right;
|
|
53
|
+
if (smallest === i)
|
|
54
|
+
break;
|
|
55
|
+
const tmp = this.data[i];
|
|
56
|
+
const sm = this.data[smallest];
|
|
57
|
+
if (!tmp || !sm)
|
|
58
|
+
break;
|
|
59
|
+
this.data[i] = sm;
|
|
60
|
+
this.data[smallest] = tmp;
|
|
61
|
+
i = smallest;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Route a single edge using A* on a 10px grid.
|
|
67
|
+
* source/target are center points of source/target nodes.
|
|
68
|
+
* obstacles are node bounding boxes to avoid (inflated by 6px margin).
|
|
69
|
+
* Returns simplified orthogonal waypoints.
|
|
70
|
+
*/
|
|
71
|
+
export function routeEdgeAstar(source, target, obstacles, canvasWidth, canvasHeight) {
|
|
72
|
+
// Extend canvas to allow routing around edges
|
|
73
|
+
const minX = Math.max(0, Math.min(source.x, target.x) - CANVAS_EXTEND);
|
|
74
|
+
const minY = Math.max(0, Math.min(source.y, target.y) - CANVAS_EXTEND);
|
|
75
|
+
const maxX = Math.max(source.x, target.x) + CANVAS_EXTEND + canvasWidth;
|
|
76
|
+
const maxY = Math.max(source.y, target.y) + CANVAS_EXTEND + canvasHeight;
|
|
77
|
+
const cols = Math.ceil((maxX - minX) / GRID_RES) + 1;
|
|
78
|
+
const rows = Math.ceil((maxY - minY) / GRID_RES) + 1;
|
|
79
|
+
// Snap source and target to grid
|
|
80
|
+
const sx = Math.round((source.x - minX) / GRID_RES);
|
|
81
|
+
const sy = Math.round((source.y - minY) / GRID_RES);
|
|
82
|
+
const tx = Math.round((target.x - minX) / GRID_RES);
|
|
83
|
+
const ty = Math.round((target.y - minY) / GRID_RES);
|
|
84
|
+
// If they're at the same grid cell, return straight line
|
|
85
|
+
if (sx === tx && sy === ty) {
|
|
86
|
+
return [source, target];
|
|
87
|
+
}
|
|
88
|
+
// Build blocked grid
|
|
89
|
+
const blocked = new Uint8Array(cols * rows);
|
|
90
|
+
for (const ob of obstacles) {
|
|
91
|
+
const ox1 = Math.floor((ob.x - OBSTACLE_MARGIN - minX) / GRID_RES);
|
|
92
|
+
const oy1 = Math.floor((ob.y - OBSTACLE_MARGIN - minY) / GRID_RES);
|
|
93
|
+
const ox2 = Math.ceil((ob.x + ob.width + OBSTACLE_MARGIN - minX) / GRID_RES);
|
|
94
|
+
const oy2 = Math.ceil((ob.y + ob.height + OBSTACLE_MARGIN - minY) / GRID_RES);
|
|
95
|
+
for (let gy = Math.max(0, oy1); gy <= Math.min(rows - 1, oy2); gy++) {
|
|
96
|
+
for (let gx = Math.max(0, ox1); gx <= Math.min(cols - 1, ox2); gx++) {
|
|
97
|
+
blocked[gy * cols + gx] = 1;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Unblock source and target cells (they're inside nodes)
|
|
102
|
+
blocked[sy * cols + sx] = 0;
|
|
103
|
+
blocked[ty * cols + tx] = 0;
|
|
104
|
+
// A* with direction-aware state: state = cell * 4 + dir
|
|
105
|
+
const INF = Number.MAX_SAFE_INTEGER;
|
|
106
|
+
// g-scores per (cell, dir)
|
|
107
|
+
const g = new Float32Array(cols * rows * 4).fill(INF);
|
|
108
|
+
const parent = new Int32Array(cols * rows * 4).fill(-1);
|
|
109
|
+
const parentDir = new Int8Array(cols * rows * 4).fill(-1);
|
|
110
|
+
const heap = new MinHeap();
|
|
111
|
+
// Initialize: try all 4 directions from source
|
|
112
|
+
for (let d = 0; d < 4; d++) {
|
|
113
|
+
const key = (sy * cols + sx) * 4 + d;
|
|
114
|
+
g[key] = 0;
|
|
115
|
+
const h = Math.abs(tx - sx) + Math.abs(ty - sy);
|
|
116
|
+
heap.push(h, key);
|
|
117
|
+
}
|
|
118
|
+
let found = false;
|
|
119
|
+
while (heap.size > 0) {
|
|
120
|
+
const item = heap.pop();
|
|
121
|
+
if (!item)
|
|
122
|
+
break;
|
|
123
|
+
const { key } = item;
|
|
124
|
+
const dir = key % 4;
|
|
125
|
+
const cell = (key - dir) / 4;
|
|
126
|
+
const cx = cell % cols;
|
|
127
|
+
const cy = (cell - cx) / cols;
|
|
128
|
+
if (cx === tx && cy === ty) {
|
|
129
|
+
found = true;
|
|
130
|
+
// Reconstruct path
|
|
131
|
+
const path = [];
|
|
132
|
+
let k = key;
|
|
133
|
+
while (k !== -1) {
|
|
134
|
+
const kDir = k % 4;
|
|
135
|
+
const kCell = (k - kDir) / 4;
|
|
136
|
+
const kx = kCell % cols;
|
|
137
|
+
const ky = (kCell - kx) / cols;
|
|
138
|
+
path.push({ x: kx * GRID_RES + minX, y: ky * GRID_RES + minY });
|
|
139
|
+
k = parent[k] ?? -1;
|
|
140
|
+
}
|
|
141
|
+
path.reverse();
|
|
142
|
+
// Simplify collinear points
|
|
143
|
+
return simplifyPath(path);
|
|
144
|
+
}
|
|
145
|
+
const gCur = g[key] ?? INF;
|
|
146
|
+
for (let nd = 0; nd < 4; nd++) {
|
|
147
|
+
const nx = cx + (DX[nd] ?? 0);
|
|
148
|
+
const ny = cy + (DY[nd] ?? 0);
|
|
149
|
+
if (nx < 0 || nx >= cols || ny < 0 || ny >= rows)
|
|
150
|
+
continue;
|
|
151
|
+
const ncell = ny * cols + nx;
|
|
152
|
+
if (blocked[ncell])
|
|
153
|
+
continue;
|
|
154
|
+
const nkey = ncell * 4 + nd;
|
|
155
|
+
const turnCost = nd !== dir ? TURN_PENALTY : 0;
|
|
156
|
+
const ng = gCur + 1 + turnCost;
|
|
157
|
+
const prevG = g[nkey] ?? INF;
|
|
158
|
+
if (ng < prevG) {
|
|
159
|
+
g[nkey] = ng;
|
|
160
|
+
parent[nkey] = key;
|
|
161
|
+
parentDir[nkey] = dir;
|
|
162
|
+
const h = Math.abs(tx - nx) + Math.abs(ty - ny);
|
|
163
|
+
heap.push(ng + h, nkey);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (!found) {
|
|
168
|
+
// Fall back to straight line
|
|
169
|
+
return [source, target];
|
|
170
|
+
}
|
|
171
|
+
return [source, target];
|
|
172
|
+
}
|
|
173
|
+
/** Remove collinear intermediate waypoints. */
|
|
174
|
+
function simplifyPath(path) {
|
|
175
|
+
if (path.length <= 2)
|
|
176
|
+
return path;
|
|
177
|
+
const result = [path[0]];
|
|
178
|
+
for (let i = 1; i < path.length - 1; i++) {
|
|
179
|
+
const prev = path[i - 1];
|
|
180
|
+
const curr = path[i];
|
|
181
|
+
const next = path[i + 1];
|
|
182
|
+
const isCollinear = (Math.abs(prev.x - curr.x) < 0.5 && Math.abs(curr.x - next.x) < 0.5) ||
|
|
183
|
+
(Math.abs(prev.y - curr.y) < 0.5 && Math.abs(curr.y - next.y) < 0.5);
|
|
184
|
+
if (!isCollinear) {
|
|
185
|
+
result.push(curr);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
result.push(path[path.length - 1]);
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=astar.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { BpmnElementType, BpmnFlowElement } from "../bpmn/bpmn-model.js";
|
|
2
|
+
import type { DirectedGraph } from "./graph.js";
|
|
3
|
+
export type FlowBlock = NodeBlock | GatewayBlock | SequenceBlock;
|
|
4
|
+
export interface NodeBlock {
|
|
5
|
+
kind: "node";
|
|
6
|
+
id: string;
|
|
7
|
+
type: BpmnElementType;
|
|
8
|
+
label?: string;
|
|
9
|
+
width: number;
|
|
10
|
+
height: number;
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SequenceBlock {
|
|
15
|
+
kind: "sequence";
|
|
16
|
+
items: FlowBlock[];
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}
|
|
22
|
+
export interface GatewayBlock {
|
|
23
|
+
kind: "gateway";
|
|
24
|
+
split: NodeBlock;
|
|
25
|
+
join: NodeBlock;
|
|
26
|
+
branches: SequenceBlock[];
|
|
27
|
+
branchColumnWidth: number;
|
|
28
|
+
width: number;
|
|
29
|
+
height: number;
|
|
30
|
+
x: number;
|
|
31
|
+
y: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Attempt to build block tree. Returns null if process is unstructured.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildBlockTree(dag: DirectedGraph, nodeIndex: Map<string, BpmnFlowElement>): SequenceBlock | null;
|
|
37
|
+
//# sourceMappingURL=block-builder.d.ts.map
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { topologicalSort } from "./graph.js";
|
|
2
|
+
import { ELEMENT_SIZES } from "./types.js";
|
|
3
|
+
const GATEWAY_TYPES = new Set([
|
|
4
|
+
"exclusiveGateway",
|
|
5
|
+
"parallelGateway",
|
|
6
|
+
"inclusiveGateway",
|
|
7
|
+
"eventBasedGateway",
|
|
8
|
+
"complexGateway",
|
|
9
|
+
]);
|
|
10
|
+
/** Make a NodeBlock for the given element. */
|
|
11
|
+
function makeNodeBlock(id, nodeIndex) {
|
|
12
|
+
const el = nodeIndex.get(id);
|
|
13
|
+
const type = el?.type ?? "serviceTask";
|
|
14
|
+
const size = ELEMENT_SIZES[type] ?? { width: 100, height: 80 };
|
|
15
|
+
return {
|
|
16
|
+
kind: "node",
|
|
17
|
+
id,
|
|
18
|
+
type,
|
|
19
|
+
label: el?.name,
|
|
20
|
+
width: size.width,
|
|
21
|
+
height: size.height,
|
|
22
|
+
x: 0,
|
|
23
|
+
y: 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Find the matching join gateway for a split gateway.
|
|
28
|
+
* Uses a depth counter across the topoOrder of provided node IDs:
|
|
29
|
+
* +1 for each split (outDegree≥2), -1 for each join (inDegree≥2).
|
|
30
|
+
* First time depth returns to 0 → that's the matching join.
|
|
31
|
+
*/
|
|
32
|
+
function findMatchingJoin(splitId, orderedIds, splitPos, dag, nodeIndex) {
|
|
33
|
+
let depth = 1;
|
|
34
|
+
for (let j = splitPos + 1; j < orderedIds.length; j++) {
|
|
35
|
+
const id = orderedIds[j];
|
|
36
|
+
if (!id)
|
|
37
|
+
continue;
|
|
38
|
+
const el = nodeIndex.get(id);
|
|
39
|
+
if (!el || !GATEWAY_TYPES.has(el.type))
|
|
40
|
+
continue;
|
|
41
|
+
const outDegree = (dag.successors.get(id) ?? []).length;
|
|
42
|
+
const inDegree = (dag.predecessors.get(id) ?? []).length;
|
|
43
|
+
if (outDegree >= 2)
|
|
44
|
+
depth++;
|
|
45
|
+
if (inDegree >= 2) {
|
|
46
|
+
depth--;
|
|
47
|
+
if (depth === 0)
|
|
48
|
+
return id;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Collect all node IDs reachable from startId before reaching stopId (exclusive).
|
|
55
|
+
* Uses BFS over DAG successors. The stop node is not included.
|
|
56
|
+
*/
|
|
57
|
+
function collectBranchNodes(startId, stopId, dag) {
|
|
58
|
+
const result = [];
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const queue = [startId];
|
|
61
|
+
while (queue.length > 0) {
|
|
62
|
+
const id = queue.shift();
|
|
63
|
+
if (!id || seen.has(id) || id === stopId)
|
|
64
|
+
continue;
|
|
65
|
+
seen.add(id);
|
|
66
|
+
result.push(id);
|
|
67
|
+
for (const succ of dag.successors.get(id) ?? []) {
|
|
68
|
+
if (!seen.has(succ) && succ !== stopId) {
|
|
69
|
+
queue.push(succ);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Build a SequenceBlock for the given ordered list of node IDs.
|
|
77
|
+
* Recursively handles nested gateways.
|
|
78
|
+
* Throws if the structure is unstructured (no matching join found).
|
|
79
|
+
*/
|
|
80
|
+
function buildSequenceFromIds(orderedIds, dag, nodeIndex, topoPos) {
|
|
81
|
+
const items = [];
|
|
82
|
+
let i = 0;
|
|
83
|
+
while (i < orderedIds.length) {
|
|
84
|
+
const id = orderedIds[i];
|
|
85
|
+
if (!id) {
|
|
86
|
+
i++;
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const el = nodeIndex.get(id);
|
|
90
|
+
const outDegree = (dag.successors.get(id) ?? []).length;
|
|
91
|
+
const isSplit = outDegree >= 2 && el !== undefined && GATEWAY_TYPES.has(el.type);
|
|
92
|
+
if (isSplit) {
|
|
93
|
+
// This is a split gateway — find its matching join within orderedIds
|
|
94
|
+
const joinId = findMatchingJoin(id, orderedIds, i, dag, nodeIndex);
|
|
95
|
+
if (!joinId)
|
|
96
|
+
throw new Error(`No matching join for split gateway ${id}`);
|
|
97
|
+
const joinPos = orderedIds.indexOf(joinId);
|
|
98
|
+
if (joinPos < 0)
|
|
99
|
+
throw new Error(`Join ${joinId} not in current sequence`);
|
|
100
|
+
// For each successor of split, collect branch nodes (up to join)
|
|
101
|
+
const successors = dag.successors.get(id) ?? [];
|
|
102
|
+
const branches = [];
|
|
103
|
+
for (const succId of successors) {
|
|
104
|
+
// Collect nodes in this branch (BFS stopping at join)
|
|
105
|
+
const branchNodes = collectBranchNodes(succId, joinId, dag);
|
|
106
|
+
// Filter to only nodes that appear in orderedIds (safety)
|
|
107
|
+
const branchSet = new Set(branchNodes);
|
|
108
|
+
// Sort by topo position
|
|
109
|
+
const branchOrdered = orderedIds.filter((nid) => branchSet.has(nid));
|
|
110
|
+
branches.push(buildSequenceFromIds(branchOrdered, dag, nodeIndex, topoPos));
|
|
111
|
+
}
|
|
112
|
+
const splitBlock = makeNodeBlock(id, nodeIndex);
|
|
113
|
+
const joinBlock = makeNodeBlock(joinId, nodeIndex);
|
|
114
|
+
const gatewayBlock = {
|
|
115
|
+
kind: "gateway",
|
|
116
|
+
split: splitBlock,
|
|
117
|
+
join: joinBlock,
|
|
118
|
+
branches,
|
|
119
|
+
branchColumnWidth: 0,
|
|
120
|
+
width: 0,
|
|
121
|
+
height: 0,
|
|
122
|
+
x: 0,
|
|
123
|
+
y: 0,
|
|
124
|
+
};
|
|
125
|
+
items.push(gatewayBlock);
|
|
126
|
+
// Skip past the join gateway
|
|
127
|
+
i = joinPos + 1;
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
items.push(makeNodeBlock(id, nodeIndex));
|
|
131
|
+
i++;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { kind: "sequence", items, width: 0, height: 0, x: 0, y: 0 };
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Attempt to build block tree. Returns null if process is unstructured.
|
|
138
|
+
*/
|
|
139
|
+
export function buildBlockTree(dag, nodeIndex) {
|
|
140
|
+
try {
|
|
141
|
+
const topoOrder = topologicalSort(dag);
|
|
142
|
+
const topoPos = new Map();
|
|
143
|
+
for (let i = 0; i < topoOrder.length; i++) {
|
|
144
|
+
const id = topoOrder[i];
|
|
145
|
+
if (id)
|
|
146
|
+
topoPos.set(id, i);
|
|
147
|
+
}
|
|
148
|
+
return buildSequenceFromIds(topoOrder, dag, nodeIndex, topoPos);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=block-builder.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { BpmnFlowElement } from "../bpmn/bpmn-model.js";
|
|
2
|
+
import type { SequenceBlock } from "./block-builder.js";
|
|
3
|
+
import type { LayoutNode } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Apply block-based layout: size (bottom-up) then position (top-down).
|
|
6
|
+
* Returns LayoutNode[] with absolute positions.
|
|
7
|
+
*/
|
|
8
|
+
export declare function applyBlockLayout(root: SequenceBlock, nodeIndex: Map<string, BpmnFlowElement>): LayoutNode[];
|
|
9
|
+
//# sourceMappingURL=block-layout.d.ts.map
|