WIP: Moves util methods to sartography-workflow-lib. Attempts to get file dialog working.

This commit is contained in:
Aaron Louie 2020-03-20 16:49:17 -04:00
parent ced9a852e8
commit afca7cece8
17 changed files with 123 additions and 146 deletions

6
package-lock.json generated
View File

@ -12171,9 +12171,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sartography-workflow-lib": {
"version": "0.0.68",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.68.tgz",
"integrity": "sha512-W5wJ7YIqDu/0ul1AZNazCUNzmxGNTbLd2i/nArYNgf0WwJO2j+zQsxd41TJVjHLp4dHpR/UsnCXGH/cC0Pz23w=="
"version": "0.0.72",
"resolved": "https://registry.npmjs.org/sartography-workflow-lib/-/sartography-workflow-lib-0.0.72.tgz",
"integrity": "sha512-l6153jMOkKPgi2dOODOdTSHdPyyZpYjuylkPt38ct4XYaaF+kiT3GadhZli3sxg8KRkYfiaJ5krZRgl3adNu7A=="
},
"sass": {
"version": "1.23.3",

View File

@ -49,7 +49,7 @@
"ngx-file-drop": "^8.0.8",
"ngx-markdown": "^9.0.0",
"rxjs": "~6.5.4",
"sartography-workflow-lib": "^0.0.68",
"sartography-workflow-lib": "^0.0.72",
"tslib": "^1.11.1",
"uuid": "^7.0.2",
"zone.js": "^0.10.3"

View File

@ -2,9 +2,8 @@ import {Component, Inject} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {FormlyFieldConfig, FormlyFormOptions} from '@ngx-formly/core';
import {FileType} from 'sartography-workflow-lib';
import {cleanUpFilename, FileType} from 'sartography-workflow-lib';
import {FileMetaDialogData} from '../../_interfaces/dialog-data';
import {cleanUpFilename} from '../../_util/string-clean';
@Component({
selector: 'app-new-file-dialog',
@ -21,55 +20,47 @@ export class FileMetaDialogComponent {
public dialogRef: MatDialogRef<FileMetaDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: FileMetaDialogData
) {
const fileTypeOptions = Object.entries(FileType).map(ft => {
return {
label: ft[0],
value: ft[1]
};
});
const fileTypeOptions = Object.entries(FileType).map(ft => {
return {
label: ft[0],
value: ft[1]
};
});
this.fields = [
{
key: 'id',
type: 'input',
defaultValue: this.data.id,
templateOptions: {
hide: true,
},
this.fields = [
{
key: 'fileName',
type: 'input',
defaultValue: this.data.fileName,
templateOptions: {
label: 'File Name',
placeholder: 'Name of file',
description: 'Enter a name, in lowercase letters, separated by underscores, that is easy for you to remember.' +
'It will be converted to all_lowercase_with_underscores when you save.',
required: true,
},
{
key: 'fileName',
type: 'input',
defaultValue: this.data.fileName,
templateOptions: {
label: 'File Name',
placeholder: 'Name of workflow specification',
description: 'Enter a name, in lowercase letters, separated by underscores, that is easy for you to remember.' +
'It will be converted to all_lowercase_with_underscores when you save.',
required: true,
},
},
{
key: 'fileType',
type: 'select',
defaultValue: this.data.fileType,
templateOptions: {
label: 'File Type',
placeholder: 'Extension of file',
required: true,
options: fileTypeOptions,
},
{
key: 'fileType',
type: 'select',
defaultValue: this.data.fileType,
templateOptions: {
label: 'File Type',
placeholder: 'Extension of file',
required: true,
options: fileTypeOptions,
},
},
{
key: 'file',
type: 'file',
defaultValue: this.data.file,
templateOptions: {
label: 'File',
required: true,
},
{
key: 'file',
type: 'file',
defaultValue: this.data.file,
templateOptions: {
label: 'File',
required: true,
},
}
];
}
];
}
onNoClick() {

View File

@ -1,8 +1,6 @@
import { Component, OnInit } from '@angular/core';
import {Component} from '@angular/core';
import {MatDialogRef} from '@angular/material/dialog';
import {ApiService} from 'sartography-workflow-lib';
import {getDiagramTypeFromXml} from '../../_util/diagram-type';
import {cleanUpFilename} from '../../_util/string-clean';
import {ApiService, cleanUpFilename, getDiagramTypeFromXml} from 'sartography-workflow-lib';
@Component({
selector: 'app-open-file-dialog',

View File

@ -2,8 +2,8 @@ import {Component, Inject} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {FormlyFieldConfig, FormlyFormOptions} from '@ngx-formly/core';
import {toSnakeCase} from 'sartography-workflow-lib';
import {WorkflowSpecDialogData} from '../../_interfaces/dialog-data';
import {toSnakeCase} from '../../_util/string-clean';
@Component({
selector: 'app-workflow-spec-category-dialog',

View File

@ -2,10 +2,9 @@ import {Component, Inject} from '@angular/core';
import {FormGroup} from '@angular/forms';
import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
import {FormlyFieldConfig, FormlyFormOptions, FormlyTemplateOptions} from '@ngx-formly/core';
import {ApiService} from 'sartography-workflow-lib';
import {ApiService, toSnakeCase} from 'sartography-workflow-lib';
import {v4 as uuidv4} from 'uuid';
import {WorkflowSpecDialogData} from '../../_interfaces/dialog-data';
import {toSnakeCase} from '../../_util/string-clean';
@Component({
selector: 'app-workflow-spec-dialog',

View File

@ -1,5 +0,0 @@
import {FileType} from 'sartography-workflow-lib';
export const getDiagramTypeFromXml = (xml: string): FileType => {
return (xml && xml.includes('dmn.xsd') ? FileType.DMN : FileType.BPMN);
};

View File

@ -1,3 +0,0 @@
export const isNumberDefined = (n: number): boolean => {
return (typeof n === 'number') && isFinite(n) && !isNaN(n);
};

View File

@ -1,20 +0,0 @@
import {cleanUpFilename, toSnakeCase, trimString} from './string-clean';
describe('String Cleaning Utilities', () => {
const afterTrimming = `I'm tired of wasting letters when punctuation will do, period. -Steve Martin`;
const beforeTrimming = ` 📌📍🏁 <>?:"{}[] ${afterTrimming} !@#$%^& ✌️👍👆 `;
it('converts a string to snake case', () => {
expect(toSnakeCase(beforeTrimming)).toEqual('i_m_tired_of_wasting_letters_when_punctuation_will_do_period_steve_martin');
});
it('cleans up a file name and replaces or adds the extension', () => {
expect(cleanUpFilename(beforeTrimming, 'bpmn')).toEqual(`I'm tired of wasting letters when punctuation will do, period.bpmn`);
expect(cleanUpFilename(' no extension ', 'bpmn')).toEqual('no extension.bpmn');
});
it('trims non-word characters from a string', () => {
expect(trimString(beforeTrimming)).toEqual(afterTrimming);
});
});

View File

@ -1,25 +0,0 @@
import {FileType} from 'sartography-workflow-lib';
export const trimString = (str: string): string => {
return !str ? '' : String(str).replace(/^\W+|\W+$/gi, '');
};
export const toSnakeCase = (str: string): string => {
str = trimString(str);
return !str ? '' : String(str)
.replace(/\W+/gi, '_')
.toLowerCase();
};
export const cleanUpFilename = (str: string, extension: FileType|string): string => {
const arr = trimString(str).split('.');
// Add file extension, if necessary
if (arr.length < 2) {
arr.push(extension);
} else {
(arr[arr.length - 1]) = extension;
}
return arr.join('.');
};

View File

@ -5,11 +5,16 @@ import {ControlValueAccessor} from '@angular/forms';
import BpmnModeler from 'bpmn-js/lib/Modeler';
import DmnModeler from 'dmn-js/lib/Modeler';
import * as fileSaver from 'file-saver';
import {ApiService, BPMN_DIAGRAM_DEFAULT, DMN_DIAGRAM_DEFAULT, FileType} from 'sartography-workflow-lib';
import {
ApiService,
BPMN_DIAGRAM_DEFAULT,
DMN_DIAGRAM_DEFAULT,
FileType,
getDiagramTypeFromXml
} from 'sartography-workflow-lib';
import {v4 as uuidv4} from 'uuid';
import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {ImportEvent} from '../_interfaces/import-event';
import {getDiagramTypeFromXml} from '../_util/diagram-type';
import {bpmnModelerConfig} from './bpmn-modeler-config';
import {dmnModelerConfig} from './dmn-modeler-config';

View File

@ -27,3 +27,17 @@
</mat-list-item>
</mat-list>
</div>
<div class="file-list-actions">
<button [queryParams]="{action: 'newFile'}" [routerLink]="['/modeler/' + workflowSpec.id]" mat-button>
<mat-icon>note_add</mat-icon>
Add new BPMN or DMN file
</button>
<button [queryParams]="{action: 'openFile'}" [routerLink]="['/modeler/' + workflowSpec.id]" mat-button>
<mat-icon>cloud_upload</mat-icon>
Upload BPMN or DMN file
</button>
<button (click)="editFile()" mat-button>
<mat-icon>cloud_upload</mat-icon>
Upload Template file
</button>
</div>

View File

@ -1,8 +1,8 @@
import {Component, Input, OnInit} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {Router} from '@angular/router';
import {ApiService, FileMeta, FileType, WorkflowSpec} from 'sartography-workflow-lib';
import {ActivatedRoute, Params, Router} from '@angular/router';
import {ApiService, FileMeta, FileType, isNumberDefined, WorkflowSpec} from 'sartography-workflow-lib';
import {DeleteFileDialogComponent} from '../_dialogs/delete-file-dialog/delete-file-dialog.component';
import {FileMetaDialogComponent} from '../_dialogs/file-meta-dialog/file-meta-dialog.component';
import {DeleteFileDialogData, FileMetaDialogData} from '../_interfaces/dialog-data';
@ -20,6 +20,7 @@ export class FileListComponent implements OnInit {
constructor(
private api: ApiService,
public dialog: MatDialog,
private route: ActivatedRoute,
private router: Router,
private snackBar: MatSnackBar,
) {
@ -29,8 +30,8 @@ export class FileListComponent implements OnInit {
this._loadFileMetas();
}
editFile(fileMeta: FileMeta) {
if (fileMeta.type === FileType.BPMN ||fileMeta.type === FileType.DMN) {
editFile(fileMeta?: FileMeta) {
if (fileMeta && ((fileMeta.type === FileType.BPMN) || (fileMeta.type === FileType.DMN))) {
this.router.navigate([`/modeler/${this.workflowSpec.id}/${fileMeta.id}`]);
} else {
// Show edit file meta dialog
@ -39,11 +40,30 @@ export class FileListComponent implements OnInit {
}
editFileMeta(fm: FileMeta) {
// Set route query string
this.router.navigate([], {
relativeTo: this.route,
fragment: this.workflowSpec.id,
queryParams: {
workflow_spec_id: this.workflowSpec.id
},
queryParamsHandling: 'merge'
}).finally(() => {
// Get file data
if (fm && isNumberDefined(fm.id)) {
this.api.getFileData(fm.id).subscribe(fileData => this._openFileDialog(fm, fileData));
} else {
this._openFileDialog();
}
});
}
private _openFileDialog(fm?: FileMeta, fileData?: Blob) {
const dialogRef = this.dialog.open(FileMetaDialogComponent, {
data: {
fileName: fm.name,
fileType: fm.type,
file: fm.file,
fileName: fm ? fm.name : undefined,
fileType: fm ? fm.type : undefined,
file: fileData ? fileData : undefined,
}
});
@ -61,6 +81,7 @@ export class FileListComponent implements OnInit {
});
}
});
}
confirmDelete(fm: FileMeta) {
@ -114,4 +135,5 @@ export class FileListComponent implements OnInit {
this.api.getFileData(fm.id).subscribe((fd: File) => fm.file = fd);
});
}
}

View File

@ -3,16 +3,21 @@ import {AfterViewInit, Component, ViewChild} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ActivatedRoute, Params, Router} from '@angular/router';
import {ApiService, FileMeta, FileType, WorkflowSpec} from 'sartography-workflow-lib';
import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {FileMetaDialogData, NewFileDialogData, OpenFileDialogData} from '../_interfaces/dialog-data';
import {ImportEvent} from '../_interfaces/import-event';
import {getDiagramTypeFromXml} from '../_util/diagram-type';
import {isNumberDefined} from '../_util/is-number-defined';
import {DiagramComponent} from '../diagram/diagram.component';
import {
ApiService,
FileMeta,
FileType,
getDiagramTypeFromXml,
isNumberDefined,
WorkflowSpec
} from 'sartography-workflow-lib';
import {FileMetaDialogComponent} from '../_dialogs/file-meta-dialog/file-meta-dialog.component';
import {NewFileDialogComponent} from '../_dialogs/new-file-dialog/new-file-dialog.component';
import {OpenFileDialogComponent} from '../_dialogs/open-file-dialog/open-file-dialog.component';
import {BpmnWarning} from '../_interfaces/bpmn-warning';
import {FileMetaDialogData, NewFileDialogData, OpenFileDialogData} from '../_interfaces/dialog-data';
import {ImportEvent} from '../_interfaces/import-event';
import {DiagramComponent} from '../diagram/diagram.component';
@Component({
selector: 'app-modeler',
@ -115,7 +120,7 @@ export class ModelerComponent implements AfterViewInit {
this.xml = (event.target as FileReader).result.toString();
const diagramType = getDiagramTypeFromXml(this.xml);
this.diagramComponent.openDiagram(this.xml, diagramType);
}
};
readFile(file: File) {
// FileReader must be instantiated this way so unit test can spy on it.
@ -157,8 +162,7 @@ export class ModelerComponent implements AfterViewInit {
}
openFileDialog() {
const dialogRef = this.dialog.open(OpenFileDialogComponent, {
});
const dialogRef = this.dialog.open(OpenFileDialogComponent, {});
dialogRef.afterClosed().subscribe((data: OpenFileDialogData) => {
if (data && data.file) {
@ -169,8 +173,7 @@ export class ModelerComponent implements AfterViewInit {
}
newFileDialog() {
const dialogRef = this.dialog.open(NewFileDialogComponent, {
});
const dialogRef = this.dialog.open(NewFileDialogComponent, {});
dialogRef.afterClosed().subscribe((data: NewFileDialogData) => {
if (data && data.fileType) {
@ -185,6 +188,7 @@ export class ModelerComponent implements AfterViewInit {
data: {
fileName: this.diagramFile ? this.diagramFile.name : this.fileName || '',
fileType: this.diagramType || getDiagramTypeFromXml(this.xml),
file: this.diagramFile || undefined,
},
});

View File

@ -1,4 +1,4 @@
<mat-card class="mat-elevation-z0">
<mat-card class="mat-elevation-z0" [id]="workflowSpec.id">
<mat-card-header>
<mat-card-title fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="10px">
<h3>{{workflowSpec.display_name}}</h3>
@ -18,13 +18,5 @@
<app-file-list [workflowSpec]="workflowSpec"></app-file-list>
</mat-card-content>
<mat-card-actions>
<button [queryParams]="{action: 'newFile'}" [routerLink]="['/modeler/' + workflowSpec.id]" mat-button>
<mat-icon>note_add</mat-icon>
Add new BPMN or DMN file
</button>
<button [queryParams]="{action: 'openFile'}" [routerLink]="['/modeler/' + workflowSpec.id]" mat-button>
<mat-icon>cloud_upload</mat-icon>
Upload BPMN or DMN file
</button>
</mat-card-actions>
</mat-card>

View File

@ -1,5 +1,7 @@
import {Component, Input, OnInit, TemplateRef} from '@angular/core';
import {WorkflowSpec} from 'sartography-workflow-lib';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ApiService, WorkflowSpec} from 'sartography-workflow-lib';
@Component({
selector: 'app-workflow-spec-card',
@ -16,4 +18,8 @@ export class WorkflowSpecCardComponent implements OnInit {
ngOnInit(): void {
}
openFileDialog() {
}
}

View File

@ -1,7 +1,7 @@
import {Component, OnInit} from '@angular/core';
import {MatDialog} from '@angular/material/dialog';
import {MatSnackBar} from '@angular/material/snack-bar';
import {ApiService, WorkflowSpec, WorkflowSpecCategory} from 'sartography-workflow-lib';
import {ApiService, isNumberDefined, WorkflowSpec, WorkflowSpecCategory} from 'sartography-workflow-lib';
import {DeleteWorkflowSpecCategoryDialogComponent} from '../_dialogs/delete-workflow-spec-category-dialog/delete-workflow-spec-category-dialog.component';
import {DeleteWorkflowSpecDialogComponent} from '../_dialogs/delete-workflow-spec-dialog/delete-workflow-spec-dialog.component';
import {WorkflowSpecCategoryDialogComponent} from '../_dialogs/workflow-spec-category-dialog/workflow-spec-category-dialog.component';
@ -12,7 +12,6 @@ import {
WorkflowSpecCategoryDialogData,
WorkflowSpecDialogData
} from '../_interfaces/dialog-data';
import {isNumberDefined} from '../_util/is-number-defined';
interface WorklflowSpecCategoryGroup {
id: number;
@ -138,13 +137,13 @@ export class WorkflowSpecListComponent implements OnInit {
}
private _loadWorkflowSpecs() {
this.api.getWorkflowSpecList().subscribe(wfs => {
this.api.getWorkflowSpecList().subscribe(wfs => {
this.workflowSpecs = wfs;
this.workflowSpecsByCategory.forEach(cat => {
cat.workflow_specs = this.workflowSpecs.filter(wf => wf.workflow_spec_category_id === cat.id);
});
});
}
}
private _upsertWorkflowSpecification(data: WorkflowSpecDialogData) {
if (data.id && data.name && data.display_name && data.description) {