diff --git a/src/app/cartography/components/selection-select/selection-select.component.ts b/src/app/cartography/components/selection-select/selection-select.component.ts index 0bd5454b..a1ca43e7 100644 --- a/src/app/cartography/components/selection-select/selection-select.component.ts +++ b/src/app/cartography/components/selection-select/selection-select.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, Input, OnInit, OnDestroy } from '@angular/core'; import { Subscription } from 'rxjs'; import { SelectionManager } from '../../managers/selection-manager'; import { MapChangeDetectorRef } from '../../services/map-change-detector-ref'; @@ -12,7 +12,10 @@ export class SelectionSelectComponent implements OnInit, OnDestroy { private onSelected: Subscription; private onUnselected: Subscription; - constructor(private selectionManager: SelectionManager, private mapChangeDetectorRef: MapChangeDetectorRef) {} + constructor( + private selectionManager: SelectionManager, + private mapChangeDetectorRef: MapChangeDetectorRef + ) {} ngOnInit() { this.onSelected = this.selectionManager.selected.subscribe(() => { diff --git a/src/app/cartography/tools/selection-tool.ts b/src/app/cartography/tools/selection-tool.ts index 80507014..2c6e12cf 100644 --- a/src/app/cartography/tools/selection-tool.ts +++ b/src/app/cartography/tools/selection-tool.ts @@ -1,5 +1,5 @@ -import { Injectable } from '@angular/core'; -import { mouse, select } from 'd3-selection'; +import { Injectable, EventEmitter } from '@angular/core'; +import { mouse, select, event } from 'd3-selection'; import { Subject } from 'rxjs'; import { SVGSelection } from '../models/types'; @@ -12,20 +12,39 @@ export class SelectionTool { static readonly SELECTABLE_CLASS = '.selectable'; public rectangleSelected = new Subject(); + public contextMenuOpened = new EventEmitter(); private path; private enabled = false; - public constructor(private context: Context, private selectionEventSource: SelectionEventSource) {} + public constructor( + private context: Context, + private selectionEventSource: SelectionEventSource + ) {} + + public disableContextMenu(){ + + } public setEnabled(enabled) { this.enabled = enabled; + this.contextMenuOpened.emit(true); } private activate(selection) { const self = this; selection.on('mousedown', function() { + // prevent deselection on right click + if (event.button == 2) { + selection.on('contextmenu', () => { + event.preventDefault(); + }); + + self.contextMenuOpened.emit(event); + return; + } + const subject = select(window); const parent = this.parentElement; diff --git a/src/app/components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component.ts b/src/app/components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component.ts index fbc5c0ae..9453629c 100644 --- a/src/app/components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component.ts @@ -13,8 +13,8 @@ import { DrawingService } from '../../../../../services/drawing.service'; }) export class MoveLayerDownActionComponent implements OnInit { @Input() server: Server; - @Input() node: Node; - @Input() drawing: Drawing; + @Input() nodes: Node[]; + @Input() drawings: Drawing[]; constructor( private nodesDataSource: NodesDataSource, @@ -26,16 +26,18 @@ export class MoveLayerDownActionComponent implements OnInit { ngOnInit() {} moveLayerDown() { - if (this.node) { - this.node.z--; - this.nodesDataSource.update(this.node); + this.nodes.forEach((node) => { + node.z--; + this.nodesDataSource.update(node); - this.nodeService.update(this.server, this.node).subscribe((node: Node) => {}); - } else if (this.drawing) { - this.drawing.z--; - this.drawingsDataSource.update(this.drawing); + this.nodeService.update(this.server, node).subscribe((node: Node) => {}); + }); - this.drawingService.update(this.server, this.drawing).subscribe((drawing: Drawing) => {}); - } + this.drawings.forEach((drawing) => { + drawing.z--; + this.drawingsDataSource.update(drawing); + + this.drawingService.update(this.server, drawing).subscribe((drawing: Drawing) => {}); + }); } } diff --git a/src/app/components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts b/src/app/components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts index 93385dac..587cfc9b 100644 --- a/src/app/components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts @@ -13,8 +13,8 @@ import { DrawingService } from '../../../../../services/drawing.service'; }) export class MoveLayerUpActionComponent implements OnInit { @Input() server: Server; - @Input() node: Node; - @Input() drawing: Drawing; + @Input() nodes: Node[]; + @Input() drawings: Drawing[]; constructor( private nodesDataSource: NodesDataSource, @@ -26,16 +26,18 @@ export class MoveLayerUpActionComponent implements OnInit { ngOnInit() {} moveLayerUp() { - if (this.node) { - this.node.z++; - this.nodesDataSource.update(this.node); + this.nodes.forEach((node) => { + node.z++; + this.nodesDataSource.update(node); - this.nodeService.update(this.server, this.node).subscribe((node: Node) => {}); - } else if (this.drawing) { - this.drawing.z++; - this.drawingsDataSource.update(this.drawing); + this.nodeService.update(this.server, node).subscribe((node: Node) => {}); + }); - this.drawingService.update(this.server, this.drawing).subscribe((drawing: Drawing) => {}); - } + this.drawings.forEach((drawing) => { + drawing.z++; + this.drawingsDataSource.update(drawing); + + this.drawingService.update(this.server, drawing).subscribe((drawing: Drawing) => {}); + }); } } diff --git a/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.html b/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.html index f3acc224..cbcad82f 100644 --- a/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.html +++ b/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.html @@ -1,4 +1,4 @@ - diff --git a/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.ts b/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.ts index e7b25309..5d0704e5 100644 --- a/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/start-node-action/start-node-action.component.ts @@ -9,13 +9,22 @@ import { Node } from '../../../../../cartography/models/node'; }) export class StartNodeActionComponent implements OnInit { @Input() server: Server; - @Input() node: Node; + @Input() nodes: Node[]; + private isNodeWithStoppedStatus: boolean; constructor(private nodeService: NodeService) {} - ngOnInit() {} + ngOnInit() { + this.nodes.forEach((node) => { + if (node.status === 'stopped') { + this.isNodeWithStoppedStatus = true; + } + }); + } - startNode() { - this.nodeService.start(this.server, this.node).subscribe((n: Node) => {}); + startNodes() { + this.nodes.forEach((node) => { + this.nodeService.start(this.server, node).subscribe((n: Node) => {}); + }); } } diff --git a/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.html b/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.html index 47f4016e..22df2cb1 100644 --- a/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.html +++ b/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.html @@ -1,4 +1,4 @@ - diff --git a/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.ts b/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.ts index bb8c78ad..5cec8935 100644 --- a/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.ts +++ b/src/app/components/project-map/context-menu/actions/stop-node-action/stop-node-action.component.ts @@ -9,13 +9,22 @@ import { Node } from '../../../../../cartography/models/node'; }) export class StopNodeActionComponent implements OnInit { @Input() server: Server; - @Input() node: Node; + @Input() nodes: Node[]; + private isNodeWithStartedStatus: boolean; constructor(private nodeService: NodeService) {} - ngOnInit() {} + ngOnInit() { + this.nodes.forEach((node) => { + if (node.status === 'started') { + this.isNodeWithStartedStatus = true; + } + }); + } - stopNode() { - this.nodeService.stop(this.server, this.node).subscribe((n: Node) => {}); + stopNodes() { + this.nodes.forEach((node) => { + this.nodeService.stop(this.server, node).subscribe((n: Node) => {}); + }); } } diff --git a/src/app/components/project-map/context-menu/context-menu.component.html b/src/app/components/project-map/context-menu/context-menu.component.html index 881d1325..3146bbbc 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.html +++ b/src/app/components/project-map/context-menu/context-menu.component.html @@ -1,31 +1,30 @@ -
+
- - - + +
diff --git a/src/app/components/project-map/context-menu/context-menu.component.spec.ts b/src/app/components/project-map/context-menu/context-menu.component.spec.ts index 2e33ac84..30c60aef 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.spec.ts +++ b/src/app/components/project-map/context-menu/context-menu.component.spec.ts @@ -16,7 +16,10 @@ describe('ContextMenuComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [MatMenuModule, BrowserModule], - providers: [{ provide: ChangeDetectorRef }, { provide: ProjectService, useClass: MockedProjectService }], + providers: [ + { provide: ChangeDetectorRef }, + { provide: ProjectService, useClass: MockedProjectService } + ], declarations: [ContextMenuComponent], schemas: [NO_ERRORS_SCHEMA] }).compileComponents(); @@ -62,4 +65,13 @@ describe('ContextMenuComponent', () => { expect(spy.calls.any()).toBeTruthy(); }); + + it('should reset capabilities while opening menu for list of elements', () => { + component.contextMenu = { openMenu() {} } as MatMenuTrigger; + var spy = spyOn(component, 'resetCapabilities'); + spyOn(component, 'setPosition').and.callFake(() => {}); + component.openMenuForListOfElements([], [], [], 0, 0); + + expect(spy.calls.any()).toBeTruthy(); + }); }); diff --git a/src/app/components/project-map/context-menu/context-menu.component.ts b/src/app/components/project-map/context-menu/context-menu.component.ts index 61f5283d..3988f3f5 100644 --- a/src/app/components/project-map/context-menu/context-menu.component.ts +++ b/src/app/components/project-map/context-menu/context-menu.component.ts @@ -7,6 +7,8 @@ import { Project } from '../../../models/project'; import { ProjectService } from '../../../services/project.service'; import { Drawing } from '../../../cartography/models/drawing'; import { TextElement } from '../../../cartography/models/drawings/text-element'; +import { Label } from '../../../cartography/models/label'; + @Component({ selector: 'app-context-menu', @@ -21,11 +23,12 @@ export class ContextMenuComponent implements OnInit { topPosition; leftPosition; - node: Node; - drawing: Drawing; - private hasNodeCapabilities: boolean = false; - private hasDrawingCapabilities: boolean = false; - private isTextElement: boolean = false; + + drawings: Drawing[] = []; + nodes: Node[] = []; + labels: Label[] = []; + + private hasTextCapabilities: boolean = false; constructor( private sanitizer: DomSanitizer, @@ -43,32 +46,49 @@ export class ContextMenuComponent implements OnInit { this.changeDetector.detectChanges(); } - public openMenuForNode(node: Node, top: number, left: number) { + public openMenuForDrawing(drawing: Drawing, top: number, left: number) { this.resetCapabilities(); - this.hasNodeCapabilities = true; + this.hasTextCapabilities = drawing.element instanceof TextElement; - this.node = node; + this.drawings = [drawing]; this.setPosition(top, left); this.contextMenu.openMenu(); } - public openMenuForDrawing(drawing: Drawing, top: number, left: number) { + public openMenuForNode(node: Node, top: number, left: number) { this.resetCapabilities(); - this.hasDrawingCapabilities = true; - this.isTextElement = drawing.element instanceof TextElement; - this.drawing = drawing; + this.nodes = [node]; + this.setPosition(top, left); + + this.contextMenu.openMenu(); + } + + public openMenuForLabel(label: Label, top: number, left: number) { + this.resetCapabilities(); + + this.labels = [label]; + this.setPosition(top, left); + + this.contextMenu.openMenu(); + } + + public openMenuForListOfElements(drawings: Drawing[], nodes: Node[], labels: Label[], top: number, left: number) { + this.resetCapabilities(); + + this.drawings = drawings; + this.nodes = nodes; + this.labels = labels; this.setPosition(top, left); this.contextMenu.openMenu(); } private resetCapabilities() { - this.node = null; - this.drawing = null; - this.hasDrawingCapabilities = false; - this.hasNodeCapabilities = false; - this.isTextElement = false; + this.drawings = []; + this.nodes = []; + this.labels = []; + this.hasTextCapabilities = false; } } diff --git a/src/app/components/project-map/project-map.component.spec.ts b/src/app/components/project-map/project-map.component.spec.ts index 06bc904b..25874c7c 100644 --- a/src/app/components/project-map/project-map.component.spec.ts +++ b/src/app/components/project-map/project-map.component.spec.ts @@ -33,6 +33,9 @@ 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'; +import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter'; +import { SelectionManager } from '../../cartography/managers/selection-manager'; +import { SelectionTool } from '../../cartography/tools/selection-tool'; export class MockedProgressService { public activate() {} @@ -139,11 +142,14 @@ describe('ProjectMapComponent', () => { { provide: DrawingsWidget }, { provide: MapNodeToNodeConverter }, { provide: MapDrawingToDrawingConverter }, + { provide: MapLabelToLabelConverter }, { provide: NodesDataSource }, { provide: LinksDataSource }, { provide: DrawingsDataSource, useValue: drawingsDataSource }, { provide: SettingsService, useClass: MockedSettingsService }, - { provide: ToolsService } + { provide: ToolsService }, + { provide: SelectionManager }, + { provide: SelectionTool } ], declarations: [ProjectMapComponent, D3MapComponent, ...ANGULAR_MAP_DECLARATIONS], schemas: [NO_ERRORS_SCHEMA] diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index c574f181..9c4bd4f5 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -32,6 +32,14 @@ import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.compo import { ToolsService } from '../../services/tools.service'; import { DrawingContextMenu } from '../../cartography/events/event-source'; import { MapDrawingToDrawingConverter } from '../../cartography/converters/map/map-drawing-to-drawing-converter'; +import { SelectionManager } from '../../cartography/managers/selection-manager'; +import { SelectionTool } from '../../cartography/tools/selection-tool'; +import { MapDrawing } from '../../cartography/models/map/map-drawing'; +import { MapLabel } from '../../cartography/models/map/map-label'; +import { Label } from '../../cartography/models/label'; +import { MapNode } from '../../cartography/models/map/map-node'; +import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter'; + @Component({ selector: 'app-project-map', @@ -86,11 +94,14 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private drawingsWidget: DrawingsWidget, private mapNodeToNode: MapNodeToNodeConverter, private mapDrawingToDrawing: MapDrawingToDrawingConverter, + private mapLabelToLabel: MapLabelToLabelConverter, private nodesDataSource: NodesDataSource, private linksDataSource: LinksDataSource, private drawingsDataSource: DrawingsDataSource, private settingsService: SettingsService, - private toolsService: ToolsService + private toolsService: ToolsService, + private selectionManager: SelectionManager, + private selectionTool: SelectionTool ) {} ngOnInit() { @@ -206,8 +217,30 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.contextMenu.openMenuForDrawing(drawing, eventDrawing.event.clientY, eventDrawing.event.clientX); }); + const onContextMenu = this.selectionTool.contextMenuOpened.subscribe((event) => { + const selectedItems = this.selectionManager.getSelected(); + if (selectedItems.length === 0) return; + + let drawings: Drawing[] = []; + let nodes: Node[] = []; + let labels: Label[] = []; + + selectedItems.forEach((elem) => { + if (elem instanceof MapDrawing) { + drawings.push(this.mapDrawingToDrawing.convert(elem)); + } else if (elem instanceof MapNode) { + nodes.push(this.mapNodeToNode.convert(elem)); + } else if (elem instanceof MapLabel) { + labels.push(this.mapLabelToLabel.convert(elem)); + } + }); + + this.contextMenu.openMenuForListOfElements(drawings, nodes, labels, event.clientY, event.clientX); + }); + this.subscriptions.push(onNodeContextMenu); this.subscriptions.push(onDrawingContextMenu); + this.subscriptions.push(onContextMenu); this.mapChangeDetectorRef.detectChanges(); } diff --git a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.spec.ts b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.spec.ts index 9c26c9e3..22ee843e 100644 --- a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.spec.ts +++ b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.spec.ts @@ -45,6 +45,10 @@ export class MockedProjectService { add() { return of(this.projects.pop); } + + isReadOnly(project: Project){ + return false; + } } describe('AddBlankProjectDialogComponent', () => { diff --git a/src/index.html b/src/index.html index 9ad7cad8..756be9a7 100644 --- a/src/index.html +++ b/src/index.html @@ -34,7 +34,7 @@ })(); - +