This commit is contained in:
hanjian
2024-08-14 15:17:51 +08:00
parent 20a221c1a2
commit b610f94b2e
3483 changed files with 650965 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
{
"presets": [ "env" ]
}

View File

@@ -0,0 +1 @@
Tue Apr 07 2020 16:59:34 GMT+0800 (GMT+08:00)

View File

@@ -0,0 +1,308 @@
# Changelog
All notable changes to [bpmn-js](https://github.com/bpmn-io/bpmn-js) are documented here. We use [semantic versioning](http://semver.org/) for releases.
## Unreleased
___Note:__ Yet to be released changes appear here._
## 4.0.4
* `FIX`: creating `bpmn:Participant` on single `bpmn:Group` throwing error ([#1133](https://github.com/bpmn-io/bpmn-js/issues/1133))
* `CHORE`: bump to `diagram-js@4.0.3`
## 4.0.3
* `FIX`: prevent dropping on labels and `bpmn:Group` elements ([#1131](https://github.com/bpmn-io/bpmn-js/pull/1131))
## 4.0.2
* `FIX`: correct element positioning update ([#1129](https://github.com/bpmn-io/bpmn-js/issues/1129))
* `CHORE`: bump to `diagram-js@4.0.2`
## 4.0.1
* `FIX`: prevent adding lane from crashing IE ([#746](https://github.com/bpmn-io/bpmn-js/issues/746))
* `FIX`: correct inverse space tool visuals ([#1105](https://github.com/bpmn-io/bpmn-js/issues/1105))
* `CHORE`: update `diagram-js-direct-editing` to prevent install warning
* `CHORE`: update to `diagram-js@4.0.1`
## 4.0.0
* `FEAT`: add top, right, bottom, left snapping with container elements ([#1108](https://github.com/bpmn-io/bpmn-js/pull/1108))
* `FEAT`: add grid snapping ([#987](https://github.com/bpmn-io/bpmn-js/pull/987))
* `FEAT`: allow modeling of groups ([#343](https://github.com/bpmn-io/bpmn-js/issues/343))
* `FEAT`: improve modeling rules behind event-based gateways ([#1006](https://github.com/bpmn-io/bpmn-js/pull/1006))
* `FEAT`: adjust default collapsed pool to standard height ([`5affe2570`](https://github.com/bpmn-io/bpmn-js/commit/5affe25705082937beace6b4a568f176a0527baf))
* `FEAT`: add connection previews ([#743](https://github.com/bpmn-io/bpmn-js/issues/743))
* `FEAT`: create expanded sub-process with start event included ([#1039](https://github.com/bpmn-io/bpmn-js/pull/1039))
* `FEAT`: improve automatic label adjustment for boundary events ([#1064](https://github.com/bpmn-io/bpmn-js/pull/1064))
* `FEAT`: improve creation of initial participant ([#1046](https://github.com/bpmn-io/bpmn-js/pull/1046))
* `FEAT`: improve boundary to host loop layout ([#1070](https://github.com/bpmn-io/bpmn-js/pull/1070))
* `FEAT`: make connection segment move the primary connection drag behavior
* `FEAT`: allow label and group movement everywhere ([#1080](https://github.com/bpmn-io/bpmn-js/pull/1080))
* `FEAT`: improve message flow to participant connection in the presence of lanes ([#950](https://github.com/bpmn-io/bpmn-js/issues/950))
* `FEAT`: allow detaching of boundary and attaching of intermediate events ([#1045](https://github.com/bpmn-io/bpmn-js/issues/1045))
* `FEAT`: simplify requested palette and context pad translations ([#1027](https://github.com/bpmn-io/bpmn-js/pull/1027))
* `FEAT`: simplify participant dragging in the presence of nested lanes ([`fdb299dc`](https://github.com/bpmn-io/bpmn-js/commit/fdb299dc888a7dcdb3f7674b6ed2a857864df457))
* `FEAT`: correctly render all kinds of multiple events ([#1091](https://github.com/bpmn-io/bpmn-js/pull/1091))
* `CHORE`: validate BPMN 2.0 XML ids as QNames ([`92c03679a`](https://github.com/bpmn-io/bpmn-js/commit/92c03679a4fd3c92a1c5ce3c97f7d366e2a5753a))
* `FIX`: correctly handle flow reconnection + type replacement ([#896](https://github.com/bpmn-io/bpmn-js/issues/896), [#1008](https://github.com/bpmn-io/bpmn-js/issues/1008))
### Breaking Changes
* `CHORE`: bump to [`diagram-js@4.0.0`](https://github.com/bpmn-io/diagram-js/blob/master/CHANGELOG.md#400)
## 3.5.0
* `FEAT`: restore `Viewer#importDefinitions` and make it public API ([#1112](https://github.com/bpmn-io/bpmn-js/pull/1112))
## 3.4.3
* `FIX`: prevent HTML injection in search ([diagram-js#362](https://github.com/bpmn-io/diagram-js/pull/362))
## 2.5.4
* `FIX`: prevent HTML injection in search ([diagram-js#362](https://github.com/bpmn-io/diagram-js/pull/362))
* `CHORE`: bump to `diagram-js@2.6.2`
## 3.4.2
* `FIX`: do not evaluate pasted text as HTML ([#1073](https://github.com/bpmn-io/bpmn-js/issues/1073))
## 2.5.3
* `FIX`: do not evaluate pasted text as HTML ([#1073](https://github.com/bpmn-io/bpmn-js/issues/1073))
## 3.4.1
_Republish of `v3.4.0` without `.git` folder._
## 3.4.0
* `FIX`: properly render colored connection markers ([#981](https://github.com/bpmn-io/bpmn-js/issues/981))
* `FEAT`: add ability to open different DI diagrams ([#87](https://github.com/bpmn-io/bpmn-js/issues/87))
* `FIX`: correctly layout straight boundary to target connections ([#891](https://github.com/bpmn-io/bpmn-js/issues/891))
* `FEAT`: resize participant to standard size on collapse ([#975](https://github.com/bpmn-io/bpmn-js/pull/975))
* `FEAT`: consistently layout connection on reconnect start and end ([#971](https://github.com/bpmn-io/bpmn-js/pull/971))
* `FEAT`: layout connection on element removal ([#989](https://github.com/bpmn-io/bpmn-js/issues/989))
* `FIX`: properly crop sequence flow ends on undo/redo ([#940](https://github.com/bpmn-io/bpmn-js/issues/940))
* `CHORE`: bump to [`diagram-js@3.3.0`](https://github.com/bpmn-io/diagram-js/blob/master/CHANGELOG.md#330)
## 3.3.1
* `FIX`: ignore unchanged direct editing completion
* `CHORE`: update to `diagram-js-direct-editing@1.4.2`
## 3.3.0
* `FEAT`: display `DataInput` / `DataOutput` labels ([`89719de3b`](https://github.com/bpmn-io/bpmn-js/commit/89719de3be50d9270227fd04216f7f19f0d018a2))
* `FEAT`: support basic `DataInput` / `DataOutput` move ([#962](https://github.com/bpmn-io/bpmn-js/pull/962))
* `FIX`: properly handle `DataInput` / `DataOutput` move ([#961](https://github.com/bpmn-io/bpmn-js/issues/961))
## 3.2.3
* `FIX`: update to `diagram-js-direct-editing@1.4.1` to trim trailing/leading whitespace in task names ([#763](https://github.com/bpmn-io/bpmn-js/issues/763))
## 3.2.2
* `FIX`: gracefully handle missing waypoints ([`45486f2`](https://github.com/bpmn-io/bpmn-js/commit/45486f2afe7f42fcac31be9ca477a7c94babe7d8))
## 3.2.1
* `FIX`: bump to `diagram-js@3.1.3` / `tiny-svg@2.2.1` to work around MS Edge bug ([`ed798a15`](https://github.com/bpmn-io/bpmn-js/commit/ed798a152539a613dbc9de9d61231ebbfb50987a))
## 3.2.0
* `FEAT`: set isHorizontal=true for new and updated participant/lane DIs ([#934](https://github.com/bpmn-io/bpmn-js/issues/934))
## 3.1.1
* `CHORE`: update to `diagram-js@3.1.1`
## 3.1.0
* `CHORE`: update to `diagram-js@3.1`
## 3.0.4
* `FIX`: render labels always on top ([#920](https://github.com/bpmn-io/bpmn-js/pull/920))
## 3.0.3
* `FIX`: do not join incoming/outgoing flows other than sequence flows on element deletion ([#917](https://github.com/bpmn-io/bpmn-js/issues/917))
## 3.0.2
* `FIX`: correct IE 11 delete keybinding ([#904](https://github.com/bpmn-io/bpmn-js/issues/904))
## 3.0.1
* `FIX`: restore copy-paste behavior
## 3.0.0
* `FEAT`: improve context pad tooltip titles for `EventBasedGateway` ([`350a5ab`](https://github.com/bpmn-io/bpmn-js/commit/350a5ab75ed675991599faff9615e4bbe184d491))
* `FEAT`: display group names ([#844](https://github.com/bpmn-io/bpmn-js/issues/844))
* `FEAT`: add ability to move selection with keyboard arrows ([#376](https://github.com/bpmn-io/bpmn-js/issues/376))
* `FEAT`: support `SHIFT` modifier to move elements / canvas with keyboard arrows at accelerated speed
* `FEAT`: require `Ctrl/Cmd` to be pressed as a modifier key to move the canvas via keyboard errors
* `FEAT`: auto-expand elements when children resize ([#786](https://github.com/bpmn-io/bpmn-js/issues/786))
* `CHORE`: bind editor actions and keyboard shortcuts for explicitly added features only ([#887](https://github.com/bpmn-io/bpmn-js/pull/887))
* `CHORE`: update to [`diagram-js@3.0.0`](https://github.com/bpmn-io/diagram-js/blob/master/CHANGELOG.md#300)
* `FIX`: disallow attaching of `BoundaryEvent` to a `ReceiveTask` following an `EventBasedGateway` ([#874](https://github.com/bpmn-io/bpmn-js/issues/874))
* `FIX`: fix date in license ([#882](https://github.com/bpmn-io/bpmn-js/pull/882))
### Breaking Changes
* `BpmnGlobalConnect` provider got removed. Use `connection.start` rule to customize whether connection should allowed to be started ([#565](https://github.com/bpmn-io/bpmn-js/issues/565), [#870](https://github.com/bpmn-io/bpmn-js/issues/870))
* `EditorActions` / `Keyboard` do not pull in features implicitly anymore. If you roll your own editor, include features you would like to ship with manually to provide the respective actions / keyboard bindings ([`645265ad`](https://github.com/bpmn-io/bpmn-js/commit/645265ad7e4a47e80657c671068a027752d7504f))
* Moving the canvas with keyboard arrows now requires the `Ctrl/Cmd` modifiers to be pressed.
## 2.5.2
* `FIX`: correct horizontal embedded label padding
## 2.5.1
* `FIX`: prevent error to be thrown on lane move ([#855](https://github.com/bpmn-io/bpmn-js/issues/855))
## 2.5.0
* `FEAT`: snap message flows to `bpmn:Event` center during connect ([#850](https://github.com/bpmn-io/bpmn-js/issues/850))
* `CHORE`: bump to `diagram-js@2.6.0`
* `FIX`: allow label movement over message flow ([#849](https://github.com/bpmn-io/bpmn-js/issues/849))
## 2.4.1
* `FIX`: make viewer IE 9 compatible
* `FIX`: prevent duplicate connections after drop on flow ([#774](https://github.com/bpmn-io/bpmn-js/issues/774))
* `FIX`: fix rules not preventing redundant loop ([#836](https://github.com/bpmn-io/bpmn-js/issues/836))
## 2.4.0
* `FEAT`: improve layouting of boundary event to host loops ([#467](https://github.com/bpmn-io/bpmn-js/issues/467))
* `FEAT`: allow circular activity to activity loops ([#824](https://github.com/bpmn-io/bpmn-js/issues/824))
* `FEAT`: create label on appropriate free position ([#825](https://github.com/bpmn-io/bpmn-js/issues/825))
* `CHORE`: bump to `diagram-js@2.5.0`
* `FIX`: repair label position not being adapted on host move
## 2.3.1
* `FIX`: revert to `Arial` as the default rendering font ([#819](https://github.com/bpmn-io/bpmn-js/issues/819))
* `FIX`: keep event definitions when switching from interrupting to non-interrupting boundary event ([#799](https://github.com/bpmn-io/bpmn-js/issues/799))
## 2.3.0
* `CHORE`: update to `diagram-js@2.4.0`
## 2.2.1
* `FIX`: correct updating of multiple data stores ([`300e7010`](https://github.com/bpmn-io/bpmn-js/commit/300e7010c4e1862394d147988dc4c4bcc09b07bc))
## 2.2.0
* `FEAT`: emit export events ([#813](https://github.com/bpmn-io/bpmn-js/issues/813))
* `FEAT`: unset businessObject name if empty ([`6c081d85`](https://github.com/bpmn-io/bpmn-js/commit/6c081d854fa8a4e87eb7cdd1744be37c78652667))
* `FEAT`: resize text annotation on text change ([`100f3fb2`](https://github.com/bpmn-io/bpmn-js/commit/100f3fb2ee6373cd4b7ad0b76e520a1afb70887e))
* `FIX`: apply data store behavior in collaboration only ([`5cc28d5d`](https://github.com/bpmn-io/bpmn-js/commit/5cc28d5d5571287a798b189aed75095f1fd0189e))
* `FIX`: create/update labels when updating element name via `Modeling#updateProperties` ([`4a0f6da8`](https://github.com/bpmn-io/bpmn-js/commit/4a0f6da814c45268e8a324e73a53479bd2435bbe))
## 2.1.0
* `FEAT`: support specifying `lineHeight` for text rendering ([#256](https://github.com/bpmn-io/diagram-js/pull/256))
* `FEAT`: `bpmn:LaneSet` elements get an ID assigned on creation
* `FEAT`: external labels can be deleted, clearing the elements name ([#791](https://github.com/bpmn-io/bpmn-js/pull/791))
* `FEAT`: add ability to override default element colors ([#713](https://github.com/bpmn-io/bpmn-js/issues/713))
* `FEAT`: add ability to override font family and size of rendered labels ([`4bb270f1`](https://github.com/bpmn-io/bpmn-js/commit/4bb270f19279db40f9cc3c179e09ee3a9a114e7c))
## 2.0.1
_Republish of `v2.0.0` due to registry error._
## 2.0.0
* `FEAT`: allow data store to be modeled between participants ([#483](https://github.com/bpmn-io/bpmn-js/issues/483))
* `CHORE`: update to [`diagram-js@2.0.0`](https://github.com/bpmn-io/diagram-js/blob/master/CHANGELOG.md#200)
* `FIX`: correctly handle missing `bpmndi:Label` bounds during model updating ([#794](https://github.com/bpmn-io/bpmn-js/issues/794))
### Breaking Changes
* The `PopupMenu` API got rewritten, cf. [`b1852e1d`](https://github.com/bpmn-io/diagram-js/pull/254/commits/b1852e1d71f67bd36ae1eb02748d2d0cbf124625)
## 1.3.3
* `CHORE`: update to [`bpmn-moddle@5.1.5`](https://github.com/bpmn-io/bpmn-moddle/blob/master/CHANGELOG.md#515)
## 1.3.2
* `FIX`: correctly serialize extension attributes on `bpmn:Expression`
## 1.3.1
* `FIX`: correctly auto-place from boundary events attached to host edges ([#788](https://github.com/bpmn-io/bpmn-js/issues/788))
## 1.3.0
* `FEAT`: expose additional `BpmnTreeWalker` APIs for advanced import use-cases
* `CHORE`: bump diagram-js and object-refs version
## 1.2.1
* `FIX`: correct side-effects config to not include `*.css` files
## 1.2.0
* `FEAT`: add initial snapping when creating associations
* `CHORE`: update to `diagram-js@1.3.0`
* `FIX`: allow message flows between collapsed pools
* `FIX`: complete direct editing on popup menu use
* `FIX`: focus label editing box on element creation
## 1.1.1
* `FIX`: escape `data-element-id` in CSS selectors
## 1.1.0
* `FEAT`: show gateway icon on context pad without marker ([`15dfab6b`](https://github.com/bpmn-io/bpmn-js/commit/15dfab6b5b12dd184acf070f2ab3ad205d1b245c))
## 1.0.4
* `FIX`: properly wire `$parent` on copy + paste
* `FIX`: improve boundary event rendering to correct SVG to image conversion
## 1.0.3
* `FIX`: re-expose `TestHelper#bootstrapBpmnJS` util
## 1.0.2
* `FIX`: correct library default export
## 1.0.1
_Republished 1.0.0 with CHANGELOG entries._
## 1.0.0
* `CHORE`: convert code base to ES modules
* `CHORE`: update utility toolbelt
### Breaking Changes
* You must now configure a module transpiler such as Babel or Webpack to handle ES module imports and exports.
## 0.31.0
* `FEAT`: encode entities in body properties during XML export
* `CHORE`: bump to [`bpmn-moddle@4.0.0`](https://github.com/bpmn-io/bpmn-moddle/releases/tag/v4.0.0)
* `CHORE`: bump utility version
## 0.30.0
* `CHORE`: bump to [`diagram-js@0.31.0`](https://github.com/bpmn-io/diagram-js/releases/tag/v0.31.0)
## ...
Check `git log` for earlier history.

View File

@@ -0,0 +1,23 @@
Copyright (c) 2014-present Camunda Services GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy,
modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The source code responsible for displaying the bpmn.io logo (two green cogwheels in
a box) that links back to http://bpmn.io as part of rendered diagrams MUST NOT be
removed or changed. When this software is being used in a website or application,
the logo must stay fully visible and not visually overlapped by other elements.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,92 @@
# bpmn-js - BPMN 2.0 for the web
[![Build Status](https://travis-ci.org/bpmn-io/bpmn-js.svg?branch=master)](https://travis-ci.org/bpmn-io/bpmn-js)
View and edit BPMN 2.0 diagrams in the browser.
[![bpmn-js screencast](./resources/screencast.gif "bpmn-js in action")](http://demo.bpmn.io/s/start)
## Installation
Use the library [pre-packaged](https://github.com/bpmn-io/bpmn-js-examples/tree/master/pre-packaged)
or include it [via npm](https://github.com/bpmn-io/bpmn-js-examples/tree/master/bundling)
into your node-style web-application.
## Usage
To get started, create a [bpmn-js](https://github.com/bpmn-io/bpmn-js) instance
and render [BPMN 2.0 diagrams](https://www.omg.org/spec/BPMN/2.0.2/) in the browser:
```javascript
var xml; // my BPMN 2.0 xml
var viewer = new BpmnJS({
container: 'body'
});
viewer.importXML(xml, function(err) {
if (err) {
console.log('error rendering', err);
} else {
console.log('rendered');
}
});
```
Checkout our [examples](https://github.com/bpmn-io/bpmn-js-examples) for many
more supported usage scenarios.
### Dynamic Attach/Detach
You may attach or detach the viewer dynamically to any element on the page, too:
```javascript
var viewer = new BpmnJS();
// attach it to some element
viewer.attachTo('#container');
// detach the panel
viewer.detach();
```
## Resources
* [Demo](http://demo.bpmn.io)
* [Issues](https://github.com/bpmn-io/bpmn-js/issues)
* [Examples](https://github.com/bpmn-io/bpmn-js-examples)
* [Forum](https://forum.bpmn.io)
* [Changelog](./CHANGELOG.md)
## Building the Project
Perform the following steps to build the library, including running all tests:
```
cd bpmn-js
npm install
npm run all
```
You may need to perform [additional project setup](./docs/project/SETUP.md) when
building the latest development snapshot.
Please checkout our [contributing guidelines](./.github/CONTRIBUTING.md) if you plan to
file an issue or pull request.
## Related
bpmn-js builds on top of a few additional powerful tools:
* [bpmn-moddle](https://github.com/bpmn-io/bpmn-moddle): Read / write support for BPMN 2.0 XML in the browsers
* [diagram-js](https://github.com/bpmn-io/diagram-js): Diagram rendering and editing toolkit
## License
Use under the terms of the [bpmn.io license](http://bpmn.io/license).

View File

@@ -0,0 +1,108 @@
.bpmn-icon-screw-wrench:before { content: '\e800'; } /* '' */
.bpmn-icon-trash:before { content: '\e801'; } /* '' */
.bpmn-icon-conditional-flow:before { content: '\e802'; } /* '' */
.bpmn-icon-default-flow:before { content: '\e803'; } /* '' */
.bpmn-icon-gateway-parallel:before { content: '\e804'; } /* '' */
.bpmn-icon-intermediate-event-catch-cancel:before { content: '\e805'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-message:before { content: '\e806'; } /* '' */
.bpmn-icon-start-event-compensation:before { content: '\e807'; } /* '' */
.bpmn-icon-start-event-non-interrupting-parallel-multiple:before { content: '\e808'; } /* '' */
.bpmn-icon-loop-marker:before { content: '\e809'; } /* '' */
.bpmn-icon-parallel-mi-marker:before { content: '\e80a'; } /* '' */
.bpmn-icon-start-event-non-interrupting-signal:before { content: '\e80b'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-timer:before { content: '\e80c'; } /* '' */
.bpmn-icon-intermediate-event-catch-parallel-multiple:before { content: '\e80d'; } /* '' */
.bpmn-icon-intermediate-event-catch-compensation:before { content: '\e80e'; } /* '' */
.bpmn-icon-gateway-xor:before { content: '\e80f'; } /* '' */
.bpmn-icon-connection:before { content: '\e810'; } /* '' */
.bpmn-icon-end-event-cancel:before { content: '\e811'; } /* '' */
.bpmn-icon-intermediate-event-catch-condition:before { content: '\e812'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-parallel-multiple:before { content: '\e813'; } /* '' */
.bpmn-icon-start-event-condition:before { content: '\e814'; } /* '' */
.bpmn-icon-start-event-non-interrupting-timer:before { content: '\e815'; } /* '' */
.bpmn-icon-sequential-mi-marker:before { content: '\e816'; } /* '' */
.bpmn-icon-user-task:before { content: '\e817'; } /* '' */
.bpmn-icon-business-rule:before { content: '\e818'; } /* '' */
.bpmn-icon-sub-process-marker:before { content: '\e819'; } /* '' */
.bpmn-icon-start-event-parallel-multiple:before { content: '\e81a'; } /* '' */
.bpmn-icon-start-event-error:before { content: '\e81b'; } /* '' */
.bpmn-icon-intermediate-event-catch-signal:before { content: '\e81c'; } /* '' */
.bpmn-icon-intermediate-event-catch-error:before { content: '\e81d'; } /* '' */
.bpmn-icon-end-event-compensation:before { content: '\e81e'; } /* '' */
.bpmn-icon-subprocess-collapsed:before { content: '\e81f'; } /* '' */
.bpmn-icon-subprocess-expanded:before { content: '\e820'; } /* '' */
.bpmn-icon-task:before { content: '\e821'; } /* '' */
.bpmn-icon-end-event-error:before { content: '\e822'; } /* '' */
.bpmn-icon-intermediate-event-catch-escalation:before { content: '\e823'; } /* '' */
.bpmn-icon-intermediate-event-catch-timer:before { content: '\e824'; } /* '' */
.bpmn-icon-start-event-escalation:before { content: '\e825'; } /* '' */
.bpmn-icon-start-event-signal:before { content: '\e826'; } /* '' */
.bpmn-icon-business-rule-task:before { content: '\e827'; } /* '' */
.bpmn-icon-manual:before { content: '\e828'; } /* '' */
.bpmn-icon-receive:before { content: '\e829'; } /* '' */
.bpmn-icon-call-activity:before { content: '\e82a'; } /* '' */
.bpmn-icon-start-event-timer:before { content: '\e82b'; } /* '' */
.bpmn-icon-start-event-message:before { content: '\e82c'; } /* '' */
.bpmn-icon-intermediate-event-none:before { content: '\e82d'; } /* '' */
.bpmn-icon-intermediate-event-catch-link:before { content: '\e82e'; } /* '' */
.bpmn-icon-end-event-escalation:before { content: '\e82f'; } /* '' */
.bpmn-icon-text-annotation:before { content: '\e830'; } /* '' */
.bpmn-icon-bpmn-io:before { content: '\e831'; } /* '' */
.bpmn-icon-gateway-complex:before { content: '\e832'; } /* '' */
.bpmn-icon-gateway-eventbased:before { content: '\e833'; } /* '' */
.bpmn-icon-gateway-none:before { content: '\e834'; } /* '' */
.bpmn-icon-gateway-or:before { content: '\e835'; } /* '' */
.bpmn-icon-end-event-terminate:before { content: '\e836'; } /* '' */
.bpmn-icon-end-event-signal:before { content: '\e837'; } /* '' */
.bpmn-icon-end-event-none:before { content: '\e838'; } /* '' */
.bpmn-icon-end-event-multiple:before { content: '\e839'; } /* '' */
.bpmn-icon-end-event-message:before { content: '\e83a'; } /* '' */
.bpmn-icon-end-event-link:before { content: '\e83b'; } /* '' */
.bpmn-icon-intermediate-event-catch-message:before { content: '\e83c'; } /* '' */
.bpmn-icon-intermediate-event-throw-compensation:before { content: '\e83d'; } /* '' */
.bpmn-icon-start-event-multiple:before { content: '\e83e'; } /* '' */
.bpmn-icon-script:before { content: '\e83f'; } /* '' */
.bpmn-icon-manual-task:before { content: '\e840'; } /* '' */
.bpmn-icon-send:before { content: '\e841'; } /* '' */
.bpmn-icon-service:before { content: '\e842'; } /* '' */
.bpmn-icon-receive-task:before { content: '\e843'; } /* '' */
.bpmn-icon-user:before { content: '\e844'; } /* '' */
.bpmn-icon-start-event-none:before { content: '\e845'; } /* '' */
.bpmn-icon-intermediate-event-throw-escalation:before { content: '\e846'; } /* '' */
.bpmn-icon-intermediate-event-catch-multiple:before { content: '\e847'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-escalation:before { content: '\e848'; } /* '' */
.bpmn-icon-intermediate-event-throw-link:before { content: '\e849'; } /* '' */
.bpmn-icon-start-event-non-interrupting-condition:before { content: '\e84a'; } /* '' */
.bpmn-icon-data-object:before { content: '\e84b'; } /* '' */
.bpmn-icon-script-task:before { content: '\e84c'; } /* '' */
.bpmn-icon-send-task:before { content: '\e84d'; } /* '' */
.bpmn-icon-data-store:before { content: '\e84e'; } /* '' */
.bpmn-icon-start-event-non-interrupting-escalation:before { content: '\e84f'; } /* '' */
.bpmn-icon-intermediate-event-throw-message:before { content: '\e850'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-multiple:before { content: '\e851'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-signal:before { content: '\e852'; } /* '' */
.bpmn-icon-intermediate-event-throw-multiple:before { content: '\e853'; } /* '' */
.bpmn-icon-start-event-non-interrupting-message:before { content: '\e854'; } /* '' */
.bpmn-icon-ad-hoc-marker:before { content: '\e855'; } /* '' */
.bpmn-icon-service-task:before { content: '\e856'; } /* '' */
.bpmn-icon-task-none:before { content: '\e857'; } /* '' */
.bpmn-icon-compensation-marker:before { content: '\e858'; } /* '' */
.bpmn-icon-start-event-non-interrupting-multiple:before { content: '\e859'; } /* '' */
.bpmn-icon-intermediate-event-throw-signal:before { content: '\e85a'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-condition:before { content: '\e85b'; } /* '' */
.bpmn-icon-participant:before { content: '\e85c'; } /* '' */
.bpmn-icon-event-subprocess-expanded:before { content: '\e85d'; } /* '' */
.bpmn-icon-lane-insert-below:before { content: '\e85e'; } /* '' */
.bpmn-icon-space-tool:before { content: '\e85f'; } /* '' */
.bpmn-icon-connection-multi:before { content: '\e860'; } /* '' */
.bpmn-icon-lane:before { content: '\e861'; } /* '' */
.bpmn-icon-lasso-tool:before { content: '\e862'; } /* '' */
.bpmn-icon-lane-insert-above:before { content: '\e863'; } /* '' */
.bpmn-icon-lane-divide-three:before { content: '\e864'; } /* '' */
.bpmn-icon-lane-divide-two:before { content: '\e865'; } /* '' */
.bpmn-icon-data-input:before { content: '\e866'; } /* '' */
.bpmn-icon-data-output:before { content: '\e867'; } /* '' */
.bpmn-icon-hand-tool:before { content: '\e868'; } /* '' */
.bpmn-icon-group:before { content: '\e869'; } /* '' */
.bpmn-icon-transaction:before { content: '\e8c4'; } /* '' */

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,164 @@
@font-face {
font-family: 'bpmn';
src: url('../font/bpmn.eot?14396105');
src: url('../font/bpmn.eot?14396105#iefix') format('embedded-opentype'),
url('../font/bpmn.woff2?14396105') format('woff2'),
url('../font/bpmn.woff?14396105') format('woff'),
url('../font/bpmn.ttf?14396105') format('truetype'),
url('../font/bpmn.svg?14396105#bpmn') format('svg');
font-weight: normal;
font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
@font-face {
font-family: 'bpmn';
src: url('../font/bpmn.svg?14396105#bpmn') format('svg');
}
}
*/
[class^="bpmn-icon-"]:before, [class*=" bpmn-icon-"]:before {
font-family: "bpmn";
font-style: normal;
font-weight: normal;
speak: none;
display: inline-block;
text-decoration: inherit;
width: 1em;
/* margin-right: .2em; */
text-align: center;
/* opacity: .8; */
/* For safety - reset parent styles, that can break glyph codes*/
font-variant: normal;
text-transform: none;
/* fix buttons height, for twitter bootstrap */
line-height: 1em;
/* Animation center compensation - margins should be symmetric */
/* remove if not needed */
/* margin-left: .2em; */
/* you can be more comfortable with increased icons size */
/* font-size: 120%; */
/* Font smoothing. That was taken from TWBS */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
/* Uncomment for 3D effect */
/* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
.bpmn-icon-screw-wrench:before { content: '\e800'; } /* '' */
.bpmn-icon-trash:before { content: '\e801'; } /* '' */
.bpmn-icon-conditional-flow:before { content: '\e802'; } /* '' */
.bpmn-icon-default-flow:before { content: '\e803'; } /* '' */
.bpmn-icon-gateway-parallel:before { content: '\e804'; } /* '' */
.bpmn-icon-intermediate-event-catch-cancel:before { content: '\e805'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-message:before { content: '\e806'; } /* '' */
.bpmn-icon-start-event-compensation:before { content: '\e807'; } /* '' */
.bpmn-icon-start-event-non-interrupting-parallel-multiple:before { content: '\e808'; } /* '' */
.bpmn-icon-loop-marker:before { content: '\e809'; } /* '' */
.bpmn-icon-parallel-mi-marker:before { content: '\e80a'; } /* '' */
.bpmn-icon-start-event-non-interrupting-signal:before { content: '\e80b'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-timer:before { content: '\e80c'; } /* '' */
.bpmn-icon-intermediate-event-catch-parallel-multiple:before { content: '\e80d'; } /* '' */
.bpmn-icon-intermediate-event-catch-compensation:before { content: '\e80e'; } /* '' */
.bpmn-icon-gateway-xor:before { content: '\e80f'; } /* '' */
.bpmn-icon-connection:before { content: '\e810'; } /* '' */
.bpmn-icon-end-event-cancel:before { content: '\e811'; } /* '' */
.bpmn-icon-intermediate-event-catch-condition:before { content: '\e812'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-parallel-multiple:before { content: '\e813'; } /* '' */
.bpmn-icon-start-event-condition:before { content: '\e814'; } /* '' */
.bpmn-icon-start-event-non-interrupting-timer:before { content: '\e815'; } /* '' */
.bpmn-icon-sequential-mi-marker:before { content: '\e816'; } /* '' */
.bpmn-icon-user-task:before { content: '\e817'; } /* '' */
.bpmn-icon-business-rule:before { content: '\e818'; } /* '' */
.bpmn-icon-sub-process-marker:before { content: '\e819'; } /* '' */
.bpmn-icon-start-event-parallel-multiple:before { content: '\e81a'; } /* '' */
.bpmn-icon-start-event-error:before { content: '\e81b'; } /* '' */
.bpmn-icon-intermediate-event-catch-signal:before { content: '\e81c'; } /* '' */
.bpmn-icon-intermediate-event-catch-error:before { content: '\e81d'; } /* '' */
.bpmn-icon-end-event-compensation:before { content: '\e81e'; } /* '' */
.bpmn-icon-subprocess-collapsed:before { content: '\e81f'; } /* '' */
.bpmn-icon-subprocess-expanded:before { content: '\e820'; } /* '' */
.bpmn-icon-task:before { content: '\e821'; } /* '' */
.bpmn-icon-end-event-error:before { content: '\e822'; } /* '' */
.bpmn-icon-intermediate-event-catch-escalation:before { content: '\e823'; } /* '' */
.bpmn-icon-intermediate-event-catch-timer:before { content: '\e824'; } /* '' */
.bpmn-icon-start-event-escalation:before { content: '\e825'; } /* '' */
.bpmn-icon-start-event-signal:before { content: '\e826'; } /* '' */
.bpmn-icon-business-rule-task:before { content: '\e827'; } /* '' */
.bpmn-icon-manual:before { content: '\e828'; } /* '' */
.bpmn-icon-receive:before { content: '\e829'; } /* '' */
.bpmn-icon-call-activity:before { content: '\e82a'; } /* '' */
.bpmn-icon-start-event-timer:before { content: '\e82b'; } /* '' */
.bpmn-icon-start-event-message:before { content: '\e82c'; } /* '' */
.bpmn-icon-intermediate-event-none:before { content: '\e82d'; } /* '' */
.bpmn-icon-intermediate-event-catch-link:before { content: '\e82e'; } /* '' */
.bpmn-icon-end-event-escalation:before { content: '\e82f'; } /* '' */
.bpmn-icon-text-annotation:before { content: '\e830'; } /* '' */
.bpmn-icon-bpmn-io:before { content: '\e831'; } /* '' */
.bpmn-icon-gateway-complex:before { content: '\e832'; } /* '' */
.bpmn-icon-gateway-eventbased:before { content: '\e833'; } /* '' */
.bpmn-icon-gateway-none:before { content: '\e834'; } /* '' */
.bpmn-icon-gateway-or:before { content: '\e835'; } /* '' */
.bpmn-icon-end-event-terminate:before { content: '\e836'; } /* '' */
.bpmn-icon-end-event-signal:before { content: '\e837'; } /* '' */
.bpmn-icon-end-event-none:before { content: '\e838'; } /* '' */
.bpmn-icon-end-event-multiple:before { content: '\e839'; } /* '' */
.bpmn-icon-end-event-message:before { content: '\e83a'; } /* '' */
.bpmn-icon-end-event-link:before { content: '\e83b'; } /* '' */
.bpmn-icon-intermediate-event-catch-message:before { content: '\e83c'; } /* '' */
.bpmn-icon-intermediate-event-throw-compensation:before { content: '\e83d'; } /* '' */
.bpmn-icon-start-event-multiple:before { content: '\e83e'; } /* '' */
.bpmn-icon-script:before { content: '\e83f'; } /* '' */
.bpmn-icon-manual-task:before { content: '\e840'; } /* '' */
.bpmn-icon-send:before { content: '\e841'; } /* '' */
.bpmn-icon-service:before { content: '\e842'; } /* '' */
.bpmn-icon-receive-task:before { content: '\e843'; } /* '' */
.bpmn-icon-user:before { content: '\e844'; } /* '' */
.bpmn-icon-start-event-none:before { content: '\e845'; } /* '' */
.bpmn-icon-intermediate-event-throw-escalation:before { content: '\e846'; } /* '' */
.bpmn-icon-intermediate-event-catch-multiple:before { content: '\e847'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-escalation:before { content: '\e848'; } /* '' */
.bpmn-icon-intermediate-event-throw-link:before { content: '\e849'; } /* '' */
.bpmn-icon-start-event-non-interrupting-condition:before { content: '\e84a'; } /* '' */
.bpmn-icon-data-object:before { content: '\e84b'; } /* '' */
.bpmn-icon-script-task:before { content: '\e84c'; } /* '' */
.bpmn-icon-send-task:before { content: '\e84d'; } /* '' */
.bpmn-icon-data-store:before { content: '\e84e'; } /* '' */
.bpmn-icon-start-event-non-interrupting-escalation:before { content: '\e84f'; } /* '' */
.bpmn-icon-intermediate-event-throw-message:before { content: '\e850'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-multiple:before { content: '\e851'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-signal:before { content: '\e852'; } /* '' */
.bpmn-icon-intermediate-event-throw-multiple:before { content: '\e853'; } /* '' */
.bpmn-icon-start-event-non-interrupting-message:before { content: '\e854'; } /* '' */
.bpmn-icon-ad-hoc-marker:before { content: '\e855'; } /* '' */
.bpmn-icon-service-task:before { content: '\e856'; } /* '' */
.bpmn-icon-task-none:before { content: '\e857'; } /* '' */
.bpmn-icon-compensation-marker:before { content: '\e858'; } /* '' */
.bpmn-icon-start-event-non-interrupting-multiple:before { content: '\e859'; } /* '' */
.bpmn-icon-intermediate-event-throw-signal:before { content: '\e85a'; } /* '' */
.bpmn-icon-intermediate-event-catch-non-interrupting-condition:before { content: '\e85b'; } /* '' */
.bpmn-icon-participant:before { content: '\e85c'; } /* '' */
.bpmn-icon-event-subprocess-expanded:before { content: '\e85d'; } /* '' */
.bpmn-icon-lane-insert-below:before { content: '\e85e'; } /* '' */
.bpmn-icon-space-tool:before { content: '\e85f'; } /* '' */
.bpmn-icon-connection-multi:before { content: '\e860'; } /* '' */
.bpmn-icon-lane:before { content: '\e861'; } /* '' */
.bpmn-icon-lasso-tool:before { content: '\e862'; } /* '' */
.bpmn-icon-lane-insert-above:before { content: '\e863'; } /* '' */
.bpmn-icon-lane-divide-three:before { content: '\e864'; } /* '' */
.bpmn-icon-lane-divide-two:before { content: '\e865'; } /* '' */
.bpmn-icon-data-input:before { content: '\e866'; } /* '' */
.bpmn-icon-data-output:before { content: '\e867'; } /* '' */
.bpmn-icon-hand-tool:before { content: '\e868'; } /* '' */
.bpmn-icon-group:before { content: '\e869'; } /* '' */
.bpmn-icon-transaction:before { content: '\e8c4'; } /* '' */

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -0,0 +1,713 @@
/**
* outline styles
*/
.djs-outline {
fill: none;
visibility: hidden;
}
.djs-element.hover .djs-outline,
.djs-element.selected .djs-outline {
visibility: visible;
shape-rendering: crispEdges;
stroke-dasharray: 3,3;
}
.djs-element.selected .djs-outline {
stroke: #8888FF;
stroke-width: 1px;
}
.djs-element.hover .djs-outline {
stroke: #FF8888;
stroke-width: 1px;
}
.djs-shape.connect-ok .djs-visual > :nth-child(1) {
fill: #DCFECC /* light-green */ !important;
}
.djs-shape.connect-not-ok .djs-visual > :nth-child(1),
.djs-shape.drop-not-ok .djs-visual > :nth-child(1) {
fill: #f9dee5 /* light-red */ !important;
}
.djs-shape.new-parent .djs-visual > :nth-child(1) {
fill: #F7F9FF !important;
}
svg.drop-not-ok {
background: #f9dee5 /* light-red */ !important;
}
svg.new-parent {
background: #F7F9FF /* light-blue */ !important;
}
.djs-connection.connect-ok .djs-visual > :nth-child(1),
.djs-connection.drop-ok .djs-visual > :nth-child(1) {
stroke: #90DD5F /* light-green */ !important;
}
.djs-connection.connect-not-ok .djs-visual > :nth-child(1),
.djs-connection.drop-not-ok .djs-visual > :nth-child(1) {
stroke: #E56283 /* light-red */ !important;
}
.drop-not-ok,
.connect-not-ok {
cursor: not-allowed;
}
.djs-element.attach-ok .djs-visual > :nth-child(1) {
stroke-width: 5px !important;
stroke: rgba(255, 116, 0, 0.7) !important;
}
.djs-frame.connect-not-ok .djs-visual > :nth-child(1),
.djs-frame.drop-not-ok .djs-visual > :nth-child(1) {
stroke-width: 3px !important;
stroke: #E56283 /* light-red */ !important;
fill: none !important;
}
/**
* Selection box style
*
*/
.djs-lasso-overlay {
fill: rgb(255, 116, 0);
fill-opacity: 0.1;
stroke-dasharray: 5 1 3 1;
stroke: rgb(255, 116, 0);
shape-rendering: crispEdges;
pointer-events: none;
}
/**
* Resize styles
*/
.djs-resize-overlay {
fill: none;
stroke-dasharray: 5 1 3 1;
stroke: rgb(255, 116, 0);
pointer-events: none;
}
.djs-resizer-hit {
fill: none;
pointer-events: all;
}
.djs-resizer-visual {
fill: white;
stroke-width: 1px;
stroke: black;
shape-rendering: crispEdges;
stroke-opacity: 0.2;
}
.djs-cursor-resize-nwse,
.djs-resizer-nw,
.djs-resizer-se {
cursor: nwse-resize;
}
.djs-cursor-resize-nesw,
.djs-resizer-ne,
.djs-resizer-sw {
cursor: nesw-resize;
}
.djs-shape.djs-resizing > .djs-outline {
visibility: hidden !important;
}
.djs-shape.djs-resizing > .djs-resizer {
visibility: hidden;
}
.djs-dragger > .djs-resizer {
visibility: hidden;
}
/**
* drag styles
*/
.djs-dragger * {
fill: none !important;
stroke: rgb(255, 116, 0) !important;
}
.djs-dragger tspan,
.djs-dragger text {
fill: rgb(255, 116, 0) !important;
stroke: none !important;
}
marker.djs-dragger circle,
marker.djs-dragger path,
marker.djs-dragger polygon,
marker.djs-dragger polyline,
marker.djs-dragger rect {
fill: rgb(255, 116, 0) !important;
stroke: none !important;
}
marker.djs-dragger text,
marker.djs-dragger tspan {
fill: none !important;
stroke: rgb(255, 116, 0) !important;
}
.djs-dragging {
opacity: 0.3;
}
.djs-dragging,
.djs-dragging > * {
pointer-events: none !important;
}
.djs-dragging .djs-context-pad,
.djs-dragging .djs-outline {
display: none !important;
}
/**
* no pointer events for visual
*/
.djs-visual,
.djs-outline {
pointer-events: none;
}
.djs-element.attach-ok .djs-hit {
stroke-width: 60px !important;
}
/**
* all pointer events for hit shape
*/
.djs-shape .djs-hit {
pointer-events: all;
}
.djs-connection .djs-hit {
pointer-events: stroke;
}
.djs-frame .djs-hit {
pointer-events: stroke;
}
/**
* shape / connection basic styles
*/
.djs-connection .djs-visual {
stroke-width: 2px;
fill: none;
}
.djs-cursor-grab {
cursor: -webkit-grab;
cursor: -moz-grab;
cursor: grab;
}
.djs-cursor-grabbing {
cursor: -webkit-grabbing;
cursor: -moz-grabbing;
cursor: grabbing;
}
.djs-cursor-crosshair {
cursor: crosshair;
}
.djs-cursor-move {
cursor: move;
}
.djs-cursor-resize-ns {
cursor: ns-resize;
}
.djs-cursor-resize-ew {
cursor: ew-resize;
}
/**
* snapping
*/
.djs-snap-line {
stroke: rgb(255, 195, 66);
stroke: rgba(255, 195, 66, 0.50);
stroke-linecap: round;
stroke-width: 2px;
pointer-events: none;
}
/**
* snapping
*/
.djs-crosshair {
stroke: #555;
stroke-linecap: round;
stroke-width: 1px;
pointer-events: none;
shape-rendering: crispEdges;
stroke-dasharray: 5, 5;
}
/**
* palette
*/
.djs-palette {
position: absolute;
left: 20px;
top: 20px;
box-sizing: border-box;
width: 48px;
}
.djs-palette .separator {
margin: 0 5px;
padding-top: 5px;
border: none;
border-bottom: solid 1px #DDD;
clear: both;
}
.djs-palette .entry:before {
vertical-align: text-bottom;
}
.djs-palette .djs-palette-toggle {
cursor: pointer;
}
.djs-palette .entry,
.djs-palette .djs-palette-toggle {
color: #333;
font-size: 30px;
text-align: center;
}
.djs-palette .entry {
float: left;
}
.djs-palette .entry img {
max-width: 100%;
}
.djs-palette .djs-palette-entries:after {
content: '';
display: table;
clear: both;
}
.djs-palette .djs-palette-toggle:hover {
background: #666;
}
.djs-palette .entry:hover {
color: rgb(255, 116, 0);
}
.djs-palette .highlighted-entry {
color: rgb(255, 116, 0) !important;
}
.djs-palette .entry,
.djs-palette .djs-palette-toggle {
width: 46px;
height: 46px;
line-height: 46px;
cursor: default;
}
/**
* Palette open / two-column layout is controlled via
* classes on the palette. Events to hook into palette
* changed life-cycle are available in addition.
*/
.djs-palette.two-column.open {
width: 94px;
}
.djs-palette:not(.open) .djs-palette-entries {
display: none;
}
.djs-palette:not(.open) {
overflow: hidden;
}
.djs-palette.open .djs-palette-toggle {
display: none;
}
/**
* context-pad
*/
.djs-overlay-context-pad {
width: 72px;
}
.djs-context-pad {
position: absolute;
display: none;
pointer-events: none;
}
.djs-context-pad .entry {
width: 22px;
height: 22px;
text-align: center;
display: inline-block;
font-size: 22px;
margin: 0 2px 2px 0;
border-radius: 3px;
cursor: default;
background-color: #FEFEFE;
box-shadow: 0 0 2px 1px #FEFEFE;
pointer-events: all;
}
.djs-context-pad .entry:before {
vertical-align: top;
}
.djs-context-pad .entry:hover {
background: rgb(255, 252, 176);
}
.djs-context-pad.open {
display: block;
}
/**
* popup styles
*/
.djs-popup .entry {
line-height: 20px;
white-space: nowrap;
cursor: default;
}
/* larger font for prefixed icons */
.djs-popup .entry:before {
vertical-align: middle;
font-size: 20px;
}
.djs-popup .entry > span {
vertical-align: middle;
font-size: 14px;
}
.djs-popup .entry:hover,
.djs-popup .entry.active:hover {
background: rgb(255, 252, 176);
}
.djs-popup .entry.disabled {
background: inherit;
}
.djs-popup .djs-popup-header .entry {
display: inline-block;
padding: 2px 3px 2px 3px;
border: solid 1px transparent;
border-radius: 3px;
}
.djs-popup .djs-popup-header .entry.active {
color: rgb(255, 116, 0);
border: solid 1px rgb(255, 116, 0);
background-color: #F6F6F6;
}
.djs-popup-body .entry {
padding: 4px 10px 4px 5px;
}
.djs-popup-body .entry > span {
margin-left: 5px;
}
.djs-popup-body {
background-color: #FEFEFE;
}
.djs-popup-header {
border-bottom: 1px solid #DDD;
}
.djs-popup-header .entry {
margin: 1px;
margin-left: 3px;
}
.djs-popup-header .entry:last-child {
margin-right: 3px;
}
/**
* popup / palette styles
*/
.djs-popup, .djs-palette {
background: #FAFAFA;
border: solid 1px #CCC;
border-radius: 2px;
}
/**
* touch
*/
.djs-shape,
.djs-connection {
touch-action: none;
}
.djs-segment-dragger,
.djs-bendpoint {
display: none;
}
/**
* bendpoints
*/
.djs-segment-dragger .djs-visual {
fill: rgba(255, 255, 121, 0.2);
stroke-width: 1px;
stroke-opacity: 1;
stroke: rgba(255, 255, 121, 0.3);
}
.djs-bendpoint .djs-visual {
fill: rgba(255, 255, 121, 0.8);
stroke-width: 1px;
stroke-opacity: 0.5;
stroke: black;
}
.djs-segment-dragger:hover,
.djs-bendpoints.hover .djs-segment-dragger,
.djs-bendpoints.selected .djs-segment-dragger,
.djs-bendpoint:hover,
.djs-bendpoints.hover .djs-bendpoint,
.djs-bendpoints.selected .djs-bendpoint {
display: block;
}
.djs-drag-active .djs-bendpoints * {
display: none;
}
.djs-bendpoints:not(.hover) .floating {
display: none;
}
.djs-segment-dragger:hover .djs-visual,
.djs-segment-dragger.djs-dragging .djs-visual,
.djs-bendpoint:hover .djs-visual,
.djs-bendpoint.floating .djs-visual {
fill: yellow;
stroke-opacity: 0.5;
stroke: black;
}
.djs-bendpoint.floating .djs-hit {
pointer-events: none;
}
.djs-segment-dragger .djs-hit,
.djs-bendpoint .djs-hit {
pointer-events: all;
fill: none;
}
.djs-segment-dragger.horizontal .djs-hit {
cursor: ns-resize;
}
.djs-segment-dragger.vertical .djs-hit {
cursor: ew-resize;
}
.djs-segment-dragger.djs-dragging .djs-hit {
pointer-events: none;
}
.djs-updating,
.djs-updating > * {
pointer-events: none !important;
}
.djs-updating .djs-context-pad,
.djs-updating .djs-outline,
.djs-updating .djs-bendpoint,
.connect-ok .djs-bendpoint,
.connect-not-ok .djs-bendpoint,
.drop-ok .djs-bendpoint,
.drop-not-ok .djs-bendpoint {
display: none !important;
}
.djs-segment-dragger.djs-dragging,
.djs-bendpoint.djs-dragging {
display: block;
opacity: 1.0;
}
.djs-segment-dragger.djs-dragging .djs-visual,
.djs-bendpoint.djs-dragging .djs-visual {
fill: yellow;
stroke-opacity: 0.5;
}
/**
* tooltips
*/
.djs-tooltip-error {
font-size: 11px;
line-height: 18px;
text-align: left;
padding: 5px;
opacity: 0.7;
}
.djs-tooltip-error > * {
width: 160px;
background: rgb(252, 236, 240);
color: rgb(158, 76, 76);
padding: 3px 7px;
border-radius: 5px;
border-left: solid 5px rgb(174, 73, 73);
}
.djs-tooltip-error:hover {
opacity: 1;
}
/**
* search pad
*/
.djs-search-container {
position: absolute;
top: 20px;
left: 0;
right: 0;
margin-left: auto;
margin-right: auto;
width: 25%;
min-width: 300px;
max-width: 400px;
z-index: 10;
font-size: 1.05em;
opacity: 0.9;
background: #FAFAFA;
border: solid 1px #CCC;
border-radius: 2px;
}
.djs-search-container:not(.open) {
display: none;
}
.djs-search-input input {
font-size: 1.05em;
width: 100%;
padding: 6px 10px;
border: 1px solid #ccc;
}
.djs-search-input input:focus {
outline: none;
border-color: #52B415;
}
.djs-search-results {
position: relative;
overflow-y: auto;
max-height: 200px;
}
.djs-search-results:hover {
/*background: #fffdd7;*/
cursor: pointer;
}
.djs-search-result {
width: 100%;
padding: 6px 10px;
background: white;
border-bottom: solid 1px #AAA;
border-radius: 1px;
}
.djs-search-highlight {
color: black;
}
.djs-search-result-primary {
margin: 0 0 10px;
}
.djs-search-result-secondary {
font-family: monospace;
margin: 0;
}
.djs-search-result:hover {
background: #fdffd6;
}
.djs-search-result-selected {
background: #fffcb0;
}
.djs-search-result-selected:hover {
background: #f7f388;
}
.djs-search-overlay {
background: yellow;
opacity: 0.3;
}
/**
* hidden styles
*/
.djs-element-hidden,
.djs-element-hidden .djs-hit,
.djs-element-hidden .djs-outline,
.djs-label-hidden .djs-label {
display: none !important;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
export {
default
} from './lib/Viewer';

View File

@@ -0,0 +1,243 @@
import inherits from 'inherits';
import Ids from 'ids';
import Viewer from './Viewer';
import NavigatedViewer from './NavigatedViewer';
import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import TouchModule from 'diagram-js/lib/navigation/touch';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
import AlignElementsModule from 'diagram-js/lib/features/align-elements';
import AutoPlaceModule from './features/auto-place';
import AutoResizeModule from './features/auto-resize';
import AutoScrollModule from 'diagram-js/lib/features/auto-scroll';
import BendpointsModule from 'diagram-js/lib/features/bendpoints';
import ConnectModule from 'diagram-js/lib/features/connect';
import ConnectionPreviewModule from 'diagram-js/lib/features/connection-preview';
import ContextPadModule from './features/context-pad';
import CopyPasteModule from './features/copy-paste';
import CreateModule from 'diagram-js/lib/features/create';
import DistributeElementsModule from './features/distribute-elements';
import EditorActionsModule from './features/editor-actions';
import GridSnappingModule from './features/grid-snapping';
import KeyboardModule from './features/keyboard';
import KeyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-selection';
import LabelEditingModule from './features/label-editing';
import ModelingModule from './features/modeling';
import MoveModule from 'diagram-js/lib/features/move';
import PaletteModule from './features/palette';
import ReplacePreviewModule from './features/replace-preview';
import ResizeModule from 'diagram-js/lib/features/resize';
import SnappingModule from './features/snapping';
import SearchModule from './features/search';
var initialDiagram =
'<?xml version="1.0" encoding="UTF-8"?>' +
'<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ' +
'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" ' +
'targetNamespace="http://bpmn.io/schema/bpmn" ' +
'id="Definitions_1">' +
'<bpmn:process id="Process_1" isExecutable="false">' +
'<bpmn:startEvent id="StartEvent_1"/>' +
'</bpmn:process>' +
'<bpmndi:BPMNDiagram id="BPMNDiagram_1">' +
'<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">' +
'<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">' +
'<dc:Bounds height="36.0" width="36.0" x="173.0" y="102.0"/>' +
'</bpmndi:BPMNShape>' +
'</bpmndi:BPMNPlane>' +
'</bpmndi:BPMNDiagram>' +
'</bpmn:definitions>';
/**
* A modeler for BPMN 2.0 diagrams.
*
*
* ## Extending the Modeler
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnModeler = new Modeler({ additionalModules: [ extensionModule ] });
* bpmnModeler.importXML(...);
* ```
*
*
* ## Customizing / Replacing Components
*
* You can replace individual diagram components by redefining them in override modules.
* This works for all components, including those defined in the core.
*
* Pass in override modules via the `options.additionalModules` flag like this:
*
* ```javascript
* function CustomContextPadProvider(contextPad) {
*
* contextPad.registerProvider(this);
*
* this.getContextPadEntries = function(element) {
* // no entries, effectively disable the context pad
* return {};
* };
* }
*
* CustomContextPadProvider.$inject = [ 'contextPad' ];
*
* var overrideModule = {
* contextPadProvider: [ 'type', CustomContextPadProvider ]
* };
*
* var bpmnModeler = new Modeler({ additionalModules: [ overrideModule ]});
* ```
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {String|Number} [options.width] the width of the viewer
* @param {String|Number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
*/
export default function Modeler(options) {
Viewer.call(this, options);
// hook ID collection into the modeler
this.on('import.parse.complete', function(event) {
if (!event.error) {
this._collectIds(event.definitions, event.context);
}
}, this);
this.on('diagram.destroy', function() {
this.get('moddle').ids.clear();
}, this);
}
inherits(Modeler, Viewer);
Modeler.Viewer = Viewer;
Modeler.NavigatedViewer = NavigatedViewer;
/**
* Create a new diagram to start modeling.
*
* @param {Function} [done]
*/
Modeler.prototype.createDiagram = function(done) {
return this.importXML(initialDiagram, done);
};
/**
* Create a moddle instance, attaching ids to it.
*
* @param {Object} options
*/
Modeler.prototype._createModdle = function(options) {
var moddle = Viewer.prototype._createModdle.call(this, options);
// attach ids to moddle to be able to track
// and validated ids in the BPMN 2.0 XML document
// tree
moddle.ids = new Ids([ 32, 36, 1 ]);
return moddle;
};
/**
* Collect ids processed during parsing of the
* definitions object.
*
* @param {ModdleElement} definitions
* @param {Context} context
*/
Modeler.prototype._collectIds = function(definitions, context) {
var moddle = definitions.$model,
ids = moddle.ids,
id;
// remove references from previous import
ids.clear();
for (id in context.elementsById) {
ids.claim(id, context.elementsById[id]);
}
};
Modeler.prototype._interactionModules = [
// non-modeling components
KeyboardMoveModule,
MoveCanvasModule,
TouchModule,
ZoomScrollModule
];
Modeler.prototype._modelingModules = [
// modeling components
AlignElementsModule,
AutoPlaceModule,
AutoScrollModule,
AutoResizeModule,
BendpointsModule,
ConnectModule,
ConnectionPreviewModule,
ContextPadModule,
CopyPasteModule,
CreateModule,
DistributeElementsModule,
EditorActionsModule,
GridSnappingModule,
KeyboardModule,
KeyboardMoveSelectionModule,
LabelEditingModule,
ModelingModule,
MoveModule,
PaletteModule,
ReplacePreviewModule,
ResizeModule,
SnappingModule,
SearchModule
];
// modules the modeler is composed of
//
// - viewer modules
// - interaction modules
// - modeling modules
Modeler.prototype._modules = [].concat(
Modeler.prototype._modules,
Modeler.prototype._interactionModules,
Modeler.prototype._modelingModules);

View File

@@ -0,0 +1,28 @@
import inherits from 'inherits';
import Viewer from './Viewer';
import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
/**
* A viewer that includes mouse navigation facilities
*
* @param {Object} options
*/
export default function NavigatedViewer(options) {
Viewer.call(this, options);
}
inherits(NavigatedViewer, Viewer);
NavigatedViewer.prototype._navigationModules = [
KeyboardMoveModule,
MoveCanvasModule,
ZoomScrollModule
];
NavigatedViewer.prototype._modules = [].concat(
NavigatedViewer.prototype._modules,
NavigatedViewer.prototype._navigationModules);

View File

@@ -0,0 +1,685 @@
/**
* The code in the <project-logo></project-logo> area
* must not be changed.
*
* @see http://bpmn.io/license for more information.
*/
import {
assign,
find,
isFunction,
isNumber,
omit
} from 'min-dash';
import {
domify,
query as domQuery,
remove as domRemove
} from 'min-dom';
import {
innerSVG
} from 'tiny-svg';
import Diagram from 'diagram-js';
import BpmnModdle from 'bpmn-moddle';
import inherits from 'inherits';
import {
importBpmnDiagram
} from './import/Importer';
import CoreModule from './core';
import TranslateModule from 'diagram-js/lib/i18n/translate';
import SelectionModule from 'diagram-js/lib/features/selection';
import OverlaysModule from 'diagram-js/lib/features/overlays';
function checkValidationError(err) {
// check if we can help the user by indicating wrong BPMN 2.0 xml
// (in case he or the exporting tool did not get that right)
var pattern = /unparsable content <([^>]+)> detected([\s\S]*)$/;
var match = pattern.exec(err.message);
if (match) {
err.message =
'unparsable content <' + match[1] + '> detected; ' +
'this may indicate an invalid BPMN 2.0 diagram file' + match[2];
}
return err;
}
var DEFAULT_OPTIONS = {
width: '100%',
height: '100%',
position: 'relative'
};
/**
* Ensure the passed argument is a proper unit (defaulting to px)
*/
function ensureUnit(val) {
return val + (isNumber(val) ? 'px' : '');
}
/**
* Find BPMNDiagram in definitions by ID
*
* @param {ModdleElement<Definitions>} definitions
* @param {String} diagramId
*
* @return {ModdleElement<BPMNDiagram>|null}
*/
function findBPMNDiagram(definitions, diagramId) {
if (!diagramId) {
return null;
}
return find(definitions.diagrams, function(element) {
return element.id === diagramId;
}) || null;
}
/**
* A viewer for BPMN 2.0 diagrams.
*
* Have a look at {@link NavigatedViewer} or {@link Modeler} for bundles that include
* additional features.
*
*
* ## Extending the Viewer
*
* In order to extend the viewer pass extension modules to bootstrap via the
* `additionalModules` option. An extension module is an object that exposes
* named services.
*
* The following example depicts the integration of a simple
* logging component that integrates with interaction events:
*
*
* ```javascript
*
* // logging component
* function InteractionLogger(eventBus) {
* eventBus.on('element.hover', function(event) {
* console.log()
* })
* }
*
* InteractionLogger.$inject = [ 'eventBus' ]; // minification save
*
* // extension module
* var extensionModule = {
* __init__: [ 'interactionLogger' ],
* interactionLogger: [ 'type', InteractionLogger ]
* };
*
* // extend the viewer
* var bpmnViewer = new Viewer({ additionalModules: [ extensionModule ] });
* bpmnViewer.importXML(...);
* ```
*
* @param {Object} [options] configuration options to pass to the viewer
* @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
* @param {String|Number} [options.width] the width of the viewer
* @param {String|Number} [options.height] the height of the viewer
* @param {Object} [options.moddleExtensions] extension packages to provide
* @param {Array<didi.Module>} [options.modules] a list of modules to override the default modules
* @param {Array<didi.Module>} [options.additionalModules] a list of modules to use with the default modules
*/
export default function Viewer(options) {
options = assign({}, DEFAULT_OPTIONS, options);
this._moddle = this._createModdle(options);
this._container = this._createContainer(options);
/* <project-logo> */
addProjectLogo(this._container);
/* </project-logo> */
this._init(this._container, this._moddle, options);
}
inherits(Viewer, Diagram);
/**
* Parse and render a BPMN 2.0 diagram.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During import the viewer will fire life-cycle events:
*
* * import.parse.start (about to read model from xml)
* * import.parse.complete (model read; may have worked or not)
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
* * import.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @param {String} xml the BPMN 2.0 xml
* @param {ModdleElement<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
* @param {Function} [done] invoked with (err, warnings=[])
*/
Viewer.prototype.importXML = function(xml, bpmnDiagram, done) {
if (isFunction(bpmnDiagram)) {
done = bpmnDiagram;
bpmnDiagram = null;
}
// done is optional
done = done || function() {};
var self = this;
// hook in pre-parse listeners +
// allow xml manipulation
xml = this._emit('import.parse.start', { xml: xml }) || xml;
this._moddle.fromXML(xml, 'bpmn:Definitions', function(err, definitions, context) {
// hook in post parse listeners +
// allow definitions manipulation
definitions = self._emit('import.parse.complete', {
error: err,
definitions: definitions,
context: context
}) || definitions;
var parseWarnings = context.warnings;
if (err) {
err = checkValidationError(err);
self._emit('import.done', { error: err, warnings: parseWarnings });
return done(err, parseWarnings);
}
self.importDefinitions(definitions, bpmnDiagram, function(err, importWarnings) {
var allWarnings = [].concat(parseWarnings, importWarnings || []);
self._emit('import.done', { error: err, warnings: allWarnings });
done(err, allWarnings);
});
});
};
/**
* Import parsed definitions and render a BPMN 2.0 diagram.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During import the viewer will fire life-cycle events:
*
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
*
* You can use these events to hook into the life-cycle.
*
* @param {ModdleElement<Definitions>} definitions parsed BPMN 2.0 definitions
* @param {ModdleElement<BPMNDiagram>|String} [bpmnDiagram] BPMN diagram or id of diagram to render (if not provided, the first one will be rendered)
* @param {Function} [done] invoked with (err, warnings=[])
*/
Viewer.prototype.importDefinitions = function(definitions, bpmnDiagram, done) {
if (isFunction(bpmnDiagram)) {
done = bpmnDiagram;
bpmnDiagram = null;
}
// done is optional
done = done || function() {};
this._setDefinitions(definitions);
return this.open(bpmnDiagram, done);
};
/**
* Open diagram of previously imported XML.
*
* Once finished the viewer reports back the result to the
* provided callback function with (err, warnings).
*
* ## Life-Cycle Events
*
* During switch the viewer will fire life-cycle events:
*
* * import.render.start (graphical import start)
* * import.render.complete (graphical import finished)
*
* You can use these events to hook into the life-cycle.
*
* @param {String|ModdleElement<BPMNDiagram>} [bpmnDiagramOrId] id or the diagram to open
* @param {Function} [done] invoked with (err, warnings=[])
*/
Viewer.prototype.open = function(bpmnDiagramOrId, done) {
if (isFunction(bpmnDiagramOrId)) {
done = bpmnDiagramOrId;
bpmnDiagramOrId = null;
}
var definitions = this._definitions;
var bpmnDiagram = bpmnDiagramOrId;
// done is optional
done = done || function() {};
if (!definitions) {
return done(new Error('no XML imported'));
}
if (typeof bpmnDiagramOrId === 'string') {
bpmnDiagram = findBPMNDiagram(definitions, bpmnDiagramOrId);
if (!bpmnDiagram) {
return done(new Error('BPMNDiagram <' + bpmnDiagramOrId + '> not found'));
}
}
// clear existing rendered diagram
// catch synchronous exceptions during #clear()
try {
this.clear();
} catch (error) {
return done(error);
}
// perform graphical import
return importBpmnDiagram(this, definitions, bpmnDiagram, done);
};
/**
* Export the currently displayed BPMN 2.0 diagram as
* a BPMN 2.0 XML document.
*
* ## Life-Cycle Events
*
* During XML saving the viewer will fire life-cycle events:
*
* * saveXML.start (before serialization)
* * saveXML.serialized (after xml generation)
* * saveXML.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @param {Object} [options] export options
* @param {Boolean} [options.format=false] output formated XML
* @param {Boolean} [options.preamble=true] output preamble
*
* @param {Function} done invoked with (err, xml)
*/
Viewer.prototype.saveXML = function(options, done) {
if (!done) {
done = options;
options = {};
}
var self = this;
var definitions = this._definitions;
if (!definitions) {
return done(new Error('no definitions loaded'));
}
// allow to fiddle around with definitions
definitions = this._emit('saveXML.start', {
definitions: definitions
}) || definitions;
this._moddle.toXML(definitions, options, function(err, xml) {
try {
xml = self._emit('saveXML.serialized', {
error: err,
xml: xml
}) || xml;
self._emit('saveXML.done', {
error: err,
xml: xml
});
} catch (e) {
console.error('error in saveXML life-cycle listener', e);
}
done(err, xml);
});
};
/**
* Export the currently displayed BPMN 2.0 diagram as
* an SVG image.
*
* ## Life-Cycle Events
*
* During SVG saving the viewer will fire life-cycle events:
*
* * saveSVG.start (before serialization)
* * saveSVG.done (everything done)
*
* You can use these events to hook into the life-cycle.
*
* @param {Object} [options]
* @param {Function} done invoked with (err, svgStr)
*/
Viewer.prototype.saveSVG = function(options, done) {
if (!done) {
done = options;
options = {};
}
this._emit('saveSVG.start');
var svg, err;
try {
var canvas = this.get('canvas');
var contentNode = canvas.getDefaultLayer(),
defsNode = domQuery('defs', canvas._svg);
var contents = innerSVG(contentNode),
defs = defsNode ? '<defs>' + innerSVG(defsNode) + '</defs>' : '';
var bbox = contentNode.getBBox();
svg =
'<?xml version="1.0" encoding="utf-8"?>\n' +
'<!-- created with bpmn-js / http://bpmn.io -->\n' +
'<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n' +
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" ' +
'width="' + bbox.width + '" height="' + bbox.height + '" ' +
'viewBox="' + bbox.x + ' ' + bbox.y + ' ' + bbox.width + ' ' + bbox.height + '" version="1.1">' +
defs + contents +
'</svg>';
} catch (e) {
err = e;
}
this._emit('saveSVG.done', {
error: err,
svg: svg
});
done(err, svg);
};
/**
* Get a named diagram service.
*
* @example
*
* var elementRegistry = viewer.get('elementRegistry');
* var startEventShape = elementRegistry.get('StartEvent_1');
*
* @param {String} name
*
* @return {Object} diagram service instance
*
* @method Viewer#get
*/
/**
* Invoke a function in the context of this viewer.
*
* @example
*
* viewer.invoke(function(elementRegistry) {
* var startEventShape = elementRegistry.get('StartEvent_1');
* });
*
* @param {Function} fn to be invoked
*
* @return {Object} the functions return value
*
* @method Viewer#invoke
*/
Viewer.prototype._setDefinitions = function(definitions) {
this._definitions = definitions;
};
Viewer.prototype.getModules = function() {
return this._modules;
};
/**
* Remove all drawn elements from the viewer.
*
* After calling this method the viewer can still
* be reused for opening another diagram.
*
* @method Viewer#clear
*/
Viewer.prototype.clear = function() {
// remove businessObject#di binding
//
// this is necessary, as we establish the bindings
// in the BpmnTreeWalker (and assume none are given
// on reimport)
this.get('elementRegistry').forEach(function(element) {
var bo = element.businessObject;
if (bo && bo.di) {
delete bo.di;
}
});
// remove drawn elements
Diagram.prototype.clear.call(this);
};
/**
* Destroy the viewer instance and remove all its
* remainders from the document tree.
*/
Viewer.prototype.destroy = function() {
// diagram destroy
Diagram.prototype.destroy.call(this);
// dom detach
domRemove(this._container);
};
/**
* Register an event listener
*
* Remove a previously added listener via {@link #off(event, callback)}.
*
* @param {String} event
* @param {Number} [priority]
* @param {Function} callback
* @param {Object} [that]
*/
Viewer.prototype.on = function(event, priority, callback, target) {
return this.get('eventBus').on(event, priority, callback, target);
};
/**
* De-register an event listener
*
* @param {String} event
* @param {Function} callback
*/
Viewer.prototype.off = function(event, callback) {
this.get('eventBus').off(event, callback);
};
Viewer.prototype.attachTo = function(parentNode) {
if (!parentNode) {
throw new Error('parentNode required');
}
// ensure we detach from the
// previous, old parent
this.detach();
// unwrap jQuery if provided
if (parentNode.get && parentNode.constructor.prototype.jquery) {
parentNode = parentNode.get(0);
}
if (typeof parentNode === 'string') {
parentNode = domQuery(parentNode);
}
parentNode.appendChild(this._container);
this._emit('attach', {});
this.get('canvas').resized();
};
Viewer.prototype.getDefinitions = function() {
return this._definitions;
};
Viewer.prototype.detach = function() {
var container = this._container,
parentNode = container.parentNode;
if (!parentNode) {
return;
}
this._emit('detach', {});
parentNode.removeChild(container);
};
Viewer.prototype._init = function(container, moddle, options) {
var baseModules = options.modules || this.getModules(),
additionalModules = options.additionalModules || [],
staticModules = [
{
bpmnjs: [ 'value', this ],
moddle: [ 'value', moddle ]
}
];
var diagramModules = [].concat(staticModules, baseModules, additionalModules);
var diagramOptions = assign(omit(options, [ 'additionalModules' ]), {
canvas: assign({}, options.canvas, { container: container }),
modules: diagramModules
});
// invoke diagram constructor
Diagram.call(this, diagramOptions);
if (options && options.container) {
this.attachTo(options.container);
}
};
/**
* Emit an event on the underlying {@link EventBus}
*
* @param {String} type
* @param {Object} event
*
* @return {Object} event processing result (if any)
*/
Viewer.prototype._emit = function(type, event) {
return this.get('eventBus').fire(type, event);
};
Viewer.prototype._createContainer = function(options) {
var container = domify('<div class="bjs-container"></div>');
assign(container.style, {
width: ensureUnit(options.width),
height: ensureUnit(options.height),
position: options.position
});
return container;
};
Viewer.prototype._createModdle = function(options) {
var moddleOptions = assign({}, this._moddleExtensions, options.moddleExtensions);
return new BpmnModdle(moddleOptions);
};
// modules the viewer is composed of
Viewer.prototype._modules = [
CoreModule,
TranslateModule,
SelectionModule,
OverlaysModule
];
// default moddle extensions the viewer is composed of
Viewer.prototype._moddleExtensions = {};
/* <project-logo> */
import {
open as openPoweredBy,
BPMNIO_IMG
} from './util/PoweredByUtil';
import {
event as domEvent
} from 'min-dom';
/**
* Adds the project logo to the diagram container as
* required by the bpmn.io license.
*
* @see http://bpmn.io/license
*
* @param {Element} container
*/
function addProjectLogo(container) {
var img = BPMNIO_IMG;
var linkMarkup =
'';
var linkElement = domify(linkMarkup);
container.appendChild(linkElement);
domEvent.bind(linkElement, 'click', function(event) {
openPoweredBy();
event.preventDefault();
});
}
/* </project-logo> */

View File

@@ -0,0 +1,9 @@
import DrawModule from '../draw';
import ImportModule from '../import';
export default {
__depends__: [
DrawModule,
ImportModule
]
};

View File

@@ -0,0 +1,142 @@
import {
every,
some
} from 'min-dash';
import {
componentsToPath
} from 'diagram-js/lib/util/RenderUtil';
// element utils //////////////////////
/**
* Checks if eventDefinition of the given element matches with semantic type.
*
* @return {boolean} true if element is of the given semantic type
*/
export function isTypedEvent(event, eventDefinitionType, filter) {
function matches(definition, filter) {
return every(filter, function(val, key) {
// we want a == conversion here, to be able to catch
// undefined == false and friends
/* jshint -W116 */
return definition[key] == val;
});
}
return some(event.eventDefinitions, function(definition) {
return definition.$type === eventDefinitionType && matches(event, filter);
});
}
export function isThrowEvent(event) {
return (event.$type === 'bpmn:IntermediateThrowEvent') || (event.$type === 'bpmn:EndEvent');
}
export function isCollection(element) {
var dataObject = element.dataObjectRef;
return element.isCollection || (dataObject && dataObject.isCollection);
}
export function getDi(element) {
return element.businessObject.di;
}
export function getSemantic(element) {
return element.businessObject;
}
// color access //////////////////////
export function getFillColor(element, defaultColor) {
return getDi(element).get('bioc:fill') || defaultColor || 'white';
}
export function getStrokeColor(element, defaultColor) {
return getDi(element).get('bioc:stroke') || defaultColor || 'black';
}
// cropping path customizations //////////////////////
export function getCirclePath(shape) {
var cx = shape.x + shape.width / 2,
cy = shape.y + shape.height / 2,
radius = shape.width / 2;
var circlePath = [
['M', cx, cy],
['m', 0, -radius],
['a', radius, radius, 0, 1, 1, 0, 2 * radius],
['a', radius, radius, 0, 1, 1, 0, -2 * radius],
['z']
];
return componentsToPath(circlePath);
}
export function getRoundRectPath(shape, borderRadius) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var roundRectPath = [
['M', x + borderRadius, y],
['l', width - borderRadius * 2, 0],
['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius],
['l', 0, height - borderRadius * 2],
['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius],
['l', borderRadius * 2 - width, 0],
['a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius],
['l', 0, borderRadius * 2 - height],
['a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius],
['z']
];
return componentsToPath(roundRectPath);
}
export function getDiamondPath(shape) {
var width = shape.width,
height = shape.height,
x = shape.x,
y = shape.y,
halfWidth = width / 2,
halfHeight = height / 2;
var diamondPath = [
['M', x + halfWidth, y],
['l', halfWidth, halfHeight],
['l', -halfWidth, halfHeight],
['l', -halfWidth, -halfHeight],
['z']
];
return componentsToPath(diamondPath);
}
export function getRectPath(shape) {
var x = shape.x,
y = shape.y,
width = shape.width,
height = shape.height;
var rectPath = [
['M', x, y],
['l', width, 0],
['l', 0, height],
['l', -width, 0],
['z']
];
return componentsToPath(rectPath);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,474 @@
/**
* Map containing SVG paths needed by BpmnRenderer.
*/
export default function PathMap() {
/**
* Contains a map of path elements
*
* <h1>Path definition</h1>
* A parameterized path is defined like this:
* <pre>
* 'GATEWAY_PARALLEL': {
* d: 'm {mx},{my} {e.x0},0 0,{e.x1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
'-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
* height: 17.5,
* width: 17.5,
* heightElements: [2.5, 7.5],
* widthElements: [2.5, 7.5]
* }
* </pre>
* <p>It's important to specify a correct <b>height and width</b> for the path as the scaling
* is based on the ratio between the specified height and width in this object and the
* height and width that is set as scale target (Note x,y coordinates will be scaled with
* individual ratios).</p>
* <p>The '<b>heightElements</b>' and '<b>widthElements</b>' array must contain the values that will be scaled.
* The scaling is based on the computed ratios.
* Coordinates on the y axis should be in the <b>heightElement</b>'s array, they will be scaled using
* the computed ratio coefficient.
* In the parameterized path the scaled values can be accessed through the 'e' object in {} brackets.
* <ul>
* <li>The values for the y axis can be accessed in the path string using {e.y0}, {e.y1}, ....</li>
* <li>The values for the x axis can be accessed in the path string using {e.x0}, {e.x1}, ....</li>
* </ul>
* The numbers x0, x1 respectively y0, y1, ... map to the corresponding array index.
* </p>
*/
this.pathMap = {
'EVENT_MESSAGE': {
d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
height: 36,
width: 36,
heightElements: [6, 14],
widthElements: [10.5, 21]
},
'EVENT_SIGNAL': {
d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x1},0 Z',
height: 36,
width: 36,
heightElements: [18],
widthElements: [10, 20]
},
'EVENT_ESCALATION': {
d: 'M {mx},{my} l {e.x0},{e.y0} l -{e.x0},-{e.y1} l -{e.x0},{e.y1} Z',
height: 36,
width: 36,
heightElements: [20, 7],
widthElements: [8]
},
'EVENT_CONDITIONAL': {
d: 'M {e.x0},{e.y0} l {e.x1},0 l 0,{e.y2} l -{e.x1},0 Z ' +
'M {e.x2},{e.y3} l {e.x0},0 ' +
'M {e.x2},{e.y4} l {e.x0},0 ' +
'M {e.x2},{e.y5} l {e.x0},0 ' +
'M {e.x2},{e.y6} l {e.x0},0 ' +
'M {e.x2},{e.y7} l {e.x0},0 ' +
'M {e.x2},{e.y8} l {e.x0},0 ',
height: 36,
width: 36,
heightElements: [8.5, 14.5, 18, 11.5, 14.5, 17.5, 20.5, 23.5, 26.5],
widthElements: [10.5, 14.5, 12.5]
},
'EVENT_LINK': {
d: 'm {mx},{my} 0,{e.y0} -{e.x1},0 0,{e.y1} {e.x1},0 0,{e.y0} {e.x0},-{e.y2} -{e.x0},-{e.y2} z',
height: 36,
width: 36,
heightElements: [4.4375, 6.75, 7.8125],
widthElements: [9.84375, 13.5]
},
'EVENT_ERROR': {
d: 'm {mx},{my} {e.x0},-{e.y0} {e.x1},-{e.y1} {e.x2},{e.y2} {e.x3},-{e.y3} -{e.x4},{e.y4} -{e.x5},-{e.y5} z',
height: 36,
width: 36,
heightElements: [0.023, 8.737, 8.151, 16.564, 10.591, 8.714],
widthElements: [0.085, 6.672, 6.97, 4.273, 5.337, 6.636]
},
'EVENT_CANCEL_45': {
d: 'm {mx},{my} -{e.x1},0 0,{e.x0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
'0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
height: 36,
width: 36,
heightElements: [4.75, 8.5],
widthElements: [4.75, 8.5]
},
'EVENT_COMPENSATION': {
d: 'm {mx},{my} {e.x0},-{e.y0} 0,{e.y1} z m {e.x1},-{e.y2} {e.x2},-{e.y3} 0,{e.y1} -{e.x2},-{e.y3} z',
height: 36,
width: 36,
heightElements: [6.5, 13, 0.4, 6.1],
widthElements: [9, 9.3, 8.7]
},
'EVENT_TIMER_WH': {
d: 'M {mx},{my} l {e.x0},-{e.y0} m -{e.x0},{e.y0} l {e.x1},{e.y1} ',
height: 36,
width: 36,
heightElements: [10, 2],
widthElements: [3, 7]
},
'EVENT_TIMER_LINE': {
d: 'M {mx},{my} ' +
'm {e.x0},{e.y0} l -{e.x1},{e.y1} ',
height: 36,
width: 36,
heightElements: [10, 3],
widthElements: [0, 0]
},
'EVENT_MULTIPLE': {
d:'m {mx},{my} {e.x1},-{e.y0} {e.x1},{e.y0} -{e.x0},{e.y1} -{e.x2},0 z',
height: 36,
width: 36,
heightElements: [6.28099, 12.56199],
widthElements: [3.1405, 9.42149, 12.56198]
},
'EVENT_PARALLEL_MULTIPLE': {
d:'m {mx},{my} {e.x0},0 0,{e.y1} {e.x1},0 0,{e.y0} -{e.x1},0 0,{e.y1} ' +
'-{e.x0},0 0,-{e.y1} -{e.x1},0 0,-{e.y0} {e.x1},0 z',
height: 36,
width: 36,
heightElements: [2.56228, 7.68683],
widthElements: [2.56228, 7.68683]
},
'GATEWAY_EXCLUSIVE': {
d:'m {mx},{my} {e.x0},{e.y0} {e.x1},{e.y0} {e.x2},0 {e.x4},{e.y2} ' +
'{e.x4},{e.y1} {e.x2},0 {e.x1},{e.y3} {e.x0},{e.y3} ' +
'{e.x3},0 {e.x5},{e.y1} {e.x5},{e.y2} {e.x3},0 z',
height: 17.5,
width: 17.5,
heightElements: [8.5, 6.5312, -6.5312, -8.5],
widthElements: [6.5, -6.5, 3, -3, 5, -5]
},
'GATEWAY_PARALLEL': {
d:'m {mx},{my} 0,{e.y1} -{e.x1},0 0,{e.y0} {e.x1},0 0,{e.y1} {e.x0},0 ' +
'0,-{e.y1} {e.x1},0 0,-{e.y0} -{e.x1},0 0,-{e.y1} -{e.x0},0 z',
height: 30,
width: 30,
heightElements: [5, 12.5],
widthElements: [5, 12.5]
},
'GATEWAY_EVENT_BASED': {
d:'m {mx},{my} {e.x0},{e.y0} {e.x0},{e.y1} {e.x1},{e.y2} {e.x2},0 z',
height: 11,
width: 11,
heightElements: [-6, 6, 12, -12],
widthElements: [9, -3, -12]
},
'GATEWAY_COMPLEX': {
d:'m {mx},{my} 0,{e.y0} -{e.x0},-{e.y1} -{e.x1},{e.y2} {e.x0},{e.y1} -{e.x2},0 0,{e.y3} ' +
'{e.x2},0 -{e.x0},{e.y1} l {e.x1},{e.y2} {e.x0},-{e.y1} 0,{e.y0} {e.x3},0 0,-{e.y0} {e.x0},{e.y1} ' +
'{e.x1},-{e.y2} -{e.x0},-{e.y1} {e.x2},0 0,-{e.y3} -{e.x2},0 {e.x0},-{e.y1} -{e.x1},-{e.y2} ' +
'-{e.x0},{e.y1} 0,-{e.y0} -{e.x3},0 z',
height: 17.125,
width: 17.125,
heightElements: [4.875, 3.4375, 2.125, 3],
widthElements: [3.4375, 2.125, 4.875, 3]
},
'DATA_OBJECT_PATH': {
d:'m 0,0 {e.x1},0 {e.x0},{e.y0} 0,{e.y1} -{e.x2},0 0,-{e.y2} {e.x1},0 0,{e.y0} {e.x0},0',
height: 61,
width: 51,
heightElements: [10, 50, 60],
widthElements: [10, 40, 50, 60]
},
'DATA_OBJECT_COLLECTION_PATH': {
d:'m {mx}, {my} ' +
'm 0 15 l 0 -15 ' +
'm 4 15 l 0 -15 ' +
'm 4 15 l 0 -15 ',
height: 61,
width: 51,
heightElements: [12],
widthElements: [1, 6, 12, 15]
},
'DATA_ARROW': {
d:'m 5,9 9,0 0,-3 5,5 -5,5 0,-3 -9,0 z',
height: 61,
width: 51,
heightElements: [],
widthElements: []
},
'DATA_STORE': {
d:'m {mx},{my} ' +
'l 0,{e.y2} ' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
'l 0,-{e.y2} ' +
'c -{e.x0},-{e.y1} -{e.x1},-{e.y1} -{e.x2},0' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0 ' +
'm -{e.x2},{e.y0}' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0' +
'm -{e.x2},{e.y0}' +
'c {e.x0},{e.y1} {e.x1},{e.y1} {e.x2},0',
height: 61,
width: 61,
heightElements: [7, 10, 45],
widthElements: [2, 58, 60]
},
'TEXT_ANNOTATION': {
d: 'm {mx}, {my} m 10,0 l -10,0 l 0,{e.y0} l 10,0',
height: 30,
width: 10,
heightElements: [30],
widthElements: [10]
},
'MARKER_SUB_PROCESS': {
d: 'm{mx},{my} m 7,2 l 0,10 m -5,-5 l 10,0',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_PARALLEL': {
d: 'm{mx},{my} m 3,2 l 0,10 m 3,-10 l 0,10 m 3,-10 l 0,10',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_SEQUENTIAL': {
d: 'm{mx},{my} m 0,3 l 10,0 m -10,3 l 10,0 m -10,3 l 10,0',
height: 10,
width: 10,
heightElements: [],
widthElements: []
},
'MARKER_COMPENSATION': {
d: 'm {mx},{my} 7,-5 0,10 z m 7.1,-0.3 6.9,-4.7 0,10 -6.9,-4.7 z',
height: 10,
width: 21,
heightElements: [],
widthElements: []
},
'MARKER_LOOP': {
d: 'm {mx},{my} c 3.526979,0 6.386161,-2.829858 6.386161,-6.320661 0,-3.490806 -2.859182,-6.320661 ' +
'-6.386161,-6.320661 -3.526978,0 -6.38616,2.829855 -6.38616,6.320661 0,1.745402 ' +
'0.714797,3.325567 1.870463,4.469381 0.577834,0.571908 1.265885,1.034728 2.029916,1.35457 ' +
'l -0.718163,-3.909793 m 0.718163,3.909793 -3.885211,0.802902',
height: 13.9,
width: 13.7,
heightElements: [],
widthElements: []
},
'MARKER_ADHOC': {
d: 'm {mx},{my} m 0.84461,2.64411 c 1.05533,-1.23780996 2.64337,-2.07882 4.29653,-1.97997996 2.05163,0.0805 ' +
'3.85579,1.15803 5.76082,1.79107 1.06385,0.34139996 2.24454,0.1438 3.18759,-0.43767 0.61743,-0.33642 ' +
'1.2775,-0.64078 1.7542,-1.17511 0,0.56023 0,1.12046 0,1.6807 -0.98706,0.96237996 -2.29792,1.62393996 ' +
'-3.6918,1.66181996 -1.24459,0.0927 -2.46671,-0.2491 -3.59505,-0.74812 -1.35789,-0.55965 ' +
'-2.75133,-1.33436996 -4.27027,-1.18121996 -1.37741,0.14601 -2.41842,1.13685996 -3.44288,1.96782996 z',
height: 4,
width: 15,
heightElements: [],
widthElements: []
},
'TASK_TYPE_SEND': {
d: 'm {mx},{my} l 0,{e.y1} l {e.x1},0 l 0,-{e.y1} z l {e.x0},{e.y0} l {e.x0},-{e.y0}',
height: 14,
width: 21,
heightElements: [6, 14],
widthElements: [10.5, 21]
},
'TASK_TYPE_SCRIPT': {
d: 'm {mx},{my} c 9.966553,-6.27276 -8.000926,-7.91932 2.968968,-14.938 l -8.802728,0 ' +
'c -10.969894,7.01868 6.997585,8.66524 -2.968967,14.938 z ' +
'm -7,-12 l 5,0 ' +
'm -4.5,3 l 4.5,0 ' +
'm -3,3 l 5,0' +
'm -4,3 l 5,0',
height: 15,
width: 12.6,
heightElements: [6, 14],
widthElements: [10.5, 21]
},
'TASK_TYPE_USER_1': {
d: 'm {mx},{my} c 0.909,-0.845 1.594,-2.049 1.594,-3.385 0,-2.554 -1.805,-4.62199999 ' +
'-4.357,-4.62199999 -2.55199998,0 -4.28799998,2.06799999 -4.28799998,4.62199999 0,1.348 ' +
'0.974,2.562 1.89599998,3.405 -0.52899998,0.187 -5.669,2.097 -5.794,4.7560005 v 6.718 ' +
'h 17 v -6.718 c 0,-2.2980005 -5.5279996,-4.5950005 -6.0509996,-4.7760005 z' +
'm -8,6 l 0,5.5 m 11,0 l 0,-5'
},
'TASK_TYPE_USER_2': {
d: 'm {mx},{my} m 2.162,1.009 c 0,2.4470005 -2.158,4.4310005 -4.821,4.4310005 ' +
'-2.66499998,0 -4.822,-1.981 -4.822,-4.4310005 '
},
'TASK_TYPE_USER_3': {
d: 'm {mx},{my} m -6.9,-3.80 c 0,0 2.25099998,-2.358 4.27399998,-1.177 2.024,1.181 4.221,1.537 ' +
'4.124,0.965 -0.098,-0.57 -0.117,-3.79099999 -4.191,-4.13599999 -3.57499998,0.001 ' +
'-4.20799998,3.36699999 -4.20699998,4.34799999 z'
},
'TASK_TYPE_MANUAL': {
d: 'm {mx},{my} c 0.234,-0.01 5.604,0.008 8.029,0.004 0.808,0 1.271,-0.172 1.417,-0.752 0.227,-0.898 ' +
'-0.334,-1.314 -1.338,-1.316 -2.467,-0.01 -7.886,-0.004 -8.108,-0.004 -0.014,-0.079 0.016,-0.533 0,-0.61 ' +
'0.195,-0.042 8.507,0.006 9.616,0.002 0.877,-0.007 1.35,-0.438 1.353,-1.208 0.003,-0.768 -0.479,-1.09 ' +
'-1.35,-1.091 -2.968,-0.002 -9.619,-0.013 -9.619,-0.013 v -0.591 c 0,0 5.052,-0.016 7.225,-0.016 ' +
'0.888,-0.002 1.354,-0.416 1.351,-1.193 -0.006,-0.761 -0.492,-1.196 -1.361,-1.196 -3.473,-0.005 ' +
'-10.86,-0.003 -11.0829995,-0.003 -0.022,-0.047 -0.045,-0.094 -0.069,-0.139 0.3939995,-0.319 ' +
'2.0409995,-1.626 2.4149995,-2.017 0.469,-0.4870005 0.519,-1.1650005 0.162,-1.6040005 -0.414,-0.511 ' +
'-0.973,-0.5 -1.48,-0.236 -1.4609995,0.764 -6.5999995,3.6430005 -7.7329995,4.2710005 -0.9,0.499 ' +
'-1.516,1.253 -1.882,2.19 -0.37000002,0.95 -0.17,2.01 -0.166,2.979 0.004,0.718 -0.27300002,1.345 ' +
'-0.055,2.063 0.629,2.087 2.425,3.312 4.859,3.318 4.6179995,0.014 9.2379995,-0.139 13.8569995,-0.158 ' +
'0.755,-0.004 1.171,-0.301 1.182,-1.033 0.012,-0.754 -0.423,-0.969 -1.183,-0.973 -1.778,-0.01 ' +
'-5.824,-0.004 -6.04,-0.004 10e-4,-0.084 0.003,-0.586 10e-4,-0.67 z'
},
'TASK_TYPE_INSTANTIATING_SEND': {
d: 'm {mx},{my} l 0,8.4 l 12.6,0 l 0,-8.4 z l 6.3,3.6 l 6.3,-3.6'
},
'TASK_TYPE_SERVICE': {
d: 'm {mx},{my} v -1.71335 c 0.352326,-0.0705 0.703932,-0.17838 1.047628,-0.32133 ' +
'0.344416,-0.14465 0.665822,-0.32133 0.966377,-0.52145 l 1.19431,1.18005 1.567487,-1.57688 ' +
'-1.195028,-1.18014 c 0.403376,-0.61394 0.683079,-1.29908 0.825447,-2.01824 l 1.622133,-0.01 ' +
'v -2.2196 l -1.636514,0.01 c -0.07333,-0.35153 -0.178319,-0.70024 -0.323564,-1.04372 ' +
'-0.145244,-0.34406 -0.321407,-0.6644 -0.522735,-0.96217 l 1.131035,-1.13631 -1.583305,-1.56293 ' +
'-1.129598,1.13589 c -0.614052,-0.40108 -1.302883,-0.68093 -2.022633,-0.82247 l 0.0093,-1.61852 ' +
'h -2.241173 l 0.0042,1.63124 c -0.353763,0.0736 -0.705369,0.17977 -1.049785,0.32371 -0.344415,0.14437 ' +
'-0.665102,0.32092 -0.9635006,0.52046 l -1.1698628,-1.15823 -1.5667691,1.5792 1.1684265,1.15669 ' +
'c -0.4026573,0.61283 -0.68308,1.29797 -0.8247287,2.01713 l -1.6588041,0.003 v 2.22174 ' +
'l 1.6724648,-0.006 c 0.073327,0.35077 0.1797598,0.70243 0.3242851,1.04472 0.1452428,0.34448 ' +
'0.3214064,0.6644 0.5227339,0.96066 l -1.1993431,1.19723 1.5840256,1.56011 1.1964668,-1.19348 ' +
'c 0.6140517,0.40346 1.3028827,0.68232 2.0233517,0.82331 l 7.19e-4,1.69892 h 2.226848 z ' +
'm 0.221462,-3.9957 c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
'0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
'0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
},
'TASK_TYPE_SERVICE_FILL': {
d: 'm {mx},{my} c -1.788948,0.7502 -3.8576,-0.0928 -4.6097055,-1.87438 -0.7521065,-1.78321 ' +
'0.090598,-3.84627 1.8802645,-4.59604 1.78823,-0.74936 3.856881,0.0929 4.608987,1.87437 ' +
'0.752106,1.78165 -0.0906,3.84612 -1.879546,4.59605 z'
},
'TASK_TYPE_BUSINESS_RULE_HEADER': {
d: 'm {mx},{my} 0,4 20,0 0,-4 z'
},
'TASK_TYPE_BUSINESS_RULE_MAIN': {
d: 'm {mx},{my} 0,12 20,0 0,-12 z' +
'm 0,8 l 20,0 ' +
'm -13,-4 l 0,8'
},
'MESSAGE_FLOW_MARKER': {
d: 'm {mx},{my} m -10.5 ,-7 l 0,14 l 21,0 l 0,-14 z l 10.5,6 l 10.5,-6'
}
};
this.getRawPath = function getRawPath(pathId) {
return this.pathMap[pathId].d;
};
/**
* Scales the path to the given height and width.
* <h1>Use case</h1>
* <p>Use case is to scale the content of elements (event, gateways) based
* on the element bounding box's size.
* </p>
* <h1>Why not transform</h1>
* <p>Scaling a path with transform() will also scale the stroke and IE does not support
* the option 'non-scaling-stroke' to prevent this.
* Also there are use cases where only some parts of a path should be
* scaled.</p>
*
* @param {String} pathId The ID of the path.
* @param {Object} param <p>
* Example param object scales the path to 60% size of the container (data.width, data.height).
* <pre>
* {
* xScaleFactor: 0.6,
* yScaleFactor:0.6,
* containerWidth: data.width,
* containerHeight: data.height,
* position: {
* mx: 0.46,
* my: 0.2,
* }
* }
* </pre>
* <ul>
* <li>targetpathwidth = xScaleFactor * containerWidth</li>
* <li>targetpathheight = yScaleFactor * containerHeight</li>
* <li>Position is used to set the starting coordinate of the path. M is computed:
* <ul>
* <li>position.x * containerWidth</li>
* <li>position.y * containerHeight</li>
* </ul>
* Center of the container <pre> position: {
* mx: 0.5,
* my: 0.5,
* }</pre>
* Upper left corner of the container
* <pre> position: {
* mx: 0.0,
* my: 0.0,
* }</pre>
* </li>
* </ul>
* </p>
*
*/
this.getScaledPath = function getScaledPath(pathId, param) {
var rawPath = this.pathMap[pathId];
// positioning
// compute the start point of the path
var mx, my;
if (param.abspos) {
mx = param.abspos.x;
my = param.abspos.y;
} else {
mx = param.containerWidth * param.position.mx;
my = param.containerHeight * param.position.my;
}
var coordinates = {}; // map for the scaled coordinates
if (param.position) {
// path
var heightRatio = (param.containerHeight / rawPath.height) * param.yScaleFactor;
var widthRatio = (param.containerWidth / rawPath.width) * param.xScaleFactor;
// Apply height ratio
for (var heightIndex = 0; heightIndex < rawPath.heightElements.length; heightIndex++) {
coordinates['y' + heightIndex] = rawPath.heightElements[heightIndex] * heightRatio;
}
// Apply width ratio
for (var widthIndex = 0; widthIndex < rawPath.widthElements.length; widthIndex++) {
coordinates['x' + widthIndex] = rawPath.widthElements[widthIndex] * widthRatio;
}
}
// Apply value to raw path
var path = format(
rawPath.d, {
mx: mx,
my: my,
e: coordinates
}
);
return path;
};
}
// helpers //////////////////////
// copied from https://github.com/adobe-webplatform/Snap.svg/blob/master/src/svg.js
var tokenRegex = /\{([^}]+)\}/g,
objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g; // matches .xxxxx or ["xxxxx"] to run over object properties
function replacer(all, key, obj) {
var res = obj;
key.replace(objNotationRegex, function(all, name, quote, quotedName, isFunc) {
name = name || quotedName;
if (res) {
if (name in res) {
res = res[name];
}
typeof res == 'function' && isFunc && (res = res());
}
});
res = (res == null || res == obj ? all : res) + '';
return res;
}
function format(str, obj) {
return String(str).replace(tokenRegex, function(all, key) {
return replacer(all, key, obj);
});
}

View File

@@ -0,0 +1,115 @@
import { assign } from 'min-dash';
import TextUtil from 'diagram-js/lib/util/Text';
var DEFAULT_FONT_SIZE = 12;
var LINE_HEIGHT_RATIO = 1.2;
var MIN_TEXT_ANNOTATION_HEIGHT = 30;
export default function TextRenderer(config) {
var defaultStyle = assign({
fontFamily: 'Arial, sans-serif',
fontSize: DEFAULT_FONT_SIZE,
fontWeight: 'normal',
lineHeight: LINE_HEIGHT_RATIO
}, config && config.defaultStyle || {});
var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;
var externalStyle = assign({}, defaultStyle, {
fontSize: fontSize
}, config && config.externalStyle || {});
var textUtil = new TextUtil({
style: defaultStyle
});
/**
* Get the new bounds of an externally rendered,
* layouted label.
*
* @param {Bounds} bounds
* @param {String} text
*
* @return {Bounds}
*/
this.getExternalLabelBounds = function(bounds, text) {
var layoutedDimensions = textUtil.getDimensions(text, {
box: {
width: 90,
height: 30,
x: bounds.width / 2 + bounds.x,
y: bounds.height / 2 + bounds.y
},
style: externalStyle
});
// resize label shape to fit label text
return {
x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
y: Math.round(bounds.y),
width: Math.ceil(layoutedDimensions.width),
height: Math.ceil(layoutedDimensions.height)
};
};
/**
* Get the new bounds of text annotation.
*
* @param {Bounds} bounds
* @param {String} text
*
* @return {Bounds}
*/
this.getTextAnnotationBounds = function(bounds, text) {
var layoutedDimensions = textUtil.getDimensions(text, {
box: bounds,
style: defaultStyle,
align: 'left-top',
padding: 5
});
return {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: Math.max(MIN_TEXT_ANNOTATION_HEIGHT, Math.round(layoutedDimensions.height))
};
};
/**
* Create a layouted text element.
*
* @param {String} text
* @param {Object} [options]
*
* @return {SVGElement} rendered text
*/
this.createText = function(text, options) {
return textUtil.createText(text, options || {});
};
/**
* Get default text style.
*/
this.getDefaultStyle = function() {
return defaultStyle;
};
/**
* Get the external text style.
*/
this.getExternalStyle = function() {
return externalStyle;
};
}
TextRenderer.$inject = [
'config.textRenderer'
];

View File

@@ -0,0 +1,11 @@
import BpmnRenderer from './BpmnRenderer';
import TextRenderer from './TextRenderer';
import PathMap from './PathMap';
export default {
__init__: [ 'bpmnRenderer' ],
bpmnRenderer: [ 'type', BpmnRenderer ],
textRenderer: [ 'type', TextRenderer ],
pathMap: [ 'type', PathMap ]
};

View File

@@ -0,0 +1,53 @@
import { getNewShapePosition } from './AutoPlaceUtil';
/**
* A service that places elements connected to existing ones
* to an appropriate position in an _automated_ fashion.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function AutoPlace(eventBus, modeling) {
function emit(event, payload) {
return eventBus.fire(event, payload);
}
/**
* Append shape to source at appropriate position.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} shape
*
* @return {djs.model.Shape} appended shape
*/
this.append = function(source, shape) {
// allow others to provide the position
var position = emit('autoPlace', {
source: source,
shape: shape
});
if (!position) {
position = getNewShapePosition(source, shape);
}
var newShape = modeling.appendShape(source, shape, position, source.parent);
// notify interested parties on new shape placed
emit('autoPlace.end', {
shape: newShape
});
return newShape;
};
}
AutoPlace.$inject = [
'eventBus',
'modeling'
];

View File

@@ -0,0 +1,18 @@
/**
* Select element after auto placement.
*
* @param {EventBus} eventBus
* @param {Selection} selection
*/
export default function AutoPlaceSelectionBehavior(eventBus, selection) {
eventBus.on('autoPlace.end', 500, function(e) {
selection.select(e.shape);
});
}
AutoPlaceSelectionBehavior.$inject = [
'eventBus',
'selection'
];

View File

@@ -0,0 +1,429 @@
import { is } from '../../util/ModelUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import {
getMid,
asTRBL,
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';
import {
find,
reduce
} from 'min-dash';
var DEFAULT_HORIZONTAL_DISTANCE = 50;
var MAX_HORIZONTAL_DISTANCE = 250;
// padding to detect element placement
var PLACEMENT_DETECTION_PAD = 10;
/**
* Find the new position for the target element to
* connect to source.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
*
* @return {Point}
*/
export function getNewShapePosition(source, element) {
if (is(element, 'bpmn:TextAnnotation')) {
return getTextAnnotationPosition(source, element);
}
if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
return getDataElementPosition(source, element);
}
if (is(element, 'bpmn:FlowNode')) {
return getFlowNodePosition(source, element);
}
return getDefaultPosition(source, element);
}
/**
* Always try to place element right of source;
* compute actual distance from previous nodes in flow.
*/
export function getFlowNodePosition(source, element) {
var sourceTrbl = asTRBL(source);
var sourceMid = getMid(source);
var horizontalDistance = getFlowNodeDistance(source, element);
var orientation = 'left',
rowSize = 80,
margin = 30;
if (is(source, 'bpmn:BoundaryEvent')) {
orientation = getOrientation(source, source.host, -25);
if (orientation.indexOf('top') !== -1) {
margin *= -1;
}
}
function getVerticalDistance(orient) {
if (orient.indexOf('top') != -1) {
return -1 * rowSize;
} else if (orient.indexOf('bottom') != -1) {
return rowSize;
} else {
return 0;
}
}
var position = {
x: sourceTrbl.right + horizontalDistance + element.width / 2,
y: sourceMid.y + getVerticalDistance(orientation)
};
var escapeDirection = {
y: {
margin: margin,
rowSize: rowSize
}
};
return deconflictPosition(source, element, position, escapeDirection);
}
/**
* Compute best distance between source and target,
* based on existing connections to and from source.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
*
* @return {Number} distance
*/
export function getFlowNodeDistance(source, element) {
var sourceTrbl = asTRBL(source);
// is connection a reference to consider?
function isReference(c) {
return is(c, 'bpmn:SequenceFlow');
}
function toTargetNode(weight) {
return function(shape) {
return {
shape: shape,
weight: weight,
distanceTo: function(shape) {
var shapeTrbl = asTRBL(shape);
return shapeTrbl.left - sourceTrbl.right;
}
};
};
}
function toSourceNode(weight) {
return function(shape) {
return {
shape: shape,
weight: weight,
distanceTo: function(shape) {
var shapeTrbl = asTRBL(shape);
return sourceTrbl.left - shapeTrbl.right;
}
};
};
}
// we create a list of nodes to take into consideration
// for calculating the optimal flow node distance
//
// * weight existing target nodes higher than source nodes
// * only take into account individual nodes once
//
var nodes = reduce([].concat(
getTargets(source, isReference).map(toTargetNode(5)),
getSources(source, isReference).map(toSourceNode(1))
), function(nodes, node) {
// filter out shapes connected twice via source or target
nodes[node.shape.id + '__weight_' + node.weight] = node;
return nodes;
}, {});
// compute distances between source and incoming nodes;
// group at the same time by distance and expose the
// favourite distance as { fav: { count, value } }.
var distancesGrouped = reduce(nodes, function(result, node) {
var shape = node.shape,
weight = node.weight,
distanceTo = node.distanceTo;
var fav = result.fav,
currentDistance,
currentDistanceCount,
currentDistanceEntry;
currentDistance = distanceTo(shape);
// ignore too far away peers
// or non-left to right modeled nodes
if (currentDistance < 0 || currentDistance > MAX_HORIZONTAL_DISTANCE) {
return result;
}
currentDistanceEntry = result[String(currentDistance)] =
result[String(currentDistance)] || {
value: currentDistance,
count: 0
};
// inc diff count
currentDistanceCount = currentDistanceEntry.count += 1 * weight;
if (!fav || fav.count < currentDistanceCount) {
result.fav = currentDistanceEntry;
}
return result;
}, { });
if (distancesGrouped.fav) {
return distancesGrouped.fav.value;
} else {
return DEFAULT_HORIZONTAL_DISTANCE;
}
}
/**
* Always try to place text annotations top right of source.
*/
export function getTextAnnotationPosition(source, element) {
var sourceTrbl = asTRBL(source);
var position = {
x: sourceTrbl.right + element.width / 2,
y: sourceTrbl.top - 50 - element.height / 2
};
var escapeDirection = {
y: {
margin: -30,
rowSize: 20
}
};
return deconflictPosition(source, element, position, escapeDirection);
}
/**
* Always put element bottom right of source.
*/
export function getDataElementPosition(source, element) {
var sourceTrbl = asTRBL(source);
var position = {
x: sourceTrbl.right - 10 + element.width / 2,
y: sourceTrbl.bottom + 40 + element.width / 2
};
var escapeDirection = {
x: {
margin: 30,
rowSize: 30
}
};
return deconflictPosition(source, element, position, escapeDirection);
}
/**
* Always put element right of source per default.
*/
export function getDefaultPosition(source, element) {
var sourceTrbl = asTRBL(source);
var sourceMid = getMid(source);
// simply put element right next to source
return {
x: sourceTrbl.right + DEFAULT_HORIZONTAL_DISTANCE + element.width / 2,
y: sourceMid.y
};
}
/**
* Returns all connected elements around the given source.
*
* This includes:
*
* - connected elements
* - host connected elements
* - attachers connected elements
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
*
* @return {Array<djs.model.Shape>}
*/
function getAutoPlaceClosure(source, element) {
var allConnected = getConnected(source);
if (source.host) {
allConnected = allConnected.concat(getConnected(source.host));
}
if (source.attachers) {
allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) {
return shapes.concat(getConnected(attacher));
}, []));
}
return allConnected;
}
/**
* Return target at given position, if defined.
*
* This takes connected elements from host and attachers
* into account, too.
*/
export function getConnectedAtPosition(source, position, element) {
var bounds = {
x: position.x - (element.width / 2),
y: position.y - (element.height / 2),
width: element.width,
height: element.height
};
var closure = getAutoPlaceClosure(source, element);
return find(closure, function(target) {
if (target === element) {
return false;
}
var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);
return orientation === 'intersect';
});
}
/**
* Returns a new, position for the given element
* based on the given element that is not occupied
* by some element connected to source.
*
* Take into account the escapeDirection (where to move
* on positining clashes) in the computation.
*
* @param {djs.model.Shape} source
* @param {djs.model.Shape} element
* @param {Point} position
* @param {Object} escapeDelta
*
* @return {Point}
*/
export function deconflictPosition(source, element, position, escapeDelta) {
function nextPosition(existingElement) {
var newPosition = {
x: position.x,
y: position.y
};
[ 'x', 'y' ].forEach(function(axis) {
var axisDelta = escapeDelta[axis];
if (!axisDelta) {
return;
}
var dimension = axis === 'x' ? 'width' : 'height';
var margin = axisDelta.margin,
rowSize = axisDelta.rowSize;
if (margin < 0) {
newPosition[axis] = Math.min(
existingElement[axis] + margin - element[dimension] / 2,
position[axis] - rowSize + margin
);
} else {
newPosition[axis] = Math.max(
existingTarget[axis] + existingTarget[dimension] + margin + element[dimension] / 2,
position[axis] + rowSize + margin
);
}
});
return newPosition;
}
var existingTarget;
// deconflict position until free slot is found
while ((existingTarget = getConnectedAtPosition(source, position, element))) {
position = nextPosition(existingTarget);
}
return position;
}
// helpers //////////////////////
function noneFilter() {
return true;
}
function getConnected(element, connectionFilter) {
return [].concat(
getTargets(element, connectionFilter),
getSources(element, connectionFilter)
);
}
function getSources(shape, connectionFilter) {
if (!connectionFilter) {
connectionFilter = noneFilter;
}
return shape.incoming.filter(connectionFilter).map(function(c) {
return c.source;
});
}
function getTargets(shape, connectionFilter) {
if (!connectionFilter) {
connectionFilter = noneFilter;
}
return shape.outgoing.filter(connectionFilter).map(function(c) {
return c.target;
});
}

View File

@@ -0,0 +1,8 @@
import AutoPlace from './AutoPlace';
import AutoPlaceSelectionBehavior from './AutoPlaceSelectionBehavior';
export default {
__init__: [ 'autoPlaceSelectionBehavior' ],
autoPlace: [ 'type', AutoPlace ],
autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
};

View File

@@ -0,0 +1,38 @@
import AutoResize from 'diagram-js/lib/features/auto-resize/AutoResize';
import inherits from 'inherits';
import { is } from '../../util/ModelUtil';
/**
* Sub class of the AutoResize module which implements a BPMN
* specific resize function.
*/
export default function BpmnAutoResize(injector) {
injector.invoke(AutoResize, this);
}
BpmnAutoResize.$inject = [
'injector'
];
inherits(BpmnAutoResize, AutoResize);
/**
* Resize shapes and lanes.
*
* @param {djs.model.Shape} target
* @param {Bounds} newBounds
* @param {Object} hints
*/
BpmnAutoResize.prototype.resize = function(target, newBounds, hints) {
if (is(target, 'bpmn:Participant')) {
this._modeling.resizeLane(target, newBounds, null, hints);
} else {
this._modeling.resizeShape(target, newBounds, null, hints);
}
};

View File

@@ -0,0 +1,51 @@
import { is } from '../../util/ModelUtil';
import inherits from 'inherits';
import { forEach } from 'min-dash';
import AutoResizeProvider from 'diagram-js/lib/features/auto-resize/AutoResizeProvider';
/**
* This module is a provider for automatically resizing parent BPMN elements
*/
export default function BpmnAutoResizeProvider(eventBus, modeling) {
AutoResizeProvider.call(this, eventBus);
this._modeling = modeling;
}
inherits(BpmnAutoResizeProvider, AutoResizeProvider);
BpmnAutoResizeProvider.$inject = [
'eventBus',
'modeling'
];
/**
* Check if the given target can be expanded
*
* @param {djs.model.Shape} target
*
* @return {boolean}
*/
BpmnAutoResizeProvider.prototype.canResize = function(elements, target) {
if (!is(target, 'bpmn:Participant') && !is(target, 'bpmn:Lane') && !(is(target, 'bpmn:SubProcess'))) {
return false;
}
var canResize = true;
forEach(elements, function(element) {
if (is(element, 'bpmn:Lane') || element.labelTarget) {
canResize = false;
return;
}
});
return canResize;
};

View File

@@ -0,0 +1,12 @@
import BpmnAutoResize from './BpmnAutoResize';
import BpmnAutoResizeProvider from './BpmnAutoResizeProvider';
export default {
__init__: [
'bpmnAutoResize',
'bpmnAutoResizeProvider'
],
bpmnAutoResize: [ 'type', BpmnAutoResize ],
bpmnAutoResizeProvider: [ 'type', BpmnAutoResizeProvider ]
};

View File

@@ -0,0 +1,434 @@
import {
assign,
forEach,
isArray
} from 'min-dash';
import {
is
} from '../../util/ModelUtil';
import {
isExpanded,
isEventSubProcess
} from '../../util/DiUtil';
import {
isAny
} from '../modeling/util/ModelingUtil';
import {
getChildLanes
} from '../modeling/util/LaneUtil';
import {
hasPrimaryModifier
} from 'diagram-js/lib/util/Mouse';
/**
* A provider for BPMN 2.0 elements context pad
*/
export default function ContextPadProvider(
config, injector, eventBus,
contextPad, modeling, elementFactory,
connect, create, popupMenu,
canvas, rules, translate) {
config = config || {};
contextPad.registerProvider(this);
this._contextPad = contextPad;
this._modeling = modeling;
this._elementFactory = elementFactory;
this._connect = connect;
this._create = create;
this._popupMenu = popupMenu;
this._canvas = canvas;
this._rules = rules;
this._translate = translate;
if (config.autoPlace !== false) {
this._autoPlace = injector.get('autoPlace', false);
}
eventBus.on('create.end', 250, function(event) {
var shape = event.context.shape;
if (!hasPrimaryModifier(event)) {
return;
}
var entries = contextPad.getEntries(shape);
if (entries.replace) {
entries.replace.action.click(event, shape);
}
});
}
ContextPadProvider.$inject = [
'config.contextPad',
'injector',
'eventBus',
'contextPad',
'modeling',
'elementFactory',
'connect',
'create',
'popupMenu',
'canvas',
'rules',
'translate'
];
ContextPadProvider.prototype.getContextPadEntries = function(element) {
var contextPad = this._contextPad,
modeling = this._modeling,
elementFactory = this._elementFactory,
connect = this._connect,
create = this._create,
popupMenu = this._popupMenu,
canvas = this._canvas,
rules = this._rules,
autoPlace = this._autoPlace,
translate = this._translate;
var actions = {};
if (element.type === 'label') {
return actions;
}
var businessObject = element.businessObject;
function startConnect(event, element) {
connect.start(event, element);
}
function removeElement(e) {
modeling.removeElements([ element ]);
}
function getReplaceMenuPosition(element) {
var Y_OFFSET = 5;
var diagramContainer = canvas.getContainer(),
pad = contextPad.getPad(element).html;
var diagramRect = diagramContainer.getBoundingClientRect(),
padRect = pad.getBoundingClientRect();
var top = padRect.top - diagramRect.top;
var left = padRect.left - diagramRect.left;
var pos = {
x: left,
y: top + padRect.height + Y_OFFSET
};
return pos;
}
/**
* Create an append action
*
* @param {String} type
* @param {String} className
* @param {String} [title]
* @param {Object} [options]
*
* @return {Object} descriptor
*/
function appendAction(type, className, title, options) {
if (typeof title !== 'string') {
options = title;
title = translate('Append {type}', { type: type.replace(/^bpmn:/, '') });
}
function appendStart(event, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
create.start(event, shape, element);
}
var append = autoPlace ? function(event, element) {
var shape = elementFactory.createShape(assign({ type: type }, options));
autoPlace.append(element, shape);
} : appendStart;
return {
group: 'model',
className: className,
title: title,
action: {
dragstart: appendStart,
click: append
}
};
}
function splitLaneHandler(count) {
return function(event, element) {
// actual split
modeling.splitLane(element, count);
// refresh context pad after split to
// get rid of split icons
contextPad.open(element, true);
};
}
if (isAny(businessObject, [ 'bpmn:Lane', 'bpmn:Participant' ]) && isExpanded(businessObject)) {
var childLanes = getChildLanes(element);
assign(actions, {
'lane-insert-above': {
group: 'lane-insert-above',
className: 'bpmn-icon-lane-insert-above',
title: translate('Add Lane above'),
action: {
click: function(event, element) {
modeling.addLane(element, 'top');
}
}
}
});
if (childLanes.length < 2) {
if (element.height >= 120) {
assign(actions, {
'lane-divide-two': {
group: 'lane-divide',
className: 'bpmn-icon-lane-divide-two',
title: translate('Divide into two Lanes'),
action: {
click: splitLaneHandler(2)
}
}
});
}
if (element.height >= 180) {
assign(actions, {
'lane-divide-three': {
group: 'lane-divide',
className: 'bpmn-icon-lane-divide-three',
title: translate('Divide into three Lanes'),
action: {
click: splitLaneHandler(3)
}
}
});
}
}
assign(actions, {
'lane-insert-below': {
group: 'lane-insert-below',
className: 'bpmn-icon-lane-insert-below',
title: translate('Add Lane below'),
action: {
click: function(event, element) {
modeling.addLane(element, 'bottom');
}
}
}
});
}
if (is(businessObject, 'bpmn:FlowNode')) {
if (is(businessObject, 'bpmn:EventBasedGateway')) {
assign(actions, {
'append.receive-task': appendAction(
'bpmn:ReceiveTask',
'bpmn-icon-receive-task'
),
'append.message-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-message',
translate('Append MessageIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:MessageEventDefinition' }
),
'append.timer-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-timer',
translate('Append TimerIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:TimerEventDefinition' }
),
'append.condtion-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-condition',
translate('Append ConditionIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:ConditionalEventDefinition' }
),
'append.signal-intermediate-event': appendAction(
'bpmn:IntermediateCatchEvent',
'bpmn-icon-intermediate-event-catch-signal',
translate('Append SignalIntermediateCatchEvent'),
{ eventDefinitionType: 'bpmn:SignalEventDefinition' }
)
});
} else
if (isEventType(businessObject, 'bpmn:BoundaryEvent', 'bpmn:CompensateEventDefinition')) {
assign(actions, {
'append.compensation-activity':
appendAction(
'bpmn:Task',
'bpmn-icon-task',
translate('Append compensation activity'),
{
isForCompensation: true
}
)
});
} else
if (!is(businessObject, 'bpmn:EndEvent') &&
!businessObject.isForCompensation &&
!isEventType(businessObject, 'bpmn:IntermediateThrowEvent', 'bpmn:LinkEventDefinition') &&
!isEventSubProcess(businessObject)) {
assign(actions, {
'append.end-event': appendAction(
'bpmn:EndEvent',
'bpmn-icon-end-event-none',
translate('Append EndEvent')
),
'append.gateway': appendAction(
'bpmn:ExclusiveGateway',
'bpmn-icon-gateway-none',
translate('Append Gateway')
),
'append.append-task': appendAction(
'bpmn:Task',
'bpmn-icon-task',
translate('Append Task')
),
'append.intermediate-event': appendAction(
'bpmn:IntermediateThrowEvent',
'bpmn-icon-intermediate-event-none',
translate('Append Intermediate/Boundary Event')
)
});
}
}
if (!popupMenu.isEmpty(element, 'bpmn-replace')) {
// Replace menu entry
assign(actions, {
'replace': {
group: 'edit',
className: 'bpmn-icon-screw-wrench',
title: translate('Change type'),
action: {
click: function(event, element) {
var position = assign(getReplaceMenuPosition(element), {
cursor: { x: event.x, y: event.y }
});
popupMenu.open(element, 'bpmn-replace', position);
}
}
}
});
}
if (isAny(businessObject, [
'bpmn:FlowNode',
'bpmn:InteractionNode',
'bpmn:DataObjectReference',
'bpmn:DataStoreReference'
])) {
assign(actions, {
'append.text-annotation': appendAction('bpmn:TextAnnotation', 'bpmn-icon-text-annotation'),
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect using ' +
(businessObject.isForCompensation ? '' : 'Sequence/MessageFlow or ') +
'Association'),
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
if (isAny(businessObject, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
assign(actions, {
'connect': {
group: 'connect',
className: 'bpmn-icon-connection-multi',
title: translate('Connect using DataInputAssociation'),
action: {
click: startConnect,
dragstart: startConnect
}
}
});
}
// delete element entry, only show if allowed by rules
var deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
if (isArray(deleteAllowed)) {
// was the element returned as a deletion candidate?
deleteAllowed = deleteAllowed[0] === element;
}
if (deleteAllowed) {
assign(actions, {
'delete': {
group: 'edit',
className: 'bpmn-icon-trash',
title: translate('Remove'),
action: {
click: removeElement
}
}
});
}
return actions;
};
function isEventType(eventBo, type, definition) {
var isType = eventBo.$instanceOf(type);
var isDefinition = false;
var definitions = eventBo.eventDefinitions || [];
forEach(definitions, function(def) {
if (def.$type === definition) {
isDefinition = true;
}
});
return isType && isDefinition;
}

View File

@@ -0,0 +1,21 @@
import DirectEditingModule from 'diagram-js-direct-editing';
import ContextPadModule from 'diagram-js/lib/features/context-pad';
import SelectionModule from 'diagram-js/lib/features/selection';
import ConnectModule from 'diagram-js/lib/features/connect';
import CreateModule from 'diagram-js/lib/features/create';
import PopupMenuModule from '../popup-menu';
import ContextPadProvider from './ContextPadProvider';
export default {
__depends__: [
DirectEditingModule,
ContextPadModule,
SelectionModule,
ConnectModule,
CreateModule,
PopupMenuModule
],
__init__: [ 'contextPadProvider' ],
contextPadProvider: [ 'type', ContextPadProvider ]
};

View File

@@ -0,0 +1,155 @@
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import ModelCloneHelper from '../../util/model/ModelCloneHelper';
import {
getProperties,
IGNORED_PROPERTIES
} from '../../util/model/ModelCloneUtils';
import {
filter,
forEach
} from 'min-dash';
function setProperties(descriptor, data, properties) {
forEach(properties, function(property) {
if (data[property] !== undefined) {
descriptor[property] = data[property];
}
});
}
function removeProperties(element, properties) {
forEach(properties, function(prop) {
if (element[prop]) {
delete element[prop];
}
});
}
export default function BpmnCopyPaste(
bpmnFactory, eventBus, copyPaste,
clipboard, canvas, bpmnRules) {
var helper = new ModelCloneHelper(eventBus, bpmnFactory);
copyPaste.registerDescriptor(function(element, descriptor) {
var businessObject = descriptor.oldBusinessObject = getBusinessObject(element);
var colors = {};
descriptor.type = element.type;
setProperties(descriptor, businessObject.di, [ 'isExpanded' ]);
setProperties(colors, businessObject.di, [ 'fill', 'stroke' ]);
descriptor.colors = colors;
if (element.type === 'label') {
return descriptor;
}
setProperties(descriptor, businessObject, [
'processRef',
'triggeredByEvent'
]);
if (businessObject.default) {
descriptor.default = businessObject.default.id;
}
return descriptor;
});
eventBus.on('element.paste', function(context) {
var descriptor = context.descriptor,
createdElements = context.createdElements,
parent = descriptor.parent,
rootElement = canvas.getRootElement(),
oldBusinessObject = descriptor.oldBusinessObject,
newBusinessObject,
source,
target,
canConnect;
newBusinessObject = bpmnFactory.create(oldBusinessObject.$type);
var properties = getProperties(oldBusinessObject.$descriptor);
properties = filter(properties, function(property) {
return IGNORED_PROPERTIES.indexOf(property.replace(/bpmn:/, '')) === -1;
});
descriptor.businessObject = helper.clone(oldBusinessObject, newBusinessObject, properties);
if (descriptor.type === 'label') {
return;
}
if (is(parent, 'bpmn:Process')) {
descriptor.parent = is(rootElement, 'bpmn:Collaboration') ? rootElement : parent;
}
if (descriptor.type === 'bpmn:DataOutputAssociation' ||
descriptor.type === 'bpmn:DataInputAssociation' ||
descriptor.type === 'bpmn:MessageFlow') {
descriptor.parent = rootElement;
}
if (is(parent, 'bpmn:Lane')) {
descriptor.parent = parent.parent;
}
// make sure that the correct type of connection is created
if (descriptor.waypoints) {
source = createdElements[descriptor.source];
target = createdElements[descriptor.target];
if (source && target) {
source = source.element;
target = target.element;
}
canConnect = bpmnRules.canConnect(source, target);
if (canConnect) {
descriptor.type = canConnect.type;
}
}
// remove the id or else we cannot paste multiple times
delete newBusinessObject.id;
// assign an ID
bpmnFactory._ensureId(newBusinessObject);
if (descriptor.type === 'bpmn:Participant' && descriptor.processRef) {
descriptor.processRef = newBusinessObject.processRef = bpmnFactory.create('bpmn:Process');
}
setProperties(newBusinessObject, descriptor, [
'isExpanded',
'triggeredByEvent'
]);
removeProperties(descriptor, [
'triggeredByEvent'
]);
});
}
BpmnCopyPaste.$inject = [
'bpmnFactory',
'eventBus',
'copyPaste',
'clipboard',
'canvas',
'bpmnRules'
];

View File

@@ -0,0 +1,11 @@
import CopyPasteModule from 'diagram-js/lib/features/copy-paste';
import BpmnCopyPaste from './BpmnCopyPaste';
export default {
__depends__: [
CopyPasteModule
],
__init__: [ 'bpmnCopyPaste' ],
bpmnCopyPaste: [ 'type', BpmnCopyPaste ]
};

View File

@@ -0,0 +1,35 @@
import {
filter
} from 'min-dash';
import {
isAny
} from '../modeling/util/ModelingUtil';
/**
* Registers element exclude filters for elements that
* currently do not support distribution.
*/
export default function BpmnDistributeElements(distributeElements) {
distributeElements.registerFilter(function(elements) {
return filter(elements, function(element) {
var cannotDistribute = isAny(element, [
'bpmn:Association',
'bpmn:BoundaryEvent',
'bpmn:DataInputAssociation',
'bpmn:DataOutputAssociation',
'bpmn:Lane',
'bpmn:MessageFlow',
'bpmn:Participant',
'bpmn:SequenceFlow',
'bpmn:TextAnnotation'
]);
return !(element.labelTarget || cannotDistribute);
});
});
}
BpmnDistributeElements.$inject = [ 'distributeElements' ];

View File

@@ -0,0 +1,12 @@
import DistributeElementsModule from 'diagram-js/lib/features/distribute-elements';
import BpmnDistributeElements from './BpmnDistributeElements';
export default {
__depends__: [
DistributeElementsModule
],
__init__: [ 'bpmnDistributeElements' ],
bpmnDistributeElements: [ 'type', BpmnDistributeElements ]
};

View File

@@ -0,0 +1,176 @@
import inherits from 'inherits';
import EditorActions from 'diagram-js/lib/features/editor-actions/EditorActions';
import { filter } from 'min-dash';
import { is } from '../../util/ModelUtil';
import {
getBBox
} from 'diagram-js/lib/util/Elements';
/**
* Registers and executes BPMN specific editor actions.
*
* @param {Injector} injector
*/
export default function BpmnEditorActions(injector) {
injector.invoke(EditorActions, this);
}
inherits(BpmnEditorActions, EditorActions);
BpmnEditorActions.$inject = [
'injector'
];
/**
* Register default actions.
*
* @param {Injector} injector
*/
BpmnEditorActions.prototype._registerDefaultActions = function(injector) {
// (0) invoke super method
EditorActions.prototype._registerDefaultActions.call(this, injector);
// (1) retrieve optional components to integrate with
var canvas = injector.get('canvas', false);
var elementRegistry = injector.get('elementRegistry', false);
var selection = injector.get('selection', false);
var spaceTool = injector.get('spaceTool', false);
var lassoTool = injector.get('lassoTool', false);
var handTool = injector.get('handTool', false);
var globalConnect = injector.get('globalConnect', false);
var distributeElements = injector.get('distributeElements', false);
var alignElements = injector.get('alignElements', false);
var directEditing = injector.get('directEditing', false);
var searchPad = injector.get('searchPad', false);
var modeling = injector.get('modeling', false);
// (2) check components and register actions
if (canvas && elementRegistry && selection) {
this._registerAction('selectElements', function() {
// select all elements except for the invisible
// root element
var rootElement = canvas.getRootElement();
var elements = elementRegistry.filter(function(element) {
return element !== rootElement;
});
selection.select(elements);
return elements;
});
}
if (spaceTool) {
this._registerAction('spaceTool', function() {
spaceTool.toggle();
});
}
if (lassoTool) {
this._registerAction('lassoTool', function() {
lassoTool.toggle();
});
}
if (handTool) {
this._registerAction('handTool', function() {
handTool.toggle();
});
}
if (globalConnect) {
this._registerAction('globalConnectTool', function() {
globalConnect.toggle();
});
}
if (selection && distributeElements) {
this._registerAction('distributeElements', function(opts) {
var currentSelection = selection.get(),
type = opts.type;
if (currentSelection.length) {
distributeElements.trigger(currentSelection, type);
}
});
}
if (selection && alignElements) {
this._registerAction('alignElements', function(opts) {
var currentSelection = selection.get(),
aligneableElements = [],
type = opts.type;
if (currentSelection.length) {
aligneableElements = filter(currentSelection, function(element) {
return !is(element, 'bpmn:Lane');
});
alignElements.trigger(aligneableElements, type);
}
});
}
if (selection && modeling) {
this._registerAction('setColor', function(opts) {
var currentSelection = selection.get();
if (currentSelection.length) {
modeling.setColor(currentSelection, opts);
}
});
}
if (selection && directEditing) {
this._registerAction('directEditing', function() {
var currentSelection = selection.get();
if (currentSelection.length) {
directEditing.activate(currentSelection[0]);
}
});
}
if (searchPad) {
this._registerAction('find', function() {
searchPad.toggle();
});
}
if (canvas && modeling) {
this._registerAction('moveToOrigin', function() {
var rootElement = canvas.getRootElement(),
boundingBox,
elements;
if (is(rootElement, 'bpmn:Collaboration')) {
elements = elementRegistry.filter(function(element) {
return is(element.parent, 'bpmn:Collaboration');
});
} else {
elements = elementRegistry.filter(function(element) {
return element !== rootElement && !is(element.parent, 'bpmn:SubProcess');
});
}
boundingBox = getBBox(elements);
modeling.moveElements(
elements,
{ x: -boundingBox.x, y: -boundingBox.y },
rootElement
);
});
}
};

View File

@@ -0,0 +1,10 @@
import EditorActionsModule from 'diagram-js/lib/features/editor-actions';
import BpmnEditorActions from './BpmnEditorActions';
export default {
__depends__: [
EditorActionsModule
],
editorActions: [ 'type', BpmnEditorActions ]
};

View File

@@ -0,0 +1,25 @@
import { isAny } from '../modeling/util/ModelingUtil';
export default function BpmnGridSnapping(eventBus) {
eventBus.on([
'create.init',
'shape.move.init'
], function(event) {
var context = event.context,
shape = event.shape;
if (isAny(shape, [
'bpmn:Participant',
'bpmn:SubProcess',
'bpmn:TextAnnotation'
])) {
if (!context.gridSnappingContext) {
context.gridSnappingContext = {};
}
context.gridSnappingContext.snapLocation = 'top-left';
}
});
}
BpmnGridSnapping.$inject = [ 'eventBus' ];

View File

@@ -0,0 +1,57 @@
import { getNewShapePosition } from '../../auto-place/AutoPlaceUtil';
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
import { is } from '../../../util/ModelUtil';
export default function AutoPlaceBehavior(eventBus, gridSnapping) {
eventBus.on('autoPlace', function(context) {
var source = context.source,
sourceMid = getMid(source),
shape = context.shape;
var position = getNewShapePosition(source, shape);
[ 'x', 'y' ].forEach(function(axis) {
var options = {};
// do not snap if x/y equal
if (position[ axis ] === sourceMid[ axis ]) {
return;
}
if (position[ axis ] > sourceMid[ axis ]) {
options.min = position[ axis ];
} else {
options.max = position[ axis ];
}
if (is(shape, 'bpmn:TextAnnotation')) {
if (isHorizontal(axis)) {
options.offset = -shape.width / 2;
} else {
options.offset = -shape.height / 2;
}
}
position[ axis ] = gridSnapping.snapValue(position[ axis ], options);
});
// must be returned to be considered by auto place
return position;
});
}
AutoPlaceBehavior.$inject = [
'eventBus',
'gridSnapping'
];
// helpers //////////
function isHorizontal(axis) {
return axis === 'x';
}

View File

@@ -0,0 +1,36 @@
import { is } from '../../../util/ModelUtil';
var HIGHER_PRIORITY = 1750;
export default function CreateParticipantBehavior(canvas, eventBus, gridSnapping) {
eventBus.on([
'create.start',
'shape.move.start'
], HIGHER_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (!is(shape, 'bpmn:Participant') ||
!is(rootElement, 'bpmn:Process') ||
!rootElement.children.length) {
return;
}
var createConstraints = context.createConstraints;
if (!createConstraints) {
return;
}
shape.width = gridSnapping.snapValue(shape.width, { min: shape.width });
shape.height = gridSnapping.snapValue(shape.height, { min: shape.height });
});
}
CreateParticipantBehavior.$inject = [
'canvas',
'eventBus',
'gridSnapping'
];

View File

@@ -0,0 +1,144 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { pointsAligned } from 'diagram-js/lib/util/Geometry';
import {
assign
} from 'min-dash';
var HIGH_PRIORITY = 3000;
/**
* Snaps connections with Manhattan layout.
*/
export default function LayoutConnectionBehavior(eventBus, gridSnapping, modeling) {
CommandInterceptor.call(this, eventBus);
this._gridSnapping = gridSnapping;
var self = this;
this.postExecuted([
'connection.create',
'connection.layout'
], HIGH_PRIORITY, function(event) {
var context = event.context,
connection = context.connection,
hints = context.hints || {},
waypoints = connection.waypoints;
if (hints.connectionStart || hints.connectionEnd) {
return;
}
if (!hasMiddleSegments(waypoints)) {
return;
}
modeling.updateWaypoints(connection, self.snapMiddleSegments(waypoints));
});
}
LayoutConnectionBehavior.$inject = [
'eventBus',
'gridSnapping',
'modeling'
];
inherits(LayoutConnectionBehavior, CommandInterceptor);
/**
* Snap middle segments of a given connection.
*
* @param {Array<Point>} waypoints
*
* @returns {Array<Point>}
*/
LayoutConnectionBehavior.prototype.snapMiddleSegments = function(waypoints) {
var gridSnapping = this._gridSnapping,
snapped;
waypoints = waypoints.slice();
for (var i = 1; i < waypoints.length - 2; i++) {
snapped = snapSegment(gridSnapping, waypoints[i], waypoints[i + 1]);
waypoints[i] = snapped[0];
waypoints[i + 1] = snapped[1];
}
return waypoints;
};
// helpers //////////
/**
* Check wether a connection has a middle segments.
*
* @param {Array} waypoints
*
* @returns {boolean}
*/
function hasMiddleSegments(waypoints) {
return waypoints.length > 3;
}
/**
* Check wether an alignment is horizontal.
*
* @param {string} aligned
*
* @returns {boolean}
*/
function horizontallyAligned(aligned) {
return aligned === 'h';
}
/**
* Check wether an alignment is vertical.
*
* @param {string} aligned
*
* @returns {boolean}
*/
function verticallyAligned(aligned) {
return aligned === 'v';
}
/**
* Get middle segments from a given connection.
*
* @param {Array} waypoints
*
* @returns {Array}
*/
function snapSegment(gridSnapping, segmentStart, segmentEnd) {
var aligned = pointsAligned(segmentStart, segmentEnd);
var snapped = {};
if (horizontallyAligned(aligned)) {
// snap horizontally
snapped.y = gridSnapping.snapValue(segmentStart.y);
}
if (verticallyAligned(aligned)) {
// snap vertically
snapped.x = gridSnapping.snapValue(segmentStart.x);
}
if ('x' in snapped || 'y' in snapped) {
segmentStart = assign({}, segmentStart, snapped);
segmentEnd = assign({}, segmentEnd, snapped);
}
return [ segmentStart, segmentEnd ];
}

View File

@@ -0,0 +1,14 @@
import AutoPlaceBehavior from './AutoPlaceBehavior';
import CreateParticipantBehavior from './CreateParticipantBehavior';
import LayoutConnectionBehavior from './LayoutConnectionBehavior';
export default {
__init__: [
'gridSnappingAutoPlaceBehavior',
'gridSnappingCreateParticipantBehavior',
'gridSnappingLayoutConnectionBehavior',
],
gridSnappingAutoPlaceBehavior: [ 'type', AutoPlaceBehavior ],
gridSnappingCreateParticipantBehavior: [ 'type', CreateParticipantBehavior ],
gridSnappingLayoutConnectionBehavior: [ 'type', LayoutConnectionBehavior ]
};

View File

@@ -0,0 +1,13 @@
import BpmnGridSnapping from './BpmnGridSnapping';
import GridSnappingModule from 'diagram-js/lib/features/grid-snapping';
import GridSnappingBehaviorModule from './behavior';
export default {
__depends__: [
GridSnappingModule,
GridSnappingBehaviorModule
],
__init__: [ 'bpmnGridSnapping' ],
bpmnGridSnapping: [ 'type', BpmnGridSnapping ]
};

View File

@@ -0,0 +1,158 @@
import inherits from 'inherits';
import KeyboardBindings from 'diagram-js/lib/features/keyboard/KeyboardBindings';
/**
* BPMN 2.0 specific keyboard bindings.
*
* @param {Injector} injector
*/
export default function BpmnKeyboardBindings(injector) {
injector.invoke(KeyboardBindings, this);
}
inherits(BpmnKeyboardBindings, KeyboardBindings);
BpmnKeyboardBindings.$inject = [
'injector'
];
/**
* Register available keyboard bindings.
*
* @param {Keyboard} keyboard
* @param {EditorActions} editorActions
*/
BpmnKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
// inherit default bindings
KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);
/**
* Add keyboard binding if respective editor action
* is registered.
*
* @param {String} action name
* @param {Function} fn that implements the key binding
*/
function addListener(action, fn) {
if (editorActions.isRegistered(action)) {
keyboard.addListener(fn);
}
}
// select all elements
// CTRL + A
addListener('selectElements', function(context) {
var event = context.keyEvent;
if (keyboard.isKey(['a', 'A'], event) && keyboard.isCmd(event)) {
editorActions.trigger('selectElements');
return true;
}
});
// search labels
// CTRL + F
addListener('find', function(context) {
var event = context.keyEvent;
if (keyboard.isKey(['f', 'F'], event) && keyboard.isCmd(event)) {
editorActions.trigger('find');
return true;
}
});
// activate space tool
// S
addListener('spaceTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey(['s', 'S'], event)) {
editorActions.trigger('spaceTool');
return true;
}
});
// activate lasso tool
// L
addListener('lassoTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey(['l', 'L'], event)) {
editorActions.trigger('lassoTool');
return true;
}
});
// activate hand tool
// H
addListener('handTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey(['h', 'H'], event)) {
editorActions.trigger('handTool');
return true;
}
});
// activate global connect tool
// C
addListener('globalConnectTool', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey(['c', 'C'], event)) {
editorActions.trigger('globalConnectTool');
return true;
}
});
// activate direct editing
// E
addListener('directEditing', function(context) {
var event = context.keyEvent;
if (keyboard.hasModifier(event)) {
return;
}
if (keyboard.isKey(['e', 'E'], event)) {
editorActions.trigger('directEditing');
return true;
}
});
};

View File

@@ -0,0 +1,11 @@
import KeyboardModule from 'diagram-js/lib/features/keyboard';
import BpmnKeyboardBindings from './BpmnKeyboardBindings';
export default {
__depends__: [
KeyboardModule
],
__init__: [ 'keyboardBindings' ],
keyboardBindings: [ 'type', BpmnKeyboardBindings ]
};

View File

@@ -0,0 +1,138 @@
import {
append as svgAppend,
attr as svgAttr,
create as svgCreate,
remove as svgRemove
} from 'tiny-svg';
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import {
translate
} from 'diagram-js/lib/util/SvgTransformUtil';
var MARKER_HIDDEN = 'djs-element-hidden',
MARKER_LABEL_HIDDEN = 'djs-label-hidden';
export default function LabelEditingPreview(
eventBus, canvas, elementRegistry,
pathMap) {
var self = this;
var defaultLayer = canvas.getDefaultLayer();
var element, absoluteElementBBox, gfx;
eventBus.on('directEditing.activate', function(context) {
var activeProvider = context.active;
element = activeProvider.element.label || activeProvider.element;
// text annotation
if (is(element, 'bpmn:TextAnnotation')) {
absoluteElementBBox = canvas.getAbsoluteBBox(element);
gfx = svgCreate('g');
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: element.height,
position: {
mx: 0.0,
my: 0.0
}
});
var path = self.path = svgCreate('path');
svgAttr(path, {
d: textPathData,
strokeWidth: 2,
stroke: getStrokeColor(element)
});
svgAppend(gfx, path);
svgAppend(defaultLayer, gfx);
translate(gfx, element.x, element.y);
}
if (is(element, 'bpmn:TextAnnotation') ||
element.labelTarget) {
canvas.addMarker(element, MARKER_HIDDEN);
} else if (is(element, 'bpmn:Task') ||
is(element, 'bpmn:CallActivity') ||
is(element, 'bpmn:SubProcess') ||
is(element, 'bpmn:Participant')) {
canvas.addMarker(element, MARKER_LABEL_HIDDEN);
}
});
eventBus.on('directEditing.resize', function(context) {
// text annotation
if (is(element, 'bpmn:TextAnnotation')) {
var height = context.height,
dy = context.dy;
var newElementHeight = Math.max(element.height / absoluteElementBBox.height * (height + dy), 0);
var textPathData = pathMap.getScaledPath('TEXT_ANNOTATION', {
xScaleFactor: 1,
yScaleFactor: 1,
containerWidth: element.width,
containerHeight: newElementHeight,
position: {
mx: 0.0,
my: 0.0
}
});
svgAttr(self.path, {
d: textPathData
});
}
});
eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
var activeProvider = context.active;
if (activeProvider) {
canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
}
element = undefined;
absoluteElementBBox = undefined;
if (gfx) {
svgRemove(gfx);
gfx = undefined;
}
});
}
LabelEditingPreview.$inject = [
'eventBus',
'canvas',
'elementRegistry',
'pathMap'
];
// helpers ///////////////////
function getStrokeColor(element, defaultColor) {
var bo = getBusinessObject(element);
return bo.di.get('stroke') || defaultColor || 'black';
}

View File

@@ -0,0 +1,429 @@
import {
assign
} from 'min-dash';
import {
getLabel
} from './LabelUtil';
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import {
createCategoryValue
} from '../modeling/behavior/util/CategoryUtil';
import { isAny } from '../modeling/util/ModelingUtil';
import { isExpanded } from '../../util/DiUtil';
import {
getExternalLabelMid,
isLabelExternal,
hasExternalLabel,
isLabel
} from '../../util/LabelUtil';
export default function LabelEditingProvider(
eventBus, bpmnFactory, canvas, directEditing,
modeling, resizeHandles, textRenderer) {
this._bpmnFactory = bpmnFactory;
this._canvas = canvas;
this._modeling = modeling;
this._textRenderer = textRenderer;
directEditing.registerProvider(this);
// listen to dblclick on non-root elements
eventBus.on('element.dblclick', function(event) {
activateDirectEdit(event.element, true);
});
// complete on followup canvas operation
eventBus.on([
'element.mousedown',
'drag.init',
'canvas.viewbox.changing',
'autoPlace',
'popupMenu.open'
], function(event) {
if (directEditing.isActive()) {
directEditing.complete();
}
});
// cancel on command stack changes
eventBus.on([ 'commandStack.changed' ], function(e) {
if (directEditing.isActive()) {
directEditing.cancel();
}
});
eventBus.on('directEditing.activate', function(event) {
resizeHandles.removeResizers();
});
eventBus.on('create.end', 500, function(event) {
var element = event.shape,
canExecute = event.context.canExecute,
isTouch = event.isTouch;
// TODO(nikku): we need to find a way to support the
// direct editing on mobile devices; right now this will
// break for desworkflowediting on mobile devices
// as it breaks the user interaction workflow
// TODO(nre): we should temporarily focus the edited element
// here and release the focused viewport after the direct edit
// operation is finished
if (isTouch) {
return;
}
if (!canExecute) {
return;
}
activateDirectEdit(element);
});
eventBus.on('autoPlace.end', 500, function(event) {
activateDirectEdit(event.shape);
});
function activateDirectEdit(element, force) {
if (force ||
isAny(element, [ 'bpmn:Task', 'bpmn:TextAnnotation', 'bpmn:Group' ]) ||
isCollapsedSubProcess(element)) {
directEditing.activate(element);
}
}
}
LabelEditingProvider.$inject = [
'eventBus',
'bpmnFactory',
'canvas',
'directEditing',
'modeling',
'resizeHandles',
'textRenderer'
];
/**
* Activate direct editing for activities and text annotations.
*
* @param {djs.model.Base} element
*
* @return {Object} an object with properties bounds (position and size), text and options
*/
LabelEditingProvider.prototype.activate = function(element) {
// text
var text = getLabel(element);
if (text === undefined) {
return;
}
var context = {
text: text
};
// bounds
var bounds = this.getEditingBBox(element);
assign(context, bounds);
var options = {};
// tasks
if (
isAny(element, [
'bpmn:Task',
'bpmn:Participant',
'bpmn:Lane',
'bpmn:CallActivity'
]) ||
isCollapsedSubProcess(element)
) {
assign(options, {
centerVertically: true
});
}
// external labels
if (isLabelExternal(element)) {
assign(options, {
autoResize: true
});
}
// text annotations
if (is(element, 'bpmn:TextAnnotation')) {
assign(options, {
resizable: true,
autoResize: true
});
}
assign(context, {
options: options
});
return context;
};
/**
* Get the editing bounding box based on the element's size and position
*
* @param {djs.model.Base} element
*
* @return {Object} an object containing information about position
* and size (fixed or minimum and/or maximum)
*/
LabelEditingProvider.prototype.getEditingBBox = function(element) {
var canvas = this._canvas;
var target = element.label || element;
var bbox = canvas.getAbsoluteBBox(target);
var mid = {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
};
// default position
var bounds = { x: bbox.x, y: bbox.y };
var zoom = canvas.zoom();
var defaultStyle = this._textRenderer.getDefaultStyle(),
externalStyle = this._textRenderer.getExternalStyle();
// take zoom into account
var externalFontSize = externalStyle.fontSize * zoom,
externalLineHeight = externalStyle.lineHeight,
defaultFontSize = defaultStyle.fontSize * zoom,
defaultLineHeight = defaultStyle.lineHeight;
var style = {
fontFamily: this._textRenderer.getDefaultStyle().fontFamily,
fontWeight: this._textRenderer.getDefaultStyle().fontWeight
};
// adjust for expanded pools AND lanes
if (is(element, 'bpmn:Lane') || isExpandedPool(element)) {
assign(bounds, {
width: bbox.height,
height: 30 * zoom,
x: bbox.x - bbox.height / 2 + (15 * zoom),
y: mid.y - (30 * zoom) / 2
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
transform: 'rotate(-90deg)'
});
}
// internal labels for tasks and collapsed call activities,
// sub processes and participants
if (isAny(element, [ 'bpmn:Task', 'bpmn:CallActivity']) ||
isCollapsedPool(element) ||
isCollapsedSubProcess(element)) {
assign(bounds, {
width: bbox.width,
height: bbox.height
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
// internal labels for expanded sub processes
if (isExpandedSubProcess(element)) {
assign(bounds, {
width: bbox.width,
x: bbox.x
});
assign(style, {
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight,
paddingTop: (7 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (5 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px'
});
}
var width = 90 * zoom,
paddingTop = 7 * zoom,
paddingBottom = 4 * zoom;
// external labels for events, data elements, gateways, groups and connections
if (target.labelTarget) {
assign(bounds, {
width: width,
height: bbox.height + paddingTop + paddingBottom,
x: mid.x - width / 2,
y: bbox.y - paddingTop
});
assign(style, {
fontSize: externalFontSize + 'px',
lineHeight: externalLineHeight,
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// external label not yet created
if (isLabelExternal(target)
&& !hasExternalLabel(target)
&& !isLabel(target)) {
var externalLabelMid = getExternalLabelMid(element);
var absoluteBBox = canvas.getAbsoluteBBox({
x: externalLabelMid.x,
y: externalLabelMid.y,
width: 0,
height: 0
});
var height = externalFontSize + paddingTop + paddingBottom;
assign(bounds, {
width: width,
height: height,
x: absoluteBBox.x - width / 2,
y: absoluteBBox.y - height / 2
});
assign(style, {
fontSize: externalFontSize + 'px',
lineHeight: externalLineHeight,
paddingTop: paddingTop + 'px',
paddingBottom: paddingBottom + 'px'
});
}
// text annotations
if (is(element, 'bpmn:TextAnnotation')) {
assign(bounds, {
width: bbox.width,
height: bbox.height,
minWidth: 30 * zoom,
minHeight: 10 * zoom
});
assign(style, {
textAlign: 'left',
paddingTop: (5 * zoom) + 'px',
paddingBottom: (7 * zoom) + 'px',
paddingLeft: (7 * zoom) + 'px',
paddingRight: (5 * zoom) + 'px',
fontSize: defaultFontSize + 'px',
lineHeight: defaultLineHeight
});
}
return { bounds: bounds, style: style };
};
LabelEditingProvider.prototype.update = function(
element, newLabel,
activeContextText, bounds) {
var newBounds,
bbox;
if (is(element, 'bpmn:TextAnnotation')) {
bbox = this._canvas.getAbsoluteBBox(element);
newBounds = {
x: element.x,
y: element.y,
width: element.width / bbox.width * bounds.width,
height: element.height / bbox.height * bounds.height
};
}
if (is(element, 'bpmn:Group')) {
var businessObject = getBusinessObject(element);
// initialize categoryValue if not existing
if (!businessObject.categoryValueRef) {
var rootElement = this._canvas.getRootElement(),
definitions = getBusinessObject(rootElement).$parent;
var categoryValue = createCategoryValue(definitions, this._bpmnFactory);
getBusinessObject(element).categoryValueRef = categoryValue;
}
}
if (isEmptyText(newLabel)) {
newLabel = null;
}
this._modeling.updateLabel(element, newLabel, newBounds);
};
// helpers //////////////////////
function isCollapsedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && !isExpanded(element);
}
function isExpandedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && isExpanded(element);
}
function isCollapsedPool(element) {
return is(element, 'bpmn:Participant') && !isExpanded(element);
}
function isExpandedPool(element) {
return is(element, 'bpmn:Participant') && isExpanded(element);
}
function isEmptyText(label) {
return !label || !label.trim();
}

View File

@@ -0,0 +1,67 @@
import { is } from '../../util/ModelUtil';
function getLabelAttr(semantic) {
if (
is(semantic, 'bpmn:FlowElement') ||
is(semantic, 'bpmn:Participant') ||
is(semantic, 'bpmn:Lane') ||
is(semantic, 'bpmn:SequenceFlow') ||
is(semantic, 'bpmn:MessageFlow') ||
is(semantic, 'bpmn:DataInput') ||
is(semantic, 'bpmn:DataOutput')
) {
return 'name';
}
if (is(semantic, 'bpmn:TextAnnotation')) {
return 'text';
}
if (is(semantic, 'bpmn:Group')) {
return 'categoryValueRef';
}
}
function getCategoryValue(semantic) {
var categoryValueRef = semantic['categoryValueRef'];
if (!categoryValueRef) {
return '';
}
return categoryValueRef.value || '';
}
export function getLabel(element) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
return getCategoryValue(semantic);
}
return semantic[attr] || '';
}
}
export function setLabel(element, text, isExternal) {
var semantic = element.businessObject,
attr = getLabelAttr(semantic);
if (attr) {
if (attr === 'categoryValueRef') {
semantic['categoryValueRef'].value = text;
} else {
semantic[attr] = text;
}
}
return element;
}

View File

@@ -0,0 +1,142 @@
import {
setLabel,
getLabel
} from '../LabelUtil';
import {
getExternalLabelMid,
isLabelExternal,
hasExternalLabel,
isLabel
} from '../../../util/LabelUtil';
import {
is
} from '../../../util/ModelUtil';
var NULL_DIMENSIONS = {
width: 0,
height: 0
};
/**
* A handler that updates the text of a BPMN element.
*/
export default function UpdateLabelHandler(modeling, textRenderer) {
/**
* Set the label and return the changed elements.
*
* Element parameter can be label itself or connection (i.e. sequence flow).
*
* @param {djs.model.Base} element
* @param {String} text
*/
function setText(element, text) {
// external label if present
var label = element.label || element;
var labelTarget = element.labelTarget || element;
setLabel(label, text, labelTarget !== label);
return [ label, labelTarget ];
}
function preExecute(ctx) {
var element = ctx.element,
businessObject = element.businessObject,
newLabel = ctx.newLabel;
if (!isLabel(element)
&& isLabelExternal(element)
&& !hasExternalLabel(element)
&& !isEmptyText(newLabel)) {
// create label
var paddingTop = 7;
var labelCenter = getExternalLabelMid(element);
labelCenter = {
x: labelCenter.x,
y: labelCenter.y + paddingTop
};
modeling.createLabel(element, labelCenter, {
id: businessObject.id + '_label',
businessObject: businessObject
});
}
}
function execute(ctx) {
ctx.oldLabel = getLabel(ctx.element);
return setText(ctx.element, ctx.newLabel);
}
function revert(ctx) {
return setText(ctx.element, ctx.oldLabel);
}
function postExecute(ctx) {
var element = ctx.element,
label = element.label || element,
newLabel = ctx.newLabel,
newBounds = ctx.newBounds,
hints = ctx.hints || {};
if (isLabel(label) && isEmptyText(newLabel)) {
if (hints.removeShape !== false) {
modeling.removeShape(label, { unsetLabel: false });
}
return;
}
// ignore internal labels for elements except text annotations
if (!isLabelExternal(element) && !is(element, 'bpmn:TextAnnotation')) {
return;
}
var text = getLabel(label);
// don't resize without text
if (!text) {
return;
}
// resize element based on label _or_ pre-defined bounds
if (typeof newBounds === 'undefined') {
newBounds = textRenderer.getExternalLabelBounds(label, text);
}
// setting newBounds to false or _null_ will
// disable the postExecute resize operation
if (newBounds) {
modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
}
}
// API
this.preExecute = preExecute;
this.execute = execute;
this.revert = revert;
this.postExecute = postExecute;
}
UpdateLabelHandler.$inject = [
'modeling',
'textRenderer'
];
// helpers ///////////////////////
function isEmptyText(label) {
return !label || !label.trim();
}

View File

@@ -0,0 +1,21 @@
import ChangeSupportModule from 'diagram-js/lib/features/change-support';
import ResizeModule from 'diagram-js/lib/features/resize';
import DirectEditingModule from 'diagram-js-direct-editing';
import LabelEditingProvider from './LabelEditingProvider';
import LabelEditingPreview from './LabelEditingPreview';
export default {
__depends__: [
ChangeSupportModule,
ResizeModule,
DirectEditingModule
],
__init__: [
'labelEditingProvider',
'labelEditingPreview'
],
labelEditingProvider: [ 'type', LabelEditingProvider ],
labelEditingPreview: [ 'type', LabelEditingPreview ]
};

View File

@@ -0,0 +1,105 @@
import {
map,
assign,
pick
} from 'min-dash';
import {
isAny
} from './util/ModelingUtil';
export default function BpmnFactory(moddle) {
this._model = moddle;
}
BpmnFactory.$inject = [ 'moddle' ];
BpmnFactory.prototype._needsId = function(element) {
return isAny(element, [
'bpmn:RootElement',
'bpmn:FlowElement',
'bpmn:MessageFlow',
'bpmn:DataAssociation',
'bpmn:Artifact',
'bpmn:Participant',
'bpmn:Lane',
'bpmn:LaneSet',
'bpmn:Process',
'bpmn:Collaboration',
'bpmndi:BPMNShape',
'bpmndi:BPMNEdge',
'bpmndi:BPMNDiagram',
'bpmndi:BPMNPlane',
'bpmn:Property',
'bpmn:CategoryValue'
]);
};
BpmnFactory.prototype._ensureId = function(element) {
// generate semantic ids for elements
// bpmn:SequenceFlow -> SequenceFlow_ID
var prefix = (element.$type || '').replace(/^[^:]*:/g, '') + '_';
if (!element.id && this._needsId(element)) {
element.id = this._model.ids.nextPrefixed(prefix, element);
}
};
BpmnFactory.prototype.create = function(type, attrs) {
var element = this._model.create(type, attrs || {});
this._ensureId(element);
return element;
};
BpmnFactory.prototype.createDiLabel = function() {
return this.create('bpmndi:BPMNLabel', {
bounds: this.createDiBounds()
});
};
BpmnFactory.prototype.createDiShape = function(semantic, bounds, attrs) {
return this.create('bpmndi:BPMNShape', assign({
bpmnElement: semantic,
bounds: this.createDiBounds(bounds)
}, attrs));
};
BpmnFactory.prototype.createDiBounds = function(bounds) {
return this.create('dc:Bounds', bounds);
};
BpmnFactory.prototype.createDiWaypoints = function(waypoints) {
var self = this;
return map(waypoints, function(pos) {
return self.createDiWaypoint(pos);
});
};
BpmnFactory.prototype.createDiWaypoint = function(point) {
return this.create('dc:Point', pick(point, [ 'x', 'y' ]));
};
BpmnFactory.prototype.createDiEdge = function(semantic, waypoints, attrs) {
return this.create('bpmndi:BPMNEdge', assign({
bpmnElement: semantic
}, attrs));
};
BpmnFactory.prototype.createDiPlane = function(semantic) {
return this.create('bpmndi:BPMNPlane', {
bpmnElement: semantic
});
};

View File

@@ -0,0 +1,393 @@
import inherits from 'inherits';
import {
assign
} from 'min-dash';
import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter';
import {
repairConnection,
withoutRedundantPoints
} from 'diagram-js/lib/layout/ManhattanLayout';
import {
getMid,
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';
import {
isExpanded
} from '../../util/DiUtil';
import { is } from '../../util/ModelUtil';
var BOUNDARY_TO_HOST_THRESHOLD = 40;
export default function BpmnLayouter() {}
inherits(BpmnLayouter, BaseLayouter);
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
hints = hints || {};
var source = hints.source || connection.source,
target = hints.target || connection.target,
waypoints = connection.waypoints,
start = hints.connectionStart,
end = hints.connectionEnd;
var manhattanOptions,
updatedWaypoints;
if (!start) {
start = getConnectionDocking(waypoints && waypoints[0], source);
}
if (!end) {
end = getConnectionDocking(waypoints && waypoints[waypoints.length - 1], target);
}
// TODO(nikku): support vertical modeling
// and invert preferredLayouts accordingly
if (is(connection, 'bpmn:Association') ||
is(connection, 'bpmn:DataAssociation')) {
if (waypoints && !isCompensationAssociation(source, target)) {
return [].concat([ start ], waypoints.slice(1, -1), [ end ]);
}
}
// manhattan layout sequence / message flows
if (is(connection, 'bpmn:MessageFlow')) {
manhattanOptions = getMessageFlowManhattanOptions(source, target);
} else
// layout all connection between flow elements h:h,
//
// except for
//
// (1) outgoing of BoundaryEvents -> layout based on attach orientation and target orientation
// (2) incoming / outgoing of Gateway -> v:h (outgoing), h:v (incoming)
// (3) loops from / to the same element
//
if (is(connection, 'bpmn:SequenceFlow') ||
isCompensationAssociation(source, target)) {
if (source === target) {
manhattanOptions = {
preferredLayouts: [ 'b:l' ]
};
} else
if (is(source, 'bpmn:BoundaryEvent')) {
manhattanOptions = {
preferredLayouts: getBoundaryEventPreferredLayouts(source, target, end)
};
} else
if (is(source, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: [ 'v:h' ]
};
} else
if (is(target, 'bpmn:Gateway')) {
manhattanOptions = {
preferredLayouts: [ 'h:v' ]
};
}
else {
manhattanOptions = {
preferredLayouts: [ 'h:h' ]
};
}
}
if (manhattanOptions) {
manhattanOptions = assign(manhattanOptions, hints);
updatedWaypoints =
withoutRedundantPoints(
repairConnection(
source, target,
start, end,
waypoints,
manhattanOptions
)
);
}
return updatedWaypoints || [ start, end ];
};
function getAttachOrientation(attachedElement) {
var hostElement = attachedElement.host,
padding = -10;
return getOrientation(getMid(attachedElement), hostElement, padding);
}
function getMessageFlowManhattanOptions(source, target) {
return {
preferredLayouts: [ 'straight', 'v:v' ],
preserveDocking: getMessageFlowPreserveDocking(source, target)
};
}
function getMessageFlowPreserveDocking(source, target) {
// (1) docking element connected to participant has precedence
if (is(target, 'bpmn:Participant')) {
return 'source';
}
if (is(source, 'bpmn:Participant')) {
return 'target';
}
// (2) docking element connected to expanded sub-process has precedence
if (isExpandedSubProcess(target)) {
return 'source';
}
if (isExpandedSubProcess(source)) {
return 'target';
}
// (3) docking event has precedence
if (is(target, 'bpmn:Event')) {
return 'target';
}
if (is(source, 'bpmn:Event')) {
return 'source';
}
return null;
}
function getConnectionDocking(point, shape) {
return point ? (point.original || point) : getMid(shape);
}
function isCompensationAssociation(source, target) {
return is(target, 'bpmn:Activity') &&
is(source, 'bpmn:BoundaryEvent') &&
target.businessObject.isForCompensation;
}
function isExpandedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && isExpanded(element);
}
function isSame(a, b) {
return a === b;
}
function isAnyOrientation(orientation, orientations) {
return orientations.indexOf(orientation) !== -1;
}
var oppositeOrientationMapping = {
'top': 'bottom',
'top-right': 'bottom-left',
'top-left': 'bottom-right',
'right': 'left',
'bottom': 'top',
'bottom-right': 'top-left',
'bottom-left': 'top-right',
'left': 'right'
};
var orientationDirectionMapping = {
top: 't',
right: 'r',
bottom: 'b',
left: 'l'
};
function getHorizontalOrientation(orientation) {
var matches = /right|left/.exec(orientation);
return matches && matches[0];
}
function getVerticalOrientation(orientation) {
var matches = /top|bottom/.exec(orientation);
return matches && matches[0];
}
function isOppositeOrientation(a, b) {
return oppositeOrientationMapping[a] === b;
}
function isOppositeHorizontalOrientation(a, b) {
var horizontalOrientation = getHorizontalOrientation(a);
var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
return b.indexOf(oppositeHorizontalOrientation) !== -1;
}
function isOppositeVerticalOrientation(a, b) {
var verticalOrientation = getVerticalOrientation(a);
var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
return b.indexOf(oppositeVerticalOrientation) !== -1;
}
function isHorizontalOrientation(orientation) {
return orientation === 'right' || orientation === 'left';
}
function getBoundaryEventPreferredLayouts(source, target, end) {
var sourceMid = getMid(source),
targetMid = getMid(target),
attachOrientation = getAttachOrientation(source),
sourceLayout,
targetLayout;
var isLoop = isSame(source.host, target);
var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
var targetOrientation = getOrientation(targetMid, sourceMid, {
x: source.width / 2 + target.width / 2,
y: source.height / 2 + target.height / 2
});
if (isLoop) {
return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end);
}
// source layout
sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide);
// target layout
targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide);
return [ sourceLayout + ':' + targetLayout ];
}
function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) {
var sourceLayout = orientationDirectionMapping[attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation)],
targetLayout;
if (attachedToSide) {
if (isHorizontalOrientation(attachOrientation)) {
targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b';
} else {
targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l';
}
} else {
targetLayout = 'v';
}
return [ sourceLayout + ':' + targetLayout ];
}
function shouldConnectToSameSide(axis, source, target, end) {
var threshold = BOUNDARY_TO_HOST_THRESHOLD;
return !(
areCloseOnAxis(axis, end, target, threshold) ||
areCloseOnAxis(axis, end, { x: target.x + target.width, y: target.y + target.height }, threshold) ||
areCloseOnAxis(axis, end, getMid(source), threshold)
);
}
function areCloseOnAxis(axis, a, b, threshold) {
return Math.abs(a[axis] - b[axis]) < threshold;
}
function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) {
// attached to either top, right, bottom or left side
if (attachedToSide) {
return orientationDirectionMapping[attachOrientation];
}
// attached to either top-right, top-left, bottom-right or bottom-left corner
// same vertical or opposite horizontal orientation
if (isSame(
getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
) || isOppositeOrientation(
getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
)) {
return orientationDirectionMapping[getVerticalOrientation(attachOrientation)];
}
// fallback
return orientationDirectionMapping[getHorizontalOrientation(attachOrientation)];
}
function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) {
// attached to either top, right, bottom or left side
if (attachedToSide) {
if (isHorizontalOrientation(attachOrientation)) {
// orientation is 'right' or 'left'
// opposite horizontal orientation or same orientation
if (
isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
isSame(attachOrientation, targetOrientation)
) {
return 'h';
}
// fallback
return 'v';
} else {
// orientation is 'top' or 'bottom'
// opposite vertical orientation or same orientation
if (
isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
isSame(attachOrientation, targetOrientation)
) {
return 'v';
}
// fallback
return 'h';
}
}
// attached to either top-right, top-left, bottom-right or bottom-left corner
// orientation is 'right', 'left'
// or same vertical orientation but also 'right' or 'left'
if (isHorizontalOrientation(targetOrientation) ||
(isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
getHorizontalOrientation(targetOrientation))) {
return 'h';
} else {
return 'v';
}
}

View File

@@ -0,0 +1,726 @@
import {
assign,
forEach
} from 'min-dash';
import inherits from 'inherits';
import {
remove as collectionRemove,
add as collectionAdd
} from 'diagram-js/lib/util/Collections';
import {
Label
} from 'diagram-js/lib/model';
import {
getBusinessObject,
is
} from '../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
* A handler responsible for updating the underlying BPMN 2.0 XML + DI
* once changes on the diagram happen
*/
export default function BpmnUpdater(
eventBus, bpmnFactory, connectionDocking,
translate) {
CommandInterceptor.call(this, eventBus);
this._bpmnFactory = bpmnFactory;
this._translate = translate;
var self = this;
// connection cropping //////////////////////
// crop connection ends during create/update
function cropConnection(e) {
var context = e.context,
connection;
if (!context.cropped) {
connection = context.connection;
connection.waypoints = connectionDocking.getCroppedWaypoints(connection);
context.cropped = true;
}
}
this.executed([
'connection.layout',
'connection.create'
], cropConnection);
this.reverted([ 'connection.layout' ], function(e) {
delete e.context.cropped;
});
// BPMN + DI update //////////////////////
// update parent
function updateParent(e) {
var context = e.context;
self.updateParent(context.shape || context.connection, context.oldParent);
}
function reverseUpdateParent(e) {
var context = e.context;
var element = context.shape || context.connection,
// oldParent is the (old) new parent, because we are undoing
oldParent = context.parent || context.newParent;
self.updateParent(element, oldParent);
}
this.executed([
'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete'
], ifBpmn(updateParent));
this.reverted([
'shape.move',
'shape.create',
'shape.delete',
'connection.create',
'connection.move',
'connection.delete'
], ifBpmn(reverseUpdateParent));
/*
* ## Updating Parent
*
* When morphing a Process into a Collaboration or vice-versa,
* make sure that both the *semantic* and *di* parent of each element
* is updated.
*
*/
function updateRoot(event) {
var context = event.context,
oldRoot = context.oldRoot,
children = oldRoot.children;
forEach(children, function(child) {
if (is(child, 'bpmn:BaseElement')) {
self.updateParent(child);
}
});
}
this.executed([ 'canvas.updateRoot' ], updateRoot);
this.reverted([ 'canvas.updateRoot' ], updateRoot);
// update bounds
function updateBounds(e) {
var shape = e.context.shape;
if (!is(shape, 'bpmn:BaseElement')) {
return;
}
self.updateBounds(shape);
}
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
// exclude labels because they're handled separately during shape.changed
if (event.context.shape.type === 'label') {
return;
}
updateBounds(event);
}));
this.reverted([ 'shape.move', 'shape.create', 'shape.resize' ], ifBpmn(function(event) {
// exclude labels because they're handled separately during shape.changed
if (event.context.shape.type === 'label') {
return;
}
updateBounds(event);
}));
// Handle labels separately. This is necessary, because the label bounds have to be updated
// every time its shape changes, not only on move, create and resize.
eventBus.on('shape.changed', function(event) {
if (event.element.type === 'label') {
updateBounds({ context: { shape: event.element } });
}
});
// attach / detach connection
function updateConnection(e) {
self.updateConnection(e.context);
}
this.executed([
'connection.create',
'connection.move',
'connection.delete',
'connection.reconnectEnd',
'connection.reconnectStart'
], ifBpmn(updateConnection));
this.reverted([
'connection.create',
'connection.move',
'connection.delete',
'connection.reconnectEnd',
'connection.reconnectStart'
], ifBpmn(updateConnection));
// update waypoints
function updateConnectionWaypoints(e) {
self.updateConnectionWaypoints(e.context.connection);
}
this.executed([
'connection.layout',
'connection.move',
'connection.updateWaypoints',
], ifBpmn(updateConnectionWaypoints));
this.reverted([
'connection.layout',
'connection.move',
'connection.updateWaypoints',
], ifBpmn(updateConnectionWaypoints));
// update Default & Conditional flows
this.executed([
'connection.reconnectEnd',
'connection.reconnectStart'
], ifBpmn(function(e) {
var context = e.context,
connection = context.connection,
businessObject = getBusinessObject(connection),
oldSource = getBusinessObject(context.oldSource),
oldTarget = getBusinessObject(context.oldTarget),
newSource = getBusinessObject(connection.source),
newTarget = getBusinessObject(connection.target);
if (oldSource === newSource || oldTarget === newTarget) {
return;
}
// on reconnectStart -> default flow
if (oldSource && oldSource.default === businessObject) {
context.default = oldSource.default;
oldSource.default = undefined;
}
// on reconnectEnd -> default flow
if ((businessObject.sourceRef && businessObject.sourceRef.default) &&
!(is(newTarget, 'bpmn:Activity') ||
is(newTarget, 'bpmn:EndEvent') ||
is(newTarget, 'bpmn:Gateway') ||
is(newTarget, 'bpmn:IntermediateThrowEvent'))) {
context.default = businessObject.sourceRef.default;
businessObject.sourceRef.default = undefined;
}
// on reconnectStart -> conditional flow
if (oldSource && (businessObject.conditionExpression) &&
!(is(newSource, 'bpmn:Activity') ||
is(newSource, 'bpmn:Gateway'))) {
context.conditionExpression = businessObject.conditionExpression;
businessObject.conditionExpression = undefined;
}
// on reconnectEnd -> conditional flow
if (oldTarget && (businessObject.conditionExpression) &&
!(is(newTarget, 'bpmn:Activity') ||
is(newTarget, 'bpmn:EndEvent') ||
is(newTarget, 'bpmn:Gateway') ||
is(newTarget, 'bpmn:IntermediateThrowEvent'))) {
context.conditionExpression = businessObject.conditionExpression;
businessObject.conditionExpression = undefined;
}
}));
this.reverted([
'connection.reconnectEnd',
'connection.reconnectStart'
], ifBpmn(function(e) {
var context = e.context,
connection = context.connection,
businessObject = getBusinessObject(connection),
newSource = getBusinessObject(connection.source);
// default flow
if (context.default) {
if (is(newSource, 'bpmn:ExclusiveGateway') || is(newSource, 'bpmn:InclusiveGateway') ||
is(newSource, 'bpmn:Activity')) {
newSource.default = context.default;
}
}
// conditional flow
if (context.conditionExpression && is(newSource, 'bpmn:Activity')) {
businessObject.conditionExpression = context.conditionExpression;
}
}));
// update attachments
function updateAttachment(e) {
self.updateAttachment(e.context);
}
this.executed([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
this.reverted([ 'element.updateAttachment' ], ifBpmn(updateAttachment));
}
inherits(BpmnUpdater, CommandInterceptor);
BpmnUpdater.$inject = [
'eventBus',
'bpmnFactory',
'connectionDocking',
'translate'
];
// implementation //////////////////////
BpmnUpdater.prototype.updateAttachment = function(context) {
var shape = context.shape,
businessObject = shape.businessObject,
host = shape.host;
businessObject.attachedToRef = host && host.businessObject;
};
BpmnUpdater.prototype.updateParent = function(element, oldParent) {
// do not update BPMN 2.0 label parent
if (element instanceof Label) {
return;
}
// data stores in collaborations are handled seperately by DataStoreBehavior
if (is(element, 'bpmn:DataStoreReference') &&
element.parent &&
is(element.parent, 'bpmn:Collaboration')) {
return;
}
var parentShape = element.parent;
var businessObject = element.businessObject,
parentBusinessObject = parentShape && parentShape.businessObject,
parentDi = parentBusinessObject && parentBusinessObject.di;
if (is(element, 'bpmn:FlowNode')) {
this.updateFlowNodeRefs(businessObject, parentBusinessObject, oldParent && oldParent.businessObject);
}
if (is(element, 'bpmn:DataOutputAssociation')) {
if (element.source) {
parentBusinessObject = element.source.businessObject;
} else {
parentBusinessObject = null;
}
}
if (is(element, 'bpmn:DataInputAssociation')) {
if (element.target) {
parentBusinessObject = element.target.businessObject;
} else {
parentBusinessObject = null;
}
}
this.updateSemanticParent(businessObject, parentBusinessObject);
if (is(element, 'bpmn:DataObjectReference') && businessObject.dataObjectRef) {
this.updateSemanticParent(businessObject.dataObjectRef, parentBusinessObject);
}
this.updateDiParent(businessObject.di, parentDi);
};
BpmnUpdater.prototype.updateBounds = function(shape) {
var di = shape.businessObject.di;
var target = (shape instanceof Label) ? this._getLabel(di) : di;
var bounds = target.bounds;
if (!bounds) {
bounds = this._bpmnFactory.createDiBounds();
target.set('bounds', bounds);
}
assign(bounds, {
x: shape.x,
y: shape.y,
width: shape.width,
height: shape.height
});
};
BpmnUpdater.prototype.updateFlowNodeRefs = function(businessObject, newContainment, oldContainment) {
if (oldContainment === newContainment) {
return;
}
var oldRefs, newRefs;
if (is (oldContainment, 'bpmn:Lane')) {
oldRefs = oldContainment.get('flowNodeRef');
collectionRemove(oldRefs, businessObject);
}
if (is(newContainment, 'bpmn:Lane')) {
newRefs = newContainment.get('flowNodeRef');
collectionAdd(newRefs, businessObject);
}
};
// update existing sourceElement and targetElement di information
BpmnUpdater.prototype.updateDiConnection = function(di, newSource, newTarget) {
if (di.sourceElement && di.sourceElement.bpmnElement !== newSource) {
di.sourceElement = newSource && newSource.di;
}
if (di.targetElement && di.targetElement.bpmnElement !== newTarget) {
di.targetElement = newTarget && newTarget.di;
}
};
BpmnUpdater.prototype.updateDiParent = function(di, parentDi) {
if (parentDi && !is(parentDi, 'bpmndi:BPMNPlane')) {
parentDi = parentDi.$parent;
}
if (di.$parent === parentDi) {
return;
}
var planeElements = (parentDi || di.$parent).get('planeElement');
if (parentDi) {
planeElements.push(di);
di.$parent = parentDi;
} else {
collectionRemove(planeElements, di);
di.$parent = null;
}
};
function getDefinitions(element) {
while (element && !is(element, 'bpmn:Definitions')) {
element = element.$parent;
}
return element;
}
BpmnUpdater.prototype.getLaneSet = function(container) {
var laneSet, laneSets;
// bpmn:Lane
if (is(container, 'bpmn:Lane')) {
laneSet = container.childLaneSet;
if (!laneSet) {
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
container.childLaneSet = laneSet;
laneSet.$parent = container;
}
return laneSet;
}
// bpmn:Participant
if (is(container, 'bpmn:Participant')) {
container = container.processRef;
}
// bpmn:FlowElementsContainer
laneSets = container.get('laneSets');
laneSet = laneSets[0];
if (!laneSet) {
laneSet = this._bpmnFactory.create('bpmn:LaneSet');
laneSet.$parent = container;
laneSets.push(laneSet);
}
return laneSet;
};
BpmnUpdater.prototype.updateSemanticParent = function(businessObject, newParent, visualParent) {
var containment,
translate = this._translate;
if (businessObject.$parent === newParent) {
return;
}
if (is(businessObject, 'bpmn:DataInput') || is(businessObject, 'bpmn:DataOutput')) {
if (is(newParent, 'bpmn:Participant') && 'processRef' in newParent) {
newParent = newParent.processRef;
}
// already in correct ioSpecification
if ('ioSpecification' in newParent && newParent.ioSpecification === businessObject.$parent) {
return;
}
}
if (is(businessObject, 'bpmn:Lane')) {
if (newParent) {
newParent = this.getLaneSet(newParent);
}
containment = 'lanes';
} else
if (is(businessObject, 'bpmn:FlowElement')) {
if (newParent) {
if (is(newParent, 'bpmn:Participant')) {
newParent = newParent.processRef;
} else
if (is(newParent, 'bpmn:Lane')) {
do {
// unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
newParent = newParent.$parent.$parent;
} while (is(newParent, 'bpmn:Lane'));
}
}
containment = 'flowElements';
} else
if (is(businessObject, 'bpmn:Artifact')) {
while (newParent &&
!is(newParent, 'bpmn:Process') &&
!is(newParent, 'bpmn:SubProcess') &&
!is(newParent, 'bpmn:Collaboration')) {
if (is(newParent, 'bpmn:Participant')) {
newParent = newParent.processRef;
break;
} else {
newParent = newParent.$parent;
}
}
containment = 'artifacts';
} else
if (is(businessObject, 'bpmn:MessageFlow')) {
containment = 'messageFlows';
} else
if (is(businessObject, 'bpmn:Participant')) {
containment = 'participants';
// make sure the participants process is properly attached / detached
// from the XML document
var process = businessObject.processRef,
definitions;
if (process) {
definitions = getDefinitions(businessObject.$parent || newParent);
if (businessObject.$parent) {
collectionRemove(definitions.get('rootElements'), process);
process.$parent = null;
}
if (newParent) {
collectionAdd(definitions.get('rootElements'), process);
process.$parent = definitions;
}
}
} else
if (is(businessObject, 'bpmn:DataOutputAssociation')) {
containment = 'dataOutputAssociations';
} else
if (is(businessObject, 'bpmn:DataInputAssociation')) {
containment = 'dataInputAssociations';
}
if (!containment) {
throw new Error(translate(
'no parent for {element} in {parent}',
{
element: businessObject.id,
parent: newParent.id
}
));
}
var children;
if (businessObject.$parent) {
// remove from old parent
children = businessObject.$parent.get(containment);
collectionRemove(children, businessObject);
}
if (!newParent) {
businessObject.$parent = null;
} else {
// add to new parent
children = newParent.get(containment);
children.push(businessObject);
businessObject.$parent = newParent;
}
if (visualParent) {
var diChildren = visualParent.get(containment);
collectionRemove(children, businessObject);
if (newParent) {
if (!diChildren) {
diChildren = [];
newParent.set(containment, diChildren);
}
diChildren.push(businessObject);
}
}
};
BpmnUpdater.prototype.updateConnectionWaypoints = function(connection) {
connection.businessObject.di.set('waypoint', this._bpmnFactory.createDiWaypoints(connection.waypoints));
};
BpmnUpdater.prototype.updateConnection = function(context) {
var connection = context.connection,
businessObject = getBusinessObject(connection),
newSource = getBusinessObject(connection.source),
newTarget = getBusinessObject(connection.target),
visualParent;
if (!is(businessObject, 'bpmn:DataAssociation')) {
var inverseSet = is(businessObject, 'bpmn:SequenceFlow');
if (businessObject.sourceRef !== newSource) {
if (inverseSet) {
collectionRemove(businessObject.sourceRef && businessObject.sourceRef.get('outgoing'), businessObject);
if (newSource && newSource.get('outgoing')) {
newSource.get('outgoing').push(businessObject);
}
}
businessObject.sourceRef = newSource;
}
if (businessObject.targetRef !== newTarget) {
if (inverseSet) {
collectionRemove(businessObject.targetRef && businessObject.targetRef.get('incoming'), businessObject);
if (newTarget && newTarget.get('incoming')) {
newTarget.get('incoming').push(businessObject);
}
}
businessObject.targetRef = newTarget;
}
} else
if (is(businessObject, 'bpmn:DataInputAssociation')) {
// handle obnoxious isMsome sourceRef
businessObject.get('sourceRef')[0] = newSource;
visualParent = context.parent || context.newParent || newTarget;
this.updateSemanticParent(businessObject, newTarget, visualParent);
} else
if (is(businessObject, 'bpmn:DataOutputAssociation')) {
visualParent = context.parent || context.newParent || newSource;
this.updateSemanticParent(businessObject, newSource, visualParent);
// targetRef = new target
businessObject.targetRef = newTarget;
}
this.updateConnectionWaypoints(connection);
this.updateDiConnection(businessObject.di, newSource, newTarget);
};
// helpers //////////////////////
BpmnUpdater.prototype._getLabel = function(di) {
if (!di.label) {
di.label = this._bpmnFactory.createDiLabel();
}
return di.label;
};
/**
* Make sure the event listener is only called
* if the touched element is a BPMN element.
*
* @param {Function} fn
* @return {Function} guarded function
*/
function ifBpmn(fn) {
return function(event) {
var context = event.context,
element = context.shape || context.connection;
if (is(element, 'bpmn:BaseElement')) {
fn(event);
}
};
}

View File

@@ -0,0 +1,241 @@
import {
assign,
forEach
} from 'min-dash';
import inherits from 'inherits';
import { is } from '../../util/ModelUtil';
import {
isExpanded
} from '../../util/DiUtil';
import BaseElementFactory from 'diagram-js/lib/core/ElementFactory';
import {
DEFAULT_LABEL_SIZE
} from '../../util/LabelUtil';
/**
* A bpmn-aware factory for diagram-js shapes
*/
export default function ElementFactory(bpmnFactory, moddle, translate) {
BaseElementFactory.call(this);
this._bpmnFactory = bpmnFactory;
this._moddle = moddle;
this._translate = translate;
}
inherits(ElementFactory, BaseElementFactory);
ElementFactory.$inject = [
'bpmnFactory',
'moddle',
'translate'
];
ElementFactory.prototype.baseCreate = BaseElementFactory.prototype.create;
ElementFactory.prototype.create = function(elementType, attrs) {
// no special magic for labels,
// we assume their businessObjects have already been created
// and wired via attrs
if (elementType === 'label') {
return this.baseCreate(elementType, assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs));
}
return this.createBpmnElement(elementType, attrs);
};
ElementFactory.prototype.createBpmnElement = function(elementType, attrs) {
var size,
translate = this._translate;
attrs = attrs || {};
var businessObject = attrs.businessObject;
if (!businessObject) {
if (!attrs.type) {
throw new Error(translate('no shape type specified'));
}
businessObject = this._bpmnFactory.create(attrs.type);
}
if (!businessObject.di) {
if (elementType === 'root') {
businessObject.di = this._bpmnFactory.createDiPlane(businessObject, [], {
id: businessObject.id + '_di'
});
} else
if (elementType === 'connection') {
businessObject.di = this._bpmnFactory.createDiEdge(businessObject, [], {
id: businessObject.id + '_di'
});
} else {
businessObject.di = this._bpmnFactory.createDiShape(businessObject, {}, {
id: businessObject.id + '_di'
});
}
}
if (is(businessObject, 'bpmn:Group')) {
attrs = assign({
isFrame: true
}, attrs);
}
if (attrs.colors) {
assign(businessObject.di, attrs.colors);
delete attrs.colors;
}
applyAttributes(businessObject, attrs, [
'processRef',
'isInterrupting',
'associationDirection',
'isForCompensation'
]);
if (attrs.isExpanded) {
applyAttribute(businessObject.di, attrs, 'isExpanded');
}
if (is(businessObject, 'bpmn:ExclusiveGateway')) {
businessObject.di.isMarkerVisible = true;
}
var eventDefinitions,
newEventDefinition;
if (attrs.eventDefinitionType) {
eventDefinitions = businessObject.get('eventDefinitions') || [];
newEventDefinition = this._moddle.create(attrs.eventDefinitionType);
if (attrs.eventDefinitionType === 'bpmn:ConditionalEventDefinition') {
newEventDefinition.condition = this._moddle.create('bpmn:FormalExpression');
}
eventDefinitions.push(newEventDefinition);
newEventDefinition.$parent = businessObject;
businessObject.eventDefinitions = eventDefinitions;
delete attrs.eventDefinitionType;
}
size = this._getDefaultSize(businessObject);
attrs = assign({
businessObject: businessObject,
id: businessObject.id
}, size, attrs);
return this.baseCreate(elementType, attrs);
};
ElementFactory.prototype._getDefaultSize = function(semantic) {
if (is(semantic, 'bpmn:SubProcess')) {
if (isExpanded(semantic)) {
return { width: 350, height: 200 };
} else {
return { width: 100, height: 80 };
}
}
if (is(semantic, 'bpmn:Task')) {
return { width: 100, height: 80 };
}
if (is(semantic, 'bpmn:Gateway')) {
return { width: 50, height: 50 };
}
if (is(semantic, 'bpmn:Event')) {
return { width: 36, height: 36 };
}
if (is(semantic, 'bpmn:Participant')) {
if (!isExpanded(semantic)) {
return { width: 400, height: 60 };
} else {
return { width: 600, height: 250 };
}
}
if (is(semantic, 'bpmn:Lane')) {
return { width: 400, height: 100 };
}
if (is(semantic, 'bpmn:DataObjectReference')) {
return { width: 36, height: 50 };
}
if (is(semantic, 'bpmn:DataStoreReference')) {
return { width: 50, height: 50 };
}
if (is(semantic, 'bpmn:TextAnnotation')) {
return { width: 100, height: 30 };
}
if (is(semantic, 'bpmn:Group')) {
return { width: 300, height: 300 };
}
return { width: 100, height: 80 };
};
ElementFactory.prototype.createParticipantShape = function(collapsed) {
var attrs = { type: 'bpmn:Participant' };
if (!collapsed) {
attrs.processRef = this._bpmnFactory.create('bpmn:Process');
}
return this.createShape(attrs);
};
// helpers //////////////////////
/**
* Apply attributes from a map to the given element,
* remove attribute from the map on application.
*
* @param {Base} element
* @param {Object} attrs (in/out map of attributes)
* @param {Array<String>} attributeNames name of attributes to apply
*/
function applyAttributes(element, attrs, attributeNames) {
forEach(attributeNames, function(property) {
if (attrs[property] !== undefined) {
applyAttribute(element, attrs, property);
}
});
}
/**
* Apply named property to element and drain it from the attrs
* collection.
*
* @param {Base} element
* @param {Object} attrs (in/out map of attributes)
* @param {String} attributeName to apply
*/
function applyAttribute(element, attrs, attributeName) {
element[attributeName] = attrs[attributeName];
delete attrs[attributeName];
}

View File

@@ -0,0 +1,192 @@
import inherits from 'inherits';
import BaseModeling from 'diagram-js/lib/features/modeling/Modeling';
import UpdatePropertiesHandler from './cmd/UpdatePropertiesHandler';
import UpdateCanvasRootHandler from './cmd/UpdateCanvasRootHandler';
import AddLaneHandler from './cmd/AddLaneHandler';
import SplitLaneHandler from './cmd/SplitLaneHandler';
import ResizeLaneHandler from './cmd/ResizeLaneHandler';
import UpdateFlowNodeRefsHandler from './cmd/UpdateFlowNodeRefsHandler';
import IdClaimHandler from './cmd/IdClaimHandler';
import SetColorHandler from './cmd/SetColorHandler';
import UpdateLabelHandler from '../label-editing/cmd/UpdateLabelHandler';
/**
* BPMN 2.0 modeling features activator
*
* @param {EventBus} eventBus
* @param {ElementFactory} elementFactory
* @param {CommandStack} commandStack
* @param {BpmnRules} bpmnRules
*/
export default function Modeling(
eventBus, elementFactory, commandStack,
bpmnRules) {
BaseModeling.call(this, eventBus, elementFactory, commandStack);
this._bpmnRules = bpmnRules;
}
inherits(Modeling, BaseModeling);
Modeling.$inject = [
'eventBus',
'elementFactory',
'commandStack',
'bpmnRules'
];
Modeling.prototype.getHandlers = function() {
var handlers = BaseModeling.prototype.getHandlers.call(this);
handlers['element.updateProperties'] = UpdatePropertiesHandler;
handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
handlers['lane.add'] = AddLaneHandler;
handlers['lane.resize'] = ResizeLaneHandler;
handlers['lane.split'] = SplitLaneHandler;
handlers['lane.updateRefs'] = UpdateFlowNodeRefsHandler;
handlers['id.updateClaim'] = IdClaimHandler;
handlers['element.setColor'] = SetColorHandler;
handlers['element.updateLabel'] = UpdateLabelHandler;
return handlers;
};
Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
this._commandStack.execute('element.updateLabel', {
element: element,
newLabel: newLabel,
newBounds: newBounds,
hints: hints || {}
});
};
Modeling.prototype.connect = function(source, target, attrs, hints) {
var bpmnRules = this._bpmnRules;
if (!attrs) {
attrs = bpmnRules.canConnect(source, target);
}
if (!attrs) {
return;
}
return this.createConnection(source, target, attrs, source.parent, hints);
};
Modeling.prototype.updateProperties = function(element, properties) {
this._commandStack.execute('element.updateProperties', {
element: element,
properties: properties
});
};
Modeling.prototype.resizeLane = function(laneShape, newBounds, balanced) {
this._commandStack.execute('lane.resize', {
shape: laneShape,
newBounds: newBounds,
balanced: balanced
});
};
Modeling.prototype.addLane = function(targetLaneShape, location) {
var context = {
shape: targetLaneShape,
location: location
};
this._commandStack.execute('lane.add', context);
return context.newLane;
};
Modeling.prototype.splitLane = function(targetLane, count) {
this._commandStack.execute('lane.split', {
shape: targetLane,
count: count
});
};
/**
* Transform the current diagram into a collaboration.
*
* @return {djs.model.Root} the new root element
*/
Modeling.prototype.makeCollaboration = function() {
var collaborationElement = this._create('root', {
type: 'bpmn:Collaboration'
});
var context = {
newRoot: collaborationElement
};
this._commandStack.execute('canvas.updateRoot', context);
return collaborationElement;
};
Modeling.prototype.updateLaneRefs = function(flowNodeShapes, laneShapes) {
this._commandStack.execute('lane.updateRefs', {
flowNodeShapes: flowNodeShapes,
laneShapes: laneShapes
});
};
/**
* Transform the current diagram into a process.
*
* @return {djs.model.Root} the new root element
*/
Modeling.prototype.makeProcess = function() {
var processElement = this._create('root', {
type: 'bpmn:Process'
});
var context = {
newRoot: processElement
};
this._commandStack.execute('canvas.updateRoot', context);
};
Modeling.prototype.claimId = function(id, moddleElement) {
this._commandStack.execute('id.updateClaim', {
id: id,
element: moddleElement,
claiming: true
});
};
Modeling.prototype.unclaimId = function(id, moddleElement) {
this._commandStack.execute('id.updateClaim', {
id: id,
element: moddleElement
});
};
Modeling.prototype.setColor = function(elements, colors) {
if (!elements.length) {
elements = [ elements ];
}
this._commandStack.execute('element.setColor', {
elements: elements,
colors: colors
});
};

View File

@@ -0,0 +1,249 @@
import inherits from 'inherits';
import {
getOrientation,
getMid,
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
import {
substract
} from 'diagram-js/lib/util/Math';
import {
hasExternalLabel
} from '../../../util/LabelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
var ALIGNMENTS = [
'top',
'bottom',
'left',
'right'
];
var ELEMENT_LABEL_DISTANCE = 10;
/**
* A component that makes sure that external labels are added
* together with respective elements and properly updated (DI wise)
* during move.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecuted([
'connection.create',
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var context = event.context,
connection = context.connection;
var source = connection.source,
target = connection.target;
checkLabelAdjustment(source);
checkLabelAdjustment(target);
});
this.postExecuted([
'label.create'
], function(event) {
checkLabelAdjustment(event.context.shape.labelTarget);
});
function checkLabelAdjustment(element) {
// skip non-existing labels
if (!hasExternalLabel(element)) {
return;
}
var optimalPosition = getOptimalPosition(element);
// no optimal position found
if (!optimalPosition) {
return;
}
adjustLabelPosition(element, optimalPosition);
}
function adjustLabelPosition(element, orientation) {
var elementMid = getMid(element),
label = element.label,
labelMid = getMid(label);
var elementTrbl = asTRBL(element);
var newLabelMid;
switch (orientation) {
case 'top':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
};
break;
case 'left':
newLabelMid = {
x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
y: elementMid.y
};
break;
case 'bottom':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
};
break;
case 'right':
newLabelMid = {
x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
y: elementMid.y
};
break;
}
var delta = substract(newLabelMid, labelMid);
modeling.moveShape(label, delta);
}
}
inherits(AdaptiveLabelPositioningBehavior, CommandInterceptor);
AdaptiveLabelPositioningBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
/**
* Return alignments which are taken by a boundary's host element
*
* @param {Shape} element
*
* @return {Array<String>}
*/
function getTakenHostAlignments(element) {
var hostElement = element.host,
elementMid = getMid(element),
hostOrientation = getOrientation(elementMid, hostElement);
var freeAlignments;
// check whether there is a multi-orientation, e.g. 'top-left'
if (hostOrientation.indexOf('-') >= 0) {
freeAlignments = hostOrientation.split('-');
} else {
freeAlignments = [ hostOrientation ];
}
var takenAlignments = ALIGNMENTS.filter(function(alignment) {
return freeAlignments.indexOf(alignment) === -1;
});
return takenAlignments;
}
/**
* Return alignments which are taken by related connections
*
* @param {Shape} element
*
* @return {Array<String>}
*/
function getTakenConnectionAlignments(element) {
var elementMid = getMid(element);
var takenAlignments = [].concat(
element.incoming.map(function(c) {
return c.waypoints[c.waypoints.length - 2 ];
}),
element.outgoing.map(function(c) {
return c.waypoints[1];
})
).map(function(point) {
return getApproximateOrientation(elementMid, point);
});
return takenAlignments;
}
/**
* Return the optimal label position around an element
* or _undefined_, if none was found.
*
* @param {Shape} element
*
* @return {String} positioning identifier
*/
function getOptimalPosition(element) {
var labelMid = getMid(element.label);
var elementMid = getMid(element);
var labelOrientation = getApproximateOrientation(elementMid, labelMid);
if (!isAligned(labelOrientation)) {
return;
}
var takenAlignments = getTakenConnectionAlignments(element);
if (element.host) {
var takenHostAlignments = getTakenHostAlignments(element);
takenAlignments = takenAlignments.concat(takenHostAlignments);
}
var freeAlignments = ALIGNMENTS.filter(function(alignment) {
return takenAlignments.indexOf(alignment) === -1;
});
// NOTHING TO DO; label already aligned a.O.K.
if (freeAlignments.indexOf(labelOrientation) !== -1) {
return;
}
return freeAlignments[0];
}
function getApproximateOrientation(p0, p1) {
return getOrientation(p1, p0, 5);
}
function isAligned(orientation) {
return ALIGNMENTS.indexOf(orientation) !== -1;
}

View File

@@ -0,0 +1,42 @@
import inherits from 'inherits';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
export default function AppendBehavior(eventBus, elementFactory, bpmnRules) {
CommandInterceptor.call(this, eventBus);
// assign correct shape position unless already set
this.preExecute('shape.append', function(context) {
var source = context.source,
shape = context.shape;
if (!context.position) {
if (is(shape, 'bpmn:TextAnnotation')) {
context.position = {
x: source.x + source.width / 2 + 75,
y: source.y - (50) - shape.height / 2
};
} else {
context.position = {
x: source.x + source.width + 80 + shape.width / 2,
y: source.y + source.height / 2
};
}
}
}, true);
}
inherits(AppendBehavior, CommandInterceptor);
AppendBehavior.$inject = [
'eventBus',
'elementFactory',
'bpmnRules'
];

View File

@@ -0,0 +1,69 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { isAny } from '../util/ModelingUtil';
import { getBusinessObject } from '../../../util/ModelUtil';
/**
* BPMN specific attach event behavior
*/
export default function AttachEventBehavior(eventBus, bpmnReplace) {
CommandInterceptor.call(this, eventBus);
/**
* replace intermediate event with boundary event when
* attaching it to a shape
*/
this.preExecute('elements.move', function(context) {
var shapes = context.shapes,
host = context.newHost,
shape,
eventDefinition,
boundaryEvent,
newShape;
if (shapes.length !== 1) {
return;
}
shape = shapes[0];
if (host && isAny(shape, [ 'bpmn:IntermediateThrowEvent', 'bpmn:IntermediateCatchEvent' ])) {
eventDefinition = getEventDefinition(shape);
boundaryEvent = {
type: 'bpmn:BoundaryEvent',
host: host
};
if (eventDefinition) {
boundaryEvent.eventDefinitionType = eventDefinition.$type;
}
newShape = bpmnReplace.replaceElement(shape, boundaryEvent, { layoutConnection: false });
context.shapes = [ newShape ];
}
}, true);
}
AttachEventBehavior.$inject = [
'eventBus',
'bpmnReplace'
];
inherits(AttachEventBehavior, CommandInterceptor);
// helper /////
function getEventDefinition(element) {
var bo = getBusinessObject(element);
return bo && bo.eventDefinitions && bo.eventDefinitions[0];
}

View File

@@ -0,0 +1,67 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import {
filter,
forEach
} from 'min-dash';
/**
* BPMN specific boundary event behavior
*/
export default function BoundaryEventBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
function getBoundaryEvents(element) {
return filter(element.attachers, function(attacher) {
return is(attacher, 'bpmn:BoundaryEvent');
});
}
// remove after connecting to event-based gateway
this.postExecute('connection.create', function(event) {
var source = event.context.source,
target = event.context.target,
boundaryEvents = getBoundaryEvents(target);
if (
is(source, 'bpmn:EventBasedGateway') &&
is(target, 'bpmn:ReceiveTask') &&
boundaryEvents.length > 0
) {
modeling.removeElements(boundaryEvents);
}
});
// remove after replacing connected gateway with event-based gateway
this.postExecute('connection.reconnectStart', function(event) {
var oldSource = event.context.oldSource,
newSource = event.context.newSource;
if (is(oldSource, 'bpmn:Gateway') &&
is(newSource, 'bpmn:EventBasedGateway')) {
forEach(newSource.outgoing, function(connection) {
var target = connection.target,
attachedboundaryEvents = getBoundaryEvents(target);
if (is(target, 'bpmn:ReceiveTask') &&
attachedboundaryEvents.length > 0) {
modeling.removeElements(attachedboundaryEvents);
}
});
}
});
}
BoundaryEventBehavior.$inject = [
'eventBus',
'modeling'
];
inherits(BoundaryEventBehavior, CommandInterceptor);

View File

@@ -0,0 +1,71 @@
import inherits from 'inherits';
import {
forEach
} from 'min-dash';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
export default function CopyPasteBehavior(eventBus, modeling, canvas) {
CommandInterceptor.call(this, eventBus);
this.preExecute('elements.paste', 1500, function(context) {
var topParent = context.topParent;
// always grab the latest root
if (!topParent.parent) {
context.topParent = canvas.getRootElement();
}
if (is(topParent, 'bpmn:Lane')) {
do {
// unwrap Lane -> LaneSet -> (Lane | FlowElementsContainer)
topParent = context.topParent = topParent.parent;
} while (is(topParent, 'bpmn:Lane') || !is(topParent, 'bpmn:Participant'));
}
}, true);
this.postExecute('elements.paste', function(context) {
var tree = context.tree,
createdElements = tree.createdElements;
forEach(createdElements, function(data) {
var element = data.element,
businessObject = element.businessObject,
descriptor = data.descriptor,
defaultFlow;
if ((is(businessObject, 'bpmn:ExclusiveGateway') || is(businessObject, 'bpmn:InclusiveGateway') ||
is(businessObject, 'bpmn:Activity')) && descriptor.default) {
defaultFlow = createdElements[descriptor.default];
// if the default flow wasn't created, means that it wasn't copied
if (defaultFlow) {
defaultFlow = defaultFlow.element;
} else {
defaultFlow = undefined;
}
delete element.default;
modeling.updateProperties(element, { default: defaultFlow });
}
});
}, true);
}
CopyPasteBehavior.$inject = [
'eventBus',
'modeling',
'canvas'
];
inherits(CopyPasteBehavior, CommandInterceptor);

View File

@@ -0,0 +1,54 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* BPMN specific create boundary event behavior
*/
export default function CreateBoundaryEventBehavior(
eventBus, modeling, elementFactory,
bpmnFactory) {
CommandInterceptor.call(this, eventBus);
/**
* replace intermediate event with boundary event when
* attaching it to a shape
*/
this.preExecute('shape.create', function(context) {
var shape = context.shape,
host = context.host,
businessObject,
boundaryEvent;
var attrs = {
cancelActivity: true
};
if (host && is(shape, 'bpmn:IntermediateThrowEvent')) {
attrs.attachedToRef = host.businessObject;
businessObject = bpmnFactory.create('bpmn:BoundaryEvent', attrs);
boundaryEvent = {
type: 'bpmn:BoundaryEvent',
businessObject: businessObject
};
context.shape = elementFactory.createShape(boundaryEvent);
}
}, true);
}
CreateBoundaryEventBehavior.$inject = [
'eventBus',
'modeling',
'elementFactory',
'bpmnFactory'
];
inherits(CreateBoundaryEventBehavior, CommandInterceptor);

View File

@@ -0,0 +1,38 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* BPMN specific create data object behavior
*/
export default function CreateDataObjectBehavior(eventBus, bpmnFactory, moddle) {
CommandInterceptor.call(this, eventBus);
this.preExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:DataObjectReference') && shape.type !== 'label') {
// create a DataObject every time a DataObjectReference is created
var dataObject = bpmnFactory.create('bpmn:DataObject');
// set the reference to the DataObject
shape.businessObject.dataObjectRef = dataObject;
}
});
}
CreateDataObjectBehavior.$inject = [
'eventBus',
'bpmnFactory',
'moddle'
];
inherits(CreateDataObjectBehavior, CommandInterceptor);

View File

@@ -0,0 +1,190 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import { isLabel } from '../../../util/LabelUtil';
import { getBBox } from 'diagram-js/lib/util/Elements';
import { assign } from 'min-dash';
import { asTRBL } from 'diagram-js/lib/layout/LayoutUtil';
var HORIZONTAL_PARTICIPANT_PADDING = 20,
VERTICAL_PARTICIPANT_PADDING = 20;
export var PARTICIPANT_BORDER_WIDTH = 30;
var HIGH_PRIORITY = 2000;
/**
* BPMN-specific behavior for creating participants.
*/
export default function CreateParticipantBehavior(canvas, eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
// fit participant
eventBus.on([
'create.start',
'shape.move.start'
], HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (!is(shape, 'bpmn:Participant') ||
!is(rootElement, 'bpmn:Process') ||
!rootElement.children.length) {
return;
}
// ignore connections, groups and labels
var children = rootElement.children.filter(function(element) {
return !is(element, 'bpmn:Group') &&
!isLabel(element) &&
!isConnection(element);
});
// ensure for available children to calculate bounds
if (!children.length) {
return;
}
var childrenBBox = getBBox(children);
var participantBounds = getParticipantBounds(shape, childrenBBox);
// assign width and height
assign(shape, participantBounds);
// assign create constraints
context.createConstraints = getParticipantCreateConstraints(shape, childrenBBox);
});
// force hovering process when creating first participant
eventBus.on('create.start', HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement(),
rootElementGfx = canvas.getGraphics(rootElement);
function ensureHoveringProcess(event) {
event.element = rootElement;
event.gfx = rootElementGfx;
}
if (is(shape, 'bpmn:Participant') && is(rootElement, 'bpmn:Process')) {
eventBus.on('element.hover', HIGH_PRIORITY, ensureHoveringProcess);
eventBus.once('create.cleanup', function() {
eventBus.off('element.hover', ensureHoveringProcess);
});
}
});
// turn process into collaboration before adding participant
this.preExecute('shape.create', function(context) {
var parent = context.parent,
shape = context.shape,
position = context.position;
var rootElement = canvas.getRootElement();
if (
is(parent, 'bpmn:Process') &&
is(shape, 'bpmn:Participant') &&
!is(rootElement, 'bpmn:Collaboration')
) {
// this is going to detach the process root
// and set the returned collaboration element
// as the new root element
var collaborationElement = modeling.makeCollaboration();
// monkey patch the create context
// so that the participant is being dropped
// onto the new collaboration root instead
context.position = position;
context.parent = collaborationElement;
context.processRoot = parent;
}
}, true);
this.execute('shape.create', function(context) {
var processRoot = context.processRoot,
shape = context.shape;
if (processRoot) {
context.oldProcessRef = shape.businessObject.processRef;
// assign the participant processRef
shape.businessObject.processRef = processRoot.businessObject;
}
}, true);
this.revert('shape.create', function(context) {
var processRoot = context.processRoot,
shape = context.shape;
if (processRoot) {
// assign the participant processRef
shape.businessObject.processRef = context.oldProcessRef;
}
}, true);
this.postExecute('shape.create', function(context) {
var processRoot = context.processRoot,
shape = context.shape;
if (processRoot) {
// process root is already detached at this point
var processChildren = processRoot.children.slice();
modeling.moveElements(processChildren, { x: 0, y: 0 }, shape);
}
}, true);
}
CreateParticipantBehavior.$inject = [
'canvas',
'eventBus',
'modeling'
];
inherits(CreateParticipantBehavior, CommandInterceptor);
// helpers //////////
function getParticipantBounds(shape, childrenBBox) {
childrenBBox = {
width: childrenBBox.width + HORIZONTAL_PARTICIPANT_PADDING * 2 + PARTICIPANT_BORDER_WIDTH,
height: childrenBBox.height + VERTICAL_PARTICIPANT_PADDING * 2
};
return {
width: Math.max(shape.width, childrenBBox.width),
height: Math.max(shape.height, childrenBBox.height)
};
}
function getParticipantCreateConstraints(shape, childrenBBox) {
childrenBBox = asTRBL(childrenBBox);
return {
bottom: childrenBBox.top + shape.height / 2 - VERTICAL_PARTICIPANT_PADDING,
left: childrenBBox.right - shape.width / 2 + HORIZONTAL_PARTICIPANT_PADDING,
top: childrenBBox.bottom - shape.height / 2 + VERTICAL_PARTICIPANT_PADDING,
right: childrenBBox.left + shape.width / 2 - HORIZONTAL_PARTICIPANT_PADDING - PARTICIPANT_BORDER_WIDTH
};
}
function isConnection(element) {
return !!element.waypoints;
}

View File

@@ -0,0 +1,158 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import {
find
} from 'min-dash';
import {
is
} from '../../../util/ModelUtil';
var TARGET_REF_PLACEHOLDER_NAME = '__targetRef_placeholder';
/**
* This behavior makes sure we always set a fake
* DataInputAssociation#targetRef as demanded by the BPMN 2.0
* XSD schema.
*
* The reference is set to a bpmn:Property{ name: '__targetRef_placeholder' }
* which is created on the fly and cleaned up afterwards if not needed
* anymore.
*
* @param {EventBus} eventBus
* @param {BpmnFactory} bpmnFactory
*/
export default function DataInputAssociationBehavior(eventBus, bpmnFactory) {
CommandInterceptor.call(this, eventBus);
this.executed([
'connection.create',
'connection.delete',
'connection.move',
'connection.reconnectEnd'
], ifDataInputAssociation(fixTargetRef));
this.reverted([
'connection.create',
'connection.delete',
'connection.move',
'connection.reconnectEnd'
], ifDataInputAssociation(fixTargetRef));
function usesTargetRef(element, targetRef, removedConnection) {
var inputAssociations = element.get('dataInputAssociations');
return find(inputAssociations, function(association) {
return association !== removedConnection &&
association.targetRef === targetRef;
});
}
function getTargetRef(element, create) {
var properties = element.get('properties');
var targetRefProp = find(properties, function(p) {
return p.name === TARGET_REF_PLACEHOLDER_NAME;
});
if (!targetRefProp && create) {
targetRefProp = bpmnFactory.create('bpmn:Property', {
name: TARGET_REF_PLACEHOLDER_NAME
});
collectionAdd(properties, targetRefProp);
}
return targetRefProp;
}
function cleanupTargetRef(element, connection) {
var targetRefProp = getTargetRef(element);
if (!targetRefProp) {
return;
}
if (!usesTargetRef(element, targetRefProp, connection)) {
collectionRemove(element.get('properties'), targetRefProp);
}
}
/**
* Make sure targetRef is set to a valid property or
* `null` if the connection is detached.
*
* @param {Event} event
*/
function fixTargetRef(event) {
var context = event.context,
connection = context.connection,
connectionBo = connection.businessObject,
target = connection.target,
targetBo = target && target.businessObject,
newTarget = context.newTarget,
newTargetBo = newTarget && newTarget.businessObject,
oldTarget = context.oldTarget || context.target,
oldTargetBo = oldTarget && oldTarget.businessObject;
var dataAssociation = connection.businessObject,
targetRefProp;
if (oldTargetBo && oldTargetBo !== targetBo) {
cleanupTargetRef(oldTargetBo, connectionBo);
}
if (newTargetBo && newTargetBo !== targetBo) {
cleanupTargetRef(newTargetBo, connectionBo);
}
if (targetBo) {
targetRefProp = getTargetRef(targetBo, true);
dataAssociation.targetRef = targetRefProp;
} else {
dataAssociation.targetRef = null;
}
}
}
DataInputAssociationBehavior.$inject = [
'eventBus',
'bpmnFactory'
];
inherits(DataInputAssociationBehavior, CommandInterceptor);
/**
* Only call the given function when the event
* touches a bpmn:DataInputAssociation.
*
* @param {Function} fn
* @return {Function}
*/
function ifDataInputAssociation(fn) {
return function(event) {
var context = event.context,
connection = context.connection;
if (is(connection, 'bpmn:DataInputAssociation')) {
return fn(event);
}
};
}

View File

@@ -0,0 +1,207 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import { isAny } from '../util/ModelingUtil';
import UpdateSemanticParentHandler from '../cmd/UpdateSemanticParentHandler';
/**
* BPMN specific data store behavior
*/
export default function DataStoreBehavior(
canvas, commandStack, elementRegistry,
eventBus) {
CommandInterceptor.call(this, eventBus);
commandStack.registerHandler('dataStore.updateContainment', UpdateSemanticParentHandler);
function getFirstParticipant() {
return elementRegistry.filter(function(element) {
return is(element, 'bpmn:Participant');
})[0];
}
function getDataStores(element) {
return element.children.filter(function(child) {
return is(child, 'bpmn:DataStoreReference') && !child.labelTarget;
});
}
function updateDataStoreParent(dataStore, newDataStoreParent) {
var dataStoreBo = dataStore.businessObject || dataStore;
newDataStoreParent = newDataStoreParent || getFirstParticipant();
if (newDataStoreParent) {
var newDataStoreParentBo = newDataStoreParent.businessObject || newDataStoreParent;
commandStack.execute('dataStore.updateContainment', {
dataStoreBo: dataStoreBo,
newSemanticParent: newDataStoreParentBo.processRef || newDataStoreParentBo,
newDiParent: newDataStoreParentBo.di
});
}
}
// disable auto-resize for data stores
this.preExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label') {
if (!context.hints) {
context.hints = {};
}
// prevent auto resizing
context.hints.autoResize = false;
}
});
// disable auto-resize for data stores
this.preExecute('elements.move', function(event) {
var context = event.context,
shapes = context.shapes;
var dataStoreReferences = shapes.filter(function(shape) {
return is(shape, 'bpmn:DataStoreReference');
});
if (dataStoreReferences.length) {
if (!context.hints) {
context.hints = {};
}
// prevent auto resizing for data store references
context.hints.autoResize = shapes.filter(function(shape) {
return !is(shape, 'bpmn:DataStoreReference');
});
}
});
// update parent on data store created
this.postExecute('shape.create', function(event) {
var context = event.context,
shape = context.shape,
parent = shape.parent;
if (is(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label' &&
is(parent, 'bpmn:Collaboration')) {
updateDataStoreParent(shape);
}
});
// update parent on data store moved
this.postExecute('shape.move', function(event) {
var context = event.context,
shape = context.shape,
oldParent = context.oldParent,
parent = shape.parent;
if (is(oldParent, 'bpmn:Collaboration')) {
// do nothing if not necessary
return;
}
if (is(shape, 'bpmn:DataStoreReference') &&
shape.type !== 'label' &&
is(parent, 'bpmn:Collaboration')) {
var participant = is(oldParent, 'bpmn:Participant') ?
oldParent :
getAncestor(oldParent, 'bpmn:Participant');
updateDataStoreParent(shape, participant);
}
});
// update data store parents on participant or subprocess deleted
this.postExecute('shape.delete', function(event) {
var context = event.context,
shape = context.shape,
rootElement = canvas.getRootElement();
if (isAny(shape, [ 'bpmn:Participant', 'bpmn:SubProcess' ])
&& is(rootElement, 'bpmn:Collaboration')) {
getDataStores(rootElement)
.filter(function(dataStore) {
return isDescendant(dataStore, shape);
})
.forEach(function(dataStore) {
updateDataStoreParent(dataStore);
});
}
});
// update data store parents on collaboration -> process
this.postExecute('canvas.updateRoot', function(event) {
var context = event.context,
oldRoot = context.oldRoot,
newRoot = context.newRoot;
var dataStores = getDataStores(oldRoot);
dataStores.forEach(function(dataStore) {
if (is(newRoot, 'bpmn:Process')) {
updateDataStoreParent(dataStore, newRoot);
}
});
});
}
DataStoreBehavior.$inject = [
'canvas',
'commandStack',
'elementRegistry',
'eventBus',
];
inherits(DataStoreBehavior, CommandInterceptor);
// helpers //////////
function isDescendant(descendant, ancestor) {
var descendantBo = descendant.businessObject || descendant,
ancestorBo = ancestor.businessObject || ancestor;
while (descendantBo.$parent) {
if (descendantBo.$parent === ancestorBo.processRef || ancestorBo) {
return true;
}
descendantBo = descendantBo.$parent;
}
return false;
}
function getAncestor(element, type) {
while (element.parent) {
if (is(element.parent, type)) {
return element.parent;
}
element = element.parent;
}
}

View File

@@ -0,0 +1,112 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import {
getChildLanes
} from '../util/LaneUtil';
import {
eachElement
} from 'diagram-js/lib/util/Elements';
var LOW_PRIORITY = 500;
/**
* BPMN specific delete lane behavior
*/
export default function DeleteLaneBehavior(eventBus, modeling, spaceTool) {
CommandInterceptor.call(this, eventBus);
function compensateLaneDelete(shape, oldParent) {
var siblings = getChildLanes(oldParent);
var topAffected = [];
var bottomAffected = [];
eachElement(siblings, function(element) {
if (element.y > shape.y) {
bottomAffected.push(element);
} else {
topAffected.push(element);
}
return element.children;
});
if (!siblings.length) {
return;
}
var offset;
if (bottomAffected.length && topAffected.length) {
offset = shape.height / 2;
} else {
offset = shape.height;
}
var topAdjustments,
bottomAdjustments;
if (topAffected.length) {
topAdjustments = spaceTool.calculateAdjustments(
topAffected, 'y', offset, shape.y - 10);
spaceTool.makeSpace(
topAdjustments.movingShapes,
topAdjustments.resizingShapes,
{ x: 0, y: offset }, 's');
}
if (bottomAffected.length) {
bottomAdjustments = spaceTool.calculateAdjustments(
bottomAffected, 'y', -offset, shape.y + shape.height + 10);
spaceTool.makeSpace(
bottomAdjustments.movingShapes,
bottomAdjustments.resizingShapes,
{ x: 0, y: -offset }, 'n');
}
}
/**
* Adjust sizes of other lanes after lane deletion
*/
this.postExecuted('shape.delete', LOW_PRIORITY, function(event) {
var context = event.context,
hints = context.hints,
shape = context.shape,
oldParent = context.oldParent;
// only compensate lane deletes
if (!is(shape, 'bpmn:Lane')) {
return;
}
// compensate root deletes only
if (hints && hints.nested) {
return;
}
compensateLaneDelete(shape, oldParent);
});
}
DeleteLaneBehavior.$inject = [
'eventBus',
'modeling',
'spaceTool'
];
inherits(DeleteLaneBehavior, CommandInterceptor);

View File

@@ -0,0 +1,75 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
import { isLabel } from '../../../util/LabelUtil';
/**
* BPMN specific detach event behavior
*/
export default function DetachEventBehavior(eventBus, bpmnReplace) {
CommandInterceptor.call(this, eventBus);
/**
* replace boundary event with intermediate event when
* detaching from a shape
*/
this.preExecute('elements.move', function(context) {
var shapes = context.shapes,
host = context.newHost,
shape,
eventDefinition,
intermediateEvent,
newShape;
if (shapes.length !== 1) {
return;
}
shape = shapes[0];
if (!isLabel(shape) && !host && is(shape, 'bpmn:BoundaryEvent')) {
eventDefinition = getEventDefinition(shape);
if (eventDefinition) {
intermediateEvent = {
type: 'bpmn:IntermediateCatchEvent',
eventDefinitionType: eventDefinition.$type
};
} else {
intermediateEvent = {
type: 'bpmn:IntermediateThrowEvent'
};
}
newShape = bpmnReplace.replaceElement(shape, intermediateEvent, { layoutConnection: false });
context.shapes = [ newShape ];
}
}, true);
}
DetachEventBehavior.$inject = [
'eventBus',
'bpmnReplace'
];
inherits(DetachEventBehavior, CommandInterceptor);
// helper /////
function getEventDefinition(element) {
var bo = getBusinessObject(element);
return bo && bo.eventDefinitions && bo.eventDefinitions[0];
}

View File

@@ -0,0 +1,206 @@
import inherits from 'inherits';
import {
assign,
find,
filter
} from 'min-dash';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getApproxIntersection
} from 'diagram-js/lib/util/LineIntersection';
export default function DropOnFlowBehavior(eventBus, bpmnRules, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Reconnect start / end of a connection after
* dropping an element on a flow.
*/
function insertShape(shape, targetFlow, position) {
var waypoints = targetFlow.waypoints,
waypointsBefore,
waypointsAfter,
dockingPoint,
source,
target,
incomingConnection,
outgoingConnection,
oldOutgoing = shape.outgoing.slice(),
oldIncoming = shape.incoming.slice();
var intersection = getApproxIntersection(waypoints, position);
if (intersection) {
waypointsBefore = waypoints.slice(0, intersection.index);
waypointsAfter = waypoints.slice(intersection.index + (intersection.bendpoint ? 1 : 0));
// due to inaccuracy intersection might have been found
if (!waypointsBefore.length || !waypointsAfter.length) {
return;
}
dockingPoint = intersection.bendpoint ? waypoints[intersection.index] : position;
// if last waypointBefore is inside shape's bounds, ignore docking point
if (!isPointInsideBBox(shape, waypointsBefore[waypointsBefore.length-1])) {
waypointsBefore.push(copy(dockingPoint));
}
// if first waypointAfter is inside shape's bounds, ignore docking point
if (!isPointInsideBBox(shape, waypointsAfter[0])) {
waypointsAfter.unshift(copy(dockingPoint));
}
}
source = targetFlow.source;
target = targetFlow.target;
if (bpmnRules.canConnect(source, shape, targetFlow)) {
// reconnect source -> inserted shape
modeling.reconnectEnd(targetFlow, shape, waypointsBefore || position);
incomingConnection = targetFlow;
}
if (bpmnRules.canConnect(shape, target, targetFlow)) {
if (!incomingConnection) {
// reconnect inserted shape -> end
modeling.reconnectStart(targetFlow, shape, waypointsAfter || position);
outgoingConnection = targetFlow;
} else {
outgoingConnection = modeling.connect(
shape, target, { type: targetFlow.type, waypoints: waypointsAfter }
);
}
}
var duplicateConnections = [].concat(
incomingConnection && filter(oldIncoming, function(connection) {
return connection.source === incomingConnection.source;
}) || [],
outgoingConnection && filter(oldOutgoing, function(connection) {
return connection.source === outgoingConnection.source;
}) || []
);
if (duplicateConnections.length) {
modeling.removeElements(duplicateConnections);
}
}
this.preExecute('elements.move', function(context) {
var newParent = context.newParent,
shapes = context.shapes,
delta = context.delta,
shape = shapes[0];
if (!shape || !newParent) {
return;
}
// if the new parent is a connection,
// change it to the new parent's parent
if (newParent && newParent.waypoints) {
context.newParent = newParent = newParent.parent;
}
var shapeMid = getMid(shape);
var newShapeMid = {
x: shapeMid.x + delta.x,
y: shapeMid.y + delta.y
};
// find a connection which intersects with the
// element's mid point
var connection = find(newParent.children, function(element) {
var canInsert = bpmnRules.canInsert(shapes, element);
return canInsert && getApproxIntersection(element.waypoints, newShapeMid);
});
if (connection) {
context.targetFlow = connection;
context.position = newShapeMid;
}
}, true);
this.postExecuted('elements.move', function(context) {
var shapes = context.shapes,
targetFlow = context.targetFlow,
position = context.position;
if (targetFlow) {
insertShape(shapes[0], targetFlow, position);
}
}, true);
this.preExecute('shape.create', function(context) {
var parent = context.parent,
shape = context.shape;
if (bpmnRules.canInsert(shape, parent)) {
context.targetFlow = parent;
context.parent = parent.parent;
}
}, true);
this.postExecuted('shape.create', function(context) {
var shape = context.shape,
targetFlow = context.targetFlow,
position = context.position;
if (targetFlow) {
insertShape(shape, targetFlow, position);
}
}, true);
}
inherits(DropOnFlowBehavior, CommandInterceptor);
DropOnFlowBehavior.$inject = [
'eventBus',
'bpmnRules',
'modeling'
];
// helpers /////////////////////
function isPointInsideBBox(bbox, point) {
var x = point.x,
y = point.y;
return x >= bbox.x &&
x <= bbox.x + bbox.width &&
y >= bbox.y &&
y <= bbox.y + bbox.height;
}
function copy(obj) {
return assign({}, obj);
}
function getMid(bounds) {
return {
x: Math.round(bounds.x + bounds.width / 2),
y: Math.round(bounds.y + bounds.height / 2)
};
}

View File

@@ -0,0 +1,80 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
export default function EventBasedGatewayBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Remove existing sequence flows of event-based target before connecting
* from event-based gateway.
*/
this.preExecuted('connection.create', function(event) {
var source = event.context.source,
target = event.context.target,
existingIncomingConnections = target.incoming.slice();
if (
is(source, 'bpmn:EventBasedGateway') &&
target.incoming.length
) {
existingIncomingConnections.filter(isSequenceFlow)
.forEach(function(sequenceFlow) {
modeling.removeConnection(sequenceFlow);
});
}
});
/**
* After replacing shape with event-based gateway, remove incoming sequence
* flows of event-based targets which do not belong to event-based gateway
* source.
*/
this.preExecuted('shape.replace', function(event) {
var newShape = event.context.newShape,
newShapeTargets,
newShapeTargetsIncomingSequenceFlows;
if (!is(newShape, 'bpmn:EventBasedGateway')) {
return;
}
newShapeTargets = newShape.outgoing.filter(isSequenceFlow)
.map(function(sequenceFlow) {
return sequenceFlow.target;
});
newShapeTargetsIncomingSequenceFlows = newShapeTargets.reduce(function(sequenceFlows, target) {
var incomingSequenceFlows = target.incoming.filter(isSequenceFlow);
return sequenceFlows.concat(incomingSequenceFlows);
}, []);
newShapeTargetsIncomingSequenceFlows.forEach(function(sequenceFlow) {
if (sequenceFlow.source !== newShape) {
modeling.removeConnection(sequenceFlow);
}
});
});
}
EventBasedGatewayBehavior.$inject = [
'eventBus',
'modeling'
];
inherits(EventBasedGatewayBehavior, CommandInterceptor);
// helpers //////////////////////
function isSequenceFlow(connection) {
return is(connection, 'bpmn:SequenceFlow');
}

View File

@@ -0,0 +1,111 @@
import { getLanesRoot } from '../util/LaneUtil';
import { is } from '../../../util/ModelUtil';
import { isAny } from '../util/ModelingUtil';
var HIGH_PRIORITY = 1500;
var HIGHEST_PRIORITY = 2000;
/**
* Correct hover targets in certain situations to improve diagram interaction.
*
* @param {ElementRegistry} elementRegistry
* @param {EventBus} eventBus
* @param {Canvas} canvas
*/
export default function FixHoverBehavior(elementRegistry, eventBus, canvas) {
eventBus.on([
'create.hover',
'create.move',
'create.end',
'shape.move.hover',
'shape.move.move',
'shape.move.end'
], HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape || event.shape,
hover = event.hover;
// ensure elements are not dropped onto a bpmn:Lane but onto
// the underlying bpmn:Participant
if (is(hover, 'bpmn:Lane') && !isAny(shape, [ 'bpmn:Lane', 'bpmn:Participant' ])) {
event.hover = getLanesRoot(hover);
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
var rootElement = canvas.getRootElement();
// ensure bpmn:Group and label elements are dropped
// always onto the root
if (hover !== rootElement && (shape.labelTarget || is(shape, 'bpmn:Group'))) {
event.hover = rootElement;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'connect.hover',
'global-connect.hover'
], HIGH_PRIORITY, function(event) {
var hover = event.hover;
// ensure connections start/end on bpmn:Participant,
// not the underlying bpmn:Lane
if (is(hover, 'bpmn:Lane')) {
event.hover = getLanesRoot(hover) || hover;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'bendpoint.move.hover'
], HIGH_PRIORITY, function(event) {
var context = event.context,
type = context.type,
hover = event.hover;
// ensure reconnect start/end on bpmn:Participant,
// not the underlying bpmn:Lane
if (is(hover, 'bpmn:Lane') && /reconnect/.test(type)) {
event.hover = getLanesRoot(hover) || hover;
event.hoverGfx = elementRegistry.getGraphics(event.hover);
}
});
eventBus.on([
'connect.start'
], HIGH_PRIORITY, function(event) {
var context = event.context,
source = context.source;
// ensure connect start on bpmn:Participant,
// not the underlying bpmn:Lane
if (is(source, 'bpmn:Lane')) {
context.source = getLanesRoot(source) || source;
}
});
// allow movement of participants from lanes
eventBus.on('shape.move.start', HIGHEST_PRIORITY, function(event) {
var shape = event.shape;
if (is(shape, 'bpmn:Lane')) {
event.shape = getLanesRoot(shape) || shape;
}
});
}
FixHoverBehavior.$inject = [
'elementRegistry',
'eventBus',
'canvas'
];

View File

@@ -0,0 +1,192 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
add as collectionAdd,
remove as collectionRemove
} from 'diagram-js/lib/util/Collections';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
import {
createCategoryValue
} from './util/CategoryUtil';
/**
* BPMN specific Group behavior
*/
export default function GroupBehavior(eventBus, bpmnFactory, canvas, elementRegistry) {
CommandInterceptor.call(this, eventBus);
/**
* Gets process definitions
*
* @return {ModdleElement} definitions
*/
function getDefinitions() {
var rootElement = canvas.getRootElement(),
businessObject = getBusinessObject(rootElement);
return businessObject.$parent;
}
/**
* Removes a referenced category value for a given group shape
*
* @param {djs.model.Shape} shape
*/
function removeReferencedCategoryValue(shape) {
var businessObject = getBusinessObject(shape),
categoryValue = businessObject.categoryValueRef,
category = categoryValue.$parent;
if (!categoryValue) {
return;
}
collectionRemove(category.categoryValue, categoryValue);
// cleanup category if it is empty
if (category && !category.categoryValue.length) {
removeCategory(category);
}
}
/**
* Removes a given category from the definitions
*
* @param {ModdleElement} category
*/
function removeCategory(category) {
var definitions = getDefinitions();
collectionRemove(definitions.get('rootElements'), category);
}
/**
* Returns all group element in the current registry
*
* @return {Array<djs.model.shape>} a list of group shapes
*/
function getGroupElements() {
return elementRegistry.filter(function(e) {
return is(e, 'bpmn:Group');
});
}
/**
* Returns true if given categoryValue is referenced in one of the given elements
*
* @param {Array<djs.model.shape>} elements
* @param {ModdleElement} categoryValue
* @return {Boolean}
*/
function isReferenced(elements, categoryValue) {
return elements.some(function(e) {
var businessObject = getBusinessObject(e);
return businessObject.categoryValueRef
&& businessObject.categoryValueRef === categoryValue;
});
}
/**
* remove referenced category + value when group was deleted
*/
this.executed('shape.delete', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:Group')) {
var businessObject = getBusinessObject(shape),
categoryValueRef = businessObject.categoryValueRef,
groupElements = getGroupElements();
if (!isReferenced(groupElements, categoryValueRef)) {
removeReferencedCategoryValue(shape);
}
}
});
/**
* re-attach removed category
*/
this.reverted('shape.delete', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:Group')) {
var businessObject = getBusinessObject(shape),
categoryValueRef = businessObject.categoryValueRef,
definitions = getDefinitions(),
category = categoryValueRef ? categoryValueRef.$parent : null;
collectionAdd(category.get('categoryValue'), categoryValueRef);
collectionAdd(definitions.get('rootElements'), category);
}
});
/**
* create new category + value when group was created
*/
this.execute('shape.create', function(event) {
var context = event.context,
shape = context.shape,
businessObject = getBusinessObject(shape),
oldBusinessObject = shape.oldBusinessObject;
if (is(businessObject, 'bpmn:Group') && !businessObject.categoryValueRef) {
var definitions = getDefinitions(),
categoryValue = createCategoryValue(definitions, bpmnFactory);
// set name from copied group if existing
if (oldBusinessObject && oldBusinessObject.categoryValueRef) {
categoryValue.value = oldBusinessObject.categoryValueRef.value;
}
// link the reference to the Group
businessObject.categoryValueRef = categoryValue;
}
});
this.revert('shape.create', function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:Group')) {
removeReferencedCategoryValue(shape);
delete getBusinessObject(shape).categoryValueRef;
}
});
}
GroupBehavior.$inject = [
'eventBus',
'bpmnFactory',
'canvas',
'elementRegistry'
];
inherits(GroupBehavior, CommandInterceptor);

View File

@@ -0,0 +1,81 @@
import {
getMid
} from 'diagram-js/lib/layout/LayoutUtil';
import lineIntersect from './util/LineIntersect';
/**
* Fix broken dockings after DI imports.
*
* @param {EventBus} eventBus
*/
export default function ImportDockingFix(eventBus) {
function adjustDocking(startPoint, nextPoint, elementMid) {
var elementTop = {
x: elementMid.x,
y: elementMid.y - 50
};
var elementLeft = {
x: elementMid.x - 50,
y: elementMid.y
};
var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);
// original is horizontal or vertical center cross intersection
var centerIntersect;
if (verticalIntersect && horizontalIntersect) {
if (getDistance(verticalIntersect, elementMid) > getDistance(horizontalIntersect, elementMid)) {
centerIntersect = horizontalIntersect;
} else {
centerIntersect = verticalIntersect;
}
} else {
centerIntersect = verticalIntersect || horizontalIntersect;
}
startPoint.original = centerIntersect;
}
function fixDockings(connection) {
var waypoints = connection.waypoints;
adjustDocking(
waypoints[0],
waypoints[1],
getMid(connection.source)
);
adjustDocking(
waypoints[waypoints.length - 1],
waypoints[waypoints.length - 2],
getMid(connection.target)
);
}
eventBus.on('bpmnElement.added', function(e) {
var element = e.element;
if (element.waypoints) {
fixDockings(element);
}
});
}
ImportDockingFix.$inject = [
'eventBus'
];
// helpers //////////////////////
function getDistance(p1, p2) {
return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
}

View File

@@ -0,0 +1,41 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject
} from '../../../util/ModelUtil';
import {
isAny
} from '../util/ModelingUtil';
/**
* A component that makes sure that each created or updated
* Pool and Lane is assigned an isHorizontal property set to true.
*
* @param {EventBus} eventBus
*/
export default function IsHorizontalFix(eventBus) {
CommandInterceptor.call(this, eventBus);
var elementTypesToUpdate = [
'bpmn:Participant',
'bpmn:Lane'
];
this.executed([ 'shape.move', 'shape.create', 'shape.resize' ], function(event) {
var bo = getBusinessObject(event.context.shape);
if (isAny(bo, elementTypesToUpdate) && !bo.di.get('isHorizontal')) {
// set attribute directly to avoid modeling#updateProperty side effects
bo.di.set('isHorizontal', true);
}
});
}
IsHorizontalFix.$inject = [ 'eventBus' ];
inherits(IsHorizontalFix, CommandInterceptor);

View File

@@ -0,0 +1,381 @@
import {
assign
} from 'min-dash';
import inherits from 'inherits';
import {
is,
getBusinessObject
} from '../../../util/ModelUtil';
import {
isLabelExternal,
getExternalLabelMid,
hasExternalLabel
} from '../../../util/LabelUtil';
import {
getLabel
} from '../../label-editing/LabelUtil';
import {
getLabelAdjustment
} from './util/LabelLayoutUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getNewAttachPoint
} from 'diagram-js/lib/util/AttachUtil';
import {
getMid,
roundPoint
} from 'diagram-js/lib/layout/LayoutUtil';
import {
delta
} from 'diagram-js/lib/util/PositionUtil';
import {
sortBy
} from 'min-dash';
import {
getDistancePointLine,
perpendicularFoot
} from './util/GeometricUtil';
var DEFAULT_LABEL_DIMENSIONS = {
width: 90,
height: 20
};
var NAME_PROPERTY = 'name';
var TEXT_PROPERTY = 'text';
/**
* A component that makes sure that external labels are added
* together with respective elements and properly updated (DI wise)
* during move.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
* @param {BpmnFactory} bpmnFactory
* @param {TextRenderer} textRenderer
*/
export default function LabelBehavior(
eventBus, modeling, bpmnFactory,
textRenderer) {
CommandInterceptor.call(this, eventBus);
// update label if name property was updated
this.postExecute('element.updateProperties', function(e) {
var context = e.context,
element = context.element,
properties = context.properties;
if (NAME_PROPERTY in properties) {
modeling.updateLabel(element, properties[NAME_PROPERTY]);
}
if (TEXT_PROPERTY in properties
&& is(element, 'bpmn:TextAnnotation')) {
var newBounds = textRenderer.getTextAnnotationBounds(
{
x: element.x,
y: element.y,
width: element.width,
height: element.height
},
properties[TEXT_PROPERTY] || ''
);
modeling.updateLabel(element, properties.text, newBounds);
}
});
// create label shape after shape/connection was created
this.postExecute([ 'shape.create', 'connection.create' ], function(e) {
var context = e.context;
var element = context.shape || context.connection,
businessObject = element.businessObject;
if (!isLabelExternal(element)) {
return;
}
// only create label if attribute available
if (!getLabel(element)) {
return;
}
var labelCenter = getExternalLabelMid(element);
// we don't care about x and y
var labelDimensions = textRenderer.getExternalLabelBounds(
DEFAULT_LABEL_DIMENSIONS,
getLabel(element)
);
modeling.createLabel(element, labelCenter, {
id: businessObject.id + '_label',
businessObject: businessObject,
width: labelDimensions.width,
height: labelDimensions.height
});
});
// update label after label shape was deleted
this.postExecute('shape.delete', function(event) {
var context = event.context,
labelTarget = context.labelTarget,
hints = context.hints || {};
// check if label
if (labelTarget && hints.unsetLabel !== false) {
modeling.updateLabel(labelTarget, null, null, { removeShape: false });
}
});
// update di information on label creation
this.postExecute([ 'label.create' ], function(event) {
var context = event.context,
element = context.shape,
businessObject,
di;
// we want to trigger on real labels only
if (!element.labelTarget) {
return;
}
// we want to trigger on BPMN elements only
if (!is(element.labelTarget || element, 'bpmn:BaseElement')) {
return;
}
businessObject = element.businessObject,
di = businessObject.di;
if (!di.label) {
di.label = bpmnFactory.create('bpmndi:BPMNLabel', {
bounds: bpmnFactory.create('dc:Bounds')
});
}
assign(di.label.bounds, {
x: element.x,
y: element.y,
width: element.width,
height: element.height
});
});
function getVisibleLabelAdjustment(event) {
var context = event.context,
connection = context.connection,
label = connection.label,
hints = assign({}, context.hints),
newWaypoints = context.newWaypoints || connection.waypoints,
oldWaypoints = context.oldWaypoints;
if (typeof hints.startChanged === 'undefined') {
hints.startChanged = !!hints.connectionStart;
}
if (typeof hints.endChanged === 'undefined') {
hints.endChanged = !!hints.connectionEnd;
}
return getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
}
this.postExecute([
'connection.layout',
'connection.updateWaypoints'
], function(event) {
var label = event.context.connection.label,
labelAdjustment;
if (!label) {
return;
}
labelAdjustment = getVisibleLabelAdjustment(event);
modeling.moveShape(label, labelAdjustment);
});
// keep label position on shape replace
this.postExecute([ 'shape.replace' ], function(event) {
var context = event.context,
newShape = context.newShape,
oldShape = context.oldShape;
var businessObject = getBusinessObject(newShape);
if (businessObject
&& isLabelExternal(businessObject)
&& oldShape.label
&& newShape.label) {
newShape.label.x = oldShape.label.x;
newShape.label.y = oldShape.label.y;
}
});
// move external label after resizing
this.postExecute('shape.resize', function(event) {
var context = event.context,
shape = context.shape,
newBounds = context.newBounds,
oldBounds = context.oldBounds;
if (hasExternalLabel(shape)) {
var label = shape.label,
labelMid = getMid(label),
edges = asEdges(oldBounds);
// get nearest border point to label as reference point
var referencePoint = getReferencePoint(labelMid, edges);
var delta = getReferencePointDelta(referencePoint, oldBounds, newBounds);
modeling.moveShape(label, delta);
}
});
}
inherits(LabelBehavior, CommandInterceptor);
LabelBehavior.$inject = [
'eventBus',
'modeling',
'bpmnFactory',
'textRenderer'
];
// helpers //////////////////////
/**
* Calculates a reference point delta relative to a new position
* of a certain element's bounds
*
* @param {Point} point
* @param {Bounds} oldBounds
* @param {Bounds} newBounds
*
* @return {Delta} delta
*/
export function getReferencePointDelta(referencePoint, oldBounds, newBounds) {
var newReferencePoint = getNewAttachPoint(referencePoint, oldBounds, newBounds);
return roundPoint(delta(newReferencePoint, referencePoint));
}
/**
* Generates the nearest point (reference point) for a given point
* onto given set of lines
*
* @param {Array<Point, Point>} lines
* @param {Point} point
*
* @param {Point}
*/
export function getReferencePoint(point, lines) {
if (!lines.length) {
return;
}
var nearestLine = getNearestLine(point, lines);
return perpendicularFoot(point, nearestLine);
}
/**
* Convert the given bounds to a lines array containing all edges
*
* @param {Bounds|Point} bounds
*
* @return Array<Point>
*/
export function asEdges(bounds) {
return [
[ // top
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
}
],
[ // right
{
x: bounds.x + (bounds.width || 0),
y: bounds.y
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // bottom
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
},
{
x: bounds.x + (bounds.width || 0),
y: bounds.y + (bounds.height || 0)
}
],
[ // left
{
x: bounds.x,
y: bounds.y
},
{
x: bounds.x,
y: bounds.y + (bounds.height || 0)
}
]
];
}
/**
* Returns the nearest line for a given point by distance
* @param {Point} point
* @param Array<Point> lines
*
* @return Array<Point>
*/
function getNearestLine(point, lines) {
var distances = lines.map(function(l) {
return {
line: l,
distance: getDistancePointLine(point, l)
};
});
var sorted = sortBy(distances, 'distance');
return sorted[0].line;
}

View File

@@ -0,0 +1,50 @@
import { is } from '../../../util/ModelUtil';
var COLLAB_ERR_MSG = 'flow elements must be children of pools/participants',
PROCESS_ERR_MSG = 'participants cannot be pasted onto a non-empty process diagram';
export default function ModelingFeedback(eventBus, tooltips, translate) {
function showError(position, message, timeout) {
tooltips.add({
position: {
x: position.x + 5,
y: position.y + 5
},
type: 'error',
timeout: timeout || 2000,
html: '<div>' + message + '</div>'
});
}
eventBus.on([ 'shape.move.rejected', 'create.rejected' ], function(event) {
var context = event.context,
shape = context.shape,
target = context.target;
if (is(target, 'bpmn:Collaboration') && is(shape, 'bpmn:FlowNode')) {
showError(event, translate(COLLAB_ERR_MSG));
}
});
eventBus.on([ 'elements.paste.rejected' ], function(event) {
var context = event.context,
position = context.position,
target = context.target;
if (is(target, 'bpmn:Collaboration')) {
showError(position, translate(COLLAB_ERR_MSG));
}
if (is(target, 'bpmn:Process')) {
showError(position, translate(PROCESS_ERR_MSG), 3000);
}
});
}
ModelingFeedback.$inject = [
'eventBus',
'tooltips',
'translate'
];

View File

@@ -0,0 +1,82 @@
import inherits from 'inherits';
import { is } from '../../../util/ModelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import lineIntersect from './util/LineIntersect';
export default function RemoveElementBehavior(eventBus, bpmnRules, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* Combine sequence flows when deleting an element
* if there is one incoming and one outgoing
* sequence flow
*/
this.preExecute('shape.delete', function(e) {
var shape = e.context.shape;
// only handle [a] -> [shape] -> [b] patterns
if (shape.incoming.length !== 1 || shape.outgoing.length !== 1) {
return;
}
var inConnection = shape.incoming[0],
outConnection = shape.outgoing[0];
// only handle sequence flows
if (!is(inConnection, 'bpmn:SequenceFlow') || !is(outConnection, 'bpmn:SequenceFlow')) {
return;
}
if (bpmnRules.canConnect(inConnection.source, outConnection.target, inConnection)) {
// compute new, combined waypoints
var newWaypoints = getNewWaypoints(inConnection.waypoints, outConnection.waypoints);
modeling.reconnectEnd(inConnection, outConnection.target, newWaypoints);
}
});
}
inherits(RemoveElementBehavior, CommandInterceptor);
RemoveElementBehavior.$inject = [
'eventBus',
'bpmnRules',
'modeling'
];
// helpers //////////////////////
function getDocking(point) {
return point.original || point;
}
function getNewWaypoints(inWaypoints, outWaypoints) {
var intersection = lineIntersect(
getDocking(inWaypoints[inWaypoints.length - 2]),
getDocking(inWaypoints[inWaypoints.length - 1]),
getDocking(outWaypoints[1]),
getDocking(outWaypoints[0]));
if (intersection) {
return [].concat(
inWaypoints.slice(0, inWaypoints.length - 1),
[ intersection ],
outWaypoints.slice(1));
} else {
return [
getDocking(inWaypoints[0]),
getDocking(outWaypoints[outWaypoints.length - 1])
];
}
}

View File

@@ -0,0 +1,47 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
/**
* BPMN specific remove behavior
*/
export default function RemoveParticipantBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
/**
* morph collaboration diagram into process diagram
* after the last participant has been removed
*/
this.preExecute('shape.delete', function(context) {
var shape = context.shape,
parent = shape.parent;
// activate the behavior if the shape to be removed
// is a participant
if (is(shape, 'bpmn:Participant')) {
context.collaborationRoot = parent;
}
}, true);
this.postExecute('shape.delete', function(context) {
var collaborationRoot = context.collaborationRoot;
if (collaborationRoot && !collaborationRoot.businessObject.participants.length) {
// replace empty collaboration with process diagram
modeling.makeProcess();
}
}, true);
}
RemoveParticipantBehavior.$inject = [ 'eventBus', 'modeling' ];
inherits(RemoveParticipantBehavior, CommandInterceptor);

View File

@@ -0,0 +1,187 @@
import {
forEach,
find,
matchPattern
} from 'min-dash';
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
export default function ReplaceConnectionBehavior(eventBus, modeling, bpmnRules, injector) {
CommandInterceptor.call(this, eventBus);
var dragging = injector.get('dragging', false);
function fixConnection(connection) {
var source = connection.source,
target = connection.target,
parent = connection.parent;
// do not do anything if connection
// is already deleted (may happen due to other
// behaviors plugged-in before)
if (!parent) {
return;
}
var replacementType,
remove;
/**
* Check if incoming or outgoing connections
* can stay or could be substituted with an
* appropriate replacement.
*
* This holds true for SequenceFlow <> MessageFlow.
*/
if (is(connection, 'bpmn:SequenceFlow')) {
if (!bpmnRules.canConnectSequenceFlow(source, target)) {
remove = true;
}
if (bpmnRules.canConnectMessageFlow(source, target)) {
replacementType = 'bpmn:MessageFlow';
}
}
// transform message flows into sequence flows, if possible
if (is(connection, 'bpmn:MessageFlow')) {
if (!bpmnRules.canConnectMessageFlow(source, target)) {
remove = true;
}
if (bpmnRules.canConnectSequenceFlow(source, target)) {
replacementType = 'bpmn:SequenceFlow';
}
}
if (is(connection, 'bpmn:Association') && !bpmnRules.canConnectAssociation(source, target)) {
remove = true;
}
// remove invalid connection,
// unless it has been removed already
if (remove) {
modeling.removeConnection(connection);
}
// replace SequenceFlow <> MessageFlow
if (replacementType) {
modeling.connect(source, target, {
type: replacementType,
waypoints: connection.waypoints.slice()
});
}
}
function replaceReconnectedConnection(event) {
var context = event.context,
connection = context.connection,
source = context.newSource || connection.source,
target = context.newTarget || connection.target,
allowed,
replacement;
allowed = bpmnRules.canConnect(source, target);
if (!allowed || allowed.type === connection.type) {
return;
}
replacement = modeling.connect(source, target, {
type: allowed.type,
waypoints: connection.waypoints.slice()
});
// remove old connection
modeling.removeConnection(connection);
// replace connection in context to reconnect end/start
context.connection = replacement;
if (dragging) {
cleanDraggingSelection(connection, replacement);
}
}
// monkey-patch selection saved in dragging in order to not re-select non-existing connection
function cleanDraggingSelection(oldConnection, newConnection) {
var context = dragging.context(),
previousSelection = context && context.payload.previousSelection,
index;
// do nothing if not dragging or no selection was present
if (!previousSelection || !previousSelection.length) {
return;
}
index = previousSelection.indexOf(oldConnection);
if (index === -1) {
return;
}
previousSelection.splice(index, 1, newConnection);
}
// lifecycle hooks
this.postExecuted('elements.move', function(context) {
var closure = context.closure,
allConnections = closure.allConnections;
forEach(allConnections, fixConnection);
}, true);
this.preExecute([
'connection.reconnectStart',
'connection.reconnectEnd'
], replaceReconnectedConnection);
this.postExecuted('element.updateProperties', function(event) {
var context = event.context,
properties = context.properties,
element = context.element,
businessObject = element.businessObject,
connection;
// remove condition expression when morphing to default flow
if (properties.default) {
connection = find(
element.outgoing,
matchPattern({ id: element.businessObject.default.id })
);
if (connection) {
modeling.updateProperties(connection, { conditionExpression: undefined });
}
}
// remove default property from source when morphing to conditional flow
if (properties.conditionExpression && businessObject.sourceRef.default === businessObject) {
modeling.updateProperties(element.source, { default: undefined });
}
});
}
inherits(ReplaceConnectionBehavior, CommandInterceptor);
ReplaceConnectionBehavior.$inject = [
'eventBus',
'modeling',
'bpmnRules',
'injector'
];

View File

@@ -0,0 +1,127 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
forEach
} from 'min-dash';
import {
isEventSubProcess
} from '../../../util/DiUtil';
import { is } from '../../../util/ModelUtil';
/**
* Defines the behaviour of what happens to the elements inside a container
* that morphs into another BPMN element
*/
export default function ReplaceElementBehaviour(
eventBus, bpmnReplace, bpmnRules,
elementRegistry, selection, modeling) {
CommandInterceptor.call(this, eventBus);
this._bpmnReplace = bpmnReplace;
this._elementRegistry = elementRegistry;
this._selection = selection;
this._modeling = modeling;
this.postExecuted([ 'elements.move' ], 500, function(event) {
var context = event.context,
target = context.newParent,
newHost = context.newHost,
elements = [];
forEach(context.closure.topLevel, function(topLevelElements) {
if (isEventSubProcess(topLevelElements)) {
elements = elements.concat(topLevelElements.children);
} else {
elements = elements.concat(topLevelElements);
}
});
// Change target to host when the moving element is a `bpmn:BoundaryEvent`
if (elements.length === 1 && newHost) {
target = newHost;
}
var canReplace = bpmnRules.canReplace(elements, target);
if (canReplace) {
this.replaceElements(elements, canReplace.replacements, newHost);
}
}, this);
// update attachments if the host is replaced
this.postExecute([ 'shape.replace' ], 1500, function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape,
attachers = oldShape.attachers,
canReplace;
if (attachers && attachers.length) {
canReplace = bpmnRules.canReplace(attachers, newShape);
this.replaceElements(attachers, canReplace.replacements);
}
}, this);
this.postExecuted([ 'shape.replace' ], 1500, function(e) {
var context = e.context,
oldShape = context.oldShape,
newShape = context.newShape;
modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
modeling.updateProperties(newShape, { id: oldShape.id });
});
}
inherits(ReplaceElementBehaviour, CommandInterceptor);
ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements, newHost) {
var elementRegistry = this._elementRegistry,
bpmnReplace = this._bpmnReplace,
selection = this._selection,
modeling = this._modeling;
forEach(newElements, function(replacement) {
var newElement = {
type: replacement.newElementType
};
var oldElement = elementRegistry.get(replacement.oldElementId);
if (newHost && is(oldElement, 'bpmn:BoundaryEvent')) {
modeling.updateAttachment(oldElement, null);
}
var idx = elements.indexOf(oldElement);
elements[idx] = bpmnReplace.replaceElement(oldElement, newElement, { select: false });
if (newHost && is(elements[idx], 'bpmn:BoundaryEvent')) {
modeling.updateAttachment(elements[idx], newHost);
}
});
if (newElements) {
selection.select(elements);
}
};
ReplaceElementBehaviour.$inject = [
'eventBus',
'bpmnReplace',
'bpmnRules',
'elementRegistry',
'selection',
'modeling'
];

View File

@@ -0,0 +1,44 @@
import { is } from '../../../util/ModelUtil';
import { isExpanded } from '../../../util/DiUtil';
import { getParticipantResizeConstraints } from './util/ResizeUtil';
var HIGH_PRIORITY = 1500;
var PARTICIPANT_MIN_DIMENSIONS = { width: 300, height: 150 },
SUB_PROCESS_MIN_DIMENSIONS = { width: 140, height: 120 },
TEXT_ANNOTATION_MIN_DIMENSIONS = { width: 50, height: 30 };
/**
* Set minimum bounds/resize constraints on resize.
*
* @param {EventBus} eventBus
*/
export default function ResizeBehavior(eventBus) {
eventBus.on('resize.start', HIGH_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
direction = context.direction,
balanced = context.balanced;
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
context.resizeConstraints = getParticipantResizeConstraints(shape, direction, balanced);
}
if (is(shape, 'bpmn:Participant')) {
context.minDimensions = PARTICIPANT_MIN_DIMENSIONS;
}
if (is(shape, 'bpmn:SubProcess') && isExpanded(shape)) {
context.minDimensions = SUB_PROCESS_MIN_DIMENSIONS;
}
if (is(shape, 'bpmn:TextAnnotation')) {
context.minDimensions = TEXT_ANNOTATION_MIN_DIMENSIONS;
}
});
}
ResizeBehavior.$inject = [ 'eventBus' ];

View File

@@ -0,0 +1,62 @@
import { is } from '../../../util/ModelUtil';
import {
roundBounds
} from 'diagram-js/lib/layout/LayoutUtil';
import {
hasPrimaryModifier
} from 'diagram-js/lib/util/Mouse';
var SLIGHTLY_HIGHER_PRIORITY = 1001;
/**
* Invoke {@link Modeling#resizeLane} instead of
* {@link Modeling#resizeShape} when resizing a Lane
* or Participant shape.
*/
export default function ResizeLaneBehavior(eventBus, modeling) {
eventBus.on('resize.start', SLIGHTLY_HIGHER_PRIORITY + 500, function(event) {
var context = event.context,
shape = context.shape;
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
// should we resize the opposite lane(s) in
// order to compensate for the resize operation?
context.balanced = !hasPrimaryModifier(event);
}
});
/**
* Intercept resize end and call resize lane function instead.
*/
eventBus.on('resize.end', SLIGHTLY_HIGHER_PRIORITY, function(event) {
var context = event.context,
shape = context.shape,
canExecute = context.canExecute,
newBounds = context.newBounds;
if (is(shape, 'bpmn:Lane') || is(shape, 'bpmn:Participant')) {
if (canExecute) {
// ensure we have actual pixel values for new bounds
// (important when zoom level was > 1 during move)
newBounds = roundBounds(newBounds);
// perform the actual resize
modeling.resizeLane(shape, newBounds, context.balanced);
}
// stop propagation
return false;
}
});
}
ResizeLaneBehavior.$inject = [
'eventBus',
'modeling'
];

View File

@@ -0,0 +1,69 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import { is } from '../../../util/ModelUtil';
import { isExpanded } from '../../../util/DiUtil.js';
/**
* Add start event child by default when creating an expanded subprocess
* with create.start or replacing a task with an expanded subprocess.
*/
export default function SubProcessStartEventBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
eventBus.on('create.start', function(event) {
var shape = event.context.shape,
hints = event.context.hints;
hints.shouldAddStartEvent = is(shape, 'bpmn:SubProcess') && isExpanded(shape);
});
this.postExecuted('shape.create', function(event) {
var shape = event.context.shape,
hints = event.context.hints,
position;
if (!hints.shouldAddStartEvent) {
return;
}
position = calculatePositionRelativeToShape(shape);
modeling.createShape({ type: 'bpmn:StartEvent' }, position, shape);
});
this.postExecuted('shape.replace', function(event) {
var oldShape = event.context.oldShape,
newShape = event.context.newShape,
position;
if (
!is(newShape, 'bpmn:SubProcess') ||
!is(oldShape, 'bpmn:Task') ||
!isExpanded(newShape)
) {
return;
}
position = calculatePositionRelativeToShape(newShape);
modeling.createShape({ type: 'bpmn:StartEvent' }, position, newShape);
});
}
SubProcessStartEventBehavior.$inject = [
'eventBus',
'modeling'
];
inherits(SubProcessStartEventBehavior, CommandInterceptor);
// helpers //////////
function calculatePositionRelativeToShape(shape) {
return {
x: shape.x + shape.width / 6,
y: shape.y + shape.height / 2
};
}

View File

@@ -0,0 +1,145 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
import {
computeChildrenBBox
} from 'diagram-js/lib/features/resize/ResizeUtil';
var LOW_PRIORITY = 500;
export default function ToggleElementCollapseBehaviour(
eventBus, elementFactory, modeling,
resize) {
CommandInterceptor.call(this, eventBus);
function hideEmptyLables(children) {
if (children.length) {
children.forEach(function(child) {
if (child.type === 'label' && !child.businessObject.name) {
child.hidden = true;
}
});
}
}
function expandedBounds(shape, defaultSize) {
var children = shape.children,
newBounds = defaultSize,
visibleElements,
visibleBBox;
visibleElements = filterVisible(children).concat([ shape ]);
visibleBBox = computeChildrenBBox(visibleElements);
if (visibleBBox) {
// center to visibleBBox with max(defaultSize, childrenBounds)
newBounds.width = Math.max(visibleBBox.width, newBounds.width);
newBounds.height = Math.max(visibleBBox.height, newBounds.height);
newBounds.x = visibleBBox.x + (visibleBBox.width - newBounds.width) / 2;
newBounds.y = visibleBBox.y + (visibleBBox.height - newBounds.height) / 2;
} else {
// center to collapsed shape with defaultSize
newBounds.x = shape.x + (shape.width - newBounds.width) / 2;
newBounds.y = shape.y + (shape.height - newBounds.height) / 2;
}
return newBounds;
}
function collapsedBounds(shape, defaultSize) {
return {
x: shape.x + (shape.width - defaultSize.width) / 2,
y: shape.y + (shape.height - defaultSize.height) / 2,
width: defaultSize.width,
height: defaultSize.height
};
}
this.executed([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
var context = e.context,
shape = context.shape;
if (!is(shape, 'bpmn:SubProcess')) {
return;
}
if (!shape.collapsed) {
// all children got made visible through djs, hide empty labels
hideEmptyLables(shape.children);
// remove collapsed marker
getBusinessObject(shape).di.isExpanded = true;
} else {
// place collapsed marker
getBusinessObject(shape).di.isExpanded = false;
}
});
this.reverted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
var context = e.context;
var shape = context.shape;
// revert removing/placing collapsed marker
if (!shape.collapsed) {
getBusinessObject(shape).di.isExpanded = true;
} else {
getBusinessObject(shape).di.isExpanded = false;
}
});
this.postExecuted([ 'shape.toggleCollapse' ], LOW_PRIORITY, function(e) {
var shape = e.context.shape,
defaultSize = elementFactory._getDefaultSize(shape),
newBounds;
if (shape.collapsed) {
// resize to default size of collapsed shapes
newBounds = collapsedBounds(shape, defaultSize);
} else {
// resize to bounds of max(visible children, defaultSize)
newBounds = expandedBounds(shape, defaultSize);
}
modeling.resizeShape(shape, newBounds, null, {
autoResize: shape.collapsed ? false : 'nwse'
});
});
}
inherits(ToggleElementCollapseBehaviour, CommandInterceptor);
ToggleElementCollapseBehaviour.$inject = [
'eventBus',
'elementFactory',
'modeling'
];
// helpers //////////////////////
function filterVisible(elements) {
return elements.filter(function(e) {
return !e.hidden;
});
}

View File

@@ -0,0 +1,33 @@
import {
forEach
} from 'min-dash';
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
* Unclaims model IDs on element deletion.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function UnclaimIdBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute('elements.delete', function(event) {
var context = event.context,
elements = context.elements;
forEach(elements, function(element) {
modeling.unclaimId(element.businessObject.id, element.businessObject);
});
});
}
inherits(UnclaimIdBehavior, CommandInterceptor);
UnclaimIdBehavior.$inject = [ 'eventBus', 'modeling' ];

View File

@@ -0,0 +1,57 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
getBusinessObject,
is
} from '../../../util/ModelUtil';
/**
* A behavior that unsets the Default property of
* sequence flow source on element delete, if the
* removed element is the Gateway or Task's default flow.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function DeleteSequenceFlowBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.preExecute('connection.delete', function(event) {
var context = event.context,
connection = context.connection,
source = connection.source;
if (isDefaultFlow(connection, source)) {
modeling.updateProperties(source, {
'default': null
});
}
});
}
inherits(DeleteSequenceFlowBehavior, CommandInterceptor);
DeleteSequenceFlowBehavior.$inject = [
'eventBus',
'modeling'
];
// helpers //////////////////////
function isDefaultFlow(connection, source) {
if (!is(connection, 'bpmn:SequenceFlow')) {
return false;
}
var sourceBo = getBusinessObject(source),
sequenceFlow = getBusinessObject(connection);
return sourceBo.get('default') === sequenceFlow;
}

View File

@@ -0,0 +1,157 @@
import inherits from 'inherits';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
import {
is
} from '../../../util/ModelUtil';
var LOW_PRIORITY = 500,
HIGH_PRIORITY = 5000;
/**
* BPMN specific delete lane behavior
*/
export default function UpdateFlowNodeRefsBehavior(eventBus, modeling, translate) {
CommandInterceptor.call(this, eventBus);
/**
* Ok, this is it:
*
* We have to update the Lane#flowNodeRefs _and_
* FlowNode#lanes with every FlowNode move/resize and
* Lane move/resize.
*
* We want to group that stuff to recompute containments
* as efficient as possible.
*
* Yea!
*/
// the update context
var context;
function initContext() {
context = context || new UpdateContext();
context.enter();
return context;
}
function getContext() {
if (!context) {
throw new Error(translate('out of bounds release'));
}
return context;
}
function releaseContext() {
if (!context) {
throw new Error(translate('out of bounds release'));
}
var triggerUpdate = context.leave();
if (triggerUpdate) {
modeling.updateLaneRefs(context.flowNodes, context.lanes);
context = null;
}
return triggerUpdate;
}
var laneRefUpdateEvents = [
'spaceTool',
'lane.add',
'lane.resize',
'lane.split',
'elements.move',
'elements.delete',
'shape.create',
'shape.delete',
'shape.move',
'shape.resize'
];
// listen to a lot of stuff to group lane updates
this.preExecute(laneRefUpdateEvents, HIGH_PRIORITY, function(event) {
initContext();
});
this.postExecuted(laneRefUpdateEvents, LOW_PRIORITY, function(event) {
releaseContext();
});
// Mark flow nodes + lanes that need an update
this.preExecute([
'shape.create',
'shape.move',
'shape.delete',
'shape.resize'
], function(event) {
var context = event.context,
shape = context.shape;
var updateContext = getContext();
// no need to update labels
if (shape.labelTarget) {
return;
}
if (is(shape, 'bpmn:Lane')) {
updateContext.addLane(shape);
}
if (is(shape, 'bpmn:FlowNode')) {
updateContext.addFlowNode(shape);
}
});
}
UpdateFlowNodeRefsBehavior.$inject = [
'eventBus',
'modeling' ,
'translate'
];
inherits(UpdateFlowNodeRefsBehavior, CommandInterceptor);
function UpdateContext() {
this.flowNodes = [];
this.lanes = [];
this.counter = 0;
this.addLane = function(lane) {
this.lanes.push(lane);
};
this.addFlowNode = function(flowNode) {
this.flowNodes.push(flowNode);
};
this.enter = function() {
this.counter++;
};
this.leave = function() {
this.counter--;
return !this.counter;
};
}

View File

@@ -0,0 +1,98 @@
import AdaptiveLabelPositioningBehavior from './AdaptiveLabelPositioningBehavior';
import AppendBehavior from './AppendBehavior';
import AttachEventBehavior from './AttachEventBehavior';
import BoundaryEventBehavior from './BoundaryEventBehavior';
import CopyPasteBehavior from './CopyPasteBehavior';
import FixHoverBehavior from './FixHoverBehavior';
import CreateBoundaryEventBehavior from './CreateBoundaryEventBehavior';
import CreateDataObjectBehavior from './CreateDataObjectBehavior';
import CreateParticipantBehavior from './CreateParticipantBehavior';
import DataInputAssociationBehavior from './DataInputAssociationBehavior';
import DataStoreBehavior from './DataStoreBehavior';
import DeleteLaneBehavior from './DeleteLaneBehavior';
import DetachEventBehavior from './DetachEventBehavior';
import DropOnFlowBehavior from './DropOnFlowBehavior';
import EventBasedGatewayBehavior from './EventBasedGatewayBehavior';
import GroupBehavior from './GroupBehavior';
import ImportDockingFix from './ImportDockingFix';
import IsHorizontalFix from './IsHorizontalFix';
import LabelBehavior from './LabelBehavior';
import ModelingFeedback from './ModelingFeedback';
import ReplaceConnectionBehavior from './ReplaceConnectionBehavior';
import RemoveParticipantBehavior from './RemoveParticipantBehavior';
import ReplaceElementBehaviour from './ReplaceElementBehaviour';
import ResizeBehavior from './ResizeBehavior';
import ResizeLaneBehavior from './ResizeLaneBehavior';
import RemoveElementBehavior from './RemoveElementBehavior';
import SubProcessStartEventBehavior from './SubProcessStartEventBehavior';
import ToggleElementCollapseBehaviour from './ToggleElementCollapseBehaviour';
import UnclaimIdBehavior from './UnclaimIdBehavior';
import UpdateFlowNodeRefsBehavior from './UpdateFlowNodeRefsBehavior';
import UnsetDefaultFlowBehavior from './UnsetDefaultFlowBehavior';
export default {
__init__: [
'adaptiveLabelPositioningBehavior',
'appendBehavior',
'attachEventBehavior',
'boundaryEventBehavior',
'copyPasteBehavior',
'fixHoverBehavior',
'createBoundaryEventBehavior',
'createDataObjectBehavior',
'createParticipantBehavior',
'dataStoreBehavior',
'dataInputAssociationBehavior',
'deleteLaneBehavior',
'detachEventBehavior',
'dropOnFlowBehavior',
'eventBasedGatewayBehavior',
'groupBehavior',
'importDockingFix',
'isHorizontalFix',
'labelBehavior',
'modelingFeedback',
'removeElementBehavior',
'removeParticipantBehavior',
'replaceConnectionBehavior',
'replaceElementBehaviour',
'resizeBehavior',
'resizeLaneBehavior',
'toggleElementCollapseBehaviour',
'subProcessStartEventBehavior',
'unclaimIdBehavior',
'unsetDefaultFlowBehavior',
'updateFlowNodeRefsBehavior'
],
adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
appendBehavior: [ 'type', AppendBehavior ],
attachEventBehavior: [ 'type', AttachEventBehavior ],
boundaryEventBehavior: [ 'type', BoundaryEventBehavior ],
copyPasteBehavior: [ 'type', CopyPasteBehavior ],
fixHoverBehavior: [ 'type', FixHoverBehavior ],
createBoundaryEventBehavior: [ 'type', CreateBoundaryEventBehavior ],
createDataObjectBehavior: [ 'type', CreateDataObjectBehavior ],
createParticipantBehavior: [ 'type', CreateParticipantBehavior ],
dataInputAssociationBehavior: [ 'type', DataInputAssociationBehavior ],
dataStoreBehavior: [ 'type', DataStoreBehavior ],
deleteLaneBehavior: [ 'type', DeleteLaneBehavior ],
detachEventBehavior: [ 'type', DetachEventBehavior ],
dropOnFlowBehavior: [ 'type', DropOnFlowBehavior ],
eventBasedGatewayBehavior: [ 'type', EventBasedGatewayBehavior ],
groupBehavior: [ 'type', GroupBehavior ],
importDockingFix: [ 'type', ImportDockingFix ],
isHorizontalFix: [ 'type', IsHorizontalFix ],
labelBehavior: [ 'type', LabelBehavior ],
modelingFeedback: [ 'type', ModelingFeedback ],
replaceConnectionBehavior: [ 'type', ReplaceConnectionBehavior ],
removeParticipantBehavior: [ 'type', RemoveParticipantBehavior ],
replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
resizeBehavior: [ 'type', ResizeBehavior ],
resizeLaneBehavior: [ 'type', ResizeLaneBehavior ],
removeElementBehavior: [ 'type', RemoveElementBehavior ],
toggleElementCollapseBehaviour : [ 'type', ToggleElementCollapseBehaviour ],
subProcessStartEventBehavior: [ 'type', SubProcessStartEventBehavior ],
unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
updateFlowNodeRefsBehavior: [ 'type', UpdateFlowNodeRefsBehavior ],
unsetDefaultFlowBehavior: [ 'type', UnsetDefaultFlowBehavior ]
};

View File

@@ -0,0 +1,30 @@
import {
add as collectionAdd
} from 'diagram-js/lib/util/Collections';
import {
getBusinessObject
} from '../../../../util/ModelUtil';
/**
* Creates a new bpmn:CategoryValue inside a new bpmn:Category
*
* @param {ModdleElement} definitions
* @param {BpmnFactory} bpmnFactory
*
* @return {ModdleElement} categoryValue.
*/
export function createCategoryValue(definitions, bpmnFactory) {
var categoryValue = bpmnFactory.create('bpmn:CategoryValue'),
category = bpmnFactory.create('bpmn:Category', {
categoryValue: [ categoryValue ]
});
// add to correct place
collectionAdd(definitions.get('rootElements'), category);
getBusinessObject(category).$parent = definitions;
getBusinessObject(categoryValue).$parent = category;
return categoryValue;
}

View File

@@ -0,0 +1,120 @@
/**
* Returns the length of a vector
*
* @param {Vector}
* @return {Float}
*/
export function vectorLength(v) {
return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
}
/**
* Calculates the angle between a line a the yAxis
*
* @param {Array}
* @return {Float}
*/
export function getAngle(line) {
// return value is between 0, 180 and -180, -0
// @janstuemmel: maybe replace return a/b with b/a
return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
}
/**
* Rotates a vector by a given angle
*
* @param {Vector}
* @param {Float} Angle in radians
* @return {Vector}
*/
export function rotateVector(vector, angle) {
return (!angle) ? vector : {
x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
};
}
/**
* Solves a 2D equation system
* a + r*b = c, where a,b,c are 2D vectors
*
* @param {Vector}
* @param {Vector}
* @param {Vector}
* @return {Float}
*/
function solveLambaSystem(a, b, c) {
// the 2d system
var system = [
{ n: a[0] - c[0], lambda: b[0] },
{ n: a[1] - c[1], lambda: b[1] }
];
// solve
var n = system[0].n * b[0] + system[1].n * b[1],
l = system[0].lambda * b[0] + system[1].lambda * b[1];
return -n/l;
}
/**
* Position of perpendicular foot
*
* @param {Point}
* @param [ {Point}, {Point} ] line defined throug two points
* @return {Point} the perpendicular foot position
*/
export function perpendicularFoot(point, line) {
var a = line[0], b = line[1];
// relative position of b from a
var bd = { x: b.x - a.x, y: b.y - a.y };
// solve equation system to the parametrized vectors param real value
var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);
return { x: a.x + r*bd.x, y: a.y + r*bd.y };
}
/**
* Calculates the distance between a point and a line
*
* @param {Point}
* @param [ {Point}, {Point} ] line defined throug two points
* @return {Float} distance
*/
export function getDistancePointLine(point, line) {
var pfPoint = perpendicularFoot(point, line);
// distance vector
var connectionVector = {
x: pfPoint.x - point.x,
y: pfPoint.y - point.y
};
return vectorLength(connectionVector);
}
/**
* Calculates the distance between two points
*
* @param {Point}
* @param {Point}
* @return {Float} distance
*/
export function getDistancePointPoint(point1, point2) {
return vectorLength({
x: point1.x - point2.x,
y: point1.y - point2.y
});
}

View File

@@ -0,0 +1,222 @@
import {
getDistancePointPoint,
rotateVector,
getAngle
} from './GeometricUtil';
import {
getAttachment
} from './LineAttachmentUtil';
import {
roundPoint
} from 'diagram-js/lib/layout/LayoutUtil';
export function findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
var index = attachment.segmentIndex;
var offset = newWaypoints.length - oldWaypoints.length;
// segmentMove happend
if (hints.segmentMove) {
var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
// if label was on moved segment return new segment index
if (index === oldSegmentStartIndex) {
return newSegmentStartIndex;
}
// label is after new segment index
if (index >= newSegmentStartIndex) {
return (index+offset < newSegmentStartIndex) ? newSegmentStartIndex : index+offset;
}
// if label is before new segment index
return index;
}
// bendpointMove happend
if (hints.bendpointMove) {
var insert = hints.bendpointMove.insert,
bendpointIndex = hints.bendpointMove.bendpointIndex,
newIndex;
// waypoints length didnt change
if (offset === 0) {
return index;
}
// label behind new/removed bendpoint
if (index >= bendpointIndex) {
newIndex = insert ? index + 1 : index - 1;
}
// label before new/removed bendpoint
if (index < bendpointIndex) {
newIndex = index;
// decide label should take right or left segment
if (insert && attachment.type !== 'bendpoint' && bendpointIndex-1 === index) {
var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
if (rel < attachment.relativeLocation) {
newIndex++;
}
}
}
return newIndex;
}
// start/end changed
if (offset === 0) {
return index;
}
if (hints.connectionStart) {
return (index === 0) ? 0 : null;
}
if (hints.connectionEnd) {
return (index === oldWaypoints.length - 2) ? newWaypoints.length - 2 : null;
}
// if nothing fits, return null
return null;
}
/**
* Calculate the required adjustment (move delta) for the given label
* after the connection waypoints got updated.
*
* @param {djs.model.Label} label
* @param {Array<Point>} newWaypoints
* @param {Array<Point>} oldWaypoints
* @param {Object} hints
*
* @return {Point} delta
*/
export function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
var x = 0,
y = 0;
var labelPosition = getLabelMid(label);
// get closest attachment
var attachment = getAttachment(labelPosition, oldWaypoints),
oldLabelLineIndex = attachment.segmentIndex,
newLabelLineIndex = findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
if (newLabelLineIndex === null) {
return { x: x, y: y };
}
// should never happen
// TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
if (newLabelLineIndex < 0 ||
newLabelLineIndex > newWaypoints.length - 2) {
return { x: x, y: y };
}
var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
newLabelLine = getLine(newWaypoints, newLabelLineIndex),
oldFoot = attachment.position;
var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
// special rule if label on bendpoint
if (attachment.type === 'bendpoint') {
var offset = newWaypoints.length - oldWaypoints.length,
oldBendpointIndex = attachment.bendpointIndex,
oldBendpoint = oldWaypoints[oldBendpointIndex];
// bendpoint position hasnt changed, return same position
if (newWaypoints.indexOf(oldBendpoint) !== -1) {
return { x: x, y: y };
}
// new bendpoint and old bendpoint have same index, then just return the offset
if (offset === 0) {
var newBendpoint = newWaypoints[oldBendpointIndex];
return {
x: newBendpoint.x - attachment.position.x,
y: newBendpoint.y - attachment.position.y
};
}
// if bendpoints get removed
if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
}
}
var newFoot = {
x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
};
// the rotated vector to label
var newLabelVector = rotateVector({
x: labelPosition.x - oldFoot.x,
y: labelPosition.y - oldFoot.y
}, angleDelta);
// the new relative position
x = newFoot.x + newLabelVector.x - labelPosition.x;
y = newFoot.y + newLabelVector.y - labelPosition.y;
return roundPoint({
x: x,
y: y
});
}
// HELPERS //////////////////////
function relativePositionMidWaypoint(waypoints, idx) {
var distanceSegment1 = getDistancePointPoint(waypoints[idx-1], waypoints[idx]),
distanceSegment2 = getDistancePointPoint(waypoints[idx], waypoints[idx+1]);
var relativePosition = distanceSegment1 / (distanceSegment1 + distanceSegment2);
return relativePosition;
}
function getLabelMid(label) {
return {
x: label.x + label.width / 2,
y: label.y + label.height / 2
};
}
function getAngleDelta(l1, l2) {
var a1 = getAngle(l1),
a2 = getAngle(l2);
return a2 - a1;
}
function getLine(waypoints, idx) {
return [ waypoints[idx], waypoints[idx+1] ];
}
function getRelativeFootPosition(line, foot) {
var length = getDistancePointPoint(line[0], line[1]),
lengthToFoot = getDistancePointPoint(line[0], foot);
return length === 0 ? 0 : lengthToFoot / length;
}

View File

@@ -0,0 +1,229 @@
var sqrt = Math.sqrt,
min = Math.min,
max = Math.max,
abs = Math.abs;
/**
* Calculate the square (power to two) of a number.
*
* @param {Number} n
*
* @return {Number}
*/
function sq(n) {
return Math.pow(n, 2);
}
/**
* Get distance between two points.
*
* @param {Point} p1
* @param {Point} p2
*
* @return {Number}
*/
function getDistance(p1, p2) {
return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
}
/**
* Return the attachment of the given point on the specified line.
*
* The attachment is either a bendpoint (attached to the given point)
* or segment (attached to a location on a line segment) attachment:
*
* ```javascript
* var pointAttachment = {
* type: 'bendpoint',
* bendpointIndex: 3,
* position: { x: 10, y: 10 } // the attach point on the line
* };
*
* var segmentAttachment = {
* type: 'segment',
* segmentIndex: 2,
* relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
* position: { x: 10, y: 10 } // the attach point on the line
* };
* ```
*
* @param {Point} point
* @param {Array<Point>} line
*
* @return {Object} attachment
*/
export function getAttachment(point, line) {
var idx = 0,
segmentStart,
segmentEnd,
segmentStartDistance,
segmentEndDistance,
attachmentPosition,
minDistance,
intersections,
attachment,
attachmentDistance,
closestAttachmentDistance,
closestAttachment;
for (idx = 0; idx < line.length - 1; idx++) {
segmentStart = line[idx];
segmentEnd = line[idx + 1];
if (pointsEqual(segmentStart, segmentEnd)) {
intersections = [ segmentStart ];
} else {
segmentStartDistance = getDistance(point, segmentStart);
segmentEndDistance = getDistance(point, segmentEnd);
minDistance = min(segmentStartDistance, segmentEndDistance);
intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
}
if (intersections.length < 1) {
throw new Error('expected between [1, 2] circle -> line intersections');
}
// one intersection -> bendpoint attachment
if (intersections.length === 1) {
attachment = {
type: 'bendpoint',
position: intersections[0],
segmentIndex: idx,
bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
};
}
// two intersections -> segment attachment
if (intersections.length === 2) {
attachmentPosition = mid(intersections[0], intersections[1]);
attachment = {
type: 'segment',
position: attachmentPosition,
segmentIndex: idx,
relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
};
}
attachmentDistance = getDistance(attachment.position, point);
if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
closestAttachment = attachment;
closestAttachmentDistance = attachmentDistance;
}
}
return closestAttachment;
}
/**
* Gets the intersection between a circle and a line segment.
*
* @param {Point} s1 segment start
* @param {Point} s2 segment end
* @param {Point} cc circle center
* @param {Number} cr circle radius
*
* @return {Array<Point>} intersections
*/
function getCircleSegmentIntersections(s1, s2, cc, cr) {
var baX = s2.x - s1.x;
var baY = s2.y - s1.y;
var caX = cc.x - s1.x;
var caY = cc.y - s1.y;
var a = baX * baX + baY * baY;
var bBy2 = baX * caX + baY * caY;
var c = caX * caX + caY * caY - cr * cr;
var pBy2 = bBy2 / a;
var q = c / a;
var disc = pBy2 * pBy2 - q;
// check against negative value to work around
// negative, very close to zero results (-4e-15)
// being produced in some environments
if (disc < 0 && disc > -0.000001) {
disc = 0;
}
if (disc < 0) {
return [];
}
// if disc == 0 ... dealt with later
var tmpSqrt = sqrt(disc);
var abScalingFactor1 = -pBy2 + tmpSqrt;
var abScalingFactor2 = -pBy2 - tmpSqrt;
var i1 = {
x: s1.x - baX * abScalingFactor1,
y: s1.y - baY * abScalingFactor1
};
if (disc === 0) { // abScalingFactor1 == abScalingFactor2
return [ i1 ];
}
var i2 = {
x: s1.x - baX * abScalingFactor2,
y: s1.y - baY * abScalingFactor2
};
// return only points on line segment
return [ i1, i2 ].filter(function(p) {
return isPointInSegment(p, s1, s2);
});
}
function isPointInSegment(p, segmentStart, segmentEnd) {
return (
fenced(p.x, segmentStart.x, segmentEnd.x) &&
fenced(p.y, segmentStart.y, segmentEnd.y)
);
}
function fenced(n, rangeStart, rangeEnd) {
// use matching threshold to work around
// precisison errors in intersection computation
return (
n >= min(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
n <= max(rangeStart, rangeEnd) + EQUAL_THRESHOLD
);
}
/**
* Calculate mid of two points.
*
* @param {Point} p1
* @param {Point} p2
*
* @return {Point}
*/
function mid(p1, p2) {
return {
x: (p1.x + p2.x) / 2,
y: (p1.y + p2.y) / 2
};
}
var EQUAL_THRESHOLD = 0.1;
function pointsEqual(p1, p2) {
return (
abs(p1.x - p2.x) <= EQUAL_THRESHOLD &&
abs(p1.y - p2.y) <= EQUAL_THRESHOLD
);
}

View File

@@ -0,0 +1,35 @@
/**
* Returns the intersection between two line segments a and b.
*
* @param {Point} l1s
* @param {Point} l1e
* @param {Point} l2s
* @param {Point} l2e
*
* @return {Point}
*/
export default function lineIntersect(l1s, l1e, l2s, l2e) {
// if the lines intersect, the result contains the x and y of the
// intersection (treating the lines as infinite) and booleans for
// whether line segment 1 or line segment 2 contain the point
var denominator, a, b, c, numerator;
denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));
if (denominator == 0) {
return null;
}
a = l1s.y - l2s.y;
b = l1s.x - l2s.x;
numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);
c = numerator / denominator;
// if we cast these lines infinitely in
// both directions, they intersect here
return {
x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
};
}

Some files were not shown because too many files have changed in this diff Show More