├── .editorconfig ├── .github ├── dependabot.yml ├── release.yml └── workflows │ ├── build.yaml │ └── publish.yaml ├── .gitignore ├── .idea ├── $CACHE_FILE$ ├── .gitignore ├── camunda-web-modeler.iml ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jsLibraryMappings.xml ├── misc.xml ├── modules.xml ├── prettier.xml ├── saveactions_settings.xml └── vcs.xml ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .yarnrc.yml ├── LICENSE ├── README.md ├── eslint.config.mjs ├── index.ts ├── package.json ├── rollup.config.mjs ├── src ├── BpmnModeler.tsx ├── DmnModeler.tsx ├── bpmnio │ ├── GlobalEventListenerUtil.ts │ ├── bpmn │ │ └── CustomBpmnJsModeler.ts │ ├── dmn │ │ └── CustomDmnJsModeler.ts │ └── index.ts ├── components │ ├── SvgIcon.tsx │ └── ToggleGroup.tsx ├── editor │ ├── BpmnEditor.tsx │ ├── DmnEditor.tsx │ └── XmlEditor.tsx ├── events │ ├── Events.ts │ ├── bpmnio │ │ └── BpmnIoEvents.ts │ ├── index.ts │ └── modeler │ │ ├── ContentSavedEvent.ts │ │ ├── DmnViewsChangedEvent.ts │ │ ├── NotificationEvent.ts │ │ ├── PropertiesPanelResizedEvent.ts │ │ └── UIUpdateRequiredEvent.ts ├── index.ts └── types │ ├── bpmn-io.d.ts │ ├── bpmn-js-element-templates.ts │ ├── bpmn-js-properties-panel.d.ts │ ├── bpmn-js.d.ts │ ├── diagram-js-origin.d.ts │ ├── dmn-js-properties-panel.d.ts │ └── dmn-js.d.ts ├── static └── screenshot.jpg ├── tsconfig.index.json ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | insert_final_newline = true 9 | max_line_length = 89 10 | trim_trailing_whitespace = false 11 | 12 | # intellij ( ij_programming-language_setting ) 13 | ij_any_line_comment_add_space_on_reformat = true 14 | # spaces 15 | ij_any_spaces_within_method_parentheses = true 16 | ij_any_spaces_within_method_call_parentheses = true 17 | ij_any_spaces_within_if_parentheses = true 18 | ij_any_spaces_within_for_parentheses = true 19 | ij_any_spaces_within_while_parentheses = true 20 | ij_any_spaces_within_switch_parentheses = true 21 | ij_any_spaces_within_catch_parentheses = true 22 | # braces 23 | ij_any_class_brace_style = next_line 24 | ij_any_method_brace_style = next_line 25 | 26 | [*{json,yml,yaml}] 27 | indent_size = 2 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Fetch and update latest `npm` packages 4 | - package-ecosystem: npm 5 | directory: "/" 6 | target-branch: "develop" 7 | labels: 8 | - "Technical Debt" 9 | schedule: 10 | interval: daily 11 | time: "05:00" 12 | commit-message: 13 | prefix: fix 14 | prefix-development: chore 15 | include: scope 16 | # Fetch and update latest `github-actions` packages 17 | - package-ecosystem: github-actions 18 | directory: "/" 19 | schedule: 20 | interval: weekly 21 | time: "05:00" 22 | target-branch: "develop" 23 | labels: 24 | - "Technical Debt" 25 | commit-message: 26 | prefix: fix 27 | prefix-development: chore 28 | include: scope 29 | -------------------------------------------------------------------------------- /.github/release.yml: -------------------------------------------------------------------------------- 1 | changelog: 2 | categories: 3 | - title: 🎉 New Features 4 | labels: 5 | - feature 6 | - title: 🐞 Bug Fixes 7 | labels: 8 | - bug 9 | - title: 🔨 Refactoring 10 | labels: 11 | - refactoring 12 | - title: 📔 Documentation 13 | labels: 14 | - docs 15 | - title: 🛠️ Misc 16 | labels: 17 | - Technical Debt 18 | - chore -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches-ignore: [ develop ] 6 | pull_request: 7 | branches: [ develop ] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v4 15 | - name: Enable corepack 16 | run: corepack enable 17 | - name: Setup NodeJS 20.x 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 20.x 21 | cache: 'yarn' 22 | - name: Install dependencies 23 | run: yarn 24 | - name: Build application 25 | run: yarn build 26 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release-tag: 7 | description: 'Release tag' 8 | required: false 9 | default: 'release/v0.0.0' 10 | 11 | jobs: 12 | release: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout sources 16 | uses: actions/checkout@v4 17 | - name: Enable corepack 18 | run: corepack enable 19 | - name: Setup NodeJS 20.x 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: 20.x 23 | registry-url: 'https://registry.npmjs.org' 24 | cache: 'yarn' 25 | - name: Install dependencies 26 | run: yarn 27 | - name: Build application 28 | run: yarn build 29 | - name: Setup .yarnrc.yml 30 | run: yarn config set npmAuthToken $NPM_AUTH_TOKEN 31 | env: 32 | NPM_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_SECRET }} 33 | - name: Publish package 34 | run: yarn npm publish --access public 35 | env: 36 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_SECRET }} 37 | 38 | github-release: 39 | if: github.event.inputs.release-tag 40 | runs-on: ubuntu-latest 41 | needs: 42 | - release 43 | steps: 44 | - name: PREP / Checkout code 45 | uses: actions/checkout@v4 46 | - name: GIT / Create tag 47 | uses: actions/github-script@v7 48 | with: 49 | script: | 50 | github.rest.git.createRef({ 51 | owner: context.repo.owner, 52 | repo: context.repo.repo, 53 | ref: 'refs/tags/${{ github.event.inputs.release-tag }}', 54 | sha: context.sha 55 | }) 56 | - name: GIT / Create GitHub Release 57 | id: create_release 58 | uses: softprops/action-gh-release@v2 59 | with: 60 | tag_name: ${{ github.event.inputs.release-tag }} 61 | draft: false 62 | prerelease: false 63 | generate_release_notes: true 64 | 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/git,node,react,linux,macos,windows,typings,intellij,webstorm,visualstudiocode 3 | # Edit at https://www.gitignore.io/?templates=git,node,react,linux,macos,windows,typings,intellij,webstorm,visualstudiocode 4 | 5 | ### Git ### 6 | # Created by git for backups. To disable backups in Git: 7 | # $ git config --global mergetool.keepBackup false 8 | *.orig 9 | 10 | # Created by git when using merge tools for conflicts 11 | *.BACKUP.* 12 | *.BASE.* 13 | *.LOCAL.* 14 | *.REMOTE.* 15 | *_BACKUP_*.txt 16 | *_BASE_*.txt 17 | *_LOCAL_*.txt 18 | *_REMOTE_*.txt 19 | 20 | ### Intellij ### 21 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 22 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 23 | 24 | # User-specific stuff 25 | .idea/**/workspace.xml 26 | .idea/**/tasks.xml 27 | .idea/**/usage.statistics.xml 28 | .idea/**/dictionaries 29 | .idea/**/shelf 30 | 31 | # Generated files 32 | .idea/**/contentModel.xml 33 | 34 | # Sensitive or high-churn files 35 | .idea/**/dataSources/ 36 | .idea/**/dataSources.ids 37 | .idea/**/dataSources.local.xml 38 | .idea/**/sqlDataSources.xml 39 | .idea/**/dynamic.xml 40 | .idea/**/uiDesigner.xml 41 | .idea/**/dbnavigator.xml 42 | 43 | # Gradle 44 | .idea/**/gradle.xml 45 | .idea/**/libraries 46 | 47 | # Gradle and Maven with auto-import 48 | # When using Gradle or Maven with auto-import, you should exclude module files, 49 | # since they will be recreated, and may cause churn. Uncomment if using 50 | # auto-import. 51 | # .idea/modules.xml 52 | # .idea/*.iml 53 | # .idea/modules 54 | # *.iml 55 | # *.ipr 56 | 57 | # CMake 58 | cmake-build-*/ 59 | 60 | # Mongo Explorer plugin 61 | .idea/**/mongoSettings.xml 62 | 63 | # File-based project format 64 | *.iws 65 | 66 | # IntelliJ 67 | out/ 68 | 69 | # mpeltonen/sbt-idea plugin 70 | .idea_modules/ 71 | 72 | # JIRA plugin 73 | atlassian-ide-plugin.xml 74 | 75 | # Cursive Clojure plugin 76 | .idea/replstate.xml 77 | 78 | # Crashlytics plugin (for Android Studio and IntelliJ) 79 | com_crashlytics_export_strings.xml 80 | crashlytics.properties 81 | crashlytics-build.properties 82 | fabric.properties 83 | 84 | # Editor-based Rest Client 85 | .idea/httpRequests 86 | 87 | # Android studio 3.1+ serialized cache file 88 | .idea/caches/build_file_checksums.ser 89 | 90 | ### Intellij Patch ### 91 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 92 | 93 | # *.iml 94 | # modules.xml 95 | # .idea/misc.xml 96 | # *.ipr 97 | 98 | # Sonarlint plugin 99 | .idea/**/sonarlint/ 100 | 101 | # SonarQube Plugin 102 | .idea/**/sonarIssues.xml 103 | 104 | # Markdown Navigator plugin 105 | .idea/**/markdown-navigator.xml 106 | .idea/**/markdown-navigator/ 107 | 108 | ### Linux ### 109 | *~ 110 | 111 | # temporary files which can be created if a process still has a handle open of a deleted file 112 | .fuse_hidden* 113 | 114 | # KDE directory preferences 115 | .directory 116 | 117 | # Linux trash folder which might appear on any partition or disk 118 | .Trash-* 119 | 120 | # .nfs files are created when an open file is removed but is still being accessed 121 | .nfs* 122 | 123 | ### macOS ### 124 | # General 125 | .DS_Store 126 | .AppleDouble 127 | .LSOverride 128 | 129 | # Icon must end with two \r 130 | Icon 131 | 132 | # Thumbnails 133 | ._* 134 | 135 | # Files that might appear in the root of a volume 136 | .DocumentRevisions-V100 137 | .fseventsd 138 | .Spotlight-V100 139 | .TemporaryItems 140 | .Trashes 141 | .VolumeIcon.icns 142 | .com.apple.timemachine.donotpresent 143 | 144 | # Directories potentially created on remote AFP share 145 | .AppleDB 146 | .AppleDesktop 147 | Network Trash Folder 148 | Temporary Items 149 | .apdisk 150 | 151 | ### Node ### 152 | # Logs 153 | logs 154 | *.log 155 | npm-debug.log* 156 | lerna-debug.log* 157 | 158 | # Diagnostic reports (https://nodejs.org/api/report.html) 159 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 160 | 161 | # Runtime data 162 | pids 163 | *.pid 164 | *.seed 165 | *.pid.lock 166 | 167 | # Directory for instrumented libs generated by jscoverage/JSCover 168 | lib-cov 169 | 170 | # Coverage directory used by tools like istanbul 171 | coverage 172 | *.lcov 173 | 174 | # nyc test coverage 175 | .nyc_output 176 | 177 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 178 | .grunt 179 | 180 | # Bower dependency directory (https://bower.io/) 181 | bower_components 182 | 183 | # node-waf configuration 184 | .lock-wscript 185 | 186 | # Compiled binary addons (https://nodejs.org/api/addons.html) 187 | build/Release 188 | 189 | # Dependency directories 190 | node_modules/ 191 | jspm_packages/ 192 | 193 | # TypeScript v1 declaration files 194 | typings/ 195 | 196 | # TypeScript cache 197 | *.tsbuildinfo 198 | 199 | # Optional npm cache directory 200 | .npm 201 | 202 | # Optional eslint cache 203 | .eslintcache 204 | 205 | # Optional REPL history 206 | .node_repl_history 207 | 208 | # Output of 'npm pack' 209 | *.tgz 210 | 211 | # dotenv environment variables file 212 | .env 213 | .env.test 214 | 215 | # parcel-bundler cache (https://parceljs.org/) 216 | .cache 217 | 218 | # next.js build output 219 | .next 220 | 221 | # nuxt.js build output 222 | .nuxt 223 | 224 | # rollup.js default build output 225 | dist/ 226 | 227 | # Uncomment the public line if your project uses Gatsby 228 | # https://nextjs.org/blog/next-9-1#public-directory-support 229 | # https://create-react-app.dev/docs/using-the-public-folder/#docsNav 230 | # public 231 | 232 | # Storybook build outputs 233 | .out 234 | .storybook-out 235 | 236 | # vuepress build output 237 | .vuepress/dist 238 | 239 | # Serverless directories 240 | .serverless/ 241 | 242 | # FuseBox cache 243 | .fusebox/ 244 | 245 | # DynamoDB Local files 246 | .dynamodb/ 247 | 248 | # Temporary folders 249 | tmp/ 250 | temp/ 251 | 252 | ### react ### 253 | .DS_* 254 | **/*.backup.* 255 | **/*.back.* 256 | 257 | node_modules 258 | 259 | *.sublime* 260 | 261 | psd 262 | thumb 263 | sketch 264 | 265 | ### Typings ### 266 | ## Ignore downloaded typings 267 | typings 268 | 269 | ### VisualStudioCode ### 270 | .vscode/* 271 | !.vscode/settings.json 272 | !.vscode/tasks.json 273 | !.vscode/launch.json 274 | !.vscode/extensions.json 275 | 276 | ### VisualStudioCode Patch ### 277 | # Ignore all local history of files 278 | .history 279 | 280 | ### WebStorm ### 281 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 282 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 283 | 284 | # User-specific stuff 285 | 286 | # Generated files 287 | 288 | # Sensitive or high-churn files 289 | 290 | # Gradle 291 | 292 | # Gradle and Maven with auto-import 293 | # When using Gradle or Maven with auto-import, you should exclude module files, 294 | # since they will be recreated, and may cause churn. Uncomment if using 295 | # auto-import. 296 | # .idea/modules.xml 297 | # .idea/*.iml 298 | # .idea/modules 299 | # *.iml 300 | # *.ipr 301 | 302 | # CMake 303 | 304 | # Mongo Explorer plugin 305 | 306 | # File-based project format 307 | 308 | # IntelliJ 309 | 310 | # mpeltonen/sbt-idea plugin 311 | 312 | # JIRA plugin 313 | 314 | # Cursive Clojure plugin 315 | 316 | # Crashlytics plugin (for Android Studio and IntelliJ) 317 | 318 | # Editor-based Rest Client 319 | 320 | # Android studio 3.1+ serialized cache file 321 | 322 | ### WebStorm Patch ### 323 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 324 | 325 | # *.iml 326 | # modules.xml 327 | # .idea/misc.xml 328 | # *.ipr 329 | 330 | # Sonarlint plugin 331 | 332 | # SonarQube Plugin 333 | 334 | # Markdown Navigator plugin 335 | 336 | ### Windows ### 337 | # Windows thumbnail cache files 338 | Thumbs.db 339 | Thumbs.db:encryptable 340 | ehthumbs.db 341 | ehthumbs_vista.db 342 | 343 | # Dump file 344 | *.stackdump 345 | 346 | # Folder config file 347 | [Dd]esktop.ini 348 | 349 | # Recycle Bin used on file shares 350 | $RECYCLE.BIN/ 351 | 352 | # Windows Installer files 353 | *.cab 354 | *.msi 355 | *.msix 356 | *.msm 357 | *.msp 358 | 359 | # Windows shortcuts 360 | *.lnk 361 | 362 | # End of https://www.gitignore.io/api/git,node,react,linux,macos,windows,typings,intellij,webstorm,visualstudiocode 363 | 364 | build/ 365 | index.js 366 | index.d.ts 367 | 368 | # Yarn (without zero-installs) 369 | .pnp.* 370 | .yarn/* 371 | !.yarn/patches 372 | !.yarn/plugins 373 | !.yarn/releases 374 | !.yarn/sdks 375 | !.yarn/versions -------------------------------------------------------------------------------- /.idea/$CACHE_FILE$: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | -------------------------------------------------------------------------------- /.idea/camunda-web-modeler.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 14 | 15 | 17 | 18 | 25 | 26 | 29 | 30 | 35 | 36 | 38 | 39 | 41 | 42 | 48 | 49 | 51 | 52 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /.idea/jsLibraryMappings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /.idea/saveactions_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v20 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 89, 3 | "singleQuote": false, 4 | "trailingComma": "all", 5 | "tabWidth": 4, 6 | "arrowParens": "avoid" 7 | } -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | nodeLinker: node-modules 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Camunda Web Modeler 2 | 3 | [![NPM](https://img.shields.io/npm/v/@miragon/camunda-web-modeler)](https://www.npmjs.com/package/@miragon/camunda-web-modeler) 4 | ![Type Definitions](https://img.shields.io/npm/types/@miragon/camunda-web-modeler) 5 | ![License](https://img.shields.io/npm/l/@miragon/camunda-web-modeler) 6 | 7 | ![Screenshot](./static/screenshot.jpg) 8 | 9 | This is a React component based on [bpmn.io](https://bpmn.io) that allows you to use a fully functional modeler for BPMN 10 | and DMN in your browser application. It has lots of configuration options and offers these features: 11 | 12 | - Full support for BPMN and DMN 13 | - Embedded XML editor 14 | - Easily import element templates 15 | - Full support for using bpmn.io plugins 16 | - Access to all bpmn.io and additional events to integrate it more easily into your application 17 | - Exposes the complete bpmn.io API 18 | - Includes type definitions for many of the undocumented features and endpoints of bpmn.io 19 | - TypeScript support 20 | 21 | # Usage 22 | 23 | > [!IMPORTANT] 24 | > There is a known issue with the latest version of `min-dash` 25 | > (read about 26 | > it [here](https://forum.bpmn.io/t/camunda-properties-provider-not-working-properly-in-react-app/10660/10)). 27 | > To fix this, you can force the version of `min-dash` to below `4.2` in your `package.json` or configure your bundler 28 | > to also include `.cjs` and/or `.mjs` files. 29 | > 30 | > | npm | yarn | 31 | > |-----------------------------------------------------------------------|-------------------------------------------------------------------------| 32 | > |
"overrides": {
  "min-dash": "4.1.1"
} |
"resolutions": {
  "min-dash": "4.1.1"
} | 33 | 34 | ## Getting Started 35 | 36 | 1. Add this dependency to your application: 37 | 38 | ``` 39 | 40 | yarn add @miragon/camunda-web-modeler 41 | 42 | ``` 43 | 44 | 2. Include it in your application: 45 | 46 | ```typescript 47 | import { 48 | BpmnModeler, 49 | CustomBpmnJsModeler, 50 | Event, 51 | isContentSavedEvent 52 | } from "@miragon/camunda-web-modeler"; 53 | import React, { useCallback, useMemo, useRef, useState } from 'react'; 54 | import './App.css'; 55 | 56 | const App: React.FC = () => { 57 | const modelerRef = useRef(); 58 | 59 | const [xml, setXml] = useState(BPMN); 60 | 61 | const onEvent = useCallback(async (event: Event) => { 62 | if (isContentSavedEvent(event)) { 63 | setXml(event.data.xml); 64 | return; 65 | } 66 | }, []); 67 | 68 | /** 69 | * ==== 70 | * CAUTION: Using useMemo() is important to prevent additional render cycles! 71 | * ==== 72 | */ 73 | 74 | const modelerTabOptions = useMemo(() => ({ 75 | modelerOptions: { 76 | refs: [modelerRef] 77 | } 78 | }), []); 79 | 80 | return ( 81 |
86 | 91 | < /div> 92 | ) 93 | ; 94 | } 95 | 96 | export default App; 97 | 98 | const BPMN = /* ... */; 99 | ``` 100 | 101 | 3. Include your BPMN in the last line and run the application! 102 | 103 | ## Full example 104 | 105 | To see all options available, you can use this example. Remember that it's important to wrap all options and callbacks 106 | that are passed into the component using `useMemo()` and `useCallback()`. Else you will have lots of additional render 107 | cycles that can lead to bugs that are difficult to debug. 108 | 109 | Using the `bpmnJsOptions`, you can pass any options that you would normally pass into bpmn.io. The component will merge 110 | these with its own options and use it to create the modeler instance. 111 | 112 | ```typescript 113 | import { 114 | BpmnModeler, 115 | ContentSavedReason, 116 | CustomBpmnJsModeler, 117 | Event, 118 | isBpmnIoEvent, 119 | isContentSavedEvent, 120 | isNotificationEvent, 121 | isPropertiesPanelResizedEvent, 122 | isUIUpdateRequiredEvent 123 | } from "@miragon/camunda-web-modeler-test"; 124 | import React, { useCallback, useMemo, useRef, useState } from 'react'; 125 | import './App.css'; 126 | 127 | const App: React.FC = () => { 128 | const modelerRef = useRef(); 129 | 130 | const [xml, setXml] = useState(BPMN); 131 | 132 | const onXmlChanged = useCallback(( 133 | newXml: string, 134 | newSvg: string | undefined, 135 | reason: ContentSavedReason 136 | ) => { 137 | console.log(`Model has been changed because of ${reason}`); 138 | // Do whatever you want here, save the XML and SVG in the backend etc. 139 | setXml(newXml); 140 | }, []); 141 | 142 | const onSaveClicked = useCallback(async () => { 143 | if (!modelerRef.current) { 144 | // Should actually never happen, but required for type safety 145 | return; 146 | } 147 | 148 | console.log("Saving model..."); 149 | const result = await modelerRef.current.save(); 150 | console.log("Saved model!", result.xml, result.svg); 151 | }, []); 152 | 153 | const onEvent = useCallback(async (event: Event) => { 154 | if (isContentSavedEvent(event)) { 155 | // Content has been saved, e.g. because user edited the model or because he switched 156 | // from BPMN to XML. 157 | onXmlChanged(event.data.xml, event.data.svg, event.data.reason); 158 | return; 159 | } 160 | 161 | if (isNotificationEvent(event)) { 162 | // There's a notification the user is supposed to see, e.g. the model could not be 163 | // imported because it was invalid. 164 | return; 165 | } 166 | 167 | if (isUIUpdateRequiredEvent(event)) { 168 | // Something in the modeler has changed and the UI (e.g. menu) should be updated. 169 | // This happens when the user selects an element, for example. 170 | return; 171 | } 172 | 173 | if (isPropertiesPanelResizedEvent(event)) { 174 | // The user has resized the properties panel. You can save this value e.g. in local 175 | // storage to restore it on next load and pass it as initializing option. 176 | console.log(`Properties panel has been resized to ${event.data.width}`); 177 | return; 178 | } 179 | 180 | if (isBpmnIoEvent(event)) { 181 | // Just a regular bpmn-js event - actually lots of them 182 | return; 183 | } 184 | 185 | // eslint-disable-next-line no-console 186 | console.log("Unhandled event received", event); 187 | }, [onXmlChanged]); 188 | 189 | /** 190 | * ==== 191 | * CAUTION: Using useMemo() is important to prevent additional render cycles! 192 | * ==== 193 | */ 194 | 195 | const xmlTabOptions = useMemo(() => ({ 196 | className: undefined, 197 | disabled: undefined, 198 | monacoOptions: undefined 199 | }), []); 200 | 201 | const propertiesPanelOptions = useMemo(() => ({ 202 | className: undefined, 203 | containerId: undefined, 204 | container: undefined, 205 | elementTemplates: undefined, 206 | hidden: undefined, 207 | size: { 208 | max: undefined, 209 | min: undefined, 210 | initial: undefined 211 | } 212 | }), []); 213 | 214 | const modelerOptions = useMemo(() => ({ 215 | className: undefined, 216 | refs: [modelerRef], 217 | container: undefined, 218 | containerId: undefined, 219 | size: { 220 | max: undefined, 221 | min: undefined, 222 | initial: undefined 223 | } 224 | }), []); 225 | 226 | const bpmnJsOptions = useMemo(() => undefined, []); 227 | 228 | const modelerTabOptions = useMemo(() => ({ 229 | className: undefined, 230 | disabled: undefined, 231 | bpmnJsOptions: bpmnJsOptions, 232 | modelerOptions: modelerOptions, 233 | propertiesPanelOptions: propertiesPanelOptions 234 | }), [bpmnJsOptions, modelerOptions, propertiesPanelOptions]); 235 | 236 | return ( 237 |
242 | 243 | 66 | ))} 67 |
68 | ); 69 | }; 70 | 71 | export default ToggleGroup; 72 | -------------------------------------------------------------------------------- /src/editor/BpmnEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | MutableRefObject, 3 | ReactNode, 4 | useCallback, 5 | useEffect, 6 | useRef, 7 | useState, 8 | } from "react"; 9 | import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; 10 | import { tss } from "tss-react"; 11 | 12 | import CustomBpmnJsModeler from "../bpmnio/bpmn/CustomBpmnJsModeler"; 13 | import { createBpmnIoEvent } from "../events/bpmnio/BpmnIoEvents"; 14 | import { Event } from "../events"; 15 | import { createContentSavedEvent } from "../events/modeler/ContentSavedEvent"; 16 | import { createNotificationEvent } from "../events/modeler/NotificationEvent"; 17 | import { createPropertiesPanelResizedEvent } from "../events/modeler/PropertiesPanelResizedEvent"; 18 | import { createUIUpdateRequiredEvent } from "../events/modeler/UIUpdateRequiredEvent"; 19 | 20 | /** 21 | * The events that trigger a UI update required event. 22 | */ 23 | const UI_UPDATE_REQUIRED_EVENTS = [ 24 | "import.done", 25 | "saveXML.done", 26 | "commandStack.changed", 27 | "selection.changed", 28 | "attach", 29 | "elements.copied", 30 | "propertiesPanel.focusin", 31 | "propertiesPanel.focusout", 32 | "directEditing.activate", 33 | "directEditing.deactivate", 34 | "searchPad.closed", 35 | "searchPad.opened", 36 | ]; 37 | 38 | /** 39 | * The events that trigger a content saved event. 40 | */ 41 | const CONTENT_SAVED_EVENT = ["import.done", "commandStack.changed"]; 42 | 43 | export interface BpmnPropertiesPanelOptions { 44 | /** 45 | * This option disables the properties panel. 46 | * CAUTION: Element templates will not be imported either if this option is set! 47 | */ 48 | hidden?: boolean; 49 | 50 | /** 51 | * The initial, minimum, and maximum sizes of the properties panel. 52 | * Can be in % or px each. 53 | */ 54 | size?: { 55 | // Default "25" 56 | initial?: number; 57 | // Default "5" 58 | min?: number; 59 | // Default "95" 60 | max?: number; 61 | }; 62 | 63 | /** 64 | * The container to host the properties panel. By default, a styled div is created. If you 65 | * pass this option, make sure you set an ID and pass it via the `containerId` prop. 66 | * Pass `false` to prevent the rendering of this component. 67 | */ 68 | container?: ReactNode; 69 | 70 | /** 71 | * The ID of the container to host the properties panel. Only required if you want to 72 | * use your own container. 73 | */ 74 | containerId?: string; 75 | 76 | /** 77 | * The class name applied to the host of the properties panel. 78 | */ 79 | className?: string; 80 | 81 | /** 82 | * The element templates to import into the modeler. 83 | */ 84 | elementTemplates?: any[]; 85 | } 86 | 87 | export interface BpmnModelerOptions { 88 | /** 89 | * Will receive the reference to the modeler instance. 90 | */ 91 | refs?: MutableRefObject[]; 92 | 93 | /** 94 | * The initial, minimum, and maximum sizes of the modeler panel. 95 | * Can be in % or px each. 96 | */ 97 | size?: { 98 | // Default "75" 99 | initial?: number; 100 | // Default "5" 101 | min?: number; 102 | // Default "95" 103 | max?: number; 104 | }; 105 | 106 | /** 107 | * The container to host the modeler. By default, a styled div is created. If you pass 108 | * this option, make sure you set an ID and pass it via the `containerId` prop. 109 | * Pass `false` to prevent the rendering of this component. 110 | */ 111 | container?: ReactNode; 112 | 113 | /** 114 | * The ID of the container to host the modeler. Only required if you want to use your own 115 | * container. 116 | */ 117 | containerId?: string; 118 | 119 | /** 120 | * The class name applied to the host of the modeler. 121 | */ 122 | className?: string; 123 | } 124 | 125 | export interface BpmnEditorProps { 126 | /** 127 | * The class name applied to the root element. 128 | */ 129 | className?: string; 130 | 131 | /** 132 | * The XML to display in the editor. 133 | */ 134 | xml: string; 135 | 136 | /** 137 | * Whether this editor is currently active and visible to the user. 138 | */ 139 | active: boolean; 140 | 141 | /** 142 | * Called whenever an event occurs. 143 | */ 144 | onEvent: (event: Event) => void; 145 | 146 | /** 147 | * The options passed to the bpmn-js modeler. 148 | * 149 | * CAUTION: When this option object is changed, the old editor instance will be destroyed 150 | * and a new one will be created without automatic saving! 151 | */ 152 | bpmnJsOptions?: any; 153 | 154 | /** 155 | * The options to control the appearance of the properties panel. 156 | * 157 | * CAUTION: When this option object is changed, the old editor instance will be destroyed 158 | * and a new one will be created without automatic saving! 159 | */ 160 | propertiesPanelOptions?: BpmnPropertiesPanelOptions; 161 | 162 | /** 163 | * The options to control the appearance of the modeler. 164 | * 165 | * CAUTION: When this option object is changed, the old editor instance will be destroyed 166 | * and a new one will be created without automatic saving! 167 | */ 168 | modelerOptions?: BpmnModelerOptions; 169 | } 170 | 171 | const useStyles = tss.create(() => ({ 172 | modeler: { 173 | height: "100%", 174 | }, 175 | propertiesPanel: { 176 | height: "100%", 177 | "&>div": { 178 | height: "100%", 179 | overflow: "auto", 180 | }, 181 | }, 182 | hidden: { 183 | display: "none", 184 | }, 185 | modelerOnly: { 186 | height: "100%", 187 | }, 188 | })); 189 | 190 | const BpmnEditor: React.FC = props => { 191 | const { classes, cx } = useStyles(); 192 | 193 | const { 194 | active, 195 | xml, 196 | onEvent, 197 | className, 198 | bpmnJsOptions, 199 | modelerOptions, 200 | propertiesPanelOptions, 201 | } = props; 202 | 203 | const [initializeCount, setInitializeCount] = useState(0); 204 | const ref = useRef(undefined); 205 | 206 | const handleEvent = useCallback( 207 | (event: string, data: any) => { 208 | // TODO: Should bpmn-js events only be forwarded if the editor is currently active? 209 | onEvent(createBpmnIoEvent(event, data)); 210 | 211 | if (!active) { 212 | return; 213 | } 214 | 215 | if (event === "elementTemplates.errors") { 216 | onEvent( 217 | createNotificationEvent( 218 | "Importing element templates failed. Check console for details.", 219 | "error", 220 | ), 221 | ); 222 | console.error("Importing element templates failed.", data); 223 | } 224 | 225 | /** 226 | * If the event should trigger a UI update required event, do it. 227 | */ 228 | if (event && UI_UPDATE_REQUIRED_EVENTS.includes(event)) { 229 | onEvent(createUIUpdateRequiredEvent(active)); 230 | } 231 | 232 | /** 233 | * If the event should trigger a content saved event, do it. 234 | */ 235 | if (event && CONTENT_SAVED_EVENT.includes(event) && ref.current) { 236 | ref.current 237 | .save() 238 | .then(saved => { 239 | onEvent( 240 | createContentSavedEvent( 241 | saved.xml, 242 | saved.svg, 243 | "diagram.changed", 244 | ), 245 | ); 246 | }) 247 | .catch(e => { 248 | console.warn("Could not save document", e); 249 | }); 250 | } 251 | }, 252 | [active, onEvent], 253 | ); 254 | 255 | /** 256 | * Instantiates the modeler and properties panel. Only happens once on mount. 257 | */ 258 | useEffect(() => { 259 | const modeler = new CustomBpmnJsModeler({ 260 | container: modelerOptions?.containerId ?? "#bpmnview", 261 | propertiesPanel: propertiesPanelOptions?.hidden 262 | ? undefined 263 | : (propertiesPanelOptions?.containerId ?? "#bpmnprop"), 264 | bpmnJsOptions: bpmnJsOptions, 265 | }); 266 | 267 | ref.current = modeler; 268 | if (modelerOptions?.refs) { 269 | modelerOptions.refs.forEach(r => { 270 | r.current = modeler; 271 | }); 272 | } 273 | 274 | setInitializeCount(count => count + 1); 275 | 276 | return () => { 277 | modeler.unregisterGlobalEventListener(handleEvent); 278 | modeler.destroy(); 279 | ref.current = undefined; 280 | if (modelerOptions?.refs) { 281 | modelerOptions.refs.forEach(r => { 282 | r.current = undefined; 283 | }); 284 | } 285 | }; 286 | }, [ 287 | handleEvent, 288 | bpmnJsOptions, 289 | propertiesPanelOptions, 290 | modelerOptions?.containerId, 291 | ]); 292 | 293 | /** 294 | * Imports the specified XML. The following steps are executed: 295 | * 296 | * 1. Export the currently loaded XML. 297 | * 2. Check if it is different from the specified XML. 298 | * 3. Import the specified XML if it has changed. 299 | * 4. Show any errors or warnings that occurred during import. 300 | */ 301 | const importXml = useCallback( 302 | async (newXml: string) => { 303 | if (ref.current) { 304 | try { 305 | const currentXml = await ref.current?.saveXML({ 306 | format: true, 307 | preamble: false, 308 | }); 309 | 310 | if (newXml === currentXml.xml) { 311 | // XML has not changed 312 | return; 313 | } 314 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 315 | } catch (e) { 316 | // The editor has not yet loaded any content 317 | // => no definitions loaded, ignores the error 318 | } 319 | 320 | try { 321 | const result = ref.current.importXML(newXml); 322 | const count = result.warnings?.length ?? 0; 323 | if (count > 0) { 324 | console.log("Imported with warnings", result.warnings); 325 | onEvent( 326 | createNotificationEvent( 327 | `Imported with ${count} warning${count === 1 ? "" : "s"}. See console for details.`, 328 | "warning", 329 | ), 330 | ); 331 | } 332 | } catch (e) { 333 | console.error("Could not import XML", e); 334 | onEvent( 335 | createNotificationEvent( 336 | "Could not import changed XML. Is it invalid? See console for details.", 337 | "error", 338 | ), 339 | ); 340 | } 341 | } 342 | }, 343 | [onEvent], 344 | ); 345 | 346 | /** 347 | * Imports the document XML whenever it changes. 348 | */ 349 | useEffect(() => { 350 | if (initializeCount > 0) { 351 | importXml(xml).catch(e => { 352 | console.error("Could not import XML", e); 353 | onEvent( 354 | createNotificationEvent( 355 | "Could not import changed XML. Is it invalid? See console for details.", 356 | "error", 357 | ), 358 | ); 359 | }); 360 | } 361 | }, [xml, importXml, initializeCount, onEvent]); 362 | 363 | useEffect(() => { 364 | const modeler = ref.current; 365 | if (initializeCount > 0 && modeler) { 366 | modeler.registerGlobalEventListener(handleEvent); 367 | return () => modeler.unregisterGlobalEventListener(handleEvent); 368 | } 369 | return undefined; 370 | }, [initializeCount, handleEvent]); 371 | 372 | /** 373 | * Imports the specified element templates whenever they change. 374 | */ 375 | useEffect(() => { 376 | if (!propertiesPanelOptions?.hidden) { 377 | ref.current?.importElementTemplates( 378 | propertiesPanelOptions?.elementTemplates ?? [], 379 | ); 380 | } 381 | }, [propertiesPanelOptions?.hidden, propertiesPanelOptions?.elementTemplates]); 382 | 383 | const onPropertiesPanelWidthChanged = useCallback( 384 | (sizes: number[]) => { 385 | onEvent(createPropertiesPanelResizedEvent(sizes[1])); 386 | }, 387 | [onEvent], 388 | ); 389 | 390 | const modelerContainer: ReactNode = modelerOptions?.container ?? ( 391 |
392 | ); 393 | 394 | const propertiesPanelContainer: ReactNode = propertiesPanelOptions?.container ?? ( 395 |
399 | ); 400 | 401 | if (propertiesPanelOptions?.hidden) { 402 | return ( 403 |
{modelerContainer}
404 | ); 405 | } 406 | 407 | return ( 408 | 416 | 421 | {modelerContainer} 422 | 423 | 430 | 435 | {propertiesPanelContainer} 436 | 437 | 438 | ); 439 | }; 440 | 441 | export default BpmnEditor; 442 | -------------------------------------------------------------------------------- /src/editor/DmnEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | MutableRefObject, 3 | ReactNode, 4 | useCallback, 5 | useEffect, 6 | useRef, 7 | useState, 8 | } from "react"; 9 | import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels"; 10 | 11 | import CustomDmnJsModeler, { DmnView } from "../bpmnio/dmn/CustomDmnJsModeler"; 12 | import { createBpmnIoEvent } from "../events/bpmnio/BpmnIoEvents"; 13 | import { Event } from "../events"; 14 | import { createContentSavedEvent } from "../events/modeler/ContentSavedEvent"; 15 | import { createDmnViewsChangedEvent } from "../events/modeler/DmnViewsChangedEvent"; 16 | import { createNotificationEvent } from "../events/modeler/NotificationEvent"; 17 | import { createPropertiesPanelResizedEvent } from "../events/modeler/PropertiesPanelResizedEvent"; 18 | import { createUIUpdateRequiredEvent } from "../events/modeler/UIUpdateRequiredEvent"; 19 | import { tss } from "tss-react"; 20 | 21 | /** 22 | * The events that trigger a UI update required event. 23 | */ 24 | const UI_UPDATE_REQUIRED_EVENTS = [ 25 | "import.done", 26 | "saveXML.done", 27 | "attach", 28 | "dmn.views.changed", 29 | "views.changed", 30 | "view.contentChanged", 31 | "view.selectionChanged", 32 | "view.directEditingChanged", 33 | "propertiesPanel.focusin", 34 | "propertiesPanel.focusout", 35 | ]; 36 | 37 | /** 38 | * The events that trigger a content saved event. 39 | */ 40 | const CONTENT_SAVED_EVENT = [ 41 | "import.done", 42 | "view.contentChanged", 43 | "dmn.views.changed", 44 | "views.changed", 45 | "elements.changed", 46 | ]; 47 | 48 | export interface DmnPropertiesPanelOptions { 49 | /** 50 | * This option disables the properties panel. 51 | */ 52 | hidden?: boolean; 53 | 54 | /** 55 | * The initial, minimum, and maximum sizes of the properties panel. 56 | * Can be in % or px each. 57 | */ 58 | size?: { 59 | // Default "25" 60 | initial?: number; 61 | // Default "5" 62 | min?: number; 63 | // Default "95" 64 | max?: number; 65 | }; 66 | 67 | /** 68 | * The container to host the properties panel. By default, a styled div is created. If you 69 | * pass this option, make sure you set an ID and pass it via the `containerId` prop. 70 | * Pass `false` to prevent the rendering of this component. 71 | */ 72 | container?: ReactNode; 73 | 74 | /** 75 | * The ID of the container to host the properties panel. Only required if you want to 76 | * use your own container. 77 | */ 78 | containerId?: string; 79 | 80 | /** 81 | * The class name applied to the host of the properties panel. 82 | */ 83 | className?: string; 84 | } 85 | 86 | export interface DmnModelerOptions { 87 | /** 88 | * Will receive the reference to the modeler instance. 89 | */ 90 | refs?: MutableRefObject[]; 91 | 92 | /** 93 | * The initial, minimum, and maximum sizes of the modeler panel. 94 | * Can be in % or px each. 95 | */ 96 | size?: { 97 | // Default "75" 98 | initial?: number; 99 | // Default "95" 100 | min?: number; 101 | // Default "5" 102 | max?: number; 103 | }; 104 | 105 | /** 106 | * The container to host the modeler. By default, a styled div is created. If you pass 107 | * this option, make sure you set an ID and pass it via the `containerId` prop. 108 | * Pass `false` to prevent the rendering of this component. 109 | */ 110 | container?: ReactNode; 111 | 112 | /** 113 | * The ID of the container to host the modeler. Only required if you want to use your own 114 | * container. 115 | */ 116 | containerId?: string; 117 | 118 | /** 119 | * The class name applied to the host of the modeler. 120 | */ 121 | className?: string; 122 | } 123 | 124 | export interface DmnEditorProps { 125 | /** 126 | * The XML to display in the editor. 127 | */ 128 | xml: string; 129 | 130 | /** 131 | * Whether this editor is currently active and visible to the user. 132 | */ 133 | active: boolean; 134 | 135 | /** 136 | * Called whenever an event occurs. 137 | */ 138 | onEvent: (event: Event) => void; 139 | 140 | /** 141 | * The class name applied to the host of the properties panel. 142 | */ 143 | className?: string; 144 | 145 | /** 146 | * The options passed to the dmn-js modeler. 147 | * 148 | * CAUTION: When this option object is changed, the old editor instance will be destroyed 149 | * and a new one will be created without automatic saving! 150 | */ 151 | dmnJsOptions?: any; 152 | 153 | /** 154 | * The options to control the appearance of the properties panel. 155 | * 156 | * CAUTION: When this option object is changed, the old editor instance will be destroyed 157 | * and a new one will be created without automatic saving! 158 | */ 159 | propertiesPanelOptions?: DmnPropertiesPanelOptions; 160 | 161 | /** 162 | * The options to control the appearance of the modeler. 163 | * 164 | * CAUTION: When this option object is changed, the old editor instance will be destroyed 165 | * and a new one will be created without automatic saving! 166 | */ 167 | modelerOptions?: DmnModelerOptions; 168 | } 169 | 170 | const useStyles = tss.create(() => ({ 171 | modeler: { 172 | height: "100%", 173 | }, 174 | propertiesPanel: { 175 | height: "100%", 176 | "&>div": { 177 | height: "100%", 178 | }, 179 | }, 180 | hidden: { 181 | display: "none", 182 | }, 183 | modelerOnly: { 184 | height: "100%", 185 | }, 186 | })); 187 | 188 | const DmnEditor: React.FC = props => { 189 | const { classes, cx } = useStyles(); 190 | 191 | const { 192 | xml, 193 | active, 194 | onEvent, 195 | dmnJsOptions, 196 | propertiesPanelOptions, 197 | modelerOptions, 198 | className, 199 | } = props; 200 | 201 | const [activeView, setActiveView] = useState(undefined); 202 | const [initializeCount, setInitializeCount] = useState(0); 203 | const ref = useRef(null); 204 | 205 | const handleEvent = useCallback( 206 | (event: string, data: any) => { 207 | // TODO: Should dmn-js events only be forwarded if the editor is currently active? 208 | onEvent(createBpmnIoEvent(event, data)); 209 | 210 | if (!active) { 211 | return; 212 | } 213 | 214 | if (event === "views.changed") { 215 | setActiveView(data.activeView); 216 | onEvent(createDmnViewsChangedEvent(data.views, data.activeView)); 217 | } 218 | 219 | /** 220 | * If the event should trigger a UI update required event, do it. 221 | */ 222 | if (event && UI_UPDATE_REQUIRED_EVENTS.includes(event)) { 223 | onEvent(createUIUpdateRequiredEvent(active)); 224 | } 225 | 226 | /** 227 | * If the event should trigger a content saved event, do it. 228 | */ 229 | if (event && CONTENT_SAVED_EVENT.includes(event) && ref.current) { 230 | ref.current 231 | .save({ format: true }) 232 | .then(saved => { 233 | // TODO: Save SVG (but which viewer?) 234 | onEvent( 235 | createContentSavedEvent( 236 | saved.xml, 237 | undefined, 238 | "diagram.changed", 239 | ), 240 | ); 241 | }) 242 | .catch(e => { 243 | console.warn("Could not save document", e); 244 | }); 245 | } 246 | }, 247 | [active, onEvent], 248 | ); 249 | 250 | const viewsChangedCallback = useCallback( 251 | (event: any, data: any) => { 252 | void handleEvent(event.type, data); 253 | if (ref.current?.getActiveViewer()) { 254 | void ref.current?.registerGlobalEventListener(handleEvent); 255 | return () => ref.current?.unregisterGlobalEventListener(handleEvent); 256 | } 257 | return undefined; 258 | }, 259 | [handleEvent], 260 | ); 261 | 262 | /** 263 | * Instantiates the modeler and properties panel. Only happens once on mount. 264 | */ 265 | useEffect(() => { 266 | const modeler = new CustomDmnJsModeler({ 267 | container: modelerOptions?.containerId ?? "#dmnview", 268 | propertiesPanel: propertiesPanelOptions?.hidden 269 | ? undefined 270 | : (propertiesPanelOptions?.containerId ?? "#dmnprop"), 271 | dmnJsOptions: dmnJsOptions, 272 | }); 273 | 274 | ref.current = modeler; 275 | if (modelerOptions?.refs) { 276 | modelerOptions.refs.forEach(r => { 277 | r.current = modeler; 278 | }); 279 | } 280 | 281 | setInitializeCount(cur => cur + 1); 282 | 283 | return () => { 284 | if (modelerOptions?.refs) { 285 | modelerOptions.refs.forEach(r => { 286 | r.current = undefined; 287 | }); 288 | } 289 | modeler.destroy(); 290 | }; 291 | }, [dmnJsOptions, modelerOptions?.containerId, propertiesPanelOptions]); 292 | 293 | useEffect(() => { 294 | const modeler = ref.current; 295 | modeler?.on("views.changed", viewsChangedCallback); 296 | return () => modeler?.off("views.changed", viewsChangedCallback); 297 | }, [viewsChangedCallback]); 298 | 299 | /** 300 | * Imports the specified XML. The following steps are executed: 301 | * 302 | * 1. Export the currently loaded XML. 303 | * 2. Check if it is different from the specified XML. 304 | * 3. Import the specified XML if it has changed. 305 | * 4. Show any errors or warnings that occurred during import. 306 | */ 307 | const importXml = useCallback( 308 | async (newXml: string, open = false): Promise => { 309 | if (ref.current) { 310 | try { 311 | const currentXml = await ref.current?.save({ 312 | format: true, 313 | }); 314 | 315 | if (newXml === currentXml.xml) { 316 | // XML has not changed 317 | return; 318 | } 319 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 320 | } catch (e) { 321 | // The editor has not yet loaded any content 322 | // ⇒ no definitions loaded, ignore the error 323 | } 324 | 325 | try { 326 | const result = await ref.current.import(newXml, open); 327 | const count = result.warnings.length; 328 | if (count > 0) { 329 | console.log("Imported with warnings", result.warnings); 330 | onEvent( 331 | createNotificationEvent( 332 | `Imported with ${count} warning${count === 1 ? "" : "s"}. See console for details.`, 333 | "warning", 334 | ), 335 | ); 336 | } 337 | } catch (e) { 338 | console.error("Could not import XML", e); 339 | onEvent( 340 | createNotificationEvent( 341 | "Could not import changed XML. Is it invalid? See console for details.", 342 | "error", 343 | ), 344 | ); 345 | } 346 | } 347 | }, 348 | [onEvent], 349 | ); 350 | 351 | /** 352 | * Imports the document XML whenever it changes. 353 | */ 354 | useEffect(() => { 355 | if (initializeCount > 0) { 356 | // Only open the view on first render 357 | void importXml(xml, initializeCount === 1); 358 | } 359 | }, [xml, importXml, initializeCount]); 360 | 361 | const onPropertiesPanelWidthChanged = useCallback( 362 | (sizes: number[]) => { 363 | onEvent(createPropertiesPanelResizedEvent(sizes[1])); 364 | }, 365 | [onEvent], 366 | ); 367 | 368 | const modelerContainer: ReactNode = modelerOptions?.container ?? ( 369 |
370 | ); 371 | 372 | const propertiesPanelContainer: ReactNode = propertiesPanelOptions?.container ?? ( 373 |
377 | ); 378 | 379 | if (propertiesPanelOptions?.hidden) { 380 | return ( 381 |
{modelerContainer}
382 | ); 383 | } 384 | 385 | return ( 386 | 394 | 399 | {modelerContainer} 400 | 401 | 408 | 413 | {propertiesPanelContainer} 414 | 415 | 416 | ); 417 | }; 418 | 419 | export default DmnEditor; 420 | -------------------------------------------------------------------------------- /src/editor/XmlEditor.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | MutableRefObject, 3 | useCallback, 4 | useEffect, 5 | useMemo, 6 | useState, 7 | } from "react"; 8 | import deepmerge from "deepmerge"; 9 | import Editor, { EditorProps, loader } from "@monaco-editor/react"; 10 | import * as monaco from "monaco-editor"; 11 | import { tss } from "tss-react"; 12 | 13 | loader.config({ monaco }); 14 | 15 | export interface MonacoOptions { 16 | /** 17 | * Will receive the reference to the editor instance, the monaco instance, and the container 18 | * element. 19 | */ 20 | refs?: MutableRefObject[]; 21 | 22 | /** 23 | * Additional props to pass to the editor component. This will override the defaults defined by 24 | * this component. 25 | */ 26 | props?: Partial; 27 | 28 | /** 29 | * Additional options to pass to the editor component. This will override the defaults defined 30 | * by this component. 31 | */ 32 | options?: Partial; 33 | } 34 | 35 | export interface XmlEditorProps { 36 | /** 37 | * The XML to display in the editor. 38 | */ 39 | xml: string; 40 | 41 | /** 42 | * Whether this editor is currently active and visible to the user. 43 | */ 44 | active: boolean; 45 | 46 | /** 47 | * Callback to execute whenever the diagram's XML changes. 48 | * 49 | * @param xml The new XML 50 | */ 51 | onChanged: (xml: string) => void; 52 | 53 | /** 54 | * The options to pass to the monaco editor. 55 | */ 56 | monacoOptions?: MonacoOptions; 57 | 58 | /** 59 | * The class name applied to the host of the modeler. 60 | */ 61 | className?: string; 62 | } 63 | 64 | const useStyles = tss.create(() => ({ 65 | root: { 66 | height: "100%", 67 | "&>div": { 68 | height: "100%", 69 | overflow: "hidden", 70 | }, 71 | }, 72 | hidden: { 73 | display: "none", 74 | }, 75 | })); 76 | 77 | const XmlEditor: React.FC = props => { 78 | const { classes, cx } = useStyles(); 79 | 80 | const { xml, onChanged, active, monacoOptions, className } = props; 81 | 82 | const [xmlEditorShown, setXmlEditorShown] = useState(false); 83 | 84 | const onEditorMount = useCallback( 85 | (editor: monaco.editor.IStandaloneCodeEditor) => { 86 | monacoOptions?.refs?.forEach(e => { 87 | e.current = editor; 88 | }); 89 | }, 90 | [monacoOptions], 91 | ); 92 | 93 | const onXmlChanged = useCallback( 94 | (value?: string) => { 95 | if (active) { 96 | const xmlValue = value ?? xml; // value is empty when the editor initialized 97 | onChanged(xmlValue); 98 | } 99 | }, 100 | [xml, active, onChanged], 101 | ); 102 | 103 | /** 104 | * Initializes the editor when it is visible for the first time. If it is shown when it is 105 | * first mounted, the size is wrong. 106 | */ 107 | useEffect(() => { 108 | if (active && !xmlEditorShown) { 109 | setXmlEditorShown(true); 110 | } 111 | }, [xmlEditorShown, active]); 112 | 113 | const options = useMemo( 114 | () => 115 | deepmerge( 116 | { 117 | theme: "vs-light", 118 | wordWrap: "on", 119 | wrappingIndent: "deepIndent", 120 | scrollBeyondLastLine: false, 121 | minimap: { 122 | enabled: false, 123 | }, 124 | }, 125 | monacoOptions?.options ?? {}, 126 | ), 127 | [monacoOptions?.options], 128 | ); 129 | 130 | /** 131 | * Only show the editor once it has become active or the editor size will be wrong. 132 | */ 133 | if (!xmlEditorShown) { 134 | return null; 135 | } 136 | 137 | return ( 138 |
139 | 149 |
150 | ); 151 | }; 152 | 153 | export default React.memo(XmlEditor); 154 | -------------------------------------------------------------------------------- /src/events/Events.ts: -------------------------------------------------------------------------------- 1 | export type ModelerEventType = 2 | | "content.saved" 3 | | "ui.update.required" 4 | | "notification" 5 | | "properties.panel.resized" 6 | | "dmn.views.changed"; 7 | 8 | type EventSource = 9 | | "bpmnio" 10 | | "modeler"; 11 | 12 | export interface Event { 13 | source: Source; 14 | event: Source extends "modeler" ? ModelerEventType : string; 15 | data: Data; 16 | } 17 | -------------------------------------------------------------------------------- /src/events/bpmnio/BpmnIoEvents.ts: -------------------------------------------------------------------------------- 1 | import { Event } from "../Events"; 2 | 3 | export const createBpmnIoEvent = (event: string, data: any): Event => ({ 4 | source: "bpmnio", 5 | event: event, 6 | data: data, 7 | }); 8 | 9 | export const isBpmnIoEvent = (event: Event): event is Event => 10 | event.source === "bpmnio"; 11 | -------------------------------------------------------------------------------- /src/events/index.ts: -------------------------------------------------------------------------------- 1 | import { isBpmnIoEvent } from "./bpmnio/BpmnIoEvents"; 2 | import { Event } from "./Events"; 3 | import { ContentSavedReason, isContentSavedEvent } from "./modeler/ContentSavedEvent"; 4 | import { isDmnViewsChangedEvent } from "./modeler/DmnViewsChangedEvent"; 5 | import { isNotificationEvent } from "./modeler/NotificationEvent"; 6 | import { isPropertiesPanelResizedEvent } from "./modeler/PropertiesPanelResizedEvent"; 7 | import { isUIUpdateRequiredEvent } from "./modeler/UIUpdateRequiredEvent"; 8 | 9 | export { 10 | isBpmnIoEvent, 11 | isUIUpdateRequiredEvent, 12 | isPropertiesPanelResizedEvent, 13 | isNotificationEvent, 14 | isDmnViewsChangedEvent, 15 | isContentSavedEvent 16 | }; 17 | 18 | export type { 19 | Event, 20 | ContentSavedReason 21 | }; 22 | -------------------------------------------------------------------------------- /src/events/modeler/ContentSavedEvent.ts: -------------------------------------------------------------------------------- 1 | import { Event } from "../Events"; 2 | 3 | const EventName = "content.saved"; 4 | 5 | export type ContentSavedReason = 6 | /** 7 | * The diagram inside the bpmnjs / dmnjs editor has been changed by the user. 8 | */ 9 | | "diagram.changed" 10 | 11 | /** 12 | * The user has changed the XML inside the XML editor. 13 | */ 14 | | "xml.changed" 15 | 16 | /** 17 | * The view has been changed, e.g., from the bpmnjs editor to the XML editor or the other way. 18 | */ 19 | | "view.changed"; 20 | 21 | /** 22 | * Indicates that the content of the model has changed for some reason. The reasons are documented 23 | * above. 24 | */ 25 | export interface ContentSavedEventData { 26 | /** 27 | * The new XML model. 28 | */ 29 | xml: string; 30 | 31 | /** 32 | * The new SVG model. Only filled if the reason for the change is the bpmnjs / dmnjs editor. 33 | */ 34 | svg: string | undefined; 35 | 36 | /** 37 | * The reason for the change. 38 | */ 39 | reason: ContentSavedReason; 40 | } 41 | 42 | export const createContentSavedEvent = ( 43 | xml: string, 44 | svg: string | undefined, 45 | reason: ContentSavedReason, 46 | ): Event => ({ 47 | source: "modeler", 48 | event: EventName, 49 | data: { 50 | xml, 51 | svg, 52 | reason, 53 | }, 54 | }); 55 | 56 | export const isContentSavedEvent = ( 57 | event: Event, 58 | ): event is Event => 59 | event.source === "modeler" && event.event === EventName; 60 | -------------------------------------------------------------------------------- /src/events/modeler/DmnViewsChangedEvent.ts: -------------------------------------------------------------------------------- 1 | import { DmnView } from "../../bpmnio/dmn/CustomDmnJsModeler"; 2 | import type { Event } from "../Events"; 3 | 4 | const EventName = "dmn.views.changed"; 5 | 6 | /** 7 | * Indicates that the views available or the selected view in the DMN editor have changed. 8 | */ 9 | export interface DmnViewsChangedEventData { 10 | /** 11 | * The list of all available views. 12 | */ 13 | views: DmnView[]; 14 | 15 | /** 16 | * The currently active view. 17 | */ 18 | activeView: DmnView | undefined; 19 | } 20 | 21 | export const createDmnViewsChangedEvent = ( 22 | views: DmnView[], 23 | activeView: DmnView | undefined 24 | ): Event => ({ 25 | source: "modeler", 26 | event: EventName, 27 | data: { 28 | views, 29 | activeView 30 | } 31 | }); 32 | 33 | export const isDmnViewsChangedEvent = ( 34 | event: Event 35 | ): event is Event => ( 36 | event.source === "modeler" && event.event === EventName 37 | ); 38 | -------------------------------------------------------------------------------- /src/events/modeler/NotificationEvent.ts: -------------------------------------------------------------------------------- 1 | import type { Event } from "../Events"; 2 | 3 | const EventName = "notification"; 4 | 5 | export type NotificationSeverity = 6 | | "success" 7 | | "info" 8 | | "warning" 9 | | "error"; 10 | 11 | /** 12 | * Indicates any notification, could be a success, info, warning, or failure. This may be displayed 13 | * by the application directly to the user, if desired. 14 | */ 15 | export interface NotificationEventData { 16 | /** 17 | * The message text. A human readable string. 18 | */ 19 | message: string; 20 | 21 | /** 22 | * The severity of the message. 23 | */ 24 | severity: NotificationSeverity; 25 | } 26 | 27 | export const createNotificationEvent = ( 28 | message: string, 29 | severity: NotificationSeverity 30 | ): Event => ({ 31 | source: "modeler", 32 | event: EventName, 33 | data: { 34 | message, 35 | severity 36 | } 37 | }); 38 | 39 | export const isNotificationEvent = (event: Event): event is Event => ( 40 | event.source === "modeler" && event.event === EventName 41 | ); 42 | -------------------------------------------------------------------------------- /src/events/modeler/PropertiesPanelResizedEvent.ts: -------------------------------------------------------------------------------- 1 | import { Event } from "../Events"; 2 | 3 | const EventName = "properties.panel.resized"; 4 | 5 | /** 6 | * Indicates that the width of the properties panel has changed. 7 | */ 8 | export interface PropertiesPanelResizedEventData { 9 | /** 10 | * The new width of the properties panel in px. 11 | */ 12 | width: number; 13 | } 14 | 15 | export const createPropertiesPanelResizedEvent = ( 16 | width: number 17 | ): Event => ({ 18 | source: "modeler", 19 | event: EventName, 20 | data: { width } 21 | }); 22 | 23 | export const isPropertiesPanelResizedEvent = ( 24 | event: Event 25 | ): event is Event => ( 26 | event.source === "modeler" && event.event === EventName 27 | ); 28 | -------------------------------------------------------------------------------- /src/events/modeler/UIUpdateRequiredEvent.ts: -------------------------------------------------------------------------------- 1 | import { Event } from "../Events"; 2 | 3 | const EventName = "ui.update.required"; 4 | 5 | /** 6 | * Indicates that something in the modeler has changed so that external UI that depends on modeler 7 | * state, such as button bars or menus, have to be updated. This event is only triggered for the 8 | * bpmnjs and dmnjs editors, but not for the XML editor. 9 | */ 10 | export interface UIUpdateRequiredEventData { 11 | /** 12 | * Whether the modeler instance that sent the event is currently active. 13 | */ 14 | isActive: boolean; 15 | } 16 | 17 | export const createUIUpdateRequiredEvent = ( 18 | isActive: boolean 19 | ): Event => ({ 20 | source: "modeler", 21 | event: EventName, 22 | data: { isActive } 23 | }); 24 | 25 | export const isUIUpdateRequiredEvent = ( 26 | event: Event 27 | ): event is Event => ( 28 | event.source === "modeler" && event.event === EventName 29 | ); 30 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import BpmnModeler from "./BpmnModeler"; 2 | import DmnModeler from "./DmnModeler"; 3 | 4 | export { 5 | BpmnModeler, 6 | DmnModeler 7 | }; 8 | 9 | export * from "./events"; 10 | export * from "./bpmnio"; 11 | -------------------------------------------------------------------------------- /src/types/bpmn-io.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@bpmn-io/properties-panel"; 2 | -------------------------------------------------------------------------------- /src/types/bpmn-js-element-templates.ts: -------------------------------------------------------------------------------- 1 | declare module "bpmn-js-element-templates"; -------------------------------------------------------------------------------- /src/types/bpmn-js-properties-panel.d.ts: -------------------------------------------------------------------------------- 1 | declare module "bpmn-js-properties-panel"; 2 | declare module "@bpmn-io/element-template-chooser"; 3 | -------------------------------------------------------------------------------- /src/types/bpmn-js.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | 3 | declare module "bpmn-js/lib/Modeler" { 4 | class Modeler { 5 | constructor(options: any); 6 | 7 | /** 8 | * Returns the requested component. 9 | * 10 | * @param component The name of the component to return 11 | */ 12 | get(component: string): any; 13 | 14 | /** 15 | * Imports the XML into the editor. 16 | * 17 | * @param xml The XML to import 18 | * @return List of import warnings 19 | * @throws If the import failed 20 | */ 21 | importXML(xml: string): ImportResponse; 22 | 23 | /** 24 | * Saves the editor content as XML and returns it. 25 | * 26 | * @param format Whether to format the output 27 | * @param preamble Whether to include the preamble `` 28 | * @return The editor content as XML 29 | */ 30 | saveXML({ format = false, preamble = false }): Promise<{ xml: string }>; 31 | 32 | /** 33 | * Saves the editor content as SVG and returns it. 34 | * 35 | * @return The editor content as SVG 36 | */ 37 | saveSVG(): Promise<{ svg: string }>; 38 | 39 | /** 40 | * Registers an event listener for bpmn-js. 41 | * 42 | * @param event The name of the event 43 | * @param handler The listener to register 44 | */ 45 | on(event: string, handler: (event: any & { type: string; }, data: any) => void); 46 | 47 | /** 48 | * Unregisters a previously registered listener for bpmn-js. 49 | * 50 | * @param event The name of the event 51 | * @param handler The previously registered listener to unregister 52 | */ 53 | off(event: string, handler: (event: any & { type: string; }, data: any) => void); 54 | 55 | /** 56 | * Destroys the modeler instance. 57 | */ 58 | destroy(); 59 | } 60 | 61 | export default Modeler; 62 | } 63 | 64 | interface ImportResponse { 65 | warnings: ImportWarning[]; 66 | } 67 | 68 | interface ImportWarning { 69 | error: Error; 70 | message: string; 71 | } 72 | -------------------------------------------------------------------------------- /src/types/diagram-js-origin.d.ts: -------------------------------------------------------------------------------- 1 | declare module "diagram-js-origin"; 2 | -------------------------------------------------------------------------------- /src/types/dmn-js-properties-panel.d.ts: -------------------------------------------------------------------------------- 1 | declare module "dmn-js-properties-panel"; 2 | -------------------------------------------------------------------------------- /src/types/dmn-js.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ 2 | 3 | declare module "dmn-js/lib/Modeler" { 4 | import { DmnView, DmnViewer } from "../bpmnio/dmn/CustomDmnJsModeler"; 5 | 6 | class Modeler { 7 | constructor(options: any); 8 | 9 | get(param: any): any; 10 | 11 | /** 12 | * Imports the passed XML into the editor. 13 | * 14 | * @param xml The XML to import 15 | * @param options Options for customization 16 | * open: Whether to open the diagram after importing 17 | */ 18 | importXML(xml: string, options?: { open: boolean }): Promise; 19 | 20 | saveXML({ format: boolean }): Promise; 21 | 22 | /** 23 | * Registers an event listener for bpmn-js. 24 | * 25 | * @param event The name of the event 26 | * @param handler The listener to register 27 | */ 28 | on(event: string, handler: (event: any, data: any) => void); 29 | 30 | /** 31 | * Unregisters a previously registered listener for bpmn-js. 32 | * 33 | * @param event The name of the event 34 | * @param handler The previously registered listener to unregister 35 | */ 36 | off(event: string, handler: (event: any, data: any) => void); 37 | 38 | /** 39 | * Returns the active viewer. 40 | */ 41 | getActiveViewer(): DmnViewer | undefined; 42 | 43 | /** 44 | * Returns all available views. 45 | */ 46 | getViews(): DmnView[]; 47 | 48 | /** 49 | * Returns the active view. 50 | */ 51 | getActiveView(): DmnView | undefined; 52 | 53 | /** 54 | * Opens the specified view. 55 | * 56 | * @param view The view to open 57 | */ 58 | open(view: DmnView): Promise; 59 | 60 | /** 61 | * Destroys the modeler instance. 62 | */ 63 | destroy(); 64 | } 65 | 66 | export type OpenResult = { 67 | /** 68 | * Warnings occurred during the opening. 69 | */ 70 | warnings: string[]; 71 | }; 72 | 73 | export type OpenError = { 74 | error: Error; 75 | /** 76 | * Warnings occurred during the opening. 77 | */ 78 | warnings: string[]; 79 | }; 80 | 81 | export type ImportXMLResult = { 82 | warnings: string[]; 83 | }; 84 | 85 | export type SaveXMLResult = { 86 | xml: string; 87 | }; 88 | 89 | export default Modeler; 90 | } 91 | -------------------------------------------------------------------------------- /static/screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Miragon/camunda-web-modeler/a7387a8dbf4f3aa0b1a35827732b53b8216520e8/static/screenshot.jpg -------------------------------------------------------------------------------- /tsconfig.index.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "." 5 | }, 6 | "include": [ 7 | "./index.ts" 8 | ], 9 | "exclude": [] 10 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "jsx": "react", 10 | "strict": true, 11 | "outDir": "dist", 12 | "module": "ESNext", 13 | "declaration": true, 14 | "skipLibCheck": true, 15 | "removeComments": false, 16 | "esModuleInterop": true, 17 | "isolatedModules": true, 18 | "resolveJsonModule": true, 19 | "moduleResolution": "node", 20 | "noFallthroughCasesInSwitch": true, 21 | "allowSyntheticDefaultImports": true, 22 | "forceConsistentCasingInFileNames": true 23 | }, 24 | "include": [ 25 | "src/**/*" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------