├── .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 | [![Netlify Status](https://api.netlify.com/api/v1/badges/72130b1d-f56b-473e-8f3b-50a5af916e64/deploy-status)](https://app.netlify.com/sites/postit-js-demo/deploys) ![Build Status](https://github.com/pinussilvestrus/postit-js/workflows/ci/badge.svg) 4 | 5 | 6 | Create post-it brainstorming boards - built with [diagram-js](https://github.com/bpmn-io/diagram-js). 7 | 8 | ![Screencast](./docs/screencast.gif) 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 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
Undo⌘ + Z
Redo⌘ + ⇧ + Z
Select All⌘ + A
Zoomingctrl + Scrolling
Direct EditingE
Hand ToolH
Lasso ToolL
Space ToolS
56 |
57 |
58 | 59 |
60 | 61 |
62 | 74 |
75 | 76 | 77 | 78 | 79 | 80 |
81 | 96 | 97 | 112 |
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 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 |
Undo⌘ + Z
Redo⌘ + ⇧ + Z
Select All⌘ + A
Zoomingctrl + Scrolling
Direct EditingE
Hand ToolH
Lasso ToolL
Space ToolS
56 |
57 |
58 | 59 |
60 | 61 |
62 | 74 |
75 | 76 | 77 | 78 | 79 | 80 |
81 | 96 | 97 | 112 |
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} a list of menu entry items 48 | */ 49 | ReplaceMenuProvider.prototype.getEntries = function(element) { 50 | 51 | var rules = this._rules; 52 | 53 | var entries = []; 54 | 55 | if (!rules.allowed('shape.replace', { element: element })) { 56 | return []; 57 | } 58 | 59 | return entries; 60 | }; 61 | 62 | 63 | /** 64 | * Get a list of header items for the given element. This includes buttons 65 | * for multi instance markers and for the ad hoc marker. 66 | * 67 | * @param {djs.model.Base} element 68 | * 69 | * @return {Array} a list of menu entry items 70 | */ 71 | ReplaceMenuProvider.prototype.getHeaderEntries = function(element) { 72 | 73 | var headerEntries = []; 74 | 75 | return headerEntries; 76 | }; 77 | 78 | 79 | /** 80 | * Creates an array of menu entry objects for a given element and filters the replaceOptions 81 | * according to a filter function. 82 | * 83 | * @param {djs.model.Base} element 84 | * @param {Object} replaceOptions 85 | * 86 | * @return {Array} a list of menu items 87 | */ 88 | ReplaceMenuProvider.prototype._createEntries = function(element, replaceOptions) { 89 | var menuEntries = []; 90 | 91 | var self = this; 92 | 93 | forEach(replaceOptions, function(definition) { 94 | var entry = self._createMenuEntry(definition, element); 95 | 96 | menuEntries.push(entry); 97 | }); 98 | 99 | return menuEntries; 100 | }; 101 | 102 | 103 | /** 104 | * Creates and returns a single menu entry item. 105 | * 106 | * @param {Object} definition a single replace options definition object 107 | * @param {djs.model.Base} element 108 | * @param {Function} [action] an action callback function which gets called when 109 | * the menu entry is being triggered. 110 | * 111 | * @return {Object} menu entry item 112 | */ 113 | ReplaceMenuProvider.prototype._createMenuEntry = function(definition, element, action) { 114 | var translate = this._translate; 115 | var replaceElement = this._postitReplace.replaceElement; 116 | 117 | var replaceAction = function() { 118 | return replaceElement(element, definition.target); 119 | }; 120 | 121 | action = action || replaceAction; 122 | 123 | var menuEntry = { 124 | label: translate(definition.label), 125 | className: definition.className, 126 | id: definition.actionName, 127 | action: action 128 | }; 129 | 130 | return menuEntry; 131 | }; 132 | -------------------------------------------------------------------------------- /lib/features/popup-menu/index.js: -------------------------------------------------------------------------------- 1 | import PopupMenuModule from 'diagram-js/lib/features/popup-menu'; 2 | import ReplaceModule from '../replace'; 3 | 4 | import ReplaceMenuProvider from './ReplaceMenuProvider'; 5 | 6 | 7 | export default { 8 | __depends__: [ 9 | PopupMenuModule, 10 | ReplaceModule 11 | ], 12 | __init__: [ 'replaceMenuProvider' ], 13 | replaceMenuProvider: [ 'type', ReplaceMenuProvider ] 14 | }; -------------------------------------------------------------------------------- /lib/features/popup-menu/util/TypeUtil.js: -------------------------------------------------------------------------------- 1 | import { 2 | getBusinessObject 3 | } from '../../../util/ModelUtil'; 4 | 5 | 6 | /** 7 | * Returns true, if an element is from a different type 8 | * than a target definition. Takes into account the type, 9 | * event definition type and triggeredByEvent property. 10 | * 11 | * @param {djs.model.Base} element 12 | * 13 | * @return {Boolean} 14 | */ 15 | export function isDifferentType(element) { 16 | 17 | return function(entry) { 18 | var target = entry.target; 19 | 20 | var businessObject = getBusinessObject(element), 21 | eventDefinition = businessObject.eventDefinitions && businessObject.eventDefinitions[0]; 22 | 23 | var isTypeEqual = businessObject.$type === target.type; 24 | 25 | var isEventDefinitionEqual = ( 26 | (eventDefinition && eventDefinition.$type) === target.eventDefinitionType 27 | ); 28 | 29 | var isTriggeredByEventEqual = ( 30 | businessObject.triggeredByEvent === target.triggeredByEvent 31 | ); 32 | 33 | return !isTypeEqual || !isEventDefinitionEqual || !isTriggeredByEventEqual; 34 | }; 35 | } -------------------------------------------------------------------------------- /lib/features/replace-preview/PostitReplacePreview.js: -------------------------------------------------------------------------------- 1 | import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; 2 | 3 | import inherits from 'inherits-browser'; 4 | 5 | import cssEscape from 'css.escape'; 6 | 7 | import { 8 | assign, 9 | forEach 10 | } from 'min-dash'; 11 | 12 | import { 13 | query as domQuery 14 | } from 'min-dom'; 15 | 16 | import { 17 | attr as svgAttr 18 | } from 'tiny-svg'; 19 | 20 | var LOW_PRIORITY = 250; 21 | 22 | 23 | export default function PostitReplacePreview( 24 | eventBus, elementRegistry, elementFactory, 25 | canvas, previewSupport) { 26 | 27 | CommandInterceptor.call(this, eventBus); 28 | 29 | /** 30 | * Replace the visuals of all elements in the context which can be replaced 31 | * 32 | * @param {Object} context 33 | */ 34 | function replaceVisual(context) { 35 | 36 | var replacements = context.canExecute.replacements; 37 | 38 | forEach(replacements, function(replacement) { 39 | 40 | var id = replacement.oldElementId; 41 | 42 | var newElement = { 43 | type: replacement.newElementType 44 | }; 45 | 46 | // if the visual of the element is already replaced 47 | if (context.visualReplacements[id]) { 48 | return; 49 | } 50 | 51 | var element = elementRegistry.get(id); 52 | 53 | assign(newElement, { x: element.x, y: element.y }); 54 | 55 | // create a temporary shape 56 | var tempShape = elementFactory.createShape(newElement); 57 | 58 | canvas.addShape(tempShape, element.parent); 59 | 60 | // select the original SVG element related to the element and hide it 61 | var gfx = domQuery('[data-element-id="' + cssEscape(element.id) + '"]', context.dragGroup); 62 | 63 | if (gfx) { 64 | svgAttr(gfx, { display: 'none' }); 65 | } 66 | 67 | // clone the gfx of the temporary shape and add it to the drag group 68 | var dragger = previewSupport.addDragger(tempShape, context.dragGroup); 69 | 70 | context.visualReplacements[id] = dragger; 71 | 72 | canvas.removeShape(tempShape); 73 | }); 74 | } 75 | 76 | /** 77 | * Restore the original visuals of the previously replaced elements 78 | * 79 | * @param {Object} context 80 | */ 81 | function restoreVisual(context) { 82 | 83 | var visualReplacements = context.visualReplacements; 84 | 85 | forEach(visualReplacements, function(dragger, id) { 86 | 87 | var originalGfx = domQuery('[data-element-id="' + cssEscape(id) + '"]', context.dragGroup); 88 | 89 | if (originalGfx) { 90 | svgAttr(originalGfx, { display: 'inline' }); 91 | } 92 | 93 | dragger.remove(); 94 | 95 | if (visualReplacements[id]) { 96 | delete visualReplacements[id]; 97 | } 98 | }); 99 | } 100 | 101 | eventBus.on('shape.move.move', LOW_PRIORITY, function(event) { 102 | 103 | var context = event.context, 104 | canExecute = context.canExecute; 105 | 106 | if (!context.visualReplacements) { 107 | context.visualReplacements = {}; 108 | } 109 | 110 | if (canExecute && canExecute.replacements) { 111 | replaceVisual(context); 112 | } else { 113 | restoreVisual(context); 114 | } 115 | }); 116 | } 117 | 118 | PostitReplacePreview.$inject = [ 119 | 'eventBus', 120 | 'elementRegistry', 121 | 'elementFactory', 122 | 'canvas', 123 | 'previewSupport' 124 | ]; 125 | 126 | inherits(PostitReplacePreview, CommandInterceptor); -------------------------------------------------------------------------------- /lib/features/replace-preview/index.js: -------------------------------------------------------------------------------- 1 | import PreviewSupportModule from 'diagram-js/lib/features/preview-support'; 2 | 3 | import PostitReplacePreview from './PostitReplacePreview'; 4 | 5 | export default { 6 | __depends__: [ 7 | PreviewSupportModule 8 | ], 9 | __init__: [ 'postitReplacePreview' ], 10 | postitReplacePreview: [ 'type', PostitReplacePreview ] 11 | }; 12 | -------------------------------------------------------------------------------- /lib/features/replace/PostitReplace.js: -------------------------------------------------------------------------------- 1 | import { 2 | pick, 3 | assign, 4 | forEach, 5 | isArray, 6 | isUndefined 7 | } from 'min-dash'; 8 | 9 | import { getPropertyNames } from '../copy-paste/ModdleCopy'; 10 | 11 | function copyProperties(source, target, properties) { 12 | if (!isArray(properties)) { 13 | properties = [ properties ]; 14 | } 15 | 16 | forEach(properties, function(property) { 17 | if (!isUndefined(source[property])) { 18 | target[property] = source[property]; 19 | } 20 | }); 21 | } 22 | 23 | var CUSTOM_PROPERTIES = [ 24 | 'cancelActivity', 25 | 'instantiate', 26 | 'eventGatewayType', 27 | 'triggeredByEvent', 28 | 'isInterrupting' 29 | ]; 30 | 31 | 32 | 33 | /** 34 | * This module takes care of replacing postit elements 35 | */ 36 | export default function PostitReplace( 37 | postitFactory, 38 | elementFactory, 39 | moddleCopy, 40 | modeling, 41 | replace, 42 | selection 43 | ) { 44 | 45 | /** 46 | * Prepares a new business object for the replacement element 47 | * and triggers the replace operation. 48 | * 49 | * @param {djs.model.Base} element 50 | * @param {Object} target 51 | * @param {Object} [hints] 52 | * 53 | * @return {djs.model.Base} the newly created element 54 | */ 55 | function replaceElement(element, target, hints) { 56 | 57 | hints = hints || {}; 58 | 59 | var type = target.type, 60 | oldBusinessObject = element.businessObject; 61 | 62 | var newBusinessObject = postitFactory.create(type); 63 | 64 | var newElement = { 65 | type: type, 66 | businessObject: newBusinessObject 67 | }; 68 | 69 | var elementProps = getPropertyNames(oldBusinessObject.$descriptor), 70 | newElementProps = getPropertyNames(newBusinessObject.$descriptor, true), 71 | copyProps = intersection(elementProps, newElementProps); 72 | 73 | // initialize special properties defined in target definition 74 | assign(newBusinessObject, pick(target, CUSTOM_PROPERTIES)); 75 | 76 | var properties = copyProps; 77 | 78 | newBusinessObject = moddleCopy.copyElement( 79 | oldBusinessObject, 80 | newBusinessObject, 81 | properties 82 | ); 83 | 84 | newBusinessObject.name = oldBusinessObject.name; 85 | 86 | newElement.di = {}; 87 | 88 | // fill and stroke will be set to DI 89 | copyProperties(oldBusinessObject.di, newElement.di, [ 90 | 'fill', 91 | 'stroke' 92 | ]); 93 | 94 | newElement = replace.replaceElement(element, newElement, hints); 95 | 96 | if (hints.select !== false) { 97 | selection.select(newElement); 98 | } 99 | 100 | return newElement; 101 | } 102 | 103 | this.replaceElement = replaceElement; 104 | } 105 | 106 | PostitReplace.$inject = [ 107 | 'postitFactory', 108 | 'elementFactory', 109 | 'moddleCopy', 110 | 'modeling', 111 | 'replace', 112 | 'selection' 113 | ]; 114 | 115 | /** 116 | * Compute intersection between two arrays. 117 | */ 118 | function intersection(a1, a2) { 119 | return a1.filter(function(el) { 120 | return a2.indexOf(el) !== -1; 121 | }); 122 | } 123 | -------------------------------------------------------------------------------- /lib/features/replace/ReplaceOptions.js: -------------------------------------------------------------------------------- 1 | export var POST_IT = [ 2 | 3 | ]; 4 | -------------------------------------------------------------------------------- /lib/features/replace/index.js: -------------------------------------------------------------------------------- 1 | import CopyPasteModule from '../copy-paste'; 2 | import ReplaceModule from 'diagram-js/lib/features/replace'; 3 | import SelectionModule from 'diagram-js/lib/features/selection'; 4 | 5 | import PostitReplace from './PostitReplace'; 6 | 7 | export default { 8 | __depends__: [ 9 | CopyPasteModule, 10 | ReplaceModule, 11 | SelectionModule 12 | ], 13 | postitReplace: [ 'type', PostitReplace ] 14 | }; 15 | -------------------------------------------------------------------------------- /lib/features/rules/PostitRules.js: -------------------------------------------------------------------------------- 1 | import { 2 | every 3 | } from 'min-dash'; 4 | 5 | import inherits from 'inherits-browser'; 6 | 7 | import { 8 | is 9 | } from '../../util/ModelUtil'; 10 | 11 | import { 12 | isLabel 13 | } from '../../util/LabelUtil'; 14 | 15 | import RuleProvider from 'diagram-js/lib/features/rules/RuleProvider'; 16 | import { isAny } from '../modeling/util/ModelingUtil'; 17 | 18 | 19 | /** 20 | * Postit specific modeling rule 21 | */ 22 | export default function PostitRules(eventBus) { 23 | RuleProvider.call(this, eventBus); 24 | } 25 | 26 | inherits(PostitRules, RuleProvider); 27 | 28 | PostitRules.$inject = [ 'eventBus' ]; 29 | 30 | PostitRules.prototype.init = function() { 31 | 32 | this.addRule('shape.resize', function(context) { 33 | 34 | var shape = context.shape, 35 | newBounds = context.newBounds; 36 | 37 | return canResize(shape, newBounds); 38 | }); 39 | 40 | this.addRule('elements.create', function(context) { 41 | var elements = context.elements, 42 | position = context.position, 43 | target = context.target; 44 | 45 | return every(elements, function(element) { 46 | if (element.host) { 47 | return canAttach(element, element.host, null, position); 48 | } 49 | 50 | return canCreate(element, target, null, position); 51 | }); 52 | }); 53 | 54 | this.addRule('elements.move', function(context) { 55 | 56 | var target = context.target, 57 | shapes = context.shapes, 58 | position = context.position; 59 | 60 | return canAttach(shapes, target, null, position) || 61 | canMove(shapes, target, position); 62 | }); 63 | 64 | this.addRule('shape.create', function(context) { 65 | return canCreate( 66 | context.shape, 67 | context.target, 68 | context.source, 69 | context.position 70 | ); 71 | }); 72 | 73 | this.addRule('shape.attach', function(context) { 74 | 75 | return canAttach( 76 | context.shape, 77 | context.target, 78 | null, 79 | context.position 80 | ); 81 | }); 82 | 83 | this.addRule('element.copy', function(context) { 84 | var element = context.element, 85 | elements = context.elements; 86 | 87 | return canCopy(elements, element); 88 | }); 89 | }; 90 | 91 | PostitRules.prototype.canMove = canMove; 92 | 93 | PostitRules.prototype.canAttach = canAttach; 94 | 95 | PostitRules.prototype.canDrop = canDrop; 96 | 97 | PostitRules.prototype.canCreate = canCreate; 98 | 99 | PostitRules.prototype.canReplace = canReplace; 100 | 101 | PostitRules.prototype.canResize = canResize; 102 | 103 | PostitRules.prototype.canCopy = canCopy; 104 | 105 | /** 106 | * Utility functions for rule checking 107 | */ 108 | 109 | function isSame(a, b) { 110 | return a === b; 111 | } 112 | 113 | function getParents(element) { 114 | 115 | var parents = []; 116 | 117 | while (element) { 118 | element = element.parent; 119 | 120 | if (element) { 121 | parents.push(element); 122 | } 123 | } 124 | 125 | return parents; 126 | } 127 | 128 | function isParent(possibleParent, element) { 129 | var allParents = getParents(element); 130 | return allParents.indexOf(possibleParent) !== -1; 131 | } 132 | 133 | function isGroup(element) { 134 | return is(element, 'postit:Group') && !element.labelTarget; 135 | } 136 | 137 | /** 138 | * Can an element be dropped into the target element 139 | * 140 | * @return {Boolean} 141 | */ 142 | function canDrop(element, target) { 143 | 144 | // can move labels 145 | if (isLabel(element) || isGroup(element)) { 146 | return true; 147 | } 148 | 149 | // drop board elements onto boards 150 | if (is(element, 'postit:BoardElement') && is(target, 'postit:PostitBoard')) { 151 | return true; 152 | } 153 | 154 | return false; 155 | } 156 | 157 | function canReplace(elements, target) { 158 | 159 | if (!target) { 160 | return false; 161 | } 162 | 163 | return true; 164 | } 165 | 166 | 167 | function canAttach(elements, target) { 168 | 169 | if (!Array.isArray(elements)) { 170 | elements = [ elements ]; 171 | } 172 | 173 | // only (re-)attach one element at a time 174 | if (elements.length !== 1) { 175 | return false; 176 | } 177 | 178 | var element = elements[0]; 179 | 180 | // do not attach labels 181 | if (isLabel(element)) { 182 | return false; 183 | } 184 | 185 | if (is(target, 'postit:BoardElement')) { 186 | return false; 187 | } 188 | 189 | return 'attach'; 190 | } 191 | 192 | 193 | function canMove(elements, target) { 194 | 195 | // allow default move check to start move operation 196 | if (!target) { 197 | return true; 198 | } 199 | 200 | return elements.every(function(element) { 201 | return canDrop(element, target); 202 | }); 203 | } 204 | 205 | function canCreate(shape, target, source, position) { 206 | 207 | if (!target) { 208 | return false; 209 | } 210 | 211 | if (isLabel(shape) || isGroup(shape)) { 212 | return true; 213 | } 214 | 215 | if (isSame(source, target)) { 216 | return false; 217 | } 218 | 219 | // ensure we do not drop the element 220 | // into source 221 | if (source && isParent(source, target)) { 222 | return false; 223 | } 224 | 225 | return canDrop(shape, target, position); 226 | } 227 | 228 | function canResize(shape, newBounds) { 229 | 230 | if (isAny(shape, [ 'postit:Postit', 'postit:TextBox' ])) { 231 | return !newBounds || (newBounds.width >= 50 && newBounds.height >= 50); 232 | } 233 | 234 | if (is(shape, 'postit:Group')) { 235 | return true; 236 | } 237 | 238 | if (is(shape, 'postit:Image')) { 239 | return true; 240 | } 241 | 242 | return false; 243 | } 244 | 245 | function canCopy(elements, element) { 246 | return true; 247 | } 248 | -------------------------------------------------------------------------------- /lib/features/rules/index.js: -------------------------------------------------------------------------------- 1 | import RulesModule from 'diagram-js/lib/features/rules'; 2 | 3 | import PostitRules from './PostitRules'; 4 | 5 | export default { 6 | __depends__: [ 7 | RulesModule 8 | ], 9 | __init__: [ 'postitRules' ], 10 | postitRules: [ 'type', PostitRules ] 11 | }; 12 | -------------------------------------------------------------------------------- /lib/features/snapping/PostitCreateMoveSnapping.js: -------------------------------------------------------------------------------- 1 | import inherits from 'inherits-browser'; 2 | 3 | import CreateMoveSnapping from 'diagram-js/lib/features/snapping/CreateMoveSnapping'; 4 | 5 | /** 6 | * Snap during create and move. 7 | * 8 | * @param {EventBus} eventBus 9 | * @param {Injector} injector 10 | */ 11 | export default function PostitCreateMoveSnapping(injector) { 12 | injector.invoke(CreateMoveSnapping, this); 13 | } 14 | 15 | inherits(PostitCreateMoveSnapping, CreateMoveSnapping); 16 | 17 | PostitCreateMoveSnapping.$inject = [ 18 | 'injector' 19 | ]; 20 | 21 | PostitCreateMoveSnapping.prototype.initSnap = function(event) { 22 | return CreateMoveSnapping.prototype.initSnap.call(this, event); 23 | }; 24 | 25 | PostitCreateMoveSnapping.prototype.addSnapTargetPoints = function(snapPoints, shape, target) { 26 | return CreateMoveSnapping.prototype.addSnapTargetPoints.call(this, snapPoints, shape, target); 27 | }; 28 | 29 | PostitCreateMoveSnapping.prototype.getSnapTargets = function(shape, target) { 30 | return CreateMoveSnapping.prototype.getSnapTargets.call(this, shape, target); 31 | }; 32 | -------------------------------------------------------------------------------- /lib/features/snapping/PostitSnappingUtil.js: -------------------------------------------------------------------------------- 1 | import { getOrientation } from 'diagram-js/lib/layout/LayoutUtil'; 2 | 3 | export function getBoundaryAttachment(position, targetBounds) { 4 | 5 | var orientation = getOrientation(position, targetBounds, -15); 6 | 7 | if (orientation !== 'intersect') { 8 | return orientation; 9 | } else { 10 | return null; 11 | } 12 | } -------------------------------------------------------------------------------- /lib/features/snapping/index.js: -------------------------------------------------------------------------------- 1 | import PostitCreateMoveSnapping from './PostitCreateMoveSnapping'; 2 | import SnappingModule from 'diagram-js/lib/features/snapping'; 3 | 4 | export default { 5 | __depends__: [ SnappingModule ], 6 | __init__: [ 7 | 'createMoveSnapping' 8 | ], 9 | createMoveSnapping: [ 'type', PostitCreateMoveSnapping ] 10 | }; -------------------------------------------------------------------------------- /lib/import/Importer.js: -------------------------------------------------------------------------------- 1 | import PostitTreeWalker from './PostitTreeWalker'; 2 | 3 | /** 4 | * The importPostitDiagram result. 5 | * 6 | * @typedef {Object} importPostitDiagramResult 7 | * 8 | * @property {Array} warnings 9 | */ 10 | 11 | /** 12 | * The importPostitDiagram error. 13 | * 14 | * @typedef {Error} importPostitDiagramError 15 | * 16 | * @property {Array} warnings 17 | */ 18 | 19 | /** 20 | * Import the definitions into a diagram. 21 | * 22 | * Errors and warnings are reported through the specified callback. 23 | * 24 | * @param {djs.Diagram} diagram 25 | * @param {ModdleElement} definitions 26 | * @param {ModdleElement} [rootBoard] the diagram to be rendered 27 | * (if not provided, the first one will be rendered) 28 | * 29 | * Returns {Promise} 30 | */ 31 | export function importPostitDiagram(diagram, definitions, rootBoard) { 32 | 33 | var importer, 34 | eventBus, 35 | translate; 36 | 37 | var error, 38 | warnings = []; 39 | 40 | /** 41 | * Walk the diagram semantically, importing (=drawing) 42 | * all elements you encounter. 43 | * 44 | * @param {ModdleElement} definitions 45 | * @param {ModdleElement} rootBoard 46 | */ 47 | function render(definitions, rootBoard) { 48 | 49 | var visitor = { 50 | 51 | root: function(element) { 52 | return importer.add(element); 53 | }, 54 | 55 | element: function(element, parentShape) { 56 | return importer.add(element, parentShape); 57 | }, 58 | 59 | error: function(message, context) { 60 | warnings.push({ message: message, context: context }); 61 | } 62 | }; 63 | 64 | var walker = new PostitTreeWalker(visitor, translate); 65 | 66 | // traverse xml document model, 67 | // starting at definitions 68 | walker.handleDefinitions(definitions, rootBoard); 69 | } 70 | 71 | return new Promise(function(resolve, reject) { 72 | try { 73 | importer = diagram.get('postitImporter'); 74 | eventBus = diagram.get('eventBus'); 75 | translate = diagram.get('translate'); 76 | 77 | eventBus.fire('import.render.start', { definitions: definitions }); 78 | 79 | render(definitions, rootBoard); 80 | 81 | eventBus.fire('import.render.complete', { 82 | error: error, 83 | warnings: warnings 84 | }); 85 | 86 | return resolve({ warnings: warnings }); 87 | } catch (e) { 88 | return reject(e); 89 | } 90 | }); 91 | } -------------------------------------------------------------------------------- /lib/import/PostitTreeWalker.js: -------------------------------------------------------------------------------- 1 | import { 2 | find, 3 | forEach 4 | } from 'min-dash'; 5 | 6 | import Refs from 'object-refs'; 7 | 8 | import { 9 | elementToString 10 | } from './Util'; 11 | 12 | var diRefs = new Refs( 13 | { name: 'boardElement', enumerable: true }, 14 | { name: 'di', configurable: true } 15 | ); 16 | 17 | /** 18 | * Returns true if an element has the given meta-model type 19 | * 20 | * @param {ModdleElement} element 21 | * @param {String} type 22 | * 23 | * @return {Boolean} 24 | */ 25 | function is(element, type) { 26 | return element.$instanceOf(type); 27 | } 28 | 29 | 30 | /** 31 | * Find a suitable display candidate for definitions where the DI does not 32 | * correctly specify one. 33 | */ 34 | function findDisplayCandidate(definitions) { 35 | return find(definitions.rootElements, function(e) { 36 | return is(e, 'postit:PostitBoard'); 37 | }); 38 | } 39 | 40 | 41 | export default function PostitTreeWalker(handler, translate) { 42 | 43 | // list of containers already walked 44 | var handledElements = {}; 45 | 46 | // list of elements to handle deferred to ensure 47 | // prerequisites are drawn 48 | var deferred = []; 49 | 50 | // Helpers ////////////////////// 51 | 52 | function visitRoot(element, diagram) { 53 | return handler.root(element, diagram); 54 | } 55 | 56 | function visit(element, ctx) { 57 | 58 | var gfx = element.gfx; 59 | 60 | // avoid multiple rendering of elements 61 | if (gfx) { 62 | throw new Error( 63 | translate('already rendered {element}', { element: elementToString(element) }) 64 | ); 65 | } 66 | 67 | // call handler 68 | return handler.element(element, ctx); 69 | } 70 | 71 | function visitIfDi(element, ctx) { 72 | 73 | try { 74 | var gfx = element.di && visit(element, ctx); 75 | 76 | handled(element); 77 | 78 | return gfx; 79 | } catch (e) { 80 | logError(e.message, { element: element, error: e }); 81 | 82 | console.error(translate('failed to import {element}', { element: elementToString(element) })); 83 | console.error(e); 84 | } 85 | } 86 | 87 | function logError(message, context) { 88 | handler.error(message, context); 89 | } 90 | 91 | function handled(element) { 92 | handledElements[element.id] = element; 93 | } 94 | 95 | // DI handling ////////////////////// 96 | 97 | function registerDi(di) { 98 | var boardElement = di.boardElement; 99 | 100 | if (boardElement) { 101 | if (boardElement.di) { 102 | logError( 103 | translate('multiple DI elements defined for {element}', { 104 | element: elementToString(boardElement) 105 | }), 106 | { element: boardElement } 107 | ); 108 | } else { 109 | diRefs.bind(boardElement, 'di'); 110 | boardElement.di = di; 111 | } 112 | } else { 113 | logError( 114 | translate('no boardElement referenced in {element}', { 115 | element: elementToString(di) 116 | }), 117 | { element: di } 118 | ); 119 | } 120 | } 121 | 122 | function handleBoard(diagram) { 123 | handlePlane(diagram.plane); 124 | } 125 | 126 | function handlePlane(plane) { 127 | registerDi(plane); 128 | 129 | forEach(plane.planeElement, handlePlaneElement); 130 | } 131 | 132 | function handlePlaneElement(planeElement) { 133 | registerDi(planeElement); 134 | } 135 | 136 | 137 | // Semantic handling ////////////////////// 138 | 139 | /** 140 | * Handle definitions and return the rendered board (if any) 141 | * 142 | * @param {ModdleElement} definitions to walk and import 143 | * @param {ModdleElement} [rootBoard] specific board to import and display 144 | * 145 | * @throws {Error} if no diagram to display could be found 146 | */ 147 | function handleDefinitions(definitions, rootBoard) { 148 | 149 | // make sure we walk the correct boardElement 150 | 151 | var rootBoards = definitions.rootBoards; 152 | 153 | if (rootBoard && rootBoards.indexOf(rootBoard) === -1) { 154 | throw new Error(translate('rootBoard not part of postit:Definitions')); 155 | } 156 | 157 | if (!rootBoard && rootBoards && rootBoards.length) { 158 | rootBoard = rootBoards[0]; 159 | } 160 | 161 | // no root board -> nothing to import 162 | if (!rootBoard) { 163 | throw new Error(translate('no rootBoard to display')); 164 | } 165 | 166 | // load DI from selected root board only 167 | handleBoard(rootBoard); 168 | 169 | var plane = rootBoard.plane; 170 | 171 | if (!plane) { 172 | throw new Error(translate( 173 | 'no plane for {element}', 174 | { element: elementToString(rootBoard) } 175 | )); 176 | } 177 | 178 | var rootElement = plane.boardElement; 179 | 180 | // ensure we default to a suitable display candidate (board), 181 | // even if non is specified in DI 182 | if (!rootElement) { 183 | rootElement = findDisplayCandidate(definitions); 184 | 185 | if (!rootElement) { 186 | throw new Error(translate('no board to display')); 187 | } else { 188 | 189 | logError( 190 | translate('correcting missing boardElement on {plane} to {rootElement}', { 191 | plane: elementToString(plane), 192 | rootElement: elementToString(rootElement) 193 | }) 194 | ); 195 | 196 | // correct DI on the fly 197 | plane.boardElement = rootElement; 198 | registerDi(plane); 199 | } 200 | } 201 | 202 | 203 | var ctx = visitRoot(rootElement, plane); 204 | 205 | if (is(rootElement, 'postit:PostitBoard')) { 206 | handlePostitBoard(rootElement, ctx); 207 | } 208 | 209 | // handle all deferred elements 210 | handleDeferred(deferred); 211 | } 212 | 213 | function handleBoardElements(boardElements, context) { 214 | forEach(boardElements, function(element) { 215 | visitIfDi(element, context); 216 | }); 217 | } 218 | 219 | function handlePostitBoard(board, context) { 220 | handleBoardElements(board.boardElements, context); 221 | 222 | // log board handled 223 | handled(board); 224 | } 225 | 226 | function handleDeferred() { 227 | 228 | var fn; 229 | 230 | // drain deferred until empty 231 | while (deferred.length) { 232 | fn = deferred.shift(); 233 | 234 | fn(); 235 | } 236 | } 237 | 238 | 239 | 240 | 241 | // API ////////////////////// 242 | 243 | return { 244 | handleDeferred: handleDeferred, 245 | handleDefinitions: handleDefinitions, 246 | registerDi: registerDi 247 | }; 248 | } -------------------------------------------------------------------------------- /lib/import/Util.js: -------------------------------------------------------------------------------- 1 | export function elementToString(e) { 2 | if (!e) { 3 | return ''; 4 | } 5 | 6 | return '<' + e.$type + (e.id ? ' id="' + e.id : '') + '" />'; 7 | } -------------------------------------------------------------------------------- /lib/import/index.js: -------------------------------------------------------------------------------- 1 | import translate from 'diagram-js/lib/i18n/translate'; 2 | 3 | import PostitImporter from './PostitImporter'; 4 | 5 | export default { 6 | __depends__: [ 7 | translate 8 | ], 9 | postitImporter: [ 'type', PostitImporter ] 10 | }; -------------------------------------------------------------------------------- /lib/moddle/Moddle.js: -------------------------------------------------------------------------------- 1 | import { isString, assign } from 'min-dash'; 2 | 3 | import { Moddle } from 'moddle'; 4 | 5 | import { Reader, Writer } from 'moddle-xml'; 6 | 7 | /** 8 | * A sub class of {@link Moddle} with support for import and export of Postit-js xml files. 9 | * 10 | * @class PostitModdle 11 | * 12 | * @extends Moddle 13 | * 14 | * @param {Object|Array} packages to use for instantiating the model 15 | * @param {Object} [options] additional options to pass over 16 | */ 17 | export default function PostitModdle(packages, options) { 18 | Moddle.call(this, packages, options); 19 | } 20 | 21 | PostitModdle.prototype = Object.create(Moddle.prototype); 22 | 23 | /** 24 | * The fromXML result. 25 | * 26 | * @typedef {Object} ParseResult 27 | * 28 | * @property {ModdleElement} rootElement 29 | * @property {Array} references 30 | * @property {Array} warnings 31 | * @property {Object} elementsById - a mapping containing each ID -> ModdleElement 32 | */ 33 | 34 | /** 35 | * The fromXML error. 36 | * 37 | * @typedef {Error} ParseError 38 | * 39 | * @property {Array} warnings 40 | */ 41 | 42 | /** 43 | * Instantiates a Postit model tree from a given xml string. 44 | * 45 | * @param {String} xmlStr 46 | * @param {String} [typeName='postit:Definitions'] name of the root element 47 | * @param {Object} [options] options to pass to the underlying reader 48 | * 49 | * @returns {Promise} 50 | */ 51 | PostitModdle.prototype.fromXML = function(xmlStr, typeName, options) { 52 | if (!isString(typeName)) { 53 | options = typeName; 54 | typeName = 'postit:Definitions'; 55 | } 56 | 57 | var reader = new Reader(assign({ model: this, lax: true }, options)); 58 | var rootHandler = reader.handler(typeName); 59 | 60 | return reader.fromXML(xmlStr, rootHandler); 61 | }; 62 | 63 | /** 64 | * The toXML result. 65 | * 66 | * @typedef {Object} SerializationResult 67 | * 68 | * @property {String} xml 69 | */ 70 | 71 | /** 72 | * Serializes a Postit object tree to XML. 73 | * 74 | * @param {String} element the root element, typically an instance of `postit:Definitions` 75 | * @param {Object} [options] to pass to the underlying writer 76 | * 77 | * @returns {Promise} 78 | */ 79 | PostitModdle.prototype.toXML = function(element, options) { 80 | var writer = new Writer(options); 81 | 82 | return new Promise(function(resolve, reject) { 83 | try { 84 | var result = writer.toXML(element); 85 | 86 | return resolve({ 87 | xml: result 88 | }); 89 | } catch (err) { 90 | return reject(err); 91 | } 92 | }); 93 | }; 94 | -------------------------------------------------------------------------------- /lib/moddle/index.js: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | assign 4 | } from 'min-dash'; 5 | 6 | import Moddle from './Moddle'; 7 | 8 | import PostitDescriptors from './resources/postit.json'; 9 | import DiDescriptors from './resources/postitDi.json'; 10 | import DcDescriptors from './resources/dc.json'; 11 | 12 | var packages = { 13 | postit: PostitDescriptors, 14 | postitDi: DiDescriptors, 15 | dc: DcDescriptors, 16 | }; 17 | 18 | export default function(additionalPackages, options) { 19 | var pks = assign({}, packages, additionalPackages); 20 | 21 | return new Moddle(pks, options); 22 | } 23 | -------------------------------------------------------------------------------- /lib/moddle/resources/dc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dc", 3 | "uri": "http://www.omg.org/spec/DD/20100524/DC", 4 | "prefix": "dc", 5 | "types": [ 6 | { 7 | "name": "Boolean" 8 | }, 9 | { 10 | "name": "Integer" 11 | }, 12 | { 13 | "name": "Real" 14 | }, 15 | { 16 | "name": "String" 17 | }, 18 | { 19 | "name": "Font", 20 | "properties": [ 21 | { 22 | "name": "name", 23 | "type": "String", 24 | "isAttr": true 25 | }, 26 | { 27 | "name": "size", 28 | "type": "Real", 29 | "isAttr": true 30 | }, 31 | { 32 | "name": "isBold", 33 | "type": "Boolean", 34 | "isAttr": true 35 | }, 36 | { 37 | "name": "isItalic", 38 | "type": "Boolean", 39 | "isAttr": true 40 | }, 41 | { 42 | "name": "isUnderline", 43 | "type": "Boolean", 44 | "isAttr": true 45 | }, 46 | { 47 | "name": "isStrikeThrough", 48 | "type": "Boolean", 49 | "isAttr": true 50 | } 51 | ] 52 | }, 53 | { 54 | "name": "Point", 55 | "properties": [ 56 | { 57 | "name": "x", 58 | "type": "Real", 59 | "default": "0", 60 | "isAttr": true 61 | }, 62 | { 63 | "name": "y", 64 | "type": "Real", 65 | "default": "0", 66 | "isAttr": true 67 | } 68 | ] 69 | }, 70 | { 71 | "name": "Bounds", 72 | "properties": [ 73 | { 74 | "name": "x", 75 | "type": "Real", 76 | "default": "0", 77 | "isAttr": true 78 | }, 79 | { 80 | "name": "y", 81 | "type": "Real", 82 | "default": "0", 83 | "isAttr": true 84 | }, 85 | { 86 | "name": "width", 87 | "type": "Real", 88 | "isAttr": true 89 | }, 90 | { 91 | "name": "height", 92 | "type": "Real", 93 | "isAttr": true 94 | } 95 | ] 96 | } 97 | ], 98 | "associations": [] 99 | } 100 | -------------------------------------------------------------------------------- /lib/moddle/resources/postit.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Postit", 3 | "uri": "http://some-company/schema/postit", 4 | "prefix": "postit", 5 | "xml": { 6 | "tagAlias": "lowerCase" 7 | }, 8 | "types": [ 9 | { 10 | "name": "BoardElement", 11 | "isAbstract": true, 12 | "properties": [ 13 | { 14 | "name": "name", 15 | "isAttr": true, 16 | "type": "String" 17 | }, 18 | { 19 | "name": "id", 20 | "isAttr": true, 21 | "type": "String", 22 | "isId": true 23 | } 24 | ] 25 | }, 26 | { 27 | "name": "PostitBoard", 28 | "superClass": ["RootElement"], 29 | "properties": [ 30 | { 31 | "name": "boardElements", 32 | "isMany": true, 33 | "type": "BoardElement" 34 | } 35 | ] 36 | }, 37 | { 38 | "name": "Postit", 39 | "superClass": ["BoardElement"], 40 | "properties": [ 41 | { 42 | "name": "color", 43 | "isAttr": true, 44 | "type": "String" 45 | } 46 | ] 47 | }, 48 | { 49 | "name": "SquarePostit", 50 | "superClass": ["Postit"] 51 | }, 52 | { 53 | "name": "CirclePostit", 54 | "superClass": ["Postit"] 55 | }, 56 | { 57 | "name": "TextBox", 58 | "superClass": ["BoardElement"] 59 | }, 60 | { 61 | "name": "Group", 62 | "superClass": ["BoardElement"] 63 | }, 64 | { 65 | "name": "Image", 66 | "superClass": ["BoardElement"], 67 | "properties": [ 68 | { 69 | "name": "source", 70 | "isAttr": true, 71 | "type": "String" 72 | } 73 | ] 74 | }, 75 | { 76 | "name": "RootElement", 77 | "isAbstract": true, 78 | "superClass": [ 79 | "BoardElement" 80 | ] 81 | }, 82 | { 83 | "name": "Definitions", 84 | "superClass": [ 85 | "BoardElement" 86 | ], 87 | "properties": [ 88 | { 89 | "name": "targetNamespace", 90 | "isAttr": true, 91 | "type": "String" 92 | }, 93 | { 94 | "name": "expressionLanguage", 95 | "default": "http://www.w3.org/1999/XPath", 96 | "isAttr": true, 97 | "type": "String" 98 | }, 99 | { 100 | "name": "typeLanguage", 101 | "default": "http://www.w3.org/2001/XMLSchema", 102 | "isAttr": true, 103 | "type": "String" 104 | }, 105 | { 106 | "name": "rootElements", 107 | "type": "RootElement", 108 | "isMany": true 109 | }, 110 | { 111 | "name": "rootBoards", 112 | "isMany": true, 113 | "type": "postitDi:PostitRootBoard" 114 | }, 115 | { 116 | "name": "exporter", 117 | "isAttr": true, 118 | "type": "String" 119 | }, 120 | { 121 | "name": "exporterVersion", 122 | "isAttr": true, 123 | "type": "String" 124 | } 125 | ] 126 | } 127 | ] 128 | } 129 | -------------------------------------------------------------------------------- /lib/moddle/resources/postitDi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "POSTITDI", 3 | "uri": "http://some-company/schema/postitdi", 4 | "prefix": "postitDi", 5 | "xml": { 6 | "tagAlias": "lowerCase" 7 | }, 8 | "types": [ 9 | { 10 | "name": "PostitRootBoard", 11 | "properties": [ 12 | { 13 | "name": "plane", 14 | "type": "PostitPlane", 15 | "redefines": "Board#rootElement" 16 | }, 17 | { 18 | "name": "labelStyle", 19 | "type": "PostitLabelStyle", 20 | "isMany": true 21 | } 22 | ], 23 | "superClass": ["Board"] 24 | }, 25 | { 26 | "name": "PostitPlane", 27 | "properties": [ 28 | { 29 | "name": "boardElement", 30 | "isAttr": true, 31 | "isReference": true, 32 | "type": "postit:BoardElement", 33 | "redefines": "BoardElement#modelElement" 34 | } 35 | ], 36 | "superClass": ["Plane"] 37 | }, 38 | { 39 | "name": "PostitShape", 40 | "properties": [ 41 | { 42 | "name": "boardElement", 43 | "isAttr": true, 44 | "isReference": true, 45 | "type": "postit:BoardElement", 46 | "redefines": "BoardElement#modelElement" 47 | }, 48 | { 49 | "name": "label", 50 | "type": "PostitLabel" 51 | } 52 | ], 53 | "superClass": ["LabeledShape"] 54 | }, 55 | { 56 | "name": "PostitLabel", 57 | "properties": [ 58 | { 59 | "name": "labelStyle", 60 | "type": "PostitLabelStyle", 61 | "isAttr": true, 62 | "isReference": true, 63 | "redefines": "BoardElement#style" 64 | } 65 | ], 66 | "superClass": ["Label"] 67 | }, 68 | { 69 | "name": "PostitLabelStyle", 70 | "properties": [ 71 | { 72 | "name": "font", 73 | "type": "dc:Font" 74 | } 75 | ], 76 | "superClass": ["Style"] 77 | }, 78 | { 79 | "name": "BoardElement", 80 | "isAbstract": true, 81 | "properties": [ 82 | { 83 | "name": "id", 84 | "isAttr": true, 85 | "isId": true, 86 | "type": "String" 87 | }, 88 | { 89 | "name": "owningBoard", 90 | "type": "Board", 91 | "isReadOnly": true, 92 | "isVirtual": true, 93 | "isReference": true 94 | }, 95 | { 96 | "name": "owningElement", 97 | "type": "BoardElement", 98 | "isReadOnly": true, 99 | "isVirtual": true, 100 | "isReference": true 101 | }, 102 | { 103 | "name": "modelElement", 104 | "isReadOnly": true, 105 | "isVirtual": true, 106 | "isReference": true, 107 | "type": "Element" 108 | }, 109 | { 110 | "name": "style", 111 | "type": "Style", 112 | "isReadOnly": true, 113 | "isVirtual": true, 114 | "isReference": true 115 | } 116 | ] 117 | }, 118 | { 119 | "name": "Node", 120 | "isAbstract": true, 121 | "superClass": ["BoardElement"] 122 | }, 123 | { 124 | "name": "Board", 125 | "isAbstract": true, 126 | "properties": [ 127 | { 128 | "name": "id", 129 | "isAttr": true, 130 | "isId": true, 131 | "type": "String" 132 | }, 133 | { 134 | "name": "rootElement", 135 | "type": "BoardElement", 136 | "isReadOnly": true, 137 | "isVirtual": true 138 | }, 139 | { 140 | "name": "name", 141 | "isAttr": true, 142 | "type": "String" 143 | }, 144 | { 145 | "name": "resolution", 146 | "isAttr": true, 147 | "type": "Real" 148 | }, 149 | { 150 | "name": "ownedStyle", 151 | "type": "Style", 152 | "isReadOnly": true, 153 | "isMany": true, 154 | "isVirtual": true 155 | } 156 | ] 157 | }, 158 | { 159 | "name": "Shape", 160 | "isAbstract": true, 161 | "superClass": ["Node"], 162 | "properties": [ 163 | { 164 | "name": "bounds", 165 | "type": "dc:Bounds" 166 | } 167 | ] 168 | }, 169 | { 170 | "name": "Plane", 171 | "isAbstract": true, 172 | "superClass": ["Node"], 173 | "properties": [ 174 | { 175 | "name": "planeElement", 176 | "type": "BoardElement", 177 | "subsettedProperty": "BoardElement-ownedElement", 178 | "isMany": true 179 | } 180 | ] 181 | }, 182 | { 183 | "name": "LabeledEdge", 184 | "isAbstract": true, 185 | "superClass": ["Edge"], 186 | "properties": [ 187 | { 188 | "name": "ownedLabel", 189 | "type": "Label", 190 | "isReadOnly": true, 191 | "subsettedProperty": "BoardElement-ownedElement", 192 | "isMany": true, 193 | "isVirtual": true 194 | } 195 | ] 196 | }, 197 | { 198 | "name": "LabeledShape", 199 | "isAbstract": true, 200 | "superClass": ["Shape"], 201 | "properties": [ 202 | { 203 | "name": "ownedLabel", 204 | "type": "Label", 205 | "isReadOnly": true, 206 | "subsettedProperty": "BoardElement-ownedElement", 207 | "isMany": true, 208 | "isVirtual": true 209 | } 210 | ] 211 | }, 212 | { 213 | "name": "Label", 214 | "isAbstract": true, 215 | "superClass": ["Node"], 216 | "properties": [ 217 | { 218 | "name": "bounds", 219 | "type": "dc:Bounds" 220 | } 221 | ] 222 | }, 223 | { 224 | "name": "Style", 225 | "isAbstract": true, 226 | "properties": [ 227 | { 228 | "name": "id", 229 | "isAttr": true, 230 | "isId": true, 231 | "type": "String" 232 | } 233 | ] 234 | }, 235 | { 236 | "name": "Extension", 237 | "properties": [ 238 | { 239 | "name": "values", 240 | "isMany": true, 241 | "type": "Element" 242 | } 243 | ] 244 | } 245 | ], 246 | "enumerations": [], 247 | "associations": [] 248 | } 249 | -------------------------------------------------------------------------------- /lib/util/ColorUtil.js: -------------------------------------------------------------------------------- 1 | export default { 2 | GREEN: '#7EC845', 3 | PINK: '#F46F60', 4 | ICE_BLUE: '#B8D6F7', 5 | BLUE: '#23BFE7', 6 | ORANGE: '#FF9D48', 7 | YELLOW: '#F5D128', 8 | BLACK: 'black' 9 | }; -------------------------------------------------------------------------------- /lib/util/FileUtil.js: -------------------------------------------------------------------------------- 1 | export function fileToDataURL(file) { 2 | return new Promise((resolve, reject) => { 3 | try { 4 | var reader = new FileReader(); 5 | reader.onloadend = function() { 6 | resolve(reader.result); 7 | }; 8 | reader.readAsDataURL(file); 9 | } catch (e) { 10 | reject(e); 11 | } 12 | }); 13 | } 14 | 15 | 16 | export async function imageProcessor(ev, file) { 17 | const base64file = await fileToDataURL(file); 18 | 19 | // createImage(ev, base64file); 20 | return base64file; // passing any errors through 21 | } 22 | 23 | export async function fileReader(ev, files) { 24 | let uploadResult = []; 25 | let errors; 26 | let fileItems = files; 27 | if (ev && (ev.dataTransfer.items || ev.dataTransfer.files)) { 28 | fileItems = ev.dataTransfer.items || ev.dataTransfer.files; 29 | } 30 | try { 31 | if (fileItems) { 32 | for (var i = 0; i < fileItems.length; i++) { 33 | if (ev && ev.dataTransfer.items) { 34 | if (fileItems[i].kind === 'file') { 35 | var file = fileItems[i].getAsFile(); 36 | uploadResult.push(await imageProcessor(ev, file)); 37 | } 38 | } else { 39 | uploadResult.push(await imageProcessor(ev, fileItems[i])); 40 | } 41 | } 42 | } 43 | } catch (e) { 44 | errors = e; 45 | uploadResult = null; 46 | } 47 | return { uploadResult, errors }; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /lib/util/LabelUtil.js: -------------------------------------------------------------------------------- 1 | import { 2 | assign 3 | } from 'min-dash'; 4 | 5 | import { is } from './ModelUtil'; 6 | 7 | 8 | export var DEFAULT_LABEL_SIZE = { 9 | width: 90, 10 | height: 20 11 | }; 12 | 13 | export var FLOW_LABEL_INDENT = 15; 14 | 15 | 16 | /** 17 | * Returns true if the given semantic has an external label 18 | * 19 | * @param {BoardElement} semantic 20 | * @return {Boolean} true if has label 21 | */ 22 | export function isLabelExternal(semantic) { 23 | return is(semantic, 'postit:Group'); 24 | } 25 | 26 | /** 27 | * Returns true if the given element has an external label 28 | * 29 | * @param {djs.model.shape} element 30 | * @return {Boolean} true if has label 31 | */ 32 | export function hasExternalLabel(element) { 33 | return isLabel(element.label); 34 | } 35 | 36 | 37 | /** 38 | * Get the middle of a number of waypoints 39 | * 40 | * @param {Array} waypoints 41 | * @return {Point} the mid point 42 | */ 43 | export function getWaypointsMid(waypoints) { 44 | 45 | var mid = waypoints.length / 2 - 1; 46 | 47 | var first = waypoints[Math.floor(mid)]; 48 | var second = waypoints[Math.ceil(mid + 0.01)]; 49 | 50 | return { 51 | x: first.x + (second.x - first.x) / 2, 52 | y: first.y + (second.y - first.y) / 2 53 | }; 54 | } 55 | 56 | 57 | export function getExternalLabelMid(element) { 58 | 59 | if (is(element, 'postit:Group')) { 60 | return { 61 | x: element.x + element.width / 2, 62 | y: element.y + DEFAULT_LABEL_SIZE.height / 2 63 | }; 64 | } else { 65 | return { 66 | x: element.x + element.width / 2, 67 | y: element.y + element.height + DEFAULT_LABEL_SIZE.height / 2 68 | }; 69 | } 70 | } 71 | 72 | 73 | /** 74 | * Returns the bounds of an elements label, parsed from the elements DI or 75 | * generated from its bounds. 76 | * 77 | * @param {BoardElement} semantic 78 | * @param {djs.model.Base} element 79 | */ 80 | export function getExternalLabelBounds(semantic, element) { 81 | 82 | var mid, 83 | size, 84 | bounds, 85 | di = semantic.di, 86 | label = di.label; 87 | 88 | if (label && label.bounds) { 89 | bounds = label.bounds; 90 | 91 | size = { 92 | width: Math.max(DEFAULT_LABEL_SIZE.width, bounds.width), 93 | height: bounds.height 94 | }; 95 | 96 | mid = { 97 | x: bounds.x + bounds.width / 2, 98 | y: bounds.y + bounds.height / 2 99 | }; 100 | } else { 101 | 102 | mid = getExternalLabelMid(element); 103 | 104 | size = DEFAULT_LABEL_SIZE; 105 | } 106 | 107 | return assign({ 108 | x: mid.x - size.width / 2, 109 | y: mid.y - size.height / 2 110 | }, size); 111 | } 112 | 113 | export function isLabel(element) { 114 | return element && !!element.labelTarget; 115 | } 116 | -------------------------------------------------------------------------------- /lib/util/ModelUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Is an element of the given postit type? 3 | * 4 | * @param {djs.model.Base|ModdleElement} element 5 | * @param {String} type 6 | * 7 | * @return {Boolean} 8 | */ 9 | export function is(element, type) { 10 | var bo = getBusinessObject(element); 11 | 12 | return bo && (typeof bo.$instanceOf === 'function') && bo.$instanceOf(type); 13 | } 14 | 15 | 16 | /** 17 | * Return the business object for a given element. 18 | * 19 | * @param {djs.model.Base|ModdleElement} element 20 | * 21 | * @return {ModdleElement} 22 | */ 23 | export function getBusinessObject(element) { 24 | return (element && element.businessObject) || element; 25 | } -------------------------------------------------------------------------------- /lib/util/PoweredByUtil.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file must not be changed or exchanged. 3 | * 4 | * @see http://bpmn.io/license for more information. 5 | */ 6 | 7 | import { 8 | domify, 9 | delegate as domDelegate 10 | } from 'min-dom'; 11 | 12 | 13 | var BPMNIO_LOGO_SVG = ''; 14 | 15 | var BPMNIO_LOGO_URL = 'data:image/svg+xml,' + encodeURIComponent(BPMNIO_LOGO_SVG); 16 | 17 | export var BPMNIO_IMG = ''; 18 | 19 | function css(attrs) { 20 | return attrs.join(';'); 21 | } 22 | 23 | var LIGHTBOX_STYLES = css([ 24 | 'z-index: 1001', 25 | 'position: fixed', 26 | 'top: 0', 27 | 'left: 0', 28 | 'right: 0', 29 | 'bottom: 0' 30 | ]); 31 | 32 | var BACKDROP_STYLES = css([ 33 | 'width: 100%', 34 | 'height: 100%', 35 | 'background: rgba(0,0,0,0.2)' 36 | ]); 37 | 38 | var NOTICE_STYLES = css([ 39 | 'position: absolute', 40 | 'left: 50%', 41 | 'top: 40%', 42 | 'margin: 0 -130px', 43 | 'width: 260px', 44 | 'padding: 10px', 45 | 'background: white', 46 | 'border: solid 1px #AAA', 47 | 'border-radius: 3px', 48 | 'font-family: Helvetica, Arial, sans-serif', 49 | 'font-size: 14px', 50 | 'line-height: 1.2em' 51 | ]); 52 | 53 | var LIGHTBOX_MARKUP = 54 | '
' + 55 | '
' + 56 | '
' + 57 | '' + 58 | BPMNIO_IMG + 59 | '' + 60 | 'Web-based tooling for BPMN, DMN and CMMN diagrams ' + 61 | 'powered by bpmn.io.' + 62 | '
' + 63 | '
'; 64 | 65 | 66 | var lightbox; 67 | 68 | export function open() { 69 | 70 | if (!lightbox) { 71 | lightbox = domify(LIGHTBOX_MARKUP); 72 | 73 | domDelegate.bind(lightbox, '.backdrop', 'click', function(event) { 74 | document.body.removeChild(lightbox); 75 | }); 76 | } 77 | 78 | document.body.appendChild(lightbox); 79 | } -------------------------------------------------------------------------------- /lib/util/ScreenUtil.js: -------------------------------------------------------------------------------- 1 | export function getMousePosition(event) { 2 | event = event || window.event; 3 | 4 | var pageX = event.pageX; 5 | var pageY = event.pageY; 6 | 7 | // IE 8 8 | if (pageX === undefined) { 9 | pageX = event.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 10 | pageY = event.clientY + document.body.scrollTop + document.documentElement.scrollTop; 11 | } 12 | 13 | return { pageX, pageY }; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "postit-js-core", 3 | "version": "1.1.0", 4 | "scripts": { 5 | "all": "run-s lint test", 6 | "lint": "eslint .", 7 | "test": "karma start karma.conf.js", 8 | "dev": "npm test -- --auto-watch --no-single-run", 9 | "sync:live": "git checkout live && git merge main && git push origin live && git checkout main", 10 | "start:example": "npm start --workspace=example" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/pinussilvestrus/postit-js" 15 | }, 16 | "files": [ 17 | ".babelrc", 18 | "index.js", 19 | "assets", 20 | "dist", 21 | "lib", 22 | "test/util", 23 | "test/helper", 24 | "test/matchers", 25 | "!.eslintrc" 26 | ], 27 | "keywords": [ 28 | "diagram-js", 29 | "toolkit", 30 | "web modeler", 31 | "post-it", 32 | "brainstorming" 33 | ], 34 | "author": { 35 | "name": "Niklas Kiefer", 36 | "url": "https://github.com/pinussilvestrus" 37 | }, 38 | "license": "SEE LICENSE IN LICENSE", 39 | "sideEffects": [ 40 | "*.css" 41 | ], 42 | "workspaces": [ 43 | "example" 44 | ], 45 | "devDependencies": { 46 | "chai": "^4.3.7", 47 | "chai-match": "^1.1.1", 48 | "eslint": "^8.42.0", 49 | "eslint-plugin-bpmn-io": "^1.0.0", 50 | "eslint-plugin-import": "^2.27.5", 51 | "file-loader": "^6.2.0", 52 | "karma": "^6.4.2", 53 | "karma-chrome-launcher": "^3.2.0", 54 | "karma-debug-launcher": "0.0.5", 55 | "karma-env-preprocessor": "^0.1.1", 56 | "karma-firefox-launcher": "^2.1.2", 57 | "karma-mocha": "^2.0.1", 58 | "karma-sinon-chai": "^2.0.2", 59 | "karma-webpack": "^5.0.0", 60 | "mocha": "^10.2.0", 61 | "mocha-test-container-support": "^0.2.0", 62 | "npm-run-all": "^4.1.5", 63 | "puppeteer": "^21.0.0", 64 | "raw-loader": "^4.0.2", 65 | "sinon": "^17.0.0", 66 | "sinon-chai": "^3.7.0", 67 | "webpack": "^5.87.0" 68 | }, 69 | "dependencies": { 70 | "@fortawesome/fontawesome-free": "^6.4.0", 71 | "@ibm/plex": "^6.3.0", 72 | "bpmn-font": "^0.12.0", 73 | "css.escape": "^1.5.1", 74 | "diagram-js": "^12.2.0", 75 | "diagram-js-direct-editing": "^2.0.0", 76 | "ids": "^1.0.0", 77 | "inherits-browser": "^0.1.0", 78 | "min-dash": "^4.1.1", 79 | "min-dom": "^4.1.0", 80 | "moddle": "^6.2.3", 81 | "moddle-xml": "^10.1.0", 82 | "object-refs": "^0.3.0", 83 | "tiny-svg": "^3.0.1" 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:js-lib", 5 | ":preserveSemverRanges", 6 | ":disableDependencyDashboard" 7 | ], 8 | "labels": ["dependencies"], 9 | "ignorePaths": [ 10 | "extension-experiments" 11 | ], 12 | "packageRules": [ 13 | { 14 | "matchUpdateTypes": ["minor", "patch", "bump", "pin"], 15 | "schedule": "at any time", 16 | "automerge": true, 17 | "automergeType": "branch" 18 | } 19 | ], 20 | "schedule": ["before 2am on monday"] 21 | } 22 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plugin:bpmn-io/mocha" 3 | } -------------------------------------------------------------------------------- /test/TestHelper.js: -------------------------------------------------------------------------------- 1 | export * from './helper'; 2 | 3 | import ChaiMatch from 'chai-match'; 4 | 5 | /* global chai */ 6 | 7 | // add suite specific matchers 8 | chai.use(ChaiMatch); 9 | -------------------------------------------------------------------------------- /test/fixtures/empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/fixtures/simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/helper/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A helper file that may be used in test cases for postit-js and extensions. 3 | * 4 | * Provides the globals 5 | * 6 | * * bootstrapModeler(): bootstrap a modeler instance 7 | * * bootstrapViewer(): bootstrap a viewer instance 8 | * * inject(function(a, b) {}): inject the postit-js services in the given function 9 | * 10 | * 11 | * In addition it provides the utilities 12 | * 13 | * * insertCSS(name, css): add a CSS file to be used in test cases 14 | * 15 | * 16 | * It is recommended to expose the helper through a per-project utility and 17 | * and perform custom bootstrapping (CSS, ...) in that utility. 18 | * 19 | * ``` 20 | * export * from 'postit-js-core/test/helper'; 21 | * 22 | * import { 23 | * insertCSS 24 | * } from 'postit-js-core/test/helper'; 25 | * 26 | * var fs = require('fs'); 27 | * 28 | * // insert postit-js.css 29 | * insertCSS('postit-js.css', require('postit-js-core/assets/postit-js.css')); 30 | * ``` 31 | */ 32 | 33 | import { 34 | isFunction, 35 | forEach, 36 | merge 37 | } from 'min-dash'; 38 | 39 | import TestContainer from 'mocha-test-container-support'; 40 | 41 | import Modeler from '../../lib/Modeler'; 42 | 43 | var OPTIONS, POSTIT_JS; 44 | 45 | export function boostrapPostitJS(PostitJS, diagram, options, locals) { 46 | 47 | return function() { 48 | var testContainer; 49 | 50 | // Make sure the test container is an optional dependency and we fall back 51 | // to an empty
if it does not exist. 52 | // 53 | // This is needed if other libraries rely on this helper for testing 54 | // while not adding the mocha-test-container-support as a dependency. 55 | try { 56 | 57 | // 'this' is the current test context 58 | testContainer = TestContainer.get(this); 59 | } catch (e) { 60 | testContainer = document.createElement('div'); 61 | testContainer.classList.add('test-content-container'); 62 | 63 | document.body.appendChild(testContainer); 64 | } 65 | 66 | var _options = options, 67 | _locals = locals; 68 | 69 | if (_locals === undefined && isFunction(_options)) { 70 | _locals = _options; 71 | _options = null; 72 | } 73 | 74 | if (isFunction(_options)) { 75 | _options = _options(); 76 | } 77 | 78 | if (isFunction(_locals)) { 79 | _locals = _locals(); 80 | } 81 | 82 | _options = merge({ 83 | container: testContainer, 84 | canvas: { 85 | deferUpdate: false 86 | } 87 | }, OPTIONS, _options); 88 | 89 | if (_locals) { 90 | var mockModule = {}; 91 | 92 | forEach(_locals, function(v, k) { 93 | mockModule[k] = [ 'value', v ]; 94 | }); 95 | 96 | _options.modules = [].concat(_options.modules || [], [ mockModule ]); 97 | } 98 | 99 | if (_options.modules && !_options.modules.length) { 100 | _options.modules = undefined; 101 | } 102 | 103 | clearPostitJS(); 104 | 105 | var instance = new PostitJS(_options); 106 | 107 | setPostitJS(instance); 108 | 109 | return instance.importXML(diagram).then(function(result) { 110 | return { error: null, warnings: result.warnings }; 111 | }).catch(function(err) { 112 | return { error: err, warnings: err.warnings }; 113 | }); 114 | }; 115 | } 116 | 117 | 118 | export function bootstrapModeler(diagram, options, locals) { 119 | return boostrapPostitJS(Modeler, diagram, options, locals); 120 | } 121 | 122 | export function inject(fn) { 123 | return function() { 124 | 125 | if (!POSTIT_JS) { 126 | throw new Error( 127 | 'no bootstraped postit-js instance, ' + 128 | 'ensure you created it via #boostrap(Modeler|Viewer)' 129 | ); 130 | } 131 | 132 | POSTIT_JS.invoke(fn); 133 | }; 134 | } 135 | 136 | export function getPostitJS() { 137 | return POSTIT_JS; 138 | } 139 | 140 | export function clearPostitJS() { 141 | 142 | // clean up old postit-js instance 143 | if (POSTIT_JS) { 144 | POSTIT_JS.destroy(); 145 | 146 | POSTIT_JS = null; 147 | } 148 | } 149 | 150 | export function setPostitJS(instance) { 151 | POSTIT_JS = instance; 152 | } 153 | 154 | export function insertCSS(name, css) { 155 | if (document.querySelector('[data-css-file="' + name + '"]')) { 156 | return; 157 | } 158 | 159 | var head = document.head || document.getElementsByTagName('head')[0], 160 | style = document.createElement('style'); 161 | style.setAttribute('data-css-file', name); 162 | 163 | style.type = 'text/css'; 164 | if (style.styleSheet) { 165 | style.styleSheet.cssText = css; 166 | } else { 167 | style.appendChild(document.createTextNode(css)); 168 | } 169 | 170 | head.appendChild(style); 171 | } -------------------------------------------------------------------------------- /test/testBundle.js: -------------------------------------------------------------------------------- 1 | var allTests = require.context('.', true, /(spec|integration).*Spec\.js$/); 2 | 3 | allTests.keys().forEach(allTests); --------------------------------------------------------------------------------