diff --git a/src/app/app.component.html b/src/app/app.component.html
index 5db1a06..fffcaca 100644
--- a/src/app/app.component.html
+++ b/src/app/app.component.html
@@ -1,6 +1,6 @@
-
+
-
@@ -27,8 +27,10 @@
+ save
+
- save
+ save_alt
arrow_drop_down
@@ -37,6 +39,12 @@
+
+
+
+
+
+
@@ -49,7 +57,7 @@
- arrow_forward
+ arrow_forward
close
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index b8157a8..34e8ee3 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -1,45 +1,50 @@
-import {Component, ViewChild} from '@angular/core';
+import {AfterViewInit, Component, ViewChild} from '@angular/core';
+import {MatDialog, MatDialogRef} from '@angular/material/dialog';
+import {MatSnackBar} from '@angular/material/snack-bar';
import {FileMeta, FileType, WorkflowSpec} from 'sartography-workflow-lib';
-import {BPMN_DIAGRAM} from '../testing/mocks/diagram.mocks';
import {BpmnWarning} from './_interfaces/bpmn-warning';
import {ImportEvent} from './_interfaces/import-event';
+import {NewFileDialogData} from './_interfaces/new-file-dialog-data';
import {ApiService} from './_services/api.service';
import {DiagramComponent} from './diagram/diagram.component';
+import {NewFileDialogComponent} from './new-file-dialog/new-file-dialog.component';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
-export class AppComponent {
+export class AppComponent implements AfterViewInit {
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;
workflowSpecs: WorkflowSpec[] = [];
bpmnFiles: FileMeta[] = [];
+ diagramFileMeta: FileMeta;
+ fileName: string;
+ private xml = '';
+ private draftXml = '';
@ViewChild(DiagramComponent, {static: false}) private diagramComponent: DiagramComponent;
- constructor(private api: ApiService) {
- this.xmlModel = BPMN_DIAGRAM;
- this.api.listWorkflowSpecifications().subscribe(wfs => {
- this.workflowSpecs = wfs;
- this.workflowSpecs.forEach(w => {
- this.api.listBpmnFiles(w.id).subscribe(files => {
- this.bpmnFiles = [];
- files.forEach(f => {
- this.api.getFileData(f.id).subscribe(d => {
- f.content_type = (f.type === FileType.SVG) ? 'image/svg+xml' : f.content_type = 'text/xml';
- f.file = new File([d], f.name, {type: f.content_type});
- this.bpmnFiles.push(f);
- });
- });
- });
- });
+ constructor(
+ private api: ApiService,
+ private snackBar: MatSnackBar,
+ public dialog: MatDialog
+ ) {
+ this.loadFilesFromDb();
+ }
+
+ ngAfterViewInit(): void {
+ this.diagramComponent.registerOnChange((newXmlValue: string) => {
+ this.draftXml = newXmlValue;
+ });
+
+ this.diagramComponent.registerOnTouched(() => {
+ console.log('diagramComponent onTouch');
});
}
@@ -60,13 +65,10 @@ export class AppComponent {
this.importError = error;
this.importWarnings = warnings;
-
- // Clear the inputs
- this.diagramFile = undefined;
- this.diagramUrl = undefined;
+ this.draftXml = this.xml;
}
- onSubmit($event: MouseEvent) {
+ onSubmitFileToOpen() {
this.expandToolbar = false;
if (this.openMethod === 'url') {
@@ -95,16 +97,110 @@ export class AppComponent {
// Arrow function here preserves this context
onLoad = (event: ProgressEvent) => {
- const xml = (event.target as FileReader).result;
- this.diagramComponent.openDiagram(xml.toString());
+ this.xml = (event.target as FileReader).result.toString();
+ this.diagramComponent.openDiagram(this.xml);
}
readFile(file: File) {
- console.log('readFile', 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);
}
+
+ saveChanges() {
+ if (this.hasChanged()) {
+
+ console.log('saveChanges this.diagramFileMeta', this.diagramFileMeta);
+ if (this.diagramFileMeta && this.diagramFileMeta.workflow_spec_id) {
+ this.xml = this.draftXml;
+ this.diagramFileMeta.file = new File([this.xml], this.diagramFileMeta.name, {type: 'text/xml'});
+ this.api.updateFileMeta(this.diagramFileMeta).subscribe(() => {
+ this.snackBar.open('Saved changes.', 'Ok', {duration: 5000});
+ });
+ } else {
+
+ // Open new filename/workflow spec dialog
+ const dialogRef = this.dialog.open(NewFileDialogComponent, {
+ height: '400px',
+ width: '400px',
+ data: {
+ fileName: '',
+ workflowSpecId: '',
+ },
+ });
+
+ dialogRef.afterClosed().subscribe((data: NewFileDialogData) => {
+ console.log('dialog afterClosed result', data);
+
+ if (data.fileName && data.workflowSpecId) {
+ this.xml = this.draftXml;
+
+ // New fileMeta
+ this.diagramFileMeta = {
+ content_type: 'text/xml',
+ name: data.fileName,
+ type: FileType.BPMN,
+ file: new File([this.xml], data.fileName, {type: 'text/xml'}),
+ workflow_spec_id: data.workflowSpecId,
+ };
+
+ const newSpec: WorkflowSpec = {
+ id: data.workflowSpecId,
+ display_name: data.displayName,
+ description: data.description,
+ };
+
+ // New workflow spec
+ this.api.addWorkflowSpecification(newSpec).subscribe(spec => {
+ console.log('spec', spec);
+ this.api.addFileMeta(this.diagramFileMeta).subscribe(fileMeta => {
+ this.loadFilesFromDb();
+ this.snackBar.open('Saved changes to new workflow spec and file.', 'Ok', {duration: 5000});
+ });
+ });
+ }
+ });
+
+ }
+ }
+ }
+
+ hasChanged(): boolean {
+ return this.xml !== this.draftXml;
+ }
+
+ loadDbFile(bf: FileMeta) {
+ this.diagramFile = bf.file;
+ this.diagramFileMeta = bf;
+ this.onSubmitFileToOpen();
+ }
+
+ newDiagram() {
+ this.xml = '';
+ this.draftXml = '';
+ this.fileName = '';
+ this.diagramFileMeta = undefined;
+ this.diagramFile = undefined;
+ this.diagramComponent.createNewDiagram();
+ }
+
+ private loadFilesFromDb() {
+ this.api.listWorkflowSpecifications().subscribe(wfs => {
+ this.workflowSpecs = wfs;
+ this.workflowSpecs.forEach(w => {
+ this.api.listBpmnFiles(w.id).subscribe(files => {
+ this.bpmnFiles = [];
+ files.forEach(f => {
+ this.api.getFileData(f.id).subscribe(d => {
+ f.content_type = (f.type === FileType.SVG) ? 'image/svg+xml' : f.content_type = 'text/xml';
+ f.file = new File([d], f.name, {type: f.content_type});
+ this.bpmnFiles.push(f);
+ });
+ });
+ });
+ });
+ });
+
+ }
}
diff --git a/src/app/diagram/diagram.component.ts b/src/app/diagram/diagram.component.ts
index 9616214..ac42b07 100644
--- a/src/app/diagram/diagram.component.ts
+++ b/src/app/diagram/diagram.component.ts
@@ -31,9 +31,7 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
) {
}
- get value(): any {
- return this.xml;
- }
+ get value(): string { return this.xml; }
ngAfterViewInit() {
this.initializeModeler();
@@ -78,12 +76,12 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
this.openDiagram(value);
}
this.xml = value;
- this.onChange(this.value);
+ this.onChange(this.xml);
}
// 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 {
+ registerOnChange(fn: (newXmlValue: string) => void): void {
this.onChange = fn;
}
@@ -103,11 +101,14 @@ export class DiagramComponent implements ControlValueAccessor, AfterViewInit {
}
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))
- );
+ this.xml = xml;
+ return this.zone.run(() => {
+ if (xml) {
+ this.modeler.importXML(xml, (e, w) => this.onImport(e, w));
+ } else {
+ this.modeler.createDiagram((e, w) => this.onImport(e, w));
+ }
+ });
}
saveSVG() {