├── .dockerignore
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .gitlab-ci.yml
├── .travis.yml
├── Dockerfile
├── README.md
├── app
├── app.js
├── diagrams
│ ├── EventBasedGateway.bpmn
│ ├── ManyErrorsAndWarnings.bpmn
│ ├── multiple.bpmn
│ ├── newDiagram.bpmn
│ ├── pizzaDelivery.bpmn
│ ├── subChoreographies.bpmn
│ └── subChoreographies.png
├── favicon.ico
├── icons
│ ├── LICENSE.txt
│ ├── config.json
│ ├── css
│ │ └── chor-editor.css
│ └── font
│ │ ├── chor-editor.eot
│ │ ├── chor-editor.svg
│ │ ├── chor-editor.ttf
│ │ ├── chor-editor.woff
│ │ └── chor-editor.woff2
├── index.html
├── lib
│ ├── properties-provider
│ │ ├── ChorPropertiesProvider.js
│ │ ├── MessageDefinition.js
│ │ └── index.js
│ └── validator
│ │ ├── Validator.js
│ │ ├── constraints
│ │ ├── CallChoreoParticipantsBijectivityConstraint.js
│ │ ├── EventBasedGatewayConstraint.js
│ │ ├── NoCyclicCallChoreosConstraint.js
│ │ ├── ParticipantNameConstraint.js
│ │ ├── SimpleFlowConstraint.js
│ │ └── SubChoreoParticipantsConstraint.js
│ │ └── util
│ │ └── ValidatorUtil.js
└── styles
│ └── app.less
├── package-lock.json
└── package.json
/.dockerignore:
--------------------------------------------------------------------------------
1 | README.md
2 | .gitignore
3 |
4 | .DS_Store
5 | node_modules
6 | dist
7 | build
8 | .cache
9 | .idea/
10 | .eslintignore
11 | .eslintrc
12 | .travis.yml
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | node_modules
3 | build
4 | .cache
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "plugin:bpmn-io/es6",
3 | "env": {
4 | "browser": true,
5 | "es6": true
6 | }
7 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | dist
4 | *.log
5 | .idea/
6 | .cache/
7 | build
8 |
--------------------------------------------------------------------------------
/.gitlab-ci.yml:
--------------------------------------------------------------------------------
1 | stages:
2 | - build
3 | - deploy
4 |
5 | buildImage:
6 | stage: build
7 | tags:
8 | - dockerbuilder
9 | script:
10 | - docker build -t bptlab/chor-js-demo:master .
11 | - docker push bptlab/chor-js-demo:master
12 |
13 | deployOnDockerHost:
14 | stage: deploy
15 | only:
16 | - master
17 | image: alpine:3.12
18 | before_script:
19 | - apk update
20 | # configure ssh tools
21 | - 'which ssh-agent || ( apk add openssh-client )'
22 | - mkdir -p ~/.ssh
23 | - eval $(ssh-agent -s)
24 | # take care to deploy private key in GitLab as corresponding env var
25 | - echo "$identitykey" | ssh-add -
26 | script:
27 | - ssh -o StrictHostKeyChecking=no $deployuser@$targethost '/var/docker-routines/chor-js-demo/deploy.sh'
28 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "12"
4 |
5 | cache:
6 | directories:
7 | - node_modules
8 |
9 | script:
10 | - npm run lint
11 | - npm run build
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:12 as builder
2 |
3 | WORKDIR /usr/src
4 | # copy both package and package-lock
5 | COPY package*.json ./
6 |
7 | COPY app ./app
8 | RUN npm install
9 | RUN npm run build
10 |
11 | # copy built files over from first stage
12 | FROM node:current-alpine
13 | EXPOSE 9013
14 | COPY --from=builder /usr/src/build /usr/src/build
15 | WORKDIR /usr/src
16 | RUN npm install http-server -g
17 | CMD http-server ./build -p 9013
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # chor-js-demo
2 |
3 | __[:rocket: Live Version :rocket:](https://bpt-lab.org/chor-js-demo/)__
4 |
5 | A simple demo application showing the usage of the _npm package_ of [`chor-js`](https://github.com/bptlab/chor-js) to view and edit BPMN 2.0 choreography diagrams in the browser.
6 |
7 | The demo also adds some features such as diagram upload and download, and a [validator](./app/lib/validator).
8 |
9 | > For an example on how to use the pre-packaged version of chor-js, please refer to the [README there](https://github.com/bptlab/chor-js).
10 |
11 | ## Local Usage
12 |
13 | ### Node
14 |
15 | You can install and run the demo locally using Node.js.
16 |
17 | #### Run Only
18 |
19 | ```shell
20 | npm install
21 | npm run dev
22 | ```
23 |
24 | You can also build it using `npm run build`.
25 |
26 | The demo is then served to `http://localhost:9013`.
27 | We use [Parcel](https://parceljs.org) as a build tool.
28 | Thus, unless you set up the project as a development environment (see below), chor-js will not be transpiled and polyfilled, which should be no problem for modern browsers.
29 |
30 | #### Development Environment
31 |
32 | If you want to use the demo while developing [chor-js](https://github.com/bptlab/chor-js), you can link the two repositories:
33 |
34 | ```shell
35 | git clone https://github.com/bptlab/chor-js.git
36 | cd chor-js
37 | npm install
38 | npm link
39 |
40 | cd ..
41 | git clone https://github.com/bptlab/chor-js-demo.git
42 | cd chor-js-demo
43 | npm install
44 | npm link chor-js
45 | npm run dev
46 | ```
47 |
48 | ### Docker
49 |
50 | We also provide a `Dockerfile` to use with Docker.
51 |
52 | ```shell
53 | docker build . -t chor-js-demo
54 | docker run --rm -p 9013:9013 --name chor-js-demo -it chor-js-demo
55 | ```
56 |
57 | The demo is then served to `http://localhost:9013` as a production build using the latest version of chor-js (see Dockerfile).
58 |
59 | ## License
60 |
61 | MIT
62 |
--------------------------------------------------------------------------------
/app/app.js:
--------------------------------------------------------------------------------
1 | import ChoreoModeler from 'chor-js/lib/Modeler';
2 | import PropertiesPanelModule from 'bpmn-js-properties-panel';
3 |
4 | import Reporter from './lib/validator/Validator.js';
5 | import PropertiesProviderModule from './lib/properties-provider';
6 |
7 | import xml from './diagrams/pizzaDelivery.bpmn';
8 | import blankXml from './diagrams/newDiagram.bpmn';
9 |
10 | let lastFile;
11 | let isValidating = false;
12 | let isDirty = false;
13 |
14 | // create and configure a chor-js instance
15 | const modeler = new ChoreoModeler({
16 | container: '#canvas',
17 | propertiesPanel: {
18 | parent: '#properties-panel'
19 | },
20 | // remove the properties' panel if you use the Viewer
21 | // or NavigatedViewer modules of chor-js
22 | additionalModules: [
23 | PropertiesPanelModule,
24 | PropertiesProviderModule
25 | ],
26 | keyboard: {
27 | bindTo: document
28 | }
29 | });
30 |
31 | // display the given model (XML representation)
32 | async function renderModel(newXml) {
33 | await modeler.importXML(newXml);
34 | isDirty = false;
35 | }
36 |
37 | // returns the file name of the diagram currently being displayed
38 | function diagramName() {
39 | if (lastFile) {
40 | return lastFile.name;
41 | }
42 | return 'diagram.bpmn';
43 | }
44 |
45 | document.addEventListener('DOMContentLoaded', () => {
46 | // download diagram as XML
47 | const downloadLink = document.getElementById('js-download-diagram');
48 | downloadLink.addEventListener('click', async e => {
49 | const result = await modeler.saveXML({ format: true });
50 | downloadLink['href'] = 'data:application/bpmn20-xml;charset=UTF-8,' + encodeURIComponent(result.xml);
51 | downloadLink['download'] = diagramName();
52 | isDirty = false;
53 | });
54 |
55 | // download diagram as SVG
56 | const downloadSvgLink = document.getElementById('js-download-svg');
57 | downloadSvgLink.addEventListener('click', async e => {
58 | const result = await modeler.saveSVG();
59 | downloadSvgLink['href'] = 'data:image/svg+xml;charset=UTF-8,' + encodeURIComponent(result.svg);
60 | downloadSvgLink['download'] = diagramName() + '.svg';
61 | });
62 |
63 | // open file dialog
64 | document.getElementById('js-open-file').addEventListener('click', e => {
65 | document.getElementById('file-input').click();
66 | });
67 |
68 | // toggle side panels
69 | const panels = Array.prototype.slice.call(
70 | document.getElementById('panel-toggle').children
71 | );
72 | panels.forEach(panel => {
73 | panel.addEventListener('click', () => {
74 | panels.forEach(otherPanel => {
75 | if (panel === otherPanel && !panel.classList.contains('active')) {
76 | // show clicked panel if it is not already active, otherwise hide it as well
77 | panel.classList.add('active');
78 | document.getElementById(panel.dataset.togglePanel).classList.remove('hidden');
79 | } else {
80 | // hide all other panels
81 | otherPanel.classList.remove('active');
82 | document.getElementById(otherPanel.dataset.togglePanel).classList.add('hidden');
83 | }
84 | });
85 | });
86 | });
87 |
88 | // create new diagram
89 | const newDiagram = document.getElementById('js-new-diagram');
90 | newDiagram.addEventListener('click', async e => {
91 | await renderModel(blankXml);
92 | lastFile = false;
93 | });
94 |
95 | // load diagram from disk
96 | const loadDiagram = document.getElementById('file-input');
97 | loadDiagram.addEventListener('change', e => {
98 | const file = loadDiagram.files[0];
99 | if (file) {
100 | const reader = new FileReader();
101 | lastFile = file;
102 | reader.addEventListener('load', async () => {
103 | await renderModel(reader.result);
104 | loadDiagram.value = null; // allows reloading the same file
105 | }, false);
106 | reader.readAsText(file);
107 | }
108 | });
109 |
110 | // drag & drop file
111 | const dropZone = document.body;
112 | dropZone.addEventListener('dragover', e => {
113 | e.preventDefault();
114 | dropZone.classList.add('is-dragover');
115 | });
116 | dropZone.addEventListener('dragleave', e => {
117 | e.preventDefault();
118 | dropZone.classList.remove('is-dragover');
119 | });
120 | dropZone.addEventListener('drop', e => {
121 | e.preventDefault();
122 | dropZone.classList.remove('is-dragover');
123 | const file = e.dataTransfer.files[0];
124 | if (file) {
125 | const reader = new FileReader();
126 | lastFile = file;
127 | reader.addEventListener('load', () => {
128 | renderModel(reader.result);
129 | }, false);
130 | reader.readAsText(file);
131 | }
132 | });
133 |
134 | // validation logic and toggle
135 | const reporter = new Reporter(modeler);
136 | const validateButton = document.getElementById('js-validate');
137 | validateButton.addEventListener('click', e => {
138 | isValidating = !isValidating;
139 | if (isValidating) {
140 | reporter.validateDiagram();
141 | validateButton.classList.add('selected');
142 | validateButton['title'] = 'Disable checking';
143 | } else {
144 | reporter.clearAll();
145 | validateButton.classList.remove('selected');
146 | validateButton['title'] = 'Check diagram for problems';
147 | }
148 | });
149 | modeler.on('commandStack.changed', () => {
150 | if (isValidating) {
151 | reporter.validateDiagram();
152 | }
153 | isDirty = true;
154 | });
155 | modeler.on('import.render.complete', () => {
156 | if (isValidating) {
157 | reporter.validateDiagram();
158 | }
159 | });
160 | });
161 |
162 | // expose bpmnjs to window for debugging purposes
163 | window.bpmnjs = modeler;
164 |
165 | window.addEventListener('beforeunload', function(e) {
166 | if (isDirty) {
167 | // see https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload
168 | e.preventDefault();
169 | e.returnValue = '';
170 | }
171 | });
172 |
173 | renderModel(xml);
174 |
--------------------------------------------------------------------------------
/app/diagrams/EventBasedGateway.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | legal
29 |
30 |
31 |
32 | legal
33 |
34 |
35 |
36 | illegal
37 |
38 |
39 |
40 | illegal
41 |
42 |
43 |
44 | SequenceFlow_1c4wf17
45 |
46 |
47 | SequenceFlow_1c4wf17
48 | SequenceFlow_1lbfc23
49 | SequenceFlow_02087ww
50 |
51 |
52 |
53 | SequenceFlow_1lbfc23
54 | Participant_1isukd8
55 | Participant_124muor
56 | MessageFlow_0dc6hmu
57 |
58 |
59 |
60 | SequenceFlow_02087ww
61 | Participant_0jezgqe
62 | Participant_1isukd8
63 | MessageFlow_0eiw5dl
64 |
65 |
66 |
67 | SequenceFlow_0j0dqfm
68 |
69 |
70 | SequenceFlow_0j0dqfm
71 | SequenceFlow_00jp6wv
72 | SequenceFlow_1yii3fr
73 |
74 |
75 | SequenceFlow_00jp6wv
76 | Participant_0jezgqe
77 | Participant_124muor
78 | MessageFlow_0r68mz5
79 |
80 |
81 | SequenceFlow_1yii3fr
82 | Participant_0jezgqe
83 | Participant_1isukd8
84 | MessageFlow_0ad17b6
85 |
86 |
87 |
88 |
89 |
90 | SequenceFlow_1sd0oqv
91 |
92 |
93 | SequenceFlow_1sd0oqv
94 | SequenceFlow_1cov845
95 | SequenceFlow_12hef02
96 |
97 |
98 | SequenceFlow_1cov845
99 | Participant_0jezgqe
100 | Participant_124muor
101 | MessageFlow_0ocgtn8
102 |
103 |
104 | SequenceFlow_12hef02
105 | Participant_124muor
106 | Participant_1isukd8
107 | MessageFlow_1srv3ec
108 |
109 |
110 |
111 |
112 |
113 | SequenceFlow_1urrpzv
114 |
115 |
116 | SequenceFlow_1sw62kj
117 | SequenceFlow_04jomg4
118 | SequenceFlow_1i3d4j0
119 |
120 |
121 | SequenceFlow_04jomg4
122 | Participant_1isukd8
123 | Participant_124muor
124 | MessageFlow_07qn4nx
125 |
126 |
127 | SequenceFlow_1i3d4j0
128 | Participant_0jezgqe
129 | Participant_1isukd8
130 | MessageFlow_00wphp8
131 |
132 |
133 |
134 |
135 | SequenceFlow_1urrpzv
136 | SequenceFlow_1sw62kj
137 | Participant_1a04puk
138 | Participant_1isukd8
139 | MessageFlow_02o85v4
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
--------------------------------------------------------------------------------
/app/diagrams/ManyErrorsAndWarnings.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | SequenceFlow_04tj6o0
31 | SequenceFlow_1iybzm8
32 | Participant_0jezgqe
33 | Participant_1isukd8
34 |
35 | SequenceFlow_18sxbve
36 | SequenceFlow_168agkb
37 | Participant_124muor
38 | Participant_1isukd8
39 | MessageFlow_0nost86
40 |
41 |
42 | SequenceFlow_12b2b03
43 | SequenceFlow_1gmotny
44 | Participant_1a04puk
45 | Participant_0jezgqe
46 | MessageFlow_1m2evxt
47 | MessageFlow_1ylyir1
48 |
49 |
50 | SequenceFlow_18sxbve
51 |
52 |
53 |
54 | SequenceFlow_1qaxlut
55 |
56 |
57 | SequenceFlow_1gmotny
58 | SequenceFlow_1qaxlut
59 | Participant_124muor
60 | Participant_1isukd8
61 | MessageFlow_17gh7ts
62 |
63 |
64 |
65 |
66 | SequenceFlow_168agkb
67 | SequenceFlow_12b2b03
68 |
69 |
70 |
71 |
72 |
73 |
74 | SequenceFlow_1iybzm8
75 | Participant_124muor
76 | Participant_1isukd8
77 | MessageFlow_08uc6pw
78 |
79 |
80 |
81 | Participant_0jezgqe
82 | Participant_1isukd8
83 |
84 | SequenceFlow_1hwnh6h
85 | SequenceFlow_0xgktu9
86 | Participant_124muor
87 | Participant_1isukd8
88 | MessageFlow_1mtov6b
89 |
90 |
91 | SequenceFlow_1hwnh6h
92 |
93 |
94 | SequenceFlow_0xgktu9
95 | SequenceFlow_0ivfzg6
96 |
97 |
98 |
99 | SequenceFlow_0ivfzg6
100 | SequenceFlow_0hb20f3
101 | Participant_1by7wp9
102 | Participant_0jezgqe
103 | MessageFlow_1nv6vrb
104 | MessageFlow_1gjxdkn
105 |
106 |
107 | SequenceFlow_0hb20f3
108 | SequenceFlow_1f7qlm1
109 | Participant_124muor
110 | Participant_1isukd8
111 | MessageFlow_085mwey
112 |
113 |
114 | SequenceFlow_1f7qlm1
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | SequenceFlow_04tj6o0
124 | Participant_124muor
125 | Participant_1isukd8
126 | MessageFlow_0nwc7sx
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
--------------------------------------------------------------------------------
/app/diagrams/multiple.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | SequenceFlow_1exndm5
16 |
17 |
18 | SequenceFlow_1exndm5
19 | SequenceFlow_09um5u6
20 | Participant_0bln1au
21 | Participant_0a8eu9l
22 | MessageFlow_0e0tqvf
23 |
24 |
25 |
26 | SequenceFlow_09um5u6
27 | SequenceFlow_1bdfjst
28 | Participant_0a8eu9l
29 | Participant_0bln1au
30 | MessageFlow_1ylcbke
31 |
32 |
33 |
34 | SequenceFlow_1bdfjst
35 | SequenceFlow_0b9zcqb
36 | SequenceFlow_1gw3sl2
37 |
38 |
39 |
40 |
41 | SequenceFlow_1gw3sl2
42 |
43 |
44 |
45 | SequenceFlow_0b9zcqb
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | SequenceFlow_1
63 | SequenceFlow_2
64 | Participant_1
65 | Participant_2
66 | MessageFlow_1
67 |
68 |
69 | SequenceFlow_2
70 | SequenceFlow_3
71 | Participant_3
72 | Participant_2
73 | Participant_5
74 | Participant_4
75 | Participant_1
76 |
77 |
78 | Participant_3
79 | Participant_2
80 | Participant_5
81 | Participant_4
82 | Participant_1
83 |
84 |
85 | SequenceFlow_1
86 |
87 |
88 |
89 |
90 | SequenceFlow_3
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
--------------------------------------------------------------------------------
/app/diagrams/newDiagram.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/diagrams/pizzaDelivery.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | SequenceFlow_0dy5er9
15 |
16 |
17 | SequenceFlow_0dy5er9
18 | SequenceFlow_036yo13
19 | Participant_06qakjv
20 | Participant_0mgz9si
21 | MessageFlow_0nzo41l
22 |
23 |
24 |
25 | SequenceFlow_036yo13
26 | SequenceFlow_0esuuaj
27 | Participant_0mgz9si
28 | Participant_0537iz2
29 | MessageFlow_0dnembp
30 |
31 |
32 |
33 | SequenceFlow_0esuuaj
34 | SequenceFlow_0xamnn2
35 | Participant_0537iz2
36 | Participant_06qakjv
37 | MessageFlow_03drbju
38 |
39 |
40 |
41 | SequenceFlow_0xamnn2
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
--------------------------------------------------------------------------------
/app/diagrams/subChoreographies.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | SequenceFlow_1
20 | SequenceFlow_2
21 | Participant_1
22 | Participant_2
23 | MessageFlow_1
24 |
25 |
26 | SequenceFlow_2
27 | SequenceFlow_3
28 | Participant_3
29 | Participant_2
30 | Participant_5
31 | Participant_4
32 | Participant_1
33 |
34 |
35 | SequenceFlow_1
36 |
37 |
38 |
39 |
40 | SequenceFlow_3
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
--------------------------------------------------------------------------------
/app/diagrams/subChoreographies.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bptlab/chor-js-demo/6b1c04b329d69618af8002b5b3afdca0c8c15d91/app/diagrams/subChoreographies.png
--------------------------------------------------------------------------------
/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bptlab/chor-js-demo/6b1c04b329d69618af8002b5b3afdca0c8c15d91/app/favicon.ico
--------------------------------------------------------------------------------
/app/icons/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Font license info
2 |
3 |
4 | ## Entypo
5 |
6 | Copyright (C) 2012 by Daniel Bruce
7 |
8 | Author: Daniel Bruce
9 | License: SIL (http://scripts.sil.org/OFL)
10 | Homepage: http://www.entypo.com
11 |
12 |
13 | ## Elusive
14 |
15 | Copyright (C) 2013 by Aristeides Stathopoulos
16 |
17 | Author: Aristeides Stathopoulos
18 | License: SIL (http://scripts.sil.org/OFL)
19 | Homepage: http://aristeides.com/
20 |
21 |
22 | ## Font Awesome
23 |
24 | Copyright (C) 2016 by Dave Gandy
25 |
26 | Author: Dave Gandy
27 | License: SIL ()
28 | Homepage: http://fortawesome.github.com/Font-Awesome/
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/icons/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chor-editor",
3 | "css_prefix_text": "icon-",
4 | "css_use_suffix": false,
5 | "hinting": true,
6 | "units_per_em": 1000,
7 | "ascent": 850,
8 | "glyphs": [
9 | {
10 | "uid": "0ccb084ddeeae372673793ed0b45bb4a",
11 | "css": "folder",
12 | "code": 59393,
13 | "src": "entypo"
14 | },
15 | {
16 | "uid": "9e0404ba55575a540164db9a5ad511df",
17 | "css": "doc-new",
18 | "code": 59394,
19 | "src": "elusive"
20 | },
21 | {
22 | "uid": "26613a2e6bc41593c54bead46f8c8ee3",
23 | "css": "file-code",
24 | "code": 61897,
25 | "src": "fontawesome"
26 | },
27 | {
28 | "uid": "3c961c1a8d874815856fc6637dc5a13c",
29 | "css": "file-image",
30 | "code": 61893,
31 | "src": "fontawesome"
32 | },
33 | {
34 | "uid": "7432077e6a2d6aa19984ca821bb6bbda",
35 | "css": "bug",
36 | "code": 61832,
37 | "src": "fontawesome"
38 | },
39 | {
40 | "uid": "0f6a2573a7b6df911ed199bb63717e27",
41 | "css": "github-circled",
42 | "code": 61595,
43 | "src": "fontawesome"
44 | }
45 | ]
46 | }
--------------------------------------------------------------------------------
/app/icons/css/chor-editor.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'chor-editor';
3 | src: url('../font/chor-editor.eot?66206553');
4 | src: url('../font/chor-editor.eot?66206553#iefix') format('embedded-opentype'),
5 | url('../font/chor-editor.woff2?66206553') format('woff2'),
6 | url('../font/chor-editor.woff?66206553') format('woff'),
7 | url('../font/chor-editor.ttf?66206553') format('truetype'),
8 | url('../font/chor-editor.svg?66206553#chor-editor') format('svg');
9 | font-weight: normal;
10 | font-style: normal;
11 | }
12 | /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
13 | /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
14 | /*
15 | @media screen and (-webkit-min-device-pixel-ratio:0) {
16 | @font-face {
17 | font-family: 'chor-editor';
18 | src: url('../font/chor-editor.svg?66206553#chor-editor') format('svg');
19 | }
20 | }
21 | */
22 |
23 | [class^="icon-"]:before, [class*=" icon-"]:before {
24 | font-family: "chor-editor";
25 | font-style: normal;
26 | font-weight: normal;
27 | speak: none;
28 |
29 | display: inline-block;
30 | text-decoration: inherit;
31 | width: 1em;
32 | text-align: center;
33 | /* opacity: .8; */
34 |
35 | /* For safety - reset parent styles, that can break glyph codes*/
36 | font-variant: normal;
37 | text-transform: none;
38 |
39 | /* fix buttons height, for twitter bootstrap */
40 | line-height: 1em;
41 |
42 | /* you can be more comfortable with increased icons size */
43 | /* font-size: 120%; */
44 |
45 | /* Font smoothing. That was taken from TWBS */
46 | -webkit-font-smoothing: antialiased;
47 | -moz-osx-font-smoothing: grayscale;
48 |
49 | /* Uncomment for 3D effect */
50 | /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
51 | }
52 |
53 | .icon-folder:before { content: '\e801'; } /* '' */
54 | .icon-doc-new:before { content: '\e802'; } /* '' */
55 | .icon-github-circled:before { content: '\f09b'; } /* '' */
56 | .icon-bug:before { content: '\f188'; } /* '' */
57 | .icon-file-image:before { content: '\f1c5'; } /* '' */
58 | .icon-file-code:before { content: '\f1c9'; } /* '' */
--------------------------------------------------------------------------------
/app/icons/font/chor-editor.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bptlab/chor-js-demo/6b1c04b329d69618af8002b5b3afdca0c8c15d91/app/icons/font/chor-editor.eot
--------------------------------------------------------------------------------
/app/icons/font/chor-editor.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/app/icons/font/chor-editor.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bptlab/chor-js-demo/6b1c04b329d69618af8002b5b3afdca0c8c15d91/app/icons/font/chor-editor.ttf
--------------------------------------------------------------------------------
/app/icons/font/chor-editor.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bptlab/chor-js-demo/6b1c04b329d69618af8002b5b3afdca0c8c15d91/app/icons/font/chor-editor.woff
--------------------------------------------------------------------------------
/app/icons/font/chor-editor.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bptlab/chor-js-demo/6b1c04b329d69618af8002b5b3afdca0c8c15d91/app/icons/font/chor-editor.woff2
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 | [chor-js] Demo
19 |
20 |
21 |
22 |
38 |
39 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/app/lib/properties-provider/ChorPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | import BpmnPropertiesProvider from 'bpmn-js-properties-panel/lib/provider/bpmn/BpmnPropertiesProvider.js';
2 | import inherits from 'inherits';
3 | import { is } from 'bpmn-js/lib/util/ModelUtil';
4 | import cmdHelper from 'bpmn-js-properties-panel/lib/helper/CmdHelper';
5 | import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';
6 | import eventDefinitionHelper from 'bpmn-js-properties-panel/lib/helper/EventDefinitionHelper';
7 | import conditionalProps from 'bpmn-js-properties-panel/lib/provider/camunda/parts/ConditionalProps.js';
8 | import messageDefinition from './MessageDefinition';
9 |
10 | export default function ChorPropertiesProvider(injector, bpmnFactory) {
11 |
12 | injector.invoke(BpmnPropertiesProvider, this);
13 |
14 | const superGetTabs = this.getTabs;
15 |
16 | this.getTabs = function(element) {
17 | let generalTab = superGetTabs.call(this, element);
18 | const detailsGroup = generalTab[0].groups.filter(g => g.id === 'details')[0];
19 | if (is(element, 'bpmn:Event')) {
20 | // Conditional Events show Camunda specific options, we have to filter those
21 | if (element.businessObject.eventDefinitions) {
22 | const definition = element.businessObject.eventDefinitions[0];
23 | if (definition.$type === 'bpmn:ConditionalEventDefinition') {
24 | detailsGroup.entries = [];
25 | this.conditionalEvent(detailsGroup, element);
26 | }
27 | }
28 | }
29 | conditionalProps(detailsGroup, element, bpmnFactory, e => e);
30 | if (is(element, 'bpmn:Message')) {
31 | messageDefinition(detailsGroup, element, bpmnFactory, element.businessObject);
32 | }
33 | return generalTab;
34 | };
35 |
36 | ChorPropertiesProvider.prototype.conditionalEvent = function(group, element) {
37 | const getValue = function(conditionalEvent, node) {
38 |
39 | const conditionalEventDefinition = eventDefinitionHelper.getConditionalEventDefinition(conditionalEvent);
40 | return {
41 | condition: conditionalEventDefinition.condition.body
42 | };
43 | };
44 |
45 | const setValue = function(conditionalEvent, values) {
46 |
47 | const conditionalEventDefinition = eventDefinitionHelper.getConditionalEventDefinition(conditionalEvent);
48 | const condition = conditionalEventDefinition.condition;
49 |
50 | return cmdHelper.updateBusinessObject(conditionalEvent, condition, { body: values.condition });
51 | };
52 |
53 | group.entries.push(entryFactory.textField({
54 | id: 'condition',
55 | label: 'Condition Expression',
56 | modelProperty: 'condition',
57 |
58 | get: getValue,
59 | set: setValue
60 | }));
61 | };
62 |
63 | }
64 |
65 | inherits(ChorPropertiesProvider, BpmnPropertiesProvider);
66 | ChorPropertiesProvider.$inject = [
67 | 'injector',
68 | 'bpmnFactory'
69 | ];
70 |
71 |
--------------------------------------------------------------------------------
/app/lib/properties-provider/MessageDefinition.js:
--------------------------------------------------------------------------------
1 | import eventDefinitionReference from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/implementation/EventDefinitionReference';
2 | import elementReferenceProperty from 'bpmn-js-properties-panel/lib/provider/bpmn/parts/implementation/ElementReferenceProperty';
3 | import entryFactory from 'bpmn-js-properties-panel/lib/factory/EntryFactory';
4 | import cmdHelper from 'bpmn-js-properties-panel/lib/helper/CmdHelper';
5 |
6 | export default function MessageDefinition(group, element, bpmnFactory, messageEventDefinition) {
7 |
8 | // Technically, the eventDefinitionReference function is only meant for events, however, it works well for our purpose, too.
9 | // I currently see no reason to duplicate ~100 lines of code, however, it might break in future versions of the panel-provider
10 | group.entries = group.entries.concat(eventDefinitionReference(element, messageEventDefinition, bpmnFactory, {
11 | label: 'Item Definition',
12 | elementName: 'item-def',
13 | elementType: 'bpmn:ItemDefinition',
14 | referenceProperty: 'itemRef',
15 | newElementIdPrefix: 'ItemDef_'
16 | }));
17 |
18 | group.entries = group.entries.concat(elementReferenceProperty(element, messageEventDefinition, bpmnFactory, {
19 | id: 'Item-Def-element-name',
20 | label: 'Item Definition Name',
21 | referenceProperty: 'itemRef',
22 | modelProperty: 'name',
23 | shouldValidate: false
24 | }));
25 |
26 | group.entries = group.entries.concat(createStructureRefTextField());
27 |
28 | }
29 |
30 | function createStructureRefTextField() {
31 | const modelProperty = 'structureRef';
32 | let entry = entryFactory.textField({
33 | id: 'structure-ref',
34 | label: 'Data Structure',
35 | modelProperty: modelProperty,
36 |
37 | get: function(element, node) {
38 | var reference = element.businessObject.itemRef;
39 | var props = {};
40 | props[modelProperty] = reference && reference.get(modelProperty);
41 | return props;
42 | },
43 |
44 | set: function(element, values, node) {
45 | var reference = element.businessObject.itemRef;
46 | var props = {};
47 | props[modelProperty] = values[modelProperty] || undefined;
48 | return cmdHelper.updateBusinessObject(element, reference, props);
49 | },
50 |
51 | hidden: function(element, node) {
52 | return !element.businessObject.itemRef;
53 | }
54 | });
55 | return [ entry ];
56 | }
57 |
58 |
59 |
--------------------------------------------------------------------------------
/app/lib/properties-provider/index.js:
--------------------------------------------------------------------------------
1 | import ChorPropertiesProvider from './ChorPropertiesProvider';
2 |
3 | module.exports = {
4 | __init__: [ 'propertiesProvider' ],
5 | propertiesProvider: [ 'type', ChorPropertiesProvider]
6 | };
--------------------------------------------------------------------------------
/app/lib/validator/Validator.js:
--------------------------------------------------------------------------------
1 | import { heightOfTopBands } from 'chor-js/lib/util/BandUtil';
2 |
3 | import {
4 | isChoreoActivity
5 | } from './util/ValidatorUtil';
6 |
7 | import eventBasedGatewayConstraint from './constraints/EventBasedGatewayConstraint';
8 | import participantNameConstraint from './constraints/ParticipantNameConstraint';
9 | import simpleFlowConstraint from './constraints/SimpleFlowConstraint';
10 | import subChoreoParticipantsConstraint from './constraints/SubChoreoParticipantsConstraint';
11 | import callChoreoParticipantsBijectivityConstraint from './constraints/CallChoreoParticipantsBijectivityConstraint';
12 | import noCyclicCallChoreosConstraint from './constraints/NoCyclicCallChoreosConstraint';
13 |
14 | const CONSTRAINTS = [
15 | eventBasedGatewayConstraint,
16 | participantNameConstraint,
17 | simpleFlowConstraint,
18 | subChoreoParticipantsConstraint,
19 | callChoreoParticipantsBijectivityConstraint,
20 | noCyclicCallChoreosConstraint,
21 |
22 | ];
23 |
24 | const LEVEL = {
25 | WARNING: 0,
26 | ERROR: 1
27 | };
28 |
29 | const LEVEL_STRING = [
30 | 'warning',
31 | 'error'
32 | ];
33 |
34 | export default function Reporter(viewer) {
35 | this.overlays = viewer.get('overlays');
36 | this.elementRegistry = viewer.get('elementRegistry');
37 | this.annotations = []; // List of all annotations
38 | this.shapeAnnotations = {}; // Mapping from shape id to annotations
39 | this.overlayIDs = [];
40 | }
41 |
42 | Reporter.prototype.validateDiagram = function() {
43 | this.clearAll();
44 | this.elementRegistry.forEach(shape => CONSTRAINTS.forEach(constraint => constraint(shape, this)));
45 | this.showAnnotations();
46 | };
47 |
48 | Reporter.prototype.addAnnotationToShape = function(shape, annotation) {
49 | if (this.shapeAnnotations[shape.id]) {
50 | this.shapeAnnotations[shape.id].push(annotation);
51 | } else {
52 | this.shapeAnnotations[shape.id] = [ annotation ];
53 | }
54 | };
55 |
56 | Reporter.prototype.report = function(shape, level, text) {
57 | this.annotations.push({ level: level, text: text, shape: shape });
58 | };
59 |
60 | Reporter.prototype.warn = function(shape, text) {
61 | this.report(shape, LEVEL.WARNING, text);
62 | };
63 |
64 | Reporter.prototype.error = function(shape, text) {
65 | this.report(shape, LEVEL.ERROR, text);
66 | };
67 |
68 | Reporter.prototype.showAnnotations = function() {
69 | function findVisibleParent(shape) {
70 | while (shape.hidden) {
71 | shape = shape.parent;
72 | }
73 | return shape;
74 | }
75 |
76 | this.annotations.forEach(annotation => this.addAnnotationToShape(
77 | findVisibleParent(annotation.shape),
78 | annotation
79 | ));
80 | Object.keys(this.shapeAnnotations).forEach(id => {
81 | const shape = this.elementRegistry.get(id);
82 | this.displayOnShape(shape, this.shapeAnnotations[id]);
83 | });
84 | };
85 |
86 | /**
87 | * Display a list of annotations on their shape.
88 | * @param annotations {Array}
89 | */
90 | Reporter.prototype.displayOnShape = function(shape, annotations) {
91 | let childAnnotations = annotations.filter(annotation => annotation.shape !== shape);
92 | let parentAnnotations = annotations
93 | .filter(annotation => annotation.shape === shape)
94 | .sort((a,b) => b.level - a.level);
95 |
96 | const topOffset = isChoreoActivity(shape) ? heightOfTopBands(shape) : -7;
97 | const level = Math.max(...annotations.map(annotation => annotation.level));
98 | const count = annotations.length;
99 |
100 | let infoText = '';
101 | parentAnnotations.forEach(annotation => {
102 | infoText += '' + annotation.text + '';
103 | });
104 | if (childAnnotations.length > 0) {
105 | let errorCount = childAnnotations.filter(annotation => annotation.level === LEVEL.ERROR).length;
106 | let warningCount = childAnnotations.filter(annotation => annotation.level === LEVEL.WARNING).length;
107 | infoText += '';
108 | if (errorCount > 0) {
109 | infoText += '' + errorCount + ' nested error';
110 | if (errorCount > 1) {
111 | infoText += 's';
112 | }
113 | if (warningCount > 0) {
114 | infoText += ',';
115 | }
116 | }
117 | if (warningCount > 0) {
118 | infoText += warningCount + ' nested warning';
119 | if (warningCount > 1) {
120 | infoText += 's';
121 | }
122 | }
123 | infoText += '';
124 | }
125 |
126 | let html = '';
127 | if (count > 1) {
128 | html += '
' + count + '
';
129 | }
130 | html += '
';
131 | html += '
';
132 |
133 | const newOverlayId = this.overlays.add(shape.id, {
134 | position: {
135 | top: topOffset,
136 | left: shape.width - 12
137 | },
138 | html: html
139 | });
140 | this.overlayIDs.push(newOverlayId);
141 | };
142 |
143 | Reporter.prototype.clearAll = function() {
144 | this.overlayIDs.forEach(id => this.overlays.remove(id));
145 | this.shapeAnnotations = {};
146 | this.annotations = [];
147 | };
148 |
--------------------------------------------------------------------------------
/app/lib/validator/constraints/CallChoreoParticipantsBijectivityConstraint.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | /**
4 | * The mapping between the CallChoreo's participants and the participants of the called choreography should be bijective.
5 | * @param shape
6 | * @param reporter {Reporter}
7 | */
8 | export default function callChoreoParticipantsBijectivityConstraint(shape, reporter) {
9 | if (is(shape, 'bpmn:CallChoreography') && shape.businessObject.calledChoreographyRef) {
10 | const callChoreoBO = shape.businessObject;
11 | const bandShapes = shape.bandShapes || [];
12 | const participantAssociations = callChoreoBO.participantAssociations || [];
13 |
14 | bandShapes.forEach(band => {
15 | const association = participantAssociations.find(p => p.outerParticipantRef === band.businessObject);
16 | const bandsInnerParticipant = association ? association.innerParticipantRef : undefined;
17 | checkFunctionProperty(band, bandsInnerParticipant, reporter);
18 | checkInjectivityProperty(band, bandsInnerParticipant, participantAssociations, reporter);
19 | });
20 |
21 | checkSurjectivityProperty(participantAssociations, callChoreoBO, shape, reporter);
22 | }
23 | }
24 |
25 |
26 | function checkFunctionProperty(band, bandsInnerParticipant, reporter) {
27 | if (!bandsInnerParticipant) {
28 | reporter.warn(band, '' + band.businessObject.name + ' ' +
29 | 'does not yet reference an inner participant');
30 | }
31 | }
32 |
33 | function checkInjectivityProperty(band, bandsInnerParticipant, participantAssociations, reporter) {
34 | const injectivityViolators = participantAssociations
35 | .filter(pa => pa.innerParticipantRef === bandsInnerParticipant && pa.outerParticipantRef !== band.businessObject)
36 | .map(pa => pa.outerParticipantRef);
37 |
38 | if (injectivityViolators.length > 0) {
39 | const violatorString = injectivityViolators.map(bo => bo.name).join(', ');
40 | reporter.warn(band, '' + band.businessObject.name + ' references the same inner participant as '
41 | + violatorString + '');
42 | }
43 | }
44 |
45 | function checkSurjectivityProperty(participantAssociations, callChoreoBO, shape, reporter) {
46 | const participantCodomain = callChoreoBO.calledChoreographyRef.participants || [];
47 | const surjectivityViolators = participantCodomain
48 | .filter(p => !participantAssociations.some(a => a.innerParticipantRef === p));
49 | if (surjectivityViolators.length > 0) {
50 | const violationString = surjectivityViolators.map(bo => bo.name).join(', ');
51 | const calleeName = callChoreoBO.calledChoreographyRef.name || callChoreoBO.calledChoreographyRef.id;
52 | reporter.warn(shape,
53 | '' + callChoreoBO.name + ' does not reference participants '
54 | + violationString + ' of ' + calleeName + '');
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/lib/validator/constraints/EventBasedGatewayConstraint.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | import {
4 | getConnectedElements,
5 | isInitiating,
6 | isChoreoActivity,
7 | flat
8 | } from '../util/ValidatorUtil';
9 |
10 | /**
11 | * Checks rules for event-based gateways. See chapter 11.7.2
12 | * @param shape
13 | * @param reporter {Reporter}
14 | */
15 | export default function eventBasedGatewayConstraint(shape, reporter) {
16 | if (is(shape, 'bpmn:EventBasedGateway')) {
17 | const following = getConnectedElements(shape, 'outgoing', isChoreoActivity);
18 | const senders = flat(following.map(a => a.bandShapes.filter(p => isInitiating(p))));
19 | const receivers = flat(following.map(a => a.bandShapes.filter(p => !isInitiating(p))));
20 | if (!(senders.every((s, i, a) => a[0].businessObject.id === s.businessObject.id) ||
21 | receivers.every((r, i, a) => a[0].businessObject.id === r.businessObject.id))) {
22 | reporter.error(shape, 'After an event-based gateway, all senders or all receivers must be the same');
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/lib/validator/constraints/NoCyclicCallChoreosConstraint.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { getAllElementsOfType } from 'chor-js/lib/util/DiagramWalkerUtil';
3 |
4 | /**
5 | * Call choreographies might lead to cyclic calls. This can be viable, but a warning should be issued as this
6 | * can lead to infinite recursions.
7 | *
8 | * @param shape
9 | * @param reporter {Reporter}
10 | */
11 | export default function noCyclicCallChoreosConstraint(shape, reporter) {
12 | if (is(shape, 'bpmn:CallChoreography')) {
13 | const callChoreoBO = shape.businessObject;
14 | const calledChoreo = callChoreoBO.calledChoreographyRef;
15 | const parentChoreography = getContainingChoreo(callChoreoBO);
16 | const cycles = dfs(calledChoreo, [parentChoreography], [callChoreoBO.name]);
17 |
18 | for (const cycle of cycles) {
19 | const nodes = cycle.nodes;
20 | const edges = cycle.edges;
21 | const cycleString = nodes.map(choreo => (choreo.name || choreo.id))
22 | .map((name,i) => {
23 | if (i < edges.length) {
24 | return name + ' -[' + edges[i] + ']-> ';
25 | }
26 | return name;
27 | }).join('');
28 | reporter.warn(shape,
29 | '' +callChoreoBO.name +' is part of a cyclic call via:
'+cycleString+'');
30 | }
31 | }
32 | }
33 |
34 | /**
35 | * Do a depth first search starting at node.
36 | * @param node {BusinessObject} Called Choreo to investigate
37 | * @param nodePath {Array}
38 | * @param edgePath {Array}
39 | * @return cycles {Array} Elementary cycles that include the node
40 | */
41 | function dfs(node, nodePath, edgePath) {
42 | nodePath = nodePath.concat([node]);
43 | const outgoingEdges = getAllCallChoreosFromChoreo(node);
44 | let cycles = [];
45 | for (let edge of outgoingEdges) {
46 | let target = edge.calledChoreographyRef;
47 | if (nodePath.indexOf(target) === -1) {
48 | cycles = cycles.concat(dfs(target, nodePath, edgePath.concat([edge.name])));
49 | } else {
50 | if (target === nodePath[0]) {
51 | cycles = cycles.concat([{ nodes: nodePath.concat([target]), edges: edgePath.concat([edge.name]) }]);
52 | }
53 | }
54 | }
55 | return cycles;
56 | }
57 |
58 | function getAllCallChoreosFromChoreo(choreo) {
59 | if (!choreo) {
60 | return [];
61 | }
62 | return getAllElementsOfType('bpmn:CallChoreography', [choreo]);
63 | }
64 |
65 | function getContainingChoreo(businessObject) {
66 | let parent = businessObject;
67 | while (!is(parent, 'bpmn:Choreography')) {
68 | parent = businessObject.$parent;
69 | }
70 | return parent;
71 | }
--------------------------------------------------------------------------------
/app/lib/validator/constraints/ParticipantNameConstraint.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | /**
4 | * Checks if a name is used for different participants.
5 | * @param shape
6 | * @param reporter {Reporter}
7 | */
8 | export default function participantNameConstraint(shape, reporter) {
9 | // Check if it is a band shape
10 | if (is(shape, 'bpmn:Participant')) {
11 | const id = shape.businessObject.id;
12 | const name = shape.businessObject.name;
13 | const isUnique = reporter.elementRegistry.filter(elem => elem.diBand)
14 | .every(elem => elem.businessObject.name !== name || elem.businessObject.id === id);
15 | if (!isUnique) {
16 | reporter.warn(shape, 'Multiple different participants share the name ' + name +
17 | ', which should be unique');
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/lib/validator/constraints/SimpleFlowConstraint.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | import {
4 | getConnectedElements,
5 | isInitiating,
6 | getParticipants,
7 | isChoreoActivity,
8 | isIntermediateTimerCatchEvent,
9 | getTimerDefinitionType
10 | } from '../util/ValidatorUtil';
11 |
12 | /**
13 | * Checks the basic sequence flow constraint.
14 | * Compare with Chapter "11.5.6 The Sequencing of Activities" in the BPMN standard.
15 | *
16 | * @param shape
17 | * @param reporter
18 | */
19 | export default function simpleFlowConstraint(shape, reporter) {
20 | if (is(shape, 'bpmn:Participant')) {
21 | if (isInitiating(shape)) {
22 | const participant = shape.businessObject;
23 |
24 | // Get all relevant predecessors. We also stop traversing the model once we encounter
25 | // an absolute timer intermediate catch event, since those suspend the simple flow
26 | // constraint (see p. 341 in the standard).
27 | const predecessors = getConnectedElements(shape, 'incoming', e => {
28 | return isChoreoActivity(e) || (
29 | isIntermediateTimerCatchEvent(e) &&
30 | getTimerDefinitionType(e) === 'timeDate'
31 | );
32 | }).filter(isChoreoActivity);
33 |
34 | // For the remaining choreography tasks, check whether they include this participant.
35 | let simpleConstraint = true;
36 | predecessors.forEach(e => {
37 | simpleConstraint = simpleConstraint && getParticipants(e).includes(participant);
38 | });
39 | if (!simpleConstraint) {
40 | reporter.error(shape, 'The initiator ' + shape.businessObject.name + ' needs to be part of all directly preceding activities');
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/app/lib/validator/constraints/SubChoreoParticipantsConstraint.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | import {
4 | flat,
5 | isChoreoActivity
6 | } from '../util/ValidatorUtil';
7 |
8 | /**
9 | * Checks if all participants in a subchoreo are shown as bands on the subchoreo.
10 | * @param shape
11 | * @param reporter {Reporter}
12 | */
13 | export default function subChoreoParticipantsConstraint(shape, reporter) {
14 | if (is(shape, 'bpmn:SubChoreography')) {
15 | const allParticipants = new Set(flat(shape.children.filter(isChoreoActivity)
16 | .map(act => act.bandShapes)).map(bs => bs.businessObject));
17 | const shownParticipants = new Set(shape.bandShapes.map(bs => bs.businessObject));
18 | const notShown = Array.from(allParticipants)
19 | .filter(part => !shownParticipants.has(part)).map(bo => bo.name);
20 | if (notShown.length > 0) {
21 | reporter.warn(shape, 'Participants ' + notShown.join(', ') + ' are used within the sub-choreography, but are not displayed as bands');
22 | }
23 |
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/lib/validator/util/ValidatorUtil.js:
--------------------------------------------------------------------------------
1 | import { is } from 'bpmn-js/lib/util/ModelUtil';
2 |
3 | /**
4 | * Get connected elements
5 | * @param shape The shape to start from
6 | * @param direction 'incoming' || 'outgoing' if to check connected shapes from incoming or outgoing
7 | * @param hasRequiredType {function} function to determine type of connected elements
8 | * @returns {Array}
9 | */
10 | export function getConnectedElements(shape, direction, hasRequiredType) {
11 | if (direction !== 'incoming' && direction !== 'outgoing') {
12 | // This would currently reload the page due to debounce perhaps?
13 | throw new Error('Illegal Argument: ' + direction);
14 | }
15 | if (is(shape, 'bpmn:Participant')) {
16 | shape = shape.parent;
17 | }
18 |
19 | if (!is(shape, 'bpmn:FlowNode')) {
20 | return [];
21 | }
22 |
23 | let visited = [];
24 | let connected = [];
25 |
26 | function track(nodeShape, direction) {
27 | const flowDirection = direction === 'incoming' ? 'source' : 'target';
28 | // avoid loops
29 | if (visited.includes(nodeShape)) {
30 | return;
31 | }
32 | visited.push(nodeShape);
33 |
34 | // add to connected if we have reached an activity
35 | if (shape !== nodeShape && hasRequiredType(nodeShape)) {
36 | connected.push(nodeShape);
37 | return;
38 | }
39 |
40 | // iterate through all incoming or outgoing sequence flows and
41 | // recurse into the sources or targets
42 | nodeShape[direction].forEach(flow => {
43 | track(flow[flowDirection], direction);
44 | });
45 | }
46 |
47 | track(shape, direction);
48 | return connected;
49 | }
50 |
51 | export function getParticipants(shape) {
52 | if (is(shape, 'bpmn:Participant')) {
53 | return [shape.businessObject];
54 | }
55 |
56 | if (is(shape, 'bpmn:ChoreographyActivity')) {
57 | return shape.bandShapes.map(bandShape => bandShape.businessObject);
58 | }
59 |
60 | return [];
61 | }
62 |
63 | export function isInitiating(shape) {
64 | if (is(shape, 'bpmn:Participant')) {
65 | return !shape.diBand.participantBandKind.endsWith('non_initiating');
66 | }
67 | return false;
68 | }
69 |
70 | export function getInitiatingParticipants(shapes) {
71 | return flat(shapes.filter(s => isChoreoActivity(s))
72 | .map(act => act.bandShapes)).filter(part => isInitiating(part));
73 | }
74 |
75 | export function isChoreoActivity(shape) {
76 | return is(shape, 'bpmn:ChoreographyActivity');
77 | }
78 |
79 | export function getTimerDefinitionType(shape) {
80 | const def = shape.businessObject.eventDefinitions[0];
81 | if (def.timeDate) {
82 | return 'timeDate';
83 | } else if (def.timeDuration) {
84 | return 'timeDuration';
85 | } else if (def.timeCycle) {
86 | return 'timeCycle';
87 | }
88 | }
89 |
90 | export function isIntermediateTimerCatchEvent(shape) {
91 | return is(shape, 'bpmn:IntermediateCatchEvent')
92 | && shape.businessObject.eventDefinitions[0].$type === 'bpmn:TimerEventDefinition';
93 | }
94 |
95 | export function participatesIn(participant, shape) {
96 | return getParticipants(shape).includes(participant);
97 | }
98 |
99 | /**
100 | * Stand-in function for flattening a array of depth 1.
101 | * @param {Array} array
102 | * @returns {Array}
103 | */
104 | export function flat(array) {
105 | // Todo: Replace this with the real flat or flatMap when we have an updated
106 | // build pipeline which allows polyfills.
107 | return array.reduce((acc, value) => acc.concat(value), []);
108 | }
109 |
--------------------------------------------------------------------------------
/app/styles/app.less:
--------------------------------------------------------------------------------
1 | @import url("../../node_modules/chor-js/assets/styles/chor-js.css");
2 | @import url("../icons/css/chor-editor.css");
3 | @import url("../../node_modules/bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css");
4 |
5 | * {
6 | box-sizing: border-box;
7 | }
8 |
9 | body,
10 | html {
11 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
12 | color: rgb(51, 51, 51);
13 | font-size: 12px;
14 |
15 | height: 100%;
16 | max-height: 100%;
17 | padding: 0;
18 | margin: 0;
19 | }
20 |
21 | div#panel-toggle {
22 | flex-shrink: 0;
23 | width: 20px;
24 | overflow-x: hidden;
25 |
26 | display: flex;
27 | flex-direction: column;
28 | justify-content: center;
29 | align-items: center;
30 |
31 | background-color: #FAFAFA;
32 | border-left: solid 1px #CCC;
33 |
34 | & > div {
35 | height: 95px;
36 | width: 20px;
37 |
38 | display: flex;
39 | justify-content: center;
40 | align-items: center;
41 |
42 | border-bottom: solid 1px #CCC;
43 | cursor: pointer;
44 |
45 | &:first-child {
46 | border-top: solid 1px #CCC;
47 | }
48 |
49 | & > span {
50 | font-size: 11px;
51 | transform: rotate(-90deg);
52 | white-space: nowrap;
53 | -moz-user-select: none;
54 | -webkit-user-select: none;
55 | -ms-user-select: none;
56 | user-select: none;
57 | }
58 |
59 | &.active {
60 | border-left: 2px solid #4d90ff;
61 | }
62 |
63 | &:hover {
64 | background-color: #eee;
65 | }
66 | }
67 | }
68 |
69 | .side-panel {
70 | background-color: #FAFAFA;
71 | max-width: 250px;
72 | min-width: 250px;
73 | min-height: 100%;
74 | border-left: 1px solid #CCC;
75 |
76 | > .bpp-properties-panel {
77 | background-color: #FAFAFA;
78 | }
79 | }
80 |
81 | .hidden {
82 | display: none;
83 | }
84 |
85 | .content {
86 | position: relative;
87 | width: 100%;
88 | height: 100%;
89 | display: flex;
90 |
91 | #canvas {
92 | flex-grow: 1;
93 | position: relative;
94 | }
95 |
96 | > .message {
97 | width: 100%;
98 | height: 100%;
99 | text-align: center;
100 | display: table;
101 |
102 | font-size: 16px;
103 | color: #111;
104 |
105 | .note {
106 | vertical-align: middle;
107 | text-align: center;
108 | display: table-cell;
109 | }
110 |
111 | &.error {
112 | .details {
113 | max-width: 500px;
114 | font-size: 12px;
115 | margin: 20px auto;
116 | text-align: left;
117 | color: #BD2828;
118 | }
119 |
120 | pre {
121 | border: solid 1px #BD2828;
122 | background: #fefafa;
123 | padding: 10px;
124 | color: #BD2828;
125 | }
126 | }
127 | }
128 |
129 | &:not(.with-error) .error,
130 | &.with-error .intro,
131 | &.with-diagram .intro {
132 | display: none;
133 | }
134 |
135 | .canvas {
136 | display: block;
137 | position: absolute;
138 | top: 0;
139 | left: 0;
140 | right: 0;
141 | bottom: 0;
142 | }
143 | }
144 |
145 | .buttons {
146 | display: flex;
147 | position: fixed;
148 | bottom: 20px;
149 | left: 20px;
150 | padding: 0;
151 | margin: 0;
152 |
153 | * {
154 | margin-right: 10px;
155 | height: 40px;
156 | text-align: center;
157 | line-height: 40px;
158 | font-size: 26px;
159 | }
160 |
161 | .divider {
162 | width: 1px;
163 | border-left: 1px solid #DDD;
164 | }
165 |
166 | a, button {
167 | display: block;
168 | width: 40px;
169 | padding: 0px;
170 | background-color: var(--palette-background-color);
171 | border: solid 1px var(--palette-border-color);
172 | border-radius: 2px;
173 | text-decoration: none;
174 | color: var(--palette-entry-color);
175 | }
176 |
177 | a:hover, button:hover {
178 | color: var(--palette-entry-hover-color);
179 | cursor: pointer;
180 | }
181 |
182 | .selected {
183 | color: var(--palette-entry-selected-color);
184 | }
185 |
186 | button {
187 | cursor: pointer;
188 | }
189 | }
190 |
191 | ul.corner-links {
192 | display: block;
193 | position: absolute;
194 | right: 80px;
195 | bottom: 10px;
196 | margin: 0;
197 | z-index: 100;
198 |
199 | display: flex;
200 | list-style: none;
201 | align-items: center;
202 |
203 | > li {
204 | margin-left: 10px;
205 |
206 | > a {
207 | font-size: 10px;
208 | color: #404040;
209 | }
210 |
211 | > a.icon-github-circled {
212 | font-size: 30px;
213 | text-decoration: none;
214 | }
215 | }
216 | }
217 |
218 | .validation-annotation {
219 | position: relative;
220 | border-width: 2px;
221 | border-color: #FFFFFF;
222 | border-style: solid;
223 | border-radius: 50%;
224 | padding: 2px;
225 | width: 20px;
226 | height: 20px;
227 |
228 | &:hover > .validation-info {
229 | display: block;
230 | }
231 |
232 | .validation-info {
233 | display: none;
234 | position: absolute;
235 | top: -2px;
236 | left: 6px;
237 | list-style: none;
238 | padding-left: 0;
239 | min-width: 180px;
240 | max-height: 300px;
241 | overflow-y: auto;
242 | z-index: 4;
243 |
244 | li {
245 | padding: 4px;
246 | border-style: solid;
247 | border-width: 2px;
248 | border-top-width: 0px;
249 | }
250 |
251 | li:first-child {
252 | border-top-left-radius: 5px;
253 | border-top-right-radius: 5px;
254 | border-top-width: 2px;
255 | }
256 |
257 | li:last-child {
258 | border-bottom-left-radius: 5px;
259 | border-bottom-right-radius: 5px;
260 | }
261 |
262 | .li-error {
263 | background-color: #FDF2F5;
264 | border-color: #EC1E0D;
265 | }
266 |
267 | .li-warning {
268 | background-color: #FFFBD5;
269 | border-color: #FB9500;
270 | }
271 |
272 | .li-note {
273 | background-color: rgb(245, 245, 245);
274 | border-color: rgb(132, 140, 148);
275 | }
276 | }
277 |
278 | .validation-count {
279 | position: absolute;
280 | font-weight: bold;
281 | color: #111111;
282 | left: 2px;
283 | top: -6px;
284 | text-shadow: 1px 1px 1px #FFFFFF, -1px -1px 1px #FFFFFF, 1px -1px 1px #FFFFFF, -1px 1px 1px #FFFFFF;
285 | }
286 | }
287 |
288 | .val-error {
289 | .validation-annotation;
290 | background-color: #EC1E0D;
291 | }
292 |
293 | .val-warning {
294 | .validation-annotation;
295 | background-color: #FB9500;
296 | }
297 |
298 | .is-dragover {
299 | background-color: #eeeeee;
300 | opacity: 0.25;
301 | }
302 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "chor-js-demo",
3 | "version": "1.1.0",
4 | "description": "A demo showcasing the chor-js BPMN 2.0 choreography diagram modeler.",
5 | "main": "app/app.js",
6 | "scripts": {
7 | "lint": "eslint .",
8 | "build": "parcel build app/index.html -d build --public-url ./",
9 | "dev": "parcel app/index.html -p 9013"
10 | },
11 | "keywords": [
12 | "choreography-modeler"
13 | ],
14 | "author": {
15 | "name": "Jan Ladleif",
16 | "url": "https://github.com/jan-ladleif"
17 | },
18 | "license": "MIT",
19 | "repository": {
20 | "type": "git",
21 | "url": "https://github.com/bptlab/chor-js-demo"
22 | },
23 | "assetTypesToStringify": [
24 | "bpmn"
25 | ],
26 | "browserslist": [
27 | "last 2 Firefox versions",
28 | "last 2 Chrome versions",
29 | "last 2 Safari versions",
30 | "last 2 Edge versions",
31 | "last 2 Opera versions",
32 | "last 2 Baidu versions"
33 | ],
34 | "devDependencies": {
35 | "@babel/core": "^7.8.7",
36 | "eslint": "^4.19.1",
37 | "eslint-plugin-bpmn-io": "^0.7.0",
38 | "less": "^3.11.1",
39 | "parcel-bundler": "^1.12.4",
40 | "parcel-plugin-stringify-anything": "^1.2.0"
41 | },
42 | "dependencies": {
43 | "bpmn-js-properties-panel": "^0.35.0",
44 | "chor-js": "0.7.1",
45 | "inherits": "^2.0.4"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------