@bpmn-io/diagram-js-canvas-lock 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -0
- package/assets/canvas-lock.css +31 -0
- package/lib/CanvasLock.js +62 -0
- package/lib/CanvasLockBehavior.js +108 -0
- package/lib/index.js +11 -0
- package/package.json +67 -0
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# diagram-js-canvas-lock
|
|
2
|
+
|
|
3
|
+
[](https://github.com/bpmn-io/diagram-js-canvas-lock/actions/workflows/CI.yml)
|
|
4
|
+
|
|
5
|
+
This module extends [diagram-js](https://github.com/bpmn-io/diagram-js)-based editors with a canvas lock feature.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
* lock and unlock canvas programmatically
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
Install via npm:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
npm install @bpmn-io/diagram-js-canvas-lock
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
Use as an extension for [bpmn-js](https://github.com/bpmn-io/bpmn-js):
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import CanvasLockModule from '@bpmn-io/diagram-js-canvas-lock';
|
|
25
|
+
|
|
26
|
+
const modeler = new BpmnModeler({
|
|
27
|
+
additionalModules: [
|
|
28
|
+
CanvasLockModule
|
|
29
|
+
]
|
|
30
|
+
});
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Lock and unlock the canvas through the `canvasLock` service:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
const canvasLock = modeler.get('canvasLock');
|
|
37
|
+
|
|
38
|
+
canvasLock.lock();
|
|
39
|
+
|
|
40
|
+
canvasLock.isLocked(); // true
|
|
41
|
+
|
|
42
|
+
canvasLock.unlock();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Integrating with diagram-js-canvas-lock
|
|
46
|
+
|
|
47
|
+
By design, this module has no knowledge of the other diagram-js plugins running
|
|
48
|
+
alongside it. Instead it exposes a small, stable contract that any plugin can
|
|
49
|
+
integrate with. If your plugin adds interactive UI (pads, overlays, menus, ...),
|
|
50
|
+
respect the lock through one or more of the following.
|
|
51
|
+
|
|
52
|
+
### 1. Query the lock state (pull)
|
|
53
|
+
|
|
54
|
+
Guard your own entry points with `canvasLock.isLocked()`:
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
MyPad.prototype.canOpen = function(target) {
|
|
58
|
+
if (this._canvasLock.isLocked()) {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ...
|
|
63
|
+
};
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`canvasLock` is an optional dependency – resolve it via the injector so your
|
|
67
|
+
plugin keeps working in editors without canvas lock:
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
function MyPad(injector) {
|
|
71
|
+
this._canvasLock = injector.get('canvasLock', false);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
MyPad.prototype.isLocked = function() {
|
|
75
|
+
return this._canvasLock && this._canvasLock.isLocked();
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
MyPad.$inject = [ 'injector' ];
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### 2. React to lock changes (push)
|
|
82
|
+
|
|
83
|
+
Subscribe to the `canvasLock.changed` event to tear down or restore UI when the
|
|
84
|
+
lock is toggled:
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
eventBus.on('canvasLock.changed', function(event) {
|
|
88
|
+
if (event.locked) {
|
|
89
|
+
myPad.close();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 3. Make an interaction vetoable (`*.allowed` convention)
|
|
95
|
+
|
|
96
|
+
Following the diagram-js convention, fire an `<x>.allowed` event before opening
|
|
97
|
+
and bail if any listener returns `false`. Canvas lock (and other features) can
|
|
98
|
+
then veto the interaction without knowing about your plugin:
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
MyPad.prototype.open = function(target) {
|
|
102
|
+
if (this._eventBus.fire('myPad.open.allowed', { target: target }) === false) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ...
|
|
107
|
+
};
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 4. Style locked state (CSS hook)
|
|
111
|
+
|
|
112
|
+
While locked, the canvas container carries the `djs-canvas-locked` class. Use it
|
|
113
|
+
to hide or dim plugin UI purely visually:
|
|
114
|
+
|
|
115
|
+
```css
|
|
116
|
+
.djs-canvas-locked .my-pad {
|
|
117
|
+
display: none;
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Public contract summary
|
|
122
|
+
|
|
123
|
+
| Surface | Type | Description |
|
|
124
|
+
| --- | --- | --- |
|
|
125
|
+
| `canvasLock.lock()` / `unlock()` | API | Toggle the lock. |
|
|
126
|
+
| `canvasLock.isLocked()` | API | Query the current lock state. |
|
|
127
|
+
| `canvasLock.changed` | Event | Fired with `{ locked }` whenever the lock toggles. |
|
|
128
|
+
| `djs-canvas-locked` | CSS class | Added to the canvas container while locked. |
|
|
129
|
+
|
|
130
|
+
## Development
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
npm install
|
|
134
|
+
|
|
135
|
+
npm start
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
## License
|
|
140
|
+
|
|
141
|
+
MIT
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
.djs-canvas-locked {
|
|
2
|
+
user-select: none;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.djs-canvas-locked .djs-connection.selected .djs-outline {
|
|
6
|
+
display: initial;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.djs-canvas-locked .djs-palette .entry {
|
|
10
|
+
opacity: 0.4;
|
|
11
|
+
pointer-events: none;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.djs-canvas-locked .djs-palette .djs-palette-toggle {
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.djs-canvas-locked .djs-element .djs-hit-stroke,
|
|
19
|
+
.djs-canvas-locked .djs-element .djs-hit-click-stroke,
|
|
20
|
+
.djs-canvas-locked .djs-element .djs-hit-all {
|
|
21
|
+
cursor: unset;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.djs-canvas-locked .djs-resizer {
|
|
25
|
+
display: none;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.djs-canvas-locked .djs-segment-dragger,
|
|
29
|
+
.djs-canvas-locked .djs-bendpoint {
|
|
30
|
+
display: none !important;
|
|
31
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
|
|
3
|
+
* @typedef {import('diagram-js/lib/core/Canvas').default} Canvas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* A service that allows to temporarily lock user interactions
|
|
8
|
+
* while still allowing programmatic changes via modeling APIs.
|
|
9
|
+
*
|
|
10
|
+
* Pan/zoom (navigation) remains enabled even while locked.
|
|
11
|
+
*
|
|
12
|
+
* @param {EventBus} eventBus
|
|
13
|
+
* @param {Canvas} canvas
|
|
14
|
+
*/
|
|
15
|
+
export default function CanvasLock(eventBus, canvas) {
|
|
16
|
+
this._eventBus = eventBus;
|
|
17
|
+
this._canvas = canvas;
|
|
18
|
+
this._locked = false;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
CanvasLock.$inject = [ 'eventBus', 'canvas' ];
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Lock user interactions.
|
|
25
|
+
*/
|
|
26
|
+
CanvasLock.prototype.lock = function() {
|
|
27
|
+
if (this._locked) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
this._locked = true;
|
|
32
|
+
|
|
33
|
+
var container = this._canvas.getContainer();
|
|
34
|
+
container.classList.add('djs-canvas-locked');
|
|
35
|
+
|
|
36
|
+
this._eventBus.fire('canvasLock.changed', { locked: true });
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Unlock user interactions.
|
|
41
|
+
*/
|
|
42
|
+
CanvasLock.prototype.unlock = function() {
|
|
43
|
+
if (!this._locked) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this._locked = false;
|
|
48
|
+
|
|
49
|
+
var container = this._canvas.getContainer();
|
|
50
|
+
container.classList.remove('djs-canvas-locked');
|
|
51
|
+
|
|
52
|
+
this._eventBus.fire('canvasLock.changed', { locked: false });
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check whether interactions are currently locked.
|
|
57
|
+
*
|
|
58
|
+
* @return {boolean}
|
|
59
|
+
*/
|
|
60
|
+
CanvasLock.prototype.isLocked = function() {
|
|
61
|
+
return this._locked;
|
|
62
|
+
};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {import('diagram-js/lib/core/EventBus').default} EventBus
|
|
3
|
+
* @typedef {import('./CanvasLock').default} CanvasLock
|
|
4
|
+
* @typedef {import('didi').Injector} Injector
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
var VERY_HIGH_PRIORITY = 10000;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Blocked interaction events.
|
|
11
|
+
*
|
|
12
|
+
* These are events that initiate user-driven interactions
|
|
13
|
+
* but should be suppressed when the canvas lock is active.
|
|
14
|
+
*/
|
|
15
|
+
var BLOCKED_EVENTS = [
|
|
16
|
+
|
|
17
|
+
// block any user-initiated drag, including move, create, connect, etc.
|
|
18
|
+
'drag.init',
|
|
19
|
+
|
|
20
|
+
// block any user-initiated open of context pad or popup menu
|
|
21
|
+
'contextPad.open.allowed',
|
|
22
|
+
'popupMenu.open.allowed',
|
|
23
|
+
|
|
24
|
+
// block direct editing activation
|
|
25
|
+
'directEditing.activate.allowed'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Editor actions that are allowed while locked
|
|
30
|
+
* because they only affect navigation, not diagram content.
|
|
31
|
+
*/
|
|
32
|
+
var ALLOWED_EDITOR_ACTIONS = [
|
|
33
|
+
'stepZoom',
|
|
34
|
+
'zoom',
|
|
35
|
+
'moveCanvas'
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A behavior that blocks user-initiated interaction events
|
|
41
|
+
* when the canvas lock is active.
|
|
42
|
+
*
|
|
43
|
+
* Navigation events (canvas.move, canvas.zoom) are explicitly
|
|
44
|
+
* not blocked to allow pan/zoom while locked. Keyboard-triggered
|
|
45
|
+
* navigation editor actions (stepZoom, zoom, moveCanvas) are also
|
|
46
|
+
* allowed through.
|
|
47
|
+
*
|
|
48
|
+
* @param {EventBus} eventBus
|
|
49
|
+
* @param {CanvasLock} canvasLock
|
|
50
|
+
* @param {Injector} injector
|
|
51
|
+
*/
|
|
52
|
+
export default function CanvasLockBehavior(eventBus, canvasLock, injector) {
|
|
53
|
+
|
|
54
|
+
BLOCKED_EVENTS.forEach(function(event) {
|
|
55
|
+
eventBus.on(event, VERY_HIGH_PRIORITY, function(e) {
|
|
56
|
+
if (canvasLock.isLocked()) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// block non-navigation editor actions when locked
|
|
63
|
+
eventBus.on('editorActions.allowed', VERY_HIGH_PRIORITY, function(event) {
|
|
64
|
+
if (canvasLock.isLocked() && !ALLOWED_EDITOR_ACTIONS.includes(event.action)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// close open overlays when locking; restore context pad on unlock
|
|
70
|
+
eventBus.on('canvasLock.changed', function(event) {
|
|
71
|
+
var contextPad = injector.get('contextPad', false);
|
|
72
|
+
var directEditing = injector.get('directEditing', false);
|
|
73
|
+
var dragging = injector.get('dragging', false);
|
|
74
|
+
var popupMenu = injector.get('popupMenu', false);
|
|
75
|
+
|
|
76
|
+
if (event.locked) {
|
|
77
|
+
if (contextPad && contextPad.isOpen()) {
|
|
78
|
+
contextPad.close();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (directEditing && directEditing.isActive()) {
|
|
82
|
+
directEditing.cancel();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (dragging && dragging.context()) {
|
|
86
|
+
dragging.cancel();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (popupMenu && popupMenu.isOpen()) {
|
|
90
|
+
popupMenu.close();
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
|
|
94
|
+
// restore context pad for current selection
|
|
95
|
+
var selection = injector.get('selection', false);
|
|
96
|
+
|
|
97
|
+
if (contextPad && selection) {
|
|
98
|
+
var selectedElements = selection.get();
|
|
99
|
+
|
|
100
|
+
if (selectedElements.length) {
|
|
101
|
+
contextPad.open(selectedElements.length === 1 ? selectedElements[0] : selectedElements);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
CanvasLockBehavior.$inject = [ 'eventBus', 'canvasLock', 'injector' ];
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import CanvasLock from './CanvasLock.js';
|
|
2
|
+
import CanvasLockBehavior from './CanvasLockBehavior.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @type { import('didi').ModuleDeclaration }
|
|
6
|
+
*/
|
|
7
|
+
export default {
|
|
8
|
+
__init__: [ 'canvasLock', 'canvasLockBehavior' ],
|
|
9
|
+
canvasLock: [ 'type', CanvasLock ],
|
|
10
|
+
canvasLockBehavior: [ 'type', CanvasLockBehavior ]
|
|
11
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@bpmn-io/diagram-js-canvas-lock",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A diagram-js plugin for locking the canvas",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"module": "lib/index.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/index.js",
|
|
10
|
+
"./lib/*": "./lib/*",
|
|
11
|
+
"./assets/*": "./assets/*",
|
|
12
|
+
"./package.json": "./package.json"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"all": "run-s lint test",
|
|
16
|
+
"test": "karma start karma.config.cjs",
|
|
17
|
+
"lint": "eslint .",
|
|
18
|
+
"dev": "npm test -- --auto-watch --no-single-run",
|
|
19
|
+
"start": "cross-env SINGLE_START=modeler npm run dev"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"diagram-js",
|
|
23
|
+
"canvas",
|
|
24
|
+
"lock"
|
|
25
|
+
],
|
|
26
|
+
"author": "Philipp Fromme <philipp.fromme@camunda.com>",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"publishConfig": {
|
|
29
|
+
"access": "public"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"assets",
|
|
33
|
+
"lib"
|
|
34
|
+
],
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"diagram-js": ">= 15.16.0",
|
|
37
|
+
"diagram-js-direct-editing": ">= 3.4.0"
|
|
38
|
+
},
|
|
39
|
+
"peerDependenciesMeta": {
|
|
40
|
+
"diagram-js-direct-editing": {
|
|
41
|
+
"optional": true
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"babel-loader": "^10.1.1",
|
|
46
|
+
"babel-plugin-istanbul": "^8.0.0",
|
|
47
|
+
"bpmn-js": "^18.19.0",
|
|
48
|
+
"chai": "^6.2.2",
|
|
49
|
+
"cross-env": "^10.1.0",
|
|
50
|
+
"eslint": "^9.39.4",
|
|
51
|
+
"eslint-plugin-bpmn-io": "^2.2.0",
|
|
52
|
+
"karma": "^6.4.4",
|
|
53
|
+
"karma-chrome-launcher-2": "^3.3.0",
|
|
54
|
+
"karma-coverage": "^2.2.1",
|
|
55
|
+
"karma-debug-launcher": "^0.0.5",
|
|
56
|
+
"karma-env-preprocessor": "^0.1.1",
|
|
57
|
+
"karma-mocha": "^2.0.1",
|
|
58
|
+
"karma-tldr-reporter": "^1.0.0",
|
|
59
|
+
"karma-webpack": "^5.0.1",
|
|
60
|
+
"mocha": "^11.7.5",
|
|
61
|
+
"mocha-test-container-support": "^0.2.0",
|
|
62
|
+
"npm-run-all2": "^9.0.2",
|
|
63
|
+
"sinon": "^22.0.0",
|
|
64
|
+
"sinon-chai": "^4.0.1",
|
|
65
|
+
"webpack": "^5.106.2"
|
|
66
|
+
}
|
|
67
|
+
}
|