diff --git a/src/app/core/component/action-button-bar/action-button-bar.component.html b/src/app/core/component/action-button-bar/action-button-bar.component.html index 78dde94c..abf87026 100644 --- a/src/app/core/component/action-button-bar/action-button-bar.component.html +++ b/src/app/core/component/action-button-bar/action-button-bar.component.html @@ -1,6 +1,9 @@ + + + {{this.displayText}} diff --git a/src/app/core/component/action-button-bar/action-button-bar.component.scss b/src/app/core/component/action-button-bar/action-button-bar.component.scss index ffdf9b50..a2300f12 100644 --- a/src/app/core/component/action-button-bar/action-button-bar.component.scss +++ b/src/app/core/component/action-button-bar/action-button-bar.component.scss @@ -2,3 +2,15 @@ margin-right: 10px; margin-left: 10px; } + +.mat-card-image { + width: auto; + height: 50px; + cursor: pointer; +} + +.image-with-margin { + margin-right: 5px; + margin-left: 15px; +} + diff --git a/src/app/core/component/action-button-bar/action-button-bar.component.ts b/src/app/core/component/action-button-bar/action-button-bar.component.ts index 21fc8cf1..5717a312 100644 --- a/src/app/core/component/action-button-bar/action-button-bar.component.ts +++ b/src/app/core/component/action-button-bar/action-button-bar.component.ts @@ -11,12 +11,15 @@ export class ActionButtonBarComponent implements OnInit { @Output() add2Clicked = new EventEmitter(); @Output() reloadClicked = new EventEmitter(); @Output() changedText = new EventEmitter(); + @Output() iconEditClicked = new EventEmitter(); @Input() addButtonText: string; @Input() reloadButton = false; @Input() goBackButton = true; @Input() secondAddButton: boolean; @Input() firstAddButton = true; @Input() secondAddButtonText: string; + @Input() iconEdit = false; + @Input() iconUrl: string; @Input() displayText: string; @@ -38,4 +41,8 @@ export class ActionButtonBarComponent implements OnInit { secondAddButtonClicked() { this.add2Clicked.emit(); } + + iconEditButtonClicked() { + this.iconEditClicked.emit(); + } } diff --git a/src/app/core/component/create-pattern-relation/create-pattern-relation.component.html b/src/app/core/component/create-pattern-relation/create-pattern-relation.component.html index 30691d86..e78c22b2 100644 --- a/src/app/core/component/create-pattern-relation/create-pattern-relation.component.html +++ b/src/app/core/component/create-pattern-relation/create-pattern-relation.component.html @@ -1,9 +1,22 @@ -

Add a relation to another pattern

+

Edit or Delete the selected Relation

+

Add a Relation to another Pattern

+ + +
+ + {{pattern.name}} + +
+
+
+
+ Add a relation to another pattern + + +
+ + {{pattern.name}} + +
+
+
+
+ Add a relation to another pattern
- - + + +
diff --git a/src/app/core/component/create-pattern-relation/create-pattern-relation.component.scss b/src/app/core/component/create-pattern-relation/create-pattern-relation.component.scss index fde0b848..8250d430 100644 --- a/src/app/core/component/create-pattern-relation/create-pattern-relation.component.scss +++ b/src/app/core/component/create-pattern-relation/create-pattern-relation.component.scss @@ -5,3 +5,7 @@ .mat-form-field { margin-left: 2.5rem !important; } + +.mat-input-element { + height: 1.125em; +} diff --git a/src/app/core/component/create-pattern-relation/create-pattern-relation.component.ts b/src/app/core/component/create-pattern-relation/create-pattern-relation.component.ts index 06a02979..415cf4d7 100644 --- a/src/app/core/component/create-pattern-relation/create-pattern-relation.component.ts +++ b/src/app/core/component/create-pattern-relation/create-pattern-relation.component.ts @@ -15,12 +15,18 @@ import { Observable } from 'rxjs'; styleUrls: ['./create-pattern-relation.component.scss'] }) - +/** + * This dialog is getting used to + * 1. Create new relations + * 2. Edit existing relations (isDelete = true) + * 3. Delete existing relations (isDelete = true) + */ export class CreatePatternRelationComponent implements OnInit { constructor(public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: DialogData, private fb: FormBuilder) { } + isDelete: boolean; directionEnum = PatternRelationDescriptorDirection; patterns: Pattern[]; directionTypes = [ @@ -54,14 +60,17 @@ export class CreatePatternRelationComponent implements OnInit { } catch (e) { } + if(this.data.description === undefined){ + this.data.description = ''; + } + this.isDelete =this.data.isDelete; // set view to delete/edit instead of create this.relationForm = this.fb.group({ firstPattern: [this.data.firstPattern, [Validators.required]], secondPattern: [this.data.secondPattern, [Validators.required]], direction: [preselectedEdgeDirection, [Validators.required]], - relationType: ['', [Validators.required]], - description: ['', []], + relationType: [this.data.relationType, [Validators.required]], + description: [this.data.description, []], }); - if (this.data.relationTypes) { this.subscriptionRefs.push(this.data.relationTypes.subscribe(relationTypes => this.relationTypes = relationTypes)); } @@ -94,9 +103,17 @@ export class CreatePatternRelationComponent implements OnInit { } + /** + * called when delete button is pressed --> delete Link + */ + deleteLink() { + this.data.deleteLink = true; + } } export interface DialogData { + relationType: string; + description: string; firstPattern?: Pattern; secondPattern?: Pattern; preselectedEdgeDirection?: PatternRelationDescriptorDirection; @@ -104,6 +121,8 @@ export interface DialogData { patternLanguage: PatternLanguage; patternContainer: PatternContainer; relationTypes?: Observable; + isDelete: boolean; // delete button toggle + deleteLink: boolean; // set true if delete button pressed } export interface PatternRelationDirection { diff --git a/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.html b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.html new file mode 100644 index 00000000..fdcbe3f4 --- /dev/null +++ b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.html @@ -0,0 +1,7 @@ +

Edit or Delete Relation

+
+ + +
diff --git a/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.scss b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.spec.ts b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.spec.ts new file mode 100644 index 00000000..10f6909f --- /dev/null +++ b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeleteConfirmationDialogComponent } from './delete-confirmation-dialog.component'; + +describe('DeleteConfirmationDialogComponent', () => { + let component: DeleteConfirmationDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DeleteConfirmationDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteConfirmationDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.ts b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.ts new file mode 100644 index 00000000..7d920c90 --- /dev/null +++ b/src/app/core/component/delete-confirmation-dialog/delete-confirmation-dialog.component.ts @@ -0,0 +1,19 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'pp-delete-confirmation-dialog', + templateUrl: './delete-confirmation-dialog.component.html', + styleUrls: ['./delete-confirmation-dialog.component.scss'] +}) +export class DeleteConfirmationDialogComponent { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data) { + } + + onNoClick(): void { + this.dialogRef.close(); + } +} diff --git a/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.html b/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.html index 108d2e77..cfb96ba5 100644 --- a/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.html +++ b/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.html @@ -1,5 +1,7 @@ -

Delete a relation

+

View incoming Edges

+

View outgoing Edges

+

View undirected Edges

@@ -14,12 +16,9 @@

Delete a relation

Source Pattern: {{edge.edge.sourcePatternName}}
Relation Type: {{edge.edge.type}}
Target Pattern: {{edge.edge.targetPatternName}} -
Description: {{edge.description}}
+
Description: {{edge.edge.description}}

- @@ -35,12 +34,9 @@

Delete a relation

Pattern 1: {{edge.edge.pattern1Name}}
Relation Type: {{edge.edge.type}}
Pattern 2: {{edge.edge.pattern2Name}} -
Description: {{edge.description}}
+
Description: {{edge.edge.description}}

- diff --git a/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.ts b/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.ts index 0e935ee0..bffafd38 100644 --- a/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.ts +++ b/src/app/core/component/delete-pattern-relation/delete-pattern-relation.component.ts @@ -9,61 +9,50 @@ import { HalLink } from '../../model/hal/hal-link.interface'; @Component({ selector: 'pp-delete-pattern-relation', templateUrl: './delete-pattern-relation.component.html', - styleUrls: ['./delete-pattern-relation.component.scss'] + styleUrls: [ './delete-pattern-relation.component.scss' ] }) export class DeletePatternRelationComponent implements OnInit { - currentEdges: Array = []; + currentEdges: Array = []; - constructor(public dialogRef: MatDialogRef, - @Inject(MAT_DIALOG_DATA) public data: DeleteRelationDialogData, - private patternRelationDescriptorService: PatternRelationDescriptorService, - private patternViewService: PatternViewService, private toasterService: ToasterService) { - this.getEdgesForPattern(); - } + constructor(public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: DeleteRelationDialogData, + private patternRelationDescriptorService: PatternRelationDescriptorService, + private patternViewService: PatternViewService, private toasterService: ToasterService) { + this.getEdgesForPattern(); + } - ngOnInit() { - } + ngOnInit() { + } + + close(): void { + this.dialogRef.close(); + } - close(): void { - this.dialogRef.close(); - } - deleteEdge(edge: EdgeWithType): void { - console.log(edge); - this.patternViewService.deleteLink(edge.edge._links.self.href).subscribe( - (res) => { - this.currentEdges = this.currentEdges.filter(item => item.edge.id !== edge.edge.id); - this.toasterService.pop('success', 'Relation removed'); - if (this.currentEdges.length === 0) { - this.dialogRef.close(); - } + private getEdgesForPattern(): void { + let links = []; + if (!this.data.edges.length) { + links[0] = this.data.edges; + } else { + links = this.data.edges; + } + for (const link of links) { + this.patternRelationDescriptorService.getUndirectedEdgeByUrl(link.href).subscribe( + data => { + const edgeWithType: EdgeWithType = new EdgeWithType(); + edgeWithType.edge = data; + edgeWithType.type = data.type; + this.currentEdges.push(edgeWithType); } ); } - - private getEdgesForPattern(): void { - let links = []; - if (!this.data.edges.length) { - links[0] = this.data.edges; - } else { - links = this.data.edges; - } - for (const link of links) { - this.patternRelationDescriptorService.getUndirectedEdgeByUrl(link.href).subscribe( - data => { - const edgeWithType: EdgeWithType = new EdgeWithType(); - edgeWithType.edge = data; - edgeWithType.type = data.type; - this.currentEdges.push(edgeWithType); - } - ); - } - } + } } export interface DeleteRelationDialogData { - edges: HalLink[]; - type: string; + deleteEdge: EdgeWithType; + edges: HalLink[]; + type: string; } diff --git a/src/app/core/component/edit-url-dialog/edit-url-dialog.component.html b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.html new file mode 100644 index 00000000..d9effe84 --- /dev/null +++ b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.html @@ -0,0 +1,15 @@ +

Edit Title or Icon for {{data.pattern.name}}

+
+ + Adjust Pattern Name + + + + Adjust Icon URL + + +
+ + + + diff --git a/src/app/core/component/edit-url-dialog/edit-url-dialog.component.scss b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.scss new file mode 100644 index 00000000..27956b0e --- /dev/null +++ b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.scss @@ -0,0 +1,3 @@ +.mat-form-field{ + width: 100%; +} diff --git a/src/app/core/component/edit-url-dialog/edit-url-dialog.component.spec.ts b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.spec.ts new file mode 100644 index 00000000..a3cfb508 --- /dev/null +++ b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditUrlDialogComponent } from './edit-url-dialog.component'; + +describe('EditUrlDialogComponent', () => { + let component: EditUrlDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EditUrlDialogComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EditUrlDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/core/component/edit-url-dialog/edit-url-dialog.component.ts b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.ts new file mode 100644 index 00000000..0d869a95 --- /dev/null +++ b/src/app/core/component/edit-url-dialog/edit-url-dialog.component.ts @@ -0,0 +1,19 @@ +import { Component, Inject } from '@angular/core'; +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; + +@Component({ + selector: 'pp-edit-url-dialog', + templateUrl: './edit-url-dialog.component.html', + styleUrls: ['./edit-url-dialog.component.scss'] +}) +export class EditUrlDialogComponent { + + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data) { + } + + onNoClick(): void { + this.dialogRef.close(); + } +} diff --git a/src/app/core/component/graph-display/graph-display.component.html b/src/app/core/component/graph-display/graph-display.component.html index 36fd8f23..6388b8e6 100644 --- a/src/app/core/component/graph-display/graph-display.component.html +++ b/src/app/core/component/graph-display/graph-display.component.html @@ -1,10 +1,12 @@ -

Add Patterns

- + {{patternLang.name}} @@ -32,9 +34,36 @@

Add Patterns

- -

Relations of {{currentPattern?.name}}

+ + +

{{showViewRelations ? 'View ' : ''}}Relations of {{currentPattern?.name}}

+ + + + {{relation?.type}} + + +
+ trending_flat + trending_flat + {{ relation['sourcePatternId'] === this.currentPattern.id ? + relation['targetPatternName'] : relation['sourcePatternName']}} +
+
+ {{ relation['sourcePatternName']}} + compare_arrows + {{ relation['pattern1Id'] === this.currentPattern.id ? relation['pattern2Name'] : + relation['pattern1Name']}} +
+ mode_edit +
+
+ {{relation.description ? '' + relation.description : ''}} +
+ + +

Pattern Language Relations

@@ -46,8 +75,8 @@

Relations of {{currentPattern?.name}}

{{ relation?.edge.sourcePatternName}} compare_arrows - {{ relation?.edge.pattern1Id === this.currentPattern.id ? relation.edge.pattern1Name : - relation.edge.pattern2Name}} + {{ relation?.edge.pattern1Id === this.currentPattern.id ? relation.edge.pattern2Name : + relation.edge.pattern1Name}}
@@ -61,11 +90,11 @@

Relations of {{currentPattern?.name}}

{{ relation?.edge.sourcePatternId === this.currentPattern.id ? relation?.edge.targetPatternName : relation?.edge.sourcePatternName}}
-
+ mode_edit - {{relation?.edge.description ? ': ' + relation?.edge.description : ''}} + {{relation?.edge.description ? relation?.edge.description : ''}}
@@ -84,7 +113,6 @@

Relations of {{currentPattern?.name}}

style="width: 100%; height: 100%" zoom="both"> -

Relations of {{currentPattern?.name}}

display: none; cursor: pointer; fill: black; - opacity: 0.1; + opacity: 0.15; transition: opacity 0.25s ease-out; } @@ -185,11 +213,10 @@

Relations of {{currentPattern?.name}}

x="45" y="10"> - - + @@ -197,3 +224,5 @@

Relations of {{currentPattern?.name}}

+ + diff --git a/src/app/core/component/graph-display/graph-display.component.scss b/src/app/core/component/graph-display/graph-display.component.scss index fd10cadb..4a9b8141 100644 --- a/src/app/core/component/graph-display/graph-display.component.scss +++ b/src/app/core/component/graph-display/graph-display.component.scss @@ -84,3 +84,14 @@ svg { .pattern-list.cdk-drop-list-dragging .pattern-box:not(.cdk-drag-placeholder) { transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } + +.sidemenu { + max-width: 30%; +} + +.edit-icon{ + margin-left: auto; + margin-right: 0; + color:rgb(63, 81, 181); + padding-left: 10px +} diff --git a/src/app/core/component/graph-display/graph-display.component.ts b/src/app/core/component/graph-display/graph-display.component.ts index 2be8b843..cb720b15 100644 --- a/src/app/core/component/graph-display/graph-display.component.ts +++ b/src/app/core/component/graph-display/graph-display.component.ts @@ -31,7 +31,8 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop'; import { GraphDataService } from '../../service/graph-data/graph-data.service'; import { GraphDataSavePatternService } from '../../service/graph-data/graph-data-save-pattern.service'; import { PatternRelationDescriptorDirection } from '../../model/pattern-relation-descriptor-direction.enum'; -import { UriConverter } from '../../util/uri-converter'; +import { Edge } from '../../model/hal/edge.model'; +import { PatternViewService } from '../../service/pattern-view.service'; // file deepcode ignore no-any: out of scope, this should be done another time @@ -65,13 +66,14 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { @Input() showPatternLanguageName: boolean; @Input() enableDeletePattern = false; @Input() showConcreteSolutions = false; + @Input() showViewRelations = false; @Input() concreteSolutions = []; @Output() addedEdge = new EventEmitter(); @Output() removedEdge = new EventEmitter(); @Output() updatedGraphEvent = new EventEmitter(); @Output() deletePatternEvent = new EventEmitter(); - @Output() aggregationAssignmentsUpdate = new EventEmitter<{ [ key: string ]: string }>(); + @Output() aggregationAssignmentsUpdate = new EventEmitter<{ [key: string]: string }>(); isLoading = true; patternClicked = false; @@ -85,12 +87,15 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { private copyOfLinks: NetworkLink[]; private patterns: Pattern[]; private patternLanguage: PatternLanguage; - private currentEdge: any; + currentEdge = null; private highlightedNodeIds: string[] = []; private clickedNodeId: string = null; private highlightedEdgeIds: string[] = []; - private manualAssignments: { [ key: string ]: string } = {}; - private aggregationAssignments: { [ key: string ]: string } = {}; + private manualAssignments: { [key: string]: string } = {}; + private aggregationAssignments: { [key: string]: string } = {}; + private relations: Edge[]; + viewRelationsOfPattern: Edge[]; + selectedPattern: Pattern; constructor(private cdr: ChangeDetectorRef, private d3Service: D3Service, @@ -98,6 +103,7 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { private patternRelationDescriptionService: PatternRelationDescriptorService, private toastService: ToasterService, private patternService: PatternService, + private patternViewService: PatternViewService, // use the implementation of GraphDataService that is provided in the module: private graphDataService: GraphDataService, private activatedRoute: ActivatedRoute, @@ -138,15 +144,15 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { if (patterns) { for (let i = 0; i < patterns.length; i++) { const node = { - id: patterns[ i ].id, - iconUrl: patterns[ i ].iconUrl, - title: patterns[ i ].name, + id: patterns[i].id, + iconUrl: patterns[i].iconUrl, + title: patterns[i].name, type: 'default', x: 5 * offsetIndex, y: 5 * offsetIndex, - patternLanguageId: patterns[ i ].patternLanguageId, - patternLanguageName: patterns[ i ].patternLanguageName, - uri: patterns[ i ].uri + patternLanguageId: patterns[i].patternLanguageId, + patternLanguageName: patterns[i].patternLanguageName, + uri: patterns[i].uri }; nodes.push(node); } @@ -195,12 +201,19 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { } } + /** + * Create Edge from event --> open Dialog --> close Dialog + * --> forward information to parent and delete edge --> + * @param event + */ handleEdgeAddedEvent(event) { if (!event.cancelable) { // Skip event on initial graph composition return; } - + if (event.detail.edge.source === event.detail.edge.target) { + event.preventDefault() + } this.currentEdge = event.detail.edge; const patterns = Array.isArray(this.patterns) ? this.patterns : this.patternContainer.patterns; const dialogRef = this.matDialog.open(CreatePatternRelationComponent, { @@ -211,13 +224,15 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { patterns, patternLanguage: this.patternLanguage, patternContainer: this.patternContainer, - relationTypes: this.graphDataService.getEdgeTypes() + relationTypes: this.graphDataService.getEdgeTypes(), + description: '', } }); dialogRef.afterClosed().subscribe((edge) => { if (edge) { // inform parent component that new edge was added this.addedEdge.emit(edge); + this.graphNativeElement.removeEdge(this.currentEdge); } else { this.graphNativeElement.removeEdge(this.currentEdge); this.triggerRerendering(); @@ -225,8 +240,17 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { }); } + /** + * If called through API --> delete Edge + * If called by User clicking the "delete"-button on an edge + * --> prevent delete, set edge as current edge and call call parent that opens a Userdialog + * that determines what should be done + * @param event + */ handleEdgeRemovedEvent(event: CustomEvent) { - if (event.type === 'edgeremove' && event.cancelable) { + if (event.detail.eventSource === 'USER_INTERACTION') { + event.preventDefault(); + this.currentEdge = event.detail.edge; this.removedEdge.emit(event.detail.edge); } } @@ -235,7 +259,7 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { if (event.isPointerOverContainer) { return; } - const patternDropped: Pattern = event.container.data[ event.previousIndex ]; + const patternDropped: Pattern = event.container.data[event.previousIndex]; this.addPatternToGraph(patternDropped); } @@ -254,12 +278,12 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { } handleNodeClickedEvent(event) { - const node = event[ 'detail' ][ 'node' ]; - if (event[ 'detail' ][ 'key' ] === 'info') { - this.router.navigate([UriConverter.doubleEncodeUri(node.uri)], { relativeTo: this.activatedRoute }); + const node = event['detail']['node']; + if (event['detail']['key'] === 'info') { + this.router.navigate(['./../..', 'pattern-languages', node.patternLanguageId, node.uri], { relativeTo: this.activatedRoute }); return; } - if (event[ 'detail' ][ 'key' ] === 'delete') { + if (event['detail']['key'] === 'delete') { this.deletePatternEvent.emit(node.id); return; } @@ -297,6 +321,7 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { this.highlightedNodeIds = []; this.highlightedEdgeIds = []; this.clickedNodeId = null; + this.selectedPattern = null; this.graphNativeElement.completeRender(); this.patternClicked = false; } @@ -341,6 +366,7 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { this.patternGraphData = this.data; if (this.patternGraphData) { this.edges = GraphDisplayComponent.mapPatternLinksToEdges(this.patternGraphData.edges); + this.relations = this.patternGraphData.edges; this.copyOfLinks = GraphDisplayComponent.mapPatternLinksToEdges(this.patternGraphData.edges); this.patterns = this.patternGraphData.patterns; this.patternLanguage = this.patternGraphData.patternLanguage; @@ -385,6 +411,9 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { })) .subscribe(edges => { this.currentEdges = edges; + if (this.showViewRelations) { + this.viewRelationsOfPattern = this.relations.filter(edge => this.highlightedEdgeIds.includes(edge.id)); + } this.cdr.detectChanges(); }); } @@ -408,25 +437,28 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { } this.triggerRerendering(); this.graphNativeElement.zoomToBoundingBox(true); + } private addNewPatternNodeToGraph(pat: Pattern, index: number) { - this.graphNativeElement.addNode(GraphDisplayComponent.mapPatternsToNodes([pat], index)[ 0 ]); + this.graphNativeElement.addNode(GraphDisplayComponent.mapPatternsToNodes([pat], index)[0]); } private showInfoForClickedNode(node): void { + this.clickedNodeId = node.id; const outgoingLinks = Array.from(this.graph.nativeElement.getEdgesByTarget(node.id)); const ingoingLinks = Array.from(this.graph.nativeElement.getEdgesBySource(node.id)); this.highlightedEdgeIds = [].concat(outgoingLinks).concat(ingoingLinks).map((edge) => edge.id ? edge.id : edgeId(edge)); - const outgoingNodeIds: string[] = outgoingLinks.map(it => it[ 'source' ]); - const ingoingNodeIds: string[] = ingoingLinks.map(it => it[ 'target' ]); + const outgoingNodeIds: string[] = outgoingLinks.map(it => it['source']); + const ingoingNodeIds: string[] = ingoingLinks.map(it => it['target']); this.highlightedNodeIds = []; this.highlightedNodeIds = outgoingNodeIds.concat(ingoingNodeIds); this.highlightedNodeIds.push(node.id); this.currentPattern = this.patterns.find(pat => pat.id === node.id); + this.selectedPattern = this.currentPattern; this.getEdgesForPattern(); this.patternClicked = true; this.triggerRerendering(); @@ -476,8 +508,8 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { if (!elementId) { return; } - const patternInstanceId = elementId[ 1 ]; - const patternInstance = this.patternContainer.patterns.filter(patternInstance => patternInstance.id === patternInstanceId)[ 0 ]; + const patternInstanceId = elementId[1]; + const patternInstance = this.patternContainer.patterns.filter(patternInstance => patternInstance.id === patternInstanceId)[0]; const concreteSolutions = this.concreteSolutions.filter(cs => cs.patternUri === patternInstance.uri).sort((a, b) => { return a.aggregatorType.localeCompare(b.aggregatorType); }); @@ -499,29 +531,29 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { * Construct SVG group element with an rectangle and a text, representing a concrete solution. * @param concreteSolutionsContainerElement * @param index - * @param text + * @param concreteSolution */ private addConcreteSolutionSvgElement(concreteSolutionsContainerElement: SVGGElement, index: number, concreteSolution: any): void { - const patternId = (concreteSolutionsContainerElement.parentNode).id.match(/node-(.+)/)[ 1 ]; + const patternId = (concreteSolutionsContainerElement.parentNode).id.match(/node-(.+)/)[1]; const concreteSolutionId = concreteSolution.id; - if (this.manualAssignments[ patternId ]) { - this.aggregationAssignments[ patternId ] = this.manualAssignments[ patternId ]; + if (this.manualAssignments[patternId]) { + this.aggregationAssignments[patternId] = this.manualAssignments[patternId]; } - if (!this.aggregationAssignments[ patternId ] && concreteSolution.fulfills) { - this.aggregationAssignments[ patternId ] = concreteSolutionId; + if (!this.aggregationAssignments[patternId] && concreteSolution.fulfills) { + this.aggregationAssignments[patternId] = concreteSolutionId; } - const color = this.aggregationAssignments[ patternId ] === concreteSolutionId ? '#00c' : concreteSolution.fulfills ? '#000' : '#ccc'; - const border = this.manualAssignments[ patternId ] === concreteSolutionId ? '4' : '0'; + const color = this.aggregationAssignments[patternId] === concreteSolutionId ? '#00c' : concreteSolution.fulfills ? '#000' : '#ccc'; + const border = this.manualAssignments[patternId] === concreteSolutionId ? '4' : '0'; const clickHandler = (event) => { - if (this.manualAssignments[ patternId ] !== concreteSolutionId) { - this.manualAssignments[ patternId ] = concreteSolutionId; + if (this.manualAssignments[patternId] !== concreteSolutionId) { + this.manualAssignments[patternId] = concreteSolutionId; } else { - delete this.manualAssignments[ patternId ]; - delete this.aggregationAssignments[ patternId ]; + delete this.manualAssignments[patternId]; + delete this.aggregationAssignments[patternId]; } console.debug('Manual selected', this.manualAssignments); this.triggerRerendering(true); @@ -556,4 +588,18 @@ export class GraphDisplayComponent implements AfterContentInit, OnChanges { concreteSolutionsContainerElement.appendChild(csSvgGroup); } + + getGraphDataService() { + return this.graphDataService + } + + openEditPL(relation: EdgeWithType) { + this.currentEdge = relation.edge; + this.removedEdge.emit(relation.edge); + } + + openEditView(relation: Edge) { + this.currentEdge = relation; + this.removedEdge.emit(relation); + } } diff --git a/src/app/core/core.module.ts b/src/app/core/core.module.ts index c9a62062..600b4737 100644 --- a/src/app/core/core.module.ts +++ b/src/app/core/core.module.ts @@ -77,6 +77,8 @@ import { CandidateManagementService } from './candidate-management/_services/can import { CandidateManagementStore } from './candidate-management'; import { MatTreeModule } from '@angular/material/tree'; import { MatFormFieldModule } from '@angular/material/form-field'; +import { DeleteConfirmationDialogComponent } from './component/delete-confirmation-dialog/delete-confirmation-dialog.component'; +import { EditUrlDialogComponent } from './component/edit-url-dialog/edit-url-dialog.component'; @NgModule({ @@ -161,7 +163,9 @@ import { MatFormFieldModule } from '@angular/material/form-field'; DeletePatternRelationComponent, CreativeLicenseFooterComponent, CommentDialogComponent, - DiscussDialogComponent + DiscussDialogComponent, + DeleteConfirmationDialogComponent, + EditUrlDialogComponent ], entryComponents: [ DefaultPlRendererComponent, diff --git a/src/app/core/default-pattern-renderer/default-pattern-renderer.component.html b/src/app/core/default-pattern-renderer/default-pattern-renderer.component.html index 92796708..699aab21 100644 --- a/src/app/core/default-pattern-renderer/default-pattern-renderer.component.html +++ b/src/app/core/default-pattern-renderer/default-pattern-renderer.component.html @@ -1,9 +1,12 @@ - - + + - + + +
@@ -16,12 +19,12 @@

- {{ relation?.sourcePatternName}} + {{ relation?.sourcePatternName}} remove{{relation?.type}} trending_flat - {{ relation?.targetPatternName}} + {{ relation?.targetPatternName}} {{relation?.description ? ': ' + relation?.description : ''}}


@@ -30,12 +33,12 @@

- {{ relation?.pattern1Name}} + {{ relation?.pattern1Name}} compare_arrows trending_flat{{relation?.type}} trending_flat - {{ relation?.pattern2Name}} + {{ relation?.pattern2Name}} {{relation?.description ? ': ' + relation?.description : ''}}


diff --git a/src/app/core/default-pattern-renderer/default-pattern-renderer.component.scss b/src/app/core/default-pattern-renderer/default-pattern-renderer.component.scss index 19356cd4..14fc6807 100644 --- a/src/app/core/default-pattern-renderer/default-pattern-renderer.component.scss +++ b/src/app/core/default-pattern-renderer/default-pattern-renderer.component.scss @@ -8,3 +8,12 @@ background-color: #f5f5f5; } +.mat-card-image { + width: auto; + height: 50px; + cursor: pointer; +} + +.mat-stroked-button{ + margin-top: 12px; +} diff --git a/src/app/core/default-pattern-renderer/default-pattern-renderer.component.ts b/src/app/core/default-pattern-renderer/default-pattern-renderer.component.ts index adc16cea..376934ad 100644 --- a/src/app/core/default-pattern-renderer/default-pattern-renderer.component.ts +++ b/src/app/core/default-pattern-renderer/default-pattern-renderer.component.ts @@ -24,14 +24,16 @@ import { PatternRelationDescriptorService } from '../service/pattern-relation-de import { DirectedEdgeModel } from '../model/hal/directed-edge.model'; import { Embedded } from '../model/hal/embedded'; import { DirectedEdesResponse } from '../model/hal/directed-edes-response.interface'; -import { UndirectedEdesResponse } from '../model/hal/undirected-edes-response.interface'; +import { UndirectedEdgesResponse } from '../model/hal/undirected-edes-response.interface'; import { UndirectedEdgeModel } from '../model/hal/undirected-edge.model'; import { globals } from '../../globals'; +import { UriConverter } from '../util/uri-converter'; +import { EditUrlDialogComponent } from '../component/edit-url-dialog/edit-url-dialog.component'; @Component({ selector: 'pp-default-pattern-renderer', templateUrl: './default-pattern-renderer.component.html', - styleUrls: ['./default-pattern-renderer.component.scss'] + styleUrls: [ './default-pattern-renderer.component.scss' ] }) export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy { @ViewChild(PatternPropertyDirective) ppPatternProperty: PatternPropertyDirective; @@ -47,6 +49,8 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy private patternLanguageId: string; private patternId: string; subscriptions: Subscription = new Subscription(); + showActionButtons: boolean; + constructor(private activatedRoute: ActivatedRoute, private toasterService: ToasterService, @@ -81,28 +85,27 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy } } - addContentInfoToPattern(edge: DirectedEdgeModel | UndirectedEdgeModel): Observable { - const toPattern = edge instanceof DirectedEdgeModel ? edge.targetPatternId : edge.pattern2Id; - return this.patternService.getPatternContentByPattern(this.patterns.find(it => it.id === toPattern)).pipe( - map((patterncontent) => { - const targetPatternContent = patterncontent.content; - // edge instanceof DirectedEdgeModel ? edge.content = targetPatternContent : edge.p2.content = targetPatternContent; - return edge; - })); - } - - getPatternInfos(): Observable { + getPatternData(): Observable { if (!this.patternLanguage) { console.log('tried to get patterns before the pattern language object with the url was instanciated'); return EMPTY; } - return this.patternService.getPatternByEncodedUri(this.patternId).pipe( - tap(pattern => this.pattern = pattern), - switchMap((pat) => { - const content = this.patternService.getPatternContentByPattern(this.pattern); - const renderedContent = this.patternService.getPatternRenderedContentByPattern(this.pattern); - return forkJoin([content, renderedContent]); - }), + // check if pattern is specified via UUIID or URI and load it accordingly + if (UriConverter.isUUID(this.patternId)) { + return this.patternService.getPatternById(this.patternLanguage, this.patternId).pipe( + tap(pattern => this.pattern = pattern), + switchMap(() => this.getPatternSectionContent())); + } else { + return this.patternService.getPatternByEncodedUri(this.patternId).pipe( + tap(pattern => this.pattern = pattern), + switchMap(() => this.getPatternSectionContent())); + } + } + + getPatternSectionContent(): Observable { + const content = this.patternService.getPatternContentByPattern(this.pattern); + const renderedContent = this.patternService.getPatternRenderedContentByPattern(this.pattern); + return forkJoin([ content, renderedContent ]).pipe( map((patternContent) => { this.pattern.renderedContent = patternContent[1].renderedContent; return this.pattern.content = patternContent[0].content; @@ -112,7 +115,7 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy getPatternLanguageLinks(): Observable { const $getDirectedEdges = this.getDirectedEdges(); const $getUndirectedEdges = this.getUndirectedEdges(); - return forkJoin([$getDirectedEdges, $getUndirectedEdges]).pipe(tap(() => this.isLoadingLinks = false)); + return forkJoin([ $getDirectedEdges, $getUndirectedEdges ]).pipe(tap(() => this.isLoadingLinks = false)); } getPatternByLink(edge: DirectedEdgeModel | UndirectedEdgeModel, res: any) { @@ -128,7 +131,7 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy } private createSectionComponent(section: string) { - let renderedContent = null; + let renderedContent; if (!this.pattern.renderedContent [section]) { renderedContent = this.pattern.content[section]; } else { @@ -164,7 +167,7 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy })); } - private getUndirectedEdges(): Observable> { + private getUndirectedEdges(): Observable> { if (!this.patternLanguage) { return EMPTY; } @@ -178,8 +181,7 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy private savePattern(section: string, previousContent: any, instance: MarkdownPatternSectionContentComponent) { const updateSubscription = this.patternService.updatePattern(this.pattern._links.self.href, this.pattern).subscribe( data => { - const test = data.body.renderedContent[section]; - this.pattern.renderedContent[section] = test; + this.pattern.renderedContent[section] = data.body.renderedContent[section]; instance.changeText(this.pattern.renderedContent[section]); this.toasterService.pop('success', 'Saved pattern'); }, @@ -194,18 +196,36 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy } private getData(): void { + //initialize pattern to get ID etc for future requests + let getPatternObservable; + if (UriConverter.isUUID(this.patternId)) { + getPatternObservable = this.patternService.getPatternById(this.patternLanguage, this.patternId).pipe( + tap(pattern => this.pattern = pattern)); + } else { + getPatternObservable = this.patternService.getPatternByEncodedUri(this.patternId).pipe(tap(pattern => this.pattern = pattern)); + } // get pattern language object with all the hal links that we need - const dataSubscription = this.patternLanguageService.getPatternLanguageByEncodedUri(this.patternLanguageId).pipe( - tap((patternLanguage) => this.patternLanguage = patternLanguage), - // get our individual pattern - switchMap(() => this.fillPatternSectionData()), - switchMap(() => this.getPatternLanguageLinks())).subscribe(() => - this.cdr.detectChanges()); + let dataObservable; + if (UriConverter.isUUID(this.patternLanguageId)) { + dataObservable = this.patternLanguageService.getPatternLanguageByID(this.patternLanguageId).pipe( + tap((patternLanguage) => this.patternLanguage = patternLanguage), + // get our individual pattern and the links in parallel + switchMap(() => forkJoin([ this.fillPatternSectionData(), this.getPatternLanguageLinks() ]))); + } else { + this.patternLanguageService.getPatternLanguageByEncodedUri(this.patternLanguageId).pipe( + tap((patternLanguage) => this.patternLanguage = patternLanguage), + // get our individual pattern and the links in parallel + switchMap(() => forkJoin([ this.fillPatternSectionData(), this.getPatternLanguageLinks() ]))); + } + const dataSubscription = forkJoin([getPatternObservable, dataObservable]).subscribe(() => this.cdr.detectChanges()); this.subscriptions.add(dataSubscription); + + + } private fillPatternSectionData() { - return this.getPatternInfos().pipe( + return this.getPatternData().pipe( tap(() => { this.patternLanguage.patternSchema.patternSectionSchemas.forEach((sec: PatternSectionSchema) => { this.createSectionComponent(sec.name); @@ -219,9 +239,6 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy { data: { firstPattern: this.pattern, patterns: this.patterns } } ); const dialogSubscription = dialogRef.afterClosed().pipe( - switchMap(result => { - return result ? this.addContentInfoToPattern(result) : EMPTY; - }), switchMap((edge) => { return edge ? this.insertEdge(edge) : EMPTY; })) @@ -242,4 +259,18 @@ export class DefaultPatternRendererComponent implements AfterViewInit, OnDestroy this.cdr.detach(); this.subscriptions.unsubscribe(); } + + editIcon() { + const dialogRef = this.dialog.open(EditUrlDialogComponent, { + width: '50%', + data: { pattern: this.pattern, icon: this.pattern.iconUrl, name: this.pattern.name } + }); + dialogRef.afterClosed().subscribe(result => { + if (result !== undefined) { + this.pattern.iconUrl = result.icon; + this.pattern.name = result.name; + this.patternService.updatePattern(this.pattern._links.self.href, this.pattern).subscribe(); + } + }); + } } diff --git a/src/app/core/default-pl-renderer/default-pl-renderer.component.html b/src/app/core/default-pl-renderer/default-pl-renderer.component.html index 867f4c5d..47536af6 100644 --- a/src/app/core/default-pl-renderer/default-pl-renderer.component.html +++ b/src/app/core/default-pl-renderer/default-pl-renderer.component.html @@ -1,4 +1,4 @@ - + + }" (addedEdge)="handleLinkAddedInGraphEditor($event)" (removedEdge)="handleLinkRemovedInGraphEditor($event)" [showPatternLanguageName]="false">
diff --git a/src/app/core/default-pl-renderer/default-pl-renderer.component.ts b/src/app/core/default-pl-renderer/default-pl-renderer.component.ts index b73fa35f..76e15a9d 100644 --- a/src/app/core/default-pl-renderer/default-pl-renderer.component.ts +++ b/src/app/core/default-pl-renderer/default-pl-renderer.component.ts @@ -19,7 +19,7 @@ import { EMPTY, forkJoin, Observable, Subscription } from 'rxjs'; import { Embedded } from '../model/hal/embedded'; import { DirectedEdesResponse } from '../model/hal/directed-edes-response.interface'; import { switchMap, tap } from 'rxjs/operators'; -import { UndirectedEdesResponse } from '../model/hal/undirected-edes-response.interface'; +import { UndirectedEdgesResponse } from '../model/hal/undirected-edes-response.interface'; import { DirectedEdgeModel } from '../model/hal/directed-edge.model'; import { UndirectedEdgeModel } from '../model/hal/undirected-edge.model'; import { CreatePatternRelationComponent } from '../component/create-pattern-relation/create-pattern-relation.component'; @@ -29,6 +29,8 @@ import { PatternService } from '../service/pattern.service'; import Pattern from '../model/hal/pattern.model'; import { FormControl } from '@angular/forms'; import { globals } from '../../globals'; +import { PatternRelationDescriptorDirection } from '../model/pattern-relation-descriptor-direction.enum'; + @Component({ selector: 'pp-default-pl-renderer', @@ -98,24 +100,52 @@ export class DefaultPlRendererComponent implements OnInit, OnDestroy { this.router.navigate(['create-patterns'], { relativeTo: this.activatedRoute }); } + + /** + * Opens a different Dialog when clicking the "Create Relation button" + * It differentiates between the user having or not having the graph component opened + * If the graph component is open when clicking "create relation" a selected pattern gets automatically filled in + * as the first Pattern of the relation in the create dialog + */ + private openCreateDialog() { + if (this.graphDisplayComponent === undefined) { + return this.dialog.open(CreatePatternRelationComponent, { + data: { + patterns: this.patterns, + patternlanguage: this.patternLanguage + } + }); + + } else { + return this.dialog.open(CreatePatternRelationComponent, { + data: { + firstPattern: this.graphDisplayComponent.selectedPattern, + patterns: this.patterns, + patternlanguage: this.patternLanguage + } + }); + } + } + + /** + * Method getting called when pressing the Add-Relation button + * If executed while having the graphview opened the button gets treated equally to + * an add-relation opertion inside of the graph + * If executed from the patternlanguage view the graph does not need to be updated and the edge just + * gets added to the database and the PatterLinklist + */ public addLink() { - // Todo: Make patternlanguage camelcase - const dialogRef = this.dialog.open(CreatePatternRelationComponent, { - data: { - patterns: this.patterns, - patternlanguage: this.patternLanguage - } - }); - const diaglogSubscription = dialogRef.afterClosed().pipe( - switchMap((edge) => { - return edge ? this.insertEdge(edge) : EMPTY; - })).subscribe(res => { - if (res) { - this.toasterService.pop('success', 'Added Relation'); - this.detectChanges(); + this.openCreateDialog().afterClosed().subscribe((edge) => { + if (edge !== undefined) { + if (this.graphDisplayComponent !== undefined) { + this.handleLinkAddedInGraphEditor(edge); + } else { + const insertionSubscription = this.insertEdge(edge).subscribe(); + this.subscriptions.add(insertionSubscription); + } + } }); - this.subscriptions.add(diaglogSubscription); } insertEdge(edge): Observable { @@ -134,15 +164,109 @@ export class DefaultPlRendererComponent implements OnInit, OnDestroy { this.subscriptions.add(relationSubscription); } - linkAddedInGraphEditor(edge) { + /** + * Gets called when Graphview child emits that an edge got added + * The graphview created edge is missing the Id that is getting assigned from the backend + * ---> it got deleted before calling this method + * ---> Edge needs to be saved in the backend & added into the graph again. + * + * @param edge + */ + handleLinkAddedInGraphEditor(edge) { + const insertionSubscription = this.insertEdge(edge).subscribe(res => { - this.toasterService.pop('success', 'Added Relation'); + let edgeAdd; + if (edge.pattern1Id != null) { + edgeAdd = { //undirected Edge + source: edge.pattern1Id, + target: edge.pattern2Id, + markerEnd: { template: 'arrow', scale: 0.5, relativeRotation: 0 }, + markerStart: { template: 'arrow', scale: 0.5, relativeRotation: 0 }, + id: res.body.id + } + } else { + edgeAdd = { //directed Edge + source: edge.sourcePatternId, + target: edge.targetPatternId, + markerEnd: { template: 'arrow', scale: 0.5, relativeRotation: 0 }, + id: res.body.id + }; + } + this.graphDisplayComponent.graphNativeElement.addEdge(edgeAdd, true); + this.toasterService.pop('success', 'Added Relation' + res.body.id); this.graphDisplayComponent.updateSideMenu(); this.detectChanges(); }); this.subscriptions.add(insertionSubscription); } + /** + * Gets called when Graphview child emits that an edge got removed. + * This is the case for Delete AND Update operations for edges + * ---> + * + * @param edge + */ + handleLinkRemovedInGraphEditor(edge) { + this.patternRelationDescriptorService.getAnyEdgeByUrl((edge.markerStart === undefined && edge.pattern1Id === undefined ? + this.patternLanguage._links.directedEdges.href : this.patternLanguage._links.undirectedEdges.href) + '/' + edge.id).subscribe(res => { + const patterns = Array.isArray(this.patterns) ? this.patterns : this.graphDisplayComponent.patternContainer.patterns; + let pattern1, pattern2, direction; + + + if (res.pattern1Id !== undefined) { + pattern1 = res.pattern1Id; + pattern2 = res.pattern2Id; + direction = PatternRelationDescriptorDirection.UnDirected + } else { + pattern1 = res.sourcePatternId; + pattern2 = res.targetPatternId; + direction = PatternRelationDescriptorDirection.DirectedRight; + } + const dialogRef = this.dialog.open(CreatePatternRelationComponent, { + data: { + firstPattern: patterns.find((pat) => pattern1 === pat.id), + secondPattern: patterns.find((pat) => pattern2 === pat.id), + preselectedEdgeDirection: direction, + patterns, + patternLanguage: this.patternLanguage, + patternContainer: this.graphDisplayComponent.patternContainer, + relationTypes: this.graphDisplayComponent.getGraphDataService().getEdgeTypes(), + description: res.description, + relationType: res.type, + isDelete: true, //indicates that the dialog is called from the linked removedRemoved method --> not create, but a delete / edit operation + } + }); + dialogRef.afterClosed().subscribe((dialogResult) => { + if (dialogResult !== undefined && dialogResult.deleteLink === undefined) { //edit edge + this.deleteEdge(this.graphDisplayComponent.currentEdge); + this.handleLinkAddedInGraphEditor(dialogResult); + } else if (dialogResult !== undefined && dialogResult.deleteLink === true) { // delete Edge + this.deleteEdge(edge); + } + }); + }) + } + + + /** + * Delete edge in graph, delete it in database and remove it from the Linklist getting used for graphrendering + * @param edge + */ + deleteEdge(edge) { + this.graphDisplayComponent.graphNativeElement.removeEdge(edge, true); + this.graphDisplayComponent.triggerRerendering(); + this.patternRelationDescriptorService.removeRelationFromPL(this.patternLanguage, edge); + this.removeEdgeFromPatternLinkList(edge) + } + + + removeEdgeFromPatternLinkList(edge) { + for (let i = 0; i < this.patternLinks.length; i++) { + this.patternLinks[i].id === edge.id ? this.patternLinks.splice(i, 1) : null; + } + } + reloadGraph() { this.graphDisplayComponent.reformatGraph(); } @@ -159,19 +283,35 @@ export class DefaultPlRendererComponent implements OnInit, OnDestroy { private loadData(): void { this.isLoadingPatternData = true; this.patternLanguageId = UriConverter.doubleDecodeUri(this.activatedRoute.snapshot.paramMap.get(globals.pathConstants.patternLanguageId)); - if (this.patternLanguageId) { - const loadDataSubscrition = this.patternLanguageService.getPatternLanguageByEncodedUri(this.patternLanguageId) + if (!this.patternLanguageId) { + return; + } + + let loadDataObservable; + // check if patternlanguage is specified via UUIID or URI and load it accordingly + if (UriConverter.isUUID(this.patternLanguageId)) { + loadDataObservable = this.patternLanguageService.getPatternLanguageByID(this.patternLanguageId) .pipe( tap(patternlanguage => this.patternLanguage = patternlanguage), - switchMap(() => this.loadPatterns()), - switchMap(() => this.getPatternLinks()) - ) - .subscribe(() => { - this.isLoadingLinkData = false; - this.detectChanges(); - }); - this.subscriptions.add(loadDataSubscrition); + switchMap(() => this.loadPatternsAndLinks()) + ); + } else { + loadDataObservable = this.patternLanguageService.getPatternLanguageByEncodedUri(this.patternLanguageId) + .pipe( + tap(patternlanguage => this.patternLanguage = patternlanguage), + switchMap(() => this.loadPatternsAndLinks()) + ); } + const loadDataSubscrition = loadDataObservable.subscribe(() => { + this.isLoadingLinkData = false; + this.detectChanges(); + }); + this.subscriptions.add(loadDataSubscrition); + + } + + loadPatternsAndLinks(): Observable { + return forkJoin([this.loadPatterns(), this.getPatternLinks()]); } private getDirectedEdges(): Observable> { @@ -184,7 +324,7 @@ export class DefaultPlRendererComponent implements OnInit, OnDestroy { })); } - private getUndirectedEdges(): Observable> { + private getUndirectedEdges(): Observable> { if (!this.patternLanguage) { return EMPTY; } @@ -200,11 +340,12 @@ export class DefaultPlRendererComponent implements OnInit, OnDestroy { this.patterns = patterns; this.patternsForCardsView = this.patterns; this.isLoadingPatternData = false; + this.detectChanges(); })); } ngOnDestroy(): void { - this.cdr.detach() + this.cdr.detach(); this.subscriptions.unsubscribe(); } } diff --git a/src/app/core/model/hal/undirected-edes-response.interface.ts b/src/app/core/model/hal/undirected-edes-response.interface.ts index 71177d78..0aa01c45 100644 --- a/src/app/core/model/hal/undirected-edes-response.interface.ts +++ b/src/app/core/model/hal/undirected-edes-response.interface.ts @@ -1,5 +1,5 @@ import { UndirectedEdgeModel } from './undirected-edge.model'; -export interface UndirectedEdesResponse { +export interface UndirectedEdgesResponse { undirectedEdgeModels: UndirectedEdgeModel[]; } diff --git a/src/app/core/service/pattern-language.service.ts b/src/app/core/service/pattern-language.service.ts index 9a8db930..6e1d9173 100644 --- a/src/app/core/service/pattern-language.service.ts +++ b/src/app/core/service/pattern-language.service.ts @@ -21,7 +21,7 @@ import { map } from 'rxjs/operators'; import PatternLanguages from '../model/hal/pattern-languages.model'; import { DirectedEdesResponse } from '../model/hal/directed-edes-response.interface'; import { Embedded } from '../model/hal/embedded'; -import { UndirectedEdesResponse } from '../model/hal/undirected-edes-response.interface'; +import { UndirectedEdgesResponse } from '../model/hal/undirected-edes-response.interface'; import { GraphNode } from '../component/graph-display/graph-display.component'; import PatternLanguageModel from '../model/hal/pattern-language-model.model'; import { GraphDataService } from './graph-data/graph-data.service'; @@ -38,7 +38,7 @@ export class PatternLanguageService implements GraphDataService { constructor(private http: HttpClient, private patternService: PatternService) { } - public getPatternLanguages(): Observable> { + getPatternLanguages(): Observable> { return this.getPatternLanguageResult() .pipe( map((result: PatternLanguages) => { @@ -47,31 +47,36 @@ export class PatternLanguageService implements GraphDataService { ); } - public getPatternLanguageResult(): Observable { + getPatternLanguageResult(): Observable { return this.http.get(this.repoEndpoint + '/patternLanguages'); } - public getPatternLanguageByUrl(url: string): Observable { + getPatternLanguageByUrl(url: string): Observable { return this.http.get(url).pipe( map(res => res) ); } - public getPatternLanguageByEncodedUri(encodedUri: string): Observable { + getPatternLanguageByEncodedUri(encodedUri: string): Observable { const url = this.repoEndpoint + '/patternLanguages/findByUri?encodedUri=' + encodedUri; return this.http.get(url); } - public savePatternLanguage(patternLanguage: PatternLanguage): Observable> { + getPatternLanguageById(id: string): Observable { + const url = this.repoEndpoint + '/patternLanguages/' + id; + return this.http.get(url); + } + + savePatternLanguage(patternLanguage: PatternLanguage): Observable> { return this.http.post>(this.repoEndpoint + '/patternLanguages', patternLanguage, { observe: 'response' }); } - public getDirectedEdges(patternLanguage: PatternLanguage): Observable> { + getDirectedEdges(patternLanguage: PatternLanguage): Observable> { return this.http.get>(patternLanguage._links.directedEdges.href); } - public getUndirectedEdges(patternLanguage: PatternLanguage): Observable> { - return this.http.get>(patternLanguage._links.undirectedEdges.href); + getUndirectedEdges(patternLanguage: PatternLanguage): Observable> { + return this.http.get>(patternLanguage._links.undirectedEdges.href); } saveGraph(patternLanguage: PatternLanguage, nodes: Array) { @@ -100,6 +105,6 @@ export class PatternLanguageService implements GraphDataService { } getEdgeTypes(): Observable { - return of([]); + return of(); } } diff --git a/src/app/core/service/pattern-relation-descriptor.service.ts b/src/app/core/service/pattern-relation-descriptor.service.ts index 21017d5d..3780f39d 100644 --- a/src/app/core/service/pattern-relation-descriptor.service.ts +++ b/src/app/core/service/pattern-relation-descriptor.service.ts @@ -1,6 +1,5 @@ import { Injectable } from '@angular/core'; -import { globals } from '../../globals'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient} from '@angular/common/http'; import { forkJoin, Observable, of } from 'rxjs'; import { DirectedEdgeModel } from '../model/hal/directed-edge.model'; import { UndirectedEdgeModel } from '../model/hal/undirected-edge.model'; @@ -16,8 +15,6 @@ import { PatternResponse } from '../model/hal/pattern-response.interface'; }) export class PatternRelationDescriptorService { - private repoEndpoint = globals.repoEndpoint; - constructor(private http: HttpClient) { } @@ -27,6 +24,20 @@ export class PatternRelationDescriptorService { this.http.post(patternLanguage._links.undirectedEdges.href, new CreateUndirectedEdgeRequest(relation), { observe: 'response' }); } + removeRelationFromPL(patternLanguage: PatternLanguage, relation: any): void { + relation.markerStart === undefined ? + this.http.delete(patternLanguage._links.directedEdges.href + '/' + relation.id).subscribe() : + this.http.delete(patternLanguage._links.undirectedEdges.href + '/' + relation.id).subscribe(); + } + + getAnyEdgeByUrl(url: string): Observable { + if (url.includes('undirectedEdges')) { + return this.http.get(url); + } + return this.http.get(url); + } + + getDirectedEdgeByUrl(url: string): Observable { return this.http.get(url); } @@ -47,7 +58,7 @@ export class PatternRelationDescriptorService { const observables = []; const edgeLinks = ['undirectedEdges', 'outgoingDirectedEdges', 'ingoingDirectedEdges']; edgeLinks.forEach((edgeType: string) => { - const edgeLink = pattern._links[ edgeType ]; + const edgeLink = pattern._links[edgeType]; if (edgeLink) { const halLinks = Array.isArray(edgeLink) ? edgeLink : [edgeLink]; observables.push(...halLinks.map(link => diff --git a/src/app/core/service/pattern-view.service.ts b/src/app/core/service/pattern-view.service.ts index 92b2d8ae..5a93586d 100644 --- a/src/app/core/service/pattern-view.service.ts +++ b/src/app/core/service/pattern-view.service.ts @@ -25,7 +25,7 @@ import { LinksToOtherPattern } from '../../pattern-view-management/add-to-view/a import { AddDirectedEdgeToViewRequest } from '../model/hal/add-directed-edge-to-view-request'; import { AddUndirectedEdgeToViewRequest } from '../model/hal/add-undirected-edge-to-view-request'; import { Embedded } from '../model/hal/embedded'; -import { UndirectedEdesResponse } from '../model/hal/undirected-edes-response.interface'; +import { UndirectedEdgesResponse } from '../model/hal/undirected-edes-response.interface'; import { DirectedEdesResponse } from '../model/hal/directed-edes-response.interface'; import { GraphNode } from '../component/graph-display/graph-display.component'; import { GraphDataService } from './graph-data/graph-data.service'; @@ -49,7 +49,7 @@ export class PatternViewService implements GraphDataService { return this.http.post(url, view, { observe: 'response' }); } - addPatterns(url: string, patterns: Pattern[]): Observable { + addPatterns(url: string, patterns: Pattern[]): Observable { const observables = patterns.map(pat => this.http.post(url, pat, { observe: 'response' })); return observables.length > 0 ? forkJoin(observables) : of(null); } @@ -82,12 +82,22 @@ export class PatternViewService implements GraphDataService { return observables.length > 0 ? forkJoin(observables) : EMPTY; } + getDirectedEdges(patternContainer: PatternContainer): Observable> { return this.http.get>(patternContainer._links.directedEdges.href); } - getUndirectedEdges(patternContainer: PatternContainer): Observable> { - return this.http.get>(patternContainer._links.undirectedEdges.href); + getUndirectedEdges(patternContainer: PatternContainer): Observable> { + return this.http.get>(patternContainer._links.undirectedEdges.href); + } + + getDirectedEdgeById(patternViewId, edgeId: string) : Observable{ + return this.http.get(this.repoEndpoint + /patternViews/+ patternViewId + /directedEdges/ + edgeId) + } + + + getUndirectedEdgeById(patternViewId, edgeId: string) : Observable{ + return this.http.get(this.repoEndpoint + /patternViews/+ patternViewId + /undirectedEdges/ + edgeId) } deleteLink(patternLink: any): Observable { @@ -105,4 +115,11 @@ export class PatternViewService implements GraphDataService { getEdgeTypes(): Observable { return of(); } + + removeRelationFromView(patternContainer: PatternContainer, relation: any):void { + relation.markerStart === undefined? + this.http.delete(patternContainer._links.directedEdges.href + '/' + relation.id).subscribe(): + this.http.delete(patternContainer._links.undirectedEdges.href + '/' + relation.id).subscribe(); + } + } diff --git a/src/app/core/util/uri-converter.ts b/src/app/core/util/uri-converter.ts index 5f0bd174..2927a7b1 100644 --- a/src/app/core/util/uri-converter.ts +++ b/src/app/core/util/uri-converter.ts @@ -51,5 +51,14 @@ export class UriConverter { }); } + // this function checks if a given urlParam is a UUID (otherwise the entity is specified via its URI) + static isUUID(urlParam) { + const s = '' + urlParam; + + const match = s.match('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'); + return match !== null; + + } + } diff --git a/src/app/design-model-module/service/design-model.service.ts b/src/app/design-model-module/service/design-model.service.ts index f55ed18d..6b611063 100644 --- a/src/app/design-model-module/service/design-model.service.ts +++ b/src/app/design-model-module/service/design-model.service.ts @@ -23,16 +23,12 @@ import { UndirectedEdgeModel } from '../../core/model/hal/undirected-edge.model' import { LinksToOtherPattern } from '../../pattern-view-management/add-to-view/add-to-view.component'; // TODO import { AddDirectedEdgeToViewRequest } from '../../core/model/hal/add-directed-edge-to-view-request'; import { AddUndirectedEdgeToViewRequest } from '../../core/model/hal/add-undirected-edge-to-view-request'; -import { Embedded } from '../../core/model/hal/embedded'; -import { UndirectedEdesResponse } from '../../core/model/hal/undirected-edes-response.interface'; -import { DirectedEdesResponse } from '../../core/model/hal/directed-edes-response.interface'; import { GraphNode } from '../../core/component/graph-display/graph-display.component'; import { DesignModel } from '../model/hal/design-model'; import { DesignModelResponse } from '../model/hal/design-model-response'; import { GraphDataService } from '../../core/service/graph-data/graph-data.service'; import { map, tap } from 'rxjs/operators'; import { GraphDataSavePatternService } from '../../core/service/graph-data/graph-data-save-pattern.service'; -import { HalLink } from '../../core/model/hal/hal-link.interface'; import { TextComponent } from '@ustutt/grapheditor-webcomponent/lib/edge'; import { HalCollectionResponse } from '../model/hal/hal-collection-response'; import { HalEntityResponse } from '../model/hal/hal-entity-response'; diff --git a/src/app/pattern-language-management/create-pattern/create-pattern.component.ts b/src/app/pattern-language-management/create-pattern/create-pattern.component.ts index 5c094813..61b47558 100644 --- a/src/app/pattern-language-management/create-pattern/create-pattern.component.ts +++ b/src/app/pattern-language-management/create-pattern/create-pattern.component.ts @@ -27,7 +27,7 @@ export class CreatePatternComponent implements OnInit { iconForm: FormGroup; patterns: Array; - patternLanguageUri: string; + patternLanguageId: string; iconPreviewVisible = false; wasSaveButtonClicked = false; patternValuesFormGroup: FormGroup; @@ -67,11 +67,11 @@ export class CreatePatternComponent implements OnInit { ngOnInit() { - this.patternLanguageUri = UriConverter.doubleDecodeUri(this.activatedRoute.snapshot.paramMap.get(globals.pathConstants.patternLanguageId)); + this.patternLanguageId = UriConverter.doubleDecodeUri(this.activatedRoute.snapshot.paramMap.get(globals.pathConstants.patternLanguageId)); this.markdown = new MarkdownIt(); this.markdown.use(markdownitKatex); - this.patternLanguageService.getPatternLanguageByEncodedUri(this.patternLanguageUri).subscribe((pl: PatternLanguage) => { + this.patternLanguageService.getPatternLanguageById(this.patternLanguageId).subscribe((pl: PatternLanguage) => { this.patternLanguage = pl; this.sections = this.patternLanguage.patternSchema ? this.patternLanguage.patternSchema.patternSectionSchemas.map((schema: PatternSectionSchema) => schema.label) : []; @@ -118,17 +118,25 @@ export class CreatePatternComponent implements OnInit { } + //Format Input text so MAP Patterns can be directly copied into Pattern Atlas + reformatMapPatternInput(text: string){ + return text.replace(new RegExp('', 'g'), ' ') + .replace(new RegExp('\{#sec:.*}', 'g'), ' ') + .replace(new RegExp('#{3,}', 'g'), '##'); + } + parseMarkdownText(): TokensList { - return marked.lexer(this._textEditor.value); + return marked.lexer(this.reformatMapPatternInput(this._textEditor.value)); } onChangeMarkdownText(): void { + this.parsePatternInput(); const currentText = this.parseMarkdownText(); if (this.invalidTextEdit(currentText)) { // TODO } if (this.markdown) { - document.getElementById('preview').innerHTML = this.markdown.render(this._textEditor.value); + document.getElementById('preview').innerHTML = this.markdown.render(this.reformatMapPatternInput(this._textEditor.value)); } } @@ -195,10 +203,14 @@ export class CreatePatternComponent implements OnInit { if (lines[ i ].type === 'heading') { break; } + if (lines[ i ].type === 'space') { + sectionContent.push('\n'); + } if (lines[ i ][ 'text' ]) { // if a list item was parsed before, add it to the text - sectionContent.push(i > 0 && CreatePatternComponent.isListItem(i, sectionIndex, lines) ? '* ' + lines[ i ][ 'text' ] : lines[ i ][ 'text' ]); + sectionContent.push(i > 0 && CreatePatternComponent.isListItem(i, sectionIndex, lines) ? '* ' + lines[ i ][ 'text' ] : lines[ i ][ 'text' ] ); } + console.log('sectioncontent:'+sectionContent) } if (this.patternValuesFormGroup) { if (this.patternValuesFormGroup.controls[ sectionName ]) { diff --git a/src/app/pattern-language-management/pattern-language-management.module.ts b/src/app/pattern-language-management/pattern-language-management.module.ts index c9316c6f..bacb55b6 100644 --- a/src/app/pattern-language-management/pattern-language-management.module.ts +++ b/src/app/pattern-language-management/pattern-language-management.module.ts @@ -31,9 +31,8 @@ import { ToasterModule } from 'angular2-toaster'; import { DragDropModule } from '@angular/cdk/drag-drop'; import { MatListModule } from '@angular/material/list'; import { MatRippleModule } from '@angular/material/core'; -import { Routes, RouterModule } from '@angular/router'; +import { RouterModule, Routes } from '@angular/router'; import { GraphDataService } from '../core/service/graph-data/graph-data.service'; -import { DesignModelService } from '../design-model-module/service/design-model.service'; import { globals } from '../globals'; import { MatFormFieldModule } from '@angular/material/form-field'; import { PatternLanguageService } from '../core/service/pattern-language.service'; @@ -69,7 +68,7 @@ export const PL_ROUTES: Routes = [ component: PatternContainerComponent } -] +]; @NgModule({ imports: [ diff --git a/src/app/pattern-language-management/pattern-language-management/pattern-language-management.component.ts b/src/app/pattern-language-management/pattern-language-management/pattern-language-management.component.ts index e6f4f68a..5f017f2f 100644 --- a/src/app/pattern-language-management/pattern-language-management/pattern-language-management.component.ts +++ b/src/app/pattern-language-management/pattern-language-management/pattern-language-management.component.ts @@ -27,7 +27,6 @@ import { DialogPatternLanguageResult } from '../data/DialogPatternLanguageResult import { map } from 'rxjs/operators'; import PatternLanguageModel from '../../core/model/hal/pattern-language-model.model'; import UriEntity from '../../core/model/hal/uri-entity.model'; -import { UriConverter } from '../../core/util/uri-converter'; @Component({ selector: 'pp-pattern-language-management', @@ -38,75 +37,74 @@ import { UriConverter } from '../../core/util/uri-converter'; export class PatternLanguageManagementComponent implements OnInit { - patternLanguages: Array; + patternLanguages: Array; - constructor( - private cdr: ChangeDetectorRef, - private router: Router, - private activatedRoute: ActivatedRoute, - private zone: NgZone, - private dialog: MatDialog, - private _cookieService: CookieService, - private _toasterService: ToasterService, - private patternLanguageService: PatternLanguageService) { - } + constructor( + private cdr: ChangeDetectorRef, + private router: Router, + private activatedRoute: ActivatedRoute, + private zone: NgZone, + private dialog: MatDialog, + private _cookieService: CookieService, + private _toasterService: ToasterService, + private patternLanguageService: PatternLanguageService) { + } - // function used to sort the patternlanguages (by name) - private static sortPatternLanguages(pl1: PatternLanguageModel, pl2: PatternLanguageModel): number { - if (pl1.name > pl2.name) { - return 1; - } - if (pl1.name < pl2.name) { - return -1; - } - return 0; + // function used to sort the patternlanguages (by name) + private static sortPatternLanguages(pl1: PatternLanguageModel, pl2: PatternLanguageModel): number { + if (pl1.name > pl2.name) { + return 1; } - - ngOnInit() { - this.patternLanguages = Array.from(this.activatedRoute.snapshot.data.patternlanguages.values()) - .sort(PatternLanguageManagementComponent.sortPatternLanguages); + if (pl1.name < pl2.name) { + return -1; } + return 0; + } - // reload the current data from https://purl.org/patternpedia that contains all patternlangauges - async reloadPatternRepo() { - this.patternLanguageService.getPatternLanguages() - .pipe( - map(result => result.sort(PatternLanguageManagementComponent.sortPatternLanguages))) - .subscribe(result => { - this.patternLanguages = result; - this._toasterService.pop('success', 'Reloaded Pattern Languages'); - this.cdr.detectChanges(); - return result; - }); - this.cdr.detectChanges(); - } + ngOnInit() { + this.patternLanguages = Array.from(this.activatedRoute.snapshot.data.patternlanguages.values()) + .sort(PatternLanguageManagementComponent.sortPatternLanguages); + } - navigateToPL(pl: UriEntity): void { - this.zone.run(() => { - this.router.navigate([UriConverter.doubleEncodeUri(pl.uri)], { relativeTo: this.activatedRoute }); + async reloadPatternRepo() { + this.patternLanguageService.getPatternLanguages() + .pipe( + map(result => result.sort(PatternLanguageManagementComponent.sortPatternLanguages))) + .subscribe(result => { + this.patternLanguages = result; + this._toasterService.pop('success', 'Reloaded Pattern Languages'); + this.cdr.detectChanges(); + return result; }); - } + this.cdr.detectChanges(); + } - goToPatternLanguageCreation(): void { - const dialogRef = this.dialog.open(CreateEditPatternLanguageComponent, { data: { componentDialogType: CreateEditComponentDialogType.PATTERN_LANGUAGE } }); + navigateToPL(pl: UriEntity): void { + this.zone.run(() => { + this.router.navigate([pl.id], { relativeTo: this.activatedRoute }); + }); + } - // Save PatternLanguage when user presses save - (dialogRef.componentInstance).saveClicked - .subscribe((result: DialogPatternLanguageResult) => { - const patternLanguage = result.dialogResult; - this.patternLanguageService.savePatternLanguage(patternLanguage) - .subscribe(() => { - this.patternLanguageService.getPatternLanguages() - .pipe( - map(patternLanguageModels => patternLanguageModels.sort(PatternLanguageManagementComponent.sortPatternLanguages))) - .subscribe(patternLanguageModels => { - this.patternLanguages = patternLanguageModels; - }); - this._toasterService.pop('success', 'Pattern Language created'); - }, err => { - console.error(err); - this._toasterService.pop('error', 'Error occurred', JSON.stringify(err)); - }); - }); - } + goToPatternLanguageCreation(): void { + const dialogRef = this.dialog.open(CreateEditPatternLanguageComponent, { data: { componentDialogType: CreateEditComponentDialogType.PATTERN_LANGUAGE } }); + + // Save PatternLanguage when user presses save + (dialogRef.componentInstance).saveClicked + .subscribe((result: DialogPatternLanguageResult) => { + const patternLanguage = result.dialogResult; + this.patternLanguageService.savePatternLanguage(patternLanguage) + .subscribe(() => { + this.patternLanguageService.getPatternLanguages() + .pipe( + map(patternLanguageModels => patternLanguageModels.sort(PatternLanguageManagementComponent.sortPatternLanguages))) + .subscribe(patternLanguageModels => { + this.patternLanguages = patternLanguageModels; + }); + this._toasterService.pop('success', 'Pattern Language created'); + }, err => { + console.error(err); + this._toasterService.pop('error', 'Error occurred', JSON.stringify(err)); + }); + }); + } } diff --git a/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.html b/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.html index 9a61fd57..3dd61a0e 100644 --- a/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.html +++ b/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.html @@ -56,7 +56,7 @@ - @@ -68,6 +68,7 @@ patternLanguage: null, patternContainer: this.patternViewResponse, patternLanguages: this.patternLanguages }" - (addedEdge)="addedEdgeInGraphView($event)" [showPatternLanguageName]="true"> + (addedEdge)="handleLinkAddedInGraphEditor($event)" (removedEdge)="handleLinkRemovedInGraphEditor($event)" [showPatternLanguageName]="true" + [showViewRelations]="true"> diff --git a/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.ts b/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.ts index bba299a9..2419532b 100644 --- a/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.ts +++ b/src/app/pattern-view-management/pattern-view-renderer/pattern-view-renderer.component.ts @@ -19,16 +19,17 @@ import { AddUndirectedEdgeToViewRequest } from '../../core/model/hal/add-undirec import { UndirectedEdgeModel } from '../../core/model/hal/undirected-edge.model'; import PatternLanguageModel from '../../core/model/hal/pattern-language-model.model'; import { Embedded } from '../../core/model/hal/embedded'; -import { UndirectedEdesResponse } from '../../core/model/hal/undirected-edes-response.interface'; +import { UndirectedEdgesResponse } from '../../core/model/hal/undirected-edes-response.interface'; import { DirectedEdesResponse } from '../../core/model/hal/directed-edes-response.interface'; import { GraphDisplayComponent } from '../../core/component/graph-display/graph-display.component'; import { DeletePatternRelationComponent } from '../../core/component/delete-pattern-relation/delete-pattern-relation.component'; import { PatternRelationDescriptorService } from '../../core/service/pattern-relation-descriptor.service'; +import { PatternRelationDescriptorDirection } from '../../core/model/pattern-relation-descriptor-direction.enum'; @Component({ selector: 'pp-pattern-view-renderer', templateUrl: './pattern-view-renderer.component.html', - styleUrls: ['./pattern-view-renderer.component.scss'] + styleUrls: [ './pattern-view-renderer.component.scss' ] }) export class PatternViewRendererComponent implements OnInit, AfterViewInit { @@ -88,25 +89,66 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { addLinks(pattern: Pattern) { const dialogRef = this.matDialog.open(AddToViewComponent, - { data: { links: this.mapPatternLinksToTreeNode(pattern), title: 'Add related Patterns', patternId: pattern.id } }); + { + data: { + links: this.mapPatternLinksToTreeNode(pattern), + title: 'Add related Patterns', + patternId: pattern.id + } + }); this.subscribeToLinkDialogResult(dialogRef); + } + /** + * Method getting called when pressing the Add-Relation button + * If executed while having the graphview opened the button gets treated equally to + * an add-relation opertion inside of the graph + * If executed from the patternview view the graph does not need to be updated and the edge just + * gets added to the database and the PatterLinklist + */ addLink() { - const dialogRef = this.matDialog.open(CreatePatternRelationComponent, { data: { patterns: this.patterns, patternview: this.patternViewResponse } }); - dialogRef.afterClosed().pipe( - switchMap((edge) => { - return edge ? this.createLink(edge) : EMPTY; - })).subscribe((res) => { - if (res) { - this.toasterService.pop('success', 'Relation added'); + this.openCreateDialog().afterClosed().subscribe((edge) => { + if (edge !== undefined) { + if (this.graphDisplayComponent !== undefined) { + this.handleLinkAddedInGraphEditor(edge); + } else { + this.createLink(edge).subscribe(() => { + this.toasterService.pop('success', 'Relation added'); + }) + } } }); } + /** + * Opens a different Dialog when clicking the "Create Relation button" + * It differentiates between the user having or not having the graph component opened + * If the graph component is open when clicking "create relation" a selected pattern gets automatically filled in + * as the first Pattern of the relation in the create dialog + */ + private openCreateDialog() { + if (this.graphDisplayComponent === undefined) { + return this.matDialog.open(CreatePatternRelationComponent, { + data: { + patterns: this.patterns, + patternContainer: this.patternViewResponse, + } + }); + + } else { + return this.matDialog.open(CreatePatternRelationComponent, { + data: { + firstPattern: this.graphDisplayComponent.selectedPattern, + patterns: this.patterns, + patternContainer: this.patternViewResponse, + } + }); + } + } + detectChanges() { this.cdr.detectChanges(); - console.log('detected'); } getLinkCount(directedEdges: HalLink[] | HalLink) { @@ -137,11 +179,11 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { showIngoingEdges(ingoingEdges: HalLink[]) { if (ingoingEdges) { const dialogRef = this.matDialog.open(DeletePatternRelationComponent, { - data: { edges: ingoingEdges, type: 'ingoing' }, + data: { edges: ingoingEdges, type: 'ingoing'}, width: '600px', panelClass: 'delete-relation-dialog' }); - dialogRef.afterClosed().subscribe(() => { + dialogRef.afterClosed().subscribe(data => { // reload patterns since ng for pattern loop doesnt get updated else this.getData().subscribe( () => { @@ -155,11 +197,11 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { showOutgoingEdges(outgoingEdges: HalLink[]) { if (outgoingEdges) { const dialogRef = this.matDialog.open(DeletePatternRelationComponent, { - data: { edges: outgoingEdges, type: 'outgoing' }, + data: { edges: outgoingEdges, type: 'outgoing'}, width: '600px', panelClass: 'delete-relation-dialog' }); - dialogRef.afterClosed().subscribe(() => { + dialogRef.afterClosed().subscribe(data => { // reload patterns since ng for pattern loop doesnt get updated else this.getData().subscribe( () => { @@ -174,12 +216,103 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { this.graphVisible = isGraphVisible; } - addedEdgeInGraphView(edge: any) { - if (edge) { - this.createLink(edge).subscribe(() => { - this.toasterService.pop('success', 'Link added'); - this.cdr.detectChanges(); + + /** + * Gets called when Graphview child emits that an edge got added + * The graphview created edge is missing the Id that is getting assigned from the backend + * ---> it got deleted before calling this method + * ---> Edge needs to be saved in the backend & added into the graph again. + * + * @param edge + */ + handleLinkAddedInGraphEditor(edge: any) { + this.createLink(edge).subscribe(res => { + let edgeAdd; + if (edge.pattern1Id != null) { //undirected Edge + edgeAdd = { + source: edge.pattern1Id, + target: edge.pattern2Id, + markerEnd: { template: 'arrow', scale: 0.5, relativeRotation: 0 }, + markerStart: { template: 'arrow', scale: 0.5, relativeRotation: 0 }, + id: res.body.id + } + } else { + edgeAdd = { //directed Edge + source: edge.sourcePatternId, + target: edge.targetPatternId, + markerEnd: { template: 'arrow', scale: 0.5, relativeRotation: 0 }, + id: res.body.id + }; + } + this.graphDisplayComponent.graphNativeElement.addEdge(edgeAdd, true); + this.toasterService.pop('success', 'Added Relation' + res.body.id); + this.graphDisplayComponent.updateSideMenu(); + this.detectChanges(); + }); + } + + /** + * Gets called when Graphview child emits that an edge got removed. + * This is the case for Delete AND Update operations for edges + * ---> + * + * @param edge (in graph format) + */ + handleLinkRemovedInGraphEditor(edge) { + this.patternRLDescriptorService.getAnyEdgeByUrl((edge.markerStart === undefined && edge.pattern1Id === undefined ? + this.patternViewResponse._links.directedEdges.href : this.patternViewResponse._links.undirectedEdges.href) + '/' + edge.id).subscribe(res => { + const patterns = Array.isArray(this.patterns) ? this.patterns : this.graphDisplayComponent.patternContainer.patterns; + let pattern1, pattern2, direction; + + if (res.pattern1Id !== undefined) { + pattern1 = res.pattern1Id; + pattern2 = res.pattern2Id; + direction = PatternRelationDescriptorDirection.UnDirected + } else { + pattern1 = res.sourcePatternId; + pattern2 = res.targetPatternId; + direction = PatternRelationDescriptorDirection.DirectedRight; + } + const dialogRef = this.matDialog.open(CreatePatternRelationComponent, { + data: { + firstPattern: patterns.find((pat) => pattern1 === pat.id), + secondPattern: patterns.find((pat) => pattern2 === pat.id), + preselectedEdgeDirection: direction, + patterns, + patternLanguage: null, + patternContainer: this.graphDisplayComponent.patternContainer, + relationTypes: this.graphDisplayComponent.getGraphDataService().getEdgeTypes(), + description: res.description, + relationType: res.type, + isDelete: true, + } }); + dialogRef.afterClosed().subscribe((dialogResult) => { + if (dialogResult !== undefined && dialogResult.deleteLink === undefined) { //edit edge + this.deleteEdge(this.graphDisplayComponent.currentEdge); + this.handleLinkAddedInGraphEditor(dialogResult); + } else if (dialogResult !== undefined && dialogResult.deleteLink === true) { // delete Edge + this.deleteEdge(edge); + } + }) + }) + } + + + /** + * Delete edge in graph, delete it in database and remove it from the Linklist getting used for graphrendering + * @param edge (in graph format) + */ + deleteEdge(edge) { + this.graphDisplayComponent.graphNativeElement.removeEdge(edge, true); + this.graphDisplayComponent.triggerRerendering(); + !(edge.markerStart === undefined) ? this.removeUndirectedEdgeFromPattern(edge) : this.removeDirectedEdgeFromPattern(edge); + this.removeEdgeFromPatternLinkList(edge); + } + + removeEdgeFromPatternLinkList(edge) { + for (let i = 0; i < this.patternLinks.length; i++) { + this.patternLinks[i].id === edge.id ? this.patternLinks.splice(i, 1) : null; } } @@ -197,7 +330,7 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { })); } - private getUndirectedEdges(): Observable> { + private getUndirectedEdges(): Observable> { if (!this.patternViewResponse) { return EMPTY; } @@ -217,7 +350,9 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { new AddDirectedEdgeToViewRequest(edge) : new AddUndirectedEdgeToViewRequest(edge) ).pipe( - tap((res) => res ? this.getEdgeByUrl(edge, res) : EMPTY)); + tap((res) => { + res ? this.getEdgeByUrl(edge, res) : EMPTY; + })); } private getEdgeByUrl(edge: DirectedEdgeModel | UndirectedEdgeModel, res: any): void { @@ -232,12 +367,75 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { ); } + + /** + * Removes Undirected Edge from the Href lists of the related patterns so they dont get shown in the card view after getting deleted + * After removing the edge from the href lists, call to backend to delete edge + * @param edge + */ + private removeUndirectedEdgeFromPattern(edge): void { + const pattern1 = this.patterns.find(x => x.id === edge.source); + const pattern2 = this.patterns.find(x => x.id === edge.target); + + const subscription = this.patternViewService.getUndirectedEdgeById(this.patternViewResponse.id, edge.id).pipe(tap(deleteEdge => { + if (Array.isArray(pattern1._links.undirectedEdges)) { + pattern1._links.undirectedEdges.splice(this.getIndexOfHref(pattern1._links.undirectedEdges,deleteEdge._links.self.href), 1); + this.patterns.find(x => x.id === edge.source)._links.undirectedEdges = pattern1._links.undirectedEdges; + } else { + this.patterns.find(x => x.id === edge.source)._links.undirectedEdges = undefined; + } + + if (Array.isArray(pattern2._links.undirectedEdges)) { + pattern2._links.undirectedEdges.splice(this.getIndexOfHref(pattern2._links.undirectedEdges,deleteEdge._links.self.href), 1); + this.patterns.find(x => x.id === edge.target)._links.undirectedEdges = pattern2._links.undirectedEdges; + } else { + this.patterns.find(x => x.id === edge.target)._links.undirectedEdges = undefined; + } + })); + subscription.subscribe(() => this.patternViewService.removeRelationFromView(this.patternViewResponse, edge)); + } + + /** + * Removes Directed Edge from the Href lists of the related patterns so they dont get shown in the card view after getting deleted + * After removing the edge from the href lists, call to backend to delete edge + * @param edge + */ + private removeDirectedEdgeFromPattern(edge): void { + const pattern1 = this.patterns.find(x => x.id === edge.source); + const pattern2 = this.patterns.find(x => x.id === edge.target); + + const subscription = this.patternViewService.getDirectedEdgeById(this.patternViewResponse.id, edge.id).pipe(tap(deleteEdge => { + if (Array.isArray(pattern1._links.outgoingDirectedEdges)) { + pattern1._links.outgoingDirectedEdges.splice(this.getIndexOfHref(pattern1._links.outgoingDirectedEdges, deleteEdge._links.self.href), 1); + this.patterns.find(x => x.id === edge.source)._links.outgoingDirectedEdges = pattern1._links.outgoingDirectedEdges; + } else { + this.patterns.find(x => x.id === edge.source)._links.outgoingDirectedEdges = undefined; + } + + if (Array.isArray(pattern2._links.ingoingDirectedEdges)) { + pattern2._links.ingoingDirectedEdges.splice(this.getIndexOfHref(pattern2._links.ingoingDirectedEdges, deleteEdge._links.self.href), 1); + this.patterns.find(x => x.id === edge.target)._links.ingoingDirectedEdges = pattern2._links.ingoingDirectedEdges; + } else { + this.patterns.find(x => x.id === edge.target)._links.ingoingDirectedEdges = undefined; + } + })); + subscription.subscribe(() => this.patternViewService.removeRelationFromView(this.patternViewResponse, edge)); + } + + getIndexOfHref(halLinkArray: HalLink[], href: string): number{ + let index = -1; + for (let i = 0; i < halLinkArray.length; i++ ){ + halLinkArray[i].href === href ? index = i : null; + } + return index; + } + private addUndirectedEdgeToPattern(edge: UndirectedEdgeModel): void { const pattern1 = this.patterns.find(x => x.id === edge.pattern1Id); if (!pattern1._links.undirectedEdges) { pattern1._links.undirectedEdges = edge._links.self; } else if (!Array.isArray(pattern1._links.undirectedEdges)) { - pattern1._links.undirectedEdges = [pattern1._links.undirectedEdges, edge._links.self]; + pattern1._links.undirectedEdges = [ pattern1._links.undirectedEdges, edge._links.self ]; } else { pattern1._links.undirectedEdges.push(edge._links.self); } @@ -247,7 +445,7 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { pattern2._links.undirectedEdges = edge._links.self; return; } else if (!Array.isArray(pattern2._links.undirectedEdges)) { - pattern2._links.undirectedEdges = [pattern2._links.undirectedEdges, edge._links.self]; + pattern2._links.undirectedEdges = [ pattern2._links.undirectedEdges, edge._links.self ]; } else { pattern2._links.undirectedEdges.push(edge._links.self); } @@ -258,7 +456,7 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { if (!srcPattern._links.outgoingDirectedEdges) { srcPattern._links.outgoingDirectedEdges = edge._links.self; } else if (!Array.isArray(srcPattern._links.outgoingDirectedEdges)) { - srcPattern._links.outgoingDirectedEdges = [srcPattern._links.outgoingDirectedEdges, edge._links.self]; + srcPattern._links.outgoingDirectedEdges = [ srcPattern._links.outgoingDirectedEdges, edge._links.self ]; } else { srcPattern._links.outgoingDirectedEdges.push(edge._links.self); } @@ -268,7 +466,7 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { targetPattern._links.ingoingDirectedEdges = edge._links.self; return; } else if (!Array.isArray(targetPattern._links.ingoingDirectedEdges)) { - targetPattern._links.ingoingDirectedEdges = [targetPattern._links.ingoingDirectedEdges, edge._links.self]; + targetPattern._links.ingoingDirectedEdges = [ targetPattern._links.ingoingDirectedEdges, edge._links.self ]; } else { targetPattern._links.ingoingDirectedEdges.push(edge._links.self); } @@ -295,13 +493,13 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { private getData(): Observable { const $getPatternLanguages = this.getPatternLanguages(); const $getCurrentPatternView = this.getCurrentPatternViewAndPatterns(); - return forkJoin([$getPatternLanguages, $getCurrentPatternView]); // , $getDirectedEdges]); + return forkJoin([ $getPatternLanguages, $getCurrentPatternView ]); // , $getDirectedEdges]); } private getLinks(): Observable { const $getUndirectedEdges = this.getUndirectedEdges(); const $getDirectedEdges = this.getDirectedEdges(); - return forkJoin([$getUndirectedEdges, $getDirectedEdges]).pipe(tap(() => { + return forkJoin([ $getUndirectedEdges, $getDirectedEdges ]).pipe(tap(() => { this.patternLinks = []; this.patternLinks.push(...this.directedPatternRelations); this.patternLinks.push(...this.undirectedPatternRelations); @@ -328,14 +526,15 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { dialogRef.afterClosed().pipe( tap(res => { nodesToAdd = res; - console.log(res); }), switchMap((res) => { - return forkJoin([this.patternViewService.addPatterns(this.patternViewResponse._links.patterns.href, this.mapDialogResultToPatterns(res)), - this.patternViewService.addLinks(this.patternViewResponse, res && Array.isArray(res) ? res.map(it => it.item) : [])]); + return forkJoin([ + this.patternViewService.addPatterns(this.patternViewResponse._links.patterns.href, this.mapDialogResultToPatterns(res)), + this.patternViewService.addLinks(this.patternViewResponse, res && Array.isArray(res) ? res.map(it => it.item) : []) ]); }), - switchMap(result => result ? this.getCurrentPatternViewAndPatterns() : EMPTY) - ).subscribe((res) => { + switchMap(result => result ? forkJoin([this.getCurrentPatternViewAndPatterns(), this.getLinks()]) + : EMPTY) + ).subscribe( res => { if (res) { this.toasterService.pop('success', 'Data added'); this.cdr.detectChanges(); @@ -346,14 +545,24 @@ export class PatternViewRendererComponent implements OnInit, AfterViewInit { private mapPatternLinksToTreeNode(pattern: Pattern): LinksToOtherPattern[] { const types: LinksToOtherPattern[] = []; const possibleEdgeTypes = [ - { link: pattern._links.ingoingDirectedEdgesFromPatternLanguage, type: 'directed', displayName: 'Ingoing directed edges' }, - { link: pattern._links.outgoingDirectedEdgesFromPatternLanguage, type: 'directed', displayName: 'Outgoing directed edges' }, + { + link: pattern._links.ingoingDirectedEdgesFromPatternLanguage, + type: 'directed', + displayName: 'Ingoing directed edges' + }, + { + link: pattern._links.outgoingDirectedEdgesFromPatternLanguage, + type: 'directed', + displayName: 'Outgoing directed edges' + }, { link: pattern._links.undirectedEdgesFromPatternLanguage, type: 'undirected', displayName: 'Undirected edges' } ]; possibleEdgeTypes.forEach((edgeType: { link: HalLink | HalLink[], displayName: string, type: string }, index) => { if (edgeType.link) { types.push({ - name: edgeType.displayName, links: Array.isArray(edgeType.link) ? edgeType.link : [edgeType.link], id: index.toString(), + name: edgeType.displayName, + links: Array.isArray(edgeType.link) ? edgeType.link : [ edgeType.link ], + id: index.toString(), type: edgeType.type }); } diff --git a/src/environments/environment.ts b/src/environments/environment.ts index 0f9a1e1c..7ea5390d 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -18,7 +18,7 @@ export const environment = { production: false, - repositoryUrl: 'http://localhost:8080', + repositoryUrl: 'http://localhost:8080/patternpedia', authorizeUrl: 'http://localhost:8081/oauth/authorize?', tokenUrl: 'http://localhost:8081/oauth/token', tokenRevokeUrl: 'http://localhost:8081/oauth/revoke_token',