Adds example code from bpmn-js-example-angular-properties-panel

This commit is contained in:
Aaron Louie 2020-01-10 14:15:33 -05:00
parent 94dcd9788e
commit 169f7bb291
40 changed files with 4314 additions and 4293 deletions

323
.gitignore vendored
View File

@ -1,46 +1,305 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# Created by https://www.gitignore.io/api/node,angular,eclipse,intellij,visualstudiocode
# Edit at https://www.gitignore.io/?templates=node,angular,eclipse,intellij,visualstudiocode
### Angular ###
## Angular ##
# compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
dist/
tmp/
app/**/*.js
app/**/*.js.map
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
node_modules/
bower_components/
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
.idea/
# IDE - VSCode
# misc
.sass-cache/
connect.lock/
coverage/
libpeerconnection.log/
npm-debug.log
testem.log
typings/
# e2e
e2e/*.js
e2e/*.map
#System Files
.DS_Store/
### Eclipse ###
.metadata
bin/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.settings/
.loadpath
.recommenders
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# PyDev specific (Python IDE for Eclipse)
*.pydevproject
# CDT-specific (C/C++ Development Tooling)
.cproject
# CDT- autotools
.autotools
# Java annotation processor (APT)
.factorypath
# PDT-specific (PHP Development Tools)
.buildpath
# sbteclipse plugin
.target
# Tern plugin
.tern-project
# TeXlipse plugin
.texlipse
# STS (Spring Tool Suite)
.springBeans
# Code Recommenders
.recommenders/
# Annotation Processing
.apt_generated/
# Scala IDE specific (Scala & Java development for Eclipse)
.cache-main
.scala_dependencies
.worksheet
### Eclipse Patch ###
# Eclipse Core
.project
# JDT-specific (Eclipse Java Development Tools)
.classpath
# Annotation Processing
.apt_generated
.sts4-cache/
### Intellij ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
### Intellij Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
# .idea/misc.xml
# *.ipr
# Sonarlint plugin
.idea/**/sonarlint/
# SonarQube Plugin
.idea/**/sonarIssues.xml
# Markdown Navigator plugin
.idea/**/markdown-navigator.xml
.idea/**/markdown-navigator/
### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
jspm_packages/
# TypeScript v1 declaration files
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# rollup.js default build output
# Uncomment the public line if your project uses Gatsby
# https://nextjs.org/blog/next-9-1#public-directory-support
# https://create-react-app.dev/docs/using-the-public-folder/#docsNav
# public
# Storybook build outputs
.out
.storybook-out
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Temporary folders
temp/
### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
### VisualStudioCode Patch ###
# Ignore all local history of files
.history
# System Files
.DS_Store
Thumbs.db
# End of https://www.gitignore.io/api/node,angular,eclipse,intellij,visualstudiocode

View File

@ -1,6 +1,6 @@
# CrConnectBpmn
# BpmnJsAngular
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.15.
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.1.1.
## Development server

View File

@ -3,32 +3,30 @@
"version": 1,
"newProjectRoot": "projects",
"projects": {
"cr-connect-bpmn": {
"projectType": "application",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
},
"bpmn-js-angular": {
"root": "",
"sourceRoot": "src",
"projectType": "application",
"prefix": "app",
"schematics": {},
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/cr-connect-bpmn",
"outputPath": "dist/bpmn-js-angular",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"tsConfig": "src/tsconfig.app.json",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"node_modules/bpmn-js/dist/assets/diagram-js.css",
"node_modules/bpmn-js/dist/assets/bpmn-font/css/bpmn.css",
"src/styles.css"
],
"scripts": []
},
@ -54,11 +52,6 @@
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
@ -67,18 +60,18 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "cr-connect-bpmn:build"
"browserTarget": "bpmn-js-angular:build"
},
"configurations": {
"production": {
"browserTarget": "cr-connect-bpmn:build:production"
"browserTarget": "bpmn-js-angular:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "cr-connect-bpmn:build"
"browserTarget": "bpmn-js-angular:build"
}
},
"test": {
@ -86,44 +79,72 @@
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js",
"styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"src/styles.css"
],
"scripts": [],
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
"codeCoverageExclude": [
"src/testing/mocks/*.ts",
"src/app/_interfaces/*.ts",
"src/polyfills.ts",
"src/test.ts"
]
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
}
}
},
"bpmn-js-angular-e2e": {
"root": "e2e/",
"projectType": "application",
"prefix": "",
"architect": {
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "cr-connect-bpmn:serve"
"devServerTarget": "bpmn-js-angular:serve"
},
"configurations": {
"production": {
"devServerTarget": "cr-connect-bpmn:serve:production"
"devServerTarget": "bpmn-js-angular:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}},
"defaultProject": "cr-connect-bpmn"
}
}
},
"defaultProject": "bpmn-js-angular",
"schematics": {
"@schematics/angular:component": {
"style": "scss"
}
}
}

View File

@ -1,12 +1,11 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# This file is currently used by autoprefixer to adjust CSS to support the below specified browsers
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
#
# For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.
not IE 9-11

View File

@ -1,19 +1,20 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
'browserName': 'chrome',
'chromeOptions': {
'args': [
'--headless'
]
}
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
@ -25,7 +26,7 @@ exports.config = {
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
project: require('path').join(__dirname, './tsconfig.e2e.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}

View File

@ -1,5 +1,4 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
@ -8,16 +7,9 @@ describe('workspace-project App', () => {
page = new AppPage();
});
it('should display welcome message', () => {
it('should display diagram', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('cr-connect-bpmn app is running!');
expect(page.getDiagramContainer()).toBeTruthy();
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

View File

@ -2,10 +2,10 @@ import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
return browser.get('/');
}
getTitleText() {
return element(by.css('app-root .content span')).getText() as Promise<string>;
getDiagramContainer() {
return element(by.css('app-root .diagram-container'));
}
}

View File

@ -1,7 +1,7 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"outDir": "../out-tsc/app",
"module": "commonjs",
"target": "es5",
"types": [
@ -10,4 +10,4 @@
"node"
]
}
}
}

View File

@ -2,8 +2,13 @@
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
if (config.browsers.indexOf('ChromeHeadlessCI') !== -1) {
process.env.CHROME_BIN = require('puppeteer').executablePath();
}
config.set({
basePath: '',
basePath: 'src',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
@ -16,17 +21,22 @@ module.exports = function (config) {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/cr-connect-bpmn'),
dir: require('path').join(__dirname, './coverage'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
customLaunchers: {
ChromeHeadlessCI: {
base: 'ChromeHeadless',
flags: ['--no-sandbox', '--disable-gpu' ]
}
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
singleRun: false
});
};

6827
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
{
"name": "cr-connect-bpmn",
"name": "bpmn-js-angular",
"version": "0.0.0",
"scripts": {
"ng": "ng",
@ -7,44 +7,53 @@
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
"e2e": "./node_modules/protractor/bin/webdriver-manager update && ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.2.12",
"@angular/common": "~8.2.12",
"@angular/compiler": "~8.2.12",
"@angular/core": "~8.2.12",
"@angular/forms": "~8.2.12",
"@angular/platform-browser": "~8.2.12",
"@angular/platform-browser-dynamic": "~8.2.12",
"@angular/router": "~8.2.12",
"bpmn-js": "^6.1.1",
"@angular/animations": "^8.2.14",
"@angular/cdk": "^8.2.3",
"@angular/common": "^8.2.14",
"@angular/compiler": "^8.2.14",
"@angular/core": "^8.2.14",
"@angular/flex-layout": "^8.0.0-beta.27",
"@angular/forms": "^8.2.14",
"@angular/material": "^8.2.3",
"@angular/platform-browser": "^8.2.14",
"@angular/platform-browser-dynamic": "^8.2.14",
"@angular/router": "^8.2.14",
"bpmn-js": "^3.5.0",
"bpmn-js-properties-panel": "^0.33.1",
"camunda-bpmn-moddle": "^4.3.0",
"rxjs": "~6.4.0",
"core-js": "^2.6.11",
"diagram-js-minimap": "^2.0.3",
"file-saver": "^2.0.2",
"hammerjs": "^2.0.8",
"rxjs": "~6.5.4",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.15",
"@angular/cli": "~8.3.15",
"@angular/compiler-cli": "~8.2.12",
"@angular/language-service": "~8.2.12",
"@angular-devkit/build-angular": "^0.803.22",
"@angular/cli": "^8.3.22",
"@angular/compiler-cli": "^8.2.14",
"@angular/language-service": "^8.2.14",
"@types/jasmine": "^3.5.0",
"@types/jasminewd2": "^2.0.8",
"@types/node": "~8.9.4",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"codelyzer": "^5.0.1",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma": "^3.1.4",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"karma-coverage-istanbul-reporter": "^2.1.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "^5.4.2",
"puppeteer": "^1.20.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
"tslint": "~5.11.0",
"typescript": "~3.5.3",
"webpack-dev-server": "^3.10.1"
}
}

View File

@ -0,0 +1,7 @@
export interface BpmnWarning {
message?: string;
element?: any;
property?: string;
value?: string;
context?: any;
}

View File

@ -0,0 +1,8 @@
import {HttpErrorResponse} from '@angular/common/http';
import {BpmnWarning} from './bpmn-warning';
export interface ImportEvent {
type: string;
warnings?: BpmnWarning[];
error?: HttpErrorResponse | Error;
}

View File

@ -0,0 +1,23 @@
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {TestBed} from '@angular/core/testing';
import {ApiService} from './api.service';
describe('ApiService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule],
providers: [ApiService]
});
});
afterEach(() => {
const httpMock = TestBed.get(HttpTestingController);
httpMock.verify();
});
it('should be created', () => {
const service: ApiService = TestBed.get(ApiService);
expect(service).toBeTruthy();
});
});

View File

@ -0,0 +1,29 @@
import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable, throwError} from 'rxjs';
import {catchError} from 'rxjs/operators';
export interface ApiError {
code: string;
message: string;
}
@Injectable({
providedIn: 'root'
})
export class ApiService {
constructor(private httpClient: HttpClient) {
}
getBpmnXml(url: string): Observable<string> {
return this.httpClient
.get(url, {responseType: 'text'})
.pipe(catchError(this._handleError));
}
private _handleError(error: ApiError) {
return throwError(error.message || 'Could not complete your request; please try again later.');
}
}

View File

@ -1,11 +0,0 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
const routes: Routes = [];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule { }

View File

@ -1,49 +1,57 @@
<h1>Add bpmn-js to Angular</h1>
<mat-toolbar [ngClass]="{'expanded': expandToolbar}">
<mat-toolbar-row>
<button mat-button (click)="diagram.createNewDiagram()" title="Create new BPMN diagram"><mat-icon>insert_drive_file</mat-icon></button>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean a tincidunt dolor. Nam semper est ut mauris cursus,
vel laoreet nunc vehicula. Mauris nec semper ipsum. Integer feugiat mi at elementum fringilla. Nunc malesuada ante sed
ligula pretium, eu lobortis lorem rhoncus. Praesent et ipsum ante. Aliquam est mi, ultrices ut neque eget, mollis
pulvinar sem. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nullam
auctor porta sapien in venenatis.
</p>
<button mat-button [matMenuTriggerFor]="importMenu" title="Open diagram">
<mat-icon>folder</mat-icon>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #importMenu="matMenu">
<button mat-menu-item (click)="openMethod = 'file'; expandToolbar = true">
<mat-icon>code</mat-icon>
Open from XML File
</button>
<button mat-menu-item (click)="openMethod = 'url'; expandToolbar = true">
<mat-icon>link</mat-icon>
Open from URL
</button>
</mat-menu>
<p>
In fringilla suscipit odio quis tincidunt. Morbi felis elit, blandit a volutpat eget, rutrum ut mi. Duis ac orci at
nibh semper vehicula. Quisque vulputate, nibh vitae laoreet convallis, urna erat imperdiet eros, et interdum arcu arcu
ac risus. Maecenas in tellus nisi. Mauris quis lectus luctus, condimentum orci eu, rhoncus sapien. Aliquam at lobortis
mauris, eget facilisis arcu. Quisque metus purus, rhoncus et tortor id, ornare maximus neque. Donec et ex tincidunt,
malesuada lacus in, volutpat eros. Cras nisi arcu, dapibus quis odio sit amet, placerat blandit elit. Nullam at dictum
mauris. Nulla feugiat elit ex, a pulvinar velit faucibus a. Nulla tincidunt velit purus, in faucibus ligula ornare
venenatis.
</p>
<button mat-button [matMenuTriggerFor]="downloadMenu" title="Download diagram">
<mat-icon>save</mat-icon>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #downloadMenu="matMenu">
<button mat-menu-item (click)="diagram.saveSVG()"><mat-icon>image</mat-icon> Download SVG Image</button>
<button mat-menu-item (click)="diagram.saveXML()"><mat-icon>code</mat-icon> Download XML File</button>
</mat-menu>
</mat-toolbar-row>
<mat-toolbar-row *ngIf="expandToolbar">
<ng-container *ngIf="openMethod === 'file'">
<mat-form-field (click)="fileInput.click()">
<input matInput disabled [value]="getFileName()" type="text">
<mat-icon matSuffix>folder_open</mat-icon>
</mat-form-field>
<input hidden (change)="onFileSelected($event)" #fileInput type="file" id="file" accept="text/xml">
</ng-container>
<ng-container *ngIf="openMethod === 'url'">
<mat-form-field>
<input name="diagramUrl" [(ngModel)]="diagramUrl" matInput placeholder="Open diagram from URL" type="text">
</mat-form-field>
</ng-container>
<button mat-icon-button (click)="onSubmit($event)"><mat-icon>arrow_forward</mat-icon></button>
<span fxFlex></span>
<button mat-icon-button (click)="expandToolbar = false"><mat-icon>close</mat-icon></button>
</mat-toolbar-row>
</mat-toolbar>
<div fxLayout="column">
<div class="diagram-parent">
<app-diagram #diagram (importDone)="handleImported($event)"></app-diagram>
<div class="diagram-parent">
<app-diagram (importDone)="handleImported($event)" [url]="diagramUrl"></app-diagram>
<div *ngIf="importError" class="import-error">
<strong>Failed to render diagram: </strong>
{{ importError.message }}
<div *ngIf="importError" class="import-error">
<strong>Failed to render diagram: </strong>
{{ importError.message }}
</div>
</div>
</div>
<p>
Ut vestibulum, magna at molestie tempus, enim sem molestie est, vel tincidunt orci leo vel sapien. Vivamus convallis,
purus in rhoncus ultricies, mauris sapien condimentum sapien, vel porta urna est eu leo. Nam dui erat, venenatis at
metus ut, rutrum rutrum lacus. Fusce non eros vehicula, aliquet nibh vitae, bibendum dolor. Sed hendrerit lectus ex.
Mauris id venenatis metus. In hac habitasse platea dictumst.
</p>
<p>
Etiam molestie, erat eget tincidunt rutrum, nulla odio pulvinar nibh, in pulvinar felis mauris a nunc. Sed eu
ullamcorper ex, id tincidunt metus. Quisque lectus enim, vehicula eget aliquam sit amet, semper ut nisl. Vivamus
ultrices, cras odio massa, sagittis in luctus sed, mattis eu massa. Nam erat nibh, pulvinar vel hendrerit quis,
ultricies eu sapien. Integer fringilla sapien ex, et posuere arcu euismod et.
</p>
<p>
Suspendisse potenti. In ut lorem vitae dolor molestie commodo. Vivamus mattis gravida metus, porttitor ornare augue
lacinia suscipit. Duis condimentum fringilla nisl, non consequat odio mollis id. Nulla tincidunt interdum elit, sed
tristique ex laoreet lobortis. Suspendisse in felis vitae magna convallis semper. Nullam odio risus, venenatis eget
erat ac, pretium aliquet urna. Duis luctus lobortis maximus.
</p>

View File

@ -1,14 +1,30 @@
.diagram-parent {
height: 400px;
border: solid 3px #EEE;
height: 90vh;
position: relative;
border-top: 1px solid #CCCCCC;
}
.import-error {
color: red;
padding: 20px;
position: absolute;
top: 0;
left: 0;
}
.row {
padding: 2rem;
}
mat-form-field {
width: 800px;
}
mat-toolbar {
transition: all 200ms ease-in-out;
height: 64px;
}
mat-toolbar.expanded {
height: 128px;
}

View File

@ -1,35 +1,163 @@
import { TestBed, async } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import {HttpErrorResponse} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {DebugNode} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatFormFieldModule} from '@angular/material/form-field';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatMenuModule} from '@angular/material/menu';
import {MatToolbarModule} from '@angular/material/toolbar';
import {BrowserAnimationsModule, NoopAnimationsModule} from '@angular/platform-browser/animations';
import {BPMN_DIAGRAM, BPMN_DIAGRAM_WITH_WARNINGS} from '../testing/mocks/diagram.mocks';
import {BpmnWarning} from './_interfaces/bpmn-warning';
import {AppComponent} from './app.component';
import {DiagramComponent} from './diagram/diagram.component';
describe('AppComponent', () => {
let fixture: ComponentFixture<AppComponent>;
let component: DebugNode['componentInstance'];
let httpMock: HttpTestingController;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
RouterTestingModule
],
declarations: [
AppComponent
AppComponent,
DiagramComponent,
],
imports: [
BrowserAnimationsModule,
NoopAnimationsModule,
FormsModule,
MatIconModule,
MatInputModule,
MatMenuModule,
MatToolbarModule,
ReactiveFormsModule,
MatFormFieldModule,
HttpClientTestingModule,
]
}).compileComponents();
httpMock = TestBed.get(HttpTestingController);
fixture = TestBed.createComponent(AppComponent);
component = fixture.debugElement.componentInstance;
component.diagramComponent = TestBed.createComponent(DiagramComponent).componentInstance;
fixture.detectChanges();
}));
it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
it('should create', () => {
expect(component).toBeTruthy();
});
it(`should have as title 'cr-connect-bpmn'`, () => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('cr-connect-bpmn');
it('renders a diagram component', () => {
expect(fixture.nativeElement.querySelector('app-diagram')).toBeTruthy();
});
it('should render title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.debugElement.nativeElement;
expect(compiled.querySelector('.content span').textContent).toContain('cr-connect-bpmn app is running!');
it('sets an error message', () => {
const error = new HttpErrorResponse({error: 'ERROR'});
component.handleImported({
type: 'error',
error
});
expect(component.importError).toEqual(error);
});
it('sets warning messages', () => {
const warnings: BpmnWarning[] = [{
message: 'WARNING'
}];
component.handleImported({
type: 'success',
error: null,
warnings: warnings,
});
expect(component.importWarnings).toEqual(warnings);
});
it('loads a diagram from URL', () => {
component.diagramUrl = 'some-url';
component.openMethod = 'url';
const event = new MouseEvent('click');
component['onSubmit'](event);
const sReq = httpMock.expectOne(component.diagramUrl);
expect(sReq.request.method).toEqual('GET');
sReq.flush(BPMN_DIAGRAM);
});
it('loads a diagram from URL with warnings', () => {
component.diagramUrl = 'some-url';
component.openMethod = 'url';
const event = new MouseEvent('click');
component['onSubmit'](event);
const sReq = httpMock.expectOne(component.diagramUrl);
expect(sReq.request.method).toEqual('GET');
sReq.flush(BPMN_DIAGRAM_WITH_WARNINGS);
});
it('loads a diagram from File', () => {
const readFileSpy = spyOn(component, 'readFile').and.stub();
const newFile = new File([BPMN_DIAGRAM], 'filename.xml', {type: 'text/xml'});
component.diagramFile = newFile;
component.openMethod = 'file';
const event = new MouseEvent('click');
component['onSubmit'](event);
expect(readFileSpy).toHaveBeenCalledWith(newFile);
});
it('opens a diagram from File', () => {
const mockFileReader = {
target: {result: BPMN_DIAGRAM},
readAsText: (blob) => {
}
};
spyOn((window as any), 'FileReader').and.returnValue(mockFileReader);
spyOn(mockFileReader, 'readAsText').and.callFake((blob) => {
component.onLoad({target: {result: BPMN_DIAGRAM}});
});
const openDiagramSpy = spyOn(component.diagramComponent, 'openDiagram').and.stub();
const newFile = new File([BPMN_DIAGRAM], 'filename.xml', {type: 'text/xml'});
component.readFile(newFile);
expect(openDiagramSpy).toHaveBeenCalledWith(BPMN_DIAGRAM);
});
it('loads a diagram from File with error', () => {
const handleImportedSpy = spyOn(component, 'handleImported').and.stub();
component.diagramFile = new File([], 'filename.jpg', {type: 'image/jpeg'});
component.openMethod = 'file';
const event = new MouseEvent('click');
component['onSubmit'](event);
const expectedParams = {
type: 'error',
error: new Error('Wrong file type. Please choose a BPMN XML file.')
};
expect(handleImportedSpy).toHaveBeenCalledWith(expectedParams);
});
it('should get the diagram file name', () => {
expect(component.getFileName()).toEqual('No file selected');
const filename = 'expected_file_name.jpg';
component.diagramFile = new File([], filename, {type: 'image/jpeg'});
expect(component.getFileName()).toEqual(filename);
});
it('should get the diagram file from the file input form control', () => {
const expectedFile = new File([], 'filename.jpg', {type: 'image/jpeg'});
const event = {target: {files: [expectedFile]}};
component['onFileSelected'](event);
expect(component.diagramFile).toEqual(expectedFile);
});
});

View File

@ -1,4 +1,8 @@
import { Component } from '@angular/core';
import {Component, ViewChild} from '@angular/core';
import {BPMN_DIAGRAM} from '../testing/mocks/diagram.mocks';
import {BpmnWarning} from './_interfaces/bpmn-warning';
import {ImportEvent} from './_interfaces/import-event';
import {DiagramComponent} from './diagram/diagram.component';
@Component({
selector: 'app-root',
@ -9,9 +13,18 @@ export class AppComponent {
title = 'bpmn-js-angular';
diagramUrl = 'https://cdn.staticaly.com/gh/bpmn-io/bpmn-js-examples/dfceecba/starter/diagram.bpmn';
importError?: Error;
importWarnings?: BpmnWarning[];
xmlModel: any;
expandToolbar = false;
openMethod: string;
diagramFile: File;
@ViewChild(DiagramComponent, {static: false}) private diagramComponent: DiagramComponent;
handleImported(event) {
constructor() {
this.xmlModel = BPMN_DIAGRAM;
}
handleImported(event: ImportEvent) {
const {
type,
error,
@ -19,7 +32,7 @@ export class AppComponent {
} = event;
if (type === 'success') {
console.log(`Rendered diagram (%s warnings)`, warnings.length);
console.log(`Rendered diagram (${warnings.length} warnings)`);
}
if (type === 'error') {
@ -27,6 +40,49 @@ export class AppComponent {
}
this.importError = error;
this.importWarnings = warnings;
// Clear the inputs
this.diagramFile = undefined;
this.diagramUrl = undefined;
}
onSubmit($event: MouseEvent) {
this.expandToolbar = false;
if (this.openMethod === 'url') {
this.diagramComponent.loadUrl(this.diagramUrl);
} else if (this.openMethod === 'file') {
if (this.diagramFile && this.diagramFile.type === 'text/xml') {
this.readFile(this.diagramFile);
} else {
this.handleImported({
type: 'error',
error: new Error('Wrong file type. Please choose a BPMN XML file.')
});
}
}
this.openMethod = undefined;
}
getFileName() {
return this.diagramFile ? this.diagramFile.name : 'No file selected';
}
onFileSelected($event: Event) {
this.diagramFile = ($event.target as HTMLFormElement).files[0];
}
onLoad(event: ProgressEvent) {
const xml = (event.target as FileReader).result;
this.diagramComponent.openDiagram(xml.toString());
}
readFile(file: File) {
// FileReader must be instantiated this way so unit test can spy on it.
const fileReader = new (window as any).FileReader();
fileReader.onload = this.onLoad;
fileReader.readAsText(file);
}
}

View File

@ -1,10 +1,17 @@
import {HttpClientModule} from '@angular/common/http';
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { DiagramComponent } from './diagram/diagram.component';
import {NgModule} from '@angular/core';
import {FlexLayoutModule} from '@angular/flex-layout';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {MatButtonModule} from '@angular/material/button';
import {MatIconModule} from '@angular/material/icon';
import {MatInputModule} from '@angular/material/input';
import {MatMenuModule} from '@angular/material/menu';
import {MatTabsModule} from '@angular/material/tabs';
import {MatToolbarModule} from '@angular/material/toolbar';
import {BrowserModule} from '@angular/platform-browser';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AppComponent} from './app.component';
import {DiagramComponent} from './diagram/diagram.component';
@NgModule({
declarations: [
@ -12,11 +19,20 @@ import { DiagramComponent } from './diagram/diagram.component';
DiagramComponent
],
imports: [
BrowserAnimationsModule,
BrowserModule,
AppRoutingModule,
HttpClientModule
FlexLayoutModule,
FormsModule,
HttpClientModule,
MatButtonModule,
MatIconModule,
MatInputModule,
MatMenuModule,
MatTabsModule,
MatToolbarModule,
ReactiveFormsModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
export class AppModule {
}

View File

@ -1 +1,4 @@
<div #ref class="diagram-container"></div>
<div fxLayout="row" class="modeler-wrapper">
<div fxFlex="75%" class="diagram-container" #containerRef></div>
<div fxFlex="25%" class="properties-container" #propertiesRef></div>
</div>

View File

@ -1,4 +1,16 @@
.diagram-container {
.modeler-wrapper {
height: 100%;
width: 100%;
::ng-deep .djs-minimap:not(.open) .toggle:before {
font-family: 'Material Icons';
content: "map";
font-size: 24px;
}
::ng-deep .djs-minimap.open .toggle:before {
font-family: 'Material Icons';
content: "close";
font-size: 24px;
}
}

View File

@ -1,25 +1,193 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import { DiagramComponent } from './diagram.component';
import {DebugNode} from '@angular/core';
import {async, ComponentFixture, getTestBed, TestBed} from '@angular/core/testing';
import {MatIconModule} from '@angular/material/icon';
import * as FileSaver from 'file-saver';
import {BPMN_DIAGRAM, BPMN_DIAGRAM_WITH_WARNINGS} from '../../testing/mocks/diagram.mocks';
import {ApiService} from '../_services/api.service';
import {DiagramComponent} from './diagram.component';
describe('DiagramComponent', () => {
let component: DiagramComponent;
let httpMock: HttpTestingController;
let fixture: ComponentFixture<DiagramComponent>;
let component: DebugNode['componentInstance'];
const injector: TestBed = getTestBed();
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DiagramComponent ]
})
.compileComponents();
imports: [
HttpClientTestingModule,
MatIconModule,
],
declarations: [DiagramComponent],
providers: [ApiService]
});
fixture = TestBed.createComponent(DiagramComponent);
component = fixture.debugElement.componentInstance;
httpMock = injector.get(HttpTestingController);
fixture.detectChanges();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DiagramComponent);
component = fixture.componentInstance;
fixture.detectChanges();
afterEach(() => {
httpMock.verify();
fixture.destroy();
});
it('should create', () => {
it('should create and tear down', () => {
expect(component).toBeTruthy();
});
it('should load and render successfully', (done) => {
// given
const diagramURL = 'some-url';
component.importDone.subscribe(result => {
// then
expect(result).toEqual({
type: 'success',
warnings: []
});
done();
});
// when
component.loadUrl(diagramURL);
const request = httpMock.expectOne({url: diagramURL, method: 'GET'});
request.flush(BPMN_DIAGRAM);
});
it('should expose import warnings', (done) => {
// given
const diagramURL = 'some-url';
component.importDone.subscribe(result => {
// then
expect(result.type).toEqual('success');
expect(result.warnings.length).toEqual(1);
expect(result.warnings[0].message).toContain('unparsable content <process> detected');
done();
});
// when
component.loadUrl(diagramURL);
const request = httpMock.expectOne({url: diagramURL, method: 'GET'});
request.flush(BPMN_DIAGRAM_WITH_WARNINGS);
});
it('should fail to load and render', (done) => {
// given
const diagramURL = 'some-url';
// when
component.loadUrl(diagramURL);
component.importDone.subscribe(result => {
// then
expect(result.type).toEqual('error');
expect(result.error).toEqual('Http failure response for some-url: 404 FOO');
done();
});
const request = httpMock.expectOne({url: diagramURL, method: 'GET'});
request.flush('Not Found', {
status: 404,
statusText: 'FOO'
});
});
it('should save diagram as SVG', () => {
const fileSaverSpy = spyOn(FileSaver, 'saveAs').and.stub();
component.saveSVG();
expect(fileSaverSpy).toHaveBeenCalled();
});
it('should save diagram as XML', () => {
const fileSaverSpy = spyOn(FileSaver, 'saveAs').and.stub();
component.saveXML();
expect(fileSaverSpy).toHaveBeenCalled();
});
it('should create a new diagram', () => {
const importXMLSpy = spyOn(component.modeler, 'importXML').and.stub();
component.createNewDiagram();
expect(importXMLSpy).toHaveBeenCalled();
});
it('should open an existing diagram from XML', () => {
const importXMLSpy = spyOn(component.modeler, 'importXML').and.stub();
component.openDiagram(BPMN_DIAGRAM);
expect(importXMLSpy).toHaveBeenCalled();
});
it('should fail to open diagram', (done) => {
component.openDiagram('INVALID BPMN XML');
component.importDone.subscribe(result => {
expect(result.type).toEqual('error');
expect(result.error.message).toContain('unparsable content INVALID BPMN XML detected');
done();
});
});
it('should edit diagram', () => {
const importXMLSpy = spyOn(component.modeler, 'importXML').and.stub();
const createDiagramSpy = spyOn(component.modeler, 'createDiagram').and.stub();
component.createNewDiagram();
expect(createDiagramSpy).toHaveBeenCalled();
component.writeValue(BPMN_DIAGRAM);
expect(importXMLSpy).toHaveBeenCalled();
});
it('should register onChange function', () => {
const fn = (s: string) => s.toLowerCase().trim();
const input = ' TRIMMED AND LOWERCASED ';
component.registerOnChange(fn);
expect(component.onChange).toEqual(fn);
const result = component.onChange(input);
expect(result).toEqual('trimmed and lowercased');
});
it('should register onTouched function', () => {
const fn = () => 123456;
component.registerOnTouched(fn);
expect(component.onTouched).toEqual(fn);
const result = component.onTouched();
expect(result).toEqual(123456);
});
it('should set disabled state', () => {
component.setDisabledState(true);
expect(component.disabled).toEqual(true);
component.setDisabledState(false);
expect(component.disabled).toEqual(false);
});
});

View File

@ -1,93 +1,167 @@
import {HttpClient} from '@angular/common/http';
import {
AfterContentInit,
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnDestroy,
Output,
SimpleChanges,
ViewChild
} from '@angular/core';
/**
* You may include a different variant of BpmnJS:
*
* bpmn-viewer - displays BPMN diagrams without the ability
* to navigate them
* bpmn-modeler - bootstraps a full-fledged BPMN editor
*/
import * as BpmnJS from 'bpmn-js/dist/bpmn-modeler.production.min.js';
import {HttpErrorResponse} from '@angular/common/http';
import {AfterViewInit, Component, ElementRef, EventEmitter, NgZone, Output, ViewChild} from '@angular/core';
import {ControlValueAccessor} from '@angular/forms';
import propertiesPanelModule from 'bpmn-js-properties-panel';
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import * as camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda.json';
import minimapModule from 'diagram-js-minimap';
import {throwError} from 'rxjs';
import {catchError} from 'rxjs/operators';
import {importDiagram} from './rx';
import * as fileSaver from 'file-saver';
import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {ImportEvent} from '../_interfaces/import-event';
import {ApiService} from '../_services/api.service';
@Component({
selector: 'app-diagram',
templateUrl: './diagram.component.html',
styleUrls: ['./diagram.component.scss']
templateUrl: 'diagram.component.html',
styleUrls: ['diagram.component.scss'],
})
export class DiagramComponent implements AfterContentInit, OnChanges, OnDestroy {
private bpmnJS: BpmnJS;
export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
@ViewChild('containerRef', {static: true}) containerRef: ElementRef;
@ViewChild('propertiesRef', {static: true}) propertiesRef: ElementRef;
@Output() private importDone: EventEmitter<ImportEvent> = new EventEmitter();
private modeler: BpmnModeler;
private xml = '';
private disabled = false;
@ViewChild('ref', {static: true}) private el: ElementRef;
@Output() private importDone: EventEmitter<any> = new EventEmitter();
constructor(
private zone: NgZone,
private api: ApiService,
) {
}
@Input() private url: string;
get value(): any {
return this.xml;
}
constructor(private http: HttpClient) {
ngAfterViewInit() {
this.initializeModeler();
this.openDiagram(this.xml);
}
this.bpmnJS = new BpmnJS();
onChange(value: any) {
}
this.bpmnJS.on('import.done', ({error}) => {
onTouched() {
}
initializeModeler() {
this.modeler = new BpmnModeler({
container: this.containerRef.nativeElement,
propertiesPanel: {
parent: this.propertiesRef.nativeElement,
},
additionalModules: [
propertiesProviderModule,
propertiesPanelModule,
minimapModule,
],
moddleExtensions: {
camunda: camundaModdleDescriptor['default']
}
});
this.modeler.get('eventBus').on('commandStack.changed', () => this.saveDiagram());
this.modeler.on('import.done', ({error}) => {
if (!error) {
this.bpmnJS.get('canvas').zoom('fit-viewport');
this.modeler.get('canvas').zoom('fit-viewport');
}
});
}
ngAfterContentInit(): void {
this.bpmnJS.attachTo(this.el.nativeElement);
}
ngOnChanges(changes: SimpleChanges) {
// re-import whenever the url changes
if (changes.url) {
this.loadUrl(changes.url.currentValue);
// Allows Angular to update the model.
// Update the model and changes needed for the view here.
writeValue(value: any): void {
if (value !== this.xml) {
this.openDiagram(value);
}
this.xml = value;
this.onChange(this.value);
}
ngOnDestroy(): void {
this.bpmnJS.destroy();
// Allows Angular to register a function to call when the model changes.
// Save the function as a property to call later here.
registerOnChange(fn: (rating: number) => void): void {
this.onChange = fn;
}
// Allows Angular to register a function to call when the input has been touched.
// Save the function as a property to call later here.
registerOnTouched(fn: () => void): void {
this.onTouched = fn;
}
// Allows Angular to disable the input.
setDisabledState(isDisabled: boolean): void {
this.disabled = isDisabled;
}
createNewDiagram() {
this.openDiagram();
}
openDiagram(xml?: string) {
return this.zone.run(
() => xml ?
this.modeler.importXML(xml, (e, w) => this.onImport(e, w)) :
this.modeler.createDiagram((e, w) => this.onImport(e, w))
);
}
saveSVG() {
this.saveDiagram();
this.modeler.saveSVG((err, svg) => {
const blob = new Blob([svg], {type: 'image/svg+xml'});
fileSaver.saveAs(blob, `BPMN Diagram - ${new Date().toISOString()}.svg`);
});
}
saveDiagram() {
this.modeler.saveXML({format: true}, (err, xml) => {
this.xml = xml;
this.writeValue(xml);
});
}
saveXML() {
this.saveDiagram();
this.modeler.saveXML({format: true}, (err, xml) => {
const blob = new Blob([xml], {type: 'text/xml'});
fileSaver.saveAs(blob, `BPMN Diagram - ${new Date().toISOString()}.xml`);
});
}
onImport(err?: HttpErrorResponse, warnings?: BpmnWarning[]) {
if (err) {
this._handleErrors(err);
} else {
this._handleWarnings(warnings);
}
}
/**
* Load diagram from URL and emit completion event
*/
loadUrl(url: string) {
this.api.getBpmnXml(url).subscribe(xml => {
this.openDiagram(xml);
}, error => this._handleErrors(error));
}
return (
this.http.get(url, {responseType: 'text'}).pipe(
catchError(err => throwError(err)),
importDiagram(this.bpmnJS)
).subscribe(
(warnings) => {
this.importDone.emit({
type: 'success',
warnings
});
},
(err) => {
this.importDone.emit({
type: 'error',
error: err
});
}
)
);
private _handleWarnings(warnings: BpmnWarning[]) {
this.importDone.emit({
type: 'success',
warnings: warnings
});
}
private _handleErrors(err) {
this.importDone.emit({
type: 'error',
error: err
});
}
}

View File

@ -1,36 +0,0 @@
import {Observable} from 'rxjs';
/**
* An operator that imports the first XML piped via the piped diagram XML
* into the passed BpmnJS instance.
*/
export const importDiagram = (bpmnJS) => <Object>(source: Observable<string>) =>
new Observable<string>(observer => {
const subscription = source.subscribe({
next(xml: string) {
// canceling the subscription as we are interested
// in the first diagram to display only
subscription.unsubscribe();
bpmnJS.importXML(xml, (err, warnings) => {
if (err) {
observer.error(err);
} else {
observer.next(warnings);
}
observer.complete();
});
},
error(e) {
console.log('ERROR');
observer.error(e);
},
complete() {
observer.complete();
}
});
});

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_06g9dcb" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="3.4.1">
<bpmn:process id="Process_1giz8il" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0myefwb</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="SequenceFlow_0myefwb" sourceRef="StartEvent_1" targetRef="StepOne" />
<bpmn:sequenceFlow id="SequenceFlow_00p5po6" sourceRef="StepOne" targetRef="StepTwo" />
<bpmn:endEvent id="EndEvent_1gsujvg">
<bpmn:incoming>SequenceFlow_0huye14</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_0huye14" sourceRef="StepTwo" targetRef="EndEvent_1gsujvg" />
<bpmn:userTask id="StepOne" name="Step 1" camunda:formKey="StepOneForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="color" label="What is your favorite color?" type="string" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_0myefwb</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_00p5po6</bpmn:outgoing>
</bpmn:userTask>
<bpmn:userTask id="StepTwo" name="Step 2" camunda:formKey="StepTwoForm">
<bpmn:extensionElements>
<camunda:formData>
<camunda:formField id="capital" label="What is the capital of Assyria?" type="string" />
</camunda:formData>
</bpmn:extensionElements>
<bpmn:incoming>SequenceFlow_00p5po6</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_0huye14</bpmn:outgoing>
</bpmn:userTask>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1giz8il">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0myefwb_di" bpmnElement="SequenceFlow_0myefwb">
<di:waypoint x="215" y="117" />
<di:waypoint x="270" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_00p5po6_di" bpmnElement="SequenceFlow_00p5po6">
<di:waypoint x="370" y="117" />
<di:waypoint x="430" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="EndEvent_1gsujvg_di" bpmnElement="EndEvent_1gsujvg">
<dc:Bounds x="592" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_0huye14_di" bpmnElement="SequenceFlow_0huye14">
<di:waypoint x="530" y="117" />
<di:waypoint x="592" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNShape id="UserTask_1xakn8i_di" bpmnElement="StepOne">
<dc:Bounds x="270" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="UserTask_0fltcd6_di" bpmnElement="StepTwo">
<dc:Bounds x="430" y="77" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -2,10 +2,13 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>CrConnectBpmn</title>
<title>BpmnJsAngular</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<app-root></app-root>

View File

@ -1,3 +1,4 @@
import 'hammerjs';
import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

12
src/styles.css Normal file
View File

@ -0,0 +1,12 @@
@import '~@angular/material/prebuilt-themes/indigo-pink.css';
@import '~bpmn-js/dist/assets/diagram-js.css';
@import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
@import '~bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';
@import '~diagram-js-minimap/assets/diagram-js-minimap.css';
html, body {
height: 100%;
margin: 0;
padding: 0;
font-family: Roboto, "Helvetica Neue", sans-serif;
}

View File

@ -1 +0,0 @@
/* You can add global styles to this file, and also import other style files */

View File

@ -0,0 +1,31 @@
export const BPMN_DIAGRAM = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2"
targetNamespace="http://bpmn.io/bpmn"
exporter="http://bpmn.io"
exporterVersion="0.10.1">
<process id="Process_1" isExecutable="false" />
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1" />
</bpmndi:BPMNDiagram>
</definitions>
`;
export const BPMN_DIAGRAM_WITH_WARNINGS = `
<?xml version="1.0" encoding="UTF-8"?>
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL"
xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2"
targetNamespace="http://bpmn.io/bpmn"
exporter="http://bpmn.io"
exporterVersion="0.10.1">
<process id="Process_1" isExecutable="false" a:b="C" />
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1" />
</bpmndi:BPMNDiagram>
</definitions>
`;

11
src/tsconfig.app.json Normal file
View File

@ -0,0 +1,11 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/app",
"types": []
},
"exclude": [
"test.ts",
"**/*.spec.ts"
]
}

18
src/tsconfig.spec.json Normal file
View File

@ -0,0 +1,18 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"test.ts",
"polyfills.ts"
],
"include": [
"**/*.spec.ts",
"**/*.d.ts"
]
}

17
src/tslint.json Normal file
View File

@ -0,0 +1,17 @@
{
"extends": "../tslint.json",
"rules": {
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
]
}
}

View File

@ -1,18 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/app",
"types": []
},
"files": [
"src/main.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.ts"
],
"exclude": [
"src/test.ts",
"src/**/*.spec.ts"
]
}

View File

@ -2,13 +2,14 @@
"compileOnSave": false,
"compilerOptions": {
"baseUrl": "./",
"downlevelIteration": true,
"outDir": "./dist/out-tsc",
"sourceMap": true,
"declaration": false,
"downlevelIteration": true,
"experimentalDecorators": true,
"module": "esnext",
"moduleResolution": "node",
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"target": "es2015",
"typeRoots": [
@ -17,10 +18,9 @@
"lib": [
"es2018",
"dom"
]
},
"angularCompilerOptions": {
"fullTemplateTypeCheck": true,
"strictInjectionParameters": true
],
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"esModuleInterop": true
}
}

View File

@ -1,18 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./out-tsc/spec",
"types": [
"jasmine",
"node"
]
},
"files": [
"src/test.ts",
"src/polyfills.ts"
],
"include": [
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View File

@ -1,32 +1,32 @@
{
"extends": "tslint:recommended",
"rulesDirectory": [
"codelyzer"
],
"rules": {
"array-type": false,
"arrow-parens": false,
"arrow-return-shorthand": true,
"callable-types": true,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"deprecation": {
"severity": "warning"
"severity": "warn"
},
"component-class-suffix": true,
"contextual-lifecycle": true,
"directive-class-suffix": true,
"directive-selector": [
true,
"attribute",
"app",
"camelCase"
],
"component-selector": [
true,
"element",
"app",
"kebab-case"
],
"eofline": true,
"forin": true,
"import-blacklist": [
true,
"rxjs/Rx"
],
"interface-name": false,
"max-classes-per-file": false,
"import-spacing": true,
"indent": [
true,
"spaces"
],
"interface-over-type-literal": true,
"label-position": true,
"max-line-length": [
true,
140
@ -43,7 +43,8 @@
]
}
],
"no-consecutive-blank-lines": false,
"no-arg": true,
"no-bitwise": true,
"no-console": [
true,
"debug",
@ -52,40 +53,79 @@
"timeEnd",
"trace"
],
"no-construct": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-empty": false,
"no-empty-interface": true,
"no-eval": true,
"no-inferrable-types": [
true,
"ignore-params"
],
"no-misused-new": true,
"no-non-null-assertion": true,
"no-redundant-jsdoc": true,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-string-throw": true,
"no-switch-case-fall-through": true,
"no-var-requires": false,
"object-literal-key-quotes": [
true,
"as-needed"
],
"no-trailing-whitespace": true,
"no-unnecessary-initializer": true,
"no-unused-expression": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"object-literal-sort-keys": false,
"ordered-imports": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-whitespace"
],
"prefer-const": true,
"quotemark": [
true,
"single"
],
"trailing-comma": false,
"no-conflicting-lifecycle": true,
"radix": true,
"semicolon": [
true,
"always"
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"unified-signatures": true,
"variable-name": false,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
],
"no-output-on-prefix": true,
"no-inputs-metadata-property": true,
"no-outputs-metadata-property": true,
"no-host-metadata-property": true,
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-native": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"template-banana-in-box": true,
"template-no-negated-async": true,
"use-lifecycle-interface": true,
"use-pipe-transform-interface": true
},
"rulesDirectory": [
"codelyzer"
]
}
"use-pipe-transform-interface": true,
"component-class-suffix": true,
"directive-class-suffix": true
}
}