From d9db136e03468086913b9e39309062981b14815e Mon Sep 17 00:00:00 2001 From: hofstef Date: Tue, 28 Jan 2025 14:36:36 +0100 Subject: [PATCH 1/4] more documentation on diagram-js --- architecture.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/architecture.md b/architecture.md index 3012a2b1..d056f06e 100644 --- a/architecture.md +++ b/architecture.md @@ -316,7 +316,18 @@ Unfortunately, diagram-js comes with little documentation. We recommend reading Some more useful information for working on the `modeler` tool: -The *canvas* contains graphical *elements* of different *types* (*shape*, *label*, *connection*, possibly more). The graphical elements can contain *business objects* that carry the information that is specific to the modeling language. Business object have types too – e.g., `domainStory:workObject` (for the Domain Storytelling modeling language). Domain Storytelling's element types for business objects are defined in `elementTypes.ts`. +The *canvas* contains graphical *elements* of different *types* (of which Egon only uses the ones mentioned in the table below). The graphical elements can contain *business objects* that carry the information that is specific to the modeling language. Business object have types too – e.g., `domainStory:workObject` (for the Domain Storytelling modeling language). Domain Storytelling's element types for business objects are defined in `elementTypes.ts`. + +| Domain Story Element Types | represented as diagram-js type | +|----------------------------|--------------------------------| +| Actor | shape | +| Work Object | shape | +| Group | shape | +| Textannotation | shape | +| Activity | connection | +| Connection* | connection | + +*) This refers to the dashed line that connects annotations with actors, work objects, or groups. Element positions on the canvas work as shown in this diagram: From d370f2949091c28d220ab9004dfea9c7b8dde279 Mon Sep 17 00:00:00 2001 From: hofstef Date: Tue, 28 Jan 2025 14:37:12 +0100 Subject: [PATCH 2/4] cleanup remaining bpmn leftovers --- .../features/domainStoryElementFactory.js | 108 ++++++++---------- 1 file changed, 47 insertions(+), 61 deletions(-) diff --git a/src/app/tools/modeler/diagram-js/features/domainStoryElementFactory.js b/src/app/tools/modeler/diagram-js/features/domainStoryElementFactory.js index 501e2468..9baf50e5 100644 --- a/src/app/tools/modeler/diagram-js/features/domainStoryElementFactory.js +++ b/src/app/tools/modeler/diagram-js/features/domainStoryElementFactory.js @@ -9,82 +9,68 @@ import BaseElementFactory from "diagram-js/lib/core/ElementFactory"; import DomainStoryIdFactory from "./domainStoryIdFactory"; import { ElementTypes } from "src/app/domain/entities/elementTypes"; -const DEFAULT_LABEL_SIZE = { - width: 90, - height: 20, -}; - export default function DomainStoryElementFactory() { let self = this; let domainStoryIdFactory = new DomainStoryIdFactory(); /** - * create a diagram-js element with the given type (any of shape, connection, label). + * create a diagram-js element * - * @param {String} elementType + * @param {String} djsElementType * @param {Object} attrs * * @return {djs.model.Base} */ - this.create = function (elementType, attrs) { - let type = attrs.type; - - if (elementType === "label") { - return self.baseCreate( - elementType, - assign({ type: "label" }, DEFAULT_LABEL_SIZE, attrs), - ); + this.create = function (djsElementType, attrs) { + let dstElementType = attrs.type; + + if (!attrs.businessObject) { + attrs.businessObject = { + type: dstElementType, + name: attrs.name ? attrs.name : "", + }; } - // add type to businessObject if custom - if (/^domainStory:/.test(type)) { - if (!attrs.businessObject) { - attrs.businessObject = { - type: type, - name: attrs.name ? attrs.name : "", - }; + if (attrs.id) { + domainStoryIdFactory.registerId(attrs.id); + } else { + attrs.id = domainStoryIdFactory.getId(djsElementType); + } + assign(attrs.businessObject, { + id: attrs.id, + }); + + let id = attrs.id; + attrs.businessObject.get = function (key) { + if (key === "id") { + return id; } - - if (attrs.id) { - domainStoryIdFactory.registerId(attrs.id); - } else { - attrs.id = domainStoryIdFactory.getId(elementType); + }; + attrs.businessObject.set = function (key, value) { + if (key === "id") { + assign(attrs.businessObject, { id: value }); } - assign(attrs.businessObject, { - id: attrs.id, - }); + }; - let id = attrs.id; - attrs.businessObject.get = function (key) { - if (key === "id") { - return id; - } - }; - attrs.businessObject.set = function (key, value) { - if (key === "id") { - assign(attrs.businessObject, { id: value }); - } - }; - - // add width and height if shape - if ( - (!/:activity$/.test(type) || !/:connection$/.test(type)) && - !((/:group$/.test(type) && attrs.height) || attrs.width) - ) { - assign(attrs, self._getCustomElementSize(type)); - } + // add width and height if shape + if (djsElementType === "shape") { + let alreadyHasSize = attrs.height || attrs.width; // if a story is imported, groups and annotations already have dimensions; we must not overwrite them with default values - if (!("$instanceOf" in attrs.businessObject)) { - // ensure we can use ModelUtil#is for type checks - Object.defineProperty(attrs.businessObject, "$instanceOf", { - value: function (type) { - return this.type === type; - }, - }); + if (!alreadyHasSize) { + assign(attrs, self._getShapeSize(dstElementType)); } + } - return self.baseCreate(elementType, attrs); + if (!("$instanceOf" in attrs.businessObject)) { + // ensure we can use ModelUtil#is for type checks + Object.defineProperty(attrs.businessObject, "$instanceOf", { + value: function (type) { + return this.type === type; + }, + }); } + + return self.baseCreate(djsElementType, attrs); }; } @@ -94,20 +80,20 @@ DomainStoryElementFactory.prototype.baseCreate = BaseElementFactory.prototype.create; /** - * returns the default size of custom shapes. + * returns the default size for shapes. * * - * @param {String} type + * @param {String} dstElementType * * @return {Dimensions} a {width, height} object representing the size of the element */ -DomainStoryElementFactory.prototype._getCustomElementSize = function (type) { +DomainStoryElementFactory.prototype._getShapeSize = function (dstElementType) { let shapes = { __default: { width: 75, height: 75 }, [ElementTypes.TEXTANNOTATION]: { width: 100, height: 30 }, [ElementTypes.GROUP]: { width: 300, height: 200 }, }; - return shapes[type] || shapes.__default; + return shapes[dstElementType] || shapes.__default; }; class Dimensions { From 261fee25c1e51137e3a049c0a1ad5c551ea1334f Mon Sep 17 00:00:00 2001 From: hofstef Date: Tue, 28 Jan 2025 15:06:50 +0100 Subject: [PATCH 3/4] cleanup remaining bpmn leftovers --- .../diagram-js/features/domainStoryRules.js | 3 - .../features/modeling/dSModeling.js | 36 +-- .../updateHandler/updateLabelHandler.js | 2 +- .../test/domainStoryModelingSpec.js | 274 ------------------ 4 files changed, 11 insertions(+), 304 deletions(-) delete mode 100644 src/app/tools/modeler/diagram-js/test/domainStoryModelingSpec.js diff --git a/src/app/tools/modeler/diagram-js/features/domainStoryRules.js b/src/app/tools/modeler/diagram-js/features/domainStoryRules.js index fed4fc30..f97d79b8 100644 --- a/src/app/tools/modeler/diagram-js/features/domainStoryRules.js +++ b/src/app/tools/modeler/diagram-js/features/domainStoryRules.js @@ -168,9 +168,6 @@ function canConnectToAnnotation(source, target, connection) { ); } -/** - * specific rules for custom elements - */ export default function DomainStoryRules(eventBus) { RuleProvider.call(this, eventBus); } diff --git a/src/app/tools/modeler/diagram-js/features/modeling/dSModeling.js b/src/app/tools/modeler/diagram-js/features/modeling/dSModeling.js index cca701b3..5c52fc71 100644 --- a/src/app/tools/modeler/diagram-js/features/modeling/dSModeling.js +++ b/src/app/tools/modeler/diagram-js/features/modeling/dSModeling.js @@ -19,19 +19,11 @@ Modeling.prototype.updateLabel = function (element, newLabel, newBounds) { ? newLabel !== element.businessObject.name : newLabel !== element.name ) { - if (/^domainStory:/.test(element.type)) { - this._commandStack.execute("element.updateCustomLabel", { - element: element, - newLabel: newLabel, - newBounds: newBounds, - }); - } else { - this._commandStack.execute("element.updateLabel", { - element: element, - newLabel: newLabel, - newBounds: newBounds, - }); - } + this._commandStack.execute("element.updateLabel", { + element: element, + newLabel: newLabel, + newBounds: newBounds, + }); } }; @@ -41,19 +33,11 @@ Modeling.prototype.updateNumber = function (element, newNumber, newBounds) { ? newNumber !== element.businessObject.number : newNumber !== element.number ) { - if (/^domainStory:/.test(element.type)) { - this._commandStack.execute("element.updateCustomLabel", { - element: element, - newNumber: newNumber, - newBounds: newBounds, - }); - } else { - this._commandStack.execute("element.updateLabel", { - element: element, - newNumber: newNumber, - newBounds: newBounds, - }); - } + this._commandStack.execute("element.updateLabel", { + element: element, + newNumber: newNumber, + newBounds: newBounds, + }); } }; diff --git a/src/app/tools/modeler/diagram-js/features/updateHandler/updateLabelHandler.js b/src/app/tools/modeler/diagram-js/features/updateHandler/updateLabelHandler.js index 62a85383..7a525318 100644 --- a/src/app/tools/modeler/diagram-js/features/updateHandler/updateLabelHandler.js +++ b/src/app/tools/modeler/diagram-js/features/updateHandler/updateLabelHandler.js @@ -23,7 +23,7 @@ export default function UpdateLabelHandler( textRenderer, commandStack, ) { - commandStack.registerHandler("element.updateCustomLabel", handlerFunction); + commandStack.registerHandler("element.updateLabel", handlerFunction); function handlerFunction() { this.execute = function (ctx) { diff --git a/src/app/tools/modeler/diagram-js/test/domainStoryModelingSpec.js b/src/app/tools/modeler/diagram-js/test/domainStoryModelingSpec.js deleted file mode 100644 index 6f92ad45..00000000 --- a/src/app/tools/modeler/diagram-js/test/domainStoryModelingSpec.js +++ /dev/null @@ -1,274 +0,0 @@ -/* import { bootstrapBpmnJS, inject } from "./testHelper"; - -import { assign } from "min-dash"; -import DomainStoryModeler from "../index"; -import { ElementTypes } from "../../../../domain/entities/elementTypes"; - -// This test is currently not running, Spec.js tests dont run when executing ng test, tests need to be named .spec.ts -// to run when executing ng test. Instead of migrating it to a typescript test, we want to introduce integration tests -// in the future and use the scenarios in this file as test cases. - -describe("domainStory modeling", function () { - const xml = require("./language/diagram.bpmn"); - - beforeEach(bootstrapBpmnJS(DomainStoryModeler, xml)); - - describe("custom elements", function () { - beforeEach(inject(function (bpmnjs) { - const businessObject = { - type: "custom:triangle", - id: "CustomTriangle_1", - }; - const customShape = { - businessObject: businessObject, - type: "custom:triangle", - id: "CustomTriangle_1", - x: 300, - y: 300, - height: 100, - width: 100, - }; - bpmnjs.addCustomElements([customShape]); - })); - - it("should export custom element", inject(function ( - bpmnjs, - elementRegistry, - modeling, - ) { - // type has to be registered for test - // initTypeDictionaries(test_conf.actors); - - // given - const customElement = { - type: "domainStory:actorPerson", - id: "CustomActor_1", - x: 200, - y: 400, - height: 100, - width: 100, - }; - - const businessObject = { - type: "domainStory:actorPerson", - id: "CustomActor_1", - x: 150, - y: 350, - }; - - assign({ businessObject: businessObject }, customElement); - const position = { x: customElement.x, y: customElement.y }, - target = elementRegistry.get("Process_1"); - - modeling.createShape(customElement, position, target); - - // when - const customElements = bpmnjs.getCustomElements(); - - // then - // we can only check for parts of our element since the create shape function adds parts to the shape, we cannot model here - expect(customElements[1]).toContain(businessObject); - })); - - it("should not resize custom shape", inject(function ( - elementRegistry, - rules, - ) { - // given - const customElement = elementRegistry.get("CustomTriangle_1"); - - // when - const allowed = rules.allowed("resize", { shape: customElement }); - - // then - expect(allowed).toBeFalsy(); - })); - - it("should update custom element", inject(function ( - elementRegistry, - modeling, - ) { - // given - const customElement = elementRegistry.get("CustomTriangle_1"); - - // when - modeling.moveShape( - customElement, - { x: 200, y: 50 }, - customElement.parent, - ); - - // then - expect(customElement.x).toEqual(500); - expect(customElement.y).toEqual(350); - })); - }); - - describe("custom connections", function () { - beforeEach(inject(function (bpmnjs) { - const customShape = { - type: "custom:triangle", - id: "CustomTriangle_1", - x: 400, - y: 300, - }; - - bpmnjs.addCustomElements([customShape]); - })); - - it("should export custom connection", inject(function ( - bpmnjs, - elementRegistry, - modeling, - ) { - // given - const customShape = elementRegistry.get("CustomTriangle_1"), - taskShape = elementRegistry.get("Task_1"); - - modeling.connect(customShape, taskShape, { - type: CONNECTION, - id: "CustomConnection_1", - }); - - // when - const customElements = bpmnjs.getCustomElements(); - - // then - const ids = customElements.map(function (element) { - return element.id; - }); - - expect(ids).toContain("CustomConnection_1"); - })); - - it("should not connect custom shape to start event", inject(function ( - elementRegistry, - rules, - ) { - // given - const customShape = elementRegistry.get("CustomTriangle_1"), - startEventShape = elementRegistry.get("StartEvent_1"); - - // when - const allowed = rules.allowed("connection.create", { - source: customShape, - target: startEventShape, - }); - - // then - expect(allowed).toBeFalsy(); - })); - - it("should reconnect start", inject(function ( - bpmnjs, - elementRegistry, - modeling, - ) { - // given - const customShape = elementRegistry.get("CustomTriangle_1"), - taskShape = elementRegistry.get("Task_1"); - - const customConnection = modeling.connect(customShape, taskShape, { - type: ElementTypes.CONNECTION, - }); - - bpmnjs.addCustomElements([ - { - type: "doaminStory:actor", - id: "CustomCircle_1", - x: 200, - y: 300, - }, - ]); - - const customCircle = elementRegistry.get("CustomCircle_1"); - - // when - modeling.reconnectStart(customConnection, customCircle, { - x: customCircle.x + customCircle.width / 2, - y: customCircle.y + customCircle.height / 2, - }); - - // then - expect(customConnection.source).toEqual(customCircle); - expect(customConnection.target).toEqual(taskShape); - })); - - it("should reconnect end", inject(function ( - bpmnjs, - elementRegistry, - modeling, - ) { - // given - const customShape = elementRegistry.get("CustomTriangle_1"), - taskShape1 = elementRegistry.get("Task_1"), - taskShape2 = elementRegistry.get("Task_2"); - - const customConnection = modeling.connect(customShape, taskShape1, { - type: ElementTypes.CONNECTION, - }); - - // when - modeling.reconnectEnd(customConnection, taskShape2, { - x: taskShape2.x + taskShape2.width / 2, - y: taskShape2.y + taskShape2.height / 2, - }); - - // then - expect(customConnection.source).toEqual(customShape); - expect(customConnection.target).toEqual(taskShape2); - })); - - it("should update custom connection", inject(function ( - elementRegistry, - modeling, - ) { - // given - const customElement = elementRegistry.get("CustomTriangle_1"), - taskShape = elementRegistry.get("Task_1"); - - const customConnection = modeling.connect(customElement, taskShape, { - type: ElementTypes.CONNECTION, - }); - - // when - modeling.moveShape( - customElement, - { x: 200, y: 50 }, - customElement.parent, - ); - - // then - - const waypoint1 = customConnection.businessObject.waypoints[0]; - const waypoint2 = customConnection.businessObject.waypoints[1]; - - expect(waypoint1.x).toEqual(600); - expect(waypoint1.y).toEqual(351); - expect(waypoint2.x).toEqual(354); - expect(waypoint2.y).toEqual(157); - })); - - it("should remove deleted connection from _elements", inject(function ( - bpmnjs, - elementRegistry, - modeling, - ) { - // given - const customShape = elementRegistry.get("CustomTriangle_1"), - taskShape = elementRegistry.get("Task_1"), - customElements = bpmnjs.getCustomElements(); - - const customConnection = modeling.connect(customShape, taskShape, { - type: ElementTypes.CONNECTION, - }); - - // when - modeling.removeConnection(customConnection); - - // then - expect(customElements.length).toEqual(1); - })); - }); -}); - */ From 9c064c2305a2d8c53a2a2dda31ed20056259b103 Mon Sep 17 00:00:00 2001 From: hofstef Date: Tue, 28 Jan 2025 22:14:05 +0100 Subject: [PATCH 4/4] cleanup remaining bpmn leftovers --- .../features/domainStoryRenderer.js | 3 --- .../diagram-js/features/domainStoryUpdater.js | 24 ++++--------------- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/src/app/tools/modeler/diagram-js/features/domainStoryRenderer.js b/src/app/tools/modeler/diagram-js/features/domainStoryRenderer.js index bb7f1d43..6bdda787 100644 --- a/src/app/tools/modeler/diagram-js/features/domainStoryRenderer.js +++ b/src/app/tools/modeler/diagram-js/features/domainStoryRenderer.js @@ -32,9 +32,6 @@ let RENDERER_IDS = new Ids(); let numbers = []; const DEFAULT_COLOR = "#000000"; -/** - * a renderer that knows how to render custom elements. - */ let _iconDictionaryService; let _elementRegistryService; let _dirtyFlagService; diff --git a/src/app/tools/modeler/diagram-js/features/domainStoryUpdater.js b/src/app/tools/modeler/diagram-js/features/domainStoryUpdater.js index b54c1394..681614fe 100644 --- a/src/app/tools/modeler/diagram-js/features/domainStoryUpdater.js +++ b/src/app/tools/modeler/diagram-js/features/domainStoryUpdater.js @@ -158,7 +158,7 @@ export default function DomainStoryUpdater(eventBus, egon, connectionDocking) { "shape.resize", "shape.removeGroupWithChildren", ], - ifDomainStoryElement(updateElement), + updateElement, ); this.reverted( @@ -169,7 +169,7 @@ export default function DomainStoryUpdater(eventBus, egon, connectionDocking) { "shape.resize", "shape.removeGroupWithChildren", ], - ifDomainStoryElement(updateElement), + updateElement, ); this.executed( @@ -181,7 +181,7 @@ export default function DomainStoryUpdater(eventBus, egon, connectionDocking) { "connection.layout", "connection.move", ], - ifDomainStoryElement(updateConnection), + updateConnection, ); this.reverted( @@ -193,26 +193,10 @@ export default function DomainStoryUpdater(eventBus, egon, connectionDocking) { "connection.layout", "connection.move", ], - ifDomainStoryElement(updateConnection), + updateConnection, ); } -// check if element in the context of an event is a domainStory element -function ifDomainStoryElement(fn) { - return (event) => { - const context = event.context; - const element = context.shape || context.connection; - - if (isDomainStory(element)) { - fn(event); - } - }; -} - -function isDomainStory(element) { - return element && /domainStory:/.test(element.type); -} - inherits(DomainStoryUpdater, CommandInterceptor); DomainStoryUpdater.$inject = ["eventBus", "egon", "connectionDocking"];