├── .babelrc
├── .eslintignore
├── .eslintrc
├── .github
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ ├── FEATURE_REQUEST.md
│ ├── TASK.md
│ └── config.yml
└── workflows
│ └── ci.yml
├── .gitignore
├── .nvmrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── assets
├── icons
│ └── image.svg
└── postit-js.css
├── docs
└── screencast.gif
├── example
├── .eslintignore
├── .eslintrc
├── .gitignore
├── README.md
├── app
│ ├── app.js
│ ├── css
│ │ └── app.css
│ └── index.html
├── package.json
├── resources
│ ├── emptyBoard.xml
│ └── newBoard.xml
└── webpack.config.js
├── extension-experiments
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmrc
├── README.md
├── app
│ ├── app.js
│ ├── css
│ │ └── app.css
│ └── index.html
├── package-lock.json
├── package.json
├── resources
│ ├── emptyBoard.xml
│ └── newBoard.xml
└── webpack.config.js
├── index.js
├── karma.conf.js
├── lib
├── .eslintrc
├── BaseModeler.js
├── BaseViewer.js
├── Modeler.js
├── NavigatedViewer.js
├── Viewer.js
├── core
│ └── index.js
├── draw
│ ├── PathMap.js
│ ├── PostitRenderer.js
│ ├── PostitRendererUtil.js
│ ├── TextRenderer.js
│ └── index.js
├── features
│ ├── canvas-create
│ │ ├── CanvasCreate.js
│ │ └── index.js
│ ├── context-pad
│ │ ├── ContextPadProvider.js
│ │ └── index.js
│ ├── copy-paste
│ │ ├── ModdleCopy.js
│ │ ├── PostitCopyPaste.js
│ │ └── index.js
│ ├── di-ordering
│ │ ├── PostitDiOrdering.js
│ │ └── index.js
│ ├── editor-actions
│ │ ├── PostitEditorActions.js
│ │ └── index.js
│ ├── image-selection
│ │ ├── ImageSelection.js
│ │ └── index.js
│ ├── keyboard
│ │ ├── PostitKeyboardBindings.js
│ │ └── index.js
│ ├── label-editing
│ │ ├── LabelEditingPreview.js
│ │ ├── LabelEditingProvider.js
│ │ ├── LabelUtil.js
│ │ ├── cmd
│ │ │ └── UpdateLabelHandler.js
│ │ └── index.js
│ ├── modeling
│ │ ├── ElementFactory.js
│ │ ├── Modeling.js
│ │ ├── PostitFactory.js
│ │ ├── PostitUpdater.js
│ │ ├── behavior
│ │ │ ├── AdaptiveLabelPositioningBehavior.js
│ │ │ ├── AppendBehavior.js
│ │ │ ├── CreateBoardElementBehavior.js
│ │ │ ├── EmptyTextBoxBehavior.js
│ │ │ ├── FixHoverBehavior.js
│ │ │ ├── ImportDockingFix.js
│ │ │ ├── LabelBehavior.js
│ │ │ ├── ReplaceElementBehaviour.js
│ │ │ ├── UnclaimIdBehavior.js
│ │ │ ├── index.js
│ │ │ └── util
│ │ │ │ ├── GeometricUtil.js
│ │ │ │ ├── LabelLayoutUtil.js
│ │ │ │ ├── LineAttachmentUtil.js
│ │ │ │ └── LineIntersect.js
│ │ ├── cmd
│ │ │ ├── IdClaimHandler.js
│ │ │ ├── SetColorHandler.js
│ │ │ ├── UpdateCanvasRootHandler.js
│ │ │ ├── UpdatePropertiesHandler.js
│ │ │ └── UpdateSemanticParentHandler.js
│ │ ├── index.js
│ │ └── util
│ │ │ └── ModelingUtil.js
│ ├── ordering
│ │ ├── PostitOrderingProvider.js
│ │ └── index.js
│ ├── palette
│ │ ├── PaletteProvider.js
│ │ └── index.js
│ ├── popup-menu
│ │ ├── ReplaceMenuProvider.js
│ │ ├── index.js
│ │ └── util
│ │ │ └── TypeUtil.js
│ ├── replace-preview
│ │ ├── PostitReplacePreview.js
│ │ └── index.js
│ ├── replace
│ │ ├── PostitReplace.js
│ │ ├── ReplaceOptions.js
│ │ └── index.js
│ ├── rules
│ │ ├── PostitRules.js
│ │ └── index.js
│ └── snapping
│ │ ├── PostitCreateMoveSnapping.js
│ │ ├── PostitSnappingUtil.js
│ │ └── index.js
├── import
│ ├── Importer.js
│ ├── PostitImporter.js
│ ├── PostitTreeWalker.js
│ ├── Util.js
│ └── index.js
├── moddle
│ ├── Moddle.js
│ ├── index.js
│ └── resources
│ │ ├── dc.json
│ │ ├── postit.json
│ │ └── postitDi.json
└── util
│ ├── ColorUtil.js
│ ├── FileUtil.js
│ ├── LabelUtil.js
│ ├── ModelUtil.js
│ ├── PoweredByUtil.js
│ └── ScreenUtil.js
├── package-lock.json
├── package.json
├── renovate.json
└── test
├── .eslintrc
├── TestHelper.js
├── fixtures
├── complex.xml
├── empty.xml
└── simple.xml
├── helper
└── index.js
├── spec
└── ModelerSpec.js
└── testBundle.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [ "env" ]
3 | }
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | coverage
2 | dist
3 | example
4 | node_modules
5 | *.json
6 | assets
7 | extension-experiments
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "plugin:bpmn-io/browser",
4 | "plugin:bpmn-io/node"
5 | ]
6 | }
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Report a problem and help us fix it.
4 | labels: "bug"
5 | ---
6 |
7 |
8 | __Describe the Bug__
9 |
10 |
11 |
12 |
13 | __Steps to Reproduce__
14 |
15 |
16 |
17 | 1. do this
18 | 2. do that
19 | 3. now this happens
20 |
21 |
22 | __Expected Behavior__
23 |
24 |
25 |
26 |
27 | __Environment__
28 |
29 | - OS: [e.g. MacOS 10.2, Windows 10]
30 | - Library version: [e.g. 2.0.0]
31 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea or general improvement.
4 | labels: "enhancement"
5 | ---
6 |
7 |
8 | __Is your feature request related to a problem? Please describe.__
9 |
10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
11 |
12 |
13 | __Describe the solution you'd like__
14 |
15 | A clear and concise description of what you want to happen.
16 |
17 |
18 | __Describe alternatives you've considered__
19 |
20 | A clear and concise description of any alternative solutions or features you've considered.
21 |
22 |
23 | __Additional context__
24 |
25 | Add any other context or screenshots about the feature request here.
26 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/TASK.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Task
3 | about: Describe a generic activity we should carry out.
4 | ---
5 |
6 |
7 | __What should we do?__
8 |
9 |
10 |
11 |
12 | __Why should we do it?__
13 |
14 |
15 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | blank_issues_enabled: false
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 |
3 | on: [push, pull_request]
4 |
5 | jobs:
6 | build:
7 |
8 | strategy:
9 | matrix:
10 | os: [ macos-latest, ubuntu-latest, windows-latest ]
11 | node-version: [ 16 ]
12 |
13 | runs-on: ${{ matrix.os }}
14 |
15 | steps:
16 | - name: Checkout
17 | uses: actions/checkout@v4
18 | - name: Use Node.js ${{ matrix.node-version }}
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.node-version }}
22 | - name: Cache Node.js modules
23 | uses: actions/cache@v3
24 | with:
25 | # npm cache files are stored in `~/.npm` on Linux/macOS
26 | path: ~/.npm
27 | key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
28 | restore-keys: |
29 | ${{ runner.OS }}-node-
30 | ${{ runner.OS }}-
31 | - name: Install depedencies
32 | run: npm install
33 | - name: Build and execute tests
34 | if: matrix.os == 'ubuntu-latest'
35 | env:
36 | TEST_BROWSERS: ChromeHeadless
37 | run: xvfb-run npm run all
38 | - name: Build
39 | if: matrix.os != 'ubuntu-latest'
40 | env:
41 | TEST_BROWSERS: ChromeHeadless,Firefox
42 | run: npm run all
43 | - name: Setup and build example
44 | working-directory: ./example
45 | run: |
46 | npm install
47 | npm run all
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | dist/
3 | coverage/
4 | .idea
5 | *.iml
6 | .DS_Store
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 18
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## Unreleased
8 |
9 | ___Note:__ Yet to be released changes appear here._
10 |
11 | * ...
12 |
13 | ## 1.1.0
14 |
15 | * `DEPS`: use fonts via packages ([`946beda4`](https://github.com/pinussilvestrus/postit-js/commit/946beda421bd3dcea658408ea26de24439fb4981))
16 | * `DEPS`: update to `diagram-js@11`([`fc3eb84d`](https://github.com/pinussilvestrus/postit-js/commit/fc3eb84da7b65b2e97b0e73a730f47b0b3c941d4))
17 |
18 | ## 1.0.1
19 |
20 | * `FIX`: remove empty text boxes on cancel editing ([#20](https://github.com/pinussilvestrus/postit-js/issues/20))
21 | * `FIX`: add missing moddle descriptors to published package ([#95](https://github.com/pinussilvestrus/postit-js/issues/95))
22 | * `CHORE`: various dependency updates
23 |
24 | ## 1.0.0
25 |
26 | * `FEAT`: switch from callbacks to Promises ([`bf699810`](https://github.com/pinussilvestrus/postit-js/commit/bf6998100f52ce50f7fdd498c31e002a83e50968))
27 | * `CHORE`: add basic tests infrastructure ([`1ea64c90`](https://github.com/pinussilvestrus/postit-js/commit/1ea64c9012e2a0849c367cd0a39d981b2d336bf9))
28 | * `CHORE`: migrate CI to GitHub actions ([`b512c6c4`](https://github.com/pinussilvestrus/postit-js/commit/b512c6c4f815d082da70304834ce3eebbfc1d856))
29 | * `CHORE`: remove unused global connect tool ([`5016d943`](https://github.com/pinussilvestrus/postit-js/commit/5016d94325624a55dcf5dae026e195a1c94a7239))
30 | * `CHORE`: rename default branch to `main`
31 | * `CHORE`: bump to `diagram-js@7.2`
32 | * `CHORE`: bump to `moddle-xml@9`
33 |
34 | ### Breaking Changes
35 |
36 | * APIs don't accept callbacks anymore, instead, they return a Promise now
37 | * `viewer#importXML`
38 | * `viewer#importDefinitions`
39 | * `viewer#open`
40 | * `viewer#saveXML`
41 | * `viewer#saveSVG`
42 | * `modeler#createDiagram`
43 | * `importer#importPostitDiagram`
44 |
45 | ## 0.6.2
46 |
47 | * `CHORE`: fix travis
48 |
49 | ## 0.6.1
50 |
51 | * `CHORE`: remove examples from package
52 |
53 | ## 0.6.0
54 |
55 | * `FEAT`: improve image selection ([#17](https://github.com/pinussilvestrus/postit-js/issues/17))
56 | * `CHORE`: examples cleanups
57 |
58 | ## 0.5.0
59 |
60 | * `FEAT`: delete empty text boxes ([#6](https://github.com/pinussilvestrus/postit-js/issues/6))
61 | * `FIX`: transparent background when editing post-it elements ([#4](https://github.com/pinussilvestrus/postit-js/issues/4))
62 |
63 | ## 0.4.0
64 |
65 | * `FEAT`: ability to re-configure image source ([#18](https://github.com/pinussilvestrus/postit-js/issues/18))
66 | * `CHORE`: update bpmn.io logo
67 |
68 | ## 0.3.0
69 |
70 | * `FEAT`: add image element support ([#11](https://github.com/pinussilvestrus/postit-js/issues/11))
71 |
72 | ## 0.2.0
73 |
74 | * `CHORE`: first npm release, add integration instructions
75 |
76 | ## 0.1.1
77 |
78 | * `FIX`: ability to delete elements created directly on canvas ([#12](https://github.com/pinussilvestrus/postit-js/issues/12))
79 |
80 | ## 0.1.0
81 |
82 | Initial release
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright 2020 Niklas Kiefer
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4 |
5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # postit-js
2 |
3 | [](https://app.netlify.com/sites/postit-js-demo/deploys) 
4 |
5 |
6 | Create post-it brainstorming boards - built with [diagram-js](https://github.com/bpmn-io/diagram-js).
7 |
8 | 
9 |
10 | Checkout the [**Demo**](https://postit-js-demo.netlify.app/) or the [**Experiments Page**](https://postit-js-experiments.netlify.app/) to get some inspiration.
11 |
12 | ## Features
13 |
14 | * Create resizable Post-its on the Canvas (squared and circled) via
15 | * Palette
16 | * Double Click (latest element type will be respected)
17 | * Change the color of Post-its
18 | * Create simple Text Boxes on the Canvas
19 | * Create grouping frame elements on the Canvas
20 | * Add external image resources on the Canvas
21 |
22 | ## Installation
23 |
24 | Install the package to include it into your web application
25 |
26 | ```sh
27 | $ npm install postit-js-core --save
28 | ```
29 |
30 | ## Usage
31 |
32 | To get started, create a [postit-js](https://github.com/pinussilvestrus/postit-js) instance
33 | and render a post-it board into your web application
34 |
35 | ```javascript
36 | import 'postit-js-core/assets/postit-js.css';
37 |
38 | import PostItModeler from 'postit-js-core/lib/Modeler';
39 |
40 | let xml; // my post-it xml
41 |
42 | const modeler = new PostItModeler({
43 | container: '#canvas',
44 | keyboard: {
45 | bindTo: window,
46 | }
47 | });
48 |
49 | modeler.importXML(xml).then(function() {
50 | console.log('board rendered');
51 | }).catch(function(error) {
52 | console.error('could not import postit board', err);
53 | });
54 | ```
55 |
56 | For using `postit-js` inside your web application you'll need a source code bundler, e.g. [webpack](https://webpack.js.org/). Checkout the [example](./example) for getting inspiration.
57 |
58 | ### Development Setup
59 |
60 | Spin up the application for development, all strings attached:
61 |
62 | ```sh
63 | $ npm install
64 | $ cd example
65 | $ npm install
66 | $ npm run dev
67 | ```
68 |
69 | ## Extensions
70 |
71 | Since [`diagram-js`](https://github.com/bpmn-io/diagram-js) and also this project is extendable by design, there exist a couple of great community maintained extensions
72 |
73 | * [`drag-drop-images`](https://github.com/xanpj/postit-js-extensions#drag-drop-images) - Drag and drop image files on the board
74 | * [`selection-organizer`](https://github.com/xanpj/postit-js-extensions#selection-organizer) - Organize and distribute groups of elements
75 | * [`properties-panel`](https://github.com/xanpj/postit-js-extensions#properties-panel) - Properties panel for post-it elements
76 |
77 | ## License
78 |
79 | MIT
80 |
81 | Contains parts of ([bpmn-io](https://github.com/bpmn-io)) released under the [bpmn.io license](http://bpmn.io/license).
82 |
--------------------------------------------------------------------------------
/assets/icons/image.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/docs/screencast.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinussilvestrus/postit-js/6869e3d260f9c5925d83ea57d8cefabaaaa88e81/docs/screencast.gif
--------------------------------------------------------------------------------
/example/.eslintignore:
--------------------------------------------------------------------------------
1 | public/
2 | *.html
--------------------------------------------------------------------------------
/example/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "plugin:bpmn-io/browser",
4 | "plugin:bpmn-io/node"
5 | ],
6 | "globals": {
7 | "Sentry": true,
8 | "dataLayer": true
9 | }
10 | }
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | public/
3 | .env
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # postit-js Modeler Example
2 |
3 | This example uses [postit-js](https://github.com/pinussilvestrus/postit-js) to implement a modeler for postit diagrams.
4 |
5 | ## About
6 |
7 | This example is a node-style web application that builds a user interface around the postit-js modeler.
8 |
9 |
10 | ## Building
11 |
12 | You need a [NodeJS](http://nodejs.org) development stack with [npm](https://npmjs.org) installed to build the project.
13 |
14 | To install all project dependencies execute
15 |
16 | ```
17 | npm install
18 | ```
19 |
20 | Build the application (including [postit-js](https://github.com/pinussilvestrus/postit-js)) via
21 |
22 | ```
23 | npm run all
24 | ```
25 |
26 | You may also spawn a development setup by executing
27 |
28 | ```
29 | npm run dev
30 | ```
31 |
32 | Both tasks generate the distribution ready client-side modeler application into the `public` folder.
33 |
34 | Serve the application locally or via a web server (nginx, apache, embedded).
--------------------------------------------------------------------------------
/example/app/app.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | import 'postit-js-core/assets/postit-js.css';
4 | import PostItModeler from 'postit-js-core/lib/Modeler';
5 |
6 | import newBoardXML from '../resources/newBoard.xml';
7 | import emptyBoardXML from '../resources/emptyBoard.xml';
8 |
9 | // modeler instance
10 | var modeler = new PostItModeler({
11 | container: '#canvas',
12 | keyboard: {
13 | bindTo: window,
14 | }
15 | });
16 |
17 | /* screen interaction */
18 | function enterFullscreen(element) {
19 | if (element.requestFullscreen) {
20 | element.requestFullscreen();
21 | } else if (element.mozRequestFullScreen) {
22 | element.mozRequestFullScreen();
23 | } else if (element.msRequestFullscreen) {
24 | element.msRequestFullscreen();
25 | } else if (element.webkitRequestFullscreen) {
26 | element.webkitRequestFullscreen();
27 | }
28 | }
29 |
30 | function exitFullscreen() {
31 | if (document.exitFullscreen) {
32 | document.exitFullscreen();
33 | } else if (document.mozCancelFullScreen) {
34 | document.mozCancelFullScreen();
35 | } else if (document.webkitExitFullscreen) {
36 | document.webkitExitFullscreen();
37 | } else if (document.msExitFullscreen) {
38 | document.msExitFullscreen();
39 | }
40 | }
41 |
42 | const state = {
43 | fullScreen: false,
44 | keyboardHelp: false,
45 | };
46 | document.getElementById('js-toggle-fullscreen').addEventListener('click', function() {
47 | state.fullScreen = !state.fullScreen;
48 | if (state.fullScreen) {
49 | enterFullscreen(document.documentElement);
50 | } else {
51 | exitFullscreen();
52 | }
53 | });
54 | document.getElementById('js-toggle-keyboard-help').addEventListener('click', function() {
55 | state.keyboardHelp = !state.keyboardHelp;
56 | let displayProp = 'none';
57 | if (state.keyboardHelp) {
58 | displayProp = 'block';
59 | }
60 | document.getElementById('io-dialog-main').style.display = displayProp;
61 | });
62 | document.getElementById('io-dialog-main').addEventListener('click', function() {
63 | state.keyboardHelp = !state.keyboardHelp;
64 | let displayProp = 'none';
65 | if (!state.keyboardHelp) {
66 | document.getElementById('io-dialog-main').style.display = displayProp;
67 | }
68 | });
69 |
70 | /* file functions */
71 | function openFile(file, callback) {
72 |
73 | // check file api availability
74 | if (!window.FileReader) {
75 | return window.alert(
76 | 'Looks like you use an older browser that does not support drag and drop. ' +
77 | 'Try using a modern browser such as Chrome, Firefox or Internet Explorer > 10.');
78 | }
79 |
80 | // no file chosen
81 | if (!file) {
82 | return;
83 | }
84 |
85 | var reader = new FileReader();
86 |
87 | reader.onload = function(e) {
88 |
89 | var xml = e.target.result;
90 |
91 | callback(xml);
92 | };
93 |
94 | reader.readAsText(file);
95 | }
96 |
97 | var fileInput = $('').appendTo(document.body).css({
98 | width: 1,
99 | height: 1,
100 | display: 'none',
101 | overflow: 'hidden'
102 | }).on('change', function(e) {
103 | openFile(e.target.files[0], openBoard);
104 | });
105 |
106 |
107 | function openBoard(xml) {
108 |
109 | // import board
110 | modeler.importXML(xml).catch(function(err) {
111 | if (err) {
112 | return console.error('could not import postit board', err);
113 | }
114 | });
115 | }
116 |
117 | function saveSVG() {
118 | return modeler.saveSVG();
119 | }
120 |
121 | function saveBoard() {
122 | return modeler.saveXML({ format: true });
123 | }
124 |
125 | // bootstrap board functions
126 | $(function() {
127 |
128 | var downloadLink = $('#js-download-board');
129 | var downloadSvgLink = $('#js-download-svg');
130 |
131 | var openNew = $('#js-open-new');
132 | var openExistingBoard = $('#js-open-board');
133 |
134 | $('.buttons a').click(function(e) {
135 | if (!$(this).is('.active')) {
136 | e.preventDefault();
137 | e.stopPropagation();
138 | }
139 | });
140 |
141 | function setEncoded(link, name, data) {
142 | var encodedData = encodeURIComponent(data);
143 |
144 | if (data) {
145 | link.addClass('active').attr({
146 | 'href': 'data:application/xml;charset=UTF-8,' + encodedData,
147 | 'download': name
148 | });
149 | } else {
150 | link.removeClass('active');
151 | }
152 | }
153 |
154 | var exportArtifacts = debounce(function() {
155 |
156 | saveSVG().then(function(result) {
157 | setEncoded(downloadSvgLink, 'board.svg', result.svg);
158 | });
159 |
160 | saveBoard().then(function(result) {
161 | setEncoded(downloadLink, 'board.xml', result.xml);
162 | });
163 | }, 500);
164 |
165 | modeler.on('commandStack.changed', exportArtifacts);
166 |
167 | openNew.on('click', function() {
168 | openBoard(emptyBoardXML);
169 | });
170 |
171 | openExistingBoard.on('click', function() {
172 | var input = $(fileInput);
173 |
174 | // clear input so that previously selected file can be reopened
175 | input.val('');
176 | input.trigger('click');
177 | });
178 |
179 | });
180 |
181 | // bootstrapping
182 | initSentry();
183 | initGA();
184 |
185 | openBoard(newBoardXML);
186 |
187 |
188 | // helpers //////////////////////
189 |
190 | function debounce(fn, timeout) {
191 | var timer;
192 |
193 | return function() {
194 | if (timer) {
195 | clearTimeout(timer);
196 | }
197 |
198 | timer = setTimeout(fn, timeout);
199 | };
200 | }
201 |
202 | function initSentry() {
203 | if (process.env.SENTRY_DSN && process.env.SOURCE_VERSION && typeof Sentry !== 'undefined') {
204 | Sentry.init({
205 | dsn: process.env.SENTRY_DSN,
206 | release: process.env.SOURCE_VERSION
207 | });
208 |
209 | // TEST
210 | // Sentry.captureException(new Error('Something broke'));
211 | }
212 | }
213 |
214 | function initGA() {
215 | window.dataLayer = window.dataLayer || [];
216 | function gtag() {dataLayer.push(arguments);}
217 | gtag('js', new Date());
218 | gtag('config', 'UA-72700874-2');
219 | }
220 |
--------------------------------------------------------------------------------
/example/app/css/app.css:
--------------------------------------------------------------------------------
1 | html, body, #canvas {
2 | font-family: 'IBM Plex Sans', sans-serif;
3 | height: 100%;
4 | padding: 0;
5 | margin: 0;
6 | }
7 |
8 | .bottom-buttons {
9 | bottom: 20px;
10 | left: 20px;
11 | }
12 |
13 | .bottom-buttons a:hover {
14 | color: #555;
15 | }
16 |
17 | .bottom-buttons a.active {
18 | opacity: 1.0;
19 | box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.1);
20 | }
21 |
22 | /* screen interaction helpers & modal */
23 |
24 | #io-dialog-main{
25 | display:none; /*default*/
26 | }
27 |
28 | #io-editing-tools-buttons {
29 | display: block;
30 | position:fixed;
31 | top:0px;
32 | right: 80px;
33 | list-style: none;
34 | padding: 5px;
35 | margin: 0;
36 | }
37 |
38 | #io-editing-tools-buttons button:hover {
39 | color: #333333;
40 | }
41 |
42 | .icon-keyboard {
43 | box-sizing: border-box;
44 | cursor: pointer;
45 | font-size: 18px;
46 | line-height: 1.2em;
47 | }
48 |
49 | .icon-keyboard::before {
50 | content: '\f11c';
51 | display: inline-block;
52 | line-height: 1.2em;
53 | }
54 |
55 | .icon-resize-full {
56 | box-sizing: border-box;
57 | cursor: pointer;
58 | font-size: 18px;
59 | line-height: 1.2em;
60 | }
61 |
62 | .icon-resize-full::before {
63 | content: '\f31e';
64 | display: inline-block;
65 | }
66 |
67 | .keybindings-dialog .binding {
68 | padding: 5px 10px;
69 | font-family: monospace;
70 | }
71 |
--------------------------------------------------------------------------------
/example/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | postit-js modeler demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Keyboard Shortcuts
12 |
13 |
14 |
15 | Undo |
16 | ⌘ + Z |
17 |
18 |
19 | Redo |
20 | ⌘ + ⇧ + Z |
21 |
22 |
23 | Select All |
24 | ⌘ + A |
25 |
26 |
27 | Zooming |
28 | ctrl + Scrolling |
29 |
30 |
38 |
39 | Direct Editing |
40 | E |
41 |
42 |
43 | Hand Tool |
44 | H |
45 |
46 |
47 | Lasso Tool |
48 | L |
49 |
50 |
51 | Space Tool |
52 | S |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
75 |
76 |
77 |
78 |
79 |
80 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postit-js-example-modeler",
3 | "version": "0.0.0",
4 | "description": "A simple modeler built with postit-js",
5 | "scripts": {
6 | "all": "run-s lint build",
7 | "build": "webpack",
8 | "dev": "run-p build:watch serve",
9 | "build:watch": "webpack -w",
10 | "serve": "serve public",
11 | "lint": "eslint .",
12 | "start": "run-p build:watch serve"
13 | },
14 | "private": "true",
15 | "keywords": [
16 | "postit-js-example"
17 | ],
18 | "author": {
19 | "name": "Niklas Kiefer",
20 | "url": "https://github.com/pinussilvestrus"
21 | },
22 | "license": "MIT",
23 | "devDependencies": {
24 | "@sentry/webpack-plugin": "^2.0.0",
25 | "copy-webpack-plugin": "^11.0.0",
26 | "css-loader": "^6.8.1",
27 | "eslint": "^8.42.0",
28 | "eslint-plugin-bpmn-io": "^1.0.0",
29 | "file-loader": "^6.2.0",
30 | "npm-run-all": "^4.1.5",
31 | "raw-loader": "^4.0.2",
32 | "serve": "^14.2.0",
33 | "style-loader": "^3.3.3",
34 | "webpack": "^5.87.0",
35 | "webpack-cli": "^5.1.4"
36 | },
37 | "dependencies": {
38 | "jquery": "^3.7.0",
39 | "less": "^4.1.3",
40 | "less-loader": "^11.1.3",
41 | "postit-js-core": "file:../"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/example/resources/emptyBoard.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/resources/newBoard.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/example/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | const CopyWebpackPlugin = require('copy-webpack-plugin');
4 | const SentryWebpackPlugin = require('@sentry/webpack-plugin');
5 |
6 | const SENTRY_DSN = process.env.SENTRY_DSN;
7 | const SOURCE_VERSION = process.env.SOURCE_VERSION || process.env.npm_package_gitHead || 'dev';
8 |
9 | module.exports = {
10 | entry: {
11 | bundle: [ './app/app.js' ],
12 | },
13 | output: {
14 | path: __dirname + '/public',
15 | filename: 'app.js',
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.xml$/,
21 | use: 'raw-loader',
22 | },
23 | {
24 | test: /\.css$/i,
25 | use: [ 'style-loader', 'css-loader' ],
26 | },
27 | {
28 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
29 | type: 'asset/resource'
30 | },
31 | {
32 | test: /\.less$/i,
33 | use: [
34 | 'style-loader',
35 | {
36 | loader: 'css-loader',
37 | options: {
38 | sourceMap: true,
39 | },
40 | },
41 | {
42 | loader: 'less-loader',
43 | options: {
44 | sourceMap: true,
45 | },
46 | },
47 | ],
48 | }
49 | ],
50 | },
51 | plugins: [
52 | new CopyWebpackPlugin({ patterns: [ { from: '**/*.{html,css,woff,ttf,eot,svg,woff2}', context: 'app/' } ] }),
53 | new webpack.DefinePlugin({
54 | 'process.env.SENTRY_DSN': JSON.stringify(SENTRY_DSN || null),
55 | 'process.env.SOURCE_VERSION': JSON.stringify(SOURCE_VERSION || null)
56 | }),
57 | ...sentryIntegration()
58 | ],
59 | mode: 'development',
60 | devtool: 'source-map',
61 | };
62 |
63 | function sentryIntegration() {
64 |
65 | if (SENTRY_DSN && SOURCE_VERSION) {
66 | return [
67 | new SentryWebpackPlugin({
68 | release: SOURCE_VERSION,
69 | include: '.',
70 | ignore: [ 'node_modules', 'webpack.config.js', '*sentry.js' ],
71 | })
72 | ];
73 | }
74 |
75 | return [];
76 | }
77 |
--------------------------------------------------------------------------------
/extension-experiments/.eslintignore:
--------------------------------------------------------------------------------
1 | public/
2 | *.html
--------------------------------------------------------------------------------
/extension-experiments/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "plugin:bpmn-io/es6",
3 | "env": {
4 | "browser": true
5 | },
6 | "globals": {
7 | "Promise": true,
8 | "Sentry": true,
9 | "dataLayer": true
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/extension-experiments/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | public/
3 | .env
--------------------------------------------------------------------------------
/extension-experiments/.npmrc:
--------------------------------------------------------------------------------
1 | package-lock=false
--------------------------------------------------------------------------------
/extension-experiments/README.md:
--------------------------------------------------------------------------------
1 | # postit-js Modeler Example
2 |
3 | This example uses [postit-js](https://github.com/pinussilvestrus/postit-js) to implement a modeler for postit diagrams.
4 |
5 | ## About
6 |
7 | This example is a node-style web application that builds a user interface around the postit-js modeler.
8 |
9 |
10 | ## Building
11 |
12 | You need a [NodeJS](http://nodejs.org) development stack with [npm](https://npmjs.org) installed to build the project.
13 |
14 | To install all project dependencies execute
15 |
16 | ```
17 | npm install
18 | ```
19 |
20 | Build the application (including [postit-js](https://github.com/pinussilvestrus/postit-js)) via
21 |
22 | ```
23 | npm run all
24 | ```
25 |
26 | You may also spawn a development setup by executing
27 |
28 | ```
29 | npm run dev
30 | ```
31 |
32 | Both tasks generate the distribution ready client-side modeler application into the `public` folder.
33 |
34 | Serve the application locally or via a web server (nginx, apache, embedded).
--------------------------------------------------------------------------------
/extension-experiments/app/app.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | import 'postit-js-core/assets/postit-js.css';
4 | import PostItModeler from 'postit-js-core/lib/Modeler';
5 |
6 | import PostItExtensions from 'postit-js-extensions';
7 |
8 | import newBoardXML from '../resources/newBoard.xml';
9 | import emptyBoardXML from '../resources/emptyBoard.xml';
10 |
11 | // modeler instance
12 | var modeler = new PostItModeler({
13 | container: '#canvas',
14 | keyboard: {
15 | bindTo: window,
16 | },
17 | additionalModules: [
18 | PostItExtensions,
19 | ],
20 | });
21 |
22 | /* screen interaction */
23 | function enterFullscreen(element) {
24 | if (element.requestFullscreen) {
25 | element.requestFullscreen();
26 | } else if (element.mozRequestFullScreen) {
27 | element.mozRequestFullScreen();
28 | } else if (element.msRequestFullscreen) {
29 | element.msRequestFullscreen();
30 | } else if (element.webkitRequestFullscreen) {
31 | element.webkitRequestFullscreen();
32 | }
33 | }
34 |
35 | function exitFullscreen() {
36 | if (document.exitFullscreen) {
37 | document.exitFullscreen();
38 | } else if (document.mozCancelFullScreen) {
39 | document.mozCancelFullScreen();
40 | } else if (document.webkitExitFullscreen) {
41 | document.webkitExitFullscreen();
42 | } else if (document.msExitFullscreen) {
43 | document.msExitFullscreen();
44 | }
45 | }
46 |
47 | const state = {
48 | fullScreen: false,
49 | keyboardHelp: false,
50 | };
51 | document.getElementById('js-toggle-fullscreen').addEventListener('click', function() {
52 | state.fullScreen = !state.fullScreen;
53 | if (state.fullScreen) {
54 | enterFullscreen(document.documentElement);
55 | } else {
56 | exitFullscreen();
57 | }
58 | });
59 | document.getElementById('js-toggle-keyboard-help').addEventListener('click', function() {
60 | state.keyboardHelp = !state.keyboardHelp;
61 | let displayProp = 'none';
62 | if (state.keyboardHelp) {
63 | displayProp = 'block';
64 | }
65 | document.getElementById('io-dialog-main').style.display = displayProp;
66 | });
67 | document.getElementById('io-dialog-main').addEventListener('click', function() {
68 | state.keyboardHelp = !state.keyboardHelp;
69 | let displayProp = 'none';
70 | if (!state.keyboardHelp) {
71 | document.getElementById('io-dialog-main').style.display = displayProp;
72 | }
73 | });
74 |
75 | /* file functions */
76 | function openFile(file, callback) {
77 |
78 | // check file api availability
79 | if (!window.FileReader) {
80 | return window.alert(
81 | 'Looks like you use an older browser that does not support drag and drop. ' +
82 | 'Try using a modern browser such as Chrome, Firefox or Internet Explorer > 10.');
83 | }
84 |
85 | // no file chosen
86 | if (!file) {
87 | return;
88 | }
89 |
90 | var reader = new FileReader();
91 |
92 | reader.onload = function(e) {
93 |
94 | var xml = e.target.result;
95 |
96 | callback(xml);
97 | };
98 |
99 | reader.readAsText(file);
100 | }
101 |
102 | var fileInput = $('').appendTo(document.body).css({
103 | width: 1,
104 | height: 1,
105 | display: 'none',
106 | overflow: 'hidden'
107 | }).on('change', function(e) {
108 | openFile(e.target.files[0], openBoard);
109 | });
110 |
111 |
112 | function openBoard(xml) {
113 |
114 | // import board
115 | return modeler.importXML(xml).catch(function(err) {
116 | return console.error('could not import postit board', err);
117 | });
118 | }
119 |
120 | function saveSVG() {
121 | return modeler.saveSVG();
122 | }
123 |
124 | function saveBoard() {
125 | return modeler.saveXML({ format: true });
126 | }
127 |
128 | // bootstrap board functions
129 | $(function() {
130 |
131 | var downloadLink = $('#js-download-board');
132 | var downloadSvgLink = $('#js-download-svg');
133 |
134 | var openNew = $('#js-open-new');
135 | var openExistingBoard = $('#js-open-board');
136 |
137 | $('.buttons a').click(function(e) {
138 | if (!$(this).is('.active')) {
139 | e.preventDefault();
140 | e.stopPropagation();
141 | }
142 | });
143 |
144 | function setEncoded(link, name, data) {
145 | var encodedData = encodeURIComponent(data);
146 |
147 | if (data) {
148 | link.addClass('active').attr({
149 | 'href': 'data:application/xml;charset=UTF-8,' + encodedData,
150 | 'download': name
151 | });
152 | } else {
153 | link.removeClass('active');
154 | }
155 | }
156 |
157 | var exportArtifacts = debounce(function() {
158 |
159 | saveSVG(function(result) {
160 | setEncoded(downloadSvgLink, 'board.svg', result.svg);
161 | });
162 |
163 | saveBoard(function(result) {
164 | setEncoded(downloadLink, 'board.xml', result.xml);
165 | });
166 | }, 500);
167 |
168 | modeler.on('commandStack.changed', exportArtifacts);
169 |
170 | openNew.on('click', function() {
171 | openBoard(emptyBoardXML);
172 | });
173 |
174 | openExistingBoard.on('click', function() {
175 | var input = $(fileInput);
176 |
177 | // clear input so that previously selected file can be reopened
178 | input.val('');
179 | input.trigger('click');
180 | });
181 |
182 | });
183 |
184 | // bootstrapping
185 | initSentry();
186 | initGA();
187 |
188 | openBoard(newBoardXML);
189 |
190 |
191 | // helpers //////////////////////
192 |
193 | function debounce(fn, timeout) {
194 | var timer;
195 |
196 | return function() {
197 | if (timer) {
198 | clearTimeout(timer);
199 | }
200 |
201 | timer = setTimeout(fn, timeout);
202 | };
203 | }
204 |
205 | function initSentry() {
206 | if (process.env.SENTRY_DSN && process.env.SOURCE_VERSION && typeof Sentry !== 'undefined') {
207 | Sentry.init({
208 | dsn: process.env.SENTRY_DSN,
209 | release: process.env.SOURCE_VERSION
210 | });
211 |
212 | // TEST
213 | // Sentry.captureException(new Error('Something broke'));
214 | }
215 | }
216 |
217 | function initGA() {
218 | window.dataLayer = window.dataLayer || [];
219 | function gtag() {dataLayer.push(arguments);}
220 | gtag('js', new Date());
221 | gtag('config', 'UA-72700874-2');
222 | }
223 |
--------------------------------------------------------------------------------
/extension-experiments/app/css/app.css:
--------------------------------------------------------------------------------
1 | html, body, #canvas {
2 | font-family: 'IBM Plex Sans', sans-serif;
3 | height: 100%;
4 | padding: 0;
5 | margin: 0;
6 | }
7 |
8 | .bottom-buttons {
9 | bottom: 20px;
10 | left: 20px;
11 | }
12 |
13 | .bottom-buttons a:hover {
14 | color: #555;
15 | }
16 |
17 | .bottom-buttons a.active {
18 | opacity: 1.0;
19 | box-shadow: 0px 2px 2px 0 rgba(0, 0, 0, 0.1);
20 | }
21 |
22 | /* screen interaction helpers & modal */
23 |
24 | #io-dialog-main{
25 | display:none; /*default*/
26 | }
27 |
28 | #io-editing-tools-buttons {
29 | display: block;
30 | position:fixed;
31 | top:0px;
32 | right: 80px;
33 | list-style: none;
34 | padding: 5px;
35 | margin: 0;
36 | }
37 |
38 | #io-editing-tools-buttons button:hover {
39 | color: #333333;
40 | }
41 |
42 | .icon-keyboard {
43 | box-sizing: border-box;
44 | cursor: pointer;
45 | font-size: 18px;
46 | line-height: 1.2em;
47 | }
48 |
49 | .icon-keyboard::before {
50 | content: '\f11c';
51 | display: inline-block;
52 | line-height: 1.2em;
53 | }
54 |
55 | .icon-resize-full {
56 | box-sizing: border-box;
57 | cursor: pointer;
58 | font-size: 18px;
59 | line-height: 1.2em;
60 | }
61 |
62 | .icon-resize-full::before {
63 | content: '\f31e';
64 | display: inline-block;
65 | }
66 |
67 | .keybindings-dialog .binding {
68 | padding: 5px 10px;
69 | font-family: monospace;
70 | }
71 |
--------------------------------------------------------------------------------
/extension-experiments/app/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | postit-js modeler demo
5 |
6 |
7 |
8 |
9 |
10 |
11 |
Keyboard Shortcuts
12 |
13 |
14 |
15 | Undo |
16 | ⌘ + Z |
17 |
18 |
19 | Redo |
20 | ⌘ + ⇧ + Z |
21 |
22 |
23 | Select All |
24 | ⌘ + A |
25 |
26 |
27 | Zooming |
28 | ctrl + Scrolling |
29 |
30 |
38 |
39 | Direct Editing |
40 | E |
41 |
42 |
43 | Hand Tool |
44 | H |
45 |
46 |
47 | Lasso Tool |
48 | L |
49 |
50 |
51 | Space Tool |
52 | S |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
75 |
76 |
77 |
78 |
79 |
80 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/extension-experiments/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "postit-js-experiments",
3 | "version": "0.0.0",
4 | "description": "Experiments built with postit-js",
5 | "private": "true",
6 | "scripts": {
7 | "all": "run-s lint build",
8 | "build": "webpack",
9 | "dev": "run-p build:watch serve",
10 | "build:watch": "webpack -w",
11 | "serve": "serve public",
12 | "lint": "eslint .",
13 | "start": "run-s dev"
14 | },
15 | "keywords": [
16 | "postit-js-example"
17 | ],
18 | "author": {
19 | "name": "Niklas Kiefer",
20 | "url": "https://github.com/pinussilvestrus"
21 | },
22 | "license": "MIT",
23 | "devDependencies": {
24 | "@sentry/webpack-plugin": "^1.18.8",
25 | "copy-webpack-plugin": "^10.2.4",
26 | "css-loader": "^6.7.1",
27 | "eslint": "^7.21.0",
28 | "eslint-plugin-bpmn-io": "^0.13.0",
29 | "file-loader": "^6.2.0",
30 | "npm-run-all": "^4.1.5",
31 | "raw-loader": "^4.0.2",
32 | "serve": "^13.0.2",
33 | "style-loader": "^3.3.1",
34 | "webpack": "^5.70.0",
35 | "webpack-cli": "^4.9.2"
36 | },
37 | "dependencies": {
38 | "jquery": "^3.6.0",
39 | "less": "^4.1.2",
40 | "less-loader": "^10.2.0",
41 | "postit-js-core": "file:../",
42 | "postit-js-extensions": "git+https://github.com/xanpj/postit-js-extensions.git"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/extension-experiments/resources/emptyBoard.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/extension-experiments/resources/newBoard.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/extension-experiments/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | const CopyWebpackPlugin = require('copy-webpack-plugin');
4 | const SentryWebpackPlugin = require('@sentry/webpack-plugin');
5 |
6 | const SENTRY_DSN = process.env.SENTRY_DSN;
7 | const SOURCE_VERSION = process.env.SOURCE_VERSION || process.env.npm_package_gitHead || 'dev';
8 |
9 | module.exports = {
10 | entry: {
11 | bundle: [ './app/app.js' ],
12 | },
13 | output: {
14 | path: __dirname + '/public',
15 | filename: 'app.js',
16 | },
17 | module: {
18 | rules: [
19 | {
20 | test: /\.xml$/,
21 | use: 'raw-loader',
22 | },
23 | {
24 | test: /\.css$/i,
25 | use: [ 'style-loader', 'css-loader' ],
26 | },
27 | {
28 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
29 | use: [ 'file-loader' ],
30 | },
31 | {
32 | test: /\.less$/i,
33 | use: [
34 | 'style-loader',
35 | {
36 | loader: 'css-loader',
37 | options: {
38 | sourceMap: true,
39 | },
40 | },
41 | {
42 | loader: 'less-loader',
43 | options: {
44 | sourceMap: true,
45 | },
46 | },
47 | ],
48 | }
49 | ],
50 | },
51 | plugins: [
52 | new CopyWebpackPlugin({ patterns: [ { from: '**/*.{html,css,woff,ttf,eot,svg,woff2}', context: 'app/' } ] }),
53 | new webpack.DefinePlugin({
54 | 'process.env.SENTRY_DSN': JSON.stringify(SENTRY_DSN || null),
55 | 'process.env.SOURCE_VERSION': JSON.stringify(SOURCE_VERSION || null)
56 | }),
57 | ...sentryIntegration()
58 | ],
59 | mode: 'development',
60 | devtool: 'source-map',
61 | };
62 |
63 | function sentryIntegration() {
64 |
65 | if (SENTRY_DSN && SOURCE_VERSION) {
66 | return [
67 | new SentryWebpackPlugin({
68 | release: SOURCE_VERSION,
69 | include: '.',
70 | ignore: [ 'node_modules', 'webpack.config.js', '*sentry.js' ],
71 | })
72 | ];
73 | }
74 |
75 | return [];
76 | }
77 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export {
2 | default
3 | } from './lib/Viewer';
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var singleStart = process.env.SINGLE_START;
4 |
5 | // configures browsers to run test against
6 | // any of [ 'ChromeHeadless', 'Chrome', 'Firefox ]
7 | var browsers = (process.env.TEST_BROWSERS || 'ChromeHeadless').split(',');
8 |
9 | // use puppeteer provided Chrome for testing
10 | process.env.CHROME_BIN = require('puppeteer').executablePath();
11 |
12 | var basePath = '.';
13 |
14 | var absoluteBasePath = path.resolve(path.join(__dirname, basePath));
15 |
16 | var suite = 'test/testBundle.js';
17 |
18 |
19 | module.exports = function(karma) {
20 |
21 | var config = {
22 |
23 | basePath,
24 |
25 | frameworks: [
26 | 'mocha',
27 | 'sinon-chai'
28 | ],
29 |
30 | files: [
31 | suite
32 | ],
33 |
34 | preprocessors: {
35 | [ suite ]: [ 'webpack', 'env' ]
36 | },
37 |
38 | reporters: [ 'progress' ],
39 |
40 | browsers,
41 |
42 | browserNoActivityTimeout: 30000,
43 |
44 | singleRun: true,
45 | autoWatch: false,
46 |
47 | webpack: {
48 | mode: 'development',
49 | module: {
50 | rules: [
51 | {
52 | test: /\.css|\.xml$/,
53 | use: 'raw-loader'
54 | },
55 | {
56 | test: /\.(woff(2)?|ttf|eot|svg)(\?v=\d+\.\d+\.\d+)?$/,
57 | use: [ 'file-loader' ],
58 | }
59 | ]
60 | },
61 | resolve: {
62 | mainFields: [
63 | 'dev:module',
64 | 'browser',
65 | 'module',
66 | 'main'
67 | ],
68 | modules: [
69 | 'node_modules',
70 | absoluteBasePath
71 | ]
72 | },
73 | devtool: 'eval-source-map'
74 | }
75 | };
76 |
77 | if (singleStart) {
78 | config.browsers = [].concat(config.browsers, 'Debug');
79 | config.envPreprocessor = [].concat(config.envPreprocessor || [], 'SINGLE_START');
80 | }
81 |
82 | karma.set(config);
83 | };
84 |
--------------------------------------------------------------------------------
/lib/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": [
3 | "import"
4 | ],
5 | "rules": {
6 | "import/no-unresolved": "error"
7 | }
8 | }
--------------------------------------------------------------------------------
/lib/BaseModeler.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import Ids from 'ids';
4 |
5 | import BaseViewer from './BaseViewer';
6 |
7 |
8 | /**
9 | * A base modeler for postit-js boards.
10 | *
11 | * Have a look at {@link Modeler} for a bundle that includes actual features.
12 | *
13 | * @param {Object} [options] configuration options to pass to the viewer
14 | * @param {DOMElement} [options.container] the container to render the viewer in, defaults to body.
15 | * @param {String|Number} [options.width] the width of the viewer
16 | * @param {String|Number} [options.height] the height of the viewer
17 | * @param {Object} [options.moddleExtensions] extension packages to provide
18 | * @param {Array} [options.modules] a list of modules to override the default modules
19 | * @param {Array} [options.additionalModules] a list of modules to use with the default modules
20 | */
21 | export default function BaseModeler(options) {
22 | BaseViewer.call(this, options);
23 |
24 | // hook ID collection into the modeler
25 | this.on('import.parse.complete', function(event) {
26 | if (!event.error) {
27 | this._collectIds(event.definitions, event.context);
28 | }
29 | }, this);
30 |
31 | this.on('diagram.destroy', function() {
32 | this.get('moddle').ids.clear();
33 | }, this);
34 | }
35 |
36 | inherits(BaseModeler, BaseViewer);
37 |
38 |
39 | /**
40 | * Create a moddle instance, attaching ids to it.
41 | *
42 | * @param {Object} options
43 | */
44 | BaseModeler.prototype._createModdle = function(options) {
45 | var moddle = BaseViewer.prototype._createModdle.call(this, options);
46 |
47 | // attach ids to moddle to be able to track
48 | // and validated ids in the XML document
49 | // tree
50 | moddle.ids = new Ids([ 32, 36, 1 ]);
51 |
52 | return moddle;
53 | };
54 |
55 | /**
56 | * Collect ids processed during parsing of the
57 | * definitions object.
58 | *
59 | * @param {ModdleElement} definitions
60 | * @param {Context} context
61 | */
62 | BaseModeler.prototype._collectIds = function(definitions, context) {
63 |
64 | var moddle = definitions.$model,
65 | ids = moddle.ids,
66 | id;
67 |
68 | // remove references from previous import
69 | ids.clear();
70 |
71 | for (id in context.elementsById) {
72 | ids.claim(id, context.elementsById[id]);
73 | }
74 | };
--------------------------------------------------------------------------------
/lib/Modeler.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import BaseModeler from './BaseModeler';
4 |
5 | import Viewer from './Viewer';
6 | import NavigatedViewer from './NavigatedViewer';
7 |
8 | import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
9 | import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
10 | import TouchModule from 'diagram-js/lib/navigation/touch';
11 | import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
12 |
13 | import AlignElementsModule from 'diagram-js/lib/features/align-elements';
14 | import AutoScrollModule from 'diagram-js/lib/features/auto-scroll';
15 | import BendpointsModule from 'diagram-js/lib/features/bendpoints';
16 | import CanvasCreate from './features/canvas-create';
17 | import ConnectModule from 'diagram-js/lib/features/connect';
18 | import ConnectionPreviewModule from 'diagram-js/lib/features/connection-preview';
19 | import ContextPadModule from './features/context-pad';
20 | import CopyPasteModule from './features/copy-paste';
21 | import CreateModule from 'diagram-js/lib/features/create';
22 | import EditorActionsModule from './features/editor-actions';
23 | import ImageSelectionModule from './features/image-selection';
24 | import KeyboardModule from './features/keyboard';
25 | import KeyboardMoveSelectionModule from 'diagram-js/lib/features/keyboard-move-selection';
26 | import LabelEditingModule from './features/label-editing';
27 | import ModelingModule from './features/modeling';
28 | import MoveModule from 'diagram-js/lib/features/move';
29 | import PaletteModule from './features/palette';
30 | import ReplacePreviewModule from './features/replace-preview';
31 | import ResizeModule from 'diagram-js/lib/features/resize';
32 | import SnappingModule from './features/snapping';
33 |
34 | var initialDiagram =
35 | `
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | `;
44 |
45 | export default function Modeler(options) {
46 | BaseModeler.call(this, options);
47 | }
48 |
49 | inherits(Modeler, BaseModeler);
50 |
51 |
52 | Modeler.Viewer = Viewer;
53 | Modeler.NavigatedViewer = NavigatedViewer;
54 |
55 | /**
56 | * The createDiagram result.
57 | *
58 | * @typedef {Object} CreateDiagramResult
59 | *
60 | * @property {Array} warnings
61 | */
62 |
63 | /**
64 | * The createDiagram error.
65 | *
66 | * @typedef {Error} CreateDiagramError
67 | *
68 | * @property {Array} warnings
69 | */
70 |
71 | /**
72 | * Create a new diagram to start modeling.
73 | *
74 | * @returns {Promise}
75 | *
76 | */
77 | Modeler.prototype.createDiagram = function() {
78 | return this.importXML(initialDiagram);
79 | };
80 |
81 |
82 | Modeler.prototype._interactionModules = [
83 |
84 | // non-modeling components
85 | KeyboardMoveModule,
86 | MoveCanvasModule,
87 | TouchModule,
88 | ZoomScrollModule
89 | ];
90 |
91 | Modeler.prototype._modelingModules = [
92 |
93 | // modeling components
94 | AlignElementsModule,
95 | AutoScrollModule,
96 | BendpointsModule,
97 | CanvasCreate,
98 | ConnectModule,
99 | ConnectionPreviewModule,
100 | ContextPadModule,
101 | CopyPasteModule,
102 | CreateModule,
103 | EditorActionsModule,
104 | ImageSelectionModule,
105 | KeyboardModule,
106 | KeyboardMoveSelectionModule,
107 | LabelEditingModule,
108 | ModelingModule,
109 | MoveModule,
110 | PaletteModule,
111 | ReplacePreviewModule,
112 | ResizeModule,
113 | SnappingModule,
114 | ];
115 |
116 |
117 | // modules the modeler is composed of
118 | //
119 | // - viewer modules
120 | // - interaction modules
121 | // - modeling modules
122 |
123 | Modeler.prototype._modules = [].concat(
124 | Viewer.prototype._modules,
125 | Modeler.prototype._interactionModules,
126 | Modeler.prototype._modelingModules
127 | );
128 |
--------------------------------------------------------------------------------
/lib/NavigatedViewer.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import Viewer from './Viewer';
4 |
5 | import KeyboardMoveModule from 'diagram-js/lib/navigation/keyboard-move';
6 | import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
7 | import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
8 |
9 |
10 | /**
11 | * A viewer that includes mouse navigation facilities
12 | *
13 | * @param {Object} options
14 | */
15 | export default function NavigatedViewer(options) {
16 | Viewer.call(this, options);
17 | }
18 |
19 | inherits(NavigatedViewer, Viewer);
20 |
21 |
22 | NavigatedViewer.prototype._navigationModules = [
23 | KeyboardMoveModule,
24 | MoveCanvasModule,
25 | ZoomScrollModule
26 | ];
27 |
28 | NavigatedViewer.prototype._modules = [].concat(
29 | Viewer.prototype._modules,
30 | NavigatedViewer.prototype._navigationModules
31 | );
--------------------------------------------------------------------------------
/lib/Viewer.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import CoreModule from './core';
4 | import TranslateModule from 'diagram-js/lib/i18n/translate';
5 | import SelectionModule from 'diagram-js/lib/features/selection';
6 | import OverlaysModule from 'diagram-js/lib/features/overlays';
7 |
8 | import BaseViewer from './BaseViewer';
9 |
10 | export default function Viewer(options) {
11 | BaseViewer.call(this, options);
12 | }
13 |
14 | inherits(Viewer, BaseViewer);
15 |
16 | // modules the viewer is composed of
17 | Viewer.prototype._modules = [
18 | CoreModule,
19 | TranslateModule,
20 | SelectionModule,
21 | OverlaysModule
22 | ];
23 |
24 | // default moddle extensions the viewer is composed of
25 | Viewer.prototype._moddleExtensions = {};
--------------------------------------------------------------------------------
/lib/core/index.js:
--------------------------------------------------------------------------------
1 | import DrawModule from '../draw';
2 | import ImportModule from '../import';
3 |
4 | export default {
5 | __depends__: [
6 | DrawModule,
7 | ImportModule
8 | ]
9 | };
--------------------------------------------------------------------------------
/lib/draw/PostitRendererUtil.js:
--------------------------------------------------------------------------------
1 | import {
2 | every,
3 | some
4 | } from 'min-dash';
5 |
6 | import {
7 | componentsToPath
8 | } from 'diagram-js/lib/util/RenderUtil';
9 |
10 | import {
11 | getBusinessObject
12 | } from '../util/ModelUtil';
13 |
14 |
15 | // element utils //////////////////////
16 |
17 | /**
18 | * Checks if eventDefinition of the given element matches with semantic type.
19 | *
20 | * @return {boolean} true if element is of the given semantic type
21 | */
22 | export function isTypedEvent(event, eventDefinitionType, filter) {
23 |
24 | function matches(definition, filter) {
25 | return every(filter, function(val, key) {
26 |
27 | // we want a == conversion here, to be able to catch
28 | // undefined == false and friends
29 | /* jshint -W116 */
30 | return definition[key] == val;
31 | });
32 | }
33 |
34 | return some(event.eventDefinitions, function(definition) {
35 | return definition.$type === eventDefinitionType && matches(event, filter);
36 | });
37 | }
38 |
39 | export function getDi(element) {
40 | return element.businessObject.di;
41 | }
42 |
43 | export function getSemantic(element) {
44 | return element.businessObject;
45 | }
46 |
47 |
48 | // color access //////////////////////
49 |
50 | export function getFillColor(element, defaultColor) {
51 | return (
52 | getColor(element) ||
53 | getDi(element).get('bioc:fill') ||
54 | defaultColor ||
55 | 'white'
56 | );
57 | }
58 |
59 | export function getStrokeColor(element, defaultColor) {
60 | return (
61 | getColor(element) ||
62 | getDi(element).get('bioc:stroke') ||
63 | defaultColor ||
64 | 'black'
65 | );
66 | }
67 |
68 |
69 | // cropping path customizations //////////////////////
70 |
71 | export function getCirclePath(shape) {
72 |
73 | var cx = shape.x + shape.width / 2,
74 | cy = shape.y + shape.height / 2,
75 | radius = shape.width / 2;
76 |
77 | var circlePath = [
78 | [ 'M', cx, cy ],
79 | [ 'm', 0, -radius ],
80 | [ 'a', radius, radius, 0, 1, 1, 0, 2 * radius ],
81 | [ 'a', radius, radius, 0, 1, 1, 0, -2 * radius ],
82 | [ 'z' ]
83 | ];
84 |
85 | return componentsToPath(circlePath);
86 | }
87 |
88 | export function getRoundRectPath(shape, borderRadius) {
89 |
90 | var x = shape.x,
91 | y = shape.y,
92 | width = shape.width,
93 | height = shape.height;
94 |
95 | var roundRectPath = [
96 | [ 'M', x + borderRadius, y ],
97 | [ 'l', width - borderRadius * 2, 0 ],
98 | [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, borderRadius ],
99 | [ 'l', 0, height - borderRadius * 2 ],
100 | [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, borderRadius ],
101 | [ 'l', borderRadius * 2 - width, 0 ],
102 | [ 'a', borderRadius, borderRadius, 0, 0, 1, -borderRadius, -borderRadius ],
103 | [ 'l', 0, borderRadius * 2 - height ],
104 | [ 'a', borderRadius, borderRadius, 0, 0, 1, borderRadius, -borderRadius ],
105 | [ 'z' ]
106 | ];
107 |
108 | return componentsToPath(roundRectPath);
109 | }
110 |
111 | export function getDiamondPath(shape) {
112 |
113 | var width = shape.width,
114 | height = shape.height,
115 | x = shape.x,
116 | y = shape.y,
117 | halfWidth = width / 2,
118 | halfHeight = height / 2;
119 |
120 | var diamondPath = [
121 | [ 'M', x + halfWidth, y ],
122 | [ 'l', halfWidth, halfHeight ],
123 | [ 'l', -halfWidth, halfHeight ],
124 | [ 'l', -halfWidth, -halfHeight ],
125 | [ 'z' ]
126 | ];
127 |
128 | return componentsToPath(diamondPath);
129 | }
130 |
131 | export function getRectPath(shape) {
132 | var x = shape.x,
133 | y = shape.y,
134 | width = shape.width,
135 | height = shape.height;
136 |
137 | var rectPath = [
138 | [ 'M', x, y ],
139 | [ 'l', width, 0 ],
140 | [ 'l', 0, height ],
141 | [ 'l', -width, 0 ],
142 | [ 'z' ]
143 | ];
144 |
145 | return componentsToPath(rectPath);
146 | }
147 |
148 | // helpers //////////
149 |
150 | function getColor(element) {
151 | var bo = getBusinessObject(element);
152 |
153 | return bo.color || element.color;
154 | }
--------------------------------------------------------------------------------
/lib/draw/TextRenderer.js:
--------------------------------------------------------------------------------
1 | import { assign } from 'min-dash';
2 |
3 | import TextUtil from 'diagram-js/lib/util/Text';
4 |
5 | var DEFAULT_FONT_SIZE = 16;
6 | var LINE_HEIGHT_RATIO = 1.2;
7 |
8 |
9 | export default function TextRenderer(config) {
10 |
11 | var defaultStyle = assign({
12 | fontFamily: 'IBM Plex, sans-serif',
13 | fontSize: DEFAULT_FONT_SIZE,
14 | fontWeight: 'normal',
15 | lineHeight: LINE_HEIGHT_RATIO
16 | }, config && config.defaultStyle || {});
17 |
18 | var fontSize = parseInt(defaultStyle.fontSize, 10) - 1;
19 |
20 | var externalStyle = assign({}, defaultStyle, {
21 | fontSize: fontSize
22 | }, config && config.externalStyle || {});
23 |
24 | var textUtil = new TextUtil({
25 | style: defaultStyle
26 | });
27 |
28 | /**
29 | * Get the new bounds of an externally rendered,
30 | * layouted label.
31 | *
32 | * @param {Bounds} bounds
33 | * @param {String} text
34 | *
35 | * @return {Bounds}
36 | */
37 | this.getExternalLabelBounds = function(bounds, text) {
38 |
39 | var layoutedDimensions = textUtil.getDimensions(text, {
40 | box: {
41 | width: 90,
42 | height: 30,
43 | x: bounds.width / 2 + bounds.x,
44 | y: bounds.height / 2 + bounds.y
45 | },
46 | style: externalStyle
47 | });
48 |
49 | // resize label shape to fit label text
50 | return {
51 | x: Math.round(bounds.x + bounds.width / 2 - layoutedDimensions.width / 2),
52 | y: Math.round(bounds.y),
53 | width: Math.ceil(layoutedDimensions.width),
54 | height: Math.ceil(layoutedDimensions.height)
55 | };
56 |
57 | };
58 |
59 | /**
60 | * Create a layouted text element.
61 | *
62 | * @param {String} text
63 | * @param {Object} [options]
64 | *
65 | * @return {SVGElement} rendered text
66 | */
67 | this.createText = function(text, options) {
68 | return textUtil.createText(text, options || {});
69 | };
70 |
71 | /**
72 | * Get default text style.
73 | */
74 | this.getDefaultStyle = function() {
75 | return defaultStyle;
76 | };
77 |
78 | /**
79 | * Get the external text style.
80 | */
81 | this.getExternalStyle = function() {
82 | return externalStyle;
83 | };
84 |
85 | }
86 |
87 | TextRenderer.$inject = [
88 | 'config.textRenderer'
89 | ];
--------------------------------------------------------------------------------
/lib/draw/index.js:
--------------------------------------------------------------------------------
1 | import PostitRenderer from './PostitRenderer';
2 | import TextRenderer from './TextRenderer';
3 |
4 | import PathMap from './PathMap';
5 |
6 | export default {
7 | __init__: [ 'postitRenderer' ],
8 | postitRenderer: [ 'type', PostitRenderer ],
9 | textRenderer: [ 'type', TextRenderer ],
10 | pathMap: [ 'type', PathMap ]
11 | };
12 |
--------------------------------------------------------------------------------
/lib/features/canvas-create/CanvasCreate.js:
--------------------------------------------------------------------------------
1 | import {
2 | delegate as domDelegate
3 | } from 'min-dom';
4 |
5 | import {
6 | assign
7 | } from 'min-dash';
8 |
9 | import {
10 | getBusinessObject
11 | } from '../../util/ModelUtil';
12 |
13 | import COLORS from '../../util/ColorUtil';
14 |
15 | import {
16 | toPoint
17 | } from 'diagram-js/lib/util/Event';
18 | import { isAny } from '../modeling/util/ModelingUtil';
19 |
20 | var DEFAULT_SHAPE = {
21 | type: 'postit:SquarePostit',
22 | color: COLORS.GREEN,
23 | $instanceOf: function() { return true; }
24 | };
25 |
26 | export default function CanvasCreate(
27 | eventBus, elementFactory, canvas, directEditing, modeling) {
28 |
29 | var lastCreatedShape = DEFAULT_SHAPE;
30 |
31 | function _getNewShapePosition(event) {
32 | var eventPoint = toPoint(event);
33 |
34 | return {
35 | x: eventPoint.x,
36 | y: eventPoint.y
37 | };
38 | }
39 |
40 | function _activateDirectEdit(element) {
41 | if (isAny(element, [ 'postit:Postit', 'postit:Group', 'postit:TextBox' ])) {
42 |
43 | directEditing.activate(element);
44 | }
45 | }
46 |
47 | function _createShapeOnCanvas(event) {
48 | var position = _getNewShapePosition(event);
49 |
50 | var newShape = elementFactory.createPostitElement(
51 | 'shape', assign(lastCreatedShape, position));
52 |
53 | var root = canvas.getRootElement();
54 |
55 | var createdShape = modeling.createShape(newShape, position, root);
56 |
57 | _activateDirectEdit(createdShape);
58 | }
59 |
60 | function _saveLastCreatedShape(shape) {
61 | if (!shape) {
62 | lastCreatedShape = DEFAULT_SHAPE;
63 | return;
64 | }
65 |
66 | var bo = getBusinessObject(shape);
67 |
68 | lastCreatedShape = {
69 | type: shape.type,
70 | color: shape.color || bo.color,
71 | $instanceOf: function(type) {
72 | return (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type);
73 | }
74 | };
75 | }
76 |
77 |
78 | eventBus.on('canvas.init', function(context) {
79 | var svg = context.svg;
80 |
81 | domDelegate.bind(svg, 'svg', 'dblclick', function(event) {
82 | if (event.target !== svg) {
83 | return;
84 | }
85 |
86 | _createShapeOnCanvas(event);
87 | });
88 |
89 | eventBus.on('create.end', function(context) {
90 | var shape = context.shape;
91 | _saveLastCreatedShape(shape);
92 | });
93 | });
94 | }
95 |
96 | CanvasCreate.prototype.$inject = [
97 | 'eventBus',
98 | 'elementFactory',
99 | 'canvas',
100 | 'directEditing',
101 | 'modeling'
102 | ];
--------------------------------------------------------------------------------
/lib/features/canvas-create/index.js:
--------------------------------------------------------------------------------
1 | import CanvasCreate from './CanvasCreate';
2 | import DirectEditingModule from 'diagram-js-direct-editing';
3 |
4 | export default {
5 | __depends__: [
6 | DirectEditingModule
7 | ],
8 | __init__: [ 'canvasCreate' ],
9 | canvasCreate: [ 'type', CanvasCreate ]
10 | };
--------------------------------------------------------------------------------
/lib/features/context-pad/ContextPadProvider.js:
--------------------------------------------------------------------------------
1 | import {
2 | assign,
3 | isArray,
4 | keys,
5 | forEach
6 | } from 'min-dash';
7 |
8 | import {
9 | hasPrimaryModifier
10 | } from 'diagram-js/lib/util/Mouse';
11 |
12 | import COLORS from '../../util/ColorUtil';
13 | import { is, getBusinessObject } from '../../util/ModelUtil';
14 |
15 |
16 | /**
17 | * A provider for postit elements context pad
18 | */
19 | export default function ContextPadProvider(
20 | config, injector, eventBus,
21 | contextPad, modeling, rules,
22 | imageSelection, translate) {
23 |
24 | config = config || {};
25 |
26 | contextPad.registerProvider(this);
27 |
28 | this._contextPad = contextPad;
29 |
30 | this._modeling = modeling;
31 |
32 | this._rules = rules;
33 | this._imageSelection = imageSelection;
34 | this._translate = translate;
35 |
36 | if (config.autoPlace !== false) {
37 | this._autoPlace = injector.get('autoPlace', false);
38 | }
39 |
40 | eventBus.on('create.end', 250, function(event) {
41 | var context = event.context,
42 | shape = context.shape;
43 |
44 | if (!hasPrimaryModifier(event) || !contextPad.isOpen(shape)) {
45 | return;
46 | }
47 |
48 | var entries = contextPad.getEntries(shape);
49 |
50 | if (entries.replace) {
51 | entries.replace.action.click(event, shape);
52 | }
53 | });
54 | }
55 |
56 | ContextPadProvider.$inject = [
57 | 'config.contextPad',
58 | 'injector',
59 | 'eventBus',
60 | 'contextPad',
61 | 'modeling',
62 | 'rules',
63 | 'imageSelection',
64 | 'translate'
65 | ];
66 |
67 |
68 | ContextPadProvider.prototype.getContextPadEntries = function(element) {
69 |
70 | const {
71 | _rules: rules,
72 | _modeling: modeling,
73 | _imageSelection: imageSelection,
74 | _translate: translate
75 | } = this;
76 |
77 | let actions = {};
78 |
79 | function removeElement(e) {
80 | modeling.removeElements([ element ]);
81 | }
82 |
83 | function setColor(color) {
84 | modeling.setColor(element, color);
85 | }
86 |
87 | function createDeleteEntry(actions) {
88 |
89 | // delete element entry, only show if allowed by rules
90 | let deleteAllowed = rules.allowed('elements.delete', { elements: [ element ] });
91 |
92 | if (isArray(deleteAllowed)) {
93 |
94 | // was the element returned as a deletion candidate?
95 | deleteAllowed = deleteAllowed[0] === element;
96 | }
97 |
98 | if (deleteAllowed) {
99 | assign(actions, {
100 | 'delete': {
101 | group: 'edit',
102 | className: 'bpmn-icon-trash',
103 | title: translate('Remove'),
104 | action: {
105 | click: removeElement
106 | }
107 | }
108 | });
109 | }
110 | }
111 |
112 | function createColoringEntries(actions) {
113 | forEach(keys(COLORS), key => {
114 | var color = COLORS[key];
115 |
116 | function getClassNames() {
117 | var classNames = [];
118 |
119 | if (color === getColor(element)) {
120 |
121 | classNames.push('pjs-color-entry-disabled');
122 | }
123 |
124 | classNames.push('pjs-color-entry-' + key);
125 |
126 | return classNames;
127 | }
128 |
129 | assign(actions, {
130 | ['color-' + key]: {
131 | group: 'color',
132 | className: getClassNames(),
133 | title: translate('Set Color'),
134 | action: {
135 | click: (event) => setColor(color)
136 | }
137 | }
138 | });
139 | });
140 | }
141 |
142 | if (element.type === 'label') {
143 | return actions;
144 | }
145 |
146 | if (is(element, 'postit:Postit')) {
147 | createColoringEntries(actions);
148 | }
149 |
150 | if (is(element, 'postit:Image')) {
151 | assign(actions, {
152 | 'replace.image': {
153 | group: 'replace',
154 | className: 'bpmn-icon-screw-wrench',
155 | title: translate('Change image source'),
156 | action: {
157 | click: (event) => imageSelection.select(element)
158 | }
159 | }
160 | });
161 | }
162 |
163 | createDeleteEntry(actions);
164 |
165 | return actions;
166 | };
167 |
168 | // helpers //////////
169 |
170 | function getColor(element) {
171 | var bo = getBusinessObject(element);
172 |
173 | return bo.color || element.color;
174 | }
175 |
--------------------------------------------------------------------------------
/lib/features/context-pad/index.js:
--------------------------------------------------------------------------------
1 | import DirectEditingModule from 'diagram-js-direct-editing';
2 | import ContextPadModule from 'diagram-js/lib/features/context-pad';
3 | import SelectionModule from 'diagram-js/lib/features/selection';
4 | import ConnectModule from 'diagram-js/lib/features/connect';
5 | import CreateModule from 'diagram-js/lib/features/create';
6 | import PopupMenuModule from '../popup-menu';
7 |
8 | import ContextPadProvider from './ContextPadProvider';
9 |
10 | export default {
11 | __depends__: [
12 | DirectEditingModule,
13 | ContextPadModule,
14 | SelectionModule,
15 | ConnectModule,
16 | CreateModule,
17 | PopupMenuModule
18 | ],
19 | __init__: [ 'contextPadProvider' ],
20 | contextPadProvider: [ 'type', ContextPadProvider ]
21 | };
--------------------------------------------------------------------------------
/lib/features/copy-paste/PostitCopyPaste.js:
--------------------------------------------------------------------------------
1 | import {
2 | getBusinessObject
3 | } from '../../util/ModelUtil';
4 |
5 | import {
6 | forEach,
7 | isArray,
8 | isUndefined,
9 | omit,
10 | reduce
11 | } from 'min-dash';
12 |
13 | function copyProperties(source, target, properties) {
14 | if (!isArray(properties)) {
15 | properties = [ properties ];
16 | }
17 |
18 | forEach(properties, function(property) {
19 | if (!isUndefined(source[property])) {
20 | target[property] = source[property];
21 | }
22 | });
23 | }
24 |
25 | function removeProperties(element, properties) {
26 | if (!isArray(properties)) {
27 | properties = [ properties ];
28 | }
29 |
30 | forEach(properties, function(property) {
31 | if (element[property]) {
32 | delete element[property];
33 | }
34 | });
35 | }
36 |
37 | var LOW_PRIORITY = 750;
38 |
39 |
40 | export default function PostitCopyPaste(postitFactory, eventBus, moddleCopy) {
41 |
42 | eventBus.on('copyPaste.copyElement', LOW_PRIORITY, function(context) {
43 | var descriptor = context.descriptor,
44 | element = context.element;
45 |
46 | var businessObject = descriptor.oldBusinessObject = getBusinessObject(element);
47 |
48 | descriptor.type = element.type;
49 |
50 | copyProperties(businessObject, descriptor, 'name');
51 |
52 | descriptor.di = {};
53 |
54 | // fill and stroke will be set to DI
55 | copyProperties(businessObject.di, descriptor.di, [
56 | 'fill',
57 | 'stroke'
58 | ]);
59 |
60 | if (isLabel(descriptor)) {
61 | return descriptor;
62 | }
63 |
64 | });
65 |
66 | var references;
67 |
68 | function resolveReferences(descriptor, cache) {
69 | var businessObject = getBusinessObject(descriptor);
70 |
71 | // default sequence flows
72 | if (descriptor.default) {
73 |
74 | // relationship cannot be resolved immediately
75 | references[ descriptor.default ] = {
76 | element: businessObject,
77 | property: 'default'
78 | };
79 | }
80 |
81 | references = omit(references, reduce(references, function(array, reference, key) {
82 | var element = reference.element,
83 | property = reference.property;
84 |
85 | if (key === descriptor.id) {
86 | element[ property ] = businessObject;
87 |
88 | array.push(descriptor.id);
89 | }
90 |
91 | return array;
92 | }, []));
93 | }
94 |
95 | eventBus.on('copyPaste.pasteElements', function() {
96 | references = {};
97 | });
98 |
99 | eventBus.on('copyPaste.pasteElement', function(context) {
100 | var cache = context.cache,
101 | descriptor = context.descriptor,
102 | oldBusinessObject = descriptor.oldBusinessObject,
103 | newBusinessObject;
104 |
105 | // do NOT copy business object if external label
106 | if (isLabel(descriptor)) {
107 | descriptor.businessObject = getBusinessObject(cache[ descriptor.labelTarget ]);
108 |
109 | return;
110 | }
111 |
112 | newBusinessObject = postitFactory.create(oldBusinessObject.$type);
113 |
114 | descriptor.businessObject = moddleCopy.copyElement(
115 | oldBusinessObject,
116 | newBusinessObject
117 | );
118 |
119 | // resolve references e.g. default sequence flow
120 | resolveReferences(descriptor, cache);
121 |
122 | copyProperties(descriptor, newBusinessObject, [
123 | 'color',
124 | 'name'
125 | ]);
126 |
127 | removeProperties(descriptor, 'oldBusinessObject');
128 | });
129 |
130 | }
131 |
132 |
133 | PostitCopyPaste.$inject = [
134 | 'postitFactory',
135 | 'eventBus',
136 | 'moddleCopy'
137 | ];
138 |
139 | // helpers //////////
140 |
141 | function isLabel(element) {
142 | return !!element.labelTarget;
143 | }
144 |
--------------------------------------------------------------------------------
/lib/features/copy-paste/index.js:
--------------------------------------------------------------------------------
1 | import CopyPasteModule from 'diagram-js/lib/features/copy-paste';
2 |
3 | import PostitCopyPaste from './PostitCopyPaste';
4 | import ModdleCopy from './ModdleCopy';
5 |
6 | export default {
7 | __depends__: [
8 | CopyPasteModule
9 | ],
10 | __init__: [ 'postitCopyPaste', 'moddleCopy' ],
11 | postitCopyPaste: [ 'type', PostitCopyPaste ],
12 | moddleCopy: [ 'type', ModdleCopy ]
13 | };
14 |
--------------------------------------------------------------------------------
/lib/features/di-ordering/PostitDiOrdering.js:
--------------------------------------------------------------------------------
1 | import { getDi } from '../../draw/PostitRendererUtil';
2 | import { getBusinessObject } from '../../util/ModelUtil';
3 |
4 | import {
5 | filter,
6 | map
7 | } from 'min-dash';
8 |
9 | import { selfAndAllChildren } from 'diagram-js/lib/util/Elements';
10 |
11 |
12 | var HIGH_PRIORITY = 2000;
13 |
14 | export default function PostitDiOrdering(eventBus, canvas) {
15 |
16 | eventBus.on('saveXML.start', HIGH_PRIORITY, orderDi);
17 |
18 | function orderDi() {
19 | var root = canvas.getRootElement(),
20 | rootDi = getBusinessObject(root).di,
21 | elements,
22 | diElements;
23 |
24 | elements = selfAndAllChildren([ root ], false);
25 |
26 | // only postitDi:Shape can be direct children of postitDi:Plane
27 | elements = filter(elements, function(element) {
28 | return element !== root && !element.labelTarget;
29 | });
30 |
31 | diElements = map(elements, getDi);
32 |
33 | rootDi.set('planeElement', diElements);
34 | }
35 | }
36 |
37 | PostitDiOrdering.$inject = [ 'eventBus', 'canvas' ];
38 |
--------------------------------------------------------------------------------
/lib/features/di-ordering/index.js:
--------------------------------------------------------------------------------
1 | import PostitDiOrdering from './PostitDiOrdering';
2 |
3 | export default {
4 | __init__: [
5 | 'postitDiOrdering'
6 | ],
7 | postitDiOrdering: [ 'type', PostitDiOrdering ]
8 | };
--------------------------------------------------------------------------------
/lib/features/editor-actions/PostitEditorActions.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import EditorActions from 'diagram-js/lib/features/editor-actions/EditorActions';
4 |
5 | import {
6 | getBBox
7 | } from 'diagram-js/lib/util/Elements';
8 |
9 |
10 | /**
11 | * Registers and executes Postit specific editor actions.
12 | *
13 | * @param {Injector} injector
14 | */
15 | export default function PostitEditorActions(injector) {
16 | injector.invoke(EditorActions, this);
17 | }
18 |
19 | inherits(PostitEditorActions, EditorActions);
20 |
21 | PostitEditorActions.$inject = [
22 | 'injector'
23 | ];
24 |
25 | /**
26 | * Register default actions.
27 | *
28 | * @param {Injector} injector
29 | */
30 | PostitEditorActions.prototype._registerDefaultActions = function(injector) {
31 |
32 | // (0) invoke super method
33 |
34 | EditorActions.prototype._registerDefaultActions.call(this, injector);
35 |
36 | // (1) retrieve optional components to integrate with
37 |
38 | var canvas = injector.get('canvas', false);
39 | var elementRegistry = injector.get('elementRegistry', false);
40 | var selection = injector.get('selection', false);
41 | var spaceTool = injector.get('spaceTool', false);
42 | var lassoTool = injector.get('lassoTool', false);
43 | var handTool = injector.get('handTool', false);
44 | var distributeElements = injector.get('distributeElements', false);
45 | var alignElements = injector.get('alignElements', false);
46 | var directEditing = injector.get('directEditing', false);
47 | var searchPad = injector.get('searchPad', false);
48 | var modeling = injector.get('modeling', false);
49 |
50 | // (2) check components and register actions
51 |
52 | if (canvas && elementRegistry && selection) {
53 | this._registerAction('selectElements', function() {
54 |
55 | // select all elements except for the invisible
56 | // root element
57 | var rootElement = canvas.getRootElement();
58 |
59 | var elements = elementRegistry.filter(function(element) {
60 | return element !== rootElement;
61 | });
62 |
63 | selection.select(elements);
64 |
65 | return elements;
66 | });
67 | }
68 |
69 | if (spaceTool) {
70 | this._registerAction('spaceTool', function() {
71 | spaceTool.toggle();
72 | });
73 | }
74 |
75 | if (lassoTool) {
76 | this._registerAction('lassoTool', function() {
77 | lassoTool.toggle();
78 | });
79 | }
80 |
81 | if (handTool) {
82 | this._registerAction('handTool', function() {
83 | handTool.toggle();
84 | });
85 | }
86 |
87 | if (selection && distributeElements) {
88 | this._registerAction('distributeElements', function(opts) {
89 | var currentSelection = selection.get(),
90 | type = opts.type;
91 |
92 | if (currentSelection.length) {
93 | distributeElements.trigger(currentSelection, type);
94 | }
95 | });
96 | }
97 |
98 | if (selection && alignElements) {
99 | this._registerAction('alignElements', function(opts) {
100 | var currentSelection = selection.get(),
101 | type = opts.type;
102 |
103 | if (currentSelection.length) {
104 | alignElements.trigger(currentSelection, type);
105 | }
106 | });
107 | }
108 |
109 | if (selection && modeling) {
110 | this._registerAction('setColor', function(opts) {
111 | var currentSelection = selection.get();
112 |
113 | if (currentSelection.length) {
114 | modeling.setColor(currentSelection, opts);
115 | }
116 | });
117 | }
118 |
119 | if (selection && directEditing) {
120 | this._registerAction('directEditing', function() {
121 | var currentSelection = selection.get();
122 |
123 | if (currentSelection.length) {
124 | directEditing.activate(currentSelection[0]);
125 | }
126 | });
127 | }
128 |
129 | if (searchPad) {
130 | this._registerAction('find', function() {
131 | searchPad.toggle();
132 | });
133 | }
134 |
135 | if (canvas && modeling) {
136 | this._registerAction('moveToOrigin', function() {
137 | var rootElement = canvas.getRootElement(),
138 | boundingBox;
139 |
140 |
141 | var elements = elementRegistry.filter(function(element) {
142 | return element !== rootElement;
143 | });
144 |
145 | boundingBox = getBBox(elements);
146 |
147 | modeling.moveElements(
148 | elements,
149 | { x: -boundingBox.x, y: -boundingBox.y },
150 | rootElement
151 | );
152 | });
153 | }
154 |
155 | };
--------------------------------------------------------------------------------
/lib/features/editor-actions/index.js:
--------------------------------------------------------------------------------
1 | import EditorActionsModule from 'diagram-js/lib/features/editor-actions';
2 |
3 | import PostitEditorActions from './PostitEditorActions';
4 |
5 | export default {
6 | __depends__: [
7 | EditorActionsModule
8 | ],
9 | editorActions: [ 'type', PostitEditorActions ]
10 | };
11 |
--------------------------------------------------------------------------------
/lib/features/image-selection/index.js:
--------------------------------------------------------------------------------
1 | import ImageSelection from './ImageSelection';
2 |
3 | export default {
4 | __init__: [ 'imageSelection' ],
5 | imageSelection: [ 'type', ImageSelection ]
6 | };
7 |
--------------------------------------------------------------------------------
/lib/features/keyboard/PostitKeyboardBindings.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import KeyboardBindings from 'diagram-js/lib/features/keyboard/KeyboardBindings';
4 |
5 |
6 | /**
7 | * Postit specific keyboard bindings.
8 | *
9 | * @param {Injector} injector
10 | */
11 | export default function PostitKeyboardBindings(injector) {
12 | injector.invoke(KeyboardBindings, this);
13 | }
14 |
15 | inherits(PostitKeyboardBindings, KeyboardBindings);
16 |
17 | PostitKeyboardBindings.$inject = [
18 | 'injector'
19 | ];
20 |
21 |
22 | /**
23 | * Register available keyboard bindings.
24 | *
25 | * @param {Keyboard} keyboard
26 | * @param {EditorActions} editorActions
27 | */
28 | PostitKeyboardBindings.prototype.registerBindings = function(keyboard, editorActions) {
29 |
30 | // inherit default bindings
31 | KeyboardBindings.prototype.registerBindings.call(this, keyboard, editorActions);
32 |
33 | /**
34 | * Add keyboard binding if respective editor action
35 | * is registered.
36 | *
37 | * @param {String} action name
38 | * @param {Function} fn that implements the key binding
39 | */
40 | function addListener(action, fn) {
41 |
42 | if (editorActions.isRegistered(action)) {
43 | keyboard.addListener(fn);
44 | }
45 | }
46 |
47 | // select all elements
48 | // CTRL + A
49 | addListener('selectElements', function(context) {
50 |
51 | var event = context.keyEvent;
52 |
53 | if (keyboard.isKey([ 'a', 'A' ], event) && keyboard.isCmd(event)) {
54 | editorActions.trigger('selectElements');
55 |
56 | return true;
57 | }
58 | });
59 |
60 | // search labels
61 | // CTRL + F
62 | addListener('find', function(context) {
63 |
64 | var event = context.keyEvent;
65 |
66 | if (keyboard.isKey([ 'f', 'F' ], event) && keyboard.isCmd(event)) {
67 | editorActions.trigger('find');
68 |
69 | return true;
70 | }
71 | });
72 |
73 | // activate space tool
74 | // S
75 | addListener('spaceTool', function(context) {
76 |
77 | var event = context.keyEvent;
78 |
79 | if (keyboard.hasModifier(event)) {
80 | return;
81 | }
82 |
83 | if (keyboard.isKey([ 's', 'S' ], event)) {
84 | editorActions.trigger('spaceTool');
85 |
86 | return true;
87 | }
88 | });
89 |
90 | // activate lasso tool
91 | // L
92 | addListener('lassoTool', function(context) {
93 |
94 | var event = context.keyEvent;
95 |
96 | if (keyboard.hasModifier(event)) {
97 | return;
98 | }
99 |
100 | if (keyboard.isKey([ 'l', 'L' ], event)) {
101 | editorActions.trigger('lassoTool');
102 |
103 | return true;
104 | }
105 | });
106 |
107 | // activate hand tool
108 | // H
109 | addListener('handTool', function(context) {
110 |
111 | var event = context.keyEvent;
112 |
113 | if (keyboard.hasModifier(event)) {
114 | return;
115 | }
116 |
117 | if (keyboard.isKey([ 'h', 'H' ], event)) {
118 | editorActions.trigger('handTool');
119 |
120 | return true;
121 | }
122 | });
123 |
124 | // activate direct editing
125 | // E
126 | addListener('directEditing', function(context) {
127 |
128 | var event = context.keyEvent;
129 |
130 | if (keyboard.hasModifier(event)) {
131 | return;
132 | }
133 |
134 | if (keyboard.isKey([ 'e', 'E' ], event)) {
135 | editorActions.trigger('directEditing');
136 |
137 | return true;
138 | }
139 | });
140 |
141 | };
--------------------------------------------------------------------------------
/lib/features/keyboard/index.js:
--------------------------------------------------------------------------------
1 | import KeyboardModule from 'diagram-js/lib/features/keyboard';
2 |
3 | import PostitKeyboardBindings from './PostitKeyboardBindings';
4 |
5 | export default {
6 | __depends__: [
7 | KeyboardModule
8 | ],
9 | __init__: [ 'keyboardBindings' ],
10 | keyboardBindings: [ 'type', PostitKeyboardBindings ]
11 | };
12 |
--------------------------------------------------------------------------------
/lib/features/label-editing/LabelEditingPreview.js:
--------------------------------------------------------------------------------
1 | import {
2 | remove as svgRemove
3 | } from 'tiny-svg';
4 |
5 | import {
6 | is
7 | } from '../../util/ModelUtil';
8 |
9 | var MARKER_HIDDEN = 'djs-element-hidden',
10 | MARKER_LABEL_HIDDEN = 'djs-label-hidden';
11 |
12 |
13 | export default function LabelEditingPreview(
14 | eventBus, canvas, elementRegistry,
15 | pathMap) {
16 |
17 |
18 | var element, gfx;
19 |
20 | eventBus.on('directEditing.activate', function(context) {
21 | var activeProvider = context.active;
22 |
23 | element = activeProvider.element.label || activeProvider.element;
24 |
25 |
26 | if (element.labelTarget) {
27 | canvas.addMarker(element, MARKER_HIDDEN);
28 | } else if (is(element, 'postit:Postit')) {
29 | canvas.addMarker(element, MARKER_LABEL_HIDDEN);
30 | }
31 | });
32 |
33 |
34 | eventBus.on([ 'directEditing.complete', 'directEditing.cancel' ], function(context) {
35 | var activeProvider = context.active;
36 |
37 | if (activeProvider) {
38 | canvas.removeMarker(activeProvider.element.label || activeProvider.element, MARKER_HIDDEN);
39 | canvas.removeMarker(element, MARKER_LABEL_HIDDEN);
40 | }
41 |
42 | element = undefined;
43 |
44 | if (gfx) {
45 | svgRemove(gfx);
46 |
47 | gfx = undefined;
48 | }
49 | });
50 | }
51 |
52 | LabelEditingPreview.$inject = [
53 | 'eventBus',
54 | 'canvas',
55 | 'elementRegistry',
56 | 'pathMap'
57 | ];
--------------------------------------------------------------------------------
/lib/features/label-editing/LabelUtil.js:
--------------------------------------------------------------------------------
1 | import { isAny } from '../modeling/util/ModelingUtil';
2 |
3 | function getLabelAttr(semantic) {
4 | if (isAny(semantic, [ 'postit:Postit', 'postit:TextBox', 'postit:Group' ])) {
5 | return 'name';
6 | }
7 | }
8 |
9 | export function getLabel(element) {
10 | var semantic = element.businessObject,
11 | attr = getLabelAttr(semantic);
12 |
13 | if (attr) {
14 | return semantic[attr] || '';
15 | }
16 | }
17 |
18 |
19 | export function setLabel(element, text) {
20 | var semantic = element.businessObject,
21 | attr = getLabelAttr(semantic);
22 |
23 | if (attr) {
24 | semantic[attr] = text;
25 | }
26 |
27 | return element;
28 | }
--------------------------------------------------------------------------------
/lib/features/label-editing/cmd/UpdateLabelHandler.js:
--------------------------------------------------------------------------------
1 | import {
2 | setLabel,
3 | getLabel
4 | } from '../LabelUtil';
5 |
6 | import {
7 | getExternalLabelMid,
8 | isLabelExternal,
9 | hasExternalLabel,
10 | isLabel
11 | } from '../../../util/LabelUtil';
12 |
13 | var NULL_DIMENSIONS = {
14 | width: 0,
15 | height: 0
16 | };
17 |
18 |
19 | /**
20 | * A handler that updates the text of a postit element.
21 | */
22 | export default function UpdateLabelHandler(modeling, textRenderer) {
23 |
24 | /**
25 | * Set the label and return the changed elements.
26 | *
27 | * Element parameter can be label itself or connection (i.e. sequence flow).
28 | *
29 | * @param {djs.model.Base} element
30 | * @param {String} text
31 | */
32 | function setText(element, text) {
33 |
34 | // external label if present
35 | var label = element.label || element;
36 |
37 | var labelTarget = element.labelTarget || element;
38 |
39 | setLabel(label, text, labelTarget !== label);
40 |
41 | return [ label, labelTarget ];
42 | }
43 |
44 | function preExecute(ctx) {
45 | var element = ctx.element,
46 | businessObject = element.businessObject,
47 | newLabel = ctx.newLabel;
48 |
49 | if (!isLabel(element)
50 | && isLabelExternal(element)
51 | && !hasExternalLabel(element)
52 | && !isEmptyText(newLabel)) {
53 |
54 | // create label
55 | var paddingTop = 7;
56 |
57 | var labelCenter = getExternalLabelMid(element);
58 |
59 | labelCenter = {
60 | x: labelCenter.x,
61 | y: labelCenter.y + paddingTop
62 | };
63 |
64 | modeling.createLabel(element, labelCenter, {
65 | id: businessObject.id + '_label',
66 | businessObject: businessObject
67 | });
68 | }
69 | }
70 |
71 | function execute(ctx) {
72 | ctx.oldLabel = getLabel(ctx.element);
73 | return setText(ctx.element, ctx.newLabel);
74 | }
75 |
76 | function revert(ctx) {
77 | return setText(ctx.element, ctx.oldLabel);
78 | }
79 |
80 | function postExecute(ctx) {
81 | var element = ctx.element,
82 | label = element.label || element,
83 | newLabel = ctx.newLabel,
84 | newBounds = ctx.newBounds,
85 | hints = ctx.hints || {};
86 |
87 | // ignore internal labels for elements
88 | if (!isLabel(label)) {
89 | return;
90 | }
91 |
92 | if (isLabel(label) && isEmptyText(newLabel)) {
93 |
94 | if (hints.removeShape !== false) {
95 | modeling.removeShape(label, { unsetLabel: false });
96 | }
97 |
98 | return;
99 | }
100 |
101 | var text = getLabel(label);
102 |
103 | // resize element based on label _or_ pre-defined bounds
104 | if (typeof newBounds === 'undefined') {
105 | newBounds = textRenderer.getExternalLabelBounds(label, text);
106 | }
107 |
108 | // setting newBounds to false or _null_ will
109 | // disable the postExecute resize operation
110 | if (newBounds) {
111 | modeling.resizeShape(label, newBounds, NULL_DIMENSIONS);
112 | }
113 | }
114 |
115 | // API
116 |
117 | this.preExecute = preExecute;
118 | this.execute = execute;
119 | this.revert = revert;
120 | this.postExecute = postExecute;
121 | }
122 |
123 | UpdateLabelHandler.$inject = [
124 | 'modeling',
125 | 'textRenderer'
126 | ];
127 |
128 |
129 | // helpers ///////////////////////
130 |
131 | function isEmptyText(label) {
132 | return !label || !label.trim();
133 | }
--------------------------------------------------------------------------------
/lib/features/label-editing/index.js:
--------------------------------------------------------------------------------
1 | import ChangeSupportModule from 'diagram-js/lib/features/change-support';
2 | import ResizeModule from 'diagram-js/lib/features/resize';
3 | import DirectEditingModule from 'diagram-js-direct-editing';
4 |
5 | import LabelEditingProvider from './LabelEditingProvider';
6 | import LabelEditingPreview from './LabelEditingPreview';
7 |
8 |
9 | export default {
10 | __depends__: [
11 | ChangeSupportModule,
12 | ResizeModule,
13 | DirectEditingModule
14 | ],
15 | __init__: [
16 | 'labelEditingProvider',
17 | 'labelEditingPreview'
18 | ],
19 | labelEditingProvider: [ 'type', LabelEditingProvider ],
20 | labelEditingPreview: [ 'type', LabelEditingPreview ]
21 | };
22 |
--------------------------------------------------------------------------------
/lib/features/modeling/ElementFactory.js:
--------------------------------------------------------------------------------
1 | import {
2 | assign,
3 | forEach
4 | } from 'min-dash';
5 |
6 | import inherits from 'inherits-browser';
7 |
8 | import { is } from '../../util/ModelUtil';
9 |
10 | import BaseElementFactory from 'diagram-js/lib/core/ElementFactory';
11 |
12 | import {
13 | DEFAULT_LABEL_SIZE
14 | } from '../../util/LabelUtil';
15 |
16 |
17 | /**
18 | * A postit-aware factory for diagram-js shapes
19 | */
20 | export default function ElementFactory(postitFactory, moddle, translate) {
21 | BaseElementFactory.call(this);
22 |
23 | this._postitFactory = postitFactory;
24 | this._moddle = moddle;
25 | this._translate = translate;
26 | }
27 |
28 | inherits(ElementFactory, BaseElementFactory);
29 |
30 | ElementFactory.$inject = [
31 | 'postitFactory',
32 | 'moddle',
33 | 'translate'
34 | ];
35 |
36 | ElementFactory.prototype.baseCreate = BaseElementFactory.prototype.create;
37 |
38 | ElementFactory.prototype.create = function(elementType, attrs) {
39 |
40 | // no special magic for labels,
41 | // we assume their businessObjects have already been created
42 | // and wired via attrs
43 | if (elementType === 'label') {
44 | return this.baseCreate(elementType, assign({ type: 'label' }, DEFAULT_LABEL_SIZE, attrs));
45 | }
46 |
47 | return this.createPostitElement(elementType, attrs);
48 | };
49 |
50 | ElementFactory.prototype.createPostitElement = function(elementType, attrs) {
51 | var size,
52 | translate = this._translate;
53 |
54 | attrs = attrs || {};
55 |
56 | var businessObject = attrs.businessObject;
57 |
58 | if (!businessObject) {
59 | if (!attrs.type) {
60 | throw new Error(translate('no shape type specified'));
61 | }
62 |
63 | businessObject = this._postitFactory.create(attrs.type);
64 | }
65 |
66 | if (!businessObject.di) {
67 | if (elementType === 'root') {
68 | businessObject.di = this._postitFactory.createDiPlane(businessObject, [], {
69 | id: businessObject.id + '_di'
70 | });
71 | } else {
72 | businessObject.di = this._postitFactory.createDiShape(businessObject, {}, {
73 | id: businessObject.id + '_di'
74 | });
75 | }
76 | }
77 |
78 | if (is(businessObject, 'postit:Group')) {
79 | attrs = assign({
80 | isFrame: true
81 | }, attrs);
82 | }
83 |
84 | if (attrs.di) {
85 | assign(businessObject.di, attrs.di);
86 |
87 | delete attrs.di;
88 | }
89 |
90 | applyAttributes(businessObject, attrs, [
91 | 'processRef',
92 | 'isInterrupting',
93 | 'associationDirection',
94 | 'isForCompensation'
95 | ]);
96 |
97 | size = this._getDefaultSize(businessObject);
98 |
99 | attrs = assign({
100 | businessObject: businessObject,
101 | id: businessObject.id
102 | }, size, attrs);
103 |
104 | return this.baseCreate(elementType, attrs);
105 | };
106 |
107 |
108 | ElementFactory.prototype._getDefaultSize = function(semantic) {
109 | if (is(semantic, 'postit:Postit')) {
110 | return { width: 150, height: 150 };
111 | }
112 |
113 | if (is(semantic, 'postit:Group')) {
114 | return { width: 300, height: 300 };
115 | }
116 |
117 | if (is(semantic, 'postit:Image')) {
118 | return { width: 300, height: 300 };
119 | }
120 |
121 | return { width: 100, height: 80 };
122 | };
123 |
124 |
125 |
126 | // helpers //////////////////////
127 |
128 | /**
129 | * Apply attributes from a map to the given element,
130 | * remove attribute from the map on application.
131 | *
132 | * @param {Base} element
133 | * @param {Object} attrs (in/out map of attributes)
134 | * @param {Array} attributeNames name of attributes to apply
135 | */
136 | function applyAttributes(element, attrs, attributeNames) {
137 |
138 | forEach(attributeNames, function(property) {
139 | if (attrs[property] !== undefined) {
140 | applyAttribute(element, attrs, property);
141 | }
142 | });
143 | }
144 |
145 | /**
146 | * Apply named property to element and drain it from the attrs
147 | * collection.
148 | *
149 | * @param {Base} element
150 | * @param {Object} attrs (in/out map of attributes)
151 | * @param {String} attributeName to apply
152 | */
153 | function applyAttribute(element, attrs, attributeName) {
154 | element[attributeName] = attrs[attributeName];
155 |
156 | delete attrs[attributeName];
157 | }
--------------------------------------------------------------------------------
/lib/features/modeling/Modeling.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import BaseModeling from 'diagram-js/lib/features/modeling/Modeling';
4 |
5 | import UpdatePropertiesHandler from './cmd/UpdatePropertiesHandler';
6 | import UpdateCanvasRootHandler from './cmd/UpdateCanvasRootHandler';
7 | import IdClaimHandler from './cmd/IdClaimHandler';
8 | import SetColorHandler from './cmd/SetColorHandler';
9 |
10 | import UpdateLabelHandler from '../label-editing/cmd/UpdateLabelHandler';
11 |
12 |
13 | /**
14 | * Postit modeling features activator
15 | *
16 | * @param {EventBus} eventBus
17 | * @param {ElementFactory} elementFactory
18 | * @param {CommandStack} commandStack
19 | * @param {PostitRules} postitRules
20 | */
21 | export default function Modeling(
22 | eventBus, elementFactory, commandStack,
23 | postitRules) {
24 |
25 | BaseModeling.call(this, eventBus, elementFactory, commandStack);
26 |
27 | this._postitRules = postitRules;
28 | }
29 |
30 | inherits(Modeling, BaseModeling);
31 |
32 | Modeling.$inject = [
33 | 'eventBus',
34 | 'elementFactory',
35 | 'commandStack',
36 | 'postitRules'
37 | ];
38 |
39 |
40 | Modeling.prototype.getHandlers = function() {
41 | var handlers = BaseModeling.prototype.getHandlers.call(this);
42 |
43 | handlers['element.updateProperties'] = UpdatePropertiesHandler;
44 | handlers['canvas.updateRoot'] = UpdateCanvasRootHandler;
45 | handlers['id.updateClaim'] = IdClaimHandler;
46 | handlers['element.setColor'] = SetColorHandler;
47 | handlers['element.updateLabel'] = UpdateLabelHandler;
48 |
49 | return handlers;
50 | };
51 |
52 |
53 | Modeling.prototype.updateLabel = function(element, newLabel, newBounds, hints) {
54 | this._commandStack.execute('element.updateLabel', {
55 | element: element,
56 | newLabel: newLabel,
57 | newBounds: newBounds,
58 | hints: hints || {}
59 | });
60 | };
61 |
62 |
63 | Modeling.prototype.updateProperties = function(element, properties) {
64 | this._commandStack.execute('element.updateProperties', {
65 | element: element,
66 | properties: properties
67 | });
68 | };
69 |
70 | Modeling.prototype.claimId = function(id, moddleElement) {
71 | this._commandStack.execute('id.updateClaim', {
72 | id: id,
73 | element: moddleElement,
74 | claiming: true
75 | });
76 | };
77 |
78 |
79 | Modeling.prototype.unclaimId = function(id, moddleElement) {
80 | this._commandStack.execute('id.updateClaim', {
81 | id: id,
82 | element: moddleElement
83 | });
84 | };
85 |
86 | Modeling.prototype.setColor = function(elements, color) {
87 | if (!elements.length) {
88 | elements = [ elements ];
89 | }
90 |
91 | this._commandStack.execute('element.setColor', {
92 | elements: elements,
93 | color: color
94 | });
95 | };
96 |
--------------------------------------------------------------------------------
/lib/features/modeling/PostitFactory.js:
--------------------------------------------------------------------------------
1 | import {
2 | assign,
3 | } from 'min-dash';
4 |
5 | import {
6 | isAny
7 | } from './util/ModelingUtil';
8 |
9 | import {
10 | is
11 | } from '../../util/ModelUtil';
12 |
13 |
14 | export default function PositFactory(moddle) {
15 | this._model = moddle;
16 | }
17 |
18 | PositFactory.$inject = [ 'moddle' ];
19 |
20 |
21 | PositFactory.prototype._needsId = function(element) {
22 | return isAny(element, [
23 | 'postit:BoardElement'
24 | ]);
25 | };
26 |
27 | PositFactory.prototype._ensureId = function(element) {
28 |
29 | // generate semantic ids for elements
30 | // postit:Postit -> Positit_ID
31 | var prefix;
32 |
33 | if (is(element, 'postit:Postit')) {
34 | prefix = 'Postit';
35 | } else {
36 | prefix = (element.$type || '').replace(/^[^:]*:/g, '');
37 | }
38 |
39 | prefix += '_';
40 |
41 | if (!element.id && this._needsId(element)) {
42 | element.id = this._model.ids.nextPrefixed(prefix, element);
43 | }
44 | };
45 |
46 |
47 | PositFactory.prototype.create = function(type, attrs) {
48 | var element = this._model.create(type, attrs || {});
49 |
50 | this._ensureId(element);
51 |
52 | return element;
53 | };
54 |
55 |
56 | PositFactory.prototype.createDiLabel = function() {
57 | return this.create('postitDi:PostitLabel', {
58 | bounds: this.createDiBounds()
59 | });
60 | };
61 |
62 |
63 | PositFactory.prototype.createDiShape = function(semantic, bounds, attrs) {
64 |
65 | return this.create('postitDi:PostitShape', assign({
66 | boardElement: semantic,
67 | bounds: this.createDiBounds(bounds)
68 | }, attrs));
69 | };
70 |
71 |
72 | PositFactory.prototype.createDiBounds = function(bounds) {
73 | return this.create('dc:Bounds', bounds);
74 | };
75 |
76 |
77 | PositFactory.prototype.createDiPlane = function(semantic) {
78 | return this.create('postitDi:PostitPlane', {
79 | boardElement: semantic
80 | });
81 | };
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/AppendBehavior.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
4 |
5 |
6 | export default function AppendBehavior(eventBus) {
7 |
8 | CommandInterceptor.call(this, eventBus);
9 |
10 | // assign correct shape position unless already set
11 |
12 | this.preExecute('shape.append', function(context) {
13 |
14 | var source = context.source,
15 | shape = context.shape;
16 |
17 | if (!context.position) {
18 |
19 | context.position = {
20 | x: source.x + source.width + 80 + shape.width / 2,
21 | y: source.y + source.height / 2
22 | };
23 |
24 | }
25 | }, true);
26 | }
27 |
28 | inherits(AppendBehavior, CommandInterceptor);
29 |
30 | AppendBehavior.$inject = [
31 | 'eventBus'
32 | ];
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/CreateBoardElementBehavior.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import {
4 | assign
5 | } from 'min-dash';
6 |
7 | import {
8 | getBusinessObject,
9 | is
10 | } from '../../../util/ModelUtil';
11 |
12 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
13 |
14 |
15 | export default function CreateBoardElementBehavior(eventBus) {
16 |
17 | CommandInterceptor.call(this, eventBus);
18 |
19 | // ensure properties were set in business object
20 |
21 | this.execute('shape.create', function(context) {
22 |
23 | var shape = context.context.shape;
24 |
25 | if (is(shape, 'postit:Postit')) {
26 | const businessObject = getBusinessObject(shape);
27 | !businessObject.color && assign(businessObject, { color: shape.color });
28 | }
29 |
30 | if (is(shape, 'postit:Image')) {
31 | const businessObject = getBusinessObject(shape);
32 | !businessObject.source && assign(businessObject, { source: shape.source });
33 | }
34 | });
35 | }
36 |
37 | inherits(CreateBoardElementBehavior, CommandInterceptor);
38 |
39 | CreateBoardElementBehavior.$inject = [
40 | 'eventBus'
41 | ];
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/EmptyTextBoxBehavior.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import {
4 | is,
5 | getBusinessObject
6 | } from '../../../util/ModelUtil';
7 |
8 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
9 |
10 |
11 | export default function EmptyTextBoxBehavior(eventBus, modeling, directEditing) {
12 |
13 | CommandInterceptor.call(this, eventBus);
14 |
15 | // delete text box if it has no text
16 | this.postExecute('element.updateLabel', function(context) {
17 |
18 | var element = context.element,
19 | newLabel = context.newLabel;
20 |
21 | if (is(element, 'postit:TextBox') && isEmpty(newLabel)) {
22 | modeling.removeElements([ element ]);
23 | }
24 | }, true);
25 |
26 | eventBus.on('directEditing.cancel', 1001, function(event) {
27 | var active = event.active,
28 | element = active.element;
29 |
30 | if (is(element, 'postit:TextBox') && isEmpty(getBusinessObject(element).name)) {
31 | directEditing._active = false;
32 | modeling.removeElements([ element ]);
33 | }
34 | });
35 | }
36 |
37 | inherits(EmptyTextBoxBehavior, CommandInterceptor);
38 |
39 | EmptyTextBoxBehavior.$inject = [
40 | 'eventBus',
41 | 'modeling',
42 | 'directEditing'
43 | ];
44 |
45 |
46 | // helpers //////////
47 |
48 | function isEmpty(label) {
49 | return !label || label === '';
50 | }
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/FixHoverBehavior.js:
--------------------------------------------------------------------------------
1 | import { is } from '../../../util/ModelUtil';
2 |
3 | var HIGH_PRIORITY = 1500;
4 |
5 |
6 | /**
7 | * Correct hover targets in certain situations to improve diagram interaction.
8 | *
9 | * @param {ElementRegistry} elementRegistry
10 | * @param {EventBus} eventBus
11 | * @param {Canvas} canvas
12 | */
13 | export default function FixHoverBehavior(elementRegistry, eventBus, canvas) {
14 |
15 | eventBus.on([
16 | 'create.hover',
17 | 'create.move',
18 | 'create.end',
19 | 'shape.move.hover',
20 | 'shape.move.move',
21 | 'shape.move.end'
22 | ], HIGH_PRIORITY, function(event) {
23 | var context = event.context,
24 | shape = context.shape || event.shape,
25 | hover = event.hover;
26 |
27 | var rootElement = canvas.getRootElement();
28 |
29 | // ensure group & label elements are dropped always onto the root
30 | if (hover !== rootElement && (shape.labelTarget || is(shape, 'postit:Group'))) {
31 | event.hover = rootElement;
32 | event.hoverGfx = elementRegistry.getGraphics(event.hover);
33 | }
34 | });
35 |
36 | }
37 |
38 | FixHoverBehavior.$inject = [
39 | 'elementRegistry',
40 | 'eventBus',
41 | 'canvas'
42 | ];
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/ImportDockingFix.js:
--------------------------------------------------------------------------------
1 | import {
2 | getMid
3 | } from 'diagram-js/lib/layout/LayoutUtil';
4 |
5 | import lineIntersect from './util/LineIntersect';
6 |
7 |
8 | /**
9 | * Fix broken dockings after DI imports.
10 | *
11 | * @param {EventBus} eventBus
12 | */
13 | export default function ImportDockingFix(eventBus) {
14 |
15 | function adjustDocking(startPoint, nextPoint, elementMid) {
16 |
17 | var elementTop = {
18 | x: elementMid.x,
19 | y: elementMid.y - 50
20 | };
21 |
22 | var elementLeft = {
23 | x: elementMid.x - 50,
24 | y: elementMid.y
25 | };
26 |
27 | var verticalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementTop),
28 | horizontalIntersect = lineIntersect(startPoint, nextPoint, elementMid, elementLeft);
29 |
30 | // original is horizontal or vertical center cross intersection
31 | var centerIntersect;
32 |
33 | if (verticalIntersect && horizontalIntersect) {
34 | if (getDistance(verticalIntersect, elementMid) > getDistance(horizontalIntersect, elementMid)) {
35 | centerIntersect = horizontalIntersect;
36 | } else {
37 | centerIntersect = verticalIntersect;
38 | }
39 | } else {
40 | centerIntersect = verticalIntersect || horizontalIntersect;
41 | }
42 |
43 | startPoint.original = centerIntersect;
44 | }
45 |
46 | function fixDockings(connection) {
47 | var waypoints = connection.waypoints;
48 |
49 | adjustDocking(
50 | waypoints[0],
51 | waypoints[1],
52 | getMid(connection.source)
53 | );
54 |
55 | adjustDocking(
56 | waypoints[waypoints.length - 1],
57 | waypoints[waypoints.length - 2],
58 | getMid(connection.target)
59 | );
60 | }
61 |
62 | eventBus.on('boardElement.added', function(e) {
63 |
64 | var element = e.element;
65 |
66 | if (element.waypoints) {
67 | fixDockings(element);
68 | }
69 | });
70 | }
71 |
72 | ImportDockingFix.$inject = [
73 | 'eventBus'
74 | ];
75 |
76 |
77 | // helpers //////////////////////
78 |
79 | function getDistance(p1, p2) {
80 | return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
81 | }
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/ReplaceElementBehaviour.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import { forEach } from 'min-dash';
4 |
5 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
6 |
7 |
8 | /**
9 | * postit-specific replace behavior.
10 | */
11 | export default function ReplaceElementBehaviour(
12 | postitReplace,
13 | postitRules,
14 | elementRegistry,
15 | injector,
16 | modeling,
17 | selection
18 | ) {
19 | injector.invoke(CommandInterceptor, this);
20 |
21 | this._postitReplace = postitReplace;
22 | this._elementRegistry = elementRegistry;
23 | this._selection = selection;
24 |
25 | // replace elements on move
26 | this.postExecuted([ 'elements.move' ], 500, function(event) {
27 | var context = event.context,
28 | target = context.newParent,
29 | newHost = context.newHost,
30 | elements = [];
31 |
32 | forEach(context.closure.topLevel, function(topLevelElements) {
33 | elements = elements.concat(topLevelElements);
34 | });
35 |
36 | // set target to host if attaching
37 | if (elements.length === 1 && newHost) {
38 | target = newHost;
39 | }
40 |
41 | var canReplace = postitRules.canReplace(elements, target);
42 |
43 | if (canReplace) {
44 | this.replaceElements(elements, canReplace.replacements, newHost);
45 | }
46 | }, this);
47 |
48 | // update attachments on host replace
49 | this.postExecute([ 'shape.replace' ], 1500, function(e) {
50 | var context = e.context,
51 | oldShape = context.oldShape,
52 | newShape = context.newShape,
53 | attachers = oldShape.attachers,
54 | canReplace;
55 |
56 | if (attachers && attachers.length) {
57 | canReplace = postitRules.canReplace(attachers, newShape);
58 |
59 | this.replaceElements(attachers, canReplace.replacements);
60 | }
61 |
62 | }, this);
63 |
64 | // keep ID on shape replace
65 | this.postExecuted([ 'shape.replace' ], 1500, function(e) {
66 | var context = e.context,
67 | oldShape = context.oldShape,
68 | newShape = context.newShape;
69 |
70 | modeling.unclaimId(oldShape.businessObject.id, oldShape.businessObject);
71 | modeling.updateProperties(newShape, { id: oldShape.id });
72 | });
73 | }
74 |
75 | inherits(ReplaceElementBehaviour, CommandInterceptor);
76 |
77 | ReplaceElementBehaviour.prototype.replaceElements = function(elements, newElements) {
78 | var elementRegistry = this._elementRegistry,
79 | postitReplace = this._postitReplace,
80 | selection = this._selection;
81 |
82 | forEach(newElements, function(replacement) {
83 | var newElement = {
84 | type: replacement.newElementType
85 | };
86 |
87 | var oldElement = elementRegistry.get(replacement.oldElementId);
88 |
89 | var idx = elements.indexOf(oldElement);
90 |
91 | elements[idx] = postitReplace.replaceElement(oldElement, newElement, { select: false });
92 | });
93 |
94 | if (newElements) {
95 | selection.select(elements);
96 | }
97 | };
98 |
99 | ReplaceElementBehaviour.$inject = [
100 | 'postitReplace',
101 | 'postitRules',
102 | 'elementRegistry',
103 | 'injector',
104 | 'modeling',
105 | 'selection'
106 | ];
107 |
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/UnclaimIdBehavior.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
4 |
5 | import { isLabel } from '../../../util/LabelUtil';
6 |
7 |
8 | /**
9 | * Unclaims model IDs on element deletion.
10 | *
11 | * @param {Canvas} canvas
12 | * @param {Injector} injector
13 | * @param {Moddle} moddle
14 | * @param {Modeling} modeling
15 | */
16 | export default function UnclaimIdBehavior(canvas, injector, moddle, modeling) {
17 | injector.invoke(CommandInterceptor, this);
18 |
19 | this.preExecute('shape.delete', function(event) {
20 | var context = event.context,
21 | shape = context.shape,
22 | shapeBo = shape.businessObject;
23 |
24 | if (isLabel(shape)) {
25 | return;
26 | }
27 |
28 | modeling.unclaimId(shapeBo.id, shapeBo);
29 | });
30 |
31 | this.preExecute('canvas.updateRoot', function() {
32 | var rootElement = canvas.getRootElement(),
33 | rootElementBo = rootElement.businessObject;
34 |
35 | moddle.ids.unclaim(rootElementBo.id);
36 | });
37 | }
38 |
39 | inherits(UnclaimIdBehavior, CommandInterceptor);
40 |
41 | UnclaimIdBehavior.$inject = [ 'canvas', 'injector', 'moddle', 'modeling' ];
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/index.js:
--------------------------------------------------------------------------------
1 | import AdaptiveLabelPositioningBehavior from './AdaptiveLabelPositioningBehavior';
2 | import AppendBehavior from './AppendBehavior';
3 | import FixHoverBehavior from './FixHoverBehavior';
4 | import ImportDockingFix from './ImportDockingFix';
5 | import LabelBehavior from './LabelBehavior';
6 | import ReplaceElementBehaviour from './ReplaceElementBehaviour';
7 | import UnclaimIdBehavior from './UnclaimIdBehavior';
8 | import CreateBoardElementBehavior from './CreateBoardElementBehavior';
9 | import EmptyTextBoxBehavior from './EmptyTextBoxBehavior';
10 |
11 | export default {
12 | __init__: [
13 | 'adaptiveLabelPositioningBehavior',
14 | 'appendBehavior',
15 | 'fixHoverBehavior',
16 | 'importDockingFix',
17 | 'labelBehavior',
18 | 'replaceElementBehaviour',
19 | 'unclaimIdBehavior',
20 | 'createBoardElementBehavior',
21 | 'emptyTextBoxBehavior'
22 | ],
23 | adaptiveLabelPositioningBehavior: [ 'type', AdaptiveLabelPositioningBehavior ],
24 | appendBehavior: [ 'type', AppendBehavior ],
25 | fixHoverBehavior: [ 'type', FixHoverBehavior ],
26 | importDockingFix: [ 'type', ImportDockingFix ],
27 | labelBehavior: [ 'type', LabelBehavior ],
28 | replaceElementBehaviour: [ 'type', ReplaceElementBehaviour ],
29 | unclaimIdBehavior: [ 'type', UnclaimIdBehavior ],
30 | createBoardElementBehavior: [ 'type', CreateBoardElementBehavior ],
31 | emptyTextBoxBehavior: [ 'type', EmptyTextBoxBehavior ]
32 | };
33 |
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/util/GeometricUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the length of a vector
3 | *
4 | * @param {Vector}
5 | * @return {Float}
6 | */
7 | export function vectorLength(v) {
8 | return Math.sqrt(Math.pow(v.x, 2) + Math.pow(v.y, 2));
9 | }
10 |
11 |
12 | /**
13 | * Calculates the angle between a line a the yAxis
14 | *
15 | * @param {Array}
16 | * @return {Float}
17 | */
18 | export function getAngle(line) {
19 |
20 | // return value is between 0, 180 and -180, -0
21 | // @janstuemmel: maybe replace return a/b with b/a
22 | return Math.atan((line[1].y - line[0].y) / (line[1].x - line[0].x));
23 | }
24 |
25 |
26 | /**
27 | * Rotates a vector by a given angle
28 | *
29 | * @param {Vector}
30 | * @param {Float} Angle in radians
31 | * @return {Vector}
32 | */
33 | export function rotateVector(vector, angle) {
34 | return (!angle) ? vector : {
35 | x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
36 | y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
37 | };
38 | }
39 |
40 |
41 | /**
42 | * Solves a 2D equation system
43 | * a + r*b = c, where a,b,c are 2D vectors
44 | *
45 | * @param {Vector}
46 | * @param {Vector}
47 | * @param {Vector}
48 | * @return {Float}
49 | */
50 | function solveLambaSystem(a, b, c) {
51 |
52 | // the 2d system
53 | var system = [
54 | { n: a[0] - c[0], lambda: b[0] },
55 | { n: a[1] - c[1], lambda: b[1] }
56 | ];
57 |
58 | // solve
59 | var n = system[0].n * b[0] + system[1].n * b[1],
60 | l = system[0].lambda * b[0] + system[1].lambda * b[1];
61 |
62 | return -n / l;
63 | }
64 |
65 |
66 | /**
67 | * Position of perpendicular foot
68 | *
69 | * @param {Point}
70 | * @param [ {Point}, {Point} ] line defined through two points
71 | * @return {Point} the perpendicular foot position
72 | */
73 | export function perpendicularFoot(point, line) {
74 |
75 | var a = line[0], b = line[1];
76 |
77 | // relative position of b from a
78 | var bd = { x: b.x - a.x, y: b.y - a.y };
79 |
80 | // solve equation system to the parametrized vectors param real value
81 | var r = solveLambaSystem([ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ]);
82 |
83 | return { x: a.x + r * bd.x, y: a.y + r * bd.y };
84 | }
85 |
86 |
87 | /**
88 | * Calculates the distance between a point and a line
89 | *
90 | * @param {Point}
91 | * @param [ {Point}, {Point} ] line defined through two points
92 | * @return {Float} distance
93 | */
94 | export function getDistancePointLine(point, line) {
95 |
96 | var pfPoint = perpendicularFoot(point, line);
97 |
98 | // distance vector
99 | var connectionVector = {
100 | x: pfPoint.x - point.x,
101 | y: pfPoint.y - point.y
102 | };
103 |
104 | return vectorLength(connectionVector);
105 | }
106 |
107 |
108 | /**
109 | * Calculates the distance between two points
110 | *
111 | * @param {Point}
112 | * @param {Point}
113 | * @return {Float} distance
114 | */
115 | export function getDistancePointPoint(point1, point2) {
116 |
117 | return vectorLength({
118 | x: point1.x - point2.x,
119 | y: point1.y - point2.y
120 | });
121 | }
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/util/LineAttachmentUtil.js:
--------------------------------------------------------------------------------
1 | var sqrt = Math.sqrt,
2 | min = Math.min,
3 | max = Math.max,
4 | abs = Math.abs;
5 |
6 | /**
7 | * Calculate the square (power to two) of a number.
8 | *
9 | * @param {Number} n
10 | *
11 | * @return {Number}
12 | */
13 | function sq(n) {
14 | return Math.pow(n, 2);
15 | }
16 |
17 | /**
18 | * Get distance between two points.
19 | *
20 | * @param {Point} p1
21 | * @param {Point} p2
22 | *
23 | * @return {Number}
24 | */
25 | function getDistance(p1, p2) {
26 | return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
27 | }
28 |
29 | /**
30 | * Return the attachment of the given point on the specified line.
31 | *
32 | * The attachment is either a bendpoint (attached to the given point)
33 | * or segment (attached to a location on a line segment) attachment:
34 | *
35 | * ```javascript
36 | * var pointAttachment = {
37 | * type: 'bendpoint',
38 | * bendpointIndex: 3,
39 | * position: { x: 10, y: 10 } // the attach point on the line
40 | * };
41 | *
42 | * var segmentAttachment = {
43 | * type: 'segment',
44 | * segmentIndex: 2,
45 | * relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
46 | * position: { x: 10, y: 10 } // the attach point on the line
47 | * };
48 | * ```
49 | *
50 | * @param {Point} point
51 | * @param {Array} line
52 | *
53 | * @return {Object} attachment
54 | */
55 | export function getAttachment(point, line) {
56 |
57 | var idx = 0,
58 | segmentStart,
59 | segmentEnd,
60 | segmentStartDistance,
61 | segmentEndDistance,
62 | attachmentPosition,
63 | minDistance,
64 | intersections,
65 | attachment,
66 | attachmentDistance,
67 | closestAttachmentDistance,
68 | closestAttachment;
69 |
70 | for (idx = 0; idx < line.length - 1; idx++) {
71 |
72 | segmentStart = line[idx];
73 | segmentEnd = line[idx + 1];
74 |
75 | if (pointsEqual(segmentStart, segmentEnd)) {
76 | intersections = [ segmentStart ];
77 | } else {
78 | segmentStartDistance = getDistance(point, segmentStart);
79 | segmentEndDistance = getDistance(point, segmentEnd);
80 |
81 | minDistance = min(segmentStartDistance, segmentEndDistance);
82 |
83 | intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
84 | }
85 |
86 | if (intersections.length < 1) {
87 | throw new Error('expected between [1, 2] circle -> line intersections');
88 | }
89 |
90 | // one intersection -> bendpoint attachment
91 | if (intersections.length === 1) {
92 | attachment = {
93 | type: 'bendpoint',
94 | position: intersections[0],
95 | segmentIndex: idx,
96 | bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
97 | };
98 | }
99 |
100 | // two intersections -> segment attachment
101 | if (intersections.length === 2) {
102 |
103 | attachmentPosition = mid(intersections[0], intersections[1]);
104 |
105 | attachment = {
106 | type: 'segment',
107 | position: attachmentPosition,
108 | segmentIndex: idx,
109 | relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
110 | };
111 | }
112 |
113 | attachmentDistance = getDistance(attachment.position, point);
114 |
115 | if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
116 | closestAttachment = attachment;
117 | closestAttachmentDistance = attachmentDistance;
118 | }
119 | }
120 |
121 | return closestAttachment;
122 | }
123 |
124 | /**
125 | * Gets the intersection between a circle and a line segment.
126 | *
127 | * @param {Point} s1 segment start
128 | * @param {Point} s2 segment end
129 | * @param {Point} cc circle center
130 | * @param {Number} cr circle radius
131 | *
132 | * @return {Array} intersections
133 | */
134 | function getCircleSegmentIntersections(s1, s2, cc, cr) {
135 |
136 | var baX = s2.x - s1.x;
137 | var baY = s2.y - s1.y;
138 | var caX = cc.x - s1.x;
139 | var caY = cc.y - s1.y;
140 |
141 | var a = baX * baX + baY * baY;
142 | var bBy2 = baX * caX + baY * caY;
143 | var c = caX * caX + caY * caY - cr * cr;
144 |
145 | var pBy2 = bBy2 / a;
146 | var q = c / a;
147 |
148 | var disc = pBy2 * pBy2 - q;
149 |
150 | // check against negative value to work around
151 | // negative, very close to zero results (-4e-15)
152 | // being produced in some environments
153 | if (disc < 0 && disc > -0.000001) {
154 | disc = 0;
155 | }
156 |
157 | if (disc < 0) {
158 | return [];
159 | }
160 |
161 | // if disc == 0 ... dealt with later
162 | var tmpSqrt = sqrt(disc);
163 | var abScalingFactor1 = -pBy2 + tmpSqrt;
164 | var abScalingFactor2 = -pBy2 - tmpSqrt;
165 |
166 | var i1 = {
167 | x: s1.x - baX * abScalingFactor1,
168 | y: s1.y - baY * abScalingFactor1
169 | };
170 |
171 | if (disc === 0) { // abScalingFactor1 == abScalingFactor2
172 | return [ i1 ];
173 | }
174 |
175 | var i2 = {
176 | x: s1.x - baX * abScalingFactor2,
177 | y: s1.y - baY * abScalingFactor2
178 | };
179 |
180 | // return only points on line segment
181 | return [ i1, i2 ].filter(function(p) {
182 | return isPointInSegment(p, s1, s2);
183 | });
184 | }
185 |
186 |
187 | function isPointInSegment(p, segmentStart, segmentEnd) {
188 | return (
189 | fenced(p.x, segmentStart.x, segmentEnd.x) &&
190 | fenced(p.y, segmentStart.y, segmentEnd.y)
191 | );
192 | }
193 |
194 | function fenced(n, rangeStart, rangeEnd) {
195 |
196 | // use matching threshold to work around
197 | // precision errors in intersection computation
198 |
199 | return (
200 | n >= min(rangeStart, rangeEnd) - EQUAL_THRESHOLD &&
201 | n <= max(rangeStart, rangeEnd) + EQUAL_THRESHOLD
202 | );
203 | }
204 |
205 | /**
206 | * Calculate mid of two points.
207 | *
208 | * @param {Point} p1
209 | * @param {Point} p2
210 | *
211 | * @return {Point}
212 | */
213 | function mid(p1, p2) {
214 |
215 | return {
216 | x: (p1.x + p2.x) / 2,
217 | y: (p1.y + p2.y) / 2
218 | };
219 | }
220 |
221 | var EQUAL_THRESHOLD = 0.1;
222 |
223 | function pointsEqual(p1, p2) {
224 |
225 | return (
226 | abs(p1.x - p2.x) <= EQUAL_THRESHOLD &&
227 | abs(p1.y - p2.y) <= EQUAL_THRESHOLD
228 | );
229 | }
230 |
--------------------------------------------------------------------------------
/lib/features/modeling/behavior/util/LineIntersect.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Returns the intersection between two line segments a and b.
3 | *
4 | * @param {Point} l1s
5 | * @param {Point} l1e
6 | * @param {Point} l2s
7 | * @param {Point} l2e
8 | *
9 | * @return {Point}
10 | */
11 | export default function lineIntersect(l1s, l1e, l2s, l2e) {
12 |
13 | // if the lines intersect, the result contains the x and y of the
14 | // intersection (treating the lines as infinite) and booleans for
15 | // whether line segment 1 or line segment 2 contain the point
16 | var denominator, a, b, c, numerator;
17 |
18 | denominator = ((l2e.y - l2s.y) * (l1e.x - l1s.x)) - ((l2e.x - l2s.x) * (l1e.y - l1s.y));
19 |
20 | if (denominator == 0) {
21 | return null;
22 | }
23 |
24 | a = l1s.y - l2s.y;
25 | b = l1s.x - l2s.x;
26 | numerator = ((l2e.x - l2s.x) * a) - ((l2e.y - l2s.y) * b);
27 |
28 | c = numerator / denominator;
29 |
30 | // if we cast these lines infinitely in
31 | // both directions, they intersect here
32 | return {
33 | x: Math.round(l1s.x + (c * (l1e.x - l1s.x))),
34 | y: Math.round(l1s.y + (c * (l1e.y - l1s.y)))
35 | };
36 | }
--------------------------------------------------------------------------------
/lib/features/modeling/cmd/IdClaimHandler.js:
--------------------------------------------------------------------------------
1 | export default function IdClaimHandler(moddle) {
2 | this._moddle = moddle;
3 | }
4 |
5 | IdClaimHandler.$inject = [ 'moddle' ];
6 |
7 |
8 | IdClaimHandler.prototype.execute = function(context) {
9 | var ids = this._moddle.ids,
10 | id = context.id,
11 | element = context.element,
12 | claiming = context.claiming;
13 |
14 | if (claiming) {
15 | ids.claim(id, element);
16 | } else {
17 | ids.unclaim(id);
18 | }
19 | };
20 |
21 | /**
22 | * Command revert implementation.
23 | */
24 | IdClaimHandler.prototype.revert = function(context) {
25 | var ids = this._moddle.ids,
26 | id = context.id,
27 | element = context.element,
28 | claiming = context.claiming;
29 |
30 | if (claiming) {
31 | ids.unclaim(id);
32 | } else {
33 | ids.claim(id, element);
34 | }
35 | };
36 |
37 |
--------------------------------------------------------------------------------
/lib/features/modeling/cmd/SetColorHandler.js:
--------------------------------------------------------------------------------
1 | import {
2 | forEach
3 | } from 'min-dash';
4 |
5 | import COLOR from '../../../util/ColorUtil';
6 |
7 |
8 | var DEFAULT_COLOR = COLOR.GREEN;
9 |
10 |
11 | export default function SetColorHandler(commandStack) {
12 | this._commandStack = commandStack;
13 | }
14 |
15 | SetColorHandler.$inject = [
16 | 'commandStack'
17 | ];
18 |
19 |
20 | SetColorHandler.prototype.postExecute = function(context) {
21 | var elements = context.elements,
22 | color = context.color || DEFAULT_COLOR;
23 |
24 | var self = this;
25 |
26 | forEach(elements, function(element) {
27 |
28 | self._commandStack.execute('element.updateProperties', {
29 | element: element,
30 | properties: {
31 | color: color
32 | }
33 | });
34 | });
35 |
36 | };
--------------------------------------------------------------------------------
/lib/features/modeling/cmd/UpdateCanvasRootHandler.js:
--------------------------------------------------------------------------------
1 | import {
2 | add as collectionAdd,
3 | remove as collectionRemove
4 | } from 'diagram-js/lib/util/Collections';
5 |
6 |
7 | export default function UpdateCanvasRootHandler(canvas, modeling) {
8 | this._canvas = canvas;
9 | this._modeling = modeling;
10 | }
11 |
12 | UpdateCanvasRootHandler.$inject = [
13 | 'canvas',
14 | 'modeling'
15 | ];
16 |
17 |
18 | UpdateCanvasRootHandler.prototype.execute = function(context) {
19 |
20 | var canvas = this._canvas;
21 |
22 | var newRoot = context.newRoot,
23 | newRootBusinessObject = newRoot.businessObject,
24 | oldRoot = canvas.getRootElement(),
25 | oldRootBusinessObject = oldRoot.businessObject,
26 | postitDefinitions = oldRootBusinessObject.$parent,
27 | diPlane = oldRootBusinessObject.di;
28 |
29 | // (1) replace process old <> new root
30 | canvas.setRootElement(newRoot, true);
31 |
32 | // (2) update root elements
33 | collectionAdd(postitDefinitions.rootElements, newRootBusinessObject);
34 | newRootBusinessObject.$parent = postitDefinitions;
35 |
36 | collectionRemove(postitDefinitions.rootElements, oldRootBusinessObject);
37 | oldRootBusinessObject.$parent = null;
38 |
39 | // (3) wire di
40 | oldRootBusinessObject.di = null;
41 |
42 | diPlane.boardElement = newRootBusinessObject;
43 | newRootBusinessObject.di = diPlane;
44 |
45 | context.oldRoot = oldRoot;
46 |
47 | // TODO(nikku): return changed elements?
48 | // return [ newRoot, oldRoot ];
49 | };
50 |
51 |
52 | UpdateCanvasRootHandler.prototype.revert = function(context) {
53 |
54 | var canvas = this._canvas;
55 |
56 | var newRoot = context.newRoot,
57 | newRootBusinessObject = newRoot.businessObject,
58 | oldRoot = context.oldRoot,
59 | oldRootBusinessObject = oldRoot.businessObject,
60 | postitDefinitions = newRootBusinessObject.$parent,
61 | diPlane = newRootBusinessObject.di;
62 |
63 | // (1) replace process old <> new root
64 | canvas.setRootElement(oldRoot, true);
65 |
66 | // (2) update root elements
67 | collectionRemove(postitDefinitions.rootElements, newRootBusinessObject);
68 | newRootBusinessObject.$parent = null;
69 |
70 | collectionAdd(postitDefinitions.rootElements, oldRootBusinessObject);
71 | oldRootBusinessObject.$parent = postitDefinitions;
72 |
73 | // (3) wire di
74 | newRootBusinessObject.di = null;
75 |
76 | diPlane.boardElement = oldRootBusinessObject;
77 | oldRootBusinessObject.di = diPlane;
78 |
79 | // TODO(nikku): return changed elements?
80 | // return [ newRoot, oldRoot ];
81 | };
--------------------------------------------------------------------------------
/lib/features/modeling/cmd/UpdatePropertiesHandler.js:
--------------------------------------------------------------------------------
1 | import {
2 | reduce,
3 | keys,
4 | forEach,
5 | assign
6 | } from 'min-dash';
7 |
8 | import {
9 | getBusinessObject
10 | } from '../../../util/ModelUtil';
11 |
12 | var ID = 'id',
13 | DI = 'di';
14 |
15 | var NULL_DIMENSIONS = {
16 | width: 0,
17 | height: 0
18 | };
19 |
20 | /**
21 | * A handler that implements a postit elements property update.
22 | *
23 | * This should be used to set simple properties on elements with
24 | * an underlying XML business object.
25 | *
26 | * Use respective diagram-js provided handlers if you would
27 | * like to perform automated modeling.
28 | */
29 | export default function UpdatePropertiesHandler(
30 | elementRegistry, moddle, translate,
31 | modeling, textRenderer) {
32 |
33 | this._elementRegistry = elementRegistry;
34 | this._moddle = moddle;
35 | this._translate = translate;
36 | this._modeling = modeling;
37 | this._textRenderer = textRenderer;
38 | }
39 |
40 | UpdatePropertiesHandler.$inject = [
41 | 'elementRegistry',
42 | 'moddle',
43 | 'translate',
44 | 'modeling',
45 | 'textRenderer'
46 | ];
47 |
48 |
49 | // api //////////////////////
50 |
51 | /**
52 | * Updates a board element with a list of new properties
53 | *
54 | * @param {Object} context
55 | * @param {djs.model.Base} context.element the element to update
56 | * @param {Object} context.properties a list of properties to set on the element's
57 | * businessObject (the XML model element)
58 | *
59 | * @return {Array} the updated element
60 | */
61 | UpdatePropertiesHandler.prototype.execute = function(context) {
62 |
63 | var element = context.element,
64 | changed = [ element ],
65 | translate = this._translate;
66 |
67 | if (!element) {
68 | throw new Error(translate('element required'));
69 | }
70 |
71 | var elementRegistry = this._elementRegistry,
72 | ids = this._moddle.ids;
73 |
74 | var businessObject = element.businessObject,
75 | properties = unwrapBusinessObjects(context.properties),
76 | oldProperties = context.oldProperties || getProperties(businessObject, properties);
77 |
78 | if (isIdChange(properties, businessObject)) {
79 | ids.unclaim(businessObject[ID]);
80 |
81 | elementRegistry.updateId(element, properties[ID]);
82 |
83 | ids.claim(properties[ID], businessObject);
84 | }
85 |
86 | // update properties
87 | setProperties(businessObject, properties);
88 |
89 | // store old values
90 | context.oldProperties = oldProperties;
91 | context.changed = changed;
92 |
93 | // indicate changed on objects affected by the update
94 | return changed;
95 | };
96 |
97 |
98 | UpdatePropertiesHandler.prototype.postExecute = function(context) {
99 | var element = context.element,
100 | label = element.label;
101 |
102 | var text = label && getBusinessObject(label).name;
103 |
104 | if (!text) {
105 | return;
106 | }
107 |
108 | // get layouted text bounds and resize external
109 | // external label accordingly
110 | var newLabelBounds = this._textRenderer.getExternalLabelBounds(label, text);
111 |
112 | this._modeling.resizeShape(label, newLabelBounds, NULL_DIMENSIONS);
113 | };
114 |
115 | /**
116 | * Reverts the update on a board elements properties.
117 | *
118 | * @param {Object} context
119 | *
120 | * @return {djs.model.Base} the updated element
121 | */
122 | UpdatePropertiesHandler.prototype.revert = function(context) {
123 |
124 | var element = context.element,
125 | properties = context.properties,
126 | oldProperties = context.oldProperties,
127 | businessObject = element.businessObject,
128 | elementRegistry = this._elementRegistry,
129 | ids = this._moddle.ids;
130 |
131 | // update properties
132 | setProperties(businessObject, oldProperties);
133 |
134 | if (isIdChange(properties, businessObject)) {
135 | ids.unclaim(properties[ID]);
136 |
137 | elementRegistry.updateId(element, oldProperties[ID]);
138 |
139 | ids.claim(oldProperties[ID], businessObject);
140 | }
141 |
142 | return context.changed;
143 | };
144 |
145 |
146 | function isIdChange(properties, businessObject) {
147 | return ID in properties && properties[ID] !== businessObject[ID];
148 | }
149 |
150 |
151 | function getProperties(businessObject, properties) {
152 | var propertyNames = keys(properties);
153 |
154 | return reduce(propertyNames, function(result, key) {
155 |
156 | // handle DI separately
157 | if (key !== DI) {
158 | result[key] = businessObject.get(key);
159 | } else {
160 | result[key] = getDiProperties(businessObject.di, keys(properties.di));
161 | }
162 |
163 | return result;
164 | }, {});
165 | }
166 |
167 |
168 | function getDiProperties(di, propertyNames) {
169 | return reduce(propertyNames, function(result, key) {
170 | result[key] = di.get(key);
171 |
172 | return result;
173 | }, {});
174 | }
175 |
176 |
177 | function setProperties(businessObject, properties) {
178 | forEach(properties, function(value, key) {
179 |
180 | if (key !== DI) {
181 | businessObject.set(key, value);
182 | } else {
183 |
184 | // only update, if businessObject.di exists
185 | if (businessObject.di) {
186 | setDiProperties(businessObject.di, value);
187 | }
188 | }
189 | });
190 | }
191 |
192 |
193 | function setDiProperties(di, properties) {
194 | forEach(properties, function(value, key) {
195 | di.set(key, value);
196 | });
197 | }
198 |
199 |
200 | var referencePropertyNames = [ 'default' ];
201 |
202 | /**
203 | * Make sure we unwrap the actual business object
204 | * behind diagram element that may have been
205 | * passed as arguments.
206 | *
207 | * @param {Object} properties
208 | *
209 | * @return {Object} unwrappedProps
210 | */
211 | function unwrapBusinessObjects(properties) {
212 |
213 | var unwrappedProps = assign({}, properties);
214 |
215 | referencePropertyNames.forEach(function(name) {
216 | if (name in properties) {
217 | unwrappedProps[name] = getBusinessObject(unwrappedProps[name]);
218 | }
219 | });
220 |
221 | return unwrappedProps;
222 | }
--------------------------------------------------------------------------------
/lib/features/modeling/cmd/UpdateSemanticParentHandler.js:
--------------------------------------------------------------------------------
1 | export default function UpdateSemanticParentHandler(postitUpdater) {
2 | this._postitUpdater = postitUpdater;
3 | }
4 |
5 | UpdateSemanticParentHandler.$inject = [ 'postitUpdater' ];
6 |
7 |
8 | UpdateSemanticParentHandler.prototype.execute = function(context) {
9 | var dataStoreBo = context.dataStoreBo,
10 | newSemanticParent = context.newSemanticParent,
11 | newDiParent = context.newDiParent;
12 |
13 | context.oldSemanticParent = dataStoreBo.$parent;
14 | context.oldDiParent = dataStoreBo.di.$parent;
15 |
16 | // update semantic parent
17 | this._postitUpdater.updateSemanticParent(dataStoreBo, newSemanticParent);
18 |
19 | // update DI parent
20 | this._postitUpdater.updateDiParent(dataStoreBo.di, newDiParent);
21 | };
22 |
23 | UpdateSemanticParentHandler.prototype.revert = function(context) {
24 | var dataStoreBo = context.dataStoreBo,
25 | oldSemanticParent = context.oldSemanticParent,
26 | oldDiParent = context.oldDiParent;
27 |
28 | // update semantic parent
29 | this._postitUpdater.updateSemanticParent(dataStoreBo, oldSemanticParent);
30 |
31 | // update DI parent
32 | this._postitUpdater.updateDiParent(dataStoreBo.di, oldDiParent);
33 | };
34 |
35 |
--------------------------------------------------------------------------------
/lib/features/modeling/index.js:
--------------------------------------------------------------------------------
1 | import BehaviorModule from './behavior';
2 | import RulesModule from '../rules';
3 | import DiOrderingModule from '../di-ordering';
4 | import OrderingModule from '../ordering';
5 | import ReplaceModule from '../replace';
6 |
7 | import CommandModule from 'diagram-js/lib/command';
8 | import TooltipsModule from 'diagram-js/lib/features/tooltips';
9 | import LabelSupportModule from 'diagram-js/lib/features/label-support';
10 | import AttachSupportModule from 'diagram-js/lib/features/attach-support';
11 | import SelectionModule from 'diagram-js/lib/features/selection';
12 | import ChangeSupportModule from 'diagram-js/lib/features/change-support';
13 | import SpaceToolModule from 'diagram-js/lib/features/space-tool';
14 |
15 | import PositFactory from './PostitFactory';
16 | import PostitUpdater from './PostitUpdater';
17 | import ElementFactory from './ElementFactory';
18 | import Modeling from './Modeling';
19 | import Layouter from 'diagram-js/lib/layout/BaseLayouter';
20 | import CroppingConnectionDocking from 'diagram-js/lib/layout/CroppingConnectionDocking';
21 |
22 |
23 | export default {
24 | __init__: [
25 | 'modeling',
26 | 'postitUpdater'
27 | ],
28 | __depends__: [
29 | BehaviorModule,
30 | RulesModule,
31 | DiOrderingModule,
32 | OrderingModule,
33 | ReplaceModule,
34 | CommandModule,
35 | TooltipsModule,
36 | LabelSupportModule,
37 | AttachSupportModule,
38 | SelectionModule,
39 | ChangeSupportModule,
40 | SpaceToolModule
41 | ],
42 | postitFactory: [ 'type', PositFactory ],
43 | postitUpdater: [ 'type', PostitUpdater ],
44 | elementFactory: [ 'type', ElementFactory ],
45 | modeling: [ 'type', Modeling ],
46 | layouter: [ 'type', Layouter ],
47 | connectionDocking: [ 'type', CroppingConnectionDocking ]
48 | };
--------------------------------------------------------------------------------
/lib/features/modeling/util/ModelingUtil.js:
--------------------------------------------------------------------------------
1 | import {
2 | some
3 | } from 'min-dash';
4 |
5 | import { is } from '../../../util/ModelUtil';
6 |
7 |
8 | /**
9 | * Return true if element has any of the given types.
10 | *
11 | * @param {djs.model.Base} element
12 | * @param {Array} types
13 | *
14 | * @return {Boolean}
15 | */
16 | export function isAny(element, types) {
17 | return some(types, function(t) {
18 | return is(element, t);
19 | });
20 | }
21 |
22 |
23 | /**
24 | * Return the parent of the element with any of the given types.
25 | *
26 | * @param {djs.model.Base} element
27 | * @param {String|Array} anyType
28 | *
29 | * @return {djs.model.Base}
30 | */
31 | export function getParent(element, anyType) {
32 |
33 | if (typeof anyType === 'string') {
34 | anyType = [ anyType ];
35 | }
36 |
37 | while ((element = element.parent)) {
38 | if (isAny(element, anyType)) {
39 | return element;
40 | }
41 | }
42 |
43 | return null;
44 | }
--------------------------------------------------------------------------------
/lib/features/ordering/PostitOrderingProvider.js:
--------------------------------------------------------------------------------
1 | import inherits from 'inherits-browser';
2 |
3 | import OrderingProvider from 'diagram-js/lib/features/ordering/OrderingProvider';
4 |
5 | import {
6 | isAny
7 | } from '../modeling/util/ModelingUtil';
8 |
9 | import {
10 | findIndex,
11 | find
12 | } from 'min-dash';
13 |
14 |
15 | /**
16 | * a simple ordering provider that makes sure:
17 | *
18 | * (0) labels and groups are rendered always on top
19 | * (1) elements are ordered by a {level} property
20 | */
21 | export default function PostitOrderingProvider(eventBus, canvas, translate) {
22 |
23 | OrderingProvider.call(this, eventBus);
24 |
25 | var orders = [
26 | { type: 'postit:BoardElement', order: { level: 5 } }
27 | ];
28 |
29 | function computeOrder(element) {
30 | if (element.labelTarget) {
31 | return { level: 10 };
32 | }
33 |
34 | var entry = find(orders, function(o) {
35 | return isAny(element, [ o.type ]);
36 | });
37 |
38 | return entry && entry.order || { level: 1 };
39 | }
40 |
41 | function getOrder(element) {
42 |
43 | var order = element.order;
44 |
45 | if (!order) {
46 | element.order = order = computeOrder(element);
47 | }
48 |
49 | return order;
50 | }
51 |
52 | function findActualParent(element, newParent, containers) {
53 |
54 | var actualParent = newParent;
55 |
56 | while (actualParent) {
57 |
58 | if (isAny(actualParent, containers)) {
59 | break;
60 | }
61 |
62 | actualParent = actualParent.parent;
63 | }
64 |
65 | if (!actualParent) {
66 | throw new Error(translate('no parent for {element} in {parent}', {
67 | element: element.id,
68 | parent: newParent.id
69 | }));
70 | }
71 |
72 | return actualParent;
73 | }
74 |
75 | this.getOrdering = function(element, newParent) {
76 |
77 | // render labels always on top
78 | if (element.labelTarget) {
79 | return {
80 | parent: canvas.getRootElement(),
81 | index: -1
82 | };
83 | }
84 |
85 | var elementOrder = getOrder(element);
86 |
87 |
88 | if (elementOrder.containers) {
89 | newParent = findActualParent(element, newParent, elementOrder.containers);
90 | }
91 |
92 |
93 | var currentIndex = newParent.children.indexOf(element);
94 |
95 | var insertIndex = findIndex(newParent.children, function(child) {
96 |
97 | // do not compare with labels, they are created
98 | // in the wrong order (right after elements) during import and
99 | // mess up the positioning.
100 | if (!element.labelTarget && child.labelTarget) {
101 | return false;
102 | }
103 |
104 | return elementOrder.level < getOrder(child).level;
105 | });
106 |
107 |
108 | // if the element is already in the child list at
109 | // a smaller index, we need to adjust the insert index.
110 | // this takes into account that the element is being removed
111 | // before being re-inserted
112 | if (insertIndex !== -1) {
113 | if (currentIndex !== -1 && currentIndex < insertIndex) {
114 | insertIndex -= 1;
115 | }
116 | }
117 |
118 | return {
119 | index: insertIndex,
120 | parent: newParent
121 | };
122 | };
123 | }
124 |
125 | PostitOrderingProvider.$inject = [ 'eventBus', 'canvas', 'translate' ];
126 |
127 | inherits(PostitOrderingProvider, OrderingProvider);
--------------------------------------------------------------------------------
/lib/features/ordering/index.js:
--------------------------------------------------------------------------------
1 | import translate from 'diagram-js/lib/i18n/translate';
2 |
3 | import PostitOrderingProvider from './PostitOrderingProvider';
4 |
5 | export default {
6 | __depends__: [
7 | translate
8 | ],
9 | __init__: [ 'postitOrderingProvider' ],
10 | postitOrderingProvider: [ 'type', PostitOrderingProvider ]
11 | };
--------------------------------------------------------------------------------
/lib/features/palette/PaletteProvider.js:
--------------------------------------------------------------------------------
1 | import {
2 | assign
3 | } from 'min-dash';
4 |
5 | import COLORS from '../../util/ColorUtil';
6 |
7 |
8 | /**
9 | * A palette provider for postit elements.
10 | */
11 | export default function PaletteProvider(
12 | palette, create, elementFactory,
13 | spaceTool, lassoTool, handTool, translate) {
14 |
15 | this._palette = palette;
16 | this._create = create;
17 | this._elementFactory = elementFactory;
18 | this._spaceTool = spaceTool;
19 | this._lassoTool = lassoTool;
20 | this._handTool = handTool;
21 | this._translate = translate;
22 |
23 | palette.registerProvider(this);
24 | }
25 |
26 | PaletteProvider.$inject = [
27 | 'palette',
28 | 'create',
29 | 'elementFactory',
30 | 'spaceTool',
31 | 'lassoTool',
32 | 'handTool',
33 | 'translate'
34 | ];
35 |
36 |
37 | PaletteProvider.prototype.getPaletteEntries = function(element) {
38 |
39 | var actions = {},
40 | create = this._create,
41 | elementFactory = this._elementFactory,
42 | spaceTool = this._spaceTool,
43 | lassoTool = this._lassoTool,
44 | handTool = this._handTool,
45 | translate = this._translate;
46 |
47 | function createAction(type, group, className, title, options) {
48 |
49 | function createListener(event) {
50 | var shape = elementFactory.createShape(assign({ type: type }, options));
51 | create.start(event, shape);
52 | }
53 |
54 | var shortType = type.replace(/^postit:/, '');
55 |
56 | return {
57 | group: group,
58 | className: className,
59 | title: title || translate('Create {type}', { type: shortType }),
60 | action: {
61 | dragstart: createListener,
62 | click: createListener
63 | }
64 | };
65 | }
66 |
67 | function createImage(event) {
68 | var shape = elementFactory.createShape({
69 | type: 'postit:Image'
70 | });
71 |
72 | create.start(event, shape, {
73 | hints: { selectImage: true }
74 | });
75 | }
76 |
77 | assign(actions, {
78 | 'hand-tool': {
79 | group: 'tools',
80 | className: 'bpmn-icon-hand-tool',
81 | title: translate('Activate the hand tool'),
82 | action: {
83 | click: function(event) {
84 | handTool.activateHand(event);
85 | }
86 | }
87 | },
88 | 'lasso-tool': {
89 | group: 'tools',
90 | className: 'bpmn-icon-lasso-tool',
91 | title: translate('Activate the lasso tool'),
92 | action: {
93 | click: function(event) {
94 | lassoTool.activateSelection(event);
95 | }
96 | }
97 | },
98 | 'space-tool': {
99 | group: 'tools',
100 | className: 'bpmn-icon-space-tool',
101 | title: translate('Activate the create/remove space tool'),
102 | action: {
103 | click: function(event) {
104 | spaceTool.activateSelection(event);
105 | }
106 | }
107 | },
108 | 'tool-separator': {
109 | group: 'tools',
110 | separator: true
111 | },
112 | 'create.square-postit': createAction(
113 | 'postit:SquarePostit', 'postits', 'pjs-postit-square',
114 | translate('Create Square Postit'), { color: COLORS.GREEN }
115 | ),
116 | 'create.circle-postit': createAction(
117 | 'postit:CirclePostit', 'postits', 'pjs-postit-circle',
118 | translate('Create Circle Postit'), { color: COLORS.PINK }
119 | ),
120 | 'postit-separator': {
121 | group: 'postits',
122 | separator: true
123 | },
124 | 'create.image': {
125 | group: 'artifact',
126 | className: 'pjs-image',
127 | title: translate('Create Image'),
128 | action: {
129 | click: createImage,
130 | dragstart: createImage
131 | }
132 | },
133 | 'create.text-box': createAction(
134 | 'postit:TextBox', 'artifact', 'pjs-text-box',
135 | translate('Create Text')
136 | ),
137 | 'create.group': createAction(
138 | 'postit:Group', 'artifact', 'pjs-group',
139 | translate('Create Group')
140 | )
141 | });
142 |
143 | return actions;
144 | };
145 |
--------------------------------------------------------------------------------
/lib/features/palette/index.js:
--------------------------------------------------------------------------------
1 | import PaletteModule from 'diagram-js/lib/features/palette';
2 | import CreateModule from 'diagram-js/lib/features/create';
3 | import SpaceToolModule from 'diagram-js/lib/features/space-tool';
4 | import LassoToolModule from 'diagram-js/lib/features/lasso-tool';
5 | import HandToolModule from 'diagram-js/lib/features/hand-tool';
6 | import translate from 'diagram-js/lib/i18n/translate';
7 |
8 | import PaletteProvider from './PaletteProvider';
9 |
10 | export default {
11 | __depends__: [
12 | PaletteModule,
13 | CreateModule,
14 | SpaceToolModule,
15 | LassoToolModule,
16 | HandToolModule,
17 | translate
18 | ],
19 | __init__: [ 'paletteProvider' ],
20 | paletteProvider: [ 'type', PaletteProvider ]
21 | };
22 |
--------------------------------------------------------------------------------
/lib/features/popup-menu/ReplaceMenuProvider.js:
--------------------------------------------------------------------------------
1 | import {
2 | forEach
3 | } from 'min-dash';
4 |
5 |
6 | /**
7 | * This module is an element agnostic replace menu provider for the popup menu.
8 | */
9 | export default function ReplaceMenuProvider(
10 | popupMenu, modeling, moddle,
11 | postitReplace, rules, translate) {
12 |
13 | this._popupMenu = popupMenu;
14 | this._modeling = modeling;
15 | this._moddle = moddle;
16 | this._postitReplace = postitReplace;
17 | this._rules = rules;
18 | this._translate = translate;
19 |
20 | this.register();
21 | }
22 |
23 | ReplaceMenuProvider.$inject = [
24 | 'popupMenu',
25 | 'modeling',
26 | 'moddle',
27 | 'postitReplace',
28 | 'rules',
29 | 'translate'
30 | ];
31 |
32 |
33 | /**
34 | * Register replace menu provider in the popup menu
35 | */
36 | ReplaceMenuProvider.prototype.register = function() {
37 | this._popupMenu.registerProvider('postit-replace', this);
38 | };
39 |
40 |
41 | /**
42 | * Get all entries from replaceOptions for the given element and apply filters
43 | * on them. Get for example only elements, which are different from the current one.
44 | *
45 | * @param {djs.model.Base} element
46 | *
47 | * @return {Array