├── .eslintignore
├── .eslintrc.json
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .npmignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── index.html
└── index.js
├── client
├── bpmn-js-extension
│ ├── context-pad
│ │ ├── OpenProcessContextPad.js
│ │ └── index.js
│ └── multi-diagram
│ │ ├── COPYING
│ │ ├── DiagramSwitch.js
│ │ ├── cmd
│ │ ├── CreateDiagramHandler.js
│ │ ├── DeleteDiagramHandler.js
│ │ └── RenameDiagramHandler.js
│ │ ├── index.js
│ │ └── utils
│ │ ├── DiagramUtil.js
│ │ └── index.js
├── index.js
├── properties-provider
│ ├── CallActivityPropertiesProvider.js
│ ├── index.js
│ └── props
│ │ ├── CalledElementProps.js
│ │ └── CalledTypeProps.js
├── react
│ ├── DiagramButtonsOverlay.js
│ ├── MultiDiagramButton.js
│ └── modals
│ │ └── rename-diagram
│ │ └── RenameDiagramModal.js
└── style.css
├── docs
└── screencast.gif
├── index.js
├── index.prod.js
├── package-lock.json
├── package.json
├── resources
├── minus-solid.svg
├── newDiagram.bpmn
├── pencil-solid.svg
├── plus-solid.svg
└── subprocess-collapsed.svg
├── styles
└── app.less
├── webpack.config.js
└── webpack.config.serve.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true
5 | },
6 | "extends": [
7 | "eslint:recommended",
8 | "plugin:react/recommended",
9 | "plugin:bpmn-io/es6"
10 | ],
11 | "rules": {
12 | "indent": [ 2, 2, {
13 | "VariableDeclarator": { "var": 2, "let": 2, "const": 3 },
14 | "FunctionDeclaration": { "body": 1, "parameters": 2 },
15 | "FunctionExpression": { "body": 1, "parameters": 2 },
16 | "ignoredNodes": [ "TemplateLiteral > *" ]
17 | } ],
18 | "no-bitwise": 0,
19 | "react/prop-types": 0,
20 | "react/react-in-jsx-scope": 0
21 | }
22 | }
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [ master ]
9 | pull_request:
10 | branches: [ master ]
11 |
12 | jobs:
13 | build:
14 |
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | matrix:
19 | node-version: [12.x, 14.x, 16.x]
20 | # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
21 |
22 | steps:
23 | - uses: actions/checkout@v2
24 | - name: Use Node.js ${{ matrix.node-version }}
25 | uses: actions/setup-node@v2
26 | with:
27 | node-version: ${{ matrix.node-version }}
28 | cache: 'npm'
29 | - run: npm ci
30 | - run: npm run all --if-present
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist
2 | public
3 | tmp
4 | node_modules
5 |
6 | .idea
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | !dist
2 | client
3 | webpack.config.js
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to the [camunda-modeler-plugin-multidiagram](https://github.com/sharedchains/camunda-modeler-plugin-multidiagram/) are documented here. We use [semantic versioning](http://semver.org/) for releases.
4 |
5 | ## Unreleased
6 |
7 | ___Note:__ Yet to be released changes appear here._
8 |
9 | ## 2.0.2
10 |
11 | * `FEAT`: Supported Collaboration multi-diagram again
12 |
13 | ## 2.0.1
14 |
15 | * `FIX`: Fixed diagram delete behaviour and revert commands
16 |
17 | ## 2.0.0
18 |
19 | * `CHORE`: support Camunda Modeler 5.x.x+
20 |
21 | ## 1.0.1
22 |
23 | * `FEAT`: Removes SwitchDiagram as a command
24 | * `FIX`: Fixes error switching between bpmn tabs
25 |
26 | ## 1.0.0
27 |
28 | * `CHORE`: support Camunda Modeler v4.x.x+
29 | * `FEAT`: Manage multiple diagrams on a single bpmn file, according to BPMN specifications
30 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Shared
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Camunda Modeler Multi-Diagram plug-in
2 |
3 | [](https://github.com/camunda/camunda-modeler)
4 |
5 | A [Camunda Modeler](https://github.com/camunda/camunda-modeler) plug-in based on the [plug-in example](https://github.com/camunda/camunda-modeler-plugin-example).
6 |
7 | ## How to install the plugin
8 |
9 | Download the latest [release zip](https://github.com/sharedchains/camunda-modeler-plugin-multidiagram/releases/) and extract it to your camunda-modeler/resources/plugins folder. That's all!
10 |
11 | ## About
12 |
13 | This plug-in adds the ability to Camunda modeler to manage multiple diagrams on a single bpmn file, as intended on BPMN specifications. Then it's possible to link, on a multi-diagram bpmn, as Caller element for a Call Activity task, one of the processes inside the bpmn file itself.
14 |
15 | 
16 |
17 | ## Development Setup
18 |
19 | Use [npm](https://www.npmjs.com/), the [Node.js](https://nodejs.org/en/) package manager to download and install required dependencies:
20 |
21 | ```sh
22 | npm install
23 | ```
24 |
25 | To make the Camunda Modeler aware of your plug-in you must link the plug-in to the [Camunda Modeler plug-in directory](https://github.com/camunda/camunda-modeler/tree/develop/docs/plugins#plugging-into-the-camunda-modeler) via a symbolic link.
26 | Available utilities to do that are [`mklink /d`](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/mklink) on Windows and [`ln -s`](https://linux.die.net/man/1/ln) on MacOS / Linux.
27 |
28 | Re-start the app in order to recognize the newly linked plug-in.
29 |
30 |
31 | ## Building the Plug-in
32 |
33 | You may spawn the development setup to watch source files and re-build the client plug-in on changes:
34 |
35 | ```sh
36 | npm run dev
37 | ```
38 |
39 | Given you've setup and linked your plug-in [as explained above](#development-setup), you should be able to reload the modeler to pick up plug-in changes. To do so, open the app's built in development toos via `F12`. Then, within the development tools press the reload shortcuts `CTRL + R` or `CMD + R` to reload the app.
40 |
41 |
42 | To prepare the plug-in for release, executing all necessary steps, run:
43 |
44 | ```sh
45 | npm run all
46 | ```
47 |
48 | ## Compatibility Notice
49 |
50 | This plugin is currently compatible with the following Camunda Modeler versions.
51 |
52 | | Camunda Modeler | MultiDiagram Plugin |
53 | |-----------------|---------------------|
54 | | 3.4 - 4.12 | 1.0.1 |
55 | | 5.x | 2.0 or newer |
56 |
57 | ## Additional Resources
58 |
59 | * [bpmn-js](https://github.com/sharedchains/bpmn-js/tree/feature/multipleDiagram)
60 | * [Camunda modeler](https://github.com/sharedchains/camunda-modeler/tree/feature/multiDiagrams)
61 | * [Plug-ins documentation](https://docs.camunda.io/docs/components/modeler/desktop-modeler/plugins/)
62 |
63 | ## Licence
64 |
65 | MIT
66 |
--------------------------------------------------------------------------------
/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | bpmn-js-properties-panel extension demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
19 |
20 |
21 |
22 |
Ooops, we could not display the BPMN 2.0 diagram.
23 |
24 |
25 |
cause of the problem
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 | import BpmnModeler from 'bpmn-js/lib/Modeler';
3 |
4 | import {
5 | BpmnPropertiesPanelModule,
6 | BpmnPropertiesProviderModule,
7 | CamundaPlatformPropertiesProviderModule
8 | } from 'bpmn-js-properties-panel';
9 |
10 | import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';
11 | import CamundaModule from 'camunda-bpmn-moddle/lib';
12 |
13 | import MultiDiagramFeaturesModule from '../client/bpmn-js-extension/multi-diagram';
14 | import ProcessContextPadModule from '../client/bpmn-js-extension/context-pad';
15 | import CallActivityExtModule from '../client/properties-provider';
16 |
17 | import {
18 | debounce
19 | } from 'min-dash';
20 |
21 | import diagramXML from '../resources/newDiagram.bpmn';
22 |
23 | import '../styles/app.less';
24 |
25 | var container = $('#js-drop-zone');
26 |
27 | var bpmnModeler = new BpmnModeler({
28 | container: '#js-canvas',
29 | propertiesPanel: {
30 | parent: '#js-properties-panel'
31 | },
32 | additionalModules: [
33 | BpmnPropertiesPanelModule,
34 | BpmnPropertiesProviderModule,
35 | MultiDiagramFeaturesModule,
36 | ProcessContextPadModule,
37 | CallActivityExtModule,
38 | CamundaPlatformPropertiesProviderModule,
39 | CamundaModule
40 | ],
41 | moddleExtensions: {
42 | camunda: camundaModdleDescriptor
43 | }
44 | });
45 |
46 | function createNewDiagram() {
47 | openDiagram(diagramXML);
48 | }
49 |
50 | async function openDiagram(xml) {
51 |
52 | try {
53 |
54 | await bpmnModeler.importXML(xml);
55 |
56 | container
57 | .removeClass('with-error')
58 | .addClass('with-diagram');
59 | } catch (err) {
60 |
61 | container
62 | .removeClass('with-diagram')
63 | .addClass('with-error');
64 |
65 | container.find('.error pre').text(err.message);
66 |
67 | console.error(err);
68 | }
69 | }
70 |
71 | function registerFileDrop(container, callback) {
72 |
73 | function handleFileSelect(e) {
74 | e.stopPropagation();
75 | e.preventDefault();
76 |
77 | var files = e.dataTransfer.files;
78 |
79 | var file = files[0];
80 |
81 | var reader = new FileReader();
82 |
83 | reader.onload = function(e) {
84 |
85 | var xml = e.target.result;
86 |
87 | callback(xml);
88 | };
89 |
90 | reader.readAsText(file);
91 | }
92 |
93 | function handleDragOver(e) {
94 | e.stopPropagation();
95 | e.preventDefault();
96 |
97 | e.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
98 | }
99 |
100 | container.get(0).addEventListener('dragover', handleDragOver, false);
101 | container.get(0).addEventListener('drop', handleFileSelect, false);
102 | }
103 |
104 |
105 | // //// file drag / drop ///////////////////////
106 |
107 | // check file api availability
108 | if (!window.FileList || !window.FileReader) {
109 | window.alert(
110 | 'Looks like you use an older browser that does not support drag and drop. ' +
111 | 'Try using Chrome, Firefox or the Internet Explorer > 10.');
112 | } else {
113 | registerFileDrop(container, openDiagram);
114 | }
115 |
116 | // bootstrap diagram functions
117 |
118 | $(function() {
119 |
120 | $('#js-create-diagram').click(function(e) {
121 | e.stopPropagation();
122 | e.preventDefault();
123 |
124 | createNewDiagram();
125 | });
126 |
127 | var downloadLink = $('#js-download-diagram');
128 | var downloadSvgLink = $('#js-download-svg');
129 |
130 | $('.buttons a').click(function(e) {
131 | if (!$(this).is('.active')) {
132 | e.preventDefault();
133 | e.stopPropagation();
134 | }
135 | });
136 |
137 | function setEncoded(link, name, data) {
138 | var encodedData = encodeURIComponent(data);
139 |
140 | if (data) {
141 | link.addClass('active').attr({
142 | 'href': 'data:application/bpmn20-xml;charset=UTF-8,' + encodedData,
143 | 'download': name
144 | });
145 | } else {
146 | link.removeClass('active');
147 | }
148 | }
149 |
150 | var exportArtifacts = debounce(async function() {
151 |
152 | try {
153 |
154 | const { svg } = await bpmnModeler.saveSVG();
155 |
156 | setEncoded(downloadSvgLink, 'diagram.svg', svg);
157 | } catch (err) {
158 |
159 | console.error('Error happened saving SVG: ', err);
160 |
161 | setEncoded(downloadSvgLink, 'diagram.svg', null);
162 | }
163 |
164 | try {
165 |
166 | const { xml } = await bpmnModeler.saveXML({ format: true });
167 |
168 | setEncoded(downloadLink, 'diagram.bpmn', xml);
169 | } catch (err) {
170 |
171 | console.error('Error happened saving diagram: ', err);
172 |
173 | setEncoded(downloadLink, 'diagram.bpmn', null);
174 | }
175 | }, 500);
176 |
177 | bpmnModeler.on('commandStack.changed', exportArtifacts);
178 |
179 |
180 |
181 |
182 | // HANDLE MULTI-DIAGRAM //////////////////////////////////
183 | function switchDiagram(e) {
184 | let id = e.target.value;
185 | const bpmnjs = bpmnModeler.get('bpmnjs');
186 | bpmnjs.open(id);
187 | }
188 |
189 | function addDiagram() {
190 | let diagramSwitch = bpmnModeler.get('diagramSwitch');
191 | diagramSwitch.addDiagram();
192 | populateDiagramCombo();
193 | }
194 |
195 | function deleteDiagram() {
196 | let diagramSwitch = bpmnModeler.get('diagramSwitch');
197 | diagramSwitch.deleteDiagram();
198 | populateDiagramCombo();
199 | }
200 |
201 | function renameDiagram(e) {
202 | let diagramSwitch = bpmnModeler.get('diagramSwitch');
203 | diagramSwitch.renameDiagram(e.target.value);
204 | populateDiagramCombo();
205 | }
206 |
207 | function populateDiagramCombo() {
208 | let diagramSwitch = bpmnModeler.get('diagramSwitch');
209 | const select = $('.djs-select');
210 | select.empty();
211 |
212 | const currentDiagram = diagramSwitch._diagramUtil.currentDiagram();
213 | const diagrams = diagramSwitch._diagramUtil.diagrams();
214 |
215 | diagrams.forEach((diagram) => {
216 | const diagramName = diagram.name || diagram.id;
217 | select.append(`
218 |
223 | `);
224 | });
225 | }
226 |
227 | function handleEndRenameEvent(e) {
228 | if (e.keyCode && e.keyCode !== 13) {
229 | return;
230 | }
231 |
232 | displaySelectInterface();
233 | }
234 |
235 | function displayRenameInterface() {
236 | hideInterface();
237 |
238 | let diagramSwitch = bpmnModeler.get('diagramSwitch');
239 | const renameWrapper = document.querySelector('.djs-rename-wrapper');
240 | renameWrapper.style.display = 'flex';
241 |
242 | const renameInput = document.querySelector('.djs-rename');
243 | const currentDiagram = diagramSwitch._diagramUtil.currentDiagram();
244 | renameInput.value = currentDiagram.name || currentDiagram.id;
245 | renameInput.focus();
246 | renameInput.select();
247 | }
248 |
249 | function displaySelectInterface() {
250 | hideInterface();
251 |
252 | populateDiagramCombo();
253 | const selectWrapper = document.querySelector('.djs-select-wrapper');
254 | selectWrapper.style.display = 'flex';
255 | }
256 |
257 | function hideInterface() {
258 | const renameWrapper = document.querySelector('.djs-rename-wrapper');
259 | renameWrapper.style.display = 'none';
260 |
261 | const selectWrapper = document.querySelector('.djs-select-wrapper');
262 | selectWrapper.style.display = 'none';
263 | }
264 |
265 | let eventBus = bpmnModeler.get('eventBus');
266 | eventBus.once('import.render.complete', populateDiagramCombo);
267 |
268 | $('.djs-palette').append(`
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 | `);
281 |
282 | $('.djs-select').on('change', switchDiagram);
283 | $('#add-diagram').on('click', addDiagram);
284 | $('#delete-diagram').on('click', deleteDiagram);
285 |
286 | $('#start-rename-diagram').on('click', displayRenameInterface);
287 | $('.djs-rename').on('change', renameDiagram);
288 | $('.djs-rename').on('keyup', handleEndRenameEvent);
289 | $('#end-rename-diagram').on('click', handleEndRenameEvent);
290 | });
291 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/context-pad/OpenProcessContextPad.js:
--------------------------------------------------------------------------------
1 | import { getBusinessObject, is } from 'bpmn-js/lib/util/ModelUtil';
2 | import { find } from 'min-dash';
3 |
4 | export default class CustomContextPad {
5 | constructor(config, eventBus, contextPad, injector, translate) {
6 | this.translate = translate;
7 | this.eventBus = eventBus;
8 |
9 | if (config.diagramUtil !== false) {
10 | this.diagramUtil = injector.get('diagramUtil', false);
11 | }
12 |
13 | contextPad.registerProvider(this);
14 | }
15 |
16 | getContextPadEntries(element) {
17 | const {
18 | translate,
19 | diagramUtil,
20 | eventBus
21 | } = this;
22 |
23 | eventBus.on('element.dblclick', 1500, function(event) {
24 | if (isInternalCallActivity(event.element)) {
25 |
26 | // do your stuff here
27 | openProcess(event, event.element);
28 |
29 | // stop propagating the event to prevent the default behavior
30 | event.stopPropagation();
31 | }
32 | });
33 |
34 | function getDiagram(rootElementId) {
35 | return find(diagramUtil.diagrams(), function(diagram) {
36 | return diagram.plane.bpmnElement && diagram.plane.bpmnElement.id === rootElementId;
37 | });
38 | }
39 |
40 | function openProcess(_event, element) {
41 | let bo = getBusinessObject(element);
42 | let calledElement = bo.get('calledElement');
43 | if (calledElement.startsWith('inner:')) {
44 | let diagram = getDiagram(calledElement.replace(/^(inner:)/, ''));
45 | if (diagram) {
46 | eventBus.fire('diagram.switch', { diagram: diagram });
47 | }
48 | }
49 | }
50 |
51 | function isInternalCallActivity(element) {
52 | let bo = getBusinessObject(element);
53 |
54 | return is(element, 'bpmn:CallActivity')
55 | && diagramUtil.diagrams().length > 1
56 | && (typeof bo.get('calledElement') !== 'undefined')
57 | && bo.get('calledElement').startsWith('inner:');
58 | }
59 |
60 | let newContext = {};
61 | if (isInternalCallActivity(element)) {
62 | newContext = {
63 | 'open.process': {
64 | group: 'model',
65 | className: 'bpmn-icon-hand-tool',
66 | title: translate('Open process'),
67 | action: {
68 | click: openProcess,
69 | }
70 | }
71 | };
72 | }
73 | return newContext;
74 | }
75 | }
76 |
77 | CustomContextPad.$inject = [
78 | 'config',
79 | 'eventBus',
80 | 'contextPad',
81 | 'injector',
82 | 'translate'
83 | ];
84 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/context-pad/index.js:
--------------------------------------------------------------------------------
1 | import OpenProcessContextPad from './OpenProcessContextPad';
2 |
3 | /**
4 | * A bpmn-js module, defining all extension services and their dependencies.
5 | *
6 | *
7 | */
8 | export default {
9 | __init__: [ 'openProcessContextPad' ],
10 | openProcessContextPad: [ 'type', OpenProcessContextPad ]
11 | };
12 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/COPYING:
--------------------------------------------------------------------------------
1 | All code is copyright 2018 from chor-js project under the MIT license.
2 |
3 | MIT License
4 |
5 | Copyright (c) 2018 Jan Ladleif
6 |
7 | Permission is hereby granted, free of charge, to any person obtaining a copy
8 | of this software and associated documentation files (the "Software"), to deal
9 | in the Software without restriction, including without limitation the rights
10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 | copies of the Software, and to permit persons to whom the Software is
12 | furnished to do so, subject to the following conditions:
13 |
14 | The above copyright notice and this permission notice shall be included in all
15 | copies or substantial portions of the Software.
16 |
17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 | SOFTWARE.
24 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/DiagramSwitch.js:
--------------------------------------------------------------------------------
1 | import CreateDiagramHandler from './cmd/CreateDiagramHandler';
2 | import DeleteDiagramHandler from './cmd/DeleteDiagramHandler';
3 | import RenameDiagramHandler from './cmd/RenameDiagramHandler';
4 |
5 | export default function DiagramSwitch(eventBus, commandStack, diagramUtil) {
6 | this._eventBus = eventBus;
7 | this._commandStack = commandStack;
8 | this._diagramUtil = diagramUtil;
9 |
10 | // Bind this globally
11 | this.registerHandlers = registerHandlers.bind(this);
12 |
13 | // Register to events
14 | this._eventBus.on('diagram.init', this.registerHandlers);
15 | }
16 |
17 | function registerHandlers() {
18 |
19 | this._commandStack.registerHandler('diagram.create', CreateDiagramHandler);
20 | this._commandStack.registerHandler('diagram.delete', DeleteDiagramHandler);
21 | this._commandStack.registerHandler('diagram.rename', RenameDiagramHandler);
22 | }
23 |
24 | DiagramSwitch.$inject = [
25 | 'eventBus',
26 | 'commandStack',
27 | 'diagramUtil'
28 | ];
29 |
30 | DiagramSwitch.prototype.addDiagram = function() {
31 | this._commandStack.execute('diagram.create', {});
32 | };
33 |
34 | DiagramSwitch.prototype.deleteDiagram = function() {
35 | if (this._commandStack.canExecute('diagram.delete', {})) {
36 | this._commandStack.execute('diagram.delete', {});
37 | }
38 | };
39 |
40 | DiagramSwitch.prototype.renameDiagram = function(name) {
41 | if (this._commandStack.canExecute('diagram.rename', { newName: name })) {
42 | this._commandStack.execute('diagram.rename', {
43 | newName: name
44 | });
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/cmd/CreateDiagramHandler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Handler which creates a new diagram in the same bpmn file.
3 | */
4 | export default function CreateDiagramHandler(bpmnjs, bpmnFactory, diagramUtil, commandStack) {
5 |
6 | this._bpmnjs = bpmnjs;
7 | this._bpmnFactory = bpmnFactory;
8 | this._diagramUtil = diagramUtil;
9 | this._commandStack = commandStack;
10 | }
11 |
12 | CreateDiagramHandler.$inject = [
13 | 'bpmnjs',
14 | 'bpmnFactory',
15 | 'diagramUtil',
16 | 'commandStack'
17 | ];
18 |
19 | CreateDiagramHandler.prototype.createProcess = function() {
20 | const process = this._bpmnFactory.create('bpmn:Process', {});
21 | process.$parent = this._diagramUtil.definitions();
22 | return process;
23 | };
24 |
25 | CreateDiagramHandler.prototype.createDiagram = function(rootElement) {
26 | const plane = this._bpmnFactory.createDiPlane(rootElement);
27 | const diagram = this._bpmnFactory.create('bpmndi:BPMNDiagram', {
28 | plane: plane
29 | });
30 | plane.$parent = diagram;
31 | diagram.$parent = this._diagramUtil.definitions();
32 | return diagram;
33 | };
34 |
35 | CreateDiagramHandler.prototype.preExecute = function(context) {
36 |
37 | context.oldDiagramId = this._diagramUtil.currentDiagram().id;
38 |
39 | // create new semantic objects
40 | const newProcess = this.createProcess();
41 | const newDiagram = this.createDiagram(newProcess);
42 |
43 | // store them in the context
44 | context.newProcess = newProcess;
45 | context.newDiagram = newDiagram;
46 | };
47 |
48 | CreateDiagramHandler.prototype.execute = function(context) {
49 | this._diagramUtil.definitions().rootElements.push(context.newProcess);
50 | this._bpmnjs._definitions.diagrams.push(context.newDiagram);
51 |
52 | this._bpmnjs.open(context.newDiagram.id);
53 | };
54 |
55 | CreateDiagramHandler.prototype.revert = function(context) {
56 | this._diagramUtil.removeDiagramById(context.newProcess.id);
57 | this._bpmnjs.open(context.oldDiagramId);
58 | };
59 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/cmd/DeleteDiagramHandler.js:
--------------------------------------------------------------------------------
1 | import { find } from 'min-dash';
2 |
3 | /**
4 | * Handler which deletes the currently displayed diagram.
5 | */
6 | export default function DeleteDiagramHandler(bpmnjs, commandStack, diagramUtil) {
7 | this._bpmnjs = bpmnjs;
8 | this._commandStack = commandStack;
9 | this._diagramUtil = diagramUtil;
10 | }
11 |
12 | DeleteDiagramHandler.$inject = [
13 | 'bpmnjs',
14 | 'commandStack',
15 | 'diagramUtil'
16 | ];
17 |
18 | // eslint-disable-next-line no-unused-vars
19 | DeleteDiagramHandler.prototype.canExecute = function(_context) {
20 | return this._diagramUtil.diagrams().length > 1;
21 | };
22 |
23 | DeleteDiagramHandler.prototype.preExecute = function(context) {
24 | const diagrams = this._diagramUtil.diagrams();
25 | if (context.diagram) {
26 | const diagramToRemove = find(diagrams, diagram => diagram.id === context.diagram);
27 | context.removedProcess = { ...diagramToRemove.plane.bpmnElement };
28 | context.removedDiagram = diagramToRemove;
29 | } else {
30 | context.removedProcess = this._diagramUtil.currentRootElement();
31 | context.removedDiagram = this._diagramUtil.currentDiagram();
32 | }
33 |
34 | // switch to the first diagram in the list that is not to be deleted
35 | const otherDiagramId = find(diagrams, function(diagram) {
36 | return (diagram.id !== context.diagram);
37 | }).id;
38 | this._bpmnjs.open(otherDiagramId);
39 | };
40 |
41 | DeleteDiagramHandler.prototype.execute = function(context) {
42 | context.indices = this._diagramUtil.removeDiagramById(context.removedProcess.id);
43 | };
44 |
45 | DeleteDiagramHandler.prototype.revert = function(context) {
46 |
47 | // reinsert the rootElement and diagram
48 | this._bpmnjs._definitions.diagrams.push(context.removedDiagram);
49 | this._bpmnjs._definitions.rootElements.push(context.removedProcess);
50 | };
51 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/cmd/RenameDiagramHandler.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Handler which renames the currently displayed diagram.
3 | */
4 | export default function RenameDiagramHandler(diagramUtil) {
5 |
6 | this._diagramUtil = diagramUtil;
7 | }
8 |
9 | RenameDiagramHandler.$inject = [
10 | 'diagramUtil'
11 | ];
12 |
13 | RenameDiagramHandler.prototype.canExecute = function(context) {
14 | return context.newName.length > 0;
15 | };
16 |
17 | RenameDiagramHandler.prototype.preExecute = function(context) {
18 | context.oldName = this._diagramUtil.currentDiagram().id;
19 | };
20 |
21 | RenameDiagramHandler.prototype.execute = function(context) {
22 | this._diagramUtil.currentDiagram().id = context.newName;
23 | };
24 |
25 | RenameDiagramHandler.prototype.revert = function(context) {
26 | this._diagramUtil.currentDiagram().id = context.oldName;
27 | };
28 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/index.js:
--------------------------------------------------------------------------------
1 | import DiagramSwitch from './DiagramSwitch';
2 | import DiagramUtil from './utils';
3 |
4 | export default {
5 | __init__: [
6 | 'diagramSwitch'
7 | ],
8 | __depends__: [
9 | DiagramUtil
10 | ],
11 | diagramSwitch: [ 'type', DiagramSwitch ]
12 | };
13 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/utils/DiagramUtil.js:
--------------------------------------------------------------------------------
1 | import { find, findIndex } from 'min-dash';
2 |
3 | export default function DiagramUtil(bpmnjs, canvas) {
4 |
5 | this._bpmnjs = bpmnjs;
6 | this._canvas = canvas;
7 | }
8 |
9 | DiagramUtil.$inject = [
10 | 'bpmnjs',
11 | 'canvas'
12 | ];
13 |
14 | DiagramUtil.prototype.currentRootElement = function() {
15 | return this._canvas.getRootElement().businessObject;
16 | };
17 |
18 | DiagramUtil.prototype.currentDiagram = function() {
19 | const currentRootElement = this.currentRootElement();
20 | if (currentRootElement) {
21 | return find(this.diagrams(), function(diagram) {
22 | return diagram.plane.bpmnElement && diagram.plane.bpmnElement.id === currentRootElement.id;
23 | });
24 | }
25 | };
26 |
27 | DiagramUtil.prototype.definitions = function() {
28 | return this._bpmnjs._definitions || [];
29 | };
30 |
31 | DiagramUtil.prototype.isCollaboration = function() {
32 | return this.definitions()?.rootElements?.filter(rootElement => rootElement.$type === 'bpmn:Collaboration').length > 0;
33 | };
34 |
35 | DiagramUtil.prototype.diagrams = function() {
36 | return this.definitions()?.diagrams || [];
37 | };
38 |
39 | DiagramUtil.prototype.removeDiagramById = function(rootElementId) {
40 | const elementIndex = findIndex(this.definitions().rootElements, function(rootElement) {
41 | return rootElement.id === rootElementId;
42 | });
43 |
44 | if (elementIndex >= 0) {
45 | this.definitions().rootElements.splice(elementIndex, 1);
46 | } else {
47 | throw new Error('could not find root element with ID ' + rootElementId);
48 | }
49 |
50 | const diagramIndex = findIndex(this.definitions().diagrams, function(diagram) {
51 | return diagram.plane.bpmnElement && diagram.plane.bpmnElement.id === rootElementId;
52 | });
53 |
54 | if (diagramIndex >= 0) {
55 | this.definitions().diagrams.splice(diagramIndex, 1);
56 | } else {
57 | throw new Error('could not find diagram for ID ' + rootElementId);
58 | }
59 |
60 | return {
61 | elementIndex: elementIndex,
62 | diagramIndex: diagramIndex
63 | };
64 | };
65 |
--------------------------------------------------------------------------------
/client/bpmn-js-extension/multi-diagram/utils/index.js:
--------------------------------------------------------------------------------
1 | import DiagramUtil from './DiagramUtil';
2 |
3 | export default {
4 | __init__: [
5 | 'diagramUtil'
6 | ],
7 | diagramUtil: [ 'type', DiagramUtil ]
8 | };
9 |
--------------------------------------------------------------------------------
/client/index.js:
--------------------------------------------------------------------------------
1 | import {
2 | registerBpmnJSPlugin,
3 | registerClientExtension
4 | } from 'camunda-modeler-plugin-helpers';
5 |
6 | import MultiDiagramButton from './react/MultiDiagramButton';
7 |
8 | import MultiDiagramFeatures from './bpmn-js-extension/multi-diagram';
9 | import ProcessContextPad from './bpmn-js-extension/context-pad';
10 | import CallActivityExt from './properties-provider';
11 |
12 | registerBpmnJSPlugin(MultiDiagramFeatures);
13 | registerBpmnJSPlugin(ProcessContextPad);
14 | registerBpmnJSPlugin(CallActivityExt);
15 |
16 | registerClientExtension(MultiDiagramButton);
--------------------------------------------------------------------------------
/client/properties-provider/CallActivityPropertiesProvider.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | import { is, getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
4 | import { find } from 'min-dash';
5 |
6 | import calledTypeProps from './props/CalledTypeProps';
7 | import calledElementProps from './props/CalledElementProps';
8 |
9 | function getCallableType(element) {
10 | const bo = getBusinessObject(element);
11 |
12 | const boCalledElement = bo.get('calledElement'),
13 | boCaseRef = bo.get('camunda:caseRef');
14 |
15 | let callActivityType = '';
16 | if (typeof boCalledElement !== 'undefined') {
17 | callActivityType = 'bpmn';
18 | } else if (typeof boCaseRef !== 'undefined') {
19 | callActivityType = 'cmmn';
20 | }
21 |
22 | return callActivityType;
23 | }
24 |
25 | function isInternal(element) {
26 | const bo = getBusinessObject(element);
27 | const boCalledElement = bo.get('calledElement');
28 | return !!(typeof boCalledElement !== 'undefined' &&
29 | boCalledElement.startsWith('inner:'));
30 | }
31 |
32 | /**
33 | * A provider for CallActivity elements, to open the global subprocess of the BPMN
34 | * @constructor
35 | */
36 | export default class CallActivityPropertiesProvider {
37 |
38 | constructor(propertiesPanel, injector) {
39 | const eventBus = injector.get('eventBus');
40 | const bpmnjs = injector.get('bpmnjs');
41 |
42 | this.diagramUtil = injector.get('diagramUtil');
43 |
44 | // Not sure it's the right place but whatever...
45 | eventBus.on('diagram.switch', 10000, (event) => {
46 | bpmnjs.open(event.diagram.id);
47 | });
48 |
49 | propertiesPanel.registerProvider(200, this);
50 | }
51 |
52 |
53 | /**
54 | * Return the groups provided for the given element.
55 | *
56 | * @param {DiagramElement} element
57 | *
58 | * @return {(Object[]) => (Object[])} groups middleware
59 | */
60 | getGroups(element) {
61 |
62 | /**
63 | * We return a middleware that modifies
64 | * the existing groups.
65 | *
66 | * @param {Object[]} groups
67 | *
68 | * @return {Object[]} modified groups
69 | */
70 | return groups => {
71 |
72 | if (is(element, 'bpmn:CallActivity') && this.diagramUtil.diagrams().length > 1 && getCallableType(element) === 'bpmn') {
73 |
74 | let calledElement = find(groups, (entry) => entry.id === 'CamundaPlatform__CallActivity');
75 |
76 | if (calledElement) {
77 | calledElement.entries.push(...calledTypeProps(element));
78 |
79 | if (isInternal(element)) {
80 | calledElement.entries.push(...calledElementProps(element));
81 | }
82 | }
83 | }
84 |
85 | return groups;
86 | };
87 |
88 | };
89 |
90 |
91 | }
92 |
93 | CallActivityPropertiesProvider.prototype.getCallableType = function(element) {
94 | return getCallableType(element);
95 | };
96 |
97 | CallActivityPropertiesProvider.$inject = [ 'propertiesPanel', 'injector' ];
--------------------------------------------------------------------------------
/client/properties-provider/index.js:
--------------------------------------------------------------------------------
1 | import CallActivityPropertiesProvider from './CallActivityPropertiesProvider';
2 |
3 | /**
4 | * A bpmn-js module, defining all extension services and their dependencies.
5 | *
6 | *
7 | */
8 | export default {
9 | __init__: [ 'CallableProcessProvider' ],
10 | CallableProcessProvider: [ 'type', CallActivityPropertiesProvider ]
11 | };
12 |
--------------------------------------------------------------------------------
/client/properties-provider/props/CalledElementProps.js:
--------------------------------------------------------------------------------
1 | import { SelectEntry, isSelectEntryEdited } from '@bpmn-io/properties-panel';
2 | import { useService } from 'bpmn-js-properties-panel';
3 |
4 | export default function(element) {
5 | return [
6 | {
7 | id: 'callableInnerElementRef',
8 | element,
9 | component: CalledElementRef,
10 | isEdited: isSelectEntryEdited
11 | }
12 | ];
13 | }
14 |
15 | function CalledElementRef(props) {
16 | const { element, id } = props;
17 |
18 | const modeling = useService('modeling');
19 | const translate = useService('translate');
20 | const debounce = useService('debounceInput');
21 | const diagramUtil = useService('diagramUtil');
22 |
23 | const getValue = () => {
24 | return element.businessObject.calledElement.replace(/^(inner:)/, '') || '';
25 | };
26 |
27 | const setValue = value => {
28 | return modeling.updateProperties(element, {
29 | calledElement: 'inner:' + value || 'inner:'
30 | });
31 | };
32 |
33 | const getOptions = () => {
34 | return [
35 | ...diagramUtil.definitions().rootElements
36 | .filter((rootElement) => rootElement.$type === 'bpmn:Process' && rootElement.id !== diagramUtil.currentRootElement().id)
37 | .map((rootElement) => {
38 | return { label: rootElement.id, value: rootElement.id };
39 | })
40 | ];
41 | };
42 |
43 | // const validate = (value) => {
44 | // const businessObject = getBusinessObject(element);
45 | // return
46 | // };
47 | // return ;
57 |
58 | return SelectEntry({
59 | element,
60 | id,
61 | label: translate('Called inner element reference'),
62 | getValue,
63 | setValue,
64 | getOptions,
65 | debounce
66 | });
67 | }
--------------------------------------------------------------------------------
/client/properties-provider/props/CalledTypeProps.js:
--------------------------------------------------------------------------------
1 | import { SelectEntry, isSelectEntryEdited } from '@bpmn-io/properties-panel';
2 | import { useService } from 'bpmn-js-properties-panel';
3 | import { getBusinessObject } from 'bpmn-js/lib/util/ModelUtil';
4 |
5 | export default function(element) {
6 | return [
7 | {
8 | id: 'callableElementTypeRef',
9 | element,
10 | component: CalledElementType,
11 | isEdited: isSelectEntryEdited
12 | }
13 | ];
14 | }
15 |
16 | function getCalledElementType(element) {
17 | const bo = getBusinessObject(element);
18 | const boCalledElement = bo.get('calledElement');
19 |
20 | let calledElementType = 'external';
21 | if (typeof boCalledElement !== 'undefined' &&
22 | boCalledElement.startsWith('inner:')) {
23 | calledElementType = 'internal';
24 | }
25 |
26 | return calledElementType;
27 | }
28 |
29 | function CalledElementType(props) {
30 | const { element, id } = props;
31 |
32 | const modeling = useService('modeling');
33 | const translate = useService('translate');
34 | const debounce = useService('debounceInput');
35 |
36 | const getValue = () => {
37 | return getCalledElementType(element);
38 | };
39 |
40 | const setValue = value => {
41 | let calledElement;
42 | if (value === 'internal') {
43 | calledElement = 'inner:';
44 | } else if (value === 'external') {
45 | calledElement = '';
46 | }
47 | return modeling.updateProperties(element, {
48 | calledElement: calledElement
49 | });
50 | };
51 |
52 | const getOptions = () => {
53 | return [
54 | { label: 'INTERNAL', value: 'internal' },
55 | { label: 'EXTERNAL', value: 'external' }
56 | ];
57 | };
58 |
59 | // return ;
69 | return SelectEntry({
70 | element,
71 | id,
72 | label: translate('Called Element Type'),
73 | getValue,
74 | setValue,
75 | getOptions,
76 | debounce
77 | });
78 | }
--------------------------------------------------------------------------------
/client/react/DiagramButtonsOverlay.js:
--------------------------------------------------------------------------------
1 | import React from 'camunda-modeler-plugin-helpers/react';
2 | import { Overlay, Section } from 'camunda-modeler-plugin-helpers/components';
3 |
4 | const OFFSET = { right: 0 };
5 |
6 | import classNames from 'classnames';
7 | import PlusIcon from '../../resources/plus-solid.svg';
8 | import MinusIcon from '../../resources/minus-solid.svg';
9 |
10 | // we can even use hooks to render into the application
11 | export default function DiagramButtonsOverlay({ anchor, initValues, onClose, actions }) {
12 |
13 | return (
14 |
15 |
16 | Global subprocesses configuration
17 |
18 |
19 |
20 |
25 |
26 | {initValues?.diagrams?.map((diagram, index) => (
27 |
28 |
actions.switchDiagram(diagram)}
30 | className={classNames('btn', initValues.activeDiagram === diagram ? 'btn-primary' : 'btn-secondary', 'diagram-name')}>
31 | {diagram}
32 |
33 |
39 |
40 | ))}
41 |
42 |
43 |
44 |
45 | );
46 | }
--------------------------------------------------------------------------------
/client/react/MultiDiagramButton.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment, PureComponent } from 'camunda-modeler-plugin-helpers/react';
2 | import { Fill } from 'camunda-modeler-plugin-helpers/components';
3 |
4 | import SubProcessIcon from '../../resources/subprocess-collapsed.svg';
5 |
6 | import classNames from 'classnames';
7 | import { find } from 'min-dash';
8 | import DiagramButtonsOverlay from './DiagramButtonsOverlay';
9 |
10 | const defaultState = {
11 | modeler: null,
12 | tabModeler: [],
13 | diagrams: [],
14 | activeDiagram: null,
15 | multi: false,
16 | collaboration: false,
17 | configOpen: false
18 | };
19 |
20 | export default class MultiDiagramButton extends PureComponent {
21 |
22 | constructor(props) {
23 | super(props);
24 |
25 | this.state = defaultState;
26 |
27 | this._multiDiagramButtonRef = React.createRef();
28 |
29 | this.handleConfigClosed = this.handleConfigClosed.bind(this);
30 | }
31 |
32 | componentDidMount() {
33 |
34 | /**
35 | * The component props include everything the Application offers plugins,
36 | * which includes:
37 | * - config: save and retrieve information to the local configuration
38 | * - subscribe: hook into application events, like , ...
39 | * - triggerAction: execute editor actions, like , ...
40 | * - log: log information into the Log panel
41 | * - displayNotification: show notifications inside the application
42 | */
43 | const {
44 | // eslint-disable-next-line react/prop-types
45 | subscribe
46 | } = this.props;
47 |
48 | subscribe('bpmn.modeler.created', ({ modeler, tab }) => {
49 | const { tabModeler } = this.state;
50 | this.setState({
51 | modeler: modeler,
52 | tabModeler: [ ...tabModeler, { tabId: tab.id, modeler: modeler } ]
53 | });
54 |
55 | const eventBus = modeler.get('eventBus');
56 | const diagramUtil = modeler.get('diagramUtil');
57 | eventBus.on('import.done', () => {
58 | let bpmnjs = modeler.get('bpmnjs');
59 | let isMultiDiagram = bpmnjs._definitions && diagramUtil.diagrams.length > 1;
60 | let isCollaboration = bpmnjs._definitions && diagramUtil.isCollaboration();
61 | this.setState({
62 | activeDiagram: diagramUtil.currentDiagram(),
63 | diagrams: diagramUtil.diagrams().map(d => d.id),
64 | multi: isMultiDiagram,
65 | collaboration: isCollaboration
66 | });
67 | });
68 |
69 | eventBus.on('commandStack.diagram.create.executed', (command) =>
70 | this.setState({
71 | activeDiagram: command.context.newDiagram,
72 | diagrams: diagramUtil.diagrams().map(d => d.id)
73 | })
74 | );
75 |
76 | eventBus.on('commandStack.diagram.delete.executed', () =>
77 | this.setState({
78 | activeDiagram: diagramUtil.currentDiagram(),
79 | diagrams: diagramUtil.diagrams().map(d => d.id)
80 | })
81 | );
82 |
83 | eventBus.on([ 'commandStack.diagram.create.reverted', 'commandStack.diagram.delete.reverted' ], () =>
84 | this.setState({
85 | activeDiagram: diagramUtil.currentDiagram(),
86 | diagrams: diagramUtil.diagrams().map(d => d.id),
87 | multi: diagramUtil.diagrams.length > 1,
88 | collaboration: diagramUtil.isCollaboration()
89 | })
90 | );
91 |
92 | eventBus.on('diagram.switch', () => {
93 | this.setState({ activeDiagram: diagramUtil.currentDiagram() });
94 | });
95 |
96 | });
97 |
98 | subscribe('app.activeTabChanged', (tab) => {
99 | const {
100 | tabModeler
101 | } = this.state;
102 | let activeTabId = tab.activeTab.id;
103 | const activeModeler = find(tabModeler, t => t.tabId === activeTabId);
104 | if (activeModeler) {
105 | let bpmnjs = activeModeler.modeler.get('bpmnjs');
106 | let diagramUtil = activeModeler.modeler.get('diagramUtil');
107 | let isMultiDiagram = bpmnjs._definitions && diagramUtil.diagrams().length > 1;
108 | let isCollaboration = bpmnjs._definitions && diagramUtil.isCollaboration();
109 | let diagramNames = bpmnjs._definitions && diagramUtil.diagrams().map(d => d.id) || [];
110 | this.setState({
111 | modeler: activeModeler.modeler,
112 | multi: isMultiDiagram,
113 | collaboration: isCollaboration,
114 | activeDiagram: diagramUtil.currentDiagram(),
115 | diagrams: diagramNames
116 | });
117 | } else {
118 | this.setState(defaultState);
119 | }
120 | });
121 |
122 | }
123 |
124 | addDiagram = () => {
125 | const { modeler } = this.state;
126 | const commandStack = modeler.get('commandStack');
127 | commandStack.execute('diagram.create', {});
128 | this.setState({ multi: true });
129 | };
130 |
131 | deleteDiagram = (diagramId) => {
132 | const { modeler } = this.state;
133 | const diagramUtil = modeler.get('diagramUtil');
134 |
135 | if (diagramUtil.diagrams().length > 1) {
136 | let stillMulti = (diagramUtil.diagrams().length - 1) > 1;
137 | const commandStack = modeler.get('commandStack');
138 | commandStack.execute('diagram.delete', { diagram: diagramId });
139 |
140 | this.setState({ multi: stillMulti });
141 | }
142 | };
143 |
144 | switchDiagram = (diagramId) => {
145 | const { modeler } = this.state;
146 | const eventBus = modeler.get('eventBus');
147 |
148 | eventBus.fire('diagram.switch', { diagram: { id: diagramId } });
149 | };
150 |
151 | handleConfigClosed = () => {
152 | this.setState({ configOpen: false });
153 | };
154 |
155 | /**
156 | * render any React component you like to extend the existing
157 | * Camunda Modeler application UI
158 | */
159 | render() {
160 | const { configOpen, activeDiagram, diagrams } = this.state;
161 | let initValues = { activeDiagram: (activeDiagram ? activeDiagram.id : undefined), diagrams };
162 |
163 | // we can use fills to hook React components into certain places of the UI
164 | return
165 |
166 |
173 |
174 | {
175 | configOpen && (
176 |
185 | )
186 | }
187 | ;
188 | }
189 | }
--------------------------------------------------------------------------------
/client/react/modals/rename-diagram/RenameDiagramModal.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/prop-types */
2 | /* eslint-disable no-unused-vars */
3 | import React, { useState } from 'camunda-modeler-plugin-helpers/react';
4 | import { Modal } from 'camunda-modeler-plugin-helpers/components';
5 |
6 | // polyfill upcoming structural components
7 | const Title = Modal.Title || (({ children }) => { children }
);
8 | const Body = Modal.Body || (({ children }) => { children }
);
9 | const Footer = Modal.Footer || (({ children }) => { children }
);
10 |
11 | export default function RenameDiagramModal({ initValues, onRename, onClose }) {
12 |
13 | const [ activeDiagram, setActiveDiagram ] = useState(initValues.activeDiagram);
14 | const onSubmit = () => onRename({ activeDiagram });
15 |
16 | return
17 | Rename current diagram
18 |
19 |
20 |
33 |
34 |
40 | ;
41 | }
42 |
--------------------------------------------------------------------------------
/client/style.css:
--------------------------------------------------------------------------------
1 | div.multi-diagram > div {
2 | margin-bottom: 5px;
3 | }
4 |
5 | div.multi-diagram div.btn + .btn {
6 | margin-left: unset;
7 | }
8 |
9 | div.multi-diagram .diagram-button {
10 | min-width: unset;
11 | }
12 |
13 | div.multi-diagram .add-new-diagram {
14 | margin: 0 0 10px 82%;
15 | }
16 |
17 | div.multi-diagram .diagram-entry {
18 | display: flex;
19 | }
20 |
21 | div.diagram-entry .diagram-name {
22 | width: 75%;
23 | margin-right: 5%;
24 | }
25 |
26 | div.diagram-entry .remove-diagram {
27 | width: 20%;
28 | }
29 |
30 | button.multi-diagram:disabled {
31 | background-color: rgba(19, 1, 1, 0.3) !important;
32 | color: rgba(255, 255, 255, 0.3) !important;
33 | border-color: rgba(195, 195, 195, 0.3) !important;
34 | }
--------------------------------------------------------------------------------
/docs/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sharedchains/camunda-modeler-plugin-multidiagram/204871e65353b421796da1b9209c13b6b8a9d09a/docs/screencast.gif
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: 'Multi-diagram model Plug-in',
5 | script: './dist/client.js',
6 | style: './dist/style.css'
7 | };
8 |
--------------------------------------------------------------------------------
/index.prod.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | module.exports = {
4 | name: 'Multi-diagram model Plug-in',
5 | script: './client/client.js',
6 | style: './client/style.css'
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "camunda-modeler-plugin-multidiagram",
3 | "version": "2.0.2",
4 | "description": "The Camunda Modeler multidiagram plug-in",
5 | "keywords": [
6 | "camunda",
7 | "modeler",
8 | "plugin",
9 | "multi",
10 | "diagram",
11 | "multi-diagram"
12 | ],
13 | "main": "index.js",
14 | "scripts": {
15 | "all": "run-s build build:serve",
16 | "build": "webpack",
17 | "build:serve": "webpack --config webpack.config.serve.js",
18 | "start": "run-s build:serve serve",
19 | "dev": "run-p \"build:serve -- --watch\" serve",
20 | "serve": "sirv public --dev",
21 | "lint": "eslint . --fix",
22 | "test": "run-s lint all"
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com/sharedchains/camunda-modeler-plugin-multidiagram.git"
27 | },
28 | "devDependencies": {
29 | "@babel/core": "^7.17.7",
30 | "@babel/preset-react": "^7.16.7",
31 | "babel-loader": "^8.2.3",
32 | "camunda-modeler-plugin-helpers": "^5.1.0",
33 | "camunda-modeler-webpack-plugin": "^0.1.0",
34 | "copy-webpack-plugin": "^10.2.4",
35 | "css-loader": "^6.7.1",
36 | "eslint": "^7.32.0",
37 | "eslint-plugin-bpmn-io": "^0.13.0",
38 | "eslint-plugin-react": "^7.29.4",
39 | "file-loader": "^6.2.0",
40 | "less": "^4.1.2",
41 | "less-loader": "^10.2.0",
42 | "npm-run-all": "^4.1.5",
43 | "raw-loader": "^4.0.2",
44 | "react-svg-loader": "^3.0.3",
45 | "sirv-cli": "^2.0.2",
46 | "style-loader": "^3.3.1",
47 | "webpack": "^5.70.0",
48 | "webpack-cli": "^4.9.2",
49 | "webpack-sources": "^3.2.3",
50 | "zip-webpack-plugin": "^4.0.1"
51 | },
52 | "dependencies": {
53 | "@bpmn-io/properties-panel": "^0.19.0",
54 | "bpmn-js": "^9.4.0",
55 | "bpmn-js-properties-panel": "^1.5.0",
56 | "camunda-bpmn-moddle": "^6.1.2",
57 | "classnames": "^2.3.1",
58 | "jquery": "^3.6.0",
59 | "min-dash": "^3.8.1",
60 | "min-dom": "^3.1.3"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/resources/minus-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/newDiagram.bpmn:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/resources/pencil-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/plus-solid.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/resources/subprocess-collapsed.svg:
--------------------------------------------------------------------------------
1 |
2 |
94 |
--------------------------------------------------------------------------------
/styles/app.less:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | body,
6 | html {
7 |
8 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
9 |
10 | font-size: 12px;
11 |
12 | height: 100%;
13 | max-height: 100%;
14 | padding: 0;
15 | margin: 0;
16 | }
17 |
18 | a:link {
19 | text-decoration: none;
20 | }
21 |
22 | .content {
23 | position: relative;
24 | width: 100%;
25 | height: 100%;
26 |
27 | > .message {
28 | width: 100%;
29 | height: 100%;
30 | text-align: center;
31 | display: table;
32 |
33 | font-size: 16px;
34 | color: #111;
35 |
36 | .note {
37 | vertical-align: middle;
38 | text-align: center;
39 | display: table-cell;
40 | }
41 |
42 | .error {
43 | .details {
44 | max-width: 500px;
45 | font-size: 12px;
46 | margin: 20px auto;
47 | text-align: left;
48 | }
49 |
50 | pre {
51 | border: solid 1px #CCC;
52 | background: #EEE;
53 | padding: 10px;
54 | }
55 | }
56 | }
57 | &:not(.with-error) .error,
58 | &.with-error .intro,
59 | &.with-diagram .intro {
60 | display: none;
61 | }
62 |
63 | .canvas {
64 | position: absolute;
65 | top: 0;
66 | left: 0;
67 | right: 0;
68 | bottom: 0;
69 | }
70 |
71 | .canvas,
72 | &.with-error .canvas {
73 | visibility: hidden;
74 | }
75 |
76 | &.with-diagram .canvas {
77 | visibility: visible;
78 | }
79 | }
80 |
81 |
82 | .buttons {
83 | position: fixed;
84 | bottom: 20px;
85 | left: 20px;
86 |
87 | padding: 0;
88 | margin: 0;
89 | list-style: none;
90 |
91 | > li {
92 | display: inline-block;
93 | margin-right: 10px;
94 |
95 | > a {
96 | background: #DDD;
97 | border: solid 1px #666;
98 | display: inline-block;
99 | padding: 5px;
100 | }
101 | }
102 |
103 | a {
104 | opacity: 0.3;
105 | }
106 |
107 | a.active {
108 | opacity: 1.0;
109 | }
110 | }
111 |
112 | #js-properties-panel {
113 | position: absolute;
114 | top: 0;
115 | bottom: 0;
116 | right: 0;
117 | width: 260px;
118 | z-index: 10;
119 | border-left: 1px solid #ccc;
120 | overflow: auto;
121 | &:empty {
122 | display: none;
123 | }
124 | > .djs-properties-panel {
125 | padding-bottom: 70px;
126 | min-height:100%;
127 | }
128 | }
129 |
130 |
131 | .djs-select-wrapper, .djs-rename-wrapper {
132 | position: absolute;
133 | display: flex;
134 | left: 55px;
135 | top: 0px;
136 | padding: 8px;
137 | margin-top: -1px;
138 | height: 46px;
139 | background-color: #FAFAFA;
140 | border: 1px solid #CCCCCC;
141 | border-radius: 2px;
142 | }
143 |
144 | .two-column.open .djs-select-wrapper,
145 | .two-column.open .djs-rename-wrapper {
146 | left: 101px;
147 | }
148 |
149 | .djs-rename-wrapper {
150 | display: none;
151 | }
152 |
153 | .djs-select-wrapper > *:last-child,
154 | .djs-rename-wrapper > *:last-child {
155 | margin-right: 0;
156 | }
157 |
158 | .djs-select-wrapper > button {
159 | font-size: 20px;
160 | text-align: center;
161 | line-height: 50%;
162 | width: 30px;
163 | height: 30px;
164 | padding: 0px;
165 |
166 | background-color: transparent;
167 | border-style: none;
168 | }
169 |
170 | .djs-select-wrapper > button:hover {
171 | color: #FF7400;
172 | }
173 |
174 | .djs-rename,
175 | .djs-select {
176 | margin-right: 10px;
177 | }
178 |
179 | .djs-rename,
180 | .djs-select {
181 | width: 300px;
182 | height: 30px;
183 | padding-left: 10px;
184 | padding-right: 10px;
185 | border-radius: 0 !important;
186 | }
187 |
188 | .djs-select {
189 | -moz-appearance: none;
190 | -webkit-appearance: none;
191 | appearance: none;
192 |
193 | background-image:
194 | linear-gradient(45deg, transparent 50%, #A6A6A6 50%),
195 | linear-gradient(135deg, #A6A6A6 50%, transparent 50%);
196 | background-position:
197 | calc(100% - 15px) calc(1em + 0px),
198 | calc(100% - 10px) calc(1em + 0px);
199 | background-size:
200 | 5px 5px,
201 | 5px 5px;
202 | background-repeat: no-repeat;
203 | }
204 |
205 | .djs-select:focus {
206 | outline: none;
207 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const CopyPlugin = require('copy-webpack-plugin');
3 | const ZipPlugin = require('zip-webpack-plugin');
4 | const CamundaModelerWebpackPlugin = require('camunda-modeler-webpack-plugin');
5 |
6 | module.exports = {
7 | mode: 'development',
8 | entry: './client/index.js',
9 | output: {
10 | path: path.resolve(__dirname, 'dist'),
11 | filename: 'client.js'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.svg$/,
17 | use: 'react-svg-loader'
18 | }
19 | ]
20 | },
21 | devtool: 'cheap-module-source-map',
22 | plugins: [
23 | new CamundaModelerWebpackPlugin(),
24 | new CopyPlugin({
25 | patterns: [
26 | {
27 | from: path.resolve(__dirname, './client/style.css'),
28 | to: path.resolve(__dirname, './dist/')
29 | },
30 | {
31 | from: path.resolve(__dirname, './index.prod.js'),
32 | to: path.resolve(__dirname, './dist/index.js')
33 | }
34 | ]
35 | }),
36 | new ZipPlugin({
37 | filename: process.env.npm_package_name + '-' + process.env.npm_package_version + '.zip',
38 | pathPrefix: process.env.npm_package_name + '/',
39 | pathMapper: function(assetPath) {
40 | if (assetPath.startsWith('client') || assetPath.startsWith('style')) {
41 | return path.join(path.dirname(assetPath), 'client', path.basename(assetPath));
42 | }
43 | return assetPath;
44 | }
45 | })
46 | ]
47 | };
--------------------------------------------------------------------------------
/webpack.config.serve.js:
--------------------------------------------------------------------------------
1 | const CopyPlugin = require('copy-webpack-plugin');
2 |
3 | const path = require('path');
4 | const CamundaModelerWebpackPlugin = require('camunda-modeler-webpack-plugin');
5 |
6 | const basePath = '.';
7 |
8 | const absoluteBasePath = path.resolve(path.join(__dirname, basePath));
9 |
10 | module.exports = {
11 | mode: 'development',
12 | entry: './app/index.js',
13 | output: {
14 | path: path.resolve(__dirname, 'public'),
15 | filename: 'index.js'
16 | },
17 | devtool: 'source-map',
18 | module: {
19 | rules: [
20 | {
21 | test: /\.less$/i,
22 | use: [
23 |
24 | // compiles Less to CSS
25 | 'style-loader',
26 | 'css-loader',
27 | 'less-loader'
28 | ]
29 | },
30 | {
31 | test: /\.bpmn$/,
32 | use: {
33 | loader: 'raw-loader'
34 | }
35 | }
36 | ]
37 | },
38 | resolve: {
39 | mainFields: [
40 | 'browser',
41 | 'module',
42 | 'main'
43 | ],
44 | modules: [
45 | 'node_modules',
46 | absoluteBasePath
47 | ]
48 | },
49 | plugins: [
50 | new CamundaModelerWebpackPlugin({ type: 'react' }),
51 | new CopyPlugin({
52 | patterns: [
53 | { from: 'app/index.html', to: '.' },
54 | { from: 'node_modules/bpmn-js/dist/assets', to: 'vendor/bpmn-js/assets' },
55 | { from: 'node_modules/bpmn-js-properties-panel/dist/assets', to: 'vendor/bpmn-js-properties-panel/assets' }
56 | ]
57 | })
58 | ]
59 | };
--------------------------------------------------------------------------------