diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5b68d94c..74e82e1a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -28,8 +28,9 @@ import { ApplianceService } from "./services/appliance.service"; import { LinkService } from "./services/link.service"; import { ProjectsComponent } from './components/projects/projects.component'; +import { AddBlankProjectDialogComponent } from './components/projects/add-blank-project-dialog/add-blank-project-dialog.component'; import { ImportProjectDialogComponent } from './components/projects/import-project-dialog/import-project-dialog.component'; -import { ImportProjectConfirmationDialogComponent} from './components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component'; +import { ConfirmationDialogComponent} from './components/projects/confirmation-dialog/confirmation-dialog.component'; import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component'; import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component'; import { AppComponent } from './app.component'; @@ -70,6 +71,7 @@ import { SnapshotsComponent } from './components/snapshots/snapshots.component'; import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-item/snapshot-menu-item.component'; import { MATERIAL_IMPORTS } from './material.imports'; import { DrawingService } from './services/drawing.service'; +import { ProjectNameValidator } from './components/projects/models/projectNameValidator'; if (environment.production) { @@ -94,8 +96,9 @@ if (environment.production) { SnapshotMenuItemComponent, SnapshotsComponent, ProjectsComponent, + AddBlankProjectDialogComponent, ImportProjectDialogComponent, - ImportProjectConfirmationDialogComponent, + ConfirmationDialogComponent, DefaultLayoutComponent, ProgressDialogComponent, NodeContextMenuComponent, @@ -153,15 +156,17 @@ if (environment.production) { InRectangleHelper, DrawingsDataSource, ServerErrorHandler, - ServerDatabase + ServerDatabase, + ProjectNameValidator ], entryComponents: [ AddServerDialogComponent, CreateSnapshotDialogComponent, ProgressDialogComponent, ApplianceListDialogComponent, + AddBlankProjectDialogComponent, ImportProjectDialogComponent, - ImportProjectConfirmationDialogComponent + ConfirmationDialogComponent ], bootstrap: [ AppComponent ] }) diff --git a/src/app/cartography/cartography.module.ts b/src/app/cartography/cartography.module.ts index 43173aa4..c87cc881 100644 --- a/src/app/cartography/cartography.module.ts +++ b/src/app/cartography/cartography.module.ts @@ -17,10 +17,27 @@ import { Context } from './models/context'; import { D3_MAP_IMPORTS } from './d3-map.imports'; import { CanvasSizeDetector } from './helpers/canvas-size-detector'; import { MapListeners } from './listeners/map-listeners'; -import { DrawingsDraggableListener } from './listeners/drawings-draggable-listener'; -import { NodesDraggableListener } from './listeners/nodes-draggable-listener'; +import { DraggableListener } from './listeners/draggable-listener'; import { DrawingsEventSource } from './events/drawings-event-source'; import { NodesEventSource } from './events/nodes-event-source'; +import { DrawingToMapDrawingConverter } from './converters/map/drawing-to-map-drawing-converter'; +import { LabelToMapLabelConverter } from './converters/map/label-to-map-label-converter'; +import { LinkToMapLinkConverter } from './converters/map/link-to-map-link-converter'; +import { MapDrawingToDrawingConverter } from './converters/map/map-drawing-to-drawing-converter'; +import { MapLabelToLabelConverter } from './converters/map/map-label-to-label-converter'; +import { MapLinkNodeToLinkNodeConverter } from './converters/map/map-link-node-to-link-node-converter'; +import { MapLinkToLinkConverter } from './converters/map/map-link-to-link-converter'; +import { MapNodeToNodeConverter } from './converters/map/map-node-to-node-converter'; +import { MapPortToPortConverter } from './converters/map/map-port-to-port-converter'; +import { MapSymbolToSymbolConverter } from './converters/map/map-symbol-to-symbol-converter'; +import { NodeToMapNodeConverter } from './converters/map/node-to-map-node-converter'; +import { PortToMapPortConverter } from './converters/map/port-to-map-port-converter'; +import { SymbolToMapSymbolConverter } from './converters/map/symbol-to-map-symbol-converter'; +import { LinkNodeToMapLinkNodeConverter } from './converters/map/link-node-to-map-link-node-converter'; +import { GraphDataManager } from './managers/graph-data-manager'; +import { SelectionUpdateListener } from './listeners/selection-update-listener'; +import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from './datasources/map-datasource'; +import { SelectionListener } from './listeners/selection-listener'; @NgModule({ @@ -44,11 +61,31 @@ import { NodesEventSource } from './events/nodes-event-source'; MapChangeDetectorRef, CanvasSizeDetector, Context, + SelectionUpdateListener, MapListeners, - DrawingsDraggableListener, - NodesDraggableListener, + DraggableListener, + SelectionListener, DrawingsEventSource, NodesEventSource, + DrawingToMapDrawingConverter, + LabelToMapLabelConverter, + LinkToMapLinkConverter, + LinkNodeToMapLinkNodeConverter, + MapDrawingToDrawingConverter, + MapLabelToLabelConverter, + MapLinkNodeToLinkNodeConverter, + MapLinkToLinkConverter, + MapNodeToNodeConverter, + MapPortToPortConverter, + MapSymbolToSymbolConverter, + NodeToMapNodeConverter, + PortToMapPortConverter, + SymbolToMapSymbolConverter, + GraphDataManager, + MapNodesDataSource, + MapLinksDataSource, + MapDrawingsDataSource, + MapSymbolsDataSource, ...D3_MAP_IMPORTS ], exports: [ MapComponent ] diff --git a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts b/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts index c3bcdffe..c687f5ea 100644 --- a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts +++ b/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts @@ -1,12 +1,12 @@ import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild } from '@angular/core'; -import { Port } from '../../../models/port'; import { DrawingLineWidget } from '../../widgets/drawing-line'; -import { Node } from '../../models/node'; import { Subscription } from 'rxjs'; import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component'; -import { LinkCreated } from '../../events/links'; +import { MapLinkCreated } from '../../events/links'; import { NodeClicked } from '../../events/nodes'; import { NodeWidget } from '../../widgets/node'; +import { MapNode } from '../../models/map/map-node'; +import { MapPort } from '../../models/map/map-port'; @Component({ @@ -17,7 +17,7 @@ import { NodeWidget } from '../../widgets/node'; export class DrawLinkToolComponent implements OnInit, OnDestroy { @ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent; - @Output('linkCreated') linkCreated = new EventEmitter(); + @Output('linkCreated') linkCreated = new EventEmitter(); private onNodeClicked: Subscription; @@ -37,18 +37,18 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy { } ngOnDestroy() { - if(this.drawingLineTool.isDrawing()) { + if (this.drawingLineTool.isDrawing()) { this.drawingLineTool.stop(); } this.onNodeClicked.unsubscribe(); } public onChooseInterface(event) { - const node: Node = event.node; - const port: Port = event.port; + const node: MapNode = event.node; + const port: MapPort = event.port; if (this.drawingLineTool.isDrawing()) { const data = this.drawingLineTool.stop(); - this.linkCreated.emit(new LinkCreated(data['node'], data['port'], node, port)); + this.linkCreated.emit(new MapLinkCreated(data['node'], data['port'], node, port)); } else { this.drawingLineTool.start(node.x + node.width / 2., node.y + node.height / 2., { 'node': node, diff --git a/src/app/cartography/components/map/map.component.ts b/src/app/cartography/components/map/map.component.ts index 902d658e..9e132299 100644 --- a/src/app/cartography/components/map/map.component.ts +++ b/src/app/cartography/components/map/map.component.ts @@ -3,29 +3,30 @@ import { } from '@angular/core'; import { Selection, select } from 'd3-selection'; -import { Node } from "../../models/node"; -import { Link } from "../../../models/link"; import { GraphLayout } from "../../widgets/graph-layout"; import { Context } from "../../models/context"; import { Size } from "../../models/size"; -import { Drawing } from "../../models/drawing"; -import { Symbol } from '../../../models/symbol'; import { NodesWidget } from '../../widgets/nodes'; import { Subscription } from 'rxjs'; import { InterfaceLabelWidget } from '../../widgets/interface-label'; import { SelectionTool } from '../../tools/selection-tool'; import { MovingTool } from '../../tools/moving-tool'; -import { LinksWidget } from '../../widgets/links'; import { MapChangeDetectorRef } from '../../services/map-change-detector-ref'; -import { NodeDragging, NodeDragged, NodeClicked } from '../../events/nodes'; import { LinkCreated } from '../../events/links'; import { CanvasSizeDetector } from '../../helpers/canvas-size-detector'; -import { NodeWidget } from '../../widgets/node'; import { MapListeners } from '../../listeners/map-listeners'; import { DraggedDataEvent } from '../../events/event-source'; import { NodesEventSource } from '../../events/nodes-event-source'; import { DrawingsEventSource } from '../../events/drawings-event-source'; import { DrawingsWidget } from '../../widgets/drawings'; +import { Node } from '../../models/node'; +import { Link } from '../../../models/link'; +import { Drawing } from '../../models/drawing'; +import { Symbol } from '../../../models/symbol'; +import { MapNodeToNodeConverter } from '../../converters/map/map-node-to-node-converter'; +import { MapPortToPortConverter } from '../../converters/map/map-port-to-port-converter'; +import { GraphDataManager } from '../../managers/graph-data-manager'; +import { MapDrawingToDrawingConverter } from '../../converters/map/map-drawing-to-drawing-converter'; @Component({ @@ -42,39 +43,41 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { @Input() width = 1500; @Input() height = 600; - @Output() nodeDragged: EventEmitter>; - @Output() drawingDragged: EventEmitter>; + @Output() nodeDragged = new EventEmitter>(); + @Output() drawingDragged = new EventEmitter>(); @Output() onLinkCreated = new EventEmitter(); private parentNativeElement: any; private svg: Selection; private onChangesDetected: Subscription; + private nodeDraggedSub: Subscription; + private drawingDraggedSub: Subscription; protected settings = { 'show_interface_labels': true }; constructor( + private graphDataManager: GraphDataManager, private context: Context, private mapChangeDetectorRef: MapChangeDetectorRef, private canvasSizeDetector: CanvasSizeDetector, private mapListeners: MapListeners, + private mapNodeToNode: MapNodeToNodeConverter, + private mapPortToPort: MapPortToPortConverter, + private mapDrawingToDrawing: MapDrawingToDrawingConverter, protected element: ElementRef, protected nodesWidget: NodesWidget, - protected nodeWidget: NodeWidget, - protected linksWidget: LinksWidget, protected drawingsWidget: DrawingsWidget, protected interfaceLabelWidget: InterfaceLabelWidget, protected selectionToolWidget: SelectionTool, protected movingToolWidget: MovingTool, public graphLayout: GraphLayout, - nodesEventSource: NodesEventSource, - drawingsEventSource: DrawingsEventSource, + private nodesEventSource: NodesEventSource, + private drawingsEventSource: DrawingsEventSource, ) { this.parentNativeElement = element.nativeElement; - this.nodeDragged = nodesEventSource.dragged; - this.drawingDragged = drawingsEventSource.dragged; } @Input('show-interface-labels') @@ -100,7 +103,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { @Input('readonly') set readonly(value) { this.nodesWidget.draggingEnabled = !value; - this.drawingsWidget.draggingEnabled == !value; + this.drawingsWidget.draggingEnabled = !value; } ngOnChanges(changes: { [propKey: string]: SimpleChange }) { @@ -113,12 +116,6 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { (changes['symbols'] && !changes['symbols'].isFirstChange()) ) { if (this.svg.empty && !this.svg.empty()) { - if (changes['nodes']) { - this.onNodesChange(changes['nodes']); - } - if (changes['links']) { - this.onLinksChange(changes['links']); - } if (changes['symbols']) { this.onSymbolsChange(changes['symbols']); } @@ -135,10 +132,18 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { this.onChangesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => { if (this.mapChangeDetectorRef.hasBeenDrawn) { - this.reload(); + this.redraw(); } }); + this.nodeDraggedSub = this.nodesEventSource.dragged.subscribe((evt) => { + this.nodeDragged.emit(new DraggedDataEvent(this.mapNodeToNode.convert(evt.datum), evt.dx, evt.dy)); + }); + + this.drawingDraggedSub = this.drawingsEventSource.dragged.subscribe((evt) => { + this.drawingDragged.emit(new DraggedDataEvent(this.mapDrawingToDrawing.convert(evt.datum), evt.dx, evt.dy)); + }); + this.mapListeners.onInit(this.svg); } @@ -146,6 +151,8 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { this.graphLayout.disconnect(this.svg); this.onChangesDetected.unsubscribe(); this.mapListeners.onDestroy(); + this.nodeDraggedSub.unsubscribe(); + this.drawingDraggedSub.unsubscribe(); } public createGraph(domElement: HTMLElement) { @@ -161,7 +168,14 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { } protected linkCreated(evt) { - this.onLinkCreated.emit(evt); + const linkCreated = new LinkCreated( + this.mapNodeToNode.convert(evt.sourceNode), + this.mapPortToPort.convert(evt.sourcePort), + this.mapNodeToNode.convert(evt.targetNode), + this.mapPortToPort.convert(evt.targetPort) + ); + + this.onLinkCreated.emit(linkCreated); } private changeLayout() { @@ -169,53 +183,22 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { this.context.size = this.getSize(); } - this.graphLayout.setNodes(this.nodes); - this.graphLayout.setLinks(this.links); - this.graphLayout.setDrawings(this.drawings); - this.redraw(); } - private onLinksChange(change: SimpleChange) { - const nodes_by_id = {}; - this.nodes.forEach((n: Node) => { - nodes_by_id[n.node_id] = n; - }); - - this.links.forEach((link: Link) => { - const source_id = link.nodes[0].node_id; - const target_id = link.nodes[1].node_id; - if (source_id in nodes_by_id) { - link.source = nodes_by_id[source_id]; - } - if (target_id in nodes_by_id) { - link.target = nodes_by_id[target_id]; - } - - if (link.source && link.target) { - link.x = link.source.x + (link.target.x - link.source.x) * 0.5; - link.y = link.source.y + (link.target.y - link.source.y) * 0.5; - } - }); - } - - private onNodesChange(change: SimpleChange) { - this.onLinksChange(null); - } private onSymbolsChange(change: SimpleChange) { - this.nodeWidget.setSymbols(this.symbols); + this.graphDataManager.setSymbols(this.symbols); } - public redraw() { + private redraw() { + this.graphDataManager.setNodes(this.nodes); + this.graphDataManager.setLinks(this.links); + this.graphDataManager.setDrawings(this.drawings); + this.graphLayout.draw(this.svg, this.context); } - public reload() { - this.onLinksChange(null); - this.redraw(); - } - @HostListener('window:resize', ['$event']) onResize(event) { this.changeLayout(); diff --git a/src/app/cartography/components/node-select-interface/node-select-interface.component.ts b/src/app/cartography/components/node-select-interface/node-select-interface.component.ts index 40d9da3a..4b773fc4 100644 --- a/src/app/cartography/components/node-select-interface/node-select-interface.component.ts +++ b/src/app/cartography/components/node-select-interface/node-select-interface.component.ts @@ -1,8 +1,8 @@ import { ChangeDetectorRef, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core'; import { MatMenuTrigger } from "@angular/material"; import { DomSanitizer } from "@angular/platform-browser"; -import { Node } from "../../../cartography/models/node"; -import { Port } from "../../../models/port"; +import { MapNode } from '../../models/map/map-node'; +import { MapPort } from '../../models/map/map-port'; @Component({ @@ -18,7 +18,7 @@ export class NodeSelectInterfaceComponent implements OnInit { protected topPosition; protected leftPosition; - public node: Node; + public node: MapNode; constructor( private sanitizer: DomSanitizer, @@ -35,13 +35,13 @@ export class NodeSelectInterfaceComponent implements OnInit { this.changeDetector.detectChanges(); } - public open(node: Node, top: number, left: number) { + public open(node: MapNode, top: number, left: number) { this.node = node; this.setPosition(top, left); this.contextMenu.openMenu(); } - public chooseInterface(port: Port) { + public chooseInterface(port: MapPort) { this.onChooseInterface.emit({ 'node': this.node, 'port': port diff --git a/src/app/cartography/converters/converter.ts b/src/app/cartography/converters/converter.ts new file mode 100644 index 00000000..3bf7ea3e --- /dev/null +++ b/src/app/cartography/converters/converter.ts @@ -0,0 +1,3 @@ +export interface Converter { + convert(obj: F): T; +} diff --git a/src/app/cartography/converters/map/drawing-to-map-drawing-converter.ts b/src/app/cartography/converters/map/drawing-to-map-drawing-converter.ts new file mode 100644 index 00000000..e6d2ff09 --- /dev/null +++ b/src/app/cartography/converters/map/drawing-to-map-drawing-converter.ts @@ -0,0 +1,24 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Drawing } from "../../models/drawing"; +import { MapDrawing } from "../../models/map/map-drawing"; + + +@Injectable() +export class DrawingToMapDrawingConverter implements Converter { + constructor( + ) {} + + convert(drawing: Drawing) { + const mapDrawing = new MapDrawing(); + mapDrawing.id = drawing.drawing_id; + mapDrawing.projectId = drawing.project_id; + mapDrawing.rotation = drawing.rotation; + mapDrawing.svg = drawing.svg; + mapDrawing.x = drawing.x; + mapDrawing.y = drawing.y; + mapDrawing.z = drawing.z; + return mapDrawing; + } +} diff --git a/src/app/cartography/converters/map/label-to-map-label-converter.ts b/src/app/cartography/converters/map/label-to-map-label-converter.ts new file mode 100644 index 00000000..0501978f --- /dev/null +++ b/src/app/cartography/converters/map/label-to-map-label-converter.ts @@ -0,0 +1,19 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Label } from "../../models/label"; +import { MapLabel } from "../../models/map/map-label"; + + +@Injectable() +export class LabelToMapLabelConverter implements Converter { + convert(label: Label) { + const mapLabel = new MapLabel(); + mapLabel.rotation = label.rotation; + mapLabel.style = label.style; + mapLabel.text = label.text; + mapLabel.x = label.x; + mapLabel.y = label.y; + return mapLabel; + } +} diff --git a/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts b/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts new file mode 100644 index 00000000..48f3cc2e --- /dev/null +++ b/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { LabelToMapLabelConverter } from "./label-to-map-label-converter"; +import { LinkNode } from "../../../models/link-node"; +import { MapLinkNode } from "../../models/map/map-link-node"; + + +@Injectable() +export class LinkNodeToMapLinkNodeConverter implements Converter { + constructor( + private labelToMapLabel: LabelToMapLabelConverter + ) {} + + convert(linkNode: LinkNode) { + const mapLinkNode = new MapLinkNode(); + mapLinkNode.nodeId = linkNode.node_id; + mapLinkNode.adapterNumber = linkNode.adapter_number; + mapLinkNode.portNumber = linkNode.port_number; + mapLinkNode.label = this.labelToMapLabel.convert(linkNode.label); + return mapLinkNode; + } +} diff --git a/src/app/cartography/converters/map/link-to-map-link-converter.ts b/src/app/cartography/converters/map/link-to-map-link-converter.ts new file mode 100644 index 00000000..0000ad78 --- /dev/null +++ b/src/app/cartography/converters/map/link-to-map-link-converter.ts @@ -0,0 +1,26 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { LinkNodeToMapLinkNodeConverter } from "./link-node-to-map-link-node-converter"; +import { Link } from "../../../models/link"; +import { MapLink } from "../../models/map/map-link"; + + +@Injectable() +export class LinkToMapLinkConverter implements Converter { + constructor( + private linkNodeToMapLinkNode: LinkNodeToMapLinkNodeConverter + ) {} + + convert(link: Link) { + const mapLink = new MapLink(); + mapLink.id = link.link_id; + mapLink.captureFileName = link.capture_file_name; + mapLink.captureFilePath = link.capture_file_path; + mapLink.capturing = link.capturing; + mapLink.linkType = link.link_type; + mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode)); + mapLink.projectId = link.project_id; + return mapLink; + } +} diff --git a/src/app/cartography/converters/map/map-drawing-to-drawing-converter.ts b/src/app/cartography/converters/map/map-drawing-to-drawing-converter.ts new file mode 100644 index 00000000..04347c0e --- /dev/null +++ b/src/app/cartography/converters/map/map-drawing-to-drawing-converter.ts @@ -0,0 +1,24 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Drawing } from "../../models/drawing"; +import { MapDrawing } from "../../models/map/map-drawing"; + + +@Injectable() +export class MapDrawingToDrawingConverter implements Converter { + constructor( + ) {} + + convert(mapDrawing: MapDrawing) { + const drawing = new Drawing(); + drawing.drawing_id = mapDrawing.id; + drawing.project_id = mapDrawing.projectId; + drawing.rotation = mapDrawing.rotation; + drawing.svg = mapDrawing.svg; + drawing.x = mapDrawing.x; + drawing.y = mapDrawing.y; + drawing.z = mapDrawing.z; + return drawing; + } +} diff --git a/src/app/cartography/converters/map/map-label-to-label-converter.ts b/src/app/cartography/converters/map/map-label-to-label-converter.ts new file mode 100644 index 00000000..1a0ca88d --- /dev/null +++ b/src/app/cartography/converters/map/map-label-to-label-converter.ts @@ -0,0 +1,19 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Label } from "../../models/label"; +import { MapLabel } from "../../models/map/map-label"; + + +@Injectable() +export class MapLabelToLabelConverter implements Converter { + convert(mapLabel: MapLabel) { + const label = new Label(); + label.rotation = mapLabel.rotation; + label.style = mapLabel.style; + label.text = mapLabel.text; + label.x = mapLabel.x; + label.y = mapLabel.y; + return label; + } +} diff --git a/src/app/cartography/converters/map/map-link-node-to-link-node-converter.ts b/src/app/cartography/converters/map/map-link-node-to-link-node-converter.ts new file mode 100644 index 00000000..1f8e5c98 --- /dev/null +++ b/src/app/cartography/converters/map/map-link-node-to-link-node-converter.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { MapLinkNode } from "../../models/map/map-link-node"; +import { MapLabelToLabelConverter } from "./map-label-to-label-converter"; +import { LinkNode } from "../../../models/link-node"; + + +@Injectable() +export class MapLinkNodeToLinkNodeConverter implements Converter { + constructor( + private mapLabelToLabel: MapLabelToLabelConverter + ) {} + + convert(mapLinkNode: MapLinkNode) { + const linkNode = new LinkNode(); + linkNode.node_id = mapLinkNode.nodeId; + linkNode.adapter_number = mapLinkNode.adapterNumber; + linkNode.port_number = mapLinkNode.portNumber; + linkNode.label = this.mapLabelToLabel.convert(mapLinkNode.label); + return linkNode; + } +} diff --git a/src/app/cartography/converters/map/map-link-to-link-converter.ts b/src/app/cartography/converters/map/map-link-to-link-converter.ts new file mode 100644 index 00000000..b892d69f --- /dev/null +++ b/src/app/cartography/converters/map/map-link-to-link-converter.ts @@ -0,0 +1,26 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { MapLinkNodeToLinkNodeConverter } from "./map-link-node-to-link-node-converter"; +import { Link } from "../../../models/link"; +import { MapLink } from "../../models/map/map-link"; + + +@Injectable() +export class MapLinkToLinkConverter implements Converter { + constructor( + private mapLinkNodeToMapLinkNode: MapLinkNodeToLinkNodeConverter + ) {} + + convert(mapLink: MapLink) { + const link = new Link(); + link.link_id = mapLink.id; + link.capture_file_name = mapLink.captureFileName; + link.capture_file_path = mapLink.captureFilePath; + link.capturing = mapLink.capturing; + link.link_type = mapLink.linkType; + link.nodes = mapLink.nodes.map((mapLinkNode) => this.mapLinkNodeToMapLinkNode.convert(mapLinkNode)); + link.project_id = mapLink.projectId; + return link; + } +} diff --git a/src/app/cartography/converters/map/map-node-to-node-converter.ts b/src/app/cartography/converters/map/map-node-to-node-converter.ts new file mode 100644 index 00000000..d8d02111 --- /dev/null +++ b/src/app/cartography/converters/map/map-node-to-node-converter.ts @@ -0,0 +1,42 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { MapNode } from "../../models/map/map-node"; +import { MapLabelToLabelConverter } from "./map-label-to-label-converter"; +import { MapPortToPortConverter } from "./map-port-to-port-converter"; +import { Node } from "../../models/node"; + + +@Injectable() +export class MapNodeToNodeConverter implements Converter { + constructor( + private mapLabelToLabel: MapLabelToLabelConverter, + private mapPortToPort: MapPortToPortConverter + ) {} + + convert(mapNode: MapNode) { + const node = new Node(); + node.node_id = mapNode.id; + node.command_line = mapNode.commandLine; + node.compute_id = mapNode.computeId; + node.console = mapNode.console; + node.console_host = mapNode.consoleHost; + node.first_port_name = mapNode.firstPortName; + node.height = mapNode.height; + node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined; + node.name = mapNode.name; + node.node_directory = mapNode.nodeDirectory; + node.node_type = mapNode.nodeType; + node.port_name_format = mapNode.portNameFormat; + node.port_segment_size = mapNode.portSegmentSize; + node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : []; + node.project_id = mapNode.projectId; + node.status = mapNode.status; + node.symbol = mapNode.symbol; + node.width = mapNode.width; + node.x = mapNode.x; + node.y = mapNode.y; + node.z = mapNode.z; + return node; + } +} diff --git a/src/app/cartography/converters/map/map-port-to-port-converter.ts b/src/app/cartography/converters/map/map-port-to-port-converter.ts new file mode 100644 index 00000000..0d40fec3 --- /dev/null +++ b/src/app/cartography/converters/map/map-port-to-port-converter.ts @@ -0,0 +1,19 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Port } from "../../../models/port"; +import { MapPort } from "../../models/map/map-port"; + + +@Injectable() +export class MapPortToPortConverter implements Converter { + convert(mapPort: MapPort) { + const port = new Port(); + port.adapter_number = mapPort.adapterNumber; + port.link_type = mapPort.linkType; + port.name = mapPort.name; + port.port_number = mapPort.portNumber; + port.short_name = mapPort.shortName; + return port; + } +} diff --git a/src/app/cartography/converters/map/map-symbol-to-symbol-converter.ts b/src/app/cartography/converters/map/map-symbol-to-symbol-converter.ts new file mode 100644 index 00000000..d48152e1 --- /dev/null +++ b/src/app/cartography/converters/map/map-symbol-to-symbol-converter.ts @@ -0,0 +1,18 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { MapSymbol } from "../../models/map/map-symbol"; +import { Symbol } from "../../../models/symbol"; + + +@Injectable() +export class MapSymbolToSymbolConverter implements Converter { + convert(mapSymbol: MapSymbol) { + const symbol = new Symbol(); + symbol.symbol_id = mapSymbol.id; + symbol.builtin = mapSymbol.builtin; + symbol.filename = mapSymbol.filename; + symbol.raw = mapSymbol.raw; + return symbol; + } +} diff --git a/src/app/cartography/converters/map/node-to-map-node-converter.ts b/src/app/cartography/converters/map/node-to-map-node-converter.ts new file mode 100644 index 00000000..2019d1a5 --- /dev/null +++ b/src/app/cartography/converters/map/node-to-map-node-converter.ts @@ -0,0 +1,42 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { MapNode } from "../../models/map/map-node"; +import { Node } from "../../models/node"; +import { LabelToMapLabelConverter } from "./label-to-map-label-converter"; +import { PortToMapPortConverter } from "./port-to-map-port-converter"; + + +@Injectable() +export class NodeToMapNodeConverter implements Converter { + constructor( + private labelToMapLabel: LabelToMapLabelConverter, + private portToMapPort: PortToMapPortConverter + ) {} + + convert(node: Node) { + const mapNode = new MapNode(); + mapNode.id = node.node_id; + mapNode.commandLine = node.command_line; + mapNode.computeId = node.compute_id; + mapNode.console = node.console; + mapNode.consoleHost = node.console_host; + mapNode.firstPortName = node.first_port_name; + mapNode.height = node.height; + mapNode.label = this.labelToMapLabel.convert(node.label); + mapNode.name = node.name; + mapNode.nodeDirectory = node.node_directory; + mapNode.nodeType = node.node_type; + mapNode.portNameFormat = node.port_name_format; + mapNode.portSegmentSize = node.port_segment_size; + mapNode.ports = node.ports.map((port) => this.portToMapPort.convert(port)); + mapNode.projectId = node.project_id; + mapNode.status = node.status; + mapNode.symbol = node.symbol; + mapNode.width = node.width; + mapNode.x = node.x; + mapNode.y = node.y; + mapNode.z = node.z; + return mapNode; + } +} diff --git a/src/app/cartography/converters/map/port-to-map-port-converter.ts b/src/app/cartography/converters/map/port-to-map-port-converter.ts new file mode 100644 index 00000000..32fc12bc --- /dev/null +++ b/src/app/cartography/converters/map/port-to-map-port-converter.ts @@ -0,0 +1,19 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Port } from "../../../models/port"; +import { MapPort } from "../../models/map/map-port"; + + +@Injectable() +export class PortToMapPortConverter implements Converter { + convert(port: Port) { + const mapPort = new MapPort(); + mapPort.adapterNumber = port.adapter_number; + mapPort.linkType = port.link_type; + mapPort.name = port.name; + mapPort.portNumber = port.port_number; + mapPort.shortName = port.short_name; + return mapPort; + } +} diff --git a/src/app/cartography/converters/map/symbol-to-map-symbol-converter.ts b/src/app/cartography/converters/map/symbol-to-map-symbol-converter.ts new file mode 100644 index 00000000..c02f8964 --- /dev/null +++ b/src/app/cartography/converters/map/symbol-to-map-symbol-converter.ts @@ -0,0 +1,18 @@ +import { Injectable } from "@angular/core"; + +import { Converter } from "../converter"; +import { Symbol } from "../../../models/symbol"; +import { MapSymbol } from "../../models/map/map-symbol"; + + +@Injectable() +export class SymbolToMapSymbolConverter implements Converter { + convert(symbol: Symbol) { + const mapSymbol = new MapSymbol(); + mapSymbol.id = symbol.symbol_id; + mapSymbol.builtin = symbol.builtin; + mapSymbol.filename = symbol.filename; + mapSymbol.raw = symbol.raw; + return mapSymbol; + } +} diff --git a/src/app/cartography/datasources/datasource.spec.ts b/src/app/cartography/datasources/datasource.spec.ts index c323fe5e..27588056 100644 --- a/src/app/cartography/datasources/datasource.spec.ts +++ b/src/app/cartography/datasources/datasource.spec.ts @@ -6,9 +6,9 @@ class Item { class TestDataSource extends DataSource { - protected findIndex(item: Item) { - return this.data.findIndex((i: Item) => i.id === item.id); - } + protected getItemKey(item: Item) { + return item.id; + }; } diff --git a/src/app/cartography/datasources/datasource.ts b/src/app/cartography/datasources/datasource.ts index 2652f0fa..a51819a0 100644 --- a/src/app/cartography/datasources/datasource.ts +++ b/src/app/cartography/datasources/datasource.ts @@ -20,10 +20,30 @@ export abstract class DataSource { } public set(data: T[]) { - this.data = data; + data.forEach((item) => { + const index = this.findIndex(item); + if (index >= 0) { + const updated = Object.assign(this.data[index], item); + this.data[index] = updated; + } + else { + this.data.push(item); + } + }); + + const toRemove = this.data.filter((item) => data.filter((i) => this.getItemKey(i) === this.getItemKey(item)).length === 0); + toRemove.forEach((item) => this.remove(item)); + this.dataChange.next(this.data); } + public get(key: string | number) { + const index = this.data.findIndex((i: T) => this.getItemKey(i) === key); + if (index >= 0) { + return this.data[index]; + } + } + public update(item: T) { const index = this.findIndex(item); if (index >= 0) { @@ -55,5 +75,9 @@ export abstract class DataSource { this.dataChange.next(this.data); } - protected abstract findIndex(item: T): number; + private findIndex(item: T) { + return this.data.findIndex((i: T) => this.getItemKey(i) === this.getItemKey(item)); + } + + protected abstract getItemKey(item: T): any; } diff --git a/src/app/cartography/datasources/drawings-datasource.ts b/src/app/cartography/datasources/drawings-datasource.ts index bb10bb75..1eae4e4b 100644 --- a/src/app/cartography/datasources/drawings-datasource.ts +++ b/src/app/cartography/datasources/drawings-datasource.ts @@ -1,12 +1,12 @@ import { Injectable } from "@angular/core"; -import { Drawing } from "../models/drawing"; import { DataSource } from "./datasource"; +import { Drawing } from "../models/drawing"; @Injectable() export class DrawingsDataSource extends DataSource { - protected findIndex(drawing: Drawing) { - return this.data.findIndex((d: Drawing) => d.drawing_id === drawing.drawing_id); + protected getItemKey(drawing: Drawing) { + return drawing.drawing_id; } } diff --git a/src/app/cartography/datasources/links-datasource.ts b/src/app/cartography/datasources/links-datasource.ts index 4b5918df..0c743f11 100644 --- a/src/app/cartography/datasources/links-datasource.ts +++ b/src/app/cartography/datasources/links-datasource.ts @@ -1,12 +1,12 @@ import { Injectable } from "@angular/core"; import { DataSource } from "./datasource"; -import { Link} from "../../models/link"; +import { Link } from "../../models/link"; @Injectable() export class LinksDataSource extends DataSource { - protected findIndex(link: Link) { - return this.data.findIndex((l: Link) => l.link_id === link.link_id); + protected getItemKey(link: Link) { + return link.link_id; } } diff --git a/src/app/cartography/datasources/map-datasource.ts b/src/app/cartography/datasources/map-datasource.ts new file mode 100644 index 00000000..21f9f31b --- /dev/null +++ b/src/app/cartography/datasources/map-datasource.ts @@ -0,0 +1,28 @@ +import { DataSource } from "./datasource"; +import { MapNode } from "../models/map/map-node"; +import { MapLink } from "../models/map/map-link"; +import { MapDrawing } from "../models/map/map-drawing"; +import { MapSymbol } from "../models/map/map-symbol"; +import { Injectable } from "@angular/core"; + +export interface Indexed { + id: number | string; +} + +export class MapDataSource extends DataSource { + protected getItemKey(item: Indexed) { + return item.id; + } +} + +@Injectable() +export class MapNodesDataSource extends MapDataSource {} + +@Injectable() +export class MapLinksDataSource extends MapDataSource {} + +@Injectable() +export class MapDrawingsDataSource extends MapDataSource {} + +@Injectable() +export class MapSymbolsDataSource extends MapDataSource {} \ No newline at end of file diff --git a/src/app/cartography/datasources/nodes-datasource.ts b/src/app/cartography/datasources/nodes-datasource.ts index d18985df..f6a5b785 100644 --- a/src/app/cartography/datasources/nodes-datasource.ts +++ b/src/app/cartography/datasources/nodes-datasource.ts @@ -6,7 +6,7 @@ import { DataSource } from "./datasource"; @Injectable() export class NodesDataSource extends DataSource { - protected findIndex(node: Node) { - return this.data.findIndex((n: Node) => n.node_id === node.node_id); + protected getItemKey(node: Node) { + return node.node_id; } } diff --git a/src/app/cartography/datasources/symbols-datasource.ts b/src/app/cartography/datasources/symbols-datasource.ts index fff06c3c..a00b9492 100644 --- a/src/app/cartography/datasources/symbols-datasource.ts +++ b/src/app/cartography/datasources/symbols-datasource.ts @@ -6,7 +6,7 @@ import { Symbol } from "../../models/symbol"; @Injectable() export class SymbolsDataSource extends DataSource { - protected findIndex(symbol: Symbol) { - return this.data.findIndex((s: Symbol) => s.symbol_id === symbol.symbol_id); + protected getItemKey(symbol: Symbol) { + return symbol.symbol_id; } } diff --git a/src/app/cartography/events/draggable.ts b/src/app/cartography/events/draggable.ts index e121b8e4..1b616ab5 100644 --- a/src/app/cartography/events/draggable.ts +++ b/src/app/cartography/events/draggable.ts @@ -43,14 +43,16 @@ export class Draggable { } private behaviour() { + let startEvt; + return drag() .on('start', (datum: Datum) => { - const evt = new DraggableStart(datum); - evt.dx = event.dx; - evt.dy = event.dy; - evt.x = event.x; - evt.y = event.y; - this.start.emit(evt); + startEvt = new DraggableStart(datum); + startEvt.dx = event.dx; + startEvt.dy = event.dy; + startEvt.x = event.x; + startEvt.y = event.y; + this.start.emit(startEvt); }) .on('drag', (datum: Datum) => { const evt = new DraggableDrag(datum); @@ -62,8 +64,8 @@ export class Draggable { }) .on('end', (datum: Datum) => { const evt = new DraggableEnd(datum); - evt.dx = event.dx; - evt.dy = event.dy; + evt.dx = event.x - startEvt.x; + evt.dy = event.y - startEvt.y; evt.x = event.x; evt.y = event.y; this.end.emit(evt); diff --git a/src/app/cartography/events/drawings-event-source.ts b/src/app/cartography/events/drawings-event-source.ts index a24f2b6b..40e1093e 100644 --- a/src/app/cartography/events/drawings-event-source.ts +++ b/src/app/cartography/events/drawings-event-source.ts @@ -1,9 +1,9 @@ import { Injectable, EventEmitter } from "@angular/core"; -import { Drawing } from "../models/drawing"; import { DraggedDataEvent } from "./event-source"; +import { MapDrawing } from "../models/map/map-drawing"; @Injectable() export class DrawingsEventSource { - public dragged = new EventEmitter>(); -} \ No newline at end of file + public dragged = new EventEmitter>(); +} diff --git a/src/app/cartography/events/event-source.ts b/src/app/cartography/events/event-source.ts index 26bc15df..c6a2368a 100644 --- a/src/app/cartography/events/event-source.ts +++ b/src/app/cartography/events/event-source.ts @@ -1,9 +1,9 @@ export class DataEventSource { constructor( - public datum: T + public datum: T, + public dx: number, + public dy: number ) {} } - -// class CreatedDataEvent extends DataEventSource {} export class DraggedDataEvent extends DataEventSource {} diff --git a/src/app/cartography/events/links.ts b/src/app/cartography/events/links.ts index b8c3ff1c..3b2d4599 100644 --- a/src/app/cartography/events/links.ts +++ b/src/app/cartography/events/links.ts @@ -1,12 +1,23 @@ -import { Node } from "../models/node"; import { Port } from "../../models/port"; +import { Node } from "../models/node"; +import { MapNode } from "../models/map/map-node"; +import { MapPort } from "../models/map/map-port"; export class LinkCreated { - constructor( - public sourceNode: Node, - public sourcePort: Port, - public targetNode: Node, - public targetPort: Port - ){} + constructor( + public sourceNode: Node, + public sourcePort: Port, + public targetNode: Node, + public targetPort: Port + ) {} +} + +export class MapLinkCreated { + constructor( + public sourceNode: MapNode, + public sourcePort: MapPort, + public targetNode: MapNode, + public targetPort: MapPort + ) {} } diff --git a/src/app/cartography/events/nodes-event-source.ts b/src/app/cartography/events/nodes-event-source.ts index bd237506..1930828b 100644 --- a/src/app/cartography/events/nodes-event-source.ts +++ b/src/app/cartography/events/nodes-event-source.ts @@ -1,9 +1,9 @@ import { Injectable, EventEmitter } from "@angular/core"; -import { Node } from "../models/node"; import { DraggedDataEvent } from "./event-source"; +import { MapNode } from "../models/map/map-node"; @Injectable() export class NodesEventSource { - public dragged = new EventEmitter>(); -} \ No newline at end of file + public dragged = new EventEmitter>(); +} diff --git a/src/app/cartography/events/nodes.ts b/src/app/cartography/events/nodes.ts index fdfa441d..77c03c93 100644 --- a/src/app/cartography/events/nodes.ts +++ b/src/app/cartography/events/nodes.ts @@ -1,9 +1,9 @@ -import { Node } from "../models/node"; +import { MapNode } from "../models/map/map-node"; class NodeEvent { constructor( public event: any, - public node: Node + public node: MapNode ) {} } @@ -11,4 +11,4 @@ export class NodeDragging extends NodeEvent {} export class NodeDragged extends NodeEvent {} export class NodeClicked extends NodeEvent {} -export class NodeContextMenu extends NodeEvent {} \ No newline at end of file +export class NodeContextMenu extends NodeEvent {} diff --git a/src/app/cartography/helpers/multi-link-calculator-helper.ts b/src/app/cartography/helpers/multi-link-calculator-helper.ts index b7189b98..d519bfa0 100644 --- a/src/app/cartography/helpers/multi-link-calculator-helper.ts +++ b/src/app/cartography/helpers/multi-link-calculator-helper.ts @@ -1,5 +1,5 @@ import { Injectable } from "@angular/core"; -import { Link } from "../../models/link"; +import { MapLink } from "../models/map/map-link"; @Injectable() @@ -30,11 +30,11 @@ export class MultiLinkCalculatorHelper { }; } - public assignDataToLinks(links: Link[]) { + public assignDataToLinks(links: MapLink[]) { const links_from_nodes = {}; - links.forEach((l: Link, i: number) => { - const sid = l.source.node_id; - const tid = l.target.node_id; + links.forEach((l: MapLink, i: number) => { + const sid = l.source.id; + const tid = l.target.id; const key = (sid < tid ? sid + "," + tid : tid + "," + sid); let idx = 1; if (!(key in links_from_nodes)) { diff --git a/src/app/cartography/listeners/draggable-listener.ts b/src/app/cartography/listeners/draggable-listener.ts new file mode 100644 index 00000000..c2406491 --- /dev/null +++ b/src/app/cartography/listeners/draggable-listener.ts @@ -0,0 +1,105 @@ +import { Injectable } from "@angular/core"; +import { NodesWidget } from "../widgets/nodes"; +import { DraggableStart, DraggableDrag, DraggableEnd } from "../events/draggable"; +import { Subscription } from "rxjs"; +import { SelectionManager } from "../managers/selection-manager"; +import { LinksWidget } from "../widgets/links"; +import { NodesEventSource } from "../events/nodes-event-source"; +import { DraggedDataEvent } from "../events/event-source"; +import { MapNode } from "../models/map/map-node"; +import { GraphDataManager } from "../managers/graph-data-manager"; +import { DrawingsWidget } from "../widgets/drawings"; +import { merge } from "rxjs"; +import { MapDrawing } from "../models/map/map-drawing"; +import { DrawingsEventSource } from "../events/drawings-event-source"; + + +@Injectable() +export class DraggableListener { + private start: Subscription; + private drag: Subscription; + private end: Subscription; + + constructor( + private nodesWidget: NodesWidget, + private drawingsWidget: DrawingsWidget, + private linksWidget: LinksWidget, + private selectionManager: SelectionManager, + private nodesEventSource: NodesEventSource, + private drawingsEventSource: DrawingsEventSource, + private graphDataManager: GraphDataManager + ) { + } + + public onInit(svg: any) { + this.start = merge( + this.nodesWidget.draggable.start, + this.drawingsWidget.draggable.start + ).subscribe((evt: DraggableStart) => { + const selected = this.selectionManager.getSelected(); + + if (evt.datum instanceof MapNode) { + if (selected.filter((item) => item instanceof MapNode && item.id === evt.datum.id).length === 0) { + this.selectionManager.setSelected([evt.datum]); + } + } + + if (evt.datum instanceof MapDrawing) { + if (selected.filter((item) => item instanceof MapDrawing && item.id === evt.datum.id).length === 0) { + this.selectionManager.setSelected([evt.datum]); + } + } + }); + + this.drag = merge( + this.nodesWidget.draggable.drag, + this.drawingsWidget.draggable.drag + ).subscribe((evt: DraggableDrag) => { + const selected = this.selectionManager.getSelected(); + + // update nodes + selected.filter((item) => item instanceof MapNode).forEach((node: MapNode) => { + node.x += evt.dx; + node.y += evt.dy; + + this.nodesWidget.redrawNode(svg, node); + + const links = this.graphDataManager.getLinks().filter( + (link) => link.target.id === node.id || link.source.id === node.id); + links.forEach((link) => { + this.linksWidget.redrawLink(svg, link); + }); + }); + + // update drawings + selected.filter((item) => item instanceof MapDrawing).forEach((drawing: MapDrawing) => { + drawing.x += evt.dx; + drawing.y += evt.dy; + this.drawingsWidget.redrawDrawing(svg, drawing); + }); + + }); + + this.end = merge( + this.nodesWidget.draggable.end, + this.drawingsWidget.draggable.end + ).subscribe((evt: DraggableEnd) => { + const selected = this.selectionManager.getSelected(); + + selected.filter((item) => item instanceof MapNode).forEach((item: MapNode) => { + this.nodesEventSource.dragged.emit(new DraggedDataEvent(item, evt.dx, evt.dy)); + }) + + selected.filter((item) => item instanceof MapDrawing).forEach((item: MapDrawing) => { + this.drawingsEventSource.dragged.emit(new DraggedDataEvent(item, evt.dx, evt.dy)); + }); + }); + + } + + public onDestroy() { + this.start.unsubscribe(); + this.drag.unsubscribe(); + this.end.unsubscribe(); + } +} \ No newline at end of file diff --git a/src/app/cartography/listeners/drawings-draggable-listener.ts b/src/app/cartography/listeners/drawings-draggable-listener.ts deleted file mode 100644 index 420b840b..00000000 --- a/src/app/cartography/listeners/drawings-draggable-listener.ts +++ /dev/null @@ -1,55 +0,0 @@ -import { Injectable } from "@angular/core"; -import { DrawingsWidget } from "../widgets/drawings"; -import { DraggableStart } from "../events/draggable"; -import { Drawing } from "../models/drawing"; -import { Subscription } from "rxjs"; -import { SelectionManager } from "../managers/selection-manager"; -import { DrawingsEventSource } from "../events/drawings-event-source"; -import { DraggedDataEvent } from "../events/event-source"; - - -@Injectable() -export class DrawingsDraggableListener { - private start: Subscription; - private drag: Subscription; - private end: Subscription; - - constructor( - private drawingsWidget: DrawingsWidget, - private selectionManager: SelectionManager, - private drawingsEventSource: DrawingsEventSource - ) { - } - - public onInit(svg: any) { - this.start = this.drawingsWidget.draggable.start.subscribe((evt: DraggableStart) => { - let drawings = this.selectionManager.getSelectedDrawings(); - if (drawings.filter((n: Drawing) => n.drawing_id === evt.datum.drawing_id).length === 0) { - this.selectionManager.setSelectedDrawings([evt.datum]); - drawings = this.selectionManager.getSelectedDrawings(); - } - }); - - this.drag = this.drawingsWidget.draggable.drag.subscribe((evt: DraggableStart) => { - let drawings = this.selectionManager.getSelectedDrawings(); - drawings.forEach((drawing: Drawing) => { - drawing.x += evt.dx; - drawing.y += evt.dy; - this.drawingsWidget.redrawDrawing(svg, drawing); - }); - }); - - this.end = this.drawingsWidget.draggable.end.subscribe((evt: DraggableStart) => { - let drawings = this.selectionManager.getSelectedDrawings(); - drawings.forEach((drawing: Drawing) => { - this.drawingsEventSource.dragged.emit(new DraggedDataEvent(drawing)); - }); - }); - } - - public onDestroy() { - this.start.unsubscribe(); - this.drag.unsubscribe(); - this.end.unsubscribe(); - } -} \ No newline at end of file diff --git a/src/app/cartography/listeners/map-listeners.ts b/src/app/cartography/listeners/map-listeners.ts index 1147fe8e..40752896 100644 --- a/src/app/cartography/listeners/map-listeners.ts +++ b/src/app/cartography/listeners/map-listeners.ts @@ -1,18 +1,21 @@ import { Injectable } from "@angular/core"; import { MapListener } from "./map-listener"; -import { DrawingsDraggableListener } from "./drawings-draggable-listener"; -import { NodesDraggableListener } from "./nodes-draggable-listener"; +import { DraggableListener } from "./draggable-listener"; +import { SelectionUpdateListener } from "./selection-update-listener"; +import { SelectionListener } from "./selection-listener"; @Injectable() export class MapListeners { private listeners: MapListener[] = []; constructor( - private drawingsDraggableListener: DrawingsDraggableListener, - private nodesDraggableListener: NodesDraggableListener + private nodesDraggableListener: DraggableListener, + private selectionUpdateListener: SelectionUpdateListener, + private selectionListener: SelectionListener ) { - this.listeners.push(this.drawingsDraggableListener); this.listeners.push(this.nodesDraggableListener); + this.listeners.push(this.selectionUpdateListener); + this.listeners.push(this.selectionListener); } public onInit(svg: any) { diff --git a/src/app/cartography/listeners/nodes-draggable-listener.ts b/src/app/cartography/listeners/nodes-draggable-listener.ts deleted file mode 100644 index 8c339976..00000000 --- a/src/app/cartography/listeners/nodes-draggable-listener.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { Injectable } from "@angular/core"; -import { NodesWidget } from "../widgets/nodes"; -import { DraggableStart } from "../events/draggable"; -import { Node } from "../models/node"; -import { Subscription } from "rxjs"; -import { SelectionManager } from "../managers/selection-manager"; -import { LinksWidget } from "../widgets/links"; -import { GraphLayout } from "../widgets/graph-layout"; -import { NodesEventSource } from "../events/nodes-event-source"; -import { DraggedDataEvent } from "../events/event-source"; - - -@Injectable() -export class NodesDraggableListener { - private start: Subscription; - private drag: Subscription; - private end: Subscription; - - constructor( - private nodesWidget: NodesWidget, - private linksWidget: LinksWidget, - private selectionManager: SelectionManager, - private graphLayout: GraphLayout, - private nodesEventSource: NodesEventSource - ) { - } - - public onInit(svg: any) { - this.start = this.nodesWidget.draggable.start.subscribe((evt: DraggableStart) => { - let nodes = this.selectionManager.getSelectedNodes(); - if (nodes.filter((n: Node) => n.node_id === evt.datum.node_id).length === 0) { - this.selectionManager.setSelectedNodes([evt.datum]); - nodes = this.selectionManager.getSelectedNodes(); - } - }); - - this.drag = this.nodesWidget.draggable.drag.subscribe((evt: DraggableStart) => { - let nodes = this.selectionManager.getSelectedNodes(); - nodes.forEach((node: Node) => { - node.x += evt.dx; - node.y += evt.dy; - this.nodesWidget.redrawNode(svg, node); - - const links = this.graphLayout.getLinks().filter((link) => link.target.node_id === node.node_id || link.source.node_id === node.node_id); - links.forEach((link) => { - this.linksWidget.redrawLink(svg, link); - }); - }); - }); - - this.end = this.nodesWidget.draggable.end.subscribe((evt: DraggableStart) => { - let nodes = this.selectionManager.getSelectedNodes(); - nodes.forEach((node: Node) => { - this.nodesEventSource.dragged.emit(new DraggedDataEvent(node)); - }); - }); - } - - public onDestroy() { - this.start.unsubscribe(); - this.drag.unsubscribe(); - this.end.unsubscribe(); - } -} \ No newline at end of file diff --git a/src/app/cartography/listeners/selection-listener.spec.ts b/src/app/cartography/listeners/selection-listener.spec.ts new file mode 100644 index 00000000..5c8436ef --- /dev/null +++ b/src/app/cartography/listeners/selection-listener.spec.ts @@ -0,0 +1,68 @@ +import { Rectangle } from "../models/rectangle"; +import { InRectangleHelper } from "../helpers/in-rectangle-helper"; +import { MapNode } from "../models/map/map-node"; +import { MapLink } from "../models/map/map-link"; +import { mock, instance, when } from "ts-mockito"; +import { fakeAsync, tick } from "@angular/core/testing"; +import { SelectionListener } from "./selection-listener"; +import { SelectionManager } from "../managers/selection-manager"; +import { GraphDataManager } from "../managers/graph-data-manager"; +import { SelectionTool } from "../tools/selection-tool"; +import { Context } from "../models/context"; + + +describe('SelectionListener', () => { + let selectionListener: SelectionListener; + let manager: SelectionManager; + let selectionTool: SelectionTool; + + beforeEach(() => { + const mockedGraphData = mock(GraphDataManager); + + const node_1 = new MapNode(); + node_1.id = "test1"; + node_1.name = "Node 1"; + node_1.x = 150; + node_1.y = 150; + + const node_2 = new MapNode(); + node_2.id = "test2"; + node_2.name = "Node 2"; + node_2.x = 300; + node_2.y = 300; + + const link_1 = new MapLink(); + link_1.id = "test1"; + + when(mockedGraphData.getNodes()).thenReturn([node_1, node_2]); + when(mockedGraphData.getLinks()).thenReturn([link_1]); + when(mockedGraphData.getDrawings()).thenReturn([]); + + const graphData = instance(mockedGraphData); + const inRectangleHelper = new InRectangleHelper(); + + manager = new SelectionManager(); + selectionTool = new SelectionTool(new Context()); + selectionListener = new SelectionListener(selectionTool, graphData, inRectangleHelper, manager); + selectionListener.onInit(null); + }); + + afterEach(() => { + selectionListener.onDestroy(); + }) + + it('node should be selected', fakeAsync(() => { + selectionTool.rectangleSelected.next(new Rectangle(100, 100, 100, 100)); + tick(); + expect(manager.getSelected().length).toEqual(1); + })); + + it('node should be selected and deselected', fakeAsync(() => { + selectionTool.rectangleSelected.next(new Rectangle(100, 100, 100, 100)); + tick(); + selectionTool.rectangleSelected.next(new Rectangle(350, 350, 100, 100)); + tick(); + expect(manager.getSelected().length).toEqual(0); + })); + +}); diff --git a/src/app/cartography/listeners/selection-listener.ts b/src/app/cartography/listeners/selection-listener.ts new file mode 100644 index 00000000..b97e366c --- /dev/null +++ b/src/app/cartography/listeners/selection-listener.ts @@ -0,0 +1,45 @@ +import { Injectable } from "@angular/core"; +import { GraphDataManager } from "../managers/graph-data-manager"; +import { InRectangleHelper } from "../helpers/in-rectangle-helper"; +import { SelectionTool } from "../tools/selection-tool"; +import { Subscription } from "rxjs"; +import { Rectangle } from "electron"; +import { SelectionManager } from "../managers/selection-manager"; + + +@Injectable() +export class SelectionListener { + private onSelection: Subscription; + + constructor( + private selectionTool: SelectionTool, + private graphDataManager: GraphDataManager, + private inRectangleHelper: InRectangleHelper, + private selectionManager: SelectionManager + ) { + } + + public onInit(svg: any) { + this.onSelection = this.selectionTool.rectangleSelected.subscribe((rectangle: Rectangle) => { + const selectedNodes = this.graphDataManager.getNodes().filter((node) => { + return this.inRectangleHelper.inRectangle(rectangle, node.x, node.y) + }); + + const selectedLinks = this.graphDataManager.getLinks().filter((link) => { + return this.inRectangleHelper.inRectangle(rectangle, link.x, link.y) + }); + + const selectedDrawings = this.graphDataManager.getDrawings().filter((drawing) => { + return this.inRectangleHelper.inRectangle(rectangle, drawing.x, drawing.y) + }); + + const selected = [...selectedNodes, ...selectedLinks, ...selectedDrawings]; + + this.selectionManager.setSelected(selected); + }); + } + + public onDestroy() { + this.onSelection.unsubscribe(); + } +} \ No newline at end of file diff --git a/src/app/cartography/listeners/selection-update-listener.ts b/src/app/cartography/listeners/selection-update-listener.ts new file mode 100644 index 00000000..8272c282 --- /dev/null +++ b/src/app/cartography/listeners/selection-update-listener.ts @@ -0,0 +1,31 @@ +import { Injectable } from "@angular/core"; +import { Subscription } from "rxjs"; +import { MapChangeDetectorRef } from "../services/map-change-detector-ref"; +import { SelectionManager } from "../managers/selection-manager"; + + +@Injectable() +export class SelectionUpdateListener { + private onSelected: Subscription; + private onUnselected: Subscription; + + constructor( + private selectionManager: SelectionManager, + private mapChangeDetectorRef: MapChangeDetectorRef + ) { + } + + public onInit(svg: any) { + this.onSelected = this.selectionManager.selected.subscribe(() => { + this.mapChangeDetectorRef.detectChanges(); + }); + this.onUnselected = this.selectionManager.unselected.subscribe(() => { + this.mapChangeDetectorRef.detectChanges(); + }); + } + + public onDestroy() { + this.onSelected.unsubscribe(); + this.onUnselected.unsubscribe(); + } +} \ No newline at end of file diff --git a/src/app/cartography/managers/graph-data-manager.spec.ts b/src/app/cartography/managers/graph-data-manager.spec.ts new file mode 100644 index 00000000..fa4d4dd1 --- /dev/null +++ b/src/app/cartography/managers/graph-data-manager.spec.ts @@ -0,0 +1,38 @@ +export class MockedGraphDataManager { + private nodes = []; + private links = []; + private drawings = []; + private symbols = []; + + public setNodes(value) { + this.nodes = value; + } + + public getNodes() { + return this.nodes; + } + + public setLinks(value) { + this.links = value; + } + + public getLinks() { + return this.links; + } + + public setDrawings(value) { + this.drawings = value; + } + + public getDrawings() { + return this.drawings; + } + + public setSymbols(value) { + this.symbols = value; + } + + public getSymbols() { + return this.symbols; + } +} \ No newline at end of file diff --git a/src/app/cartography/managers/graph-data-manager.ts b/src/app/cartography/managers/graph-data-manager.ts new file mode 100644 index 00000000..7e6516d0 --- /dev/null +++ b/src/app/cartography/managers/graph-data-manager.ts @@ -0,0 +1,103 @@ +import { Injectable } from "@angular/core"; +import { Node } from "../models/node"; +import { NodeToMapNodeConverter } from "../converters/map/node-to-map-node-converter"; +import { LinkToMapLinkConverter } from "../converters/map/link-to-map-link-converter"; +import { DrawingToMapDrawingConverter } from "../converters/map/drawing-to-map-drawing-converter"; +import { SymbolToMapSymbolConverter } from "../converters/map/symbol-to-map-symbol-converter"; +import { MapNode } from "../models/map/map-node"; +import { MapLink } from "../models/map/map-link"; +import { Link } from "../../models/link"; +import { Drawing } from "../models/drawing"; +import { Symbol } from "../../models/symbol"; +import { LayersManager } from "./layers-manager"; +import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from "../datasources/map-datasource"; + +@Injectable() +export class GraphDataManager { + constructor( + private mapNodesDataSource: MapNodesDataSource, + private mapLinksDataSource: MapLinksDataSource, + private mapDrawingsDataSource: MapDrawingsDataSource, + private mapSymbolsDataSource: MapSymbolsDataSource, + private nodeToMapNode: NodeToMapNodeConverter, + private linkToMapLink: LinkToMapLinkConverter, + private drawingToMapDrawing: DrawingToMapDrawingConverter, + private symbolToMapSymbol: SymbolToMapSymbolConverter, + private layersManager: LayersManager + ) {} + + public setNodes(nodes: Node[]) { + const mapNodes = nodes.map((n) => this.nodeToMapNode.convert(n)); + this.mapNodesDataSource.set(mapNodes); + + this.assignDataToLinks(); + this.onDataUpdate(); + } + + public setLinks(links: Link[]) { + const mapLinks = links.map((l) => this.linkToMapLink.convert(l)); + this.mapLinksDataSource.set(mapLinks); + + this.assignDataToLinks(); + this.onDataUpdate(); + } + + public setDrawings(drawings: Drawing[]) { + const mapDrawings = drawings.map((d) => this.drawingToMapDrawing.convert(d)); + this.mapDrawingsDataSource.set(mapDrawings); + + this.onDataUpdate(); + } + + public setSymbols(symbols: Symbol[]) { + const mapSymbols = symbols.map((s) => this.symbolToMapSymbol.convert(s)); + this.mapSymbolsDataSource.set(mapSymbols); + } + + public getNodes() { + return this.mapNodesDataSource.getItems(); + } + + public getLinks() { + return this.mapLinksDataSource.getItems(); + } + + public getDrawings() { + return this.mapDrawingsDataSource.getItems(); + } + + public getSymbols() { + return this.mapSymbolsDataSource.getItems(); + } + + private onDataUpdate() { + this.layersManager.clear(); + this.layersManager.setNodes(this.getNodes()); + this.layersManager.setLinks(this.getLinks()); + this.layersManager.setDrawings(this.getDrawings()); + } + + private assignDataToLinks() { + const nodes_by_id = {}; + this.getNodes().forEach((n: MapNode) => { + nodes_by_id[n.id] = n; + }); + + this.getLinks().forEach((link: MapLink) => { + const source_id = link.nodes[0].nodeId; + const target_id = link.nodes[1].nodeId; + if (source_id in nodes_by_id) { + link.source = nodes_by_id[source_id]; + } + if (target_id in nodes_by_id) { + link.target = nodes_by_id[target_id]; + } + + if (link.source && link.target) { + link.x = link.source.x + (link.target.x - link.source.x) * 0.5; + link.y = link.source.y + (link.target.y - link.source.y) * 0.5; + } + }); + } + +} \ No newline at end of file diff --git a/src/app/cartography/managers/layers-manager.spec.ts b/src/app/cartography/managers/layers-manager.spec.ts index a9f709e9..5a1571a1 100644 --- a/src/app/cartography/managers/layers-manager.spec.ts +++ b/src/app/cartography/managers/layers-manager.spec.ts @@ -1,7 +1,7 @@ import { LayersManager } from "./layers-manager"; -import { Node } from "../models/node"; -import { Drawing } from "../models/drawing"; -import { Link } from "../../models/link"; +import { MapDrawing } from "../models/map/map-drawing"; +import { MapLink } from "../models/map/map-link"; +import { MapNode } from "../models/map/map-node"; describe('LayersManager', () => { @@ -12,9 +12,9 @@ describe('LayersManager', () => { }); it('nodes should be added', () => { - const node_1 = new Node(); + const node_1 = new MapNode(); node_1.z = 1; - const node_2 = new Node(); + const node_2 = new MapNode(); node_2.z = 2; manager.setNodes([node_1, node_2]); @@ -27,9 +27,9 @@ describe('LayersManager', () => { }); it('drawings should be added', () => { - const drawing_1 = new Drawing(); + const drawing_1 = new MapDrawing(); drawing_1.z = 1; - const drawing_2 = new Drawing(); + const drawing_2 = new MapDrawing(); drawing_2.z = 2; manager.setDrawings([drawing_1, drawing_2]); @@ -42,17 +42,17 @@ describe('LayersManager', () => { }); it('links should be added', () => { - const node_1 = new Node(); + const node_1 = new MapNode(); node_1.z = 1; - const node_2 = new Node(); + const node_2 = new MapNode(); node_2.z = 2; - const link_1 = new Link(); + const link_1 = new MapLink(); link_1.source = node_1; link_1.target = node_1; - const link_2 = new Link(); + const link_2 = new MapLink(); link_2.source = node_1; link_2.target = node_1; @@ -64,9 +64,9 @@ describe('LayersManager', () => { }); it('layers should be cleared', () => { - const node_1 = new Node(); + const node_1 = new MapNode(); node_1.z = 1; - const node_2 = new Node(); + const node_2 = new MapNode(); node_2.z = 2; manager.setNodes([node_1, node_2]); diff --git a/src/app/cartography/managers/layers-manager.ts b/src/app/cartography/managers/layers-manager.ts index bfa86be8..773160cf 100644 --- a/src/app/cartography/managers/layers-manager.ts +++ b/src/app/cartography/managers/layers-manager.ts @@ -1,10 +1,10 @@ import { Injectable } from "@angular/core"; import { Layer } from "../models/layer"; -import { Node } from "../models/node"; -import { Drawing } from "../models/drawing"; -import { Link } from "../../models/link"; import { Dictionary } from "../models/types"; +import { MapNode } from "../models/map/map-node"; +import { MapDrawing } from "../models/map/map-drawing"; +import { MapLink } from "../models/map/map-link"; @Injectable() @@ -23,26 +23,26 @@ export class LayersManager { }); } - public setNodes(nodes: Node[]) { + public setNodes(nodes: MapNode[]) { nodes - .forEach((node: Node) => { + .forEach((node: MapNode) => { const layer = this.getLayerForKey(node.z.toString()); layer.nodes.push(node); }); } - public setDrawings(drawings: Drawing[]) { + public setDrawings(drawings: MapDrawing[]) { drawings - .forEach((drawing: Drawing) => { + .forEach((drawing: MapDrawing) => { const layer = this.getLayerForKey(drawing.z.toString()); layer.drawings.push(drawing); }); } - public setLinks(links: Link[]) { + public setLinks(links: MapLink[]) { links - .filter((link: Link) => link.source && link.target) - .forEach((link: Link) => { + .filter((link: MapLink) => link.source && link.target) + .forEach((link: MapLink) => { const key = Math.min(link.source.z, link.target.z).toString(); const layer = this.getLayerForKey(key); layer.links.push(link); diff --git a/src/app/cartography/managers/selection-manager.spec.ts b/src/app/cartography/managers/selection-manager.spec.ts index b627dd84..85746789 100644 --- a/src/app/cartography/managers/selection-manager.spec.ts +++ b/src/app/cartography/managers/selection-manager.spec.ts @@ -1,95 +1,20 @@ -import { Subject} from "rxjs"; - -import { Node } from "../models/node"; -import { Link } from "../../models/link"; -import { Drawing } from "../models/drawing"; -import { Rectangle } from "../models/rectangle"; +import { MapNode } from "../models/map/map-node"; import { SelectionManager } from "./selection-manager"; -import { NodesDataSource } from "../datasources/nodes-datasource"; -import { LinksDataSource } from "../datasources/links-datasource"; -import { InRectangleHelper } from "../helpers/in-rectangle-helper"; -import { DrawingsDataSource } from "../datasources/drawings-datasource"; describe('SelectionManager', () => { let manager: SelectionManager; - let selectedRectangleSubject: Subject; - let nodesDataSource: NodesDataSource; beforeEach(() => { - const linksDataSource = new LinksDataSource(); - const drawingsDataSource = new DrawingsDataSource(); - const inRectangleHelper = new InRectangleHelper(); - - selectedRectangleSubject = new Subject(); - - nodesDataSource = new NodesDataSource(); - - manager = new SelectionManager(nodesDataSource, linksDataSource, drawingsDataSource, inRectangleHelper); - manager.subscribe(selectedRectangleSubject); - - const node_1 = new Node(); - node_1.node_id = "test1"; - node_1.name = "Node 1"; - node_1.x = 150; - node_1.y = 150; - - nodesDataSource.add(node_1); - - const node_2 = new Node(); - node_2.node_id = "test2"; - node_2.name = "Node 2"; - node_2.x = 300; - node_2.y = 300; - nodesDataSource.add(node_2); - - const link_1 = new Link(); - link_1.link_id = "test1"; - linksDataSource.add(link_1); - }); - - it('node should be selected', () => { - selectedRectangleSubject.next(new Rectangle(100, 100, 100, 100)); - expect(nodesDataSource.getItems()[0].is_selected).toEqual(true); - expect(manager.getSelectedNodes().length).toEqual(1); - expect(manager.getSelectedLinks().length).toEqual(0); - }); - - it('node should be selected and deselected', () => { - selectedRectangleSubject.next(new Rectangle(100, 100, 100, 100)); - selectedRectangleSubject.next(new Rectangle(350, 350, 100, 100)); - expect(nodesDataSource.getItems()[0].is_selected).toEqual(false); - expect(manager.getSelectedNodes().length).toEqual(0); - expect(manager.getSelectedLinks().length).toEqual(0); + manager = new SelectionManager(); }); + it('nodes should be manually selected', () => { - const node = new Node(); - node.node_id = "test1"; - manager.setSelectedNodes([node]); - expect(manager.getSelectedNodes().length).toEqual(1); + const node = new MapNode(); + node.id = "test1"; + manager.setSelected([node]); + expect(manager.getSelected().length).toEqual(1); }); - it('links should be manually selected', () => { - const link = new Link(); - link.link_id = "test1"; - manager.setSelectedLinks([link]); - expect(manager.getSelectedLinks().length).toEqual(1); - }); - - it('items should be cleared', () => { - const link = new Link(); - link.link_id = "test1"; - const node = new Node(); - node.node_id = "test1"; - const drawing = new Drawing(); - drawing.drawing_id = "test1"; - manager.setSelectedLinks([link]); - manager.setSelectedNodes([node]); - manager.setSelectedDrawings([drawing]); - manager.clearSelection(); - expect(manager.getSelectedLinks().length).toEqual(0); - expect(manager.getSelectedDrawings().length).toEqual(0); - expect(manager.getSelectedNodes().length).toEqual(0); - }); }); diff --git a/src/app/cartography/managers/selection-manager.ts b/src/app/cartography/managers/selection-manager.ts index 5d6cdd2a..2f7b0dd3 100644 --- a/src/app/cartography/managers/selection-manager.ts +++ b/src/app/cartography/managers/selection-manager.ts @@ -1,147 +1,55 @@ -import { Injectable } from "@angular/core"; +import { Injectable, EventEmitter } from "@angular/core"; -import { Subject } from "rxjs"; -import { Subscription } from "rxjs"; +import { Indexed } from "../datasources/map-datasource"; -import { NodesDataSource } from "../datasources/nodes-datasource"; -import { LinksDataSource } from "../datasources/links-datasource"; -import { Node } from "../models/node"; -import { InRectangleHelper } from "../helpers/in-rectangle-helper"; -import { Rectangle } from "../models/rectangle"; -import { Link} from "../../models/link"; -import { DataSource } from "../datasources/datasource"; -import { Drawing } from "../models/drawing"; -import { InterfaceLabel } from "../models/interface-label"; -import { DrawingsDataSource } from "../datasources/drawings-datasource"; - - -export interface Selectable { - x: number; - y: number; - is_selected: boolean; -} @Injectable() export class SelectionManager { - private selectedNodes: Node[] = []; - private selectedLinks: Link[] = []; - private selectedDrawings: Drawing[] = []; - private selectedInterfaceLabels: InterfaceLabel[] = []; + private selection: {[id:string]: any} = {}; - private subscription: Subscription; + public selected = new EventEmitter(); + public unselected = new EventEmitter(); - constructor(private nodesDataSource: NodesDataSource, - private linksDataSource: LinksDataSource, - private drawingsDataSource: DrawingsDataSource, - private inRectangleHelper: InRectangleHelper) {} + public setSelected(items: Indexed[]) { + const dictItems = this.convertToKeyDict(items); + const selected = Object.keys(dictItems).filter((key) => { + return !this.isSelectedByKey(key); + }).map(key => dictItems[key]); - public subscribe(subject: Subject) { - this.subscription = subject.subscribe((rectangle: Rectangle) => { - this.onSelection(rectangle); + const unselected = Object.keys(this.selection).filter((key) => { + return !(key in dictItems); + }).map((key) => this.selection[key]); + + this.selection = dictItems; + + this.selected.emit(selected); + this.unselected.emit(unselected); + } + + public getSelected(): Indexed[] { + return Object.keys(this.selection).map(key => this.selection[key]); + } + + public isSelected(item): boolean { + const key = this.getKey(item); + return this.isSelectedByKey(key); + } + + private isSelectedByKey(key): boolean { + return key in this.selection; + } + + private getKey(item: Indexed): string { + const type = item.constructor.name; + return `${type}-${item.id}`; + } + + private convertToKeyDict(items: Indexed[]) { + const dict = {}; + items.forEach((item) => { + dict[this.getKey(item)] = item; }); - return this.subscription; - } - - public onSelection(rectangle: Rectangle) { - this.selectedNodes = this.getSelectedItemsInRectangle(this.nodesDataSource, rectangle); - this.selectedLinks = this.getSelectedItemsInRectangle(this.linksDataSource, rectangle); - this.selectedDrawings = this.getSelectedItemsInRectangle(this.drawingsDataSource, rectangle); - // don't select interfaces for now - // this.selectedInterfaceLabels = this.getSelectedInterfaceLabelsInRectangle(rectangle); - } - - public getSelectedNodes() { - return this.selectedNodes; - } - - public getSelectedLinks() { - return this.selectedLinks; - } - - public getSelectedDrawings() { - return this.selectedDrawings; - } - - public setSelectedNodes(nodes: Node[]) { - this.selectedNodes = this.setSelectedItems(this.nodesDataSource, (node: Node) => { - return !!nodes.find((n: Node) => node.node_id === n.node_id); - }); - } - - public setSelectedLinks(links: Link[]) { - this.selectedLinks = this.setSelectedItems(this.linksDataSource, (link: Link) => { - return !!links.find((l: Link) => link.link_id === l.link_id); - }); - } - - public setSelectedDrawings(drawings: Drawing[]) { - this.selectedDrawings = this.setSelectedItems(this.drawingsDataSource, (drawing: Drawing) => { - return !!drawings.find((d: Drawing) => drawing.drawing_id === d.drawing_id); - }); - } - - public clearSelection() { - this.setSelectedDrawings([]); - this.setSelectedLinks([]); - this.setSelectedNodes([]); - } - - private getSelectedItemsInRectangle(dataSource: DataSource, rectangle: Rectangle) { - return this.setSelectedItems(dataSource, (item: T) => { - return this.inRectangleHelper.inRectangle(rectangle, item.x, item.y); - }); - } - - private getSelectedInterfaceLabelsInRectangle(rectangle: Rectangle) { - this.linksDataSource.getItems().forEach((link: Link) => { - if (!(link.source && link.target && link.nodes.length > 1)) { - return; - } - - let updated = false; - - let x = link.source.x + link.nodes[0].label.x; - let y = link.source.y + link.nodes[0].label.y; - - if (this.inRectangleHelper.inRectangle(rectangle, x, y)) { - link.nodes[0].label.is_selected = true; - updated = true; - } - - x = link.target.x + link.nodes[1].label.x; - y = link.target.y + link.nodes[1].label.y; - - if (this.inRectangleHelper.inRectangle(rectangle, x, y)) { - link.nodes[1].label.is_selected = true; - updated = true; - } - - if (updated) { - this.linksDataSource.update(link); - } - }); - - return []; - } - - private setSelected(item: T, isSelected: boolean, dataSource: DataSource): boolean { - if (item.is_selected !== isSelected) { - item.is_selected = isSelected; - dataSource.update(item); - } - return item.is_selected; - } - - private setSelectedItems(dataSource: DataSource, discriminator: (item: T) => boolean) { - const selected: T[] = []; - dataSource.getItems().forEach((item: T) => { - const isSelected = discriminator(item); - this.setSelected(item, isSelected, dataSource); - if (isSelected) { - selected.push(item); - } - }); - return selected; + return dict; } } diff --git a/src/app/cartography/models/drawing.ts b/src/app/cartography/models/drawing.ts index a94a6ccd..e31f9c62 100644 --- a/src/app/cartography/models/drawing.ts +++ b/src/app/cartography/models/drawing.ts @@ -1,7 +1,6 @@ -import { Selectable } from "../managers/selection-manager"; import { DrawingElement } from "./drawings/drawing-element"; -export class Drawing implements Selectable { +export class Drawing { drawing_id: string; project_id: string; rotation: number; @@ -9,6 +8,5 @@ export class Drawing implements Selectable { x: number; y: number; z: number; - is_selected = false; element: DrawingElement; // @todo; move to context } diff --git a/src/app/cartography/models/graph-link.ts b/src/app/cartography/models/graph-link.ts deleted file mode 100644 index f007abcb..00000000 --- a/src/app/cartography/models/graph-link.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class GraphLink { - distance: number; // this is not from server - length: number; // this is not from server - source: Node; // this is not from server - target: Node; // this is not from server - x: number; // this is not from server - y: number; // this is not from server -} diff --git a/src/app/cartography/models/interface-label.ts b/src/app/cartography/models/interface-label.ts index 4880031b..9f2e654c 100644 --- a/src/app/cartography/models/interface-label.ts +++ b/src/app/cartography/models/interface-label.ts @@ -1,6 +1,4 @@ -import { Selectable } from "../managers/selection-manager"; - -export class InterfaceLabel implements Selectable { +export class InterfaceLabel { constructor( public link_id: string, public direction: string, @@ -9,6 +7,5 @@ export class InterfaceLabel implements Selectable { public text: string, public style: string, public rotation = 0, - public is_selected = false ) {} } diff --git a/src/app/cartography/models/label.ts b/src/app/cartography/models/label.ts index 72bef2f9..a0e63e7c 100644 --- a/src/app/cartography/models/label.ts +++ b/src/app/cartography/models/label.ts @@ -1,10 +1,7 @@ -import { Selectable } from "../managers/selection-manager"; - -export class Label implements Selectable { +export class Label { rotation: number; style: string; text: string; x: number; y: number; - is_selected: boolean; } diff --git a/src/app/cartography/models/layer.ts b/src/app/cartography/models/layer.ts index 9d32a32e..fd0cc162 100644 --- a/src/app/cartography/models/layer.ts +++ b/src/app/cartography/models/layer.ts @@ -1,13 +1,13 @@ -import {Drawing} from "./drawing"; -import {Link} from "../../models/link"; -import {Node} from "./node"; +import { MapNode } from "./map/map-node"; +import { MapDrawing } from "./map/map-drawing"; +import { MapLink } from "./map/map-link"; export class Layer { constructor( public index?: number, - public nodes: Node[] = [], - public drawings: Drawing[] = [], - public links: Link[] = [] + public nodes: MapNode[] = [], + public drawings: MapDrawing[] = [], + public links: MapLink[] = [] ) { } } diff --git a/src/app/cartography/models/map/map-drawing.ts b/src/app/cartography/models/map/map-drawing.ts new file mode 100644 index 00000000..4363165f --- /dev/null +++ b/src/app/cartography/models/map/map-drawing.ts @@ -0,0 +1,13 @@ +import { DrawingElement } from "../drawings/drawing-element"; +import { Indexed } from "../../datasources/map-datasource"; + +export class MapDrawing implements Indexed { + id: string; + projectId: string; + rotation: number; + svg: string; + x: number; + y: number; + z: number; + element: DrawingElement; // @todo; apply converters +} diff --git a/src/app/cartography/models/map/map-label.ts b/src/app/cartography/models/map/map-label.ts new file mode 100644 index 00000000..fd1ffbd0 --- /dev/null +++ b/src/app/cartography/models/map/map-label.ts @@ -0,0 +1,8 @@ +export class MapLabel { + rotation: number; + style: string; + text: string; + x: number; + y: number; + isSelected: boolean; +} diff --git a/src/app/cartography/models/map/map-link-node.ts b/src/app/cartography/models/map/map-link-node.ts new file mode 100644 index 00000000..baf81c73 --- /dev/null +++ b/src/app/cartography/models/map/map-link-node.ts @@ -0,0 +1,8 @@ +import { MapLabel } from "./map-label"; + +export class MapLinkNode { + nodeId: string; + adapterNumber: number; + portNumber: number; + label: MapLabel; +} diff --git a/src/app/cartography/models/map/map-link.ts b/src/app/cartography/models/map/map-link.ts new file mode 100644 index 00000000..97dd1c31 --- /dev/null +++ b/src/app/cartography/models/map/map-link.ts @@ -0,0 +1,22 @@ +import { MapLinkNode } from "./map-link-node"; +import { MapNode } from "./map-node"; +import { Indexed } from "../../datasources/map-datasource"; + +export class MapLink implements Indexed { + id: string; + captureFileName: string; + captureFilePath: string; + capturing: boolean; + linkType: string; + nodes: MapLinkNode[]; + projectId: string; + + distance: number; // this is not from server + length: number; // this is not from server + source: MapNode; // this is not from server + target: MapNode; // this is not from server + + isSelected = false; // this is not from server + x: number; // this is not from server + y: number; // this is not from server +} diff --git a/src/app/cartography/models/map/map-node.ts b/src/app/cartography/models/map/map-node.ts new file mode 100644 index 00000000..c9141aaa --- /dev/null +++ b/src/app/cartography/models/map/map-node.ts @@ -0,0 +1,29 @@ +import { MapLabel } from "./map-label"; +import { MapPort } from "./map-port"; +import { Indexed } from "../../datasources/map-datasource"; + +export class MapNode implements Indexed { + id: string; + commandLine: string; + computeId: string; + console: number; + consoleHost: string; + consoleType: string; + firstPortName: string; + height: number; + label: MapLabel; + name: string; + nodeDirectory: string; + nodeType: string; + portNameFormat: string; + portSegmentSize: number; + ports: MapPort[]; + projectId: string; + status: string; + symbol: string; + width: number; + x: number; + y: number; + z: number; + isSelected = false; +} diff --git a/src/app/cartography/models/map/map-port.ts b/src/app/cartography/models/map/map-port.ts new file mode 100644 index 00000000..36959fb9 --- /dev/null +++ b/src/app/cartography/models/map/map-port.ts @@ -0,0 +1,7 @@ +export class MapPort { + adapterNumber: number; + linkType: string; + name: string; + portNumber: number; + shortName: string; +} diff --git a/src/app/cartography/models/map/map-symbol.ts b/src/app/cartography/models/map/map-symbol.ts new file mode 100644 index 00000000..a766153d --- /dev/null +++ b/src/app/cartography/models/map/map-symbol.ts @@ -0,0 +1,8 @@ +import { Indexed } from "../../datasources/map-datasource"; + +export class MapSymbol implements Indexed { + id: string; + builtin: boolean; + filename: string; + raw: string; +} diff --git a/src/app/cartography/models/node.ts b/src/app/cartography/models/node.ts index 2f30da3a..160e421f 100644 --- a/src/app/cartography/models/node.ts +++ b/src/app/cartography/models/node.ts @@ -1,9 +1,8 @@ import { Label } from "./label"; import { Port } from "../../models/port"; -import { Selectable } from "../managers/selection-manager"; -export class Node implements Selectable { +export class Node { command_line: string; compute_id: string; console: number; @@ -26,5 +25,4 @@ export class Node implements Selectable { x: number; y: number; z: number; - is_selected = false; } diff --git a/src/app/cartography/models/point.ts b/src/app/cartography/models/point.ts index f83ded3b..6250d93a 100644 --- a/src/app/cartography/models/point.ts +++ b/src/app/cartography/models/point.ts @@ -3,4 +3,4 @@ export class Point { public x?: number, public y?: number ) {} -}; +} diff --git a/src/app/cartography/services/map-change-detector-ref.ts b/src/app/cartography/services/map-change-detector-ref.ts index bac6038d..2d1fbce7 100644 --- a/src/app/cartography/services/map-change-detector-ref.ts +++ b/src/app/cartography/services/map-change-detector-ref.ts @@ -3,11 +3,11 @@ import { Injectable, EventEmitter } from "@angular/core"; @Injectable() export class MapChangeDetectorRef { - public changesDetected = new EventEmitter(); + public changesDetected = new EventEmitter(); - public hasBeenDrawn = false; + public hasBeenDrawn = false; - public detectChanges() { - this.changesDetected.emit(true); - } + public detectChanges() { + this.changesDetected.emit(true); + } } diff --git a/src/app/cartography/tools/selection-tool.ts b/src/app/cartography/tools/selection-tool.ts index da78d63a..e5602dd4 100644 --- a/src/app/cartography/tools/selection-tool.ts +++ b/src/app/cartography/tools/selection-tool.ts @@ -23,14 +23,6 @@ export class SelectionTool { ) {} public setEnabled(enabled) { - if (this.enabled != enabled) { - if (enabled) { - this.needsActivate = true; - } - else { - this.needsDeactivate = true; - } - } this.enabled = enabled; } @@ -80,13 +72,17 @@ export class SelectionTool { .attr("visibility", "hidden"); } - if(this.needsActivate) { + const tool = canvas.select("g.selection-line-tool"); + const status = tool.attr('status'); + + + if(status !== 'activated' && this.enabled) { this.activate(selection); - this.needsActivate = false; + tool.attr('activated'); } - if(this.needsDeactivate) { + if(status !== 'deactivated' && !this.enabled) { this.deactivate(selection); - this.needsDeactivate = false; + tool.attr('deactivated'); } } diff --git a/src/app/cartography/widgets/drawing.ts b/src/app/cartography/widgets/drawing.ts index ca4e0fe4..14f1a104 100644 --- a/src/app/cartography/widgets/drawing.ts +++ b/src/app/cartography/widgets/drawing.ts @@ -2,13 +2,13 @@ import { Injectable } from "@angular/core"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; -import { Drawing } from "../models/drawing"; import { DrawingShapeWidget } from "./drawings/drawing-shape-widget"; import { TextDrawingWidget } from "./drawings/text-drawing"; import { ImageDrawingWidget } from "./drawings/image-drawing"; import { RectDrawingWidget } from "./drawings/rect-drawing"; import { LineDrawingWidget } from "./drawings/line-drawing"; import { EllipseDrawingWidget } from "./drawings/ellipse-drawing"; +import { MapDrawing } from "../models/map/map-drawing"; @Injectable() @@ -32,7 +32,7 @@ export class DrawingWidget implements Widget { } public draw(view: SVGSelection) { - const drawing_body = view.selectAll("g.drawing_body") + const drawing_body = view.selectAll("g.drawing_body") .data((l) => [l]); const drawing_body_enter = drawing_body.enter() @@ -40,7 +40,7 @@ export class DrawingWidget implements Widget { .attr("class", "drawing_body"); const drawing_body_merge = drawing_body.merge(drawing_body_enter) - .attr('transform', (d: Drawing) => { + .attr('transform', (d: MapDrawing) => { return `translate(${d.x},${d.y}) rotate(${d.rotation})`; }); diff --git a/src/app/cartography/widgets/drawings.ts b/src/app/cartography/widgets/drawings.ts index 3f3b0f80..d20988a9 100644 --- a/src/app/cartography/widgets/drawings.ts +++ b/src/app/cartography/widgets/drawings.ts @@ -1,17 +1,17 @@ import { Injectable } from "@angular/core"; import { Widget } from "./widget"; -import { Drawing } from "../models/drawing"; import { SVGSelection } from "../models/types"; import { Layer } from "../models/layer"; import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter"; import { Draggable } from "../events/draggable"; import { DrawingWidget } from "./drawing"; +import { MapDrawing } from "../models/map/map-drawing"; @Injectable() export class DrawingsWidget implements Widget { - public draggable = new Draggable(); + public draggable = new Draggable(); public draggingEnabled = false; // public onContextMenu = new EventEmitter(); @@ -26,15 +26,15 @@ export class DrawingsWidget implements Widget { this.svgToDrawingConverter = new SvgToDrawingConverter(); } - public redrawDrawing(view: SVGSelection, drawing: Drawing) { + public redrawDrawing(view: SVGSelection, drawing: MapDrawing) { this.drawingWidget.draw(this.selectDrawing(view, drawing)); } public draw(view: SVGSelection) { const drawing = view - .selectAll("g.drawing") + .selectAll("g.drawing") .data((layer: Layer) => { - layer.drawings.forEach((d: Drawing) => { + layer.drawings.forEach((d: MapDrawing) => { try { d.element = this.svgToDrawingConverter.convert(d.svg); } catch (error) { @@ -42,14 +42,14 @@ export class DrawingsWidget implements Widget { } }); return layer.drawings; - }, (l: Drawing) => { - return l.drawing_id; + }, (l: MapDrawing) => { + return l.id; }); const drawing_enter = drawing.enter() .append('g') .attr('class', 'drawing') - .attr('drawing_id', (l: Drawing) => l.drawing_id) + .attr('drawing_id', (l: MapDrawing) => l.id) const merge = drawing.merge(drawing_enter); @@ -64,7 +64,7 @@ export class DrawingsWidget implements Widget { } } - private selectDrawing(view: SVGSelection, drawing: Drawing) { - return view.selectAll(`g.drawing[drawing_id="${drawing.drawing_id}"]`); + private selectDrawing(view: SVGSelection, drawing: MapDrawing) { + return view.selectAll(`g.drawing[drawing_id="${drawing.id}"]`); } } diff --git a/src/app/cartography/widgets/drawings/ellipse-drawing.spec.ts b/src/app/cartography/widgets/drawings/ellipse-drawing.spec.ts index c772cbd7..1f7ec018 100644 --- a/src/app/cartography/widgets/drawings/ellipse-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/ellipse-drawing.spec.ts @@ -1,19 +1,19 @@ import { TestSVGCanvas } from "../../testing"; -import { Drawing } from "../../models/drawing"; -import { EllipseDrawingWidget } from "./ellipse-drawing"; import { EllipseElement } from "../../models/drawings/ellipse-element"; import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; +import { EllipseDrawingWidget } from "./ellipse-drawing"; describe('EllipseDrawingWidget', () => { let svg: TestSVGCanvas; let widget: EllipseDrawingWidget; - let drawing: Drawing; + let drawing: MapDrawing; beforeEach(() => { svg = new TestSVGCanvas(); - drawing = new Drawing(); + drawing = new MapDrawing(); widget = new EllipseDrawingWidget(new QtDasharrayFixer()); }); @@ -34,7 +34,7 @@ describe('EllipseDrawingWidget', () => { ellipse.ry = 40; drawing.element = ellipse; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); diff --git a/src/app/cartography/widgets/drawings/ellipse-drawing.ts b/src/app/cartography/widgets/drawings/ellipse-drawing.ts index 576578f2..2e2aedf3 100644 --- a/src/app/cartography/widgets/drawings/ellipse-drawing.ts +++ b/src/app/cartography/widgets/drawings/ellipse-drawing.ts @@ -1,10 +1,10 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../../models/types"; -import { Drawing } from "../../models/drawing"; import { EllipseElement } from "../../models/drawings/ellipse-element"; import { DrawingShapeWidget } from "./drawing-shape-widget"; import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; @Injectable() @@ -17,7 +17,7 @@ export class EllipseDrawingWidget implements DrawingShapeWidget { public draw(view: SVGSelection) { const drawing = view .selectAll('ellipse.ellipse_element') - .data((d: Drawing) => { + .data((d: MapDrawing) => { return (d.element && d.element instanceof EllipseElement) ? [d.element] : []; }); diff --git a/src/app/cartography/widgets/drawings/image-drawing.spec.ts b/src/app/cartography/widgets/drawings/image-drawing.spec.ts index a722ff23..7fc19d2c 100644 --- a/src/app/cartography/widgets/drawings/image-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/image-drawing.spec.ts @@ -1,18 +1,18 @@ import { TestSVGCanvas } from "../../testing"; -import { Drawing } from "../../models/drawing"; import { ImageDrawingWidget } from "./image-drawing"; import { ImageElement } from "../../models/drawings/image-element"; +import { MapDrawing } from "../../models/map/map-drawing"; describe('ImageDrawingWidget', () => { let svg: TestSVGCanvas; let widget: ImageDrawingWidget; - let drawing: Drawing; + let drawing: MapDrawing; beforeEach(() => { svg = new TestSVGCanvas(); - drawing = new Drawing(); + drawing = new MapDrawing(); widget = new ImageDrawingWidget(); }); @@ -27,7 +27,7 @@ describe('ImageDrawingWidget', () => { image.data = "data:image/svg+xml;base64,DATA"; drawing.element = image; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); diff --git a/src/app/cartography/widgets/drawings/image-drawing.ts b/src/app/cartography/widgets/drawings/image-drawing.ts index 1dcf50ed..6d1a8a2c 100644 --- a/src/app/cartography/widgets/drawings/image-drawing.ts +++ b/src/app/cartography/widgets/drawings/image-drawing.ts @@ -1,9 +1,9 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../../models/types"; -import { Drawing } from "../../models/drawing"; import { ImageElement } from "../../models/drawings/image-element"; import { DrawingShapeWidget } from "./drawing-shape-widget"; +import { MapDrawing } from "../../models/map/map-drawing"; @Injectable() @@ -11,7 +11,7 @@ export class ImageDrawingWidget implements DrawingShapeWidget { public draw(view: SVGSelection) { const drawing = view .selectAll('image.image_element') - .data((d: Drawing) => { + .data((d: MapDrawing) => { return (d.element && d.element instanceof ImageElement) ? [d.element] : []; }); diff --git a/src/app/cartography/widgets/drawings/line-drawing.spec.ts b/src/app/cartography/widgets/drawings/line-drawing.spec.ts index 06dfc7d5..c4830190 100644 --- a/src/app/cartography/widgets/drawings/line-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/line-drawing.spec.ts @@ -1,19 +1,19 @@ import { TestSVGCanvas } from "../../testing"; -import { Drawing } from "../../models/drawing"; import { LineDrawingWidget } from "./line-drawing"; import { LineElement } from "../../models/drawings/line-element"; import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; describe('LineDrawingWidget', () => { let svg: TestSVGCanvas; let widget: LineDrawingWidget; - let drawing: Drawing; + let drawing: MapDrawing; beforeEach(() => { svg = new TestSVGCanvas(); - drawing = new Drawing(); + drawing = new MapDrawing(); widget = new LineDrawingWidget(new QtDasharrayFixer()); }); @@ -32,7 +32,7 @@ describe('LineDrawingWidget', () => { line.y2 = 40; drawing.element = line; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); diff --git a/src/app/cartography/widgets/drawings/line-drawing.ts b/src/app/cartography/widgets/drawings/line-drawing.ts index e3816f50..729b1be2 100644 --- a/src/app/cartography/widgets/drawings/line-drawing.ts +++ b/src/app/cartography/widgets/drawings/line-drawing.ts @@ -1,10 +1,10 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../../models/types"; -import { Drawing } from "../../models/drawing"; import { LineElement } from "../../models/drawings/line-element"; import { DrawingShapeWidget } from "./drawing-shape-widget"; import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; @Injectable() @@ -17,7 +17,7 @@ export class LineDrawingWidget implements DrawingShapeWidget { public draw(view: SVGSelection) { const drawing = view .selectAll('line.line_element') - .data((d: Drawing) => { + .data((d: MapDrawing) => { return (d.element && d.element instanceof LineElement) ? [d.element] : []; }); diff --git a/src/app/cartography/widgets/drawings/rect-drawing.spec.ts b/src/app/cartography/widgets/drawings/rect-drawing.spec.ts index 1b66d364..f90d02ad 100644 --- a/src/app/cartography/widgets/drawings/rect-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/rect-drawing.spec.ts @@ -1,19 +1,19 @@ import { TestSVGCanvas } from "../../testing"; -import { Drawing } from "../../models/drawing"; import { RectDrawingWidget } from "./rect-drawing"; import { RectElement } from "../../models/drawings/rect-element"; import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; describe('RectDrawingWidget', () => { let svg: TestSVGCanvas; let widget: RectDrawingWidget; - let drawing: Drawing; + let drawing: MapDrawing; beforeEach(() => { svg = new TestSVGCanvas(); - drawing = new Drawing(); + drawing = new MapDrawing(); widget = new RectDrawingWidget(new QtDasharrayFixer()); }); @@ -32,7 +32,7 @@ describe('RectDrawingWidget', () => { rect.height = 200; drawing.element = rect; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); diff --git a/src/app/cartography/widgets/drawings/rect-drawing.ts b/src/app/cartography/widgets/drawings/rect-drawing.ts index f3c768b9..ba909184 100644 --- a/src/app/cartography/widgets/drawings/rect-drawing.ts +++ b/src/app/cartography/widgets/drawings/rect-drawing.ts @@ -1,10 +1,10 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../../models/types"; -import { Drawing } from "../../models/drawing"; import { RectElement } from "../../models/drawings/rect-element"; import { DrawingShapeWidget } from "./drawing-shape-widget"; import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; @Injectable() @@ -16,7 +16,7 @@ export class RectDrawingWidget implements DrawingShapeWidget { public draw(view: SVGSelection) { const drawing = view .selectAll('rect.rect_element') - .data((d: Drawing) => { + .data((d: MapDrawing) => { return (d.element && d.element instanceof RectElement) ? [d.element] : []; }); diff --git a/src/app/cartography/widgets/drawings/text-drawing.spec.ts b/src/app/cartography/widgets/drawings/text-drawing.spec.ts index 7d497bd3..1d75f237 100644 --- a/src/app/cartography/widgets/drawings/text-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/text-drawing.spec.ts @@ -1,18 +1,18 @@ import { TestSVGCanvas } from "../../testing"; import { TextDrawingWidget } from "./text-drawing"; -import { Drawing } from "../../models/drawing"; import { TextElement } from "../../models/drawings/text-element"; import { FontFixer } from "../../helpers/font-fixer"; +import { MapDrawing } from "../../models/map/map-drawing"; describe('TextDrawingWidget', () => { let svg: TestSVGCanvas; let widget: TextDrawingWidget; - let drawing: Drawing; + let drawing: MapDrawing; beforeEach(() => { svg = new TestSVGCanvas(); - drawing = new Drawing(); + drawing = new MapDrawing(); widget = new TextDrawingWidget(new FontFixer()); }); @@ -32,7 +32,7 @@ describe('TextDrawingWidget', () => { drawing.element = text; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); @@ -52,7 +52,7 @@ describe('TextDrawingWidget', () => { text.text = 'THIS' + "\n" + 'IS TEXT'; drawing.element = text; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); @@ -75,7 +75,7 @@ describe('TextDrawingWidget', () => { text.text = ' Text with whitespaces'; drawing.element = text; - const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); const drawings_enter = drawings.enter().append('g').classed('drawing', true); const drawings_merge = drawings.merge(drawings_enter); diff --git a/src/app/cartography/widgets/drawings/text-drawing.ts b/src/app/cartography/widgets/drawings/text-drawing.ts index 3522fb61..49f79cf7 100644 --- a/src/app/cartography/widgets/drawings/text-drawing.ts +++ b/src/app/cartography/widgets/drawings/text-drawing.ts @@ -2,10 +2,10 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../../models/types"; import { TextElement } from "../../models/drawings/text-element"; -import { Drawing } from "../../models/drawing"; import { DrawingShapeWidget } from "./drawing-shape-widget"; import { FontFixer } from "../../helpers/font-fixer"; import { select } from "d3-selection"; +import { MapDrawing } from "../../models/map/map-drawing"; @Injectable() @@ -20,7 +20,7 @@ export class TextDrawingWidget implements DrawingShapeWidget { const drawing = view .selectAll('text.text_element') - .data((d: Drawing) => { + .data((d: MapDrawing) => { return (d.element && d.element instanceof TextElement) ? [d.element] : []; }); diff --git a/src/app/cartography/widgets/graph-layout.ts b/src/app/cartography/widgets/graph-layout.ts index 233311bb..a559853a 100644 --- a/src/app/cartography/widgets/graph-layout.ts +++ b/src/app/cartography/widgets/graph-layout.ts @@ -1,10 +1,7 @@ import { Context } from "../models/context"; -import { Node } from "../models/node"; -import { Link } from "../../models/link"; import { NodesWidget } from "./nodes"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; -import { Drawing } from "../models/drawing"; import { DrawingLineWidget } from "./drawing-line"; import { SelectionTool } from "../tools/selection-tool"; import { MovingTool } from "../tools/moving-tool"; @@ -15,35 +12,16 @@ import { Injectable } from "@angular/core"; @Injectable() export class GraphLayout implements Widget { - private nodes: Node[] = []; - private links: Link[] = []; - private drawings: Drawing[] = []; - constructor( private nodesWidget: NodesWidget, private drawingLineTool: DrawingLineWidget, private selectionTool: SelectionTool, private movingTool: MovingTool, - private layersWidget: LayersWidget + private layersWidget: LayersWidget, + private layersManager: LayersManager ) { } - public setNodes(nodes: Node[]) { - this.nodes = nodes; - } - - public setLinks(links: Link[]) { - this.links = links; - } - - public getLinks() { - return this.links; - } - - public setDrawings(drawings: Drawing[]) { - this.drawings = drawings; - } - public getNodesWidget() { return this.nodesWidget; } @@ -84,13 +62,7 @@ export class GraphLayout implements Widget { return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`; }); - // @fix me - const layersManager = new LayersManager(); - layersManager.setNodes(this.nodes); - layersManager.setDrawings(this.drawings); - layersManager.setLinks(this.links); - - this.layersWidget.draw(canvas, layersManager.getLayersList()); + this.layersWidget.draw(canvas, this.layersManager.getLayersList()); this.drawingLineTool.draw(view, context); this.selectionTool.draw(view, context); diff --git a/src/app/cartography/widgets/interface-label.spec.ts b/src/app/cartography/widgets/interface-label.spec.ts index e5b2aac2..c007d4a5 100644 --- a/src/app/cartography/widgets/interface-label.spec.ts +++ b/src/app/cartography/widgets/interface-label.spec.ts @@ -1,61 +1,61 @@ import { Selection } from "d3-selection"; import { TestSVGCanvas } from "../testing"; -import { Node } from "../models/node"; -import { Link } from "../../models/link"; -import { LinkNode } from "../../models/link-node"; -import { Label } from "../models/label"; import { InterfaceLabel } from "../models/interface-label"; import { InterfaceLabelWidget } from "./interface-label"; import { CssFixer } from "../helpers/css-fixer"; +import { MapNode } from "../models/map/map-node"; +import { MapLink } from "../models/map/map-link"; +import { MapLinkNode } from "../models/map/map-link-node"; +import { MapLabel } from "../models/map/map-label"; describe('InterfaceLabelsWidget', () => { let svg: TestSVGCanvas; let widget: InterfaceLabelWidget; - let linksEnter: Selection; - let links: Link[]; + let linksEnter: Selection; + let links: MapLink[]; beforeEach(() => { svg = new TestSVGCanvas(); - const node_1 = new Node(); - node_1.node_id = "1"; + const node_1 = new MapNode(); + node_1.id = "1"; node_1.x = 100; node_1.y = 200; - const node_2 = new Node(); - node_2.node_id = "2"; + const node_2 = new MapNode(); + node_2.id = "2"; node_2.x = 300; node_2.y = 400; - const link_node_1 = new LinkNode(); - link_node_1.label = new Label(); + const link_node_1 = new MapLinkNode(); + link_node_1.label = new MapLabel(); link_node_1.label.rotation = 5; link_node_1.label.text = "Interface 1"; link_node_1.label.x = 10; link_node_1.label.y = 20; link_node_1.label.style = "font-size: 12px"; - const link_node_2 = new LinkNode(); - link_node_2.label = new Label(); + const link_node_2 = new MapLinkNode(); + link_node_2.label = new MapLabel(); link_node_2.label.rotation = 0; link_node_2.label.text = "Interface 2"; link_node_2.label.x = -30; link_node_2.label.y = -40; link_node_2.label.style = ""; - const link_1 = new Link(); - link_1.link_id = "link1"; + const link_1 = new MapLink(); + link_1.id = "link1"; link_1.source = node_1; link_1.target = node_2; link_1.nodes = [link_node_1, link_node_2]; - link_1.link_type = "ethernet"; + link_1.linkType = "ethernet"; links = [link_1]; const linksSelection = svg.canvas - .selectAll('g.link') + .selectAll('g.link') .data(links); linksEnter = linksSelection @@ -110,14 +110,14 @@ describe('InterfaceLabelsWidget', () => { expect(drew.nodes().length).toEqual(0); }); - it('should draw interface label with class `selected` when selected', () => { - links[0].nodes[0].label.is_selected = true; + // it('should draw interface label with class `selected` when selected', () => { + // links[0].nodes[0].label.isSelected = true; - widget.draw(linksEnter); + // widget.draw(linksEnter); - const drew = svg.canvas.selectAll('g.interface_label_container'); - const sourceInterface = drew.nodes()[0]; - expect(sourceInterface.getAttribute('class')).toContain('selected'); - }); + // const drew = svg.canvas.selectAll('g.interface_label_container'); + // const sourceInterface = drew.nodes()[0]; + // expect(sourceInterface.getAttribute('class')).toContain('selected'); + // }); }); diff --git a/src/app/cartography/widgets/interface-label.ts b/src/app/cartography/widgets/interface-label.ts index b40d6fa2..4a81ce30 100644 --- a/src/app/cartography/widgets/interface-label.ts +++ b/src/app/cartography/widgets/interface-label.ts @@ -1,10 +1,10 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../models/types"; -import { Link } from "../../models/link"; import { InterfaceLabel } from "../models/interface-label"; import { CssFixer } from "../helpers/css-fixer"; import { select } from "d3-selection"; +import { MapLink } from "../models/map/map-link"; @Injectable() export class InterfaceLabelWidget { @@ -25,27 +25,25 @@ export class InterfaceLabelWidget { const labels = selection .selectAll('g.interface_label_container') - .data((l: Link) => { + .data((l: MapLink) => { const sourceInterface = new InterfaceLabel( - l.link_id, + l.id, 'source', Math.round(l.source.x + l.nodes[0].label.x), Math.round(l.source.y + l.nodes[0].label.y), l.nodes[0].label.text, l.nodes[0].label.style, - l.nodes[0].label.rotation, - l.nodes[0].label.is_selected + l.nodes[0].label.rotation ); const targetInterface = new InterfaceLabel( - l.link_id, + l.id, 'target', Math.round( l.target.x + l.nodes[1].label.x), Math.round( l.target.y + l.nodes[1].label.y), l.nodes[1].label.text, l.nodes[1].label.style, - l.nodes[1].label.rotation, - l.nodes[1].label.is_selected + l.nodes[1].label.rotation ); if (this.enabled) { @@ -81,7 +79,7 @@ export class InterfaceLabelWidget { const y = l.y + bbox.height; return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`; }) - .classed('selected', (l: InterfaceLabel) => l.is_selected); + .classed('selected', (l: InterfaceLabel) => false); // update label merge @@ -95,7 +93,7 @@ export class InterfaceLabelWidget { // update surrounding rect merge .select('rect.interface_label_border') - .attr('visibility', (l: InterfaceLabel) => l.is_selected ? 'visible' : 'hidden') + .attr('visibility', (l: InterfaceLabel) => false ? 'visible' : 'hidden') .attr('stroke-dasharray', '3,3') .attr('stroke-width', '0.5') .each(function (this: SVGRectElement, l: InterfaceLabel) { @@ -106,7 +104,7 @@ export class InterfaceLabelWidget { const border = InterfaceLabelWidget.SURROUNDING_TEXT_BORDER; - current.attr('width', bbox.width + border*2); + current.attr('width', bbox.width + border * 2); current.attr('height', bbox.height + border); current.attr('x', - border); current.attr('y', - bbox.height); diff --git a/src/app/cartography/widgets/interface-status.ts b/src/app/cartography/widgets/interface-status.ts index ad4a13de..a38bcebd 100644 --- a/src/app/cartography/widgets/interface-status.ts +++ b/src/app/cartography/widgets/interface-status.ts @@ -4,8 +4,8 @@ import { select } from "d3-selection"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; -import { Link } from "../../models/link"; import { LinkStatus } from "../models/link-status"; +import { MapLink } from "../models/map/map-link"; @Injectable() @@ -13,8 +13,8 @@ export class InterfaceStatusWidget implements Widget { constructor() {} public draw(view: SVGSelection) { - view.each(function (this: SVGGElement, l: Link) { - const link_group = select(this); + view.each(function (this: SVGGElement, l: MapLink) { + const link_group = select(this); const link_path = link_group.select('path'); const start_point: SVGPoint = link_path.node().getPointAtLength(45); diff --git a/src/app/cartography/widgets/link.ts b/src/app/cartography/widgets/link.ts index 1b3dc965..b7557fbd 100644 --- a/src/app/cartography/widgets/link.ts +++ b/src/app/cartography/widgets/link.ts @@ -2,12 +2,13 @@ import { Injectable } from "@angular/core"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; -import { Link } from "../../models/link"; import { SerialLinkWidget } from "./links/serial-link"; import { EthernetLinkWidget } from "./links/ethernet-link"; import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper"; import { InterfaceLabelWidget } from "./interface-label"; import { InterfaceStatusWidget } from "./interface-status"; +import { MapLink } from "../models/map/map-link"; +import { SelectionManager } from "../managers/selection-manager"; @Injectable() @@ -16,11 +17,12 @@ export class LinkWidget implements Widget { constructor( private multiLinkCalculatorHelper: MultiLinkCalculatorHelper, private interfaceLabelWidget: InterfaceLabelWidget, - private interfaceStatusWidget: InterfaceStatusWidget + private interfaceStatusWidget: InterfaceStatusWidget, + private selectionManager: SelectionManager ) {} public draw(view: SVGSelection) { - const link_body = view.selectAll("g.link_body") + const link_body = view.selectAll("g.link_body") .data((l) => [l]); const link_body_enter = link_body.enter() @@ -41,7 +43,7 @@ export class LinkWidget implements Widget { link_body_merge .select('path') - .classed('selected', (l: Link) => l.is_selected); + .classed('selected', (l: MapLink) => this.selectionManager.isSelected(l)); this.interfaceLabelWidget.draw(link_body_merge); this.interfaceStatusWidget.draw(link_body_merge); diff --git a/src/app/cartography/widgets/links.spec.ts b/src/app/cartography/widgets/links.spec.ts index 705daba3..08ce74ce 100644 --- a/src/app/cartography/widgets/links.spec.ts +++ b/src/app/cartography/widgets/links.spec.ts @@ -5,10 +5,10 @@ import { Selection } from "d3-selection"; import { TestSVGCanvas } from "../testing"; import { Layer } from "../models/layer"; import { LinksWidget } from "./links"; -import { Node } from "../models/node"; -import { Link } from "../../models/link"; import { LinkWidget } from "./link"; import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper"; +import { MapNode } from "../models/map/map-node"; +import { MapLink } from "../models/map/map-link"; describe('LinksWidget', () => { @@ -23,21 +23,21 @@ describe('LinksWidget', () => { linkWidget = instance(mock(LinkWidget)); widget = new LinksWidget(new MultiLinkCalculatorHelper(), linkWidget); - const node_1 = new Node(); - node_1.node_id = "1"; + const node_1 = new MapNode(); + node_1.id = "1"; node_1.x = 10; node_1.y = 10; - const node_2 = new Node(); - node_2.node_id = "2"; + const node_2 = new MapNode(); + node_2.id = "2"; node_2.x = 100; node_2.y = 100; - const link_1 = new Link(); - link_1.link_id = "link1"; + const link_1 = new MapLink(); + link_1.id = "link1"; link_1.source = node_1; link_1.target = node_2; - link_1.link_type = "ethernet"; + link_1.linkType = "ethernet"; layer = new Layer(); layer.index = 1; @@ -67,7 +67,7 @@ describe('LinksWidget', () => { it('should draw links', () => { widget.draw(layersEnter); - const drew = svg.canvas.selectAll('g.link'); + const drew = svg.canvas.selectAll('g.link'); const linkNode = drew.nodes()[0]; expect(linkNode.getAttribute('link_id')).toEqual('link1'); expect(linkNode.getAttribute('map-source')).toEqual('1'); diff --git a/src/app/cartography/widgets/links.ts b/src/app/cartography/widgets/links.ts index e96e9baf..0544dcac 100644 --- a/src/app/cartography/widgets/links.ts +++ b/src/app/cartography/widgets/links.ts @@ -2,10 +2,10 @@ import { Injectable } from "@angular/core"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; -import { Link } from "../../models/link"; import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper"; import { Layer } from "../models/layer"; import { LinkWidget } from "./link"; +import { MapLink } from "../models/map/map-link"; @Injectable() export class LinksWidget implements Widget { @@ -15,32 +15,32 @@ export class LinksWidget implements Widget { ) { } - public redrawLink(view: SVGSelection, link: Link) { + public redrawLink(view: SVGSelection, link: MapLink) { this.linkWidget.draw(this.selectLink(view, link)); } public draw(view: SVGSelection) { const link = view - .selectAll("g.link") + .selectAll("g.link") .data((layer: Layer) => { if (layer.links) { - const layer_links = layer.links.filter((l: Link) => { + const layer_links = layer.links.filter((l: MapLink) => { return l.target && l.source; }); this.multiLinkCalculatorHelper.assignDataToLinks(layer_links); return layer_links; } return []; - }, (l: Link) => { - return l.link_id; + }, (l: MapLink) => { + return l.id; }); const link_enter = link.enter() .append('g') .attr('class', 'link') - .attr('link_id', (l: Link) => l.link_id) - .attr('map-source', (l: Link) => l.source.node_id) - .attr('map-target', (l: Link) => l.target.node_id); + .attr('link_id', (l: MapLink) => l.id) + .attr('map-source', (l: MapLink) => l.source.id) + .attr('map-target', (l: MapLink) => l.target.id); const merge = link.merge(link_enter); @@ -51,7 +51,7 @@ export class LinksWidget implements Widget { .remove(); } - private selectLink(view: SVGSelection, link: Link) { - return view.selectAll(`g.link[link_id="${link.link_id}"]`); + private selectLink(view: SVGSelection, link: MapLink) { + return view.selectAll(`g.link[link_id="${link.id}"]`); } } diff --git a/src/app/cartography/widgets/links/ethernet-link.ts b/src/app/cartography/widgets/links/ethernet-link.ts index 162c3a52..d8a3b977 100644 --- a/src/app/cartography/widgets/links/ethernet-link.ts +++ b/src/app/cartography/widgets/links/ethernet-link.ts @@ -2,7 +2,7 @@ import { path } from "d3-path"; import { Widget } from "../widget"; import { SVGSelection } from "../../models/types"; -import { Link } from "../../../models/link"; +import { MapLink } from "../../models/map/map-link"; class EthernetLinkPath { constructor( @@ -13,7 +13,7 @@ class EthernetLinkPath { } export class EthernetLinkWidget implements Widget { - private linktoEthernetLink(link: Link) { + private linktoEthernetLink(link: MapLink) { return new EthernetLinkPath( [link.source.x + link.source.width / 2., link.source.y + link.source.height / 2.], [link.target.x + link.target.width / 2., link.target.y + link.target.height / 2.] @@ -24,9 +24,9 @@ export class EthernetLinkWidget implements Widget { const link = view .selectAll('path.ethernet_link') - .data((link) => { - if(link.link_type === 'ethernet') { - return [this.linktoEthernetLink(link)]; + .data((l) => { + if (l.linkType === 'ethernet') { + return [this.linktoEthernetLink(l)]; } return []; }); diff --git a/src/app/cartography/widgets/links/serial-link.ts b/src/app/cartography/widgets/links/serial-link.ts index 7f108043..f684d4ae 100644 --- a/src/app/cartography/widgets/links/serial-link.ts +++ b/src/app/cartography/widgets/links/serial-link.ts @@ -2,7 +2,7 @@ import { path } from "d3-path"; import { Widget } from "../widget"; import { SVGSelection } from "../../models/types"; -import { Link } from "../../../models/link"; +import { MapLink } from "../../models/map/map-link"; class SerialLinkPath { @@ -18,7 +18,7 @@ class SerialLinkPath { export class SerialLinkWidget implements Widget { - private linkToSerialLink(link: Link) { + private linkToSerialLink(link: MapLink) { const source = { 'x': link.source.x + link.source.width / 2, 'y': link.source.y + link.source.height / 2 @@ -60,9 +60,9 @@ export class SerialLinkWidget implements Widget { const link = view .selectAll('path.serial_link') - .data((link) => { - if(link.link_type === 'serial') { - return [this.linkToSerialLink(link)]; + .data((l) => { + if (l.linkType === 'serial') { + return [this.linkToSerialLink(l)]; } return []; }); diff --git a/src/app/cartography/widgets/node.spec.ts b/src/app/cartography/widgets/node.spec.ts index 49edd7b2..5f2dfa2e 100644 --- a/src/app/cartography/widgets/node.spec.ts +++ b/src/app/cartography/widgets/node.spec.ts @@ -1,19 +1,30 @@ import { TestSVGCanvas } from "../testing"; -import { Node } from "../models/node"; -import { Label } from "../models/label"; import { CssFixer } from "../helpers/css-fixer"; import { FontFixer } from "../helpers/font-fixer"; import { NodeWidget } from "./node"; +import { MapNode } from "../models/map/map-node"; +import { MapLabel } from "../models/map/map-label"; +import { MockedGraphDataManager } from "../managers/graph-data-manager.spec"; +import { GraphDataManager } from "../managers/graph-data-manager"; +import { SelectionManager } from "../managers/selection-manager"; describe('NodesWidget', () => { let svg: TestSVGCanvas; let widget: NodeWidget; + let graphData: MockedGraphDataManager; beforeEach(() => { svg = new TestSVGCanvas(); - widget = new NodeWidget(new CssFixer(), new FontFixer()); + graphData = new MockedGraphDataManager(); + + // widget = new NodeWidget( + // new CssFixer(), + // new FontFixer(), + // graphData, + // new SelectionManager() + // ); }); afterEach(() => { @@ -21,9 +32,9 @@ describe('NodesWidget', () => { }); describe('draggable behaviour', () => { - let node: Node; + let node: MapNode; const tryToDrag = () => { - const drew = svg.canvas.selectAll('g.node'); + const drew = svg.canvas.selectAll('g.node'); const drewNode = drew.nodes()[0]; drewNode.dispatchEvent( @@ -38,12 +49,12 @@ describe('NodesWidget', () => { }; beforeEach(() => { - node = new Node(); + node = new MapNode(); node.x = 100; node.y = 200; node.width = 100; node.height = 100; - node.label = new Label(); + node.label = new MapLabel(); }); // it('should be draggable when enabled', () => { diff --git a/src/app/cartography/widgets/node.ts b/src/app/cartography/widgets/node.ts index e879786e..20377d22 100644 --- a/src/app/cartography/widgets/node.ts +++ b/src/app/cartography/widgets/node.ts @@ -2,13 +2,14 @@ import { Injectable, EventEmitter } from "@angular/core"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; -import { Node } from "../models/node"; import { NodeContextMenu, NodeClicked, NodeDragged, NodeDragging } from "../events/nodes"; import { CssFixer } from "../helpers/css-fixer"; import { FontFixer } from "../helpers/font-fixer"; import { select, event } from "d3-selection"; -import { Symbol } from "../../models/symbol"; -import { D3DragEvent, drag } from "d3-drag"; +import { MapSymbol } from "../models/map/map-symbol"; +import { MapNode } from "../models/map/map-node"; +import { GraphDataManager } from "../managers/graph-data-manager"; +import { SelectionManager } from "../managers/selection-manager"; @Injectable() @@ -20,21 +21,17 @@ export class NodeWidget implements Widget { public onNodeDragged = new EventEmitter(); public onNodeDragging = new EventEmitter(); - private symbols: Symbol[] = []; - constructor( private cssFixer: CssFixer, private fontFixer: FontFixer, + private graphDataManager: GraphDataManager, + private selectionManager: SelectionManager ) {} - public setSymbols(symbols: Symbol[]) { - this.symbols = symbols; - } - public draw(view: SVGSelection) { const self = this; - const node_body = view.selectAll("g.node_body") + const node_body = view.selectAll("g.node_body") .data((n) => [n]); const node_body_enter = node_body.enter() @@ -50,52 +47,52 @@ export class NodeWidget implements Widget { .attr('class', 'label'); const node_body_merge = node_body.merge(node_body_enter) - .classed('selected', (n: Node) => n.is_selected) - .on("contextmenu", function (n: Node, i: number) { + .classed('selected', (n: MapNode) => this.selectionManager.isSelected(n)) + .on("contextmenu", function (n: MapNode, i: number) { event.preventDefault(); self.onContextMenu.emit(new NodeContextMenu(event, n)); }) - .on('click', (n: Node) => { + .on('click', (n: MapNode) => { this.onNodeClicked.emit(new NodeClicked(event, n)); }); // update image of node node_body_merge .select('image') - .attr('xnode:href', (n: Node) => { - const symbol = this.symbols.find((s: Symbol) => s.symbol_id === n.symbol); + .attr('xnode:href', (n: MapNode) => { + const symbol = this.graphDataManager.getSymbols().find((s: MapSymbol) => s.id === n.symbol); if (symbol) { return 'data:image/svg+xml;base64,' + btoa(symbol.raw); } // @todo; we need to have default image return 'data:image/svg+xml;base64,none'; }) - .attr('width', (n: Node) => n.width) - .attr('height', (n: Node) => n.height) - .attr('x', (n: Node) => 0) - .attr('y', (n: Node) => 0) - .on('mouseover', function (this, n: Node) { + .attr('width', (n: MapNode) => n.width) + .attr('height', (n: MapNode) => n.height) + .attr('x', (n: MapNode) => 0) + .attr('y', (n: MapNode) => 0) + .on('mouseover', function (this, n: MapNode) { select(this).attr("class", "over"); }) - .on('mouseout', function (this, n: Node) { + .on('mouseout', function (this, n: MapNode) { select(this).attr("class", ""); }); node_body_merge - .attr('transform', (n: Node) => { + .attr('transform', (n: MapNode) => { return `translate(${n.x},${n.y})`; }); node_body_merge .select('text.label') // .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way - .attr('style', (n: Node) => { + .attr('style', (n: MapNode) => { let styles = this.cssFixer.fix(n.label.style); styles = this.fontFixer.fixStyles(styles); return styles; }) - .text((n: Node) => n.label.text) - .attr('x', function (this: SVGTextElement, n: Node) { + .text((n: MapNode) => n.label.text) + .attr('x', function (this: SVGTextElement, n: MapNode) { if (n.label.x === null) { // center const bbox = this.getBBox(); @@ -103,7 +100,7 @@ export class NodeWidget implements Widget { } return n.label.x + NodeWidget.NODE_LABEL_MARGIN; }) - .attr('y', function (this: SVGTextElement, n: Node) { + .attr('y', function (this: SVGTextElement, n: MapNode) { let bbox = this.getBBox(); if (n.label.x === null) { diff --git a/src/app/cartography/widgets/nodes.ts b/src/app/cartography/widgets/nodes.ts index 60fed63b..63496c93 100644 --- a/src/app/cartography/widgets/nodes.ts +++ b/src/app/cartography/widgets/nodes.ts @@ -1,18 +1,18 @@ import { Injectable } from "@angular/core"; import { Widget } from "./widget"; -import { Node } from "../models/node"; import { SVGSelection } from "../models/types"; import { Layer } from "../models/layer"; import { NodeWidget } from "./node"; import { Draggable } from "../events/draggable"; +import { MapNode } from "../models/map/map-node"; @Injectable() export class NodesWidget implements Widget { static NODE_LABEL_MARGIN = 3; - public draggable = new Draggable(); + public draggable = new Draggable(); public draggingEnabled = false; constructor( @@ -20,29 +20,29 @@ export class NodesWidget implements Widget { ) { } - public redrawNode(view: SVGSelection, node: Node) { + public redrawNode(view: SVGSelection, node: MapNode) { this.nodeWidget.draw(this.selectNode(view, node)); } public draw(view: SVGSelection) { const node = view - .selectAll("g.node") + .selectAll("g.node") .data((layer: Layer) => { if (layer.nodes) { return layer.nodes; } return []; - }, (n: Node) => { - return n.node_id; + }, (n: MapNode) => { + return n.id; }); const node_enter = node.enter() .append('g') .attr('class', 'node') - .attr('node_id', (n: Node) => n.node_id) + .attr('node_id', (n: MapNode) => n.id) const merge = node.merge(node_enter); - + this.nodeWidget.draw(merge); node @@ -54,8 +54,8 @@ export class NodesWidget implements Widget { } } - private selectNode(view: SVGSelection, node: Node) { - return view.selectAll(`g.node[node_id="${node.node_id}"]`); + private selectNode(view: SVGSelection, node: MapNode) { + return view.selectAll(`g.node[node_id="${node.id}"]`); } } diff --git a/src/app/components/project-map/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts b/src/app/components/project-map/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts index 564405e1..5e51e937 100644 --- a/src/app/components/project-map/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts +++ b/src/app/components/project-map/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component.ts @@ -21,6 +21,7 @@ export class MoveLayerUpActionComponent implements OnInit { moveLayerUp() { this.node.z++; + this.nodesDataSource.update(this.node); this.nodeService .update(this.server, this.node) .subscribe((node: Node) => {}); diff --git a/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts b/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts index 8a93ee5c..4b609013 100644 --- a/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts +++ b/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts @@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HttpClientTestingModule } from "@angular/common/http/testing"; import { inject } from "@angular/core/testing"; -import { mock, instance, capture, when } from "ts-mockito"; +import { mock, instance, capture, when, anything } from "ts-mockito"; import { HotkeyModule, HotkeysService, Hotkey } from "angular2-hotkeys"; import { of } from "rxjs"; @@ -12,13 +12,17 @@ import { NodeService } from "../../../services/node.service"; import { HttpServer } from "../../../services/http-server.service"; import { SelectionManager } from "../../../cartography/managers/selection-manager"; import { Server } from "../../../models/server"; -import { Node } from "../../../cartography/models/node"; import { Project } from "../../../models/project"; import { ProjectService } from "../../../services/project.service"; import { MockedProjectService } from "../../../services/project.service.spec"; import { SettingsService } from "../../../services/settings.service"; import { MockedToasterService } from "../../../services/toaster.service.spec"; import { mapTo } from "rxjs/internal/operators"; +import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter'; +import { MapNode } from '../../../cartography/models/map/map-node'; +import { MapLabelToLabelConverter } from '../../../cartography/converters/map/map-label-to-label-converter'; +import { MapPortToPortConverter } from '../../../cartography/converters/map/map-port-to-port-converter'; +import { Node } from '../../../cartography/models/node'; describe('ProjectMapShortcutsComponent', () => { @@ -27,8 +31,13 @@ describe('ProjectMapShortcutsComponent', () => { let hotkeyServiceMock: HotkeysService; let hotkeyServiceInstanceMock: HotkeysService; let nodeServiceMock: NodeService; + let node: MapNode; beforeEach(async(() => { + node = new MapNode(); + const selectionManagerMock = mock(SelectionManager); + when(selectionManagerMock.getSelected()).thenReturn([node]); + nodeServiceMock = mock(NodeService); hotkeyServiceMock = mock(HotkeysService); hotkeyServiceInstanceMock = instance(hotkeyServiceMock); @@ -43,7 +52,11 @@ describe('ProjectMapShortcutsComponent', () => { { provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock }, { provide: ToasterService, useClass: MockedToasterService }, { provide: ProjectService, useClass: MockedProjectService }, - { provide: SettingsService, useClass: SettingsService } + { provide: SelectionManager, useValue: instance(selectionManagerMock)}, + SettingsService, + MapNodeToNodeConverter, + MapLabelToLabelConverter, + MapPortToPortConverter ], declarations: [ ProjectMapShortcutsComponent ] }) @@ -75,19 +88,14 @@ describe('ProjectMapShortcutsComponent', () => { describe('onDeleteHandler', () => { beforeEach(() => { - const selectionManagerMock = mock(SelectionManager); - const node = new Node(); - node.node_id = "nodeid"; const server = new Server(); const project = new Project(); - when(selectionManagerMock.getSelectedNodes()).thenReturn([node]); - when(nodeServiceMock.delete(server, node)) - .thenReturn(of(node).pipe(mapTo(null))); + when(nodeServiceMock.delete(server, anything())) + .thenReturn(of(new Node()).pipe(mapTo(null))); component.project = project; component.server = server; - component.selectionManager = instance(selectionManagerMock); }); it('should handle delete', inject([ToasterService], (toaster: MockedToasterService) => { diff --git a/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.ts b/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.ts index 41124df8..ab372d90 100644 --- a/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.ts +++ b/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.ts @@ -7,6 +7,8 @@ import { Server } from '../../../models/server'; import { ToasterService } from '../../../services/toaster.service'; import { Project } from "../../../models/project"; import { ProjectService } from "../../../services/project.service"; +import { MapNode } from '../../../cartography/models/map/map-node'; +import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter'; @Component({ @@ -16,7 +18,6 @@ import { ProjectService } from "../../../services/project.service"; export class ProjectMapShortcutsComponent implements OnInit, OnDestroy { @Input() project: Project; @Input() server: Server; - @Input() selectionManager: SelectionManager; private deleteHotkey: Hotkey; @@ -24,7 +25,9 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy { private hotkeysService: HotkeysService, private toaster: ToasterService, private nodesService: NodeService, - private projectService: ProjectService + private projectService: ProjectService, + private mapNodeToNode: MapNodeToNodeConverter, + private selectionManager: SelectionManager ) { } ngOnInit() { @@ -37,14 +40,14 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy { onDeleteHandler(event: KeyboardEvent): boolean { if (!this.projectService.isReadOnly(this.project)) { - const selectedNodes = this.selectionManager.getSelectedNodes(); - if (selectedNodes) { - selectedNodes.forEach((node) => { - this.nodesService.delete(this.server, node).subscribe(data => { - this.toaster.success("Node has been deleted"); - }); + const selected = this.selectionManager.getSelected(); + + selected.filter((item) => item instanceof MapNode).forEach((item: MapNode) => { + const node = this.mapNodeToNode.convert(item); + this.nodesService.delete(this.server, node).subscribe(data => { + this.toaster.success("Node has been deleted"); }); - } + }); } return false; } diff --git a/src/app/components/project-map/project-map.component.html b/src/app/components/project-map/project-map.component.html index a62f2834..9bfda43d 100644 --- a/src/app/components/project-map/project-map.component.html +++ b/src/app/components/project-map/project-map.component.html @@ -88,6 +88,5 @@ + [server]="server"> diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index 4596861c..1f0343d9 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -22,7 +22,6 @@ import { LinkService } from "../../services/link.service"; import { NodesDataSource } from "../../cartography/datasources/nodes-datasource"; import { LinksDataSource } from "../../cartography/datasources/links-datasource"; import { ProjectWebServiceHandler } from "../../handlers/project-web-service-handler"; -import { SelectionManager } from "../../cartography/managers/selection-manager"; import { DrawingsDataSource } from "../../cartography/datasources/drawings-datasource"; import { ProgressService } from "../../common/progress/progress.service"; import { MapChangeDetectorRef } from '../../cartography/services/map-change-detector-ref'; @@ -31,6 +30,7 @@ import { LinkCreated } from '../../cartography/events/links'; import { NodeWidget } from '../../cartography/widgets/node'; import { DraggedDataEvent } from '../../cartography/events/event-source'; import { DrawingService } from '../../services/drawing.service'; +import { MapNodeToNodeConverter } from '../../cartography/converters/map/map-node-to-node-converter'; @Component({ @@ -76,7 +76,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private projectWebServiceHandler: ProjectWebServiceHandler, private mapChangeDetectorRef: MapChangeDetectorRef, private nodeWidget: NodeWidget, - private selectionManager: SelectionManager, + private mapNodeToNode: MapNodeToNodeConverter, protected nodesDataSource: NodesDataSource, protected linksDataSource: LinksDataSource, protected drawingsDataSource: DrawingsDataSource, @@ -187,20 +187,15 @@ export class ProjectMapComponent implements OnInit, OnDestroy { setUpMapCallbacks(project: Project) { const onContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => { + const node = this.mapNodeToNode.convert(eventNode.node); this.nodeContextMenu.open( - eventNode.node, + node, eventNode.event.clientY, eventNode.event.clientX ); }); this.subscriptions.push(onContextMenu); - - this.subscriptions.push( - this.selectionManager.subscribe( - this.mapChild.graphLayout.getSelectionTool().rectangleSelected) - ); - this.mapChangeDetectorRef.detectChanges(); } @@ -217,20 +212,26 @@ export class ProjectMapComponent implements OnInit, OnDestroy { } onNodeDragged(draggedEvent: DraggedDataEvent) { - this.nodesDataSource.update(draggedEvent.datum); + const node = this.nodesDataSource.get(draggedEvent.datum.node_id); + node.x += draggedEvent.dx; + node.y += draggedEvent.dy; + this.nodeService - .updatePosition(this.server, draggedEvent.datum, draggedEvent.datum.x, draggedEvent.datum.y) - .subscribe((node: Node) => { - this.nodesDataSource.update(node); + .updatePosition(this.server, node, node.x, node.y) + .subscribe((serverNode: Node) => { + this.nodesDataSource.update(serverNode); }); } onDrawingDragged(draggedEvent: DraggedDataEvent) { - this.drawingsDataSource.update(draggedEvent.datum); + const drawing = this.drawingsDataSource.get(draggedEvent.datum.drawing_id); + drawing.x += draggedEvent.dx; + drawing.y += draggedEvent.dy; + this.drawingService - .updatePosition(this.server, draggedEvent.datum, draggedEvent.datum.x, draggedEvent.datum.y) - .subscribe((drawing: Drawing) => { - this.drawingsDataSource.update(drawing); + .updatePosition(this.server, drawing, drawing.x, drawing.y) + .subscribe((serverDrawing: Drawing) => { + this.drawingsDataSource.update(serverDrawing); }); } @@ -238,8 +239,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.inReadOnlyMode = value; if (value) { this.tools.selection = false; - } - else { + } else { this.tools.selection = true; } } diff --git a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.css b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.css new file mode 100644 index 00000000..fd32953d --- /dev/null +++ b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.css @@ -0,0 +1,7 @@ +.file-name-form-field { + width: 100%; +} + +.project-snackbar { + background: #2196F3; +} \ No newline at end of file diff --git a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.html b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.html new file mode 100644 index 00000000..83c83b24 --- /dev/null +++ b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.html @@ -0,0 +1,12 @@ +

Create new project

+
+ + + Project name is required + Project name is incorrect + +
+ + +
+
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 new file mode 100644 index 00000000..800718e0 --- /dev/null +++ b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.spec.ts @@ -0,0 +1,132 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { AddBlankProjectDialogComponent } from "./add-blank-project-dialog.component"; +import { Server } from "../../../models/server"; +import { ReactiveFormsModule, FormsModule } from '@angular/forms'; +import { MatDialogModule, MatInputModule, MatFormFieldModule, MatDialogRef, MAT_DIALOG_DATA, MatSnackBarModule } from '@angular/material'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { ProjectService } from '../../../services/project.service'; +import { ToasterService } from '../../../services/toaster.service'; +import { of } from 'rxjs/internal/observable/of'; +import { Project } from '../../../models/project'; +import { Router } from '@angular/router'; + +export class MockedProjectService { + public projects: Project[] = [{ + auto_close: false, + auto_open: false, + auto_start: false, + filename: "blank", + name: "blank", + path: "", + project_id: "", + scene_height: 100, + scene_width: 100, + status: "opened", + readonly: false, + show_interface_labels: false, + show_layers: false, + show_grid: false, + snap_to_grid: false, + }]; + + list() { + return of(this.projects); + } + + add(){ + return of(this.projects.pop); + } +} + +describe('AddBlankProjectDialogComponent', () => { + let component: AddBlankProjectDialogComponent; + let fixture: ComponentFixture; + let server: Server; + let router = { + navigate: jasmine.createSpy('navigate') + }; + let toaster = { + success: jasmine.createSpy('success') + }; + let dialogRef = { + close: jasmine.createSpy('close') + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + MatDialogModule, + MatFormFieldModule, + MatInputModule, + NoopAnimationsModule, + FormsModule, + ReactiveFormsModule, + MatSnackBarModule + ], + providers: [ + { provide: MatDialogRef, useValue: dialogRef }, + { provide: MAT_DIALOG_DATA }, + { provide: ProjectService, useClass: MockedProjectService }, + { provide: ToasterService, useValue: toaster }, + { provide: Router, useValue: router } + ], + declarations : [AddBlankProjectDialogComponent] + }) + .compileComponents(); + + server = new Server(); + server.ip = "localhost"; + server.port = 80; + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AddBlankProjectDialogComponent); + component = fixture.componentInstance; + component.server = server; + component.projectNameForm.controls['projectName'].setValue("ValidName"); + fixture.detectChanges(); + }) + + it('should be created', () => { + expect(fixture).toBeDefined(); + expect(component).toBeTruthy(); + }); + + it('should call adding project when name is valid', () => { + spyOn(component, "addProject"); + + component.onAddClick(); + + expect(component.addProject).toHaveBeenCalled(); + }); + + it('should sanitize file name input', () => { + component.projectNameForm.controls['projectName'].setValue("[][]"); + fixture.detectChanges(); + spyOn(component, "addProject"); + + component.onAddClick(); + + expect(component.addProject).not.toHaveBeenCalled(); + expect(component.projectNameForm.valid).toBeFalsy(); + }); + + it('should open confirmation dialog if project with the same exists', () => { + component.projectNameForm.controls['projectName'].setValue("blank"); + spyOn(component, "openConfirmationDialog"); + + component.onAddClick(); + + expect(component.openConfirmationDialog).toHaveBeenCalled(); + }); + + it('should redirect to newly created project', () => { + component.projectNameForm.controls['projectName'].setValue("validName"); + + component.addProject(); + + expect(dialogRef.close).toHaveBeenCalled(); + expect(toaster.success).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); + }); +}); diff --git a/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts new file mode 100644 index 00000000..ca41ece5 --- /dev/null +++ b/src/app/components/projects/add-blank-project-dialog/add-blank-project-dialog.component.ts @@ -0,0 +1,92 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatDialog, MatDialogRef } from '@angular/material'; +import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; +import { Project } from '../../../models/project'; +import { Server } from '../../../models/server'; +import { ProjectService } from '../../../services/project.service'; +import { v4 as uuid } from 'uuid'; +import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; +import { ProjectNameValidator } from '../models/projectNameValidator'; +import { ToasterService } from '../../../services/toaster.service'; + +@Component({ + selector: 'app-add-blank-project-dialog', + templateUrl: './add-blank-project-dialog.component.html', + styleUrls: ['./add-blank-project-dialog.component.css'], + providers: [ProjectNameValidator] +}) +export class AddBlankProjectDialogComponent implements OnInit { + server: Server; + projectNameForm: FormGroup; + + constructor( + public dialogRef: MatDialogRef, + private router: Router, + private dialog: MatDialog, + private projectService: ProjectService, + private toasterService: ToasterService, + private formBuilder: FormBuilder, + private projectNameValidator: ProjectNameValidator) { + this.projectNameForm = this.formBuilder.group({ + projectName: new FormControl(null, [Validators.required, projectNameValidator.get]) + }); + } + + ngOnInit() {} + + get form() { + return this.projectNameForm.controls; + } + + onAddClick() : void{ + if (this.projectNameForm.invalid){ + return; + } + this.projectService + .list(this.server) + .subscribe((projects: Project[]) => { + const projectName = this.projectNameForm.controls['projectName'].value; + let existingProject = projects.find(project => project.name === projectName); + + if (existingProject){ + this.openConfirmationDialog(existingProject); + } else { + this.addProject(); + } + }); + } + + onNoClick() : void{ + this.dialogRef.close(); + } + + addProject() : void{ + this.projectService.add(this.server, this.projectNameForm.controls['projectName'].value, uuid()) + .subscribe((project: Project) => { + this.dialogRef.close(); + this.toasterService.success(`Project ${project.name} added`) + this.router.navigate(['/server', this.server.id, 'project', project.project_id]); + }); + } + + openConfirmationDialog(existingProject: Project) { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { + width: '300px', + height: '150px', + data: { + 'existingProject': existingProject + } + }); + + dialogRef.afterClosed().subscribe((answer: boolean) => { + if (answer) { + this.projectService.close(this.server, existingProject.project_id).subscribe(() => { + this.projectService.delete(this.server, existingProject.project_id).subscribe(() => { + this.addProject(); + }); + }); + } + }); + } +} diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.css b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.css similarity index 100% rename from src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.css rename to src/app/components/projects/confirmation-dialog/confirmation-dialog.component.css diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.html similarity index 98% rename from src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html rename to src/app/components/projects/confirmation-dialog/confirmation-dialog.component.html index 6d10a1db..34ebc2b1 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.html +++ b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.html @@ -5,4 +5,4 @@
-
+ \ No newline at end of file diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.spec.ts similarity index 84% rename from src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts rename to src/app/components/projects/confirmation-dialog/confirmation-dialog.component.spec.ts index b51e949d..e548d4c5 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.spec.ts +++ b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.spec.ts @@ -2,11 +2,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { MatDialogModule, MatDialog } from "@angular/material"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; import { Component, NgModule } from '@angular/core'; -import { Project } from '../../../../models/project'; -import { ImportProjectConfirmationDialogComponent } from './import-project-confirmation-dialog.component'; +import { Project } from '../../../models/project'; +import { ConfirmationDialogComponent } from './confirmation-dialog.component'; import { OverlayContainer } from '@angular/cdk/overlay'; -describe('ImportProjectConfirmationDialogComponent', () => { +describe('ConfirmationDialogComponent', () => { let dialog: MatDialog; let overlayContainerElement: HTMLElement; @@ -53,7 +53,7 @@ describe('ImportProjectConfirmationDialogComponent', () => { } }; - dialog.open(ImportProjectConfirmationDialogComponent, config); + dialog.open(ConfirmationDialogComponent, config); noop.detectChanges(); const message = overlayContainerElement.querySelector('span'); @@ -68,7 +68,7 @@ describe('ImportProjectConfirmationDialogComponent', () => { } }; - dialog.open(ImportProjectConfirmationDialogComponent, config); + dialog.open(ConfirmationDialogComponent, config); noop.detectChanges(); const message = overlayContainerElement.querySelector('span'); @@ -83,7 +83,7 @@ describe('ImportProjectConfirmationDialogComponent', () => { } }; - let dialogRef = dialog.open(ImportProjectConfirmationDialogComponent, config); + let dialogRef = dialog.open(ConfirmationDialogComponent, config); noop.detectChanges(); const button = overlayContainerElement.querySelector('button'); spyOn(dialogRef.componentInstance.dialogRef, 'close'); @@ -100,7 +100,7 @@ describe('ImportProjectConfirmationDialogComponent', () => { } }; - let dialogRef = dialog.open(ImportProjectConfirmationDialogComponent, config); + let dialogRef = dialog.open(ConfirmationDialogComponent, config); noop.detectChanges(); const button: HTMLButtonElement = overlayContainerElement.querySelector('.confirmButton'); spyOn(dialogRef.componentInstance.dialogRef, 'close'); @@ -116,7 +116,7 @@ describe('ImportProjectConfirmationDialogComponent', () => { class NoopComponent {} const TEST_DIRECTIVES = [ - ImportProjectConfirmationDialogComponent, + ConfirmationDialogComponent, NoopComponent ]; @@ -125,7 +125,7 @@ const TEST_DIRECTIVES = [ exports: TEST_DIRECTIVES, declarations: TEST_DIRECTIVES, entryComponents: [ - ImportProjectConfirmationDialogComponent + ConfirmationDialogComponent ], }) class DialogTestModule { } diff --git a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.ts similarity index 72% rename from src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts rename to src/app/components/projects/confirmation-dialog/confirmation-dialog.component.ts index 48036d07..645b53e7 100644 --- a/src/app/components/projects/import-project-dialog/import-project-confirmation-dialog/import-project-confirmation-dialog.component.ts +++ b/src/app/components/projects/confirmation-dialog/confirmation-dialog.component.ts @@ -1,18 +1,18 @@ import { Component, OnInit, Inject } from '@angular/core'; import { MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; -import { Project } from '../../../../models/project'; +import { Project } from '../../../models/project'; @Component({ selector: 'app-import-project-dialog', - templateUrl: 'import-project-confirmation-dialog.component.html', - styleUrls: ['import-project-confirmation-dialog.component.css'] + templateUrl: 'confirmation-dialog.component.html', + styleUrls: ['confirmation-dialog.component.css'] }) -export class ImportProjectConfirmationDialogComponent implements OnInit { +export class ConfirmationDialogComponent implements OnInit { private existingProject : Project; public confirmationMessage : string; public isOpen : boolean; constructor( - public dialogRef: MatDialogRef, + public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any ){ this.existingProject = data['existingProject'] diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts index 7ee72a8a..9840cdeb 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.spec.ts @@ -1,11 +1,11 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { ImportProjectDialogComponent, Validator } from "./import-project-dialog.component"; +import { ImportProjectDialogComponent } from "./import-project-dialog.component"; import { Server } from "../../../models/server"; -import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatStepperModule, MatFormFieldModule, MatDialogRef, MatDialog, MAT_DIALOG_DATA } from "@angular/material"; +import { MatInputModule, MatIconModule, MatSortModule, MatTableModule, MatTooltipModule, MatDialogModule, MatFormFieldModule, MatDialogRef, MAT_DIALOG_DATA } from "@angular/material"; import { RouterTestingModule } from "@angular/router/testing"; import { NoopAnimationsModule } from "@angular/platform-browser/animations"; -import { FileUploadModule, FileSelectDirective, FileItem, FileUploader, ParsedResponseHeaders } from "ng2-file-upload"; -import { FormsModule, ReactiveFormsModule, FormBuilder, FormControl, Validators } from '@angular/forms'; +import { FileUploadModule, FileSelectDirective, FileItem, ParsedResponseHeaders } from "ng2-file-upload"; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DebugElement } from '@angular/core'; import { By } from '@angular/platform-browser'; import { ProjectService } from '../../../services/project.service'; @@ -31,7 +31,7 @@ export class MockedProjectService { snap_to_grid: false, }]; - list(server: Server) { + list() { return of(this.projects); } } @@ -42,7 +42,6 @@ describe('ImportProjectDialogComponent', () => { let server: Server; let debugElement: DebugElement; let fileSelectDirective: FileSelectDirective; - let formBuilder: FormBuilder; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -52,7 +51,6 @@ describe('ImportProjectDialogComponent', () => { MatIconModule, MatSortModule, MatDialogModule, - MatStepperModule, MatFormFieldModule, MatInputModule, NoopAnimationsModule, @@ -73,7 +71,6 @@ describe('ImportProjectDialogComponent', () => { server = new Server(); server.ip = "localhost"; server.port = 80; - formBuilder = new FormBuilder(); })); beforeEach(() => { @@ -81,15 +78,12 @@ describe('ImportProjectDialogComponent', () => { debugElement = fixture.debugElement; component = fixture.componentInstance; component.server = server; - component.projectNameForm = formBuilder.group({ - projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator]) - }); component.projectNameForm.controls['projectName'].setValue("ValidName"); fixture.detectChanges(); debugElement = fixture.debugElement.query(By.directive(FileSelectDirective)); fileSelectDirective = debugElement.injector.get(FileSelectDirective) as FileSelectDirective; - component.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => {}; + component.uploader.onErrorItem = () => {}; }); it('should be created', () => { diff --git a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts index e6a3ddbc..67daa81e 100644 --- a/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts +++ b/src/app/components/projects/import-project-dialog/import-project-dialog.component.ts @@ -1,30 +1,20 @@ -import { Component, OnInit, Inject, ViewChild } from '@angular/core'; -import { MatStepper, MatDialogRef, MAT_DIALOG_DATA, MatDialog } from "@angular/material"; +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA, MatDialog } from "@angular/material"; import { FileUploader, ParsedResponseHeaders, FileItem } from 'ng2-file-upload'; import { Server } from '../../../models/server'; import { v4 as uuid } from 'uuid'; import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; import { ProjectService } from '../../../services/project.service'; import { Project } from '../../../models/project'; -import { ImportProjectConfirmationDialogComponent } from './import-project-confirmation-dialog/import-project-confirmation-dialog.component'; +import { ConfirmationDialogComponent } from '../confirmation-dialog/confirmation-dialog.component'; import { ServerResponse } from '../../../models/serverResponse'; - -export class Validator { - static projectNameValidator(projectName) { - var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?]/); - - if(!pattern.test(projectName.value)) { - return null; - } - - return { invalidName: true } - } -} +import { ProjectNameValidator } from '../models/projectNameValidator'; @Component({ selector: 'app-import-project-dialog', templateUrl: 'import-project-dialog.component.html', - styleUrls: ['import-project-dialog.component.css'] + styleUrls: ['import-project-dialog.component.css'], + providers: [ProjectNameValidator] }) export class ImportProjectDialogComponent implements OnInit { uploader: FileUploader; @@ -42,9 +32,10 @@ export class ImportProjectDialogComponent implements OnInit { public dialogRef: MatDialogRef, @Inject(MAT_DIALOG_DATA) public data: any, private formBuilder: FormBuilder, - private projectService: ProjectService){ + private projectService: ProjectService, + private projectNameValidator: ProjectNameValidator){ this.projectNameForm = this.formBuilder.group({ - projectName: new FormControl(null, [Validators.required, Validator.projectNameValidator]) + projectName: new FormControl(null, [Validators.required, projectNameValidator.get]) }); } @@ -104,7 +95,7 @@ export class ImportProjectDialogComponent implements OnInit { } openConfirmationDialog(existingProject: Project) { - const dialogRef = this.dialog.open(ImportProjectConfirmationDialogComponent, { + const dialogRef = this.dialog.open(ConfirmationDialogComponent, { width: '300px', height: '150px', data: { diff --git a/src/app/components/projects/models/projectNameValidator.ts b/src/app/components/projects/models/projectNameValidator.ts new file mode 100644 index 00000000..38725ac8 --- /dev/null +++ b/src/app/components/projects/models/projectNameValidator.ts @@ -0,0 +1,14 @@ +import { Injectable } from "@angular/core"; + +@Injectable() +export class ProjectNameValidator { + get(projectName) { + var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?]/); + + if(!pattern.test(projectName.value)) { + return null; + } + + return { invalidName: true } + } +} \ No newline at end of file diff --git a/src/app/components/projects/projects.component.css b/src/app/components/projects/projects.component.css index d49aad69..d7d423ad 100644 --- a/src/app/components/projects/projects.component.css +++ b/src/app/components/projects/projects.component.css @@ -2,3 +2,8 @@ height: 40px; margin: 20px; } + +.add-button { + height: 40px; + margin: 20px; +} diff --git a/src/app/components/projects/projects.component.html b/src/app/components/projects/projects.component.html index e14ad2d6..2726427b 100644 --- a/src/app/components/projects/projects.component.html +++ b/src/app/components/projects/projects.component.html @@ -2,6 +2,7 @@

Projects

+
diff --git a/src/app/components/projects/projects.component.ts b/src/app/components/projects/projects.component.ts index 571d8364..fc8bead2 100644 --- a/src/app/components/projects/projects.component.ts +++ b/src/app/components/projects/projects.component.ts @@ -15,6 +15,7 @@ import { SettingsService, Settings } from "../../services/settings.service"; import { ProgressService } from "../../common/progress/progress.service"; import { ImportProjectDialogComponent } from './import-project-dialog/import-project-dialog.component'; +import { AddBlankProjectDialogComponent } from './add-blank-project-dialog/add-blank-project-dialog.component'; @Component({ selector: 'app-projects', @@ -99,6 +100,14 @@ export class ProjectsComponent implements OnInit { }); } + addBlankProject(){ + const dialogRef = this.dialog.open(AddBlankProjectDialogComponent, { + width: '550px', + }) + let instance = dialogRef.componentInstance; + instance.server = this.server; + } + importProject(){ const dialogRef = this.dialog.open(ImportProjectDialogComponent, { width: '550px', diff --git a/src/app/handlers/project-web-service-handler.ts b/src/app/handlers/project-web-service-handler.ts index 9b6aba5e..2d359047 100644 --- a/src/app/handlers/project-web-service-handler.ts +++ b/src/app/handlers/project-web-service-handler.ts @@ -16,9 +16,11 @@ export class WebServiceMessage { @Injectable() export class ProjectWebServiceHandler { - constructor(private nodesDataSource: NodesDataSource, - private linksDataSource: LinksDataSource, - private drawingsDataSource: DrawingsDataSource) {} + constructor( + private nodesDataSource: NodesDataSource, + private linksDataSource: LinksDataSource, + private drawingsDataSource: DrawingsDataSource + ) {} public connect(ws: Subject) { const subscription = ws.subscribe((message: WebServiceMessage) => { diff --git a/src/app/models/link.ts b/src/app/models/link.ts index d9b4dc67..c5caa0cd 100644 --- a/src/app/models/link.ts +++ b/src/app/models/link.ts @@ -1,9 +1,8 @@ import { Node } from "../cartography/models/node"; import { LinkNode } from "./link-node"; -import { Selectable } from "../cartography/managers/selection-manager"; -export class Link implements Selectable { +export class Link { capture_file_name: string; capture_file_path: string; capturing: boolean; @@ -18,7 +17,6 @@ export class Link implements Selectable { source: Node; // this is not from server target: Node; // this is not from server - is_selected = false; // this is not from server x: number; // this is not from server y: number; // this is not from server } diff --git a/src/app/services/drawing.service.spec.ts b/src/app/services/drawing.service.spec.ts index 85a5c3f7..363e4bb5 100644 --- a/src/app/services/drawing.service.spec.ts +++ b/src/app/services/drawing.service.spec.ts @@ -14,7 +14,6 @@ describe('DrawingService', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; let httpServer: HttpServer; - let service: DrawingService; let server: Server; beforeEach(() => { @@ -32,7 +31,6 @@ describe('DrawingService', () => { httpClient = TestBed.get(HttpClient); httpTestingController = TestBed.get(HttpTestingController); httpServer = TestBed.get(HttpServer); - service = TestBed.get(DrawingService); server = getTestServer(); }); diff --git a/src/app/services/link.service.spec.ts b/src/app/services/link.service.spec.ts index 0e3c1e8f..aaad284e 100644 --- a/src/app/services/link.service.spec.ts +++ b/src/app/services/link.service.spec.ts @@ -14,7 +14,6 @@ describe('LinkService', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; let httpServer: HttpServer; - let service: LinkService; let server: Server; beforeEach(() => { @@ -32,7 +31,6 @@ describe('LinkService', () => { httpClient = TestBed.get(HttpClient); httpTestingController = TestBed.get(HttpTestingController); httpServer = TestBed.get(HttpServer); - service = TestBed.get(LinkService); server = getTestServer(); }); diff --git a/src/app/services/link.service.ts b/src/app/services/link.service.ts index ecbf59a2..06b262c3 100644 --- a/src/app/services/link.service.ts +++ b/src/app/services/link.service.ts @@ -10,26 +10,27 @@ import {Port} from "../models/port"; export class LinkService { - constructor(private httpServer: HttpServer) { } + constructor( + private httpServer: HttpServer) {} 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`, - {"nodes": [ - { - node_id: source_node.node_id, - port_number: source_port.port_number, - adapter_number: source_port.adapter_number - }, - { - node_id: target_node.node_id, - port_number: target_port.port_number, - adapter_number: target_port.adapter_number - } - ]}); + .post( + server, + `/projects/${source_node.project_id}/links`, + {"nodes": [ + { + node_id: source_node.node_id, + port_number: source_port.port_number, + adapter_number: source_port.adapter_number + }, + { + node_id: target_node.node_id, + port_number: target_port.port_number, + adapter_number: target_port.adapter_number + } + ]}); } } diff --git a/src/app/services/project.service.ts b/src/app/services/project.service.ts index adfb5c33..4d4236d3 100644 --- a/src/app/services/project.service.ts +++ b/src/app/services/project.service.ts @@ -12,47 +12,53 @@ import { SettingsService } from "./settings.service"; @Injectable() export class ProjectService { - constructor(private httpServer: HttpServer, - private settingsService: SettingsService) { } + constructor( + private httpServer: HttpServer, + private settingsService: SettingsService) {} get(server: Server, project_id: string) { return this.httpServer - .get(server, `/projects/${project_id}`); + .get(server, `/projects/${project_id}`); } open(server: Server, project_id: string) { return this.httpServer - .post(server, `/projects/${project_id}/open`, {}); + .post(server, `/projects/${project_id}/open`, {}); } close(server: Server, project_id: string) { return this.httpServer - .post(server, `/projects/${project_id}/close`, {}); + .post(server, `/projects/${project_id}/close`, {}); } list(server: Server) { return this.httpServer - .get(server, '/projects'); + .get(server, '/projects'); } nodes(server: Server, project_id: string) { return this.httpServer - .get(server, `/projects/${project_id}/nodes`); + .get(server, `/projects/${project_id}/nodes`); } links(server: Server, project_id: string) { return this.httpServer - .get(server, `/projects/${project_id}/links`); + .get(server, `/projects/${project_id}/links`); } drawings(server: Server, project_id: string) { return this.httpServer - .get(server, `/projects/${project_id}/drawings`); + .get(server, `/projects/${project_id}/drawings`); + } + + add(server: Server, project_name: string, project_id: string): Observable{ + return this.httpServer + .post(server, `/projects`, { "name": project_name, "project_id": project_id}); } delete(server: Server, project_id: string): Observable { return this.httpServer - .delete(server, `/projects/${project_id}`); + .delete(server, `/projects/${project_id}`); } notificationsPath(server: Server, project_id: string): string { diff --git a/src/app/services/symbol.service.spec.ts b/src/app/services/symbol.service.spec.ts index 75fbffb2..a1c20eba 100644 --- a/src/app/services/symbol.service.spec.ts +++ b/src/app/services/symbol.service.spec.ts @@ -14,7 +14,6 @@ describe('SymbolService', () => { let httpClient: HttpClient; let httpTestingController: HttpTestingController; let httpServer: HttpServer; - let service: SymbolService; let server: Server; beforeEach(() => { @@ -32,7 +31,6 @@ describe('SymbolService', () => { httpClient = TestBed.get(HttpClient); httpTestingController = TestBed.get(HttpTestingController); httpServer = TestBed.get(HttpServer); - service = TestBed.get(SymbolService); server = getTestServer(); }); diff --git a/src/app/services/toaster.service.spec.ts b/src/app/services/toaster.service.spec.ts index a5bdafd4..3c4f5c0b 100644 --- a/src/app/services/toaster.service.spec.ts +++ b/src/app/services/toaster.service.spec.ts @@ -7,6 +7,18 @@ import { ToasterService } from './toaster.service'; export class MockedToasterService { public errors: string[]; public successes: string[]; + snackBarConfigForSuccess = { + duration: 2000, + panelClass: ['snackabar-success'], + MatSnackBarHorizontalPosition : 'center', + MatSnackBarVerticalPosition : 'bottom' + }; + snackBarConfigForError = { + duration: 2000, + panelClass: ['snackabar-error'], + MatSnackBarHorizontalPosition : 'center', + MatSnackBarVerticalPosition : 'bottom' + }; constructor() { this.errors = []; @@ -41,12 +53,12 @@ describe('ToasterService', () => { it('should open when success', inject([ToasterService, MatSnackBar], (service: ToasterService, snackBar: MatSnackBar) => { const spy = spyOn(snackBar, 'open'); service.success("message"); - expect(snackBar.open).toHaveBeenCalledWith("message", null, { duration: 2000 }); + expect(snackBar.open).toHaveBeenCalledWith("message", 'Close', service.snackBarConfigForSuccess); })); it('should open when error', inject([ToasterService, MatSnackBar], (service: ToasterService, snackBar: MatSnackBar) => { const spy = spyOn(snackBar, 'open'); service.error("message"); - expect(snackBar.open).toHaveBeenCalledWith("message", null, { duration: 2000 }); + expect(snackBar.open).toHaveBeenCalledWith("message", 'Close', service.snackBarConfigForError); })); }); diff --git a/src/app/services/toaster.service.ts b/src/app/services/toaster.service.ts index d85e7b08..513406ea 100644 --- a/src/app/services/toaster.service.ts +++ b/src/app/services/toaster.service.ts @@ -3,14 +3,25 @@ import { MatSnackBar } from '@angular/material'; @Injectable() export class ToasterService { + snackBarConfigForSuccess = { + duration: 2000, + panelClass: ['snackabar-success'], + MatSnackBarHorizontalPosition : 'center', + MatSnackBarVerticalPosition : 'bottom' + }; + snackBarConfigForError = { + duration: 2000, + panelClass: ['snackabar-error'], + MatSnackBarHorizontalPosition : 'center', + MatSnackBarVerticalPosition : 'bottom' + }; constructor(private snackbar: MatSnackBar) { } public error(message: string) { - this.snackbar.open(message, null, { duration: 2000 }); + this.snackbar.open(message, 'Close', this.snackBarConfigForError); } public success(message: string) { - this.snackbar.open(message, null, { duration: 2000 }); + this.snackbar.open(message, 'Close', this.snackBarConfigForSuccess); } } - diff --git a/src/styles.css b/src/styles.css index bab361e4..cb956273 100644 --- a/src/styles.css +++ b/src/styles.css @@ -6,6 +6,16 @@ a.table-link { color: #0097a7; } +.snackabar-success { + background:#0097a7!important; + color: white!important; +} + +.snackbar-error { + background: #B00020!important; + color: white!important; +} + app-root { width: 100%; }