mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-02-24 02:51:14 +00:00
Merge pull request #301 from GNS3/As-a-user-I-can-delete-project
As a user i can delete project & deletion support for drawings/nodes/links in context menu & offset support in scrolled area
This commit is contained in:
commit
c841a3d879
@ -164,6 +164,7 @@ import { SymbolsMenuComponent } from './components/preferences/common/symbols-me
|
|||||||
import { SearchFilter } from './filters/searchFilter.pipe';
|
import { SearchFilter } from './filters/searchFilter.pipe';
|
||||||
import { RecentlyOpenedProjectService } from './services/recentlyOpenedProject.service';
|
import { RecentlyOpenedProjectService } from './services/recentlyOpenedProject.service';
|
||||||
import { ServerManagementService } from './services/server-management.service';
|
import { ServerManagementService } from './services/server-management.service';
|
||||||
|
import { DeleteActionComponent } from './components/project-map/context-menu/actions/delete-action/delete-action.component';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -198,6 +199,7 @@ if (environment.production) {
|
|||||||
MoveLayerUpActionComponent,
|
MoveLayerUpActionComponent,
|
||||||
EditStyleActionComponent,
|
EditStyleActionComponent,
|
||||||
EditTextActionComponent,
|
EditTextActionComponent,
|
||||||
|
DeleteActionComponent,
|
||||||
ProjectMapShortcutsComponent,
|
ProjectMapShortcutsComponent,
|
||||||
SettingsComponent,
|
SettingsComponent,
|
||||||
PreferencesComponent,
|
PreferencesComponent,
|
||||||
|
@ -25,8 +25,8 @@ export class DrawingAddingComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
let listener = (event: MouseEvent) => {
|
let listener = (event: MouseEvent) => {
|
||||||
let x = event.clientX - this.context.getZeroZeroTransformationPoint().x;
|
let x = event.pageX - this.context.getZeroZeroTransformationPoint().x;
|
||||||
let y = event.clientY - this.context.getZeroZeroTransformationPoint().y;
|
let y = event.pageY - this.context.getZeroZeroTransformationPoint().y;
|
||||||
|
|
||||||
this.drawingsEventSource.pointToAddSelected.emit(new AddedDataEvent(x, y));
|
this.drawingsEventSource.pointToAddSelected.emit(new AddedDataEvent(x, y));
|
||||||
this.deactivate();
|
this.deactivate();
|
||||||
|
@ -45,8 +45,8 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
activateTextAdding() {
|
activateTextAdding() {
|
||||||
let addTextListener = (event: MouseEvent) => {
|
let addTextListener = (event: MouseEvent) => {
|
||||||
this.leftPosition = event.clientX.toString() + 'px';
|
this.leftPosition = event.pageX.toString() + 'px';
|
||||||
this.topPosition = (event.clientY + window.pageYOffset).toString() + 'px';
|
this.topPosition = event.pageY.toString() + 'px';
|
||||||
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
this.renderer.setStyle(this.temporaryTextElement.nativeElement, 'display', 'initial');
|
||||||
this.temporaryTextElement.nativeElement.focus();
|
this.temporaryTextElement.nativeElement.focus();
|
||||||
|
|
||||||
@ -54,8 +54,8 @@ export class TextEditorComponent implements OnInit, OnDestroy {
|
|||||||
this.drawingsEventSource.textAdded.emit(
|
this.drawingsEventSource.textAdded.emit(
|
||||||
new TextAddedDataEvent(
|
new TextAddedDataEvent(
|
||||||
this.temporaryTextElement.nativeElement.innerText.replace(/\n$/, ''),
|
this.temporaryTextElement.nativeElement.innerText.replace(/\n$/, ''),
|
||||||
event.clientX,
|
event.pageX,
|
||||||
event.clientY
|
event.pageY
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
this.deactivateTextAdding();
|
this.deactivateTextAdding();
|
||||||
|
@ -111,15 +111,15 @@ export class DrawingsWidget implements Widget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
|
dy = y - (evt.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y);
|
||||||
y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
y = evt.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y;
|
||||||
|
|
||||||
if (datum.element.height + dy < 0) {
|
if (datum.element.height + dy < 0) {
|
||||||
isReflectedVertical = false;
|
isReflectedVertical = false;
|
||||||
y = topEdge;
|
y = topEdge;
|
||||||
datum.element.height = Math.abs(datum.element.height + evt.dy);
|
datum.element.height = Math.abs(datum.element.height + evt.dy);
|
||||||
} else {
|
} else {
|
||||||
datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
datum.y = evt.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y;
|
||||||
datum.element.height += dy;
|
datum.element.height += dy;
|
||||||
if (datum.element instanceof EllipseElement) {
|
if (datum.element instanceof EllipseElement) {
|
||||||
(datum.element as EllipseElement).cy =
|
(datum.element as EllipseElement).cy =
|
||||||
@ -143,7 +143,7 @@ export class DrawingsWidget implements Widget {
|
|||||||
|
|
||||||
let top = drag()
|
let top = drag()
|
||||||
.on('start', (datum: MapDrawing) => {
|
.on('start', (datum: MapDrawing) => {
|
||||||
y = event.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
y = event.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y;
|
||||||
bottomEdge = y + datum.element.height;
|
bottomEdge = y + datum.element.height;
|
||||||
document.body.style.cursor = 'ns-resize';
|
document.body.style.cursor = 'ns-resize';
|
||||||
})
|
})
|
||||||
@ -151,15 +151,15 @@ export class DrawingsWidget implements Widget {
|
|||||||
const evt = event;
|
const evt = event;
|
||||||
|
|
||||||
if (!isReflectedVertical) {
|
if (!isReflectedVertical) {
|
||||||
dy = y - (evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y);
|
dy = y - (evt.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y);
|
||||||
y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
y = evt.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y;
|
||||||
|
|
||||||
if (datum.element.height + dy < 0) {
|
if (datum.element.height + dy < 0) {
|
||||||
y = bottomEdge;
|
y = bottomEdge;
|
||||||
isReflectedVertical = true;
|
isReflectedVertical = true;
|
||||||
datum.element.height = Math.abs(datum.element.height + evt.dy);
|
datum.element.height = Math.abs(datum.element.height + evt.dy);
|
||||||
} else {
|
} else {
|
||||||
datum.y = evt.sourceEvent.clientY - this.context.getZeroZeroTransformationPoint().y;
|
datum.y = evt.sourceEvent.pageY - this.context.getZeroZeroTransformationPoint().y;
|
||||||
datum.element.height += dy;
|
datum.element.height += dy;
|
||||||
if (datum.element instanceof EllipseElement) {
|
if (datum.element instanceof EllipseElement) {
|
||||||
(datum.element as EllipseElement).cy =
|
(datum.element as EllipseElement).cy =
|
||||||
@ -207,7 +207,7 @@ export class DrawingsWidget implements Widget {
|
|||||||
let isReflectedHorizontal: boolean = false;
|
let isReflectedHorizontal: boolean = false;
|
||||||
let right = drag()
|
let right = drag()
|
||||||
.on('start', (datum: MapDrawing) => {
|
.on('start', (datum: MapDrawing) => {
|
||||||
x = event.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
|
x = event.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x;
|
||||||
leftEdge = x + datum.element.width;
|
leftEdge = x + datum.element.width;
|
||||||
document.body.style.cursor = 'ew-resize';
|
document.body.style.cursor = 'ew-resize';
|
||||||
})
|
})
|
||||||
@ -215,15 +215,15 @@ export class DrawingsWidget implements Widget {
|
|||||||
const evt = event;
|
const evt = event;
|
||||||
|
|
||||||
if (!isReflectedHorizontal) {
|
if (!isReflectedHorizontal) {
|
||||||
dx = x - (evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x);
|
dx = x - (evt.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x);
|
||||||
x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
|
x = evt.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x;
|
||||||
|
|
||||||
if (datum.element.width + dx < 0) {
|
if (datum.element.width + dx < 0) {
|
||||||
x = leftEdge;
|
x = leftEdge;
|
||||||
isReflectedHorizontal = true;
|
isReflectedHorizontal = true;
|
||||||
datum.element.width = Math.abs(datum.element.width + evt.dx);
|
datum.element.width = Math.abs(datum.element.width + evt.dx);
|
||||||
} else {
|
} else {
|
||||||
datum.x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
|
datum.x = evt.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x;
|
||||||
datum.element.width += dx;
|
datum.element.width += dx;
|
||||||
if (datum.element instanceof EllipseElement) {
|
if (datum.element instanceof EllipseElement) {
|
||||||
(datum.element as EllipseElement).cx =
|
(datum.element as EllipseElement).cx =
|
||||||
@ -290,15 +290,15 @@ export class DrawingsWidget implements Widget {
|
|||||||
datum.element.width = datum.element.width + evt.dx < 0 ? 1 : (datum.element.width += evt.dx);
|
datum.element.width = datum.element.width + evt.dx < 0 ? 1 : (datum.element.width += evt.dx);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dx = x - (evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x);
|
dx = x - (evt.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x);
|
||||||
x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
|
x = evt.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x;
|
||||||
|
|
||||||
if (datum.element.width + dx < 0) {
|
if (datum.element.width + dx < 0) {
|
||||||
x = rightEdge;
|
x = rightEdge;
|
||||||
isReflectedHorizontal = false;
|
isReflectedHorizontal = false;
|
||||||
datum.element.width = Math.abs(datum.element.width + evt.dx);
|
datum.element.width = Math.abs(datum.element.width + evt.dx);
|
||||||
} else {
|
} else {
|
||||||
datum.x = evt.sourceEvent.clientX - this.context.getZeroZeroTransformationPoint().x;
|
datum.x = evt.sourceEvent.pageX - this.context.getZeroZeroTransformationPoint().x;
|
||||||
datum.element.width += dx;
|
datum.element.width += dx;
|
||||||
if (datum.element instanceof EllipseElement) {
|
if (datum.element instanceof EllipseElement) {
|
||||||
(datum.element as EllipseElement).cx =
|
(datum.element as EllipseElement).cx =
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="delete()">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
<span>Delete</span>
|
||||||
|
</button>
|
@ -0,0 +1,86 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
import { DeleteActionComponent } from './delete-action.component';
|
||||||
|
import { MatIconModule, MatMenuModule } from '@angular/material';
|
||||||
|
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
|
||||||
|
import { NodeService } from '../../../../../services/node.service';
|
||||||
|
import { DrawingService } from '../../../../../services/drawing.service';
|
||||||
|
import { MockedDrawingService, MockedNodeService, MockedLinkService } from '../../../project-map.component.spec';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { Drawing } from '../../../../../cartography/models/drawing';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { LinksDataSource } from '../../../../../cartography/datasources/links-datasource';
|
||||||
|
import { LinkService } from '../../../../../services/link.service';
|
||||||
|
import { Link } from '../../../../../models/link';
|
||||||
|
|
||||||
|
describe('DeleteActionComponent', () => {
|
||||||
|
let component: DeleteActionComponent;
|
||||||
|
let fixture: ComponentFixture<DeleteActionComponent>;
|
||||||
|
let mockedNodeService: MockedNodeService = new MockedNodeService();
|
||||||
|
let mockedDrawingService: MockedDrawingService = new MockedDrawingService();
|
||||||
|
let mockedLinkService: MockedLinkService = new MockedLinkService();
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [MatIconModule, MatMenuModule, NoopAnimationsModule],
|
||||||
|
providers: [
|
||||||
|
{ provide: NodesDataSource, useClass: NodesDataSource },
|
||||||
|
{ provide: DrawingsDataSource, useClass: DrawingsDataSource },
|
||||||
|
{ provide: LinksDataSource, useClass: LinksDataSource },
|
||||||
|
{ provide: NodeService, useValue: mockedNodeService },
|
||||||
|
{ provide: DrawingService, useValue: mockedDrawingService },
|
||||||
|
{ provide: LinkService, useValue: mockedLinkService }
|
||||||
|
],
|
||||||
|
declarations: [DeleteActionComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(DeleteActionComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call delete action in drawing service', () => {
|
||||||
|
let node = { node_id: '1' } as Node;
|
||||||
|
component.nodes = [node];
|
||||||
|
let drawing = { drawing_id: '1' } as Drawing;
|
||||||
|
component.drawings = [drawing];
|
||||||
|
component.links = [];
|
||||||
|
spyOn(mockedDrawingService, 'delete').and.returnValue(of());
|
||||||
|
|
||||||
|
component.delete();
|
||||||
|
|
||||||
|
expect(mockedDrawingService.delete).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call delete action in node service', () => {
|
||||||
|
let node = { node_id: '1' } as Node;
|
||||||
|
component.nodes = [node];
|
||||||
|
let drawing = { drawing_id: '1' } as Drawing;
|
||||||
|
component.drawings = [drawing];
|
||||||
|
component.links = [];
|
||||||
|
spyOn(mockedNodeService, 'delete').and.returnValue(of());
|
||||||
|
|
||||||
|
component.delete();
|
||||||
|
|
||||||
|
expect(mockedNodeService.delete).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should call delete action in link service', () => {
|
||||||
|
component.nodes = [];
|
||||||
|
component.drawings = [];
|
||||||
|
let link = { link_id: '1', project_id: '1' } as Link;
|
||||||
|
component.links = [link];
|
||||||
|
spyOn(mockedLinkService, 'deleteLink').and.returnValue(of());
|
||||||
|
|
||||||
|
component.delete();
|
||||||
|
|
||||||
|
expect(mockedLinkService.deleteLink).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,53 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Server } from '../../../../../models/server';
|
||||||
|
import { Node } from '../../../../../cartography/models/node';
|
||||||
|
import { NodesDataSource } from '../../../../../cartography/datasources/nodes-datasource';
|
||||||
|
import { NodeService } from '../../../../../services/node.service';
|
||||||
|
import { Drawing } from '../../../../../cartography/models/drawing';
|
||||||
|
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
|
||||||
|
import { DrawingService } from '../../../../../services/drawing.service';
|
||||||
|
import { Link } from '../../../../../models/link';
|
||||||
|
import { LinkService } from '../../../../../services/link.service';
|
||||||
|
import { LinksDataSource } from '../../../../../cartography/datasources/links-datasource';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-delete-action',
|
||||||
|
templateUrl: './delete-action.component.html'
|
||||||
|
})
|
||||||
|
export class DeleteActionComponent implements OnInit {
|
||||||
|
@Input() server: Server;
|
||||||
|
@Input() nodes: Node[];
|
||||||
|
@Input() drawings: Drawing[];
|
||||||
|
@Input() links: Link[];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
|
private linksDataSource: LinksDataSource,
|
||||||
|
private nodeService: NodeService,
|
||||||
|
private drawingService: DrawingService,
|
||||||
|
private linkService: LinkService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
delete() {
|
||||||
|
this.nodes.forEach((node) => {
|
||||||
|
this.nodesDataSource.remove(node);
|
||||||
|
|
||||||
|
this.nodeService.delete(this.server, node).subscribe((node: Node) => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.drawings.forEach((drawing) => {
|
||||||
|
this.drawingsDataSource.remove(drawing);
|
||||||
|
|
||||||
|
this.drawingService.delete(this.server, drawing).subscribe((drawing: Drawing) => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.links.forEach((link) => {
|
||||||
|
this.linksDataSource.remove(link);
|
||||||
|
|
||||||
|
this.linkService.deleteLink(this.server, link).subscribe(() => {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -15,16 +15,23 @@
|
|||||||
[drawing]="drawings[0]"
|
[drawing]="drawings[0]"
|
||||||
></app-edit-text-action>
|
></app-edit-text-action>
|
||||||
<app-move-layer-up-action
|
<app-move-layer-up-action
|
||||||
*ngIf="!projectService.isReadOnly(project)"
|
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length)"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
[drawings]="drawings"
|
[drawings]="drawings"
|
||||||
></app-move-layer-up-action>
|
></app-move-layer-up-action>
|
||||||
<app-move-layer-down-action
|
<app-move-layer-down-action
|
||||||
*ngIf="!projectService.isReadOnly(project)"
|
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length)"
|
||||||
[server]="server"
|
[server]="server"
|
||||||
[nodes]="nodes"
|
[nodes]="nodes"
|
||||||
[drawings]="drawings"
|
[drawings]="drawings"
|
||||||
></app-move-layer-down-action>
|
></app-move-layer-down-action>
|
||||||
|
<app-delete-action
|
||||||
|
*ngIf="!projectService.isReadOnly(project)"
|
||||||
|
[server]="server"
|
||||||
|
[nodes]="nodes"
|
||||||
|
[drawings]="drawings"
|
||||||
|
[links]="links"
|
||||||
|
></app-delete-action>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,7 +70,7 @@ describe('ContextMenuComponent', () => {
|
|||||||
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
component.contextMenu = { openMenu() {} } as MatMenuTrigger;
|
||||||
var spy = spyOn<any>(component, 'resetCapabilities');
|
var spy = spyOn<any>(component, 'resetCapabilities');
|
||||||
spyOn(component, 'setPosition').and.callFake(() => {});
|
spyOn(component, 'setPosition').and.callFake(() => {});
|
||||||
component.openMenuForListOfElements([], [], [], 0, 0);
|
component.openMenuForListOfElements([], [], [], [], 0, 0);
|
||||||
|
|
||||||
expect(spy.calls.any()).toBeTruthy();
|
expect(spy.calls.any()).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
@ -8,6 +8,7 @@ import { ProjectService } from '../../../services/project.service';
|
|||||||
import { Drawing } from '../../../cartography/models/drawing';
|
import { Drawing } from '../../../cartography/models/drawing';
|
||||||
import { TextElement } from '../../../cartography/models/drawings/text-element';
|
import { TextElement } from '../../../cartography/models/drawings/text-element';
|
||||||
import { Label } from '../../../cartography/models/label';
|
import { Label } from '../../../cartography/models/label';
|
||||||
|
import { Link } from '../../../models/link';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -27,6 +28,7 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
drawings: Drawing[] = [];
|
drawings: Drawing[] = [];
|
||||||
nodes: Node[] = [];
|
nodes: Node[] = [];
|
||||||
labels: Label[] = [];
|
labels: Label[] = [];
|
||||||
|
links: Link[] = [];
|
||||||
|
|
||||||
hasTextCapabilities: boolean = false;
|
hasTextCapabilities: boolean = false;
|
||||||
|
|
||||||
@ -74,12 +76,13 @@ export class ContextMenuComponent implements OnInit {
|
|||||||
this.contextMenu.openMenu();
|
this.contextMenu.openMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
public openMenuForListOfElements(drawings: Drawing[], nodes: Node[], labels: Label[], top: number, left: number) {
|
public openMenuForListOfElements(drawings: Drawing[], nodes: Node[], labels: Label[], links: Link[], top: number, left: number) {
|
||||||
this.resetCapabilities();
|
this.resetCapabilities();
|
||||||
|
|
||||||
this.drawings = drawings;
|
this.drawings = drawings;
|
||||||
this.nodes = nodes;
|
this.nodes = nodes;
|
||||||
this.labels = labels;
|
this.labels = labels;
|
||||||
|
this.links = links;
|
||||||
this.setPosition(top, left);
|
this.setPosition(top, left);
|
||||||
|
|
||||||
this.contextMenu.openMenu();
|
this.contextMenu.openMenu();
|
||||||
|
@ -1 +1 @@
|
|||||||
<app-node-select-interface (onChooseInterface)="onChooseInterface($event)"></app-node-select-interface>
|
<app-node-select-interface [links]="links" (onChooseInterface)="onChooseInterface($event)"></app-node-select-interface>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, OnInit, OnDestroy, ViewChild, Input } from '@angular/core';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { NodeSelectInterfaceComponent } from '../../../components/project-map/node-select-interface/node-select-interface.component';
|
import { NodeSelectInterfaceComponent } from '../../../components/project-map/node-select-interface/node-select-interface.component';
|
||||||
import { DrawingLineWidget } from '../../../cartography/widgets/drawing-line';
|
import { DrawingLineWidget } from '../../../cartography/widgets/drawing-line';
|
||||||
@ -7,6 +7,8 @@ import { LinksEventSource } from '../../../cartography/events/links-event-source
|
|||||||
import { MapNode } from '../../../cartography/models/map/map-node';
|
import { MapNode } from '../../../cartography/models/map/map-node';
|
||||||
import { MapPort } from '../../../cartography/models/map/map-port';
|
import { MapPort } from '../../../cartography/models/map/map-port';
|
||||||
import { MapLinkCreated } from '../../../cartography/events/links';
|
import { MapLinkCreated } from '../../../cartography/events/links';
|
||||||
|
import { Link } from '../../../models/link';
|
||||||
|
import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-draw-link-tool',
|
selector: 'app-draw-link-tool',
|
||||||
@ -14,6 +16,7 @@ import { MapLinkCreated } from '../../../cartography/events/links';
|
|||||||
styleUrls: ['./draw-link-tool.component.scss']
|
styleUrls: ['./draw-link-tool.component.scss']
|
||||||
})
|
})
|
||||||
export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
||||||
|
@Input() links: Link[];
|
||||||
@ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
|
@ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
|
||||||
|
|
||||||
private nodeClicked$: Subscription;
|
private nodeClicked$: Subscription;
|
||||||
@ -21,12 +24,14 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
|||||||
constructor(
|
constructor(
|
||||||
private drawingLineTool: DrawingLineWidget,
|
private drawingLineTool: DrawingLineWidget,
|
||||||
private nodesEventSource: NodesEventSource,
|
private nodesEventSource: NodesEventSource,
|
||||||
private linksEventSource: LinksEventSource
|
private linksEventSource: LinksEventSource,
|
||||||
|
private mapNodeToNode: MapNodeToNodeConverter
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.nodeClicked$ = this.nodesEventSource.clicked.subscribe(clickedEvent => {
|
this.nodeClicked$ = this.nodesEventSource.clicked.subscribe(clickedEvent => {
|
||||||
this.nodeSelectInterfaceMenu.open(clickedEvent.datum, clickedEvent.y, clickedEvent.x);
|
let node = this.mapNodeToNode.convert(clickedEvent.datum);
|
||||||
|
this.nodeSelectInterfaceMenu.open(node, clickedEvent.y, clickedEvent.x);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition" *ngIf="node">
|
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition" *ngIf="node">
|
||||||
<span [matMenuTriggerFor]="selectInterfaceMenu"></span>
|
<span [matMenuTriggerFor]="selectInterfaceMenu"></span>
|
||||||
<mat-menu #selectInterfaceMenu="matMenu" class="context-menu-items">
|
<mat-menu #selectInterfaceMenu="matMenu" class="context-menu-items">
|
||||||
<button mat-menu-item *ngFor="let port of node.ports" (click)="chooseInterface(port)">
|
<button mat-menu-item *ngFor="let port of availablePorts" (click)="chooseInterface(port)">
|
||||||
<mat-icon>add_circle_outline</mat-icon>
|
<mat-icon>add_circle_outline</mat-icon>
|
||||||
<span>{{ port.name }}</span>
|
<span>{{ port.name }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -3,6 +3,8 @@ import { MatMenuTrigger } from '@angular/material';
|
|||||||
import { DomSanitizer } from '@angular/platform-browser';
|
import { DomSanitizer } from '@angular/platform-browser';
|
||||||
import { Node } from '../../../cartography/models/node';
|
import { Node } from '../../../cartography/models/node';
|
||||||
import { Port } from '../../../models/port';
|
import { Port } from '../../../models/port';
|
||||||
|
import { Link } from '../../../models/link';
|
||||||
|
import { LinkNode } from '../../../models/link-node';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-node-select-interface',
|
selector: 'app-node-select-interface',
|
||||||
@ -10,6 +12,7 @@ import { Port } from '../../../models/port';
|
|||||||
styleUrls: ['./node-select-interface.component.scss']
|
styleUrls: ['./node-select-interface.component.scss']
|
||||||
})
|
})
|
||||||
export class NodeSelectInterfaceComponent implements OnInit {
|
export class NodeSelectInterfaceComponent implements OnInit {
|
||||||
|
@Input() links: Link[];
|
||||||
@Output() onChooseInterface = new EventEmitter<any>();
|
@Output() onChooseInterface = new EventEmitter<any>();
|
||||||
|
|
||||||
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
|
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
|
||||||
@ -17,8 +20,12 @@ export class NodeSelectInterfaceComponent implements OnInit {
|
|||||||
protected topPosition;
|
protected topPosition;
|
||||||
protected leftPosition;
|
protected leftPosition;
|
||||||
public node: Node;
|
public node: Node;
|
||||||
|
public availablePorts: Port[];
|
||||||
|
|
||||||
constructor(private sanitizer: DomSanitizer, private changeDetector: ChangeDetectorRef) {}
|
constructor(
|
||||||
|
private sanitizer: DomSanitizer,
|
||||||
|
private changeDetector: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.setPosition(0, 0);
|
this.setPosition(0, 0);
|
||||||
@ -32,10 +39,29 @@ export class NodeSelectInterfaceComponent implements OnInit {
|
|||||||
|
|
||||||
public open(node: Node, top: number, left: number) {
|
public open(node: Node, top: number, left: number) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
|
this.filterNodePorts();
|
||||||
this.setPosition(top, left);
|
this.setPosition(top, left);
|
||||||
this.contextMenu.openMenu();
|
this.contextMenu.openMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public filterNodePorts() {
|
||||||
|
let linkNodes: LinkNode[] = [];
|
||||||
|
this.links.forEach((link: Link) => {
|
||||||
|
link.nodes.forEach((linkNode: LinkNode) => {
|
||||||
|
if(linkNode.node_id === this.node.node_id) {
|
||||||
|
linkNodes.push(linkNode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.availablePorts = [];
|
||||||
|
this.node.ports.forEach((port: Port) => {
|
||||||
|
if(linkNodes.filter((linkNode: LinkNode) => linkNode.port_number === port.port_number).length === 0){
|
||||||
|
this.availablePorts.push(port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public chooseInterface(port: Port) {
|
public chooseInterface(port: Port) {
|
||||||
this.onChooseInterface.emit({
|
this.onChooseInterface.emit({
|
||||||
node: this.node,
|
node: this.node,
|
||||||
|
@ -141,7 +141,7 @@
|
|||||||
|
|
||||||
<app-project-map-shortcuts *ngIf="project" [project]="project" [server]="server"> </app-project-map-shortcuts>
|
<app-project-map-shortcuts *ngIf="project" [project]="project" [server]="server"> </app-project-map-shortcuts>
|
||||||
|
|
||||||
<app-draw-link-tool *ngIf="tools.draw_link"></app-draw-link-tool>
|
<app-draw-link-tool [links]="links" *ngIf="tools.draw_link"></app-draw-link-tool>
|
||||||
|
|
||||||
<app-drawing-added
|
<app-drawing-added
|
||||||
[server]="server"
|
[server]="server"
|
||||||
|
@ -37,6 +37,8 @@ import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-l
|
|||||||
import { SelectionManager } from '../../cartography/managers/selection-manager';
|
import { SelectionManager } from '../../cartography/managers/selection-manager';
|
||||||
import { SelectionTool } from '../../cartography/tools/selection-tool';
|
import { SelectionTool } from '../../cartography/tools/selection-tool';
|
||||||
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
|
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
|
||||||
|
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
||||||
|
import { Link } from '../../models/link';
|
||||||
|
|
||||||
export class MockedProgressService {
|
export class MockedProgressService {
|
||||||
public activate() {}
|
public activate() {}
|
||||||
@ -53,6 +55,10 @@ export class MockedNodeService {
|
|||||||
updatePosition(): Observable<Node> {
|
updatePosition(): Observable<Node> {
|
||||||
return of(this.node);
|
return of(this.node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(server: Server, node: Node) {
|
||||||
|
return of();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MockedDrawingService {
|
export class MockedDrawingService {
|
||||||
@ -87,6 +93,10 @@ export class MockedDrawingService {
|
|||||||
export class MockedLinkService {
|
export class MockedLinkService {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
|
deleteLink(_server: Server, link: Link){
|
||||||
|
return of({})
|
||||||
|
}
|
||||||
|
|
||||||
createLink() {
|
createLink() {
|
||||||
return of({});
|
return of({});
|
||||||
}
|
}
|
||||||
@ -144,6 +154,7 @@ describe('ProjectMapComponent', () => {
|
|||||||
{ provide: MapNodeToNodeConverter },
|
{ provide: MapNodeToNodeConverter },
|
||||||
{ provide: MapDrawingToDrawingConverter },
|
{ provide: MapDrawingToDrawingConverter },
|
||||||
{ provide: MapLabelToLabelConverter },
|
{ provide: MapLabelToLabelConverter },
|
||||||
|
{ provide: MapLinkToLinkConverter },
|
||||||
{ provide: NodesDataSource },
|
{ provide: NodesDataSource },
|
||||||
{ provide: LinksDataSource },
|
{ provide: LinksDataSource },
|
||||||
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
|
{ provide: DrawingsDataSource, useValue: drawingsDataSource },
|
||||||
|
@ -40,6 +40,8 @@ import { Label } from '../../cartography/models/label';
|
|||||||
import { MapNode } from '../../cartography/models/map/map-node';
|
import { MapNode } from '../../cartography/models/map/map-node';
|
||||||
import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
|
import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter';
|
||||||
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
|
import { RecentlyOpenedProjectService } from '../../services/recentlyOpenedProject.service';
|
||||||
|
import { MapLink } from '../../cartography/models/map/map-link';
|
||||||
|
import { MapLinkToLinkConverter } from '../../cartography/converters/map/map-link-to-link-converter';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -96,6 +98,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
private mapNodeToNode: MapNodeToNodeConverter,
|
private mapNodeToNode: MapNodeToNodeConverter,
|
||||||
private mapDrawingToDrawing: MapDrawingToDrawingConverter,
|
private mapDrawingToDrawing: MapDrawingToDrawingConverter,
|
||||||
private mapLabelToLabel: MapLabelToLabelConverter,
|
private mapLabelToLabel: MapLabelToLabelConverter,
|
||||||
|
private mapLinkToLink: MapLinkToLinkConverter,
|
||||||
private nodesDataSource: NodesDataSource,
|
private nodesDataSource: NodesDataSource,
|
||||||
private linksDataSource: LinksDataSource,
|
private linksDataSource: LinksDataSource,
|
||||||
private drawingsDataSource: DrawingsDataSource,
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
@ -218,12 +221,12 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
|
const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
|
||||||
const node = this.mapNodeToNode.convert(eventNode.node);
|
const node = this.mapNodeToNode.convert(eventNode.node);
|
||||||
this.contextMenu.openMenuForNode(node, eventNode.event.clientY, eventNode.event.clientX);
|
this.contextMenu.openMenuForNode(node, eventNode.event.pageY, eventNode.event.pageX);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onDrawingContextMenu = this.drawingsWidget.onContextMenu.subscribe((eventDrawing: DrawingContextMenu) => {
|
const onDrawingContextMenu = this.drawingsWidget.onContextMenu.subscribe((eventDrawing: DrawingContextMenu) => {
|
||||||
const drawing = this.mapDrawingToDrawing.convert(eventDrawing.drawing);
|
const drawing = this.mapDrawingToDrawing.convert(eventDrawing.drawing);
|
||||||
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.clientY, eventDrawing.event.clientX);
|
this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.pageY, eventDrawing.event.pageX);
|
||||||
});
|
});
|
||||||
|
|
||||||
const onContextMenu = this.selectionTool.contextMenuOpened.subscribe((event) => {
|
const onContextMenu = this.selectionTool.contextMenuOpened.subscribe((event) => {
|
||||||
@ -233,6 +236,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
let drawings: Drawing[] = [];
|
let drawings: Drawing[] = [];
|
||||||
let nodes: Node[] = [];
|
let nodes: Node[] = [];
|
||||||
let labels: Label[] = [];
|
let labels: Label[] = [];
|
||||||
|
let links: Link[] = [];
|
||||||
|
|
||||||
selectedItems.forEach((elem) => {
|
selectedItems.forEach((elem) => {
|
||||||
if (elem instanceof MapDrawing) {
|
if (elem instanceof MapDrawing) {
|
||||||
@ -241,10 +245,12 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
nodes.push(this.mapNodeToNode.convert(elem));
|
nodes.push(this.mapNodeToNode.convert(elem));
|
||||||
} else if (elem instanceof MapLabel) {
|
} else if (elem instanceof MapLabel) {
|
||||||
labels.push(this.mapLabelToLabel.convert(elem));
|
labels.push(this.mapLabelToLabel.convert(elem));
|
||||||
|
} else if (elem instanceof MapLink) {
|
||||||
|
links.push(this.mapLinkToLink.convert(elem))
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, event.clientY, event.clientX);
|
this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, links, event.pageY, event.pageX);
|
||||||
});
|
});
|
||||||
|
|
||||||
this.subscriptions.push(onNodeContextMenu);
|
this.subscriptions.push(onNodeContextMenu);
|
||||||
|
@ -43,9 +43,9 @@
|
|||||||
<button mat-icon-button matTooltip="Close project" (click)="close(row)" *ngIf="row.status == 'opened'">
|
<button mat-icon-button matTooltip="Close project" (click)="close(row)" *ngIf="row.status == 'opened'">
|
||||||
<mat-icon aria-label="Close project">pause</mat-icon>
|
<mat-icon aria-label="Close project">pause</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
<!--<button mat-icon-button matTooltip="Delete project" (click)="delete(row)" *ngIf="settings.experimental_features">-->
|
<button mat-icon-button matTooltip="Delete project" (click)="delete(row)" *ngIf="row.status == 'closed'">
|
||||||
<!--<mat-icon aria-label="Delete project">delete</mat-icon>-->
|
<mat-icon aria-label="Open project">delete</mat-icon>
|
||||||
<!--</button>-->
|
</button>
|
||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ describe('ProjectsComponent', () => {
|
|||||||
let serverService: ServerService;
|
let serverService: ServerService;
|
||||||
let server: Server;
|
let server: Server;
|
||||||
let progressService: ProgressService;
|
let progressService: ProgressService;
|
||||||
|
let mockedProjectService: MockedProjectService = new MockedProjectService();
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
@ -39,7 +40,7 @@ describe('ProjectsComponent', () => {
|
|||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
{ provide: ServerService, useClass: MockedServerService },
|
{ provide: ServerService, useClass: MockedServerService },
|
||||||
{ provide: ProjectService, useClass: MockedProjectService },
|
{ provide: ProjectService, useValue: mockedProjectService },
|
||||||
{ provide: SettingsService, useClass: MockedSettingsService },
|
{ provide: SettingsService, useClass: MockedSettingsService },
|
||||||
ProgressService
|
ProgressService
|
||||||
],
|
],
|
||||||
@ -74,6 +75,16 @@ describe('ProjectsComponent', () => {
|
|||||||
expect(component).toBeTruthy();
|
expect(component).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should remove item after delete action', () => {
|
||||||
|
spyOn(mockedProjectService, 'delete').and.returnValue(of());
|
||||||
|
let project = new Project();
|
||||||
|
project.project_id = '1';
|
||||||
|
|
||||||
|
component.delete(project);
|
||||||
|
|
||||||
|
expect(mockedProjectService.delete).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
describe('ProjectComponent open', () => {
|
describe('ProjectComponent open', () => {
|
||||||
let project: Project;
|
let project: Project;
|
||||||
|
|
||||||
|
@ -12,6 +12,11 @@ import { LinkNode } from '../models/link-node';
|
|||||||
export class LinkService {
|
export class LinkService {
|
||||||
constructor(private httpServer: HttpServer) {}
|
constructor(private httpServer: HttpServer) {}
|
||||||
|
|
||||||
|
deleteLink(server: Server, link: Link) {
|
||||||
|
//return this.httpServer.delete(server, `/compute/projects/${link.project_id}/vpcs/nodes/${link.nodes[0].node_id}/adapters/0/ports/0/nio`)
|
||||||
|
return this.httpServer.delete(server, `/projects/${link.project_id}/links/${link.link_id}`)
|
||||||
|
}
|
||||||
|
|
||||||
createLink(server: Server, source_node: Node, source_port: Port, target_node: Node, target_port: Port) {
|
createLink(server: Server, source_node: Node, source_port: Port, target_node: Node, target_port: Port) {
|
||||||
return this.httpServer.post(server, `/projects/${source_node.project_id}/links`, {
|
return this.httpServer.post(server, `/projects/${source_node.project_id}/links`, {
|
||||||
nodes: [
|
nodes: [
|
||||||
|
@ -8,9 +8,9 @@ import { getTestServer } from './testing';
|
|||||||
import { ProjectService } from './project.service';
|
import { ProjectService } from './project.service';
|
||||||
import { SettingsService } from './settings.service';
|
import { SettingsService } from './settings.service';
|
||||||
import { MockedSettingsService } from './settings.service.spec';
|
import { MockedSettingsService } from './settings.service.spec';
|
||||||
import { Observable, of } from 'rxjs';
|
|
||||||
import { Project } from '../models/project';
|
import { Project } from '../models/project';
|
||||||
import { AppTestingModule } from '../testing/app-testing/app-testing.module';
|
import { AppTestingModule } from '../testing/app-testing/app-testing.module';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mocks ProjectsService so it's not based on settings
|
* Mocks ProjectsService so it's not based on settings
|
||||||
@ -37,6 +37,10 @@ export class MockedProjectService {
|
|||||||
links(server: Server, project_id: string) {
|
links(server: Server, project_id: string) {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
delete(server: Server, project_id: string) {
|
||||||
|
return of(project_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('ProjectService', () => {
|
describe('ProjectService', () => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user