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 @@ + + @@ -37,6 +39,12 @@ + + + + + + @@ -49,7 +57,7 @@ - + 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() {