From a03e9ccc958e26a8bdb975e02e66715603631d45 Mon Sep 17 00:00:00 2001 From: Martin Stamm Date: Mon, 25 Oct 2021 12:00:38 +0200 Subject: [PATCH] feat(subprocesses): add connections to expanded view --- .../subprocess-navigation/SubprocessFlows.js | 109 ++++++++++++++++ lib/features/subprocess-navigation/index.js | 8 +- .../SubprocessNavigationSpec.js | 113 ++++++++++++++++ .../nested-subprocesses.bpmn | 121 ++++++------------ 4 files changed, 264 insertions(+), 87 deletions(-) create mode 100644 lib/features/subprocess-navigation/SubprocessFlows.js diff --git a/lib/features/subprocess-navigation/SubprocessFlows.js b/lib/features/subprocess-navigation/SubprocessFlows.js new file mode 100644 index 00000000..f29b1f31 --- /dev/null +++ b/lib/features/subprocess-navigation/SubprocessFlows.js @@ -0,0 +1,109 @@ +var FLOW_LENGTH = 50; +var HIGH_RENDER_PRIORITY = 2000; + +export default function SubprocessFlows(eventBus, config) { + + var mutedStrokeColor = (config && config.mutedStrokeColor) || '#dddddd'; + + function drawFakeFlows(element, parentGfx) { + var primary = element.primaryShape; + + var drawFakeConnection = function(connection, isOutgoing) { + + var segment; + if (isOutgoing) { + segment = connection.waypoints.slice(0, 2); + + // Reverse order for normalizing + segment = segment.reverse(); + } else { + segment = connection.waypoints.slice(connection.waypoints.length - 2); + } + + var endpoint = segment[1]; + + var relativePosition = { + x: (endpoint.x - primary.x) / primary.width, + y: (endpoint.y - primary.y) / primary.height + }; + + var anchorPoint = { + x: endpoint.x - (element.width * relativePosition.x), + y: endpoint.y - (element.height * relativePosition.y) + }; + + var newWaypoints = segment.map(function(el) { + return { x: el.x - anchorPoint.x, y: el.y - anchorPoint.y }; + }); + + newWaypoints[0] = normalizeLength(newWaypoints[1], newWaypoints[0], FLOW_LENGTH); + + var attrs = { + stroke: mutedStrokeColor + }; + + if (isOutgoing) { + + // Reverse order again for correct end marker position + newWaypoints = newWaypoints.reverse(); + } else { + + // Remove start marker for incoming flows + attrs.markerStart = undefined; + } + + eventBus.fire('render.connection', { + type: connection.type, + gfx: parentGfx, + element: { + type: connection.type, + waypoints: newWaypoints, + di: connection.di, + businessObject: connection.businessObject + }, + attrs: attrs + }); + }; + + primary.incoming && primary.incoming.forEach(function(connection) { + drawFakeConnection(connection, false); + }); + + primary.outgoing && primary.outgoing.forEach(function(connection) { + drawFakeConnection(connection, true); + }); + } + + eventBus.on([ 'render.shape' ], HIGH_RENDER_PRIORITY, function(evt, context) { + var element = context.element, + visuals = context.gfx; + + if (element.isSecondary && element.primaryShape) { + drawFakeFlows(element, visuals); + } + + }); + +} + +SubprocessFlows.$inject = [ 'eventBus', 'config' ]; + + +// helpers + +function normalizeLength(fixedPoint, variablePoint, length) { + var dx = fixedPoint.x - variablePoint.x, + dy = fixedPoint.y - variablePoint.y, + totalDistance = Math.abs(dx) + Math.abs(dy); + + dx = dx / totalDistance; + dy = dy / totalDistance; + + dx *= length; + dy *= length; + + return { + x: fixedPoint.x - dx, + y: fixedPoint.y - dy + }; +} diff --git a/lib/features/subprocess-navigation/index.js b/lib/features/subprocess-navigation/index.js index 0360ff3d..70f1f7d5 100644 --- a/lib/features/subprocess-navigation/index.js +++ b/lib/features/subprocess-navigation/index.js @@ -1,14 +1,18 @@ import OverlaysModule from 'diagram-js/lib/features/overlays'; import ChangeSupportModule from 'diagram-js/lib/features/change-support'; +import SubprocessElements from './SubprocessElements'; import SubprocessCentering from './SubprocessCentering'; import SubprocessCompatibility from './SubprocessCompatibility'; import SubprocessOverlays from './SubprocessOverlays'; +import SubprocessFlows from './SubprocessFlows'; export default { __depends__: [ OverlaysModule, ChangeSupportModule ], - __init__: [ 'subprocessOverlays', 'subprocessCompatibility', 'subprocessCentering' ], + __init__: [ 'subprocessElements', 'subprocessOverlays', 'subprocessCompatibility', 'subprocessCentering', 'subprocessFlows' ], + subprocessElements: [ 'type', SubprocessElements ], subprocessOverlays: [ 'type', SubprocessOverlays ], subprocessCompatibility: [ 'type', SubprocessCompatibility ], - subprocessCentering: [ 'type', SubprocessCentering ] + subprocessCentering: [ 'type', SubprocessCentering ], + subprocessFlows: ['type', SubprocessFlows] }; \ No newline at end of file diff --git a/test/spec/features/subprocess-navigation/SubprocessNavigationSpec.js b/test/spec/features/subprocess-navigation/SubprocessNavigationSpec.js index a369af34..cd011ea8 100644 --- a/test/spec/features/subprocess-navigation/SubprocessNavigationSpec.js +++ b/test/spec/features/subprocess-navigation/SubprocessNavigationSpec.js @@ -201,6 +201,119 @@ describe('features - subprocess-navigation', function() { }); + + describe('Secondary Elements', function() { + + it('should render a secondary element', inject(function(elementRegistry) { + + // given + var processShape = elementRegistry.get('collapsedProcess_secondary'); + + // expect + expect(processShape).to.exist; + })); + + + it('should link to primary element', inject(function(elementRegistry) { + + // given + var processShape = elementRegistry.get('collapsedProcess_secondary'); + + // expect + expect(processShape.primaryShape).to.exist; + })); + + + it('should have padding', inject(function(elementRegistry) { + + // given + var processShape = elementRegistry.get('single-task-process_secondary'); + + // expect + // default task is 100x80, expect 50px padding to every side + expect(processShape.width).to.eql(200); + expect(processShape.height).to.eql(180); + + })); + + + it('should render flows with muted stroke color', inject(function(eventBus, elementRegistry, canvas) { + + // given + var spy = sinon.spy(); + var primaryElement = elementRegistry.get('root_startEvent'); + + var secondaryElement = { + id:'secondary', + type: 'bpmn:StartEvent', + x: 0, + y: 0, + width: 32, + height: 32, + isSecondary: true, + primaryShape: primaryElement, + businessObject: primaryElement.businessObject, + di: primaryElement.di + }; + + eventBus.on('render.connection', 2000, spy); + + // when + canvas.addShape(secondaryElement); + + // then + expect(spy).to.have.been.called; + + var attrs = spy.firstCall.args[1].attrs; + expect(attrs.stroke).to.equal('#dddddd'); + + })); + + + describe('boundary events', function() { + + it('should render secondary element', inject(function(elementRegistry) { + + // given + var boundary_secondary = elementRegistry.get('boundaryError_secondary'); + + // expect + expect(boundary_secondary).to.exist; + })); + + + it('should position it relatively to original element', inject(function(elementRegistry) { + + // given + var process_secondary = elementRegistry.get('errorSubProcess_secondary'); + var process_primary = process_secondary.primaryShape; + var boundary_secondary = process_secondary.attachers[0]; + var boundary_primary = process_primary.attachers[0]; + + // assume + expect(boundary_primary).to.exist; + expect(boundary_secondary).to.exist; + + // (middle - element process offset) / total border length + var relativePositionPrimary = { + x: (boundary_primary.x + boundary_primary.width/2 - process_primary.x) / process_primary.width, + y: (boundary_primary.y + boundary_primary.width/2 - process_primary.y) / process_primary.height + }; + + var relativePositionSecondary = { + x: (boundary_secondary.x + boundary_secondary.width/2 - process_secondary.x) / process_secondary.width, + y: (boundary_secondary.y + boundary_secondary.width/2 - process_secondary.y) / process_secondary.height + }; + + // then + expect(relativePositionPrimary).to.be.eql(relativePositionSecondary); + })); + + }); + + + }); + }); diff --git a/test/spec/features/subprocess-navigation/nested-subprocesses.bpmn b/test/spec/features/subprocess-navigation/nested-subprocesses.bpmn index 154d6904..f42404a9 100644 --- a/test/spec/features/subprocess-navigation/nested-subprocesses.bpmn +++ b/test/spec/features/subprocess-navigation/nested-subprocesses.bpmn @@ -1,8 +1,8 @@ - + - + sid-89A3F9F2-CCC8-46C7-816B-DD8AC8A98300 @@ -21,10 +21,6 @@ sid-FB543319-8DFB-4445-AAA3-720137FB230B - - - - sid-FB543319-8DFB-4445-AAA3-720137FB230B sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0 @@ -43,30 +39,23 @@ sid-01982395-64E8-43EF-A6D3-CDD276C312AA - - - - + + sid-910420B0-D11B-4F9D-B285-703D8AC0BA90 - - - - + + sid-B99D259B-1BD5-45FF-BD57-FB99C360BAC0 - - - - - - + + + - + sid-FC2ECAF5-771E-4ED3-BEF6-EFAB45E79500 sid-5B23450F-AF5E-4519-B134-32107776BD44 @@ -85,28 +74,12 @@ sid-4E25B80E-EF68-4EE5-BB08-C1F54F1A7C39 - - - - + + - + sid-6B9741CD-D94B-41C7-A2EA-63A4C9445E16 sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519 - - sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC - - - sid-E5404926-738D-4447-87FE-FC6DD1E8BEFC - sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E - - - sid-FED62A8F-6C3A-4BB2-8DE9-18FB0B35B50E - - - - - sid-1A9DABC6-6079-4BF2-9D49-C4DC9569C519 @@ -116,20 +89,19 @@ - + - - + sid-5B23450F-AF5E-4519-B134-32107776BD44 sid-31F6EC44-E44C-4121-B4FE-BD69AF208C05 sid-F7DA1903-6A1A-4858-AF4B-286A968C957F - + sid-DCB98638-BEBD-4548-B501-F0E29AC71ED4 @@ -143,22 +115,15 @@ sid-3FAE72F2-4037-4CBA-8B89-01D7FC7FF3E3 - - - - - - - - - - - - - - - - + + + + + + + + + @@ -199,7 +164,7 @@ - + @@ -211,7 +176,7 @@ - + @@ -232,7 +197,7 @@ - + @@ -243,7 +208,7 @@ - + @@ -272,9 +237,9 @@ - + - + @@ -325,7 +290,7 @@ - + @@ -335,7 +300,7 @@ - + @@ -388,28 +353,14 @@ - - - - - + + - + - - - - - - - - - - -