diff --git a/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js b/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js index 9b169b2e..086cea4d 100644 --- a/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js +++ b/components/bpmn-q/modeler-component/QuantumWorkflowModeler.js @@ -8,6 +8,7 @@ import "./editor/ui/notifications/Notification.css"; import "./editor/resources/styling/camunda-styles/style.css"; import "bpmn-js-bpmnlint/dist/assets/css/bpmn-js-bpmnlint.css"; import "./modeler.css"; +import "./editor/resources/styling/bpmn-js-token-simulation.css"; import React from "react"; import { createRoot } from "react-dom/client"; diff --git a/components/bpmn-q/modeler-component/editor/ModelerHandler.js b/components/bpmn-q/modeler-component/editor/ModelerHandler.js index 73ff3914..56f21ff1 100644 --- a/components/bpmn-q/modeler-component/editor/ModelerHandler.js +++ b/components/bpmn-q/modeler-component/editor/ModelerHandler.js @@ -11,9 +11,10 @@ import { getAdditionalModules, getModdleExtension, } from "./plugin/PluginHandler"; -import ModelerRulesModule from "./rules/"; +import ModelingModule from "./modeling/"; import LintModule from "bpmn-js-bpmnlint"; import bpmnlintConfig from "../../.bpmnlintrc"; +import TokenSimulationModule from "bpmn-js-token-simulation"; import Clipboard from "diagram-js/lib/features/clipboard/Clipboard"; let camundaModdleDescriptor = require("camunda-bpmn-moddle/resources/camunda.json"); @@ -141,7 +142,8 @@ function getModules() { CustomPopupMenuModule, LintModule, clipboardModule, - ModelerRulesModule, + TokenSimulationModule, + ModelingModule, ].concat(pluginModules); console.log("\n Additional modules of the modeler: "); diff --git a/components/bpmn-q/modeler-component/editor/modeling/ModelerRules.js b/components/bpmn-q/modeler-component/editor/modeling/ModelerRules.js new file mode 100644 index 00000000..2cae4282 --- /dev/null +++ b/components/bpmn-q/modeler-component/editor/modeling/ModelerRules.js @@ -0,0 +1,19 @@ +import BpmnRules from "bpmn-js/lib/features/rules/BpmnRules"; + +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +export default class ModelerRules extends BpmnRules { + constructor(eventBus) { + super(eventBus); + } +} + +ModelerRules.$inject = ["eventBus"]; diff --git a/components/bpmn-q/modeler-component/editor/modeling/NeutralElementColors.js b/components/bpmn-q/modeler-component/editor/modeling/NeutralElementColors.js new file mode 100644 index 00000000..61a7c4e4 --- /dev/null +++ b/components/bpmn-q/modeler-component/editor/modeling/NeutralElementColors.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) 2023 Institute of Architecture of Application Systems - + * University of Stuttgart + * + * This program and the accompanying materials are made available under the + * terms the Apache Software License 2.0 + * which is available at https://www.apache.org/licenses/LICENSE-2.0. + * + * SPDX-License-Identifier: Apache-2.0 + */ +export default function NeutralElementColors( + eventBus, + elementRegistry, + elementColors +) { + this._elementRegistry = elementRegistry; + this._elementColors = elementColors; + + // overwrite the default white rendering of the token simulation module + eventBus.on("tokenSimulation.toggleMode", () => {}); +} + +NeutralElementColors.$inject = ["eventBus", "elementRegistry", "elementColors"]; diff --git a/components/bpmn-q/modeler-component/editor/modeling/index.js b/components/bpmn-q/modeler-component/editor/modeling/index.js new file mode 100644 index 00000000..ca0b8a58 --- /dev/null +++ b/components/bpmn-q/modeler-component/editor/modeling/index.js @@ -0,0 +1,8 @@ +import NeutralElementColors from "./NeutralElementColors"; +import ModelerRules from "./ModelerRules"; + +export default { + __init__: ["neutralElementColors", "modelerRules"], + neutralElementColors: ["type", NeutralElementColors], + modelerRules: ["type", ModelerRules], +}; diff --git a/components/bpmn-q/modeler-component/editor/resources/styling/bpmn-js-token-simulation.css b/components/bpmn-q/modeler-component/editor/resources/styling/bpmn-js-token-simulation.css new file mode 100644 index 00000000..09fc45b5 --- /dev/null +++ b/components/bpmn-q/modeler-component/editor/resources/styling/bpmn-js-token-simulation.css @@ -0,0 +1,508 @@ +:root { + --token-simulation-green-base-44: #10d070; + --token-simulation-grey-base-40: #666666; + --token-simulation-grey-darken-30: #212121; + --token-simulation-grey-lighten-56: #909090; + --token-simulation-red-base-62: #ff3d3d; + --token-simulation-silver-base-97: #f8f8f8; + --token-simulation-silver-darken-94: #efefef; + --token-simulation-blue-base-44: hsl(205, 100%, 45%); + --token-simulation-white: #ffffff; +} + +.bjs-container { + border: solid 4px transparent; + box-sizing: border-box; +} + +.bjs-breadcrumbs { + /* ensure breadcrumbs don't overlap with token-simulation controls */ + top: 60px; + left: 50px; +} + +.bjs-container.simulation { + border-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bjs-container.simulation.paused { + border-color: var(--token-simulation-grey-base-40, #666666); +} + +.bjs-container.simulation.warning { + border-color: var(--token-simulation-red-base-62, #ff3d3d) !important; +} + +.bts-context-pad { + cursor: pointer; + background-color: var(--token-simulation-grey-lighten-56, #909090); + border-radius: 2px; + margin: 0; + font-size: 16px; + color: var(--token-simulation-grey-darken-30, #212121); + opacity: 0.5; + transition: all 0.1s ease-in-out; + width: 30px; + height: 30px; + display: flex; + justify-content: center; + align-items: center; + box-sizing: border-box; +} + +.bts-context-pad:not(.disabled):hover { + background-color: var(--token-simulation-blue-base-44, #10d070); + opacity: 1; +} + +.djs-overlays:not(.hover) .bts-context-pad:not(:hover).show-hover, +.bts-context-pad:not(:hover) .show-hover, +.bts-context-pad:hover .hide-hover { + display: none; +} + +.bts-context-pad.disabled { + background-color: var(--token-simulation-silver-darken-94, #efefef); + color: var(--token-simulation-grey-base-40, #666666); + pointer-events: none; +} + +.bts-context-pad.hidden { + display: none; +} + +.bts-context-pad [class^="bpmn-icon-"]:before, +.bts-context-pad [class*=" bpmn-icon-"]:before { + margin: 0; +} + +.bts-token .text { + font-family: "Arial", sans-serif; +} + +.bts-token-count-parent { + white-space: nowrap; +} + +.bts-token-count { + border-radius: 100%; + width: 25px; + height: 25px; + line-height: 25px; + text-align: center; + font-size: 14px; + color: var(--token-simulation-grey-darken-30, #212121); + user-select: none; + animation: bts-jump 1s infinite; + animation-timing-function: ease; + position: relative; + top: 0; + display: inline-block; +} + +.bts-token-count.inactive { + display: none; +} + +.bts-token-count + .bts-token-count { + margin-left: -8px; +} + +.bts-token-count.waiting { + font-family: "Arial", sans-serif; + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +@keyframes bts-jump { + 50% { + top: 5px; + } +} + +.bts-notifications { + position: absolute; + bottom: 20px; + left: 20px; +} + +.bts-notifications .bts-notification { + background-color: var(--token-simulation-silver-darken-94, #efefef); + border-radius: 2px; + padding: 5px 8px; + font-size: 16px; + color: var(--token-simulation-silver-darken-94, #efefef); + user-select: none; + margin-top: 4px; + min-width: 300px; + max-width: 400px; + display: flex; + justify-content: center; + align-items: stretch; +} + +.bts-notifications .bts-notification.info { + background-color: var(--token-simulation-silver-darken-94, #efefef); + color: #000; +} + +.bts-notifications .bts-notification.success { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bts-notifications .bts-notification.warning { + background-color: var(--token-simulation-red-base-62, #ff3d3d); +} + +.bts-notifications .bts-notification > * { + flex: initial; +} + +.bts-notifications .bts-notification > :not(:last-child) { + margin-right: 6px; +} + +.bts-notifications .bts-notification > .bts-icon { + min-width: 20px; + text-align: center; +} + +.bts-notifications .bts-notification > .bts-text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bts-notifications .bts-notification > .bts-scope { + font-family: monospace; + font-size: 0.8em; + padding: 2px 3px; + border-radius: 3px; + cursor: default; +} + +.bts-notifications .bts-notification > .bts-icon [class^="bpmn-icon-"]:before, +.bts-notifications .bts-notification > .bts-icon [class*=" bpmn-icon-"]:before { + margin: 0; +} + +.bjs-container.paused .bts-play-pause.active { + color: var(--token-simulation-silver-darken-94, #efefef); + background-color: var(--token-simulation-silver-darken-94, #efefef); +} + +.bts-element-notification { + background-color: var(--token-simulation-silver-darken-94, #efefef); + color: var(--token-simulation-silver-darken-94, #efefef); + border-radius: 2px; + height: 30px; + box-sizing: border-box; + display: flex; + justify-content: center; + align-items: center; + padding: 0 6px 0 6px; + user-select: none; +} + +.bts-element-notification .bts-text { + margin: 0 3px 0 3px; +} + +.bts-element-notification .bts-text { + white-space: nowrap; +} + +.bts-element-notification.success { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bts-element-notification.warning { + background-color: var(--token-simulation-red-base-62, #ff3d3d); +} + +.bts-toggle-mode { + cursor: pointer; + position: absolute; + font-family: Arial, serif; + top: 20px; + left: 20px; + background-color: var(--token-simulation-silver-darken-94, #efefef); + border-radius: 2px; + padding: 6px; + font-size: 16px; + color: var(--token-simulation-grey-darken-30, #212121); + user-select: none; + display: inline-flex; +} + +.bts-toggle-mode .bts-toggle { + margin-left: 0.25em; + display: inline-flex; +} + +.bjs-container.simulation .bts-toggle-mode, +.bts-toggle-mode:hover { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bjs-container .djs-palette { + top: 60px; +} + +.bjs-container.simulation .djs-palette { + display: none; +} + +.bjs-container.simulation .djs-outline, +.bjs-container.simulation .djs-bendpoint, +.bjs-container.simulation .djs-segment-dragger, +.bjs-container.simulation .djs-resizer { + display: none !important; +} + +.bts-palette { + position: absolute; + top: 60px; + left: 20px; +} + +.bts-palette.hidden { + display: none; +} + +.bts-palette .bts-entry { + cursor: pointer; + background-color: var(--token-simulation-silver-darken-94, #efefef); + border-radius: 2px; + padding: 6px 0; + margin-bottom: 6px; + font-size: 16px; + color: var(--token-simulation-grey-darken-30, #212121); + user-select: none; + display: flex; + justify-content: center; + align-items: center; + width: 30px; + box-sizing: border-box; + transition: all 0.1s ease; +} + +.bts-palette .bts-entry:last-child { + margin-bottom: 0; +} + +.bts-palette .bts-entry:not(.disabled):hover { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bts-palette .bts-entry.active { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bts-palette .bts-entry.disabled { + pointer-events: none; + color: var(--token-simulation-grey-base-40, #666666); +} + +.bts-log { + position: absolute; + top: 30%; + right: 20px; + bottom: 50px; + width: 300px; + background-color: var(--token-simulation-silver-darken-94, #efefef); + border-radius: 2px; + z-index: 10000; + overflow: hidden; + display: flex; + flex-direction: column; +} + +.bts-log.hidden { + display: none; +} + +.bts-log .bts-header { + background-color: var(--token-simulation-blue-base-44, #10d070); + padding: 6px 8px; + height: 30px; + box-sizing: border-box; + font-size: 16px; + flex: 0; + display: flex; + justify-content: space-between; +} + +.bts-log .bts-close { + background: none; + border: none; + cursor: pointer; +} + +.bts-log .bts-log-icon { + cursor: pointer; +} + +.bts-log .bts-content { + overflow-y: auto; + box-sizing: border-box; + flex: 1; + margin: 7px 3px 7px 12px; + padding: 5px 9px 5px 0; +} + +.bts-log *::-webkit-scrollbar { + width: 6px; +} + +.bts-log *::-webkit-scrollbar-thumb { + border-radius: 3px; + background-color: rgba(0, 0, 0, 0.2); +} + +.bts-log *::-webkit-scrollbar-track { + box-shadow: none; + background: transparent; + margin: 0; + padding: 5px; +} + +.bts-log .bts-entry { + font-size: 16px; + margin: 0 0 6px 0; + padding: 6px; + border-radius: 2px; + display: flex; + align-items: stretch; + justify-content: center; +} + +.bts-log .bts-entry.inactive { + opacity: 0.5; +} + +.bts-log .bts-entry.success { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bts-log .bts-entry.warning { + background-color: var(--token-simulation-red-base-62, #ff3d3d); +} + +.bts-log .bts-entry:last-child { + margin: 0; +} + +.bts-log .bts-entry > * { + flex: initial; +} + +.bts-log .bts-entry > :not(:last-child) { + margin-right: 6px; +} + +.bts-log .bts-entry > .bts-icon { + min-width: 20px; + text-align: center; +} + +.bts-log .bts-entry > .bts-scope { + font-family: monospace; + font-size: 0.8em; + padding: 2px 3px; + border-radius: 3px; + cursor: default; +} + +.bts-log .bts-entry > .bts-text { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.bts-log .bts-entry.placeholder.hidden { + display: none; +} + +.bts-scopes { + position: absolute; + top: 22px; + left: 190px; + display: flex; + flex-direction: row; +} + +.bts-scopes.hidden { + display: none; +} + +.bts-scopes .bts-scope { + border-radius: 100%; + width: 25px; + height: 25px; + font-size: 14px; + line-height: 25px; + text-align: center; + background-color: var(--token-simulation-silver-darken-94, #efefef); + margin-right: 10px; + cursor: pointer; +} + +.bts-scopes .bts-scope.inactive:hover { + opacity: 1; +} + +.bts-scopes .bts-scope.inactive { + opacity: 0.25; +} + +.bts-set-animation-speed { + position: absolute; + bottom: 20px; + left: 75%; + transform: translate(-50%, 0); + font-size: 16px; + text-align: center; + background-color: var(--token-simulation-silver-darken-94, #efefef); + border-radius: 2px; + display: flex; + flex-direction: row; + align-items: center; + padding-left: 6px; + overflow: hidden; +} + +.bts-set-animation-speed.hidden { + display: none; +} + +.bts-set-animation-speed .bts-animation-speed-buttons { + display: flex; + flex-direction: row; + + margin-left: 6px; +} + +.bts-set-animation-speed .bts-animation-speed-button { + padding: 10px 0; + width: 30px; + display: inline-flex; + justify-content: center; + border: none; +} + +.bts-set-animation-speed .bts-animation-speed-button.active, +.bts-set-animation-speed .bts-animation-speed-button:hover { + background-color: var(--token-simulation-blue-base-44, #10d070); +} + +.bts-icon { + display: inline-flex; + align-items: center; + justify-content: center; +} + +.bts-icon > svg { + height: 1em; +} diff --git a/components/bpmn-q/modeler-component/editor/resources/styling/camunda-styles/_modal.css b/components/bpmn-q/modeler-component/editor/resources/styling/camunda-styles/_modal.css index e2553272..db1f1d01 100644 --- a/components/bpmn-q/modeler-component/editor/resources/styling/camunda-styles/_modal.css +++ b/components/bpmn-q/modeler-component/editor/resources/styling/camunda-styles/_modal.css @@ -97,6 +97,7 @@ body, font-size: 16px; font-weight: bold; } + .qwm-modal-body { padding: 16px 20px 20px 20px; font-size: 14px; diff --git a/components/bpmn-q/modeler-component/editor/rules/ModelerRules.js b/components/bpmn-q/modeler-component/editor/rules/ModelerRules.js deleted file mode 100644 index c2b1c40b..00000000 --- a/components/bpmn-q/modeler-component/editor/rules/ModelerRules.js +++ /dev/null @@ -1,17 +0,0 @@ -import BpmnRules from "bpmn-js/lib/features/rules/BpmnRules"; -import { getModeler } from "../ModelerHandler"; -import { saveModelerAsLocalFile } from "../util/IoUtilities"; - -/** - * Contains the rules for the modeler. - */ -export default class ModelerRules extends BpmnRules { - constructor(eventBus) { - super(eventBus); - eventBus.on("saveFile", function () { - saveModelerAsLocalFile(getModeler()); - }); - } -} - -ModelerRules.$inject = ["eventBus"]; diff --git a/components/bpmn-q/modeler-component/editor/rules/index.js b/components/bpmn-q/modeler-component/editor/rules/index.js deleted file mode 100644 index 7fe33779..00000000 --- a/components/bpmn-q/modeler-component/editor/rules/index.js +++ /dev/null @@ -1,6 +0,0 @@ -import ModelerRules from "./ModelerRules"; - -export default { - __init__: ["modelerRules"], - modelerRules: ["type", ModelerRules], -}; diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEActivityBehavior.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEActivityBehavior.js new file mode 100644 index 00000000..46c7e141 --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMEActivityBehavior.js @@ -0,0 +1,217 @@ +import { + isEventSubProcess, + isMessageFlow, + isSequenceFlow, +} from "bpmn-js-token-simulation/lib/simulator/util/ModelUtil"; + +import * as quantMEConstants from "../Constants"; + +export default function QuantMEActivityBehavior( + simulator, + scopeBehavior, + transactionBehavior +) { + this._simulator = simulator; + this._scopeBehavior = scopeBehavior; + this._transactionBehavior = transactionBehavior; + + for (const element of quantMEConstants.QUANTME_TASKS) { + simulator.registerBehavior(element, this); + } +} + +QuantMEActivityBehavior.$inject = [ + "simulator", + "scopeBehavior", + "transactionBehavior", +]; + +QuantMEActivityBehavior.prototype.signal = function (context) { + // trigger messages that are pending send + const event = this._triggerMessages(context); + + if (event) { + return this.signalOnEvent(context, event); + } + + this._simulator.exit(context); +}; + +QuantMEActivityBehavior.prototype.enter = function (context) { + const { element } = context; + + const continueEvent = this.waitAtElement(element); + + if (continueEvent) { + return this.signalOnEvent(context, continueEvent); + } + + // trigger messages that are pending send + const event = this._triggerMessages(context); + + if (event) { + return this.signalOnEvent(context, event); + } + + this._simulator.exit(context); +}; + +QuantMEActivityBehavior.prototype.exit = function (context) { + const { element, scope } = context; + + const parentScope = scope.parent; + + const complete = !scope.failed; + + if (complete && !isEventSubProcess(element)) { + this._transactionBehavior.registerCompensation(scope); + } + + // if exception flow is active, + // do not activate any outgoing flows + const activatedFlows = complete + ? element.outgoing.filter(isSequenceFlow) + : []; + + activatedFlows.forEach((element) => + this._simulator.enter({ + element, + scope: parentScope, + }) + ); + + // element has token sink semantics + if (activatedFlows.length === 0) { + this._scopeBehavior.tryExit(parentScope, scope); + } +}; + +QuantMEActivityBehavior.prototype.signalOnEvent = function (context, event) { + const { scope, element } = context; + + const subscription = this._simulator.subscribe(scope, event, (initiator) => { + subscription.remove(); + + return this._simulator.signal({ + scope, + element, + initiator, + }); + }); +}; + +/** + * Returns an event to subscribe to if wait on element is configured. + * + * @param {Element} element + * + * @return {Object|null} event + */ +QuantMEActivityBehavior.prototype.waitAtElement = function (element) { + const wait = this._simulator.getConfig(element).wait; + + return ( + wait && { + element, + type: "continue", + interrupting: false, + boundary: false, + } + ); +}; + +QuantMEActivityBehavior.prototype._getMessageContexts = function ( + element, + after = null +) { + const filterAfter = after + ? (ctx) => ctx.referencePoint.x > after.x + : () => true; + const sortByReference = (a, b) => a.referencePoint.x - b.referencePoint.x; + + return [ + ...element.incoming.filter(isMessageFlow).map((flow) => ({ + incoming: flow, + referencePoint: last(flow.waypoints), + })), + ...element.outgoing.filter(isMessageFlow).map((flow) => ({ + outgoing: flow, + referencePoint: first(flow.waypoints), + })), + ] + .sort(sortByReference) + .filter(filterAfter); +}; + +/** + * @param {any} context + * + * @return {Object} event to subscribe to proceed + */ +QuantMEActivityBehavior.prototype._triggerMessages = function (context) { + // check for the next message flows to either + // trigger or wait for; this implements intuitive, + // as-you-would expect message flow execution in modeling + // direction (left-to-right). + + const { element, initiator, scope } = context; + + let messageContexts = scope.messageContexts; + + if (!messageContexts) { + messageContexts = scope.messageContexts = this._getMessageContexts(element); + } + + const initiatingFlow = initiator && initiator.element; + + if (isMessageFlow(initiatingFlow)) { + // ignore out of bounds messages received; + // user may manually advance and force send all outgoing + // messages + if (scope.expectedIncoming !== initiatingFlow) { + console.debug( + "Simulator :: QuantMEActivityBehavior :: ignoring out-of-bounds message" + ); + + return; + } + } + + while (messageContexts.length) { + const { incoming, outgoing } = messageContexts.shift(); + + if (incoming) { + // force sending of all remaining messages, + // as the user triggered the task manually (for demonstration + // purposes + if (!initiator) { + continue; + } + + // remember expected incoming for future use + scope.expectedIncoming = incoming; + + return { + element, + type: "message", + name: incoming.id, + interrupting: false, + boundary: false, + }; + } + + this._simulator.signal({ + element: outgoing, + }); + } +}; + +// helpers ////////////////// + +function first(arr) { + return arr && arr[0]; +} + +function last(arr) { + return arr && arr[arr.length - 1]; +} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMESubProcessBehavior.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMESubProcessBehavior.js new file mode 100644 index 00000000..861a872c --- /dev/null +++ b/components/bpmn-q/modeler-component/extensions/quantme/modeling/QuantMESubProcessBehavior.js @@ -0,0 +1,139 @@ +import { + is, + isAny, + isCompensationActivity, + isEventSubProcess, + isInterrupting, + isLabel, +} from "bpmn-js-token-simulation/lib/simulator/util/ModelUtil"; + +import * as quantMEConstants from "../Constants"; + +export default function QuantMESubProcessBehavior( + simulator, + activityBehavior, + scopeBehavior, + transactionBehavior, + elementRegistry +) { + this._simulator = simulator; + this._activityBehavior = activityBehavior; + this._scopeBehavior = scopeBehavior; + this._transactionBehavior = transactionBehavior; + this._elementRegistry = elementRegistry; + + simulator.registerBehavior( + quantMEConstants.QUANTUM_HARDWARE_SELECTION_SUBPROCESS, + this + ); + simulator.registerBehavior(quantMEConstants.CIRCUIT_CUTTING_SUBPROCESS, this); +} + +QuantMESubProcessBehavior.$inject = [ + "simulator", + "activityBehavior", + "scopeBehavior", + "transactionBehavior", + "elementRegistry", +]; + +QuantMESubProcessBehavior.prototype.signal = function (context) { + this._start(context); +}; + +QuantMESubProcessBehavior.prototype.enter = function (context) { + const { element } = context; + + const continueEvent = this._activityBehavior.waitAtElement(element); + + if (continueEvent) { + return this._activityBehavior.signalOnEvent(context, continueEvent); + } + + this._start(context); +}; + +QuantMESubProcessBehavior.prototype.exit = function (context) { + const { scope } = context; + + const parentScope = scope.parent; + + // successful completion of the fail initiator (event sub-process) + // recovers the parent, so that the normal flow is being executed + if (parentScope.failInitiator === scope) { + parentScope.complete(); + } + + this._activityBehavior.exit(context); +}; + +QuantMESubProcessBehavior.prototype._start = function (context) { + const { element, startEvent, scope } = context; + + const targetScope = scope.parent; + + if (isEventSubProcess(element)) { + if (!startEvent) { + throw new Error("missing : required for event sub-process"); + } + } else { + if (startEvent) { + throw new Error("unexpected : not allowed for sub-process"); + } + } + + if (targetScope.destroyed) { + throw new Error(`target scope <${targetScope.id}> destroyed`); + } + + if (isTransaction(element)) { + this._transactionBehavior.setup(context); + } + + if (startEvent && isInterrupting(startEvent)) { + this._scopeBehavior.interrupt(targetScope, scope); + } + + const startEvents = startEvent ? [startEvent] : this._findStarts(element); + + for (const element of startEvents) { + this._simulator.signal({ + element, + parentScope: scope, + initiator: scope, + }); + } +}; + +QuantMESubProcessBehavior.prototype._findStarts = function (element) { + // ensure bpmn-js@9 compatibility + // + // sub-process may be collapsed, in this case operate on the plane + element = this._elementRegistry.get(element.id + "_plane") || element; + + return element.children.filter((child) => { + if (isLabel(child)) { + return false; + } + + const incoming = child.incoming.find((c) => is(c, "bpmn:SequenceFlow")); + + if (incoming) { + return false; + } + + if (isCompensationActivity(child)) { + return false; + } + + if (isEventSubProcess(child)) { + return false; + } + + return isAny(child, ["bpmn:Activity", "bpmn:StartEvent", "bpmn:EndEvent"]); + }); +}; + +function isTransaction(element) { + return is(element, "bpmn:Transaction"); +} diff --git a/components/bpmn-q/modeler-component/extensions/quantme/modeling/index.js b/components/bpmn-q/modeler-component/extensions/quantme/modeling/index.js index f96f9db0..c99f58c7 100644 --- a/components/bpmn-q/modeler-component/extensions/quantme/modeling/index.js +++ b/components/bpmn-q/modeler-component/extensions/quantme/modeling/index.js @@ -16,6 +16,8 @@ import QuantMEPropertiesProvider from "./properties-provider/QuantMEPropertiesPr import BpmnKeyboardBinding from "./BpmnKeyboardBindings"; import BpmnEditorActions from "./BpmnEditorActions"; import BpmnKeyboard from "./BpmnKeyboard"; +import QuantMEActivityBehavior from "./QuantMEActivityBehavior"; +import QuantMESubProcessBehavior from "./QuantMESubProcessBehavior"; import QuantMERules from "./QuantMERules"; export default { @@ -28,6 +30,8 @@ export default { "keyboardBindings", "editorActions", "keyboard", + "quantMEActivityBehavior", + "quantMESubProcessBehavior", "quantMERules", ], quantMERenderer: ["type", QuantMERenderer], @@ -38,5 +42,7 @@ export default { keyboardBindings: ["type", BpmnKeyboardBinding], editorActions: ["type", BpmnEditorActions], keyboard: ["type", BpmnKeyboard], + quantMEActivityBehavior: ["type", QuantMEActivityBehavior], + quantMESubProcessBehavior: ["type", QuantMESubProcessBehavior], quantMERules: ["type", QuantMERules], }; diff --git a/components/bpmn-q/package-lock.json b/components/bpmn-q/package-lock.json index 20f7bfd9..1cb509cb 100644 --- a/components/bpmn-q/package-lock.json +++ b/components/bpmn-q/package-lock.json @@ -16,6 +16,7 @@ "bpmn-js": "^11.1.1", "bpmn-js-cli": "^2.3.0", "bpmn-js-properties-panel": "^1.17.2", + "bpmn-js-token-simulation": "^0.31.1", "camunda-bpmn-moddle": "^7.0.1", "classnames": "^2.3.2", "clsx": "^1.2.1", @@ -4061,6 +4062,17 @@ "diagram-js": ">= 7" } }, + "node_modules/bpmn-js-token-simulation": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.31.1.tgz", + "integrity": "sha512-UdSJPW6hAHrznDCYAm/G+bGkR0ZSlHTdMKETpmfxZBnKb99xWLz2/SqvzhN7Hlxr489sxs1VtmOX++U1PDNJPw==", + "dependencies": { + "inherits-browser": "^0.1.0", + "min-dash": "^4.0.0", + "min-dom": "^4.0.3", + "randomcolor": "^0.6.2" + } + }, "node_modules/bpmn-moddle": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/bpmn-moddle/-/bpmn-moddle-8.0.1.tgz", @@ -12768,6 +12780,11 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/randomcolor": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz", + "integrity": "sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A==" + }, "node_modules/randomfill": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", @@ -19069,6 +19086,17 @@ "semver-compare": "^1.0.0" } }, + "bpmn-js-token-simulation": { + "version": "0.31.1", + "resolved": "https://registry.npmjs.org/bpmn-js-token-simulation/-/bpmn-js-token-simulation-0.31.1.tgz", + "integrity": "sha512-UdSJPW6hAHrznDCYAm/G+bGkR0ZSlHTdMKETpmfxZBnKb99xWLz2/SqvzhN7Hlxr489sxs1VtmOX++U1PDNJPw==", + "requires": { + "inherits-browser": "^0.1.0", + "min-dash": "^4.0.0", + "min-dom": "^4.0.3", + "randomcolor": "^0.6.2" + } + }, "bpmn-moddle": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/bpmn-moddle/-/bpmn-moddle-8.0.1.tgz", @@ -26116,6 +26144,11 @@ "safe-buffer": "^5.1.0" } }, + "randomcolor": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/randomcolor/-/randomcolor-0.6.2.tgz", + "integrity": "sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A==" + }, "randomfill": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", diff --git a/components/bpmn-q/package.json b/components/bpmn-q/package.json index 2be336fa..ee9a5dbf 100644 --- a/components/bpmn-q/package.json +++ b/components/bpmn-q/package.json @@ -32,6 +32,7 @@ "bpmn-js": "^11.1.1", "bpmn-js-cli": "^2.3.0", "bpmn-js-properties-panel": "^1.17.2", + "bpmn-js-token-simulation": "^0.31.1", "camunda-bpmn-moddle": "^7.0.1", "classnames": "^2.3.2", "clsx": "^1.2.1",