diff --git a/flowset-ui-kit/src/main/java/io/flowset/uikit/fragment/bpmnviewer/BpmnViewerFragment.java b/flowset-ui-kit/src/main/java/io/flowset/uikit/fragment/bpmnviewer/BpmnViewerFragment.java
index e544621..23ca4a4 100644
--- a/flowset-ui-kit/src/main/java/io/flowset/uikit/fragment/bpmnviewer/BpmnViewerFragment.java
+++ b/flowset-ui-kit/src/main/java/io/flowset/uikit/fragment/bpmnviewer/BpmnViewerFragment.java
@@ -59,6 +59,11 @@ public class BpmnViewerFragment extends Fragment
{
protected ViewerMode mode;
protected BpmnViewer bpmnViewer;
+ @Subscribe(target = Target.HOST_CONTROLLER)
+ public void onHostInit(final View.InitEvent event) {
+ onInit();
+ }
+
@Subscribe(target = Target.HOST_CONTROLLER)
public void onHostBeforeShow(final View.BeforeShowEvent event) {
if (!noBorders) {
@@ -103,7 +108,7 @@ public ViewerMode getMode() {
}
public void initViewer(String bpmnXml) {
- this.bpmnViewer = uiComponents.create(BpmnViewer.class);
+ this.bpmnViewer = createBpmnViewer();
this.bpmnViewer.setBpmnXml(bpmnXml);
this.bpmnViewer.setMode(mode);
@@ -274,4 +279,12 @@ public void showCalledProcessOverlays() {
});
}
}
+
+ protected void onInit() {
+
+ }
+
+ protected BpmnViewer createBpmnViewer() {
+ return uiComponents.create(BpmnViewer.class);
+ }
}
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/flowset-bpmn-viewer.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/flowset-bpmn-viewer.ts
index 2c32225..031c497 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/flowset-bpmn-viewer.ts
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/flowset-bpmn-viewer.ts
@@ -165,6 +165,18 @@ class FlowsetBpmnViewer extends LitElement {
});
}
+ public addTransactionBoundaries() {
+ this.awaitRun(() => {
+ this.overlayManager.addTransactionBoundaryOverlays();
+ });
+ }
+
+ public setTransactionBoundariesVisible(visible: boolean) {
+ this.awaitRun(() => {
+ this.overlayManager.updateOverlaysVisibility(OverlayType.TRANSACTION_BOUNDARY, visible);
+ });
+ }
+
public addMarker(cmdJson: string) {
const cmd: AddMarkerCmd = JSON.parse(cmdJson);
this.awaitRun(() => this.canvas.addMarker(cmd.elementId, cmd.marker));
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/OverlayManager.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/OverlayManager.ts
index ec96e22..0144f09 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/OverlayManager.ts
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/OverlayManager.ts
@@ -23,12 +23,18 @@ import BpmnViewer from "../bpm/js/BpmnViewer";
import ElementRegistry from 'diagram-js/lib/core/ElementRegistry';
import Canvas from 'diagram-js/lib/core/Canvas';
import {createNavigationOverlay} from "./createNavigationOverlay";
-import {getMessage, isMessageSupported} from "../utils/bpmnEventUtils";
+import {getMessage, isMessageSupported} from "../utils/eventDefinitionUtils";
import {createSendMessageOverlay} from "./createSendMessageOverlay";
import {getBinding, getCalledElement, getVersion, getVersionTag} from "../utils/callActivityUtils";
-import {ElementLike} from "diagram-js/lib/model/Types";
+import {ElementLike, Shape} from "diagram-js/lib/model/Types";
import {findElementDocumentation} from "../utils/documentationUtils";
import {createActivityStatisticsOverlay} from "./createActivityStatisticsOverlay";
+import {getElementTransactionBoundary} from "../utils/transactionBoundaryUtils";
+import {forEach} from 'min-dash';
+import {createTransactionBoundaryOverlay} from "./createTransactionBoundaryOverlay";
+import {BeforeElementTransactionType, ElementTransactionBoundary} from "../types";
+import {DEFAULT_LABEL_SIZE} from "bpmn-js/lib/util/LabelUtil";
+import {Point, Rect} from "diagram-js/lib/util/Types";
/**
* OverlayManager class manages various overlays associated with BPMN diagram elements,
@@ -51,7 +57,10 @@ export class OverlayManager {
* @param data overlay data
*/
public showIncidentOverlay(data: IncidentOverlayData) {
- const incidentOverlay = createIncidentOverlay({tooltipMessage : data.tooltipMessage, incidentCount : data.incidentCount});
+ const incidentOverlay = createIncidentOverlay({
+ tooltipMessage: data.tooltipMessage,
+ incidentCount: data.incidentCount
+ });
this.overlays.add(data.elementId, OverlayType.INCIDENT_COUNT, incidentOverlay);
}
@@ -117,7 +126,10 @@ export class OverlayManager {
const handleOverlayClick = () => {
handleClick(data.decisionInstanceId);
}
- const decisionInstanceOverlay = createNavigationOverlay({title : data.tooltipMessage, handleClick : handleOverlayClick});
+ const decisionInstanceOverlay = createNavigationOverlay({
+ title: data.tooltipMessage,
+ handleClick: handleOverlayClick
+ });
this.overlays.add(element.id, OverlayType.DECISION_INSTANCE, decisionInstanceOverlay);
}
@@ -148,7 +160,10 @@ export class OverlayManager {
}
const overlayTooltip = `${data.tooltipMessage}: ${message.get("name")}`;
- const sendMessageOverlay: OverlayAttrs = createSendMessageOverlay({title : overlayTooltip, handleClick : handleOverlayClick});
+ const sendMessageOverlay: OverlayAttrs = createSendMessageOverlay({
+ title: overlayTooltip,
+ handleClick: handleOverlayClick
+ });
this.overlays.add(element.id, OverlayType.SEND_MESSAGE, sendMessageOverlay);
}
});
@@ -167,7 +182,10 @@ export class OverlayManager {
handleClick(details);
}
- const calledInstancesOverlay = createNavigationOverlay({title : data.tooltipMessage, handleClick : handleOverlayClick});
+ const calledInstancesOverlay = createNavigationOverlay({
+ title: data.tooltipMessage,
+ handleClick: handleOverlayClick
+ });
this.overlays.add(data.elementId, OverlayType.CALLED_PROCESS_INSTANCE, calledInstancesOverlay);
}
@@ -197,7 +215,10 @@ export class OverlayManager {
}
const tooltipMessage = `${data.tooltipMessage} (${calledElement})`;
- const calledProcessOverlay = createNavigationOverlay({title : tooltipMessage, handleClick : handleOverlayClick});
+ const calledProcessOverlay = createNavigationOverlay({
+ title: tooltipMessage,
+ handleClick: handleOverlayClick
+ });
this.overlays.add(element.id, OverlayType.CALLED_PROCESS, calledProcessOverlay);
}
@@ -227,6 +248,111 @@ export class OverlayManager {
this.overlays.add(data.elementId, OverlayType.ACTIVITY_STATISTICS, createActivityStatisticsOverlay(data));
}
+ /**
+ * Adds overlays for the transaction boundary (before/after) for all elements.
+ */
+ public addTransactionBoundaryOverlays() {
+ this.overlays.remove({type: OverlayType.TRANSACTION_BOUNDARY});
+
+ const elements: ElementLike[] = this.elementRegistry.filter(element => {
+ return element.type !== 'label';
+ });
+
+ elements.forEach((shape: ElementLike) => {
+ const transactionBoundary: ElementTransactionBoundary = getElementTransactionBoundary(shape);
+
+ if (!transactionBoundary) {
+ return;
+ }
+
+ const addIncomingTransactionBoundaryOverlay = (type: BeforeElementTransactionType) => {
+ const incoming = shape.incoming || [];
+ const hasIncoming = incoming.length > 0;
+
+ if (hasIncoming) {
+ this.addOverlayForConnections(shape, transactionBoundary, incoming, true, type);
+ } else {
+ // no incoming connection, calculate position in the front
+ this.addTransactionBoundaryOverlay(shape, {
+ x: shape.x,
+ y: shape.y + shape.height / 2
+ }, transactionBoundary, type);
+ }
+ };
+
+ if (transactionBoundary.engineWaitState) {
+ addIncomingTransactionBoundaryOverlay(BeforeElementTransactionType.ENGINE_WAIT_STATE)
+ }
+
+ if (transactionBoundary.asyncBefore) {
+ addIncomingTransactionBoundaryOverlay(BeforeElementTransactionType.ASYNC_BEFORE);
+ }
+
+ if (transactionBoundary.asyncAfter) {
+ const outgoing = shape.outgoing || [];
+ const hasOutgoing = outgoing.length > 0;
+
+ if (hasOutgoing) {
+ this.addOverlayForConnections(shape, transactionBoundary, outgoing, false);
+ } else {
+ // no outgoing connection, calculate position after the element
+ this.addTransactionBoundaryOverlay(shape, {
+ x: shape.x + shape.width,
+ y: shape.y + shape.height / 2
+ }, transactionBoundary);
+ }
+ }
+
+ });
+ }
+
+ /**
+ * Adds a transaction boundary overlay to a shape element at a specified waypoint.
+ *
+ * @param {ElementLike} shape - The shape element to which the transaction boundary overlay will be added.
+ * @param {Point} waypoint - The waypoint indicating the location of the transaction boundary.
+ * @param {ElementTransactionBoundary} [transactionBoundary] - Optional parameter representing the transaction boundary element.
+ * @param {BeforeElementTransactionType} [type] - Required for the transactions before the element.
+ */
+ private addTransactionBoundaryOverlay = (shape: ElementLike, waypoint: Point,
+ transactionBoundary?: ElementTransactionBoundary, type?: BeforeElementTransactionType): void => {
+ const rect = {...waypoint};
+ const overlay = createTransactionBoundaryOverlay({
+ shape,
+ waypoint: rect,
+ transactionBoundary,
+ beforeType: type
+ });
+ this.overlays.add(shape.id, OverlayType.TRANSACTION_BOUNDARY, overlay);
+ };
+
+ /**
+ * Adds overlays to the provided connections based on transaction boundaries.
+ *
+ * @param {ElementLike} shape - The visual object to which the overlays will be added.
+ * @param {ElementTransactionBoundary} transactionBoundary - The transaction boundary linked to the overlay.
+ * @param {any[]} connections - An array of connections to process for adding overlays.
+ * @param {boolean} isIncoming - Specifies whether the connections are incoming or outgoing.
+ * @return {void} This method does not return a value.
+ */
+ private addOverlayForConnections(shape: ElementLike, transactionBoundary: ElementTransactionBoundary,
+ connections: any[], isIncoming: boolean, type?: BeforeElementTransactionType): void {
+ forEach(connections, (connection: any) => {
+ if (connection.type !== 'bpmn:SequenceFlow') {
+ return;
+ }
+
+ let waypoint: Rect;
+ if(isIncoming) {
+ const lastWaypointIndex = connection.waypoints.length - 1;
+ waypoint = connection.waypoints[lastWaypointIndex];
+ } else {
+ waypoint = connection.waypoints[0];
+ }
+ this.addTransactionBoundaryOverlay(shape, waypoint, transactionBoundary, type);
+ });
+ }
+
private shouldRenderSendMessageOverlay(element: ElementLike, data: SendMessageOverlaysData): boolean {
if (data.useActiveEvents) {
const isRunningActivity = this.canvas.hasMarker(element.id, 'running-activity');
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/createTransactionBoundaryOverlay.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/createTransactionBoundaryOverlay.ts
new file mode 100644
index 0000000..9f80c07
--- /dev/null
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/createTransactionBoundaryOverlay.ts
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) Haulmont 2025. All Rights Reserved.
+ * Use is subject to license terms.
+ */
+
+import {OverlayPosition} from "./types";
+
+import {OverlayAttrs} from "diagram-js/lib/features/overlays/Overlays";
+import {BeforeElementTransactionType, ElementTransactionBoundary} from "../types";
+import {getOrientation} from "diagram-js/lib/layout/LayoutUtil";
+import {ShapeLike} from "bpmn-js/lib/draw/BpmnRenderUtil";
+
+export type TransactionBoundaryOverlayData = {
+ shape: ShapeLike;
+ waypoint: any;
+ beforeType?: BeforeElementTransactionType;
+ transactionBoundary?: ElementTransactionBoundary;
+}
+
+type Orientation =
+ 'left'
+ | 'right'
+ | 'top'
+ | 'bottom'
+ | 'top-left'
+ | 'top-right'
+ | 'bottom-left'
+ | 'bottom-right'
+ | 'intersect';
+
+/**
+ * Creates an overlay representing a transaction boundary at a given position on a shape.
+ *
+ * The function calculates the overlay position based on the relative position of the point and the element.
+ * For example, in the case of an overlay for a gateway with 3 incoming elements (top/bottom and right):
+ * 1. For the left connection, there will be a vertical overlay on the left of element
+ * 2. For the top and bottom connections, there will be a horizontal overlay on the top and bottom of element
+ *
+ * @param {TransactionBoundaryOverlayData} config - the configuration object for creating the overlay
+ * @param {Shape} config.shape - the diagram shape for which the overlay is created.
+ * @param {Waypoint} config.waypoint - a waypoint used for alignment and orientation.
+ * @param {string} config.beforeType - the type of transaction if transaction is done before the element.
+ * @param {TransactionBoundary} config.transactionBoundary - transaction boundary details.
+ * @returns {OverlayAttrs} The calculated attributes for the overlay, such as HTML structure and position.
+ */
+export const createTransactionBoundaryOverlay = ({
+
+ shape,
+ waypoint,
+ beforeType,
+ transactionBoundary
+ }: TransactionBoundaryOverlayData): OverlayAttrs => {
+
+ let orientation: Orientation = getOrientation(waypoint, shape, -7);
+
+ if (orientation === 'intersect') {
+ // Try again using a bigger padding to get an orientation which is not 'intersect'.
+ // Otherwise the boundary would not be visible if the connection is attached on the
+ // diagonal edge of a gateway. Not perfect, but much better than showing no overlay at all.
+ orientation = getOrientation(waypoint, shape, -20);
+ }
+
+ let strokeWidth = 7; //width of overlay dot line
+ const offset = 5; //overlay offset from top/right
+
+ let margin = 2;
+
+ let position: OverlayPosition = {};
+ let height: number;
+ let width: number;
+
+ if (beforeType === BeforeElementTransactionType.ENGINE_WAIT_STATE && transactionBoundary && transactionBoundary.asyncBefore) {
+ margin = margin + strokeWidth + 5; //the "waiting state" overlay is always added to the left of the "async before" overlay
+ }
+
+ // if orientation is either 'left', 'top-left' or 'bottom-left'
+ if (/left/.test(orientation)) {
+ width = strokeWidth;
+ height = shape.height + 2 * offset;
+
+ // horizontal position: at the left border respecting margin
+ // vertical position: slightly above the diagram element
+ position.left = -width - margin;
+ position.top = -offset;
+
+ console.log("left position: ", beforeType, width, margin)
+
+ // if orientation is either 'right', 'top-right' or 'bottom-right'
+ } else if (/right/.test(orientation)) {
+
+ width = strokeWidth;
+ height = shape.height + 2 * offset;
+
+ // horizontal position: at the right border respecting margin
+ // vertical position: slightly above the diagram element
+ position.right = -margin;
+ position.top = -offset;
+
+ } else if (orientation === 'top') {
+ width = shape.width;
+ height = strokeWidth;
+
+ // horizontal position: slightly right to the diagram element start
+ // vertical position: at the top border respecting margin
+ position.left = -offset;
+ position.top = -offset - margin;
+
+ } else if (orientation === 'bottom') {
+ width = shape.width;
+ height = strokeWidth;
+
+ // horizontal position: slightly right to the diagram element start
+ // vertical position: at the bottom border respecting margin
+ position.bottom = -margin;
+ position.left = -offset;
+ }
+
+ const isHorizontalOverlay = orientation === 'top' || orientation === 'bottom';
+ const className = isHorizontalOverlay ? 'transaction-boundary-horizontal-overlay' :
+ 'transaction-boundary-vertical-overlay';
+
+ const additionalClassName = beforeType === BeforeElementTransactionType.ENGINE_WAIT_STATE ? 'engine-wait-state' : '';
+
+ return {
+ html: ``,
+ position: position
+ }
+};
\ No newline at end of file
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/types.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/types.ts
index 4233972..e9bc05a 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/types.ts
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/overlay/types.ts
@@ -13,6 +13,7 @@ export enum OverlayType {
ACTIVITY_STATISTICS = 'activity-statistics',
INCIDENT_COUNT = 'incident-count',
SEND_MESSAGE = 'send-message',
+ TRANSACTION_BOUNDARY = 'transaction-boundary'
}
export interface IncidentOverlayData {
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/styles/bpmnViewerStyles.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/styles/bpmnViewerStyles.ts
index 97a51ef..9768db3 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/styles/bpmnViewerStyles.ts
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/styles/bpmnViewerStyles.ts
@@ -134,4 +134,20 @@ export const bpmnViewerStyles = css`
border-radius: var(--lumo-border-radius-m);
border: var(--bpmn-group-overlay-border);
}
+
+ .transaction-boundary-vertical-overlay {
+ background: var(--bpmn-async-transaction-overlay-vertical-background);
+ }
+
+ .transaction-boundary-horizontal-overlay {
+ background: var(--bpmn-async-transaction-boundary-overlay-horizontal-background);
+ }
+
+ .transaction-boundary-vertical-overlay.engine-wait-state {
+ background: var(--bpmn-engine-transaction-overlay-vertical-background);
+ }
+
+ .transaction-boundary-horizontal-overlay.engine-wait-state {
+ background: var(--bpmn-engine-transaction-boundary-overlay-horizontal-background);
+ }
`;
\ No newline at end of file
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/types.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/types.ts
index 15bb85b..b189232 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/types.ts
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/types.ts
@@ -39,4 +39,15 @@ export interface ActivityData {
id: string;
name?: string;
type: string;
+}
+
+export interface ElementTransactionBoundary {
+ asyncBefore: boolean;
+ asyncAfter: boolean;
+ engineWaitState: boolean;
+}
+
+export enum BeforeElementTransactionType {
+ ENGINE_WAIT_STATE = 'ENGINE_WAIT_STATE',
+ ASYNC_BEFORE = 'ASYNC_BEFORE'
}
\ No newline at end of file
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/bpmnEventUtils.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/eventDefinitionUtils.ts
similarity index 88%
rename from flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/bpmnEventUtils.ts
rename to flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/eventDefinitionUtils.ts
index 1c905e8..a3f6ae9 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/bpmnEventUtils.ts
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/eventDefinitionUtils.ts
@@ -27,7 +27,7 @@ export const getMessageEventDefinition = (element: ElementLike) => {
return getEventDefinition(element, 'bpmn:MessageEventDefinition');
};
-export const getEventDefinition = (element: ElementLike, eventType: string)=> {
+export const getEventDefinition = (element: ElementLike, eventType: string) => {
const businessObject = getBusinessObject(element);
const eventDefinitions = businessObject.get('eventDefinitions') || [];
@@ -37,6 +37,12 @@ export const getEventDefinition = (element: ElementLike, eventType: string)=> {
});
}
+export const getEventDefinitions = (element: ElementLike) => {
+ const businessObject = getBusinessObject(element);
+
+ return businessObject.get('eventDefinitions') || [];
+}
+
export const getMessage = (element: ElementLike) => {
const messageEventDefinition = getMessageEventDefinition(element);
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/transactionBoundaryUtils.ts b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/transactionBoundaryUtils.ts
new file mode 100644
index 0000000..0141499
--- /dev/null
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/src/bpmn-viewer/utils/transactionBoundaryUtils.ts
@@ -0,0 +1,116 @@
+/*
+ * Copyright (c) Haulmont 2025. All Rights Reserved.
+ * Use is subject to license terms.
+ */
+
+import {ElementTransactionBoundary} from "../types";
+import {ElementLike} from "diagram-js/lib/model/Types";
+import {getBusinessObject, is, isAny} from 'bpmn-js/lib/util/ModelUtil';
+import {getEventDefinitions} from "./eventDefinitionUtils";
+
+/**
+ * Determines the transaction boundary information for a given element.
+ *
+ * This function analyzes a specified element to determine its transaction boundaries
+ * based on its asynchronous continuations properties and default engine wait states.
+ *
+ * @param {ElementLike} element - a diagram element.
+ * @returns {ElementTransactionBoundary | undefined} an object containing transaction boundary details or undefined if element does not have transaction boundary
+ */
+export const getElementTransactionBoundary = (element: ElementLike): ElementTransactionBoundary | undefined => {
+ const businessObject = getBusinessObject(element);
+ const loopCharacteristics = businessObject.loopCharacteristics;
+
+ const waitStateTask = isWaitStateTask(element);
+ const waitStateGateway = isWaitStateGateway(element);
+ const waitStateEvent = isWaitStateEvent(element);
+
+ const asyncAfter = isAsyncAfter(businessObject) || (loopCharacteristics && isAsyncAfter(loopCharacteristics));
+ const asyncBefore = isAsyncBefore(businessObject) || (loopCharacteristics && isAsyncBefore(loopCharacteristics));
+
+ const isWaitState = waitStateTask || waitStateEvent || waitStateGateway;
+
+ if (isWaitState || asyncBefore || asyncAfter) {
+ return {
+ asyncBefore,
+ asyncAfter,
+ engineWaitState: isWaitState
+ };
+ }
+
+ return undefined;
+}
+
+const isAsyncAfter = bo => {
+ const camundaAsyncAfter = !!bo.get('camunda:asyncAfter');
+ if (camundaAsyncAfter) {
+ return true;
+ }
+ return !!bo.get('operaton:asyncAfter');
+};
+
+const isAsyncBefore = bo => {
+ const camundaAsyncBefore = !!(bo.get('camunda:asyncBefore') || bo.get('camunda:async'));
+ if (camundaAsyncBefore) {
+ return true;
+ }
+
+ return !!(bo.get('operaton:asyncBefore') || bo.get('operaton:async'));
+};
+
+
+const isWaitStateEvent = (element: ElementLike) => {
+ const eventDefinitions = getEventDefinitions(element);
+ if (!eventDefinitions || eventDefinitions.length === 0) {
+ return false;
+ }
+ const eventDefinition = eventDefinitions[0];
+ const eventDefinitionType = eventDefinition.$type;
+
+ const isCatchEvent = is(element, 'bpmn:IntermediateCatchEvent');
+
+ const isMessageEvent = eventDefinitionType === 'bpmn:MessageEventDefinition';
+ const isTimerEvent = eventDefinitionType === 'bpmn:TimerEventDefinition';
+ const isSignalEvent = eventDefinitionType === 'bpmn:SignalEventDefinition';
+ const isConditionalEvent = eventDefinitionType === 'bpmn:ConditionalEventDefinition';
+
+ if (isCatchEvent && (isMessageEvent || isTimerEvent || isSignalEvent || isConditionalEvent)) {
+ return true;
+ }
+
+ const isThrowEvent = is(element, 'bpmn:IntermediateThrowEvent');
+ const isEndEvent = is(element, 'bpmn:IntermediateEndEvent');
+
+ if (isMessageEvent && (isThrowEvent || isEndEvent)) {
+ const businessObject = getBusinessObject(eventDefinition);
+
+ return hasExternalImplementation(businessObject);
+ }
+
+ return false;
+}
+
+const isWaitStateTask = (element) => {
+ if (isAny(element, ['bpmn:ReceiveTask', 'bpmn:UserTask'])) {
+ return true;
+ }
+ if (isAny(element, ['bpmn:ServiceTask', 'bpmn:SendTask', 'bpmn:BusinessRuleTask'])) {
+ const businessObject = getBusinessObject(element);
+
+ return hasExternalImplementation(businessObject);
+ }
+
+ return false;
+}
+
+const hasExternalImplementation = businessObject => {
+ const camundaTaskType = businessObject.get('camunda:type');
+ const operatonTaskType = businessObject.get('operaton:type');
+
+ return camundaTaskType === 'external' || operatonTaskType === 'external';
+}
+
+const isWaitStateGateway = (element) => {
+ return is(element, 'bpmn:EventBasedGateway');
+}
+
diff --git a/flowset-ui-kit/src/main/resources/META-INF/frontend/styles/bpmn-viewer.css b/flowset-ui-kit/src/main/resources/META-INF/frontend/styles/bpmn-viewer.css
index 8765b77..623a108 100644
--- a/flowset-ui-kit/src/main/resources/META-INF/frontend/styles/bpmn-viewer.css
+++ b/flowset-ui-kit/src/main/resources/META-INF/frontend/styles/bpmn-viewer.css
@@ -28,4 +28,24 @@
--default-bpmn-element-overlay-size: 1.2em;
--default-bpmn-element-overlay-font-size: var(--lumo-font-size-xxs);
+
+ --bpmn-async-transaction-overlay-vertical-background: repeating-linear-gradient(to bottom,
+ var(--bpmn-async-transaction-boundary-overlay-color) 0px,
+ var(--bpmn-async-transaction-boundary-overlay-color) 15px,
+ transparent 10px, transparent 20px);
+
+ --bpmn-engine-transaction-overlay-vertical-background: repeating-linear-gradient(to bottom,
+ var(--bpmn-engine-transaction-boundary-overlay-color) 0px,
+ var(--bpmn-engine-transaction-boundary-overlay-color) 15px,
+ transparent 10px, transparent 20px);
+
+ --bpmn-async-transaction-boundary-overlay-horizontal-background: repeating-linear-gradient(to right,
+ var(--bpmn-async-transaction-boundary-overlay-color) 0px,
+ var(--bpmn-async-transaction-boundary-overlay-color) 15px,
+ transparent 10px, transparent 20px);
+
+ --bpmn-engine-transaction-boundary-overlay-horizontal-background: repeating-linear-gradient(to right,
+ var(--bpmn-engine-transaction-boundary-overlay-color) 0px,
+ var(--bpmn-engine-transaction-boundary-overlay-color) 15px,
+ transparent 10px, transparent 20px);
}
\ No newline at end of file
diff --git a/flowset-ui-kit/src/main/resources/io/flowset/uikit/fragment/bpmnviewer/bpmn-viewer-fragment.xml b/flowset-ui-kit/src/main/resources/io/flowset/uikit/fragment/bpmnviewer/bpmn-viewer-fragment.xml
index 5c26c27..dcf558f 100644
--- a/flowset-ui-kit/src/main/resources/io/flowset/uikit/fragment/bpmnviewer/bpmn-viewer-fragment.xml
+++ b/flowset-ui-kit/src/main/resources/io/flowset/uikit/fragment/bpmnviewer/bpmn-viewer-fragment.xml
@@ -8,7 +8,7 @@