Merge pull request #261 from GNS3/As-user-I-can-use-context-menu-of-drawings

Basic implementation of context menu for drawings
This commit is contained in:
ziajka 2019-01-14 14:19:29 +01:00 committed by GitHub
commit 36a23fac12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 214 additions and 95 deletions

View File

@ -36,9 +36,9 @@ import { AppComponent } from './app.component';
import { ProjectMapComponent } from './components/project-map/project-map.component';
import { ServersComponent, AddServerDialogComponent } from './components/servers/servers.component';
import { NodeContextMenuComponent } from './components/project-map/node-context-menu/node-context-menu.component';
import { StartNodeActionComponent } from './components/project-map/node-context-menu/actions/start-node-action/start-node-action.component';
import { StopNodeActionComponent } from './components/project-map/node-context-menu/actions/stop-node-action/stop-node-action.component';
import { ContextMenuComponent } from './components/project-map/context-menu/context-menu.component';
import { StartNodeActionComponent } from './components/project-map/context-menu/actions/start-node-action/start-node-action.component';
import { StopNodeActionComponent } from './components/project-map/context-menu/actions/stop-node-action/stop-node-action.component';
import { TemplateComponent } from './components/template/template.component';
import { TemplateListDialogComponent } from './components/template/template-list-dialog/template-list-dialog.component';
import { CartographyModule } from './cartography/cartography.module';
@ -50,8 +50,9 @@ import { SymbolsDataSource } from "./cartography/datasources/symbols-datasource"
import { SelectionManager } from "./cartography/managers/selection-manager";
import { InRectangleHelper } from "./cartography/helpers/in-rectangle-helper";
import { DrawingsDataSource } from "./cartography/datasources/drawings-datasource";
import { MoveLayerDownActionComponent } from './components/project-map/node-context-menu/actions/move-layer-down-action/move-layer-down-action.component';
import { MoveLayerUpActionComponent } from './components/project-map/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component';
import { EditStyleActionComponent } from './components/project-map/context-menu/actions/edit-style-action/edit-style-action.component';
import { MoveLayerDownActionComponent } from './components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component';
import { MoveLayerUpActionComponent } from './components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component';
import { ProjectMapShortcutsComponent } from './components/project-map/project-map-shortcuts/project-map-shortcuts.component';
import { SettingsComponent } from './components/settings/settings.component';
import { SettingsService } from "./services/settings.service";
@ -113,13 +114,14 @@ if (environment.production) {
ConfirmationDialogComponent,
DefaultLayoutComponent,
ProgressDialogComponent,
NodeContextMenuComponent,
ContextMenuComponent,
StartNodeActionComponent,
StopNodeActionComponent,
TemplateComponent,
TemplateListDialogComponent,
MoveLayerDownActionComponent,
MoveLayerUpActionComponent,
EditStyleActionComponent,
ProjectMapShortcutsComponent,
SettingsComponent,
LocalServerComponent,

View File

@ -1,6 +1,5 @@
<div #temporaryTextElement id="temporaryElement" class="temporaryElement" contenteditable="true"
[style.top]=topPosition
[style.left]=leftPosition
[style.display]=display>
[style.left]=leftPosition>
{{innerText}}
</div>
</div>

View File

@ -17,9 +17,9 @@ export class TextEditorComponent implements OnInit, OnDestroy {
@ViewChild('temporaryTextElement') temporaryTextElement: ElementRef;
@Input('svg') svg: SVGSVGElement;
private leftPosition: string = '0px';
private topPosition: string = '0px';
private innerText: string = '';
leftPosition: string = '0px';
topPosition: string = '0px';
innerText: string = '';
private editingDrawingId: string;
private editedElement: any;

View File

@ -1,4 +1,5 @@
import { TextElement } from '../models/drawings/text-element';
import { MapDrawing } from '../models/map/map-drawing';
export class DataEventSource<T> {
constructor(
@ -50,3 +51,10 @@ export class TextEditedDataEvent {
public textElement: TextElement
) {}
}
export class DrawingContextMenu {
constructor(
public event: any,
public drawing: MapDrawing
) {}
}

View File

@ -14,6 +14,7 @@ import { EllipseElement } from "../models/drawings/ellipse-element";
import { ResizingEnd } from "../events/resizing";
import { LineElement } from "../models/drawings/line-element";
import { MapSettingsManager } from "../managers/map-settings-manager";
import { DrawingContextMenu } from '../events/event-source';
@Injectable()
@ -21,6 +22,7 @@ export class DrawingsWidget implements Widget {
public draggable = new Draggable<SVGGElement, MapDrawing>();
public draggingEnabled = false;
public resizingFinished = new EventEmitter<ResizingEnd<MapDrawing>>();
public onContextMenu = new EventEmitter<DrawingContextMenu>();
// public onContextMenu = new EventEmitter<NodeContextMenu>();
// public onDrawingClicked = new EventEmitter<NodeClicked>();
@ -60,6 +62,10 @@ export class DrawingsWidget implements Widget {
.append<SVGGElement>('g')
.attr('class', 'drawing')
.attr('drawing_id', (l: MapDrawing) => l.id)
.on('contextmenu', (l: MapDrawing) => {
event.preventDefault();
this.onContextMenu.emit(new DrawingContextMenu(event, l));
})
const merge = drawing.merge(drawing_enter);

View File

@ -0,0 +1,4 @@
<button mat-menu-item (click)="editStyle()">
<mat-icon>style</mat-icon>
<span>Edit style</span>
</button>

View File

@ -0,0 +1,24 @@
import { Component, OnInit, Input } from "@angular/core";
import { Drawing } from '../../../../../cartography/models/drawing';
import { Server } from '../../../../../models/server';
import { DrawingsDataSource } from '../../../../../cartography/datasources/drawings-datasource';
import { DrawingService } from '../../../../../services/drawing.service';
@Component({
selector: 'app-edit-style-action',
templateUrl: './edit-style-action.component.html'
})
export class EditStyleActionComponent implements OnInit {
@Input() server: Server;
@Input() drawing: Drawing;
constructor(
private drawingsDataSource: DrawingsDataSource,
private drawingService: DrawingService
) {}
ngOnInit() {}
editStyle(){}
}

View File

@ -0,0 +1,45 @@
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';
@Component({
selector: 'app-move-layer-down-action',
templateUrl: './move-layer-down-action.component.html'
})
export class MoveLayerDownActionComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
@Input() drawing: Drawing;
constructor(
private nodesDataSource: NodesDataSource,
private drawingsDataSource: DrawingsDataSource,
private nodeService: NodeService,
private drawingService: DrawingService
) {}
ngOnInit() {}
moveLayerDown() {
if (this.node) {
this.node.z--;
this.nodesDataSource.update(this.node);
this.nodeService
.update(this.server, this.node)
.subscribe((node: Node) => {});
} else if(this.drawing) {
this.drawing.z--;
this.drawingsDataSource.update(this.drawing);
this.drawingService
.update(this.server, this.drawing)
.subscribe((drawing: Drawing) => {});
}
}
}

View File

@ -0,0 +1,45 @@
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';
@Component({
selector: 'app-move-layer-up-action',
templateUrl: './move-layer-up-action.component.html'
})
export class MoveLayerUpActionComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
@Input() drawing: Drawing;
constructor(
private nodesDataSource: NodesDataSource,
private drawingsDataSource: DrawingsDataSource,
private nodeService: NodeService,
private drawingService: DrawingService
) { }
ngOnInit() {}
moveLayerUp() {
if (this.node) {
this.node.z++;
this.nodesDataSource.update(this.node);
this.nodeService
.update(this.server, this.node)
.subscribe((node: Node) => {});
} else if(this.drawing) {
this.drawing.z++;
this.drawingsDataSource.update(this.drawing);
this.drawingService
.update(this.server, this.drawing)
.subscribe((drawing: Drawing) => {});
}
}
}

View File

@ -0,0 +1,10 @@
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition" *ngIf="node || drawing">
<span [matMenuTriggerFor]="contextMenu"></span>
<mat-menu #contextMenu="matMenu" class="context-menu-items">
<app-start-node-action *ngIf="hasNodeCapabilities" [server]="server" [node]="node"></app-start-node-action>
<app-stop-node-action *ngIf="hasNodeCapabilities" [server]="server" [node]="node"></app-stop-node-action>
<app-edit-style-action *ngIf="hasDrawingCapabilities" [server]="server" [drawing]="drawing"></app-edit-style-action>
<app-move-layer-up-action *ngIf="!projectService.isReadOnly(project)" [server]="server" [node]="node" [drawing]="drawing"></app-move-layer-up-action>
<app-move-layer-down-action *ngIf="!projectService.isReadOnly(project)" [server]="server" [node]="node" [drawing]="drawing"></app-move-layer-down-action>
</mat-menu>
</div>

View File

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NodeContextMenuComponent } from './node-context-menu.component';
import { ContextMenuComponent } from './context-menu.component';
describe('NodeContextMenuComponent', () => {
let component: NodeContextMenuComponent;
let fixture: ComponentFixture<NodeContextMenuComponent>;
let component: ContextMenuComponent;
let fixture: ComponentFixture<ContextMenuComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NodeContextMenuComponent ]
declarations: [ ContextMenuComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NodeContextMenuComponent);
fixture = TestBed.createComponent(ContextMenuComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@ -5,14 +5,15 @@ import { Node } from "../../../cartography/models/node";
import { Server } from "../../../models/server";
import { Project } from "../../../models/project";
import { ProjectService } from "../../../services/project.service";
import { Drawing } from '../../../cartography/models/drawing';
@Component({
selector: 'app-node-context-menu',
templateUrl: './node-context-menu.component.html',
styleUrls: ['./node-context-menu.component.scss']
selector: 'app-context-menu',
templateUrl: './context-menu.component.html',
styleUrls: ['./context-menu.component.scss']
})
export class NodeContextMenuComponent implements OnInit {
export class ContextMenuComponent implements OnInit {
@Input() project: Project;
@Input() server: Server;
@ -21,6 +22,9 @@ export class NodeContextMenuComponent implements OnInit {
topPosition;
leftPosition;
node: Node;
drawing: Drawing;
private hasNodeCapabilities: boolean = false;
private hasDrawingCapabilities: boolean = false;
constructor(
private sanitizer: DomSanitizer,
@ -37,10 +41,30 @@ export class NodeContextMenuComponent implements OnInit {
this.changeDetector.detectChanges();
}
public open(node: Node, top: number, left: number) {
public openMenuForNode(node: Node, top: number, left: number) {
this.resetCapabilities();
this.hasNodeCapabilities = true;
this.node = node;
this.setPosition(top, left);
this.contextMenu.openMenu();
}
public openMenuForDrawing(drawing: Drawing, top: number, left: number) {
this.resetCapabilities();
this.hasDrawingCapabilities = true;
this.drawing = drawing;
this.setPosition(top, left);
this.contextMenu.openMenu();
}
private resetCapabilities() {
this.node = null;
this.drawing = null;
this.hasDrawingCapabilities = false;
this.hasNodeCapabilities = false;
}
}

View File

@ -1,29 +0,0 @@
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';
@Component({
selector: 'app-move-layer-down-action',
templateUrl: './move-layer-down-action.component.html'
})
export class MoveLayerDownActionComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
constructor(
private nodesDataSource: NodesDataSource,
private nodeService: NodeService
) { }
ngOnInit() {}
moveLayerDown() {
this.node.z--;
this.nodesDataSource.update(this.node);
this.nodeService
.update(this.server, this.node)
.subscribe((node: Node) => {});
}
}

View File

@ -1,29 +0,0 @@
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';
@Component({
selector: 'app-move-layer-up-action',
templateUrl: './move-layer-up-action.component.html'
})
export class MoveLayerUpActionComponent implements OnInit {
@Input() server: Server;
@Input() node: Node;
constructor(
private nodesDataSource: NodesDataSource,
private nodeService: NodeService
) { }
ngOnInit() {}
moveLayerUp() {
this.node.z++;
this.nodesDataSource.update(this.node);
this.nodeService
.update(this.server, this.node)
.subscribe((node: Node) => {});
}
}

View File

@ -1,9 +0,0 @@
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition" *ngIf="node">
<span [matMenuTriggerFor]="contextMenu"></span>
<mat-menu #contextMenu="matMenu" class="context-menu-items">
<app-start-node-action [server]="server" [node]="node"></app-start-node-action>
<app-stop-node-action [server]="server" [node]="node"></app-stop-node-action>
<app-move-layer-up-action *ngIf="!projectService.isReadOnly(project)" [server]="server" [node]="node"></app-move-layer-up-action>
<app-move-layer-down-action *ngIf="!projectService.isReadOnly(project)" [server]="server" [node]="node"></app-move-layer-down-action>
</mat-menu>
</div>

View File

@ -121,7 +121,7 @@
</button>
</div>
<app-node-context-menu [project]="project" [server]="server"></app-node-context-menu>
<app-context-menu [project]="project" [server]="server"></app-context-menu>
</div>
<app-progress></app-progress>

View File

@ -31,6 +31,8 @@ import { of } from 'rxjs';
import { Server } from '../../models/server';
import { Node } from '../../cartography/models/node';
import { ToolsService } from '../../services/tools.service';
import { DrawingsWidget } from '../../cartography/widgets/drawings';
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
export class MockedProgressService {
public activate() {}
@ -133,7 +135,9 @@ describe('ProjectMapComponent', () => {
{ provide: ProjectWebServiceHandler },
{ provide: MapChangeDetectorRef },
{ provide: NodeWidget },
{ provide: DrawingsWidget },
{ provide: MapNodeToNodeConverter },
{ provide: MapDrawingToDrawingConverter },
{ provide: NodesDataSource },
{ provide: LinksDataSource },
{ provide: DrawingsDataSource, useValue: drawingsDataSource},

View File

@ -12,7 +12,7 @@ import { ServerService } from "../../services/server.service";
import { ProjectService } from '../../services/project.service';
import { Server } from "../../models/server";
import { Drawing } from "../../cartography/models/drawing";
import { NodeContextMenuComponent } from "./node-context-menu/node-context-menu.component";
import { ContextMenuComponent } from "./context-menu/context-menu.component";
import { Template } from "../../models/template";
import { NodeService } from "../../services/node.service";
import { Symbol } from "../../models/symbol";
@ -24,11 +24,14 @@ import { ProgressService } from "../../common/progress/progress.service";
import { MapChangeDetectorRef } from '../../cartography/services/map-change-detector-ref';
import { NodeContextMenu } from '../../cartography/events/nodes';
import { NodeWidget } from '../../cartography/widgets/node';
import { DrawingsWidget } from '../../cartography/widgets/drawings';
import { DrawingService } from '../../services/drawing.service';
import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-node-to-node-converter';
import { SettingsService, Settings } from '../../services/settings.service';
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
import { ToolsService } from '../../services/tools.service';
import { DrawingContextMenu } from '../../cartography/events/event-source';
import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter';
@Component({
@ -66,7 +69,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private inReadOnlyMode = false;
@ViewChild(NodeContextMenuComponent) nodeContextMenu: NodeContextMenuComponent;
@ViewChild(ContextMenuComponent) contextMenu: ContextMenuComponent;
@ViewChild(D3MapComponent) mapChild: D3MapComponent;
private subscriptions: Subscription[] = [];
@ -81,7 +84,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private projectWebServiceHandler: ProjectWebServiceHandler,
private mapChangeDetectorRef: MapChangeDetectorRef,
private nodeWidget: NodeWidget,
private drawingsWidget: DrawingsWidget,
private mapNodeToNode: MapNodeToNodeConverter,
private mapDrawingToDrawing: MapDrawingToDrawingConverter,
private nodesDataSource: NodesDataSource,
private linksDataSource: LinksDataSource,
private drawingsDataSource: DrawingsDataSource,
@ -189,16 +194,26 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
}
setUpMapCallbacks() {
const onContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
const onNodeContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
const node = this.mapNodeToNode.convert(eventNode.node);
this.nodeContextMenu.open(
this.contextMenu.openMenuForNode(
node,
eventNode.event.clientY,
eventNode.event.clientX
);
});
this.subscriptions.push(onContextMenu);
const onDrawingContextMenu = this.drawingsWidget.onContextMenu.subscribe((eventDrawing: DrawingContextMenu) => {
const drawing = this.mapDrawingToDrawing.convert(eventDrawing.drawing);
this.contextMenu.openMenuForDrawing(
drawing,
eventDrawing.event.clientY,
eventDrawing.event.clientX
);
});
this.subscriptions.push(onNodeContextMenu);
this.subscriptions.push(onDrawingContextMenu);
this.mapChangeDetectorRef.detectChanges();
}