Skip to content

Commit

Permalink
feat: provide selection outlines
Browse files Browse the repository at this point in the history
Closes #1996
  • Loading branch information
smbea committed Oct 31, 2023
1 parent 66eca90 commit 3dc9a27
Show file tree
Hide file tree
Showing 6 changed files with 414 additions and 0 deletions.
2 changes: 2 additions & 0 deletions lib/Modeler.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import LabelEditingModule from './features/label-editing';
import ModelingModule from './features/modeling';
import ModelingFeedbackModule from './features/modeling-feedback';
import MoveModule from 'diagram-js/lib/features/move';
import OutlineModule from './features/outline';
import PaletteModule from './features/palette';
import ReplacePreviewModule from './features/replace-preview';
import ResizeModule from 'diagram-js/lib/features/resize';
Expand Down Expand Up @@ -182,6 +183,7 @@ Modeler.prototype._modelingModules = [
ModelingModule,
ModelingFeedbackModule,
MoveModule,
OutlineModule,
PaletteModule,
ReplacePreviewModule,
ResizeModule,
Expand Down
163 changes: 163 additions & 0 deletions lib/features/outline/OutlineProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import { assign } from 'min-dash';

import {
attr as svgAttr,
create as svgCreate
} from 'tiny-svg';

import {
is,
isAny
} from '../../util/ModelUtil';

import { isLabel } from '../../util/LabelUtil';

import {
DATA_OBJECT_REFERENCE_OUTLINE_PATH,
DATA_STORE_REFERENCE_OUTLINE_PATH,
DATA_OBJECT_REFERENCE_STANDARD_SIZE,
DATA_STORE_REFERENCE_STANDARD_SIZE,
createPath
} from './OutlineUtil';

const DEFAULT_OFFSET = 5;

/**
* BPMN-specific outline provider.
*
* @implements {BaseOutlineProvider}
*
* @param {Outline} outline
* @param {Styles} styles
*/
export default function OutlineProvider(outline, styles) {

this._styles = styles;
outline.registerProvider(this);
}

OutlineProvider.$inject = [
'outline',
'styles'
];

/**
* Returns outline for a given element.
*
* @param {Element} element
*
* @return {Outline}
*/
OutlineProvider.prototype.getOutline = function(element) {

const OUTLINE_STYLE = this._styles.cls('djs-outline', [ 'no-fill' ]);

var outline;

if (isLabel(element)) {
return;
}

if (is(element, 'bpmn:Gateway')) {
outline = svgCreate('rect');

assign(outline.style, {
'transform-box': 'fill-box',
'transform': 'rotate(45deg)',
'transform-origin': 'center'
});

svgAttr(outline, assign({
x: 2,
y: 2,
rx: 4,
width: element.width - 4,
height: element.height - 4,
}, OUTLINE_STYLE));

} else if (isAny(element, [ 'bpmn:Task', 'bpmn:SubProcess', 'bpmn:Group' ])) {
outline = svgCreate('rect');

svgAttr(outline, assign({
x: -DEFAULT_OFFSET,
y: -DEFAULT_OFFSET,
rx: 14,
width: element.width + DEFAULT_OFFSET * 2,
height: element.height + DEFAULT_OFFSET * 2
}, OUTLINE_STYLE));

} else if (is(element, 'bpmn:Event')) {
outline = svgCreate('circle');

svgAttr(outline, assign({
cx: element.width / 2,
cy: element.height / 2,
r: element.width / 2 + DEFAULT_OFFSET
}, OUTLINE_STYLE));

} else if (is(element, 'bpmn:DataObjectReference') && isStandardSize(element, 'bpmn:DataObjectReference')) {

outline = createPath(
DATA_OBJECT_REFERENCE_OUTLINE_PATH,
{ x: -6, y: -6 },
OUTLINE_STYLE
);

} else if (is(element, 'bpmn:DataStoreReference') && isStandardSize(element, 'bpmn:DataStoreReference')) {

outline = createPath(
DATA_STORE_REFERENCE_OUTLINE_PATH,
{ x: -6, y: -6 },
OUTLINE_STYLE
);
}

return outline;
};

/**
* Updates the outline for a given element.
* Returns true if the update for the given element was handled by this provider.
*
* @param {Element} element
* @param {Outline} outline
* @returns {boolean}
*/
OutlineProvider.prototype.updateOutline = function(element, outline) {

if (isAny(element, [ 'bpmn:SubProcess', 'bpmn:Group' ])) {

svgAttr(outline, {
width: element.width + DEFAULT_OFFSET * 2,
height: element.height + DEFAULT_OFFSET * 2
});

return true;

} else if (isAny(element, [
'bpmn:Event',
'bpmn:Gateway',
'bpmn:DataStoreReference',
'bpmn:DataObjectReference'
])) {
return true;
}

return false;
};


// helpers //////////

function isStandardSize(element, type) {
var standardSize;

if (type === 'bpmn:DataObjectReference') {
standardSize = DATA_OBJECT_REFERENCE_STANDARD_SIZE;
} else if (type === 'bpmn:DataStoreReference') {
standardSize = DATA_STORE_REFERENCE_STANDARD_SIZE;
}

return element.width === standardSize.width
&& element.height === standardSize.height;
}
32 changes: 32 additions & 0 deletions lib/features/outline/OutlineUtil.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
create as svgCreate
} from 'tiny-svg';

export const DATA_OBJECT_REFERENCE_OUTLINE_PATH = 'M44.7648 11.3263L36.9892 2.64074C36.0451 1.58628 34.5651 0.988708 33.1904 0.988708H5.98667C3.22688 0.988708 0.989624 3.34892 0.989624 6.26039V55.0235C0.989624 57.9349 3.22688 60.2952 5.98667 60.2952H40.966C43.7257 60.2952 45.963 57.9349 45.963 55.0235V14.9459C45.963 13.5998 45.6407 12.3048 44.7648 11.3263Z';
export const DATA_STORE_REFERENCE_OUTLINE_PATH = 'M1.03845 48.1347C1.03845 49.3511 1.07295 50.758 1.38342 52.064C1.69949 53.3938 2.32428 54.7154 3.56383 55.6428C6.02533 57.4841 10.1161 58.7685 14.8212 59.6067C19.5772 60.4538 25.1388 60.8738 30.6831 60.8738C36.2276 60.8738 41.7891 60.4538 46.545 59.6067C51.2504 58.7687 55.3412 57.4842 57.8028 55.6429C59.0424 54.7156 59.6673 53.3938 59.9834 52.064C60.2938 50.7579 60.3285 49.351 60.3285 48.1344V13.8415C60.3285 12.6249 60.2938 11.218 59.9834 9.91171C59.6673 8.58194 59.0423 7.2602 57.8027 6.33294C55.341 4.49168 51.2503 3.20723 46.545 2.36914C41.7891 1.522 36.2276 1.10204 30.6831 1.10205C25.1388 1.10206 19.5772 1.52206 14.8213 2.36923C10.1162 3.20734 6.02543 4.49183 3.5639 6.33314C2.32433 7.26038 1.69951 8.58206 1.38343 9.91181C1.07295 11.2179 1.03845 12.6247 1.03845 13.8411V48.1347Z';

/**
* @type {Dimensions}
*/
export const DATA_OBJECT_REFERENCE_STANDARD_SIZE = { width: 36, height: 50 };

/**
* @type {Dimensions}
*/
export const DATA_STORE_REFERENCE_STANDARD_SIZE = { width: 50, height: 50 };

/**
* Create a path element with given attributes.
* @param {string} path
* @param {Object} attrs
* @param {Object} OUTLINE_STYLE
* @return {SVGElement}
*/
export function createPath(path, attrs, OUTLINE_STYLE) {
return svgCreate('path', {
d: path,
strokeWidth: 2,
transform: `translate(${attrs.x}, ${attrs.y})`,
...OUTLINE_STYLE
});
}
10 changes: 10 additions & 0 deletions lib/features/outline/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Ouline from 'diagram-js/lib/features/outline';
import OulineProvider from './OutlineProvider';

export default {
__depends__: [
Ouline
],
__init__: [ 'outlineProvider' ],
outlineProvider: [ 'type', OulineProvider ]
};
45 changes: 45 additions & 0 deletions test/spec/features/outline/OutlineProvider.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_0runo6g" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.15.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="8.2.0">
<bpmn:collaboration id="Collaboration_0zr6kvh">
<bpmn:participant id="Participant" processRef="Process_0wvtx2g" />
<bpmn:group id="Group" />
</bpmn:collaboration>
<bpmn:process id="Process_0wvtx2g" isExecutable="true">
<bpmn:dataObjectReference id="DataObject" dataObjectRef="DataObject_0usa0me" />
<bpmn:dataObject id="DataObject_0usa0me" />
<bpmn:dataStoreReference id="DataStore" />
<bpmn:subProcess id="SubProcess">
<bpmn:startEvent id="Event" />
<bpmn:task id="Task" />
<bpmn:exclusiveGateway id="Gateway" />
</bpmn:subProcess>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Collaboration_0zr6kvh">
<bpmndi:BPMNShape id="Participant_1fsrebj_di" bpmnElement="Participant" isHorizontal="true">
<dc:Bounds x="129" y="117" width="681" height="423" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataObjectReference_0cawmpm_di" bpmnElement="DataObject">
<dc:Bounds x="252" y="215" width="36" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="DataStoreReference_0xv2i27_di" bpmnElement="DataStore">
<dc:Bounds x="245" y="375" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1xbvt0p_di" bpmnElement="SubProcess" isExpanded="true">
<dc:Bounds x="420" y="240" width="330" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_0dihp8i_di" bpmnElement="Event">
<dc:Bounds x="460" y="322" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0px6pz2_di" bpmnElement="Gateway" isMarkerVisible="true">
<dc:Bounds x="665" y="315" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_0b3cy2o_di" bpmnElement="Task">
<dc:Bounds x="530" y="300" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Group_1utiosm_di" bpmnElement="Group">
<dc:Bounds x="200" y="179" width="140" height="300" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Loading

0 comments on commit 3dc9a27

Please sign in to comment.