diff --git a/.editorconfig b/.editorconfig index 6e87a003..e89330a6 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# Editor configuration, see http://editorconfig.org +# Editor configuration, see https://editorconfig.org root = true [*] diff --git a/.travis.yml b/.travis.yml index 80362253..6d2671cd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -46,7 +46,7 @@ before_script: - greenkeeper-lockfile-update - npm install -g codecov -script: yarn ng test --watch=false --code-coverage +script: yarn coverage after_success: - codecov diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..cb8ae355 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,16 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch localhost", + "type": "firefox", + "request": "launch", + "reAttach": true, + "url": "http://localhost:4200", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/angular.json b/angular.json index 66e1b1ba..08e6c2f8 100644 --- a/angular.json +++ b/angular.json @@ -133,6 +133,9 @@ "assets": [ "src/assets", "src/favicon.ico" + ], + "codeCoverageExclude": [ + "src/app/cartography/components/experimental-map/**/*" ] } }, diff --git a/package.json b/package.json index 03f56f86..fce0d3e0 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,8 @@ "distlinux": "yarn buildforelectron && electron-builder --linux --x64", "distwin": "yarn buildforelectron && electron-builder --win --x64", "distmac": "yarn buildforelectron && electron-builder --mac --x64", - "release": "build" + "release": "build", + "coverage": "ng test --watch=false --code-coverage" }, "private": true, "dependencies": { @@ -91,8 +92,5 @@ "ignore": [ "typescript" ] - }, - "comments": [ - "Typescript should remain below 2.8.0, @todo: check later if packages were adjusted" - ] + } } diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 74e82e1a..9b382efd 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -72,6 +72,8 @@ import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu- import { MATERIAL_IMPORTS } from './material.imports'; import { DrawingService } from './services/drawing.service'; import { ProjectNameValidator } from './components/projects/models/projectNameValidator'; +import { NodeSelectInterfaceComponent } from './components/project-map/node-select-interface/node-select-interface.component'; +import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component'; if (environment.production) { @@ -113,6 +115,8 @@ if (environment.production) { LocalServerComponent, ProgressComponent, ServerDiscoveryComponent, + NodeSelectInterfaceComponent, + DrawLinkToolComponent ], imports: [ NgbModule.forRoot(), diff --git a/src/app/cartography/angular-map.imports.ts b/src/app/cartography/angular-map.imports.ts new file mode 100644 index 00000000..b98e6247 --- /dev/null +++ b/src/app/cartography/angular-map.imports.ts @@ -0,0 +1,28 @@ +import { NodeComponent } from './components/experimental-map/node/node.component'; +import { LinkComponent } from './components/experimental-map/link/link.component'; +import { StatusComponent } from './components/experimental-map/status/status.component'; +import { DrawingComponent } from './components/experimental-map/drawing/drawing.component'; +import { EllipseComponent } from './components/experimental-map/drawing/drawings/ellipse/ellipse.component'; +import { ImageComponent } from './components/experimental-map/drawing/drawings/image/image.component'; +import { LineComponent } from './components/experimental-map/drawing/drawings/line/line.component'; +import { RectComponent } from './components/experimental-map/drawing/drawings/rect/rect.component'; +import { TextComponent } from './components/experimental-map/drawing/drawings/text/text.component'; +import { InterfaceLabelComponent } from './components/experimental-map/interface-label/interface-label.component'; +import { DraggableComponent } from './components/experimental-map/draggable/draggable.component'; +import { SelectionComponent } from './components/experimental-map/selection/selection.component'; + + +export const ANGULAR_MAP_DECLARATIONS = [ + NodeComponent, + LinkComponent, + StatusComponent, + DrawingComponent, + EllipseComponent, + ImageComponent, + LineComponent, + RectComponent, + TextComponent, + DraggableComponent, + SelectionComponent, + InterfaceLabelComponent +]; diff --git a/src/app/cartography/cartography.module.ts b/src/app/cartography/cartography.module.ts index 8b9080fd..d860a486 100644 --- a/src/app/cartography/cartography.module.ts +++ b/src/app/cartography/cartography.module.ts @@ -2,10 +2,6 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatMenuModule, MatIconModule } from '@angular/material'; -import { MapComponent } from './components/map/map.component'; -import { DrawLinkToolComponent } from './components/draw-link-tool/draw-link-tool.component'; -import { NodeSelectInterfaceComponent } from './components/node-select-interface/node-select-interface.component'; - import { CssFixer } from './helpers/css-fixer'; import { FontFixer } from './helpers/font-fixer'; import { MultiLinkCalculatorHelper } from './helpers/multi-link-calculator-helper'; @@ -14,10 +10,9 @@ import { QtDasharrayFixer } from './helpers/qt-dasharray-fixer'; import { LayersManager } from './managers/layers-manager'; import { MapChangeDetectorRef } from './services/map-change-detector-ref'; import { Context } from './models/context'; +import { ANGULAR_MAP_DECLARATIONS } from './angular-map.imports'; import { D3_MAP_IMPORTS } from './d3-map.imports'; import { CanvasSizeDetector } from './helpers/canvas-size-detector'; -import { MapListeners } from './listeners/map-listeners'; -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'; @@ -35,10 +30,17 @@ import { PortToMapPortConverter } from './converters/map/port-to-map-port-conver 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'; import { LinksEventSource } from './events/links-event-source'; +import { D3MapComponent } from './components/d3-map/d3-map.component'; +import { ExperimentalMapComponent } from './components/experimental-map/experimental-map.component'; +import { SelectionEventSource } from './events/selection-event-source'; +import { SelectionControlComponent } from './components/selection-control/selection-control.component'; +import { SelectionSelectComponent } from './components/selection-select/selection-select.component'; +import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component'; +import { MapSettingsManager } from './managers/map-settings-manager'; +import { FontBBoxCalculator } from './helpers/font-bbox-calculator'; +import { StylesToFontConverter } from './converters/styles-to-font-converter'; @NgModule({ @@ -48,9 +50,12 @@ import { LinksEventSource } from './events/links-event-source'; MatIconModule ], declarations: [ - MapComponent, - DrawLinkToolComponent, - NodeSelectInterfaceComponent + D3MapComponent, + ExperimentalMapComponent, + ...ANGULAR_MAP_DECLARATIONS, + SelectionControlComponent, + SelectionSelectComponent, + DraggableSelectionComponent ], providers: [ CssFixer, @@ -62,10 +67,6 @@ import { LinksEventSource } from './events/links-event-source'; MapChangeDetectorRef, CanvasSizeDetector, Context, - SelectionUpdateListener, - MapListeners, - DraggableListener, - SelectionListener, DrawingsEventSource, NodesEventSource, LinksEventSource, @@ -88,8 +89,12 @@ import { LinksEventSource } from './events/links-event-source'; MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource, + SelectionEventSource, + MapSettingsManager, + FontBBoxCalculator, + StylesToFontConverter, ...D3_MAP_IMPORTS ], - exports: [ MapComponent ] + exports: [ D3MapComponent, ExperimentalMapComponent ] }) export class CartographyModule { } diff --git a/src/app/cartography/components/d3-map/d3-map.component.html b/src/app/cartography/components/d3-map/d3-map.component.html new file mode 100644 index 00000000..018e64f0 --- /dev/null +++ b/src/app/cartography/components/d3-map/d3-map.component.html @@ -0,0 +1,13 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/app/cartography/components/map/map.component.scss b/src/app/cartography/components/d3-map/d3-map.component.scss similarity index 100% rename from src/app/cartography/components/map/map.component.scss rename to src/app/cartography/components/d3-map/d3-map.component.scss diff --git a/src/app/cartography/components/map/map.component.spec.ts b/src/app/cartography/components/d3-map/d3-map.component.spec.ts similarity index 67% rename from src/app/cartography/components/map/map.component.spec.ts rename to src/app/cartography/components/d3-map/d3-map.component.spec.ts index 2aa33cd9..ed5697fc 100644 --- a/src/app/cartography/components/map/map.component.spec.ts +++ b/src/app/cartography/components/d3-map/d3-map.component.spec.ts @@ -1,14 +1,14 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; -import { MapComponent } from './map.component'; +import { D3MapComponent } from './d3-map.component'; -describe('MapComponent', () => { - let component: MapComponent; - let fixture: ComponentFixture; +describe('D3MapComponent', () => { + let component: D3MapComponent; + let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ - declarations: [ MapComponent ] + declarations: [ D3MapComponent ] }) .compileComponents(); })); diff --git a/src/app/cartography/components/map/map.component.ts b/src/app/cartography/components/d3-map/d3-map.component.ts similarity index 86% rename from src/app/cartography/components/map/map.component.ts rename to src/app/cartography/components/d3-map/d3-map.component.ts index d3879d8b..77949d08 100644 --- a/src/app/cartography/components/map/map.component.ts +++ b/src/app/cartography/components/d3-map/d3-map.component.ts @@ -1,33 +1,31 @@ import { - Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChange, EventEmitter, Output + Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChange, EventEmitter, Output, ViewChild } from '@angular/core'; import { Selection, select } from 'd3-selection'; import { GraphLayout } from "../../widgets/graph-layout"; import { Context } from "../../models/context"; import { Size } from "../../models/size"; -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 { MapChangeDetectorRef } from '../../services/map-change-detector-ref'; import { CanvasSizeDetector } from '../../helpers/canvas-size-detector'; -import { MapListeners } from '../../listeners/map-listeners'; -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 { GraphDataManager } from '../../managers/graph-data-manager'; +import { MapSettingsManager } from '../../managers/map-settings-manager'; @Component({ - selector: 'app-map', - templateUrl: './map.component.html', - styleUrls: ['./map.component.scss'] + selector: 'app-d3-map', + templateUrl: './d3-map.component.html', + styleUrls: ['./d3-map.component.scss'] }) -export class MapComponent implements OnInit, OnChanges, OnDestroy { +export class D3MapComponent implements OnInit, OnChanges, OnDestroy { @Input() nodes: Node[] = []; @Input() links: Link[] = []; @Input() drawings: Drawing[] = []; @@ -36,6 +34,8 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { @Input() width = 1500; @Input() height = 600; + @ViewChild('svg') svgRef: ElementRef; + private parentNativeElement: any; private svg: Selection; @@ -50,10 +50,8 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { private context: Context, private mapChangeDetectorRef: MapChangeDetectorRef, private canvasSizeDetector: CanvasSizeDetector, - private mapListeners: MapListeners, + private mapSettings: MapSettingsManager, protected element: ElementRef, - protected nodesWidget: NodesWidget, - protected drawingsWidget: DrawingsWidget, protected interfaceLabelWidget: InterfaceLabelWidget, protected selectionToolWidget: SelectionTool, protected movingToolWidget: MovingTool, @@ -84,8 +82,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { @Input('draw-link-tool') drawLinkTool: boolean; @Input('readonly') set readonly(value) { - this.nodesWidget.draggingEnabled = !value; - this.drawingsWidget.draggingEnabled = !value; + this.mapSettings.isReadOnly = value; } ngOnChanges(changes: { [propKey: string]: SimpleChange }) { @@ -117,14 +114,11 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy { this.redraw(); } }); - - this.mapListeners.onInit(this.svg); } ngOnDestroy() { this.graphLayout.disconnect(this.svg); this.onChangesDetected.unsubscribe(); - this.mapListeners.onDestroy(); } public createGraph(domElement: HTMLElement) { diff --git a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.scss b/src/app/cartography/components/draggable-selection/draggable-selection.component.html similarity index 100% rename from src/app/cartography/components/draw-link-tool/draw-link-tool.component.scss rename to src/app/cartography/components/draggable-selection/draggable-selection.component.html diff --git a/src/app/cartography/components/draggable-selection/draggable-selection.component.scss b/src/app/cartography/components/draggable-selection/draggable-selection.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/draggable-selection/draggable-selection.component.spec.ts b/src/app/cartography/components/draggable-selection/draggable-selection.component.spec.ts new file mode 100644 index 00000000..d9122328 --- /dev/null +++ b/src/app/cartography/components/draggable-selection/draggable-selection.component.spec.ts @@ -0,0 +1,549 @@ +import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; + +import { DraggableSelectionComponent } from './draggable-selection.component'; +import { NodesWidget } from '../../widgets/nodes'; +import { DrawingsWidget } from '../../widgets/drawings'; +import { LinksWidget } from '../../widgets/links'; +import { LabelWidget } from '../../widgets/label'; +import { InterfaceLabelWidget } from '../../widgets/interface-label'; +import { SelectionManager } from '../../managers/selection-manager'; +import { SelectionManagerMock } from '../../managers/selection-manager.spec'; +import { NodesEventSource } from '../../events/nodes-event-source'; +import { DrawingsEventSource } from '../../events/drawings-event-source'; +import { GraphDataManager } from '../../managers/graph-data-manager'; +import { MockedGraphDataManager } from '../../managers/graph-data-manager.spec'; +import { LinksEventSource } from '../../events/links-event-source'; +import { DraggableStart, DraggableDrag, DraggableEnd } from '../../events/draggable'; +import { MapNode } from '../../models/map/map-node'; +import { EventEmitter } from '@angular/core'; +import { MapDrawing } from '../../models/map/map-drawing'; +import { MapLabel } from '../../models/map/map-label'; +import { MapLinkNode } from '../../models/map/map-link-node'; +import { select } from 'd3-selection'; +import { MapLink } from '../../models/map/map-link'; + + +describe('DraggableSelectionComponent', () => { + let component: DraggableSelectionComponent; + let fixture: ComponentFixture; + let mockedGraphDataManager: MockedGraphDataManager; + let nodesStartEventEmitter: EventEmitter>; + let nodesDragEventEmitter: EventEmitter>; + let nodesEndEventEmitter: EventEmitter>; + + let drawingsStartEventEmitter: EventEmitter>; + let drawingsDragEventEmitter: EventEmitter>; + let drawingsEndEventEmitter: EventEmitter>; + + let labelStartEventEmitter: EventEmitter>; + let labelDragEventEmitter: EventEmitter>; + let labelEndEventEmitter: EventEmitter>; + + let interfaceLabelStartEventEmitter: EventEmitter>; + let interfaceLabelDragEventEmitter: EventEmitter>; + let interfaceLabelEndEventEmitter: EventEmitter>; + + beforeEach(async(() => { + mockedGraphDataManager = new MockedGraphDataManager(); + + nodesStartEventEmitter = new EventEmitter>(); + nodesDragEventEmitter = new EventEmitter>(); + nodesEndEventEmitter = new EventEmitter>(); + + drawingsStartEventEmitter = new EventEmitter>(); + drawingsDragEventEmitter = new EventEmitter>(); + drawingsEndEventEmitter = new EventEmitter>(); + + labelStartEventEmitter = new EventEmitter>(); + labelDragEventEmitter = new EventEmitter>(); + labelEndEventEmitter = new EventEmitter>(); + + interfaceLabelStartEventEmitter = new EventEmitter>(); + interfaceLabelDragEventEmitter = new EventEmitter>(); + interfaceLabelEndEventEmitter = new EventEmitter>(); + + const nodesWidgetStub = { + redrawNode: () => {}, + draggable: { + start: nodesStartEventEmitter, + drag: nodesDragEventEmitter, + end: nodesEndEventEmitter + } + }; + + const drawingsWidgetStub = { + redrawDrawing: () => {}, + draggable: { + start: drawingsStartEventEmitter, + drag: drawingsDragEventEmitter, + end: drawingsEndEventEmitter + } + }; + const linksWidgetStub = { + redrawLink: () => {}, + }; + + const labelWidgetStub = { + redrawLabel: () => {}, + draggable: { + start: labelStartEventEmitter, + drag: labelDragEventEmitter, + end: labelEndEventEmitter + } + }; + + const interfaceLabelWidgetStub = { + draggable: { + start: interfaceLabelStartEventEmitter, + drag: interfaceLabelDragEventEmitter, + end: interfaceLabelEndEventEmitter + } + }; + + const nodesEventSourceStub = { + dragged: { emit: () => {}}, + labelDragged: { emit: () => {}} + }; + const drawingsEventSourceStub = { + dragged: { emit: () => {}} + }; + const linksEventSourceStub = { + interfaceDragged: { emit: () => {}} + }; + + TestBed.configureTestingModule({ + providers: [ + { provide: NodesWidget, useValue: nodesWidgetStub }, + { provide: DrawingsWidget, useValue: drawingsWidgetStub }, + { provide: LinksWidget, useValue: linksWidgetStub }, + { provide: LabelWidget, useValue: labelWidgetStub }, + { provide: InterfaceLabelWidget, useValue: interfaceLabelWidgetStub }, + { provide: SelectionManager, useValue: new SelectionManagerMock() }, + { provide: NodesEventSource, useValue: nodesEventSourceStub }, + { provide: DrawingsEventSource, useValue: drawingsEventSourceStub }, + { provide: GraphDataManager, useValue: mockedGraphDataManager }, + { provide: LinksEventSource, useValue: linksEventSourceStub }, + ], + declarations: [ DraggableSelectionComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DraggableSelectionComponent); + component = fixture.componentInstance; + component.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('nodes dragging', () => { + let nodesWidgetStub: NodesWidget; + let linksWidgetStub: LinksWidget; + let selectionManagerStub: SelectionManager; + let node: MapNode; + + beforeEach(() => { + nodesWidgetStub = fixture.debugElement.injector.get(NodesWidget); + linksWidgetStub = fixture.debugElement.injector.get(LinksWidget); + selectionManagerStub = fixture.debugElement.injector.get(SelectionManager); + node = new MapNode(); + node.id = "nodeid"; + node.x = 1; + node.y = 2; + }); + + it('should select node when started dragging', fakeAsync(() => { + nodesWidgetStub.draggable.start.emit(new DraggableStart(node)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should ignore node when started dragging and node is in selection', fakeAsync(() => { + selectionManagerStub.setSelected([node]); + nodesWidgetStub.draggable.start.emit(new DraggableStart(node)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should update node position when dragging', fakeAsync(() => { + spyOn(nodesWidgetStub, 'redrawNode'); + selectionManagerStub.setSelected([node]); + + const dragEvent = new DraggableDrag(node); + dragEvent.dx = 10; + dragEvent.dy = 20; + + nodesWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(nodesWidgetStub.redrawNode).toHaveBeenCalledWith(select(fixture.componentInstance.svg), node); + expect(node.x).toEqual(11); + expect(node.y).toEqual(22); + })); + + it('should redraw related links target when dragging node', fakeAsync(() => { + spyOn(nodesWidgetStub, 'redrawNode'); + spyOn(linksWidgetStub, 'redrawLink'); + const link = new MapLink(); + link.target = node; + mockedGraphDataManager.setLinks([link]); + selectionManagerStub.setSelected([node]); + nodesWidgetStub.draggable.drag.emit(new DraggableDrag(node)); + + tick(); + expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link); + })); + + it('should redraw related links source when dragging node', fakeAsync(() => { + spyOn(nodesWidgetStub, 'redrawNode'); + spyOn(linksWidgetStub, 'redrawLink'); + const link = new MapLink(); + link.source = node; + mockedGraphDataManager.setLinks([link]); + selectionManagerStub.setSelected([node]); + nodesWidgetStub.draggable.drag.emit(new DraggableDrag(node)); + + tick(); + expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link); + })); + + it('should emit event when node stopped dragging', fakeAsync(() => { + const nodesEventSourceStub = fixture.debugElement.injector.get(NodesEventSource); + const spyDragged = spyOn(nodesEventSourceStub.dragged, 'emit'); + + selectionManagerStub.setSelected([node]); + const dragEvent = new DraggableEnd(node); + dragEvent.dx = 10; + dragEvent.dy = 20; + + nodesWidgetStub.draggable.end.emit(dragEvent); + tick(); + expect(nodesEventSourceStub.dragged.emit).toHaveBeenCalled(); + expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(node); + expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10); + expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20); + })); + }); + + describe('drawings dragging', () => { + let drawingsWidgetStub: DrawingsWidget; + let selectionManagerStub: SelectionManager; + let drawing: MapDrawing; + + beforeEach(() => { + drawingsWidgetStub = fixture.debugElement.injector.get(DrawingsWidget); + selectionManagerStub = fixture.debugElement.injector.get(SelectionManager); + drawing = new MapDrawing(); + drawing.id = "drawingid"; + drawing.x = 1; + drawing.y = 2; + }); + + it('should select drawing when started dragging', fakeAsync(() => { + drawingsWidgetStub.draggable.start.emit(new DraggableStart(drawing)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should ignore drawing when started dragging and node is in selection', fakeAsync(() => { + selectionManagerStub.setSelected([drawing]); + drawingsWidgetStub.draggable.start.emit(new DraggableStart(drawing)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should update drawing position when dragging', fakeAsync(() => { + spyOn(drawingsWidgetStub, 'redrawDrawing'); + selectionManagerStub.setSelected([drawing]); + + const dragEvent = new DraggableDrag(drawing); + dragEvent.dx = 10; + dragEvent.dy = 20; + + drawingsWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(drawingsWidgetStub.redrawDrawing).toHaveBeenCalledWith(select(fixture.componentInstance.svg), drawing); + expect(drawing.x).toEqual(11); + expect(drawing.y).toEqual(22); + })); + + it('should emit event when drawing stopped dragging', fakeAsync(() => { + const drawingsEventSourceStub = fixture.debugElement.injector.get(DrawingsEventSource); + const spyDragged = spyOn(drawingsEventSourceStub.dragged, 'emit'); + + selectionManagerStub.setSelected([drawing]); + const dragEvent = new DraggableEnd(drawing); + dragEvent.dx = 10; + dragEvent.dy = 20; + + drawingsWidgetStub.draggable.end.emit(dragEvent); + tick(); + expect(drawingsEventSourceStub.dragged.emit).toHaveBeenCalled(); + expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(drawing); + expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10); + expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20); + })); + }); + + describe('labels dragging', () => { + let labelWidgetStub: LabelWidget; + let selectionManagerStub: SelectionManager; + let label: MapLabel; + + beforeEach(() => { + labelWidgetStub = fixture.debugElement.injector.get(LabelWidget); + selectionManagerStub = fixture.debugElement.injector.get(SelectionManager); + label = new MapLabel(); + label.id = "labelid"; + label.x = 1; + label.y = 2; + }); + + it('should select label when started dragging', fakeAsync(() => { + labelWidgetStub.draggable.start.emit(new DraggableStart(label)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should ignore label when started dragging and node is in selection', fakeAsync(() => { + selectionManagerStub.setSelected([label]); + labelWidgetStub.draggable.start.emit(new DraggableStart(label)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should update label position when dragging', fakeAsync(() => { + spyOn(labelWidgetStub, 'redrawLabel'); + selectionManagerStub.setSelected([label]); + const node = new MapNode(); + node.id = "nodeid"; + node.label = label; + label.nodeId = node.id; + + mockedGraphDataManager.setNodes([node]); + + const dragEvent = new DraggableDrag(label); + dragEvent.dx = 10; + dragEvent.dy = 20; + + labelWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(labelWidgetStub.redrawLabel).toHaveBeenCalledWith(select(fixture.componentInstance.svg), label); + expect(label.x).toEqual(11); + expect(label.y).toEqual(22); + })); + + it('should not update label position when dragging and parent is selected', fakeAsync(() => { + spyOn(labelWidgetStub, 'redrawLabel'); + const node = new MapNode(); + node.id = "nodeid"; + node.label = label; + label.nodeId = node.id; + + selectionManagerStub.setSelected([label, node]); + mockedGraphDataManager.setNodes([node]); + + const dragEvent = new DraggableDrag(label); + dragEvent.dx = 10; + dragEvent.dy = 20; + + labelWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(labelWidgetStub.redrawLabel).not.toHaveBeenCalled(); + expect(label.x).toEqual(1); + expect(label.y).toEqual(2); + })); + + + it('should emit event when label stopped dragging', fakeAsync(() => { + const nodesEventSourceStub = fixture.debugElement.injector.get(NodesEventSource); + const spyDragged = spyOn(nodesEventSourceStub.labelDragged, 'emit'); + + selectionManagerStub.setSelected([label]); + const dragEvent = new DraggableEnd(label); + dragEvent.dx = 10; + dragEvent.dy = 20; + + labelWidgetStub.draggable.end.emit(dragEvent); + tick(); + expect(nodesEventSourceStub.labelDragged.emit).toHaveBeenCalled(); + expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(label); + expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10); + expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20); + })); + + it('should not emit event when label stopped dragging and parent node is selected', fakeAsync(() => { + const nodesEventSourceStub = fixture.debugElement.injector.get(NodesEventSource); + spyOn(nodesEventSourceStub.labelDragged, 'emit'); + const node = new MapNode(); + node.id = "nodeid"; + label.nodeId = node.id; + + selectionManagerStub.setSelected([label, node]); + const dragEvent = new DraggableEnd(label); + dragEvent.dx = 10; + dragEvent.dy = 20; + + labelWidgetStub.draggable.end.emit(dragEvent); + tick(); + expect(nodesEventSourceStub.labelDragged.emit).not.toHaveBeenCalled(); + })); + }); + + describe('interfaces labels dragging', () => { + let linksWidgetStub: LinksWidget; + let interfaceLabelWidgetStub: InterfaceLabelWidget; + let selectionManagerStub: SelectionManager; + let linkNode: MapLinkNode; + + beforeEach(() => { + interfaceLabelWidgetStub = fixture.debugElement.injector.get(InterfaceLabelWidget); + linksWidgetStub = fixture.debugElement.injector.get(LinksWidget); + selectionManagerStub = fixture.debugElement.injector.get(SelectionManager); + linkNode = new MapLinkNode(); + linkNode.label = new MapLabel(); + linkNode.label.x = 1; + linkNode.label.y = 2; + linkNode.id = "linknodeid"; + }); + + it('should select interface label when started dragging', fakeAsync(() => { + interfaceLabelWidgetStub.draggable.start.emit(new DraggableStart(linkNode)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should ignore interface label when started dragging and node is in selection', fakeAsync(() => { + selectionManagerStub.setSelected([linkNode]); + interfaceLabelWidgetStub.draggable.start.emit(new DraggableStart(linkNode)); + tick(); + expect(selectionManagerStub.getSelected().length).toEqual(1); + })); + + it('should update interface label position when dragging first node', fakeAsync(() => { + spyOn(linksWidgetStub, 'redrawLink'); + selectionManagerStub.setSelected([linkNode]); + const node = new MapNode(); + node.id = "nodeid"; + linkNode.nodeId = node.id; + + const secondLinkNode = new MapLinkNode(); + secondLinkNode.label = new MapLabel(); + secondLinkNode.label.x = 1; + secondLinkNode.label.y = 2; + secondLinkNode.id = "secondlinknodeid"; + + const link = new MapLink(); + link.nodes = [linkNode, secondLinkNode]; + + mockedGraphDataManager.setLinks([link]); + + const dragEvent = new DraggableDrag(linkNode); + dragEvent.dx = 10; + dragEvent.dy = 20; + + interfaceLabelWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link); + expect(linkNode.label.x).toEqual(11); + expect(linkNode.label.y).toEqual(22); + })); + + it('should update interface label position when dragging second node', fakeAsync(() => { + spyOn(linksWidgetStub, 'redrawLink'); + selectionManagerStub.setSelected([linkNode]); + const node = new MapNode(); + node.id = "nodeid"; + linkNode.nodeId = node.id; + + const secondLinkNode = new MapLinkNode(); + secondLinkNode.label = new MapLabel(); + secondLinkNode.label.x = 1; + secondLinkNode.label.y = 2; + secondLinkNode.id = "secondlinknodeid"; + + const link = new MapLink(); + link.nodes = [secondLinkNode, linkNode]; + + mockedGraphDataManager.setLinks([link]); + + const dragEvent = new DraggableDrag(linkNode); + dragEvent.dx = 10; + dragEvent.dy = 20; + + interfaceLabelWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(linksWidgetStub.redrawLink).toHaveBeenCalledWith(select(fixture.componentInstance.svg), link); + expect(linkNode.label.x).toEqual(11); + expect(linkNode.label.y).toEqual(22); + })); + + it('should not update interface label position when dragging and parent node is selected', fakeAsync(() => { + spyOn(linksWidgetStub, 'redrawLink'); + const node = new MapNode(); + node.id = "nodeid"; + linkNode.nodeId = node.id; + + selectionManagerStub.setSelected([linkNode, node]); + + const secondLinkNode = new MapLinkNode(); + secondLinkNode.label = new MapLabel(); + secondLinkNode.label.x = 1; + secondLinkNode.label.y = 2; + secondLinkNode.id = "secondlinknodeid"; + + const link = new MapLink(); + link.nodes = [linkNode, secondLinkNode]; + + mockedGraphDataManager.setLinks([link]); + + const dragEvent = new DraggableDrag(linkNode); + dragEvent.dx = 10; + dragEvent.dy = 20; + + interfaceLabelWidgetStub.draggable.drag.emit(dragEvent); + tick(); + expect(linksWidgetStub.redrawLink).not.toHaveBeenCalled(); + expect(linkNode.label.x).toEqual(1); + expect(linkNode.label.y).toEqual(2); + })); + + it('should emit event when interface label stopped dragging', fakeAsync(() => { + const linksEventSourceStub = fixture.debugElement.injector.get(LinksEventSource); + const spyDragged = spyOn(linksEventSourceStub.interfaceDragged, 'emit'); + + selectionManagerStub.setSelected([linkNode]); + const dragEvent = new DraggableEnd(linkNode); + dragEvent.dx = 10; + dragEvent.dy = 20; + + interfaceLabelWidgetStub.draggable.end.emit(dragEvent); + tick(); + expect(linksEventSourceStub.interfaceDragged.emit).toHaveBeenCalled(); + expect(spyDragged.calls.mostRecent().args[0].datum).toEqual(linkNode); + expect(spyDragged.calls.mostRecent().args[0].dx).toEqual(10); + expect(spyDragged.calls.mostRecent().args[0].dy).toEqual(20); + })); + + it('should not emit event when interface label stopped dragging and parent node is selected', fakeAsync(() => { + const linksEventSourceStub = fixture.debugElement.injector.get(LinksEventSource); + spyOn(linksEventSourceStub.interfaceDragged, 'emit'); + + const node = new MapNode(); + node.id = "nodeid"; + linkNode.nodeId = node.id; + + selectionManagerStub.setSelected([linkNode, node]); + const dragEvent = new DraggableEnd(linkNode); + dragEvent.dx = 10; + dragEvent.dy = 20; + + interfaceLabelWidgetStub.draggable.end.emit(dragEvent); + tick(); + expect(linksEventSourceStub.interfaceDragged.emit).not.toHaveBeenCalled(); + })); + }); +}); diff --git a/src/app/cartography/components/draggable-selection/draggable-selection.component.ts b/src/app/cartography/components/draggable-selection/draggable-selection.component.ts new file mode 100644 index 00000000..0b0ca455 --- /dev/null +++ b/src/app/cartography/components/draggable-selection/draggable-selection.component.ts @@ -0,0 +1,191 @@ +import { Component, OnInit, OnDestroy, Input } from '@angular/core'; +import { Subscription, merge } from 'rxjs'; +import { NodesWidget } from '../../widgets/nodes'; +import { DrawingsWidget } from '../../widgets/drawings'; +import { LinksWidget } from '../../widgets/links'; +import { SelectionManager } from '../../managers/selection-manager'; +import { NodesEventSource } from '../../events/nodes-event-source'; +import { DrawingsEventSource } from '../../events/drawings-event-source'; +import { GraphDataManager } from '../../managers/graph-data-manager'; +import { DraggableStart, DraggableDrag, DraggableEnd } from '../../events/draggable'; +import { MapNode } from '../../models/map/map-node'; +import { MapDrawing } from '../../models/map/map-drawing'; +import { DraggedDataEvent } from '../../events/event-source'; +import { select } from 'd3-selection'; +import { MapLabel } from '../../models/map/map-label'; +import { LabelWidget } from '../../widgets/label'; +import { InterfaceLabelWidget } from '../../widgets/interface-label'; +import { MapLinkNode } from '../../models/map/map-link-node'; +import { LinksEventSource } from '../../events/links-event-source'; + +@Component({ + selector: 'app-draggable-selection', + templateUrl: './draggable-selection.component.html', + styleUrls: ['./draggable-selection.component.scss'] +}) +export class DraggableSelectionComponent implements OnInit, OnDestroy { + private start: Subscription; + private drag: Subscription; + private end: Subscription; + + @Input('svg') svg: SVGSVGElement; + + constructor( + private nodesWidget: NodesWidget, + private drawingsWidget: DrawingsWidget, + private linksWidget: LinksWidget, + private labelWidget: LabelWidget, + private interfaceWidget: InterfaceLabelWidget, + private selectionManager: SelectionManager, + private nodesEventSource: NodesEventSource, + private drawingsEventSource: DrawingsEventSource, + private graphDataManager: GraphDataManager, + private linksEventSource: LinksEventSource + ) { } + + ngOnInit() { + const svg = select(this.svg); + + this.start = merge( + this.nodesWidget.draggable.start, + this.drawingsWidget.draggable.start, + this.labelWidget.draggable.start, + this.interfaceWidget.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]); + } + } + + if (evt.datum instanceof MapLabel) { + if (selected.filter((item) => item instanceof MapLabel && item.id === evt.datum.id).length === 0) { + this.selectionManager.setSelected([evt.datum]); + } + } + + if (evt.datum instanceof MapLinkNode) { + if (selected.filter((item) => item instanceof MapLinkNode && item.id === evt.datum.id).length === 0) { + this.selectionManager.setSelected([evt.datum]); + } + } + }); + + this.drag = merge( + this.nodesWidget.draggable.drag, + this.drawingsWidget.draggable.drag, + this.labelWidget.draggable.drag, + this.interfaceWidget.draggable.drag + ).subscribe((evt: DraggableDrag) => { + const selected = this.selectionManager.getSelected(); + const selectedNodes = selected.filter((item) => item instanceof MapNode); + // update nodes + selectedNodes.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 !== undefined && link.target.id === node.id) || (link.source !== undefined && 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); + }); + + // update labels + selected.filter((item) => item instanceof MapLabel).forEach((label: MapLabel) => { + const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0; + if (isParentNodeSelected) { + return; + } + + const node = this.graphDataManager.getNodes().filter((node) => node.id === label.nodeId)[0]; + node.label.x += evt.dx; + node.label.y += evt.dy; + this.labelWidget.redrawLabel(svg, label); + }); + + // update interface labels + selected.filter((item) => item instanceof MapLinkNode).forEach((interfaceLabel: MapLinkNode) => { + const isParentNodeSelected = selectedNodes.filter((node) => node.id === interfaceLabel.nodeId).length > 0; + if (isParentNodeSelected) { + return; + } + + const link = this.graphDataManager.getLinks().filter((link) => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0]; + if(link.nodes[0].id === interfaceLabel.id) { + link.nodes[0].label.x += evt.dx; + link.nodes[0].label.y += evt.dy; + } + if(link.nodes[1].id === interfaceLabel.id) { + link.nodes[1].label.x += evt.dx; + link.nodes[1].label.y += evt.dy; + } + + this.linksWidget.redrawLink(svg, link); + }); + + }); + + this.end = merge( + this.nodesWidget.draggable.end, + this.drawingsWidget.draggable.end, + this.labelWidget.draggable.end, + this.interfaceWidget.draggable.end + ).subscribe((evt: DraggableEnd) => { + const selected = this.selectionManager.getSelected(); + const selectedNodes = selected.filter((item) => item instanceof MapNode); + + selectedNodes.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)); + }); + + selected.filter((item) => item instanceof MapLabel).forEach((label: MapLabel) => { + const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0; + if (isParentNodeSelected) { + return; + } + + this.nodesEventSource.labelDragged.emit(new DraggedDataEvent(label, evt.dx, evt.dy)); + }); + + selected.filter((item) => item instanceof MapLinkNode).forEach((label: MapLinkNode) => { + const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0; + if (isParentNodeSelected) { + return; + } + + this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent(label, evt.dx, evt.dy)); + }); + + }); + + } + + ngOnDestroy() { + this.start.unsubscribe(); + this.drag.unsubscribe(); + this.end.unsubscribe(); + } + +} diff --git a/src/app/cartography/components/experimental-map/draggable/draggable.component.scss b/src/app/cartography/components/experimental-map/draggable/draggable.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/draggable/draggable.component.spec.ts b/src/app/cartography/components/experimental-map/draggable/draggable.component.spec.ts new file mode 100644 index 00000000..84bfbb9c --- /dev/null +++ b/src/app/cartography/components/experimental-map/draggable/draggable.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DraggableComponent } from './draggable.component'; + +describe('DraggableComponent', () => { + let component: DraggableComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DraggableComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DraggableComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/draggable/draggable.component.ts b/src/app/cartography/components/experimental-map/draggable/draggable.component.ts new file mode 100644 index 00000000..e612b1eb --- /dev/null +++ b/src/app/cartography/components/experimental-map/draggable/draggable.component.ts @@ -0,0 +1,101 @@ +import { Component, OnInit, ElementRef, AfterViewInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; +import { Observable, Subscription } from 'rxjs'; +import { Point } from '../../../models/point'; + + +export class DraggableDraggedEvent { + constructor( + public x: number, + public y: number, + public dx: number, + public dy: number + ) {} +} + + +@Component({ + selector: '[app-draggable]', + template:``, + styleUrls: ['./draggable.component.scss'] +}) +export class DraggableComponent implements OnInit, AfterViewInit, OnDestroy { + @Input('app-draggable') item: Point; + @Output() dragging = new EventEmitter(); + @Output() dragged = new EventEmitter(); + + draggable: Subscription; + + private startX: number; + private startY: number; + + private posX: number; + private posY: number; + + constructor( + private elementRef: ElementRef + ) { } + + ngOnInit() { + } + + ngAfterViewInit() { + const down = Observable.fromEvent(this.elementRef.nativeElement, 'mousedown').do((e: MouseEvent) => e.preventDefault()) + + down.subscribe((e: MouseEvent) => { + this.posX = this.item.x; + this.posY = this.item.y; + + this.startX = e.clientX; + this.startY = e.clientY; + }); + + const up = Observable + .fromEvent(document, 'mouseup') + .do((e: MouseEvent) => { + e.preventDefault(); + }); + + const mouseMove = Observable + .fromEvent(document, 'mousemove') + .do((e: MouseEvent) => e.stopPropagation()); + + const scrollWindow = Observable + .fromEvent(document, 'scroll') + .startWith({}); + + const move = Observable.combineLatest(mouseMove, scrollWindow); + + const drag = down.mergeMap((md: MouseEvent) => { + return move + .map(([mm, s]) => mm) + .do((mm: MouseEvent) => { + const x = this.startX - mm.clientX; + const y = this.startY - mm.clientY; + + this.item.x = Math.round(this.posX - x); + this.item.y = Math.round(this.posY - y); + this.dragging.emit(new DraggableDraggedEvent(this.item.x, this.item.y, -x, -y)); + }) + .skipUntil(up + .take(1) + .do((e: MouseEvent) => { + const x = this.startX - e.clientX; + const y = this.startY - e.clientY; + + this.item.x = Math.round(this.posX - x); + this.item.y = Math.round(this.posY - y); + + this.dragged.emit(new DraggableDraggedEvent(this.item.x, this.item.y, -x, -y)); + })) + .take(1); + }); + + this.draggable = drag.subscribe((e: MouseEvent) => { + // this.cd.detectChanges(); + }); + } + + ngOnDestroy() { + this.draggable.unsubscribe(); + } +} diff --git a/src/app/cartography/components/experimental-map/drawing/drawing.component.html b/src/app/cartography/components/experimental-map/drawing/drawing.component.html new file mode 100644 index 00000000..abfcf9e2 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawing.component.html @@ -0,0 +1,32 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/drawing/drawing.component.scss b/src/app/cartography/components/experimental-map/drawing/drawing.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/drawing/drawing.component.spec.ts b/src/app/cartography/components/experimental-map/drawing/drawing.component.spec.ts new file mode 100644 index 00000000..d0ab0a6f --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawing.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DrawingComponent } from './drawing.component'; + +describe('DrawingComponent', () => { + let component: DrawingComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ DrawingComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(DrawingComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/drawing/drawing.component.ts b/src/app/cartography/components/experimental-map/drawing/drawing.component.ts new file mode 100644 index 00000000..e2bfc811 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawing.component.ts @@ -0,0 +1,71 @@ +import { Component, OnInit, Input, ChangeDetectorRef } from '@angular/core'; +import { EllipseElement } from '../../../models/drawings/ellipse-element'; +import { ImageElement } from '../../../models/drawings/image-element'; +import { LineElement } from '../../../models/drawings/line-element'; +import { RectElement } from '../../../models/drawings/rect-element'; +import { TextElement } from '../../../models/drawings/text-element'; +import { SvgToDrawingConverter } from '../../../helpers/svg-to-drawing-converter'; +import { DraggedDataEvent } from '../../../events/event-source'; +import { MapDrawing } from '../../../models/map/map-drawing'; +import { DrawingsEventSource } from '../../../events/drawings-event-source'; + +@Component({ + selector: '[app-drawing]', + templateUrl: './drawing.component.html', + styleUrls: ['./drawing.component.scss'] +}) +export class DrawingComponent implements OnInit { + @Input('app-drawing') drawing: MapDrawing; + + constructor( + private svgToDrawingConverter: SvgToDrawingConverter, + private drawingsEventSource: DrawingsEventSource, + private cd: ChangeDetectorRef, + ) { } + + ngOnInit() { + try { + this.drawing.element = this.svgToDrawingConverter.convert(this.drawing.svg); + } catch (error) { + console.log(`Cannot convert due to Error: '${error}'`); + } + } + + OnDragging(evt) { + this.drawing.x = evt.x; + this.drawing.y = evt.y; + this.cd.detectChanges(); + } + + OnDragged(evt) { + this.cd.detectChanges(); + this.drawingsEventSource.dragged.emit(new DraggedDataEvent(this.drawing, evt.dx, evt.dy)) + } + + is(element, type: string) { + if (!element) { + return false; + } + + if (type === "ellipse") { + return element instanceof EllipseElement; + } + if (type === "image") { + return element instanceof ImageElement; + } + if (type === "line") { + return element instanceof LineElement; + } + if (type === "rect") { + return element instanceof RectElement; + } + if (type === "text") { + return element instanceof TextElement; + } + return false; + } + + get transformation() { + return `translate(${this.drawing.x},${this.drawing.y}) rotate(${this.drawing.rotation})`; + } +} diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.html b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.html new file mode 100644 index 00000000..8c4a296b --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.html @@ -0,0 +1,12 @@ + diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.scss b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.spec.ts b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.spec.ts new file mode 100644 index 00000000..ac1e24ac --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EllipseComponent } from './ellipse.component'; + +describe('EllipseComponent', () => { + let component: EllipseComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ EllipseComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(EllipseComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.ts b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.ts new file mode 100644 index 00000000..c2bb0475 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/ellipse/ellipse.component.ts @@ -0,0 +1,40 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { EllipseElement } from '../../../../../models/drawings/ellipse-element'; +import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer'; + +@Component({ + selector: '[app-ellipse]', + templateUrl: './ellipse.component.html', + styleUrls: ['./ellipse.component.scss'] +}) +export class EllipseComponent implements OnInit { + @Input('app-ellipse') ellipse: EllipseElement; + + constructor( + private qtDasharrayFixer: QtDasharrayFixer + ) { } + + ngOnInit() { + } + + get fill_opacity() { + if(isFinite(this.ellipse.fill_opacity)) { + return this.ellipse.fill_opacity; + } + return null; + } + + get stroke_width() { + if(isFinite(this.ellipse.stroke_width)) { + return this.ellipse.stroke_width; + } + return null + } + + get stroke_dasharray() { + if(this.ellipse.stroke_dasharray) { + return this.qtDasharrayFixer.fix(this.ellipse.stroke_dasharray); + } + return null; + } +} diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.html b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.html new file mode 100644 index 00000000..6e36c391 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.html @@ -0,0 +1,6 @@ + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.scss b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.spec.ts b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.spec.ts new file mode 100644 index 00000000..6d8acaad --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ImageComponent } from './image.component'; + +describe('ImageComponent', () => { + let component: ImageComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ ImageComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ImageComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.ts b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.ts new file mode 100644 index 00000000..f151d5a2 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/image/image.component.ts @@ -0,0 +1,17 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { ImageElement } from '../../../../../models/drawings/image-element'; + +@Component({ + selector: '[app-image]', + templateUrl: './image.component.html', + styleUrls: ['./image.component.scss'] +}) +export class ImageComponent implements OnInit { + + @Input('app-image') image: ImageElement; + + constructor() { } + + ngOnInit() { + } +} diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.html b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.html new file mode 100644 index 00000000..d32f4207 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.scss b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.spec.ts b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.spec.ts new file mode 100644 index 00000000..64f2500f --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LineComponent } from './line.component'; + +describe('LineComponent', () => { + let component: LineComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LineComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LineComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.ts b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.ts new file mode 100644 index 00000000..a7f8fa06 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/line/line.component.ts @@ -0,0 +1,34 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer'; +import { LineElement } from '../../../../../models/drawings/line-element'; + +@Component({ + selector: '[app-line]', + templateUrl: './line.component.html', + styleUrls: ['./line.component.scss'] +}) +export class LineComponent implements OnInit { + @Input('app-line') line: LineElement; + + constructor( + private qtDasharrayFixer: QtDasharrayFixer + ) { } + + ngOnInit() { + } + + get stroke_width() { + if(isFinite(this.line.stroke_width)) { + return this.line.stroke_width; + } + return null + } + + get stroke_dasharray() { + if(this.line.stroke_dasharray) { + return this.qtDasharrayFixer.fix(this.line.stroke_dasharray); + } + return null; + } + +} diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.html b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.html new file mode 100644 index 00000000..1a0944f4 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.html @@ -0,0 +1,10 @@ + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.scss b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.spec.ts b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.spec.ts new file mode 100644 index 00000000..d7b1ea95 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RectComponent } from './rect.component'; + +describe('RectComponent', () => { + let component: RectComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ RectComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(RectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.ts b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.ts new file mode 100644 index 00000000..42878aab --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/rect/rect.component.ts @@ -0,0 +1,41 @@ +import { Component, OnInit, Input } from '@angular/core'; +import { RectElement } from '../../../../../models/drawings/rect-element'; +import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer'; + +@Component({ + selector: '[app-rect]', + templateUrl: './rect.component.html', + styleUrls: ['./rect.component.scss'] +}) +export class RectComponent implements OnInit { + @Input('app-rect') rect: RectElement; + + constructor( + private qtDasharrayFixer: QtDasharrayFixer + ) { } + + ngOnInit() { + } + + get fill_opacity() { + if(isFinite(this.rect.fill_opacity)) { + return this.rect.fill_opacity; + } + return null; + } + + get stroke_width() { + if(isFinite(this.rect.stroke_width)) { + return this.rect.stroke_width; + } + return null + } + + get stroke_dasharray() { + if(this.rect.stroke_dasharray) { + return this.qtDasharrayFixer.fix(this.rect.stroke_dasharray); + } + return null; + } + +} diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html new file mode 100644 index 00000000..a9f62c84 --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html @@ -0,0 +1,14 @@ + + {{line}} + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.scss b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.spec.ts b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.spec.ts new file mode 100644 index 00000000..845e947d --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TextComponent } from './text.component'; + +describe('TextComponent', () => { + let component: TextComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ TextComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(TextComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.ts b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.ts new file mode 100644 index 00000000..be113d5b --- /dev/null +++ b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.ts @@ -0,0 +1,68 @@ +import { Component, OnInit, Input, ViewChild, ElementRef, DoCheck } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { TextElement } from '../../../../../models/drawings/text-element'; +import { FontFixer } from '../../../../../helpers/font-fixer'; + +@Component({ + selector: '[app-text]', + templateUrl: './text.component.html', + styleUrls: ['./text.component.scss'] +}) +export class TextComponent implements OnInit, DoCheck { + static MARGIN = 4; + + @Input('app-text') text: TextElement; + + @ViewChild('text') textRef: ElementRef; + + lines: string[] = []; + + transformation = ""; + + constructor( + private fontFixer: FontFixer, + private sanitizer: DomSanitizer + ) { } + + ngOnInit() { + this.lines = this.getLines(this.text.text); + } + + ngDoCheck() { + this.transformation = this.calculateTransformation(); + } + + get style() { + const font = this.fontFixer.fix(this.text); + + const styles: string[] = []; + if (font.font_family) { + styles.push(`font-family: "${this.text.font_family}"`); + } + if (font.font_size) { + styles.push(`font-size: ${this.text.font_size}pt`); + } + if (font.font_weight) { + styles.push(`font-weight: ${this.text.font_weight}`); + } + return this.sanitizer.bypassSecurityTrustStyle(styles.join("; ")); + } + + get textDecoration() { + return this.text.text_decoration; + } + + calculateTransformation() { + const tspans = this.textRef.nativeElement.getElementsByTagName('tspan'); + if(tspans.length > 0) { + const height = this.textRef.nativeElement.getBBox().height / tspans.length; + return `translate(${TextComponent.MARGIN}, ${height - TextComponent.MARGIN})`; + } + return ''; + } + + getLines(text: string) { + return text.split(/\r?\n/) + } + +} diff --git a/src/app/cartography/components/experimental-map/experimental-map.component.html b/src/app/cartography/components/experimental-map/experimental-map.component.html new file mode 100644 index 00000000..2a918534 --- /dev/null +++ b/src/app/cartography/components/experimental-map/experimental-map.component.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/app/cartography/components/experimental-map/experimental-map.component.scss b/src/app/cartography/components/experimental-map/experimental-map.component.scss new file mode 100644 index 00000000..7fb7062f --- /dev/null +++ b/src/app/cartography/components/experimental-map/experimental-map.component.scss @@ -0,0 +1,5 @@ +svg { + display: block; +} + + diff --git a/src/app/cartography/components/experimental-map/experimental-map.component.spec.ts b/src/app/cartography/components/experimental-map/experimental-map.component.spec.ts new file mode 100644 index 00000000..0d5db4bb --- /dev/null +++ b/src/app/cartography/components/experimental-map/experimental-map.component.spec.ts @@ -0,0 +1,26 @@ +// import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +// import { MapComponent } from './map.component'; + +// describe('MapComponent', () => { +// let component: MapComponent; +// let fixture: ComponentFixture; + +// beforeEach(async(() => { +// TestBed.configureTestingModule({ +// declarations: [ MapComponent ] +// }) +// .compileComponents(); +// })); + +// // beforeEach(() => { +// // fixture = TestBed.createComponent(MapComponent); +// // component = fixture.componentInstance; +// // fixture.detectChanges(); +// // }); +// // +// // it('should create', () => { +// // expect(component).toBeTruthy(); +// // }); +// }); +// // \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/experimental-map.component.ts b/src/app/cartography/components/experimental-map/experimental-map.component.ts new file mode 100644 index 00000000..21442474 --- /dev/null +++ b/src/app/cartography/components/experimental-map/experimental-map.component.ts @@ -0,0 +1,131 @@ +import { + Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, + SimpleChange, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild +} from '@angular/core'; + +import { GraphLayout } from "../../widgets/graph-layout"; +import { Context } from "../../models/context"; +import { Size } from "../../models/size"; +import { Subscription } from 'rxjs'; +import { MapChangeDetectorRef } from '../../services/map-change-detector-ref'; +import { CanvasSizeDetector } from '../../helpers/canvas-size-detector'; +import { Node } from '../../models/node'; +import { Link } from '../../../models/link'; +import { Drawing } from '../../models/drawing'; +import { Symbol } from '../../../models/symbol'; +import { GraphDataManager } from '../../managers/graph-data-manager'; +import { LayersManager } from '../../managers/layers-manager'; + + +@Component({ + selector: 'app-experimental-map', + templateUrl: './experimental-map.component.html', + styleUrls: ['./experimental-map.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class ExperimentalMapComponent implements OnInit, OnChanges, OnDestroy { + @Input() nodes: Node[] = []; + @Input() links: Link[] = []; + @Input() drawings: Drawing[] = []; + @Input() symbols: Symbol[] = []; + // @Input() changed: EventEmitter; + + // @Input('node-updated') nodeUpdated: EventEmitter; + + @Input() width = 1500; + @Input() height = 600; + + @ViewChild('svg') svg: ElementRef; + + private changesDetected: Subscription; + + protected settings = { + 'show_interface_labels': true + }; + + constructor( + private graphDataManager: GraphDataManager, + private context: Context, + private mapChangeDetectorRef: MapChangeDetectorRef, + private canvasSizeDetector: CanvasSizeDetector, + private changeDetectorRef: ChangeDetectorRef, + private layersManger: LayersManager, + public graphLayout: GraphLayout, + ) { + } + + @Input('show-interface-labels') + set showInterfaceLabels(value) { + this.settings.show_interface_labels = value; + this.mapChangeDetectorRef.detectChanges(); + } + + @Input('moving-tool') + set movingTool(value) { + this.mapChangeDetectorRef.detectChanges(); + } + + @Input('selection-tool') + set selectionTool(value) { + this.mapChangeDetectorRef.detectChanges(); + } + + @Input('draw-link-tool') drawLinkTool: boolean; + + @Input('readonly') set readonly(value) { + } + + ngOnChanges(changes: { [propKey: string]: SimpleChange }) { + } + + ngOnInit() { + // this.changeDetectorRef.detach(); + + this.changesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => { + this.graphDataManager.setNodes(this.nodes); + this.graphDataManager.setLinks(this.links); + this.graphDataManager.setDrawings(this.drawings); + this.graphDataManager.setSymbols(this.symbols); + + this.changeDetectorRef.detectChanges(); + }); + + + // this.changedSubscription = this.changed.subscribe(() => { + // this.changeDetectorRef.detectChanges(); + // }); + + // this.nodeUpdated.subscribe((node: Node) => { + // this.nodeChanged.emit(node); + // }); + } + + ngOnDestroy() { + this.changesDetected.unsubscribe(); + // this.changedSubscription.unsubscribe(); + } + + public getSize(): Size { + return this.canvasSizeDetector.getOptimalSize(this.width, this.height); + } + + public get layers() { + return this.layersManger.getLayersList(); + } + + public get transform() { + const ctx = new Context(); + ctx.size = this.getSize(); + + const xTrans = ctx.getZeroZeroTransformationPoint().x + ctx.transformation.x; + const yTrans = ctx.getZeroZeroTransformationPoint().y + ctx.transformation.y; + const kTrans = ctx.transformation.k; + return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`; + } + + + @HostListener('window:resize', ['$event']) + onResize(event) { + + } +} diff --git a/src/app/cartography/components/experimental-map/interface-label/interface-label.component.html b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.html new file mode 100644 index 00000000..2a2317ad --- /dev/null +++ b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.html @@ -0,0 +1,28 @@ + + + + + {{ text }} + + + diff --git a/src/app/cartography/components/experimental-map/interface-label/interface-label.component.scss b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/interface-label/interface-label.component.spec.ts b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.spec.ts new file mode 100644 index 00000000..1e121d81 --- /dev/null +++ b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { InterfaceLabelComponent } from './interface-label.component'; + +describe('InterfaceLabelComponent', () => { + let component: InterfaceLabelComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ InterfaceLabelComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(InterfaceLabelComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/interface-label/interface-label.component.ts b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.ts new file mode 100644 index 00000000..d596cc3a --- /dev/null +++ b/src/app/cartography/components/experimental-map/interface-label/interface-label.component.ts @@ -0,0 +1,98 @@ +import { Component, OnInit, Input, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { CssFixer } from '../../../helpers/css-fixer'; + +@Component({ + selector: '[app-interface-label]', + templateUrl: './interface-label.component.html', + styleUrls: ['./interface-label.component.scss'] +}) +export class InterfaceLabelComponent implements OnInit { + @Input('app-interface-label') ignore: any; + + @ViewChild('textSvg') textRef: ElementRef; + + private label = { + 'x': 0, + 'y': 0, + 'text': '', + 'style': '', + 'rotation': 0 + }; + + borderSize = 5; + + textWidth = 0; + textHeight = 0; + + constructor( + private elementRef: ElementRef, + private ref: ChangeDetectorRef, + private sanitizer: DomSanitizer, + private cssFixer: CssFixer + ) { } + + ngOnInit() { + } + + @Input('x') + set x(value) { + this.label['x'] = value; + this.ref.detectChanges(); + } + + @Input('y') + set y(value) { + this.label['y'] = value; + this.ref.detectChanges(); + } + + @Input('text') + set text(value) { + this.label['text'] = value; + this.ref.detectChanges(); + } + + @Input('style') + set style(value) { + this.label['style'] = this.cssFixer.fix(value); + this.ref.detectChanges(); + } + + @Input('rotation') + set rotation(value) { + this.label['rotation'] = value; + this.ref.detectChanges(); + } + + get text() { + return this.label.text; + } + + get sanitizedStyle() { + return this.sanitizer.bypassSecurityTrustStyle(this.label.style); + } + + get rectX() { + return 0; + } + + get rectY() { + return -this.textRef.nativeElement.getBBox().height - this.borderSize; + } + + get rectWidth() { + return this.textRef.nativeElement.getBBox().width + this.borderSize*2; + } + + get rectHeight() { + return this.textRef.nativeElement.getBBox().height + this.borderSize; + } + + get transform() { + const bbox = this.elementRef.nativeElement.getBBox(); + const x = this.label.x; + const y = this.label.y + bbox.height; + return `translate(${x}, ${y}) rotate(${this.label.rotation}, ${x}, ${y})`; + } +} diff --git a/src/app/cartography/components/experimental-map/link/link.component.html b/src/app/cartography/components/experimental-map/link/link.component.html new file mode 100644 index 00000000..c9201b75 --- /dev/null +++ b/src/app/cartography/components/experimental-map/link/link.component.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/link/link.component.scss b/src/app/cartography/components/experimental-map/link/link.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/link/link.component.spec.ts b/src/app/cartography/components/experimental-map/link/link.component.spec.ts new file mode 100644 index 00000000..3642d967 --- /dev/null +++ b/src/app/cartography/components/experimental-map/link/link.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { LinkComponent } from './link.component'; + +describe('LinkComponent', () => { + let component: LinkComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ LinkComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(LinkComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/link/link.component.ts b/src/app/cartography/components/experimental-map/link/link.component.ts new file mode 100644 index 00000000..8c0d8d60 --- /dev/null +++ b/src/app/cartography/components/experimental-map/link/link.component.ts @@ -0,0 +1,65 @@ +import { + Component, OnInit, Input, ViewChild, + ElementRef, EventEmitter, ChangeDetectorRef, + OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { LinkStrategy } from './strategies/link-strategy'; +import { EthernetLinkStrategy } from './strategies/ethernet-link-strategy'; +import { SerialLinkStrategy } from './strategies/serial-link-strategy'; +import { MultiLinkCalculatorHelper } from '../../../helpers/multi-link-calculator-helper'; +import { Node } from '../../../models/node'; +import { MapLink } from '../../../models/map/map-link'; + + +@Component({ + selector: '[app-link]', + templateUrl: './link.component.html', + styleUrls: ['./link.component.scss'], +}) +export class LinkComponent implements OnInit, OnDestroy { + @Input('app-link') link: MapLink; + @Input('node-changed') nodeChanged: EventEmitter; + @Input('show-interface-labels') showInterfaceLabels: boolean; + + @ViewChild('path') path: ElementRef; + + private ethernetLinkStrategy = new EthernetLinkStrategy(); + private serialLinkStrategy = new SerialLinkStrategy(); + + private nodeChangedSubscription: Subscription; + + constructor( + private multiLinkCalculatorHelper: MultiLinkCalculatorHelper, + private ref: ChangeDetectorRef + ) {} + + ngOnInit() { + this.ref.detectChanges(); + // this.nodeChangedSubscription = this.nodeChanged.subscribe((node: Node) => { + // if (this.link.source.node_id === node.node_id || this.link.target.node_id === node.node_id) { + // this.ref.detectChanges(); + // } + // }); + } + + ngOnDestroy() { + // this.nodeChangedSubscription.unsubscribe(); + } + + get strategy(): LinkStrategy { + if (this.link.linkType === 'serial') { + return this.serialLinkStrategy; + } + return this.ethernetLinkStrategy; + } + + get transform() { + const translation = this.multiLinkCalculatorHelper.linkTranslation(this.link.distance, this.link.source, this.link.target); + return `translate (${translation.dx}, ${translation.dy})`; + } + + get d() { + return this.strategy.d(this.link); + } + +} diff --git a/src/app/cartography/components/experimental-map/link/strategies/ethernet-link-strategy.ts b/src/app/cartography/components/experimental-map/link/strategies/ethernet-link-strategy.ts new file mode 100644 index 00000000..f4767e72 --- /dev/null +++ b/src/app/cartography/components/experimental-map/link/strategies/ethernet-link-strategy.ts @@ -0,0 +1,18 @@ +import { LinkStrategy } from "./link-strategy"; +import { path } from "d3-path"; +import { MapLink } from "../../../../models/map/map-link"; + + +export class EthernetLinkStrategy implements LinkStrategy { + public d(link: MapLink): string { + const points = [ + [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.] + ]; + + const line_generator = path(); + line_generator.moveTo(points[0][0], points[0][1]); + line_generator.lineTo(points[1][0], points[1][1]); + return line_generator.toString(); + } +} \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/link/strategies/link-strategy.ts b/src/app/cartography/components/experimental-map/link/strategies/link-strategy.ts new file mode 100644 index 00000000..511c9e7f --- /dev/null +++ b/src/app/cartography/components/experimental-map/link/strategies/link-strategy.ts @@ -0,0 +1,5 @@ +import { MapLink } from "../../../../models/map/map-link"; + +export interface LinkStrategy { + d(link: MapLink): string; +} diff --git a/src/app/cartography/components/experimental-map/link/strategies/serial-link-strategy.ts b/src/app/cartography/components/experimental-map/link/strategies/serial-link-strategy.ts new file mode 100644 index 00000000..9dd64d2d --- /dev/null +++ b/src/app/cartography/components/experimental-map/link/strategies/serial-link-strategy.ts @@ -0,0 +1,55 @@ +import { path } from "d3-path"; +import { LinkStrategy } from "./link-strategy"; +import { MapLink } from "../../../../models/map/map-link"; + + +export class SerialLinkStrategy implements LinkStrategy { + private linkToPoints(link: MapLink) { + const source = { + 'x': link.source.x + link.source.width / 2, + 'y': link.source.y + link.source.height / 2 + }; + const target = { + 'x': link.target.x + link.target.width / 2, + 'y': link.target.y + link.target.height / 2 + }; + + const dx = target.x - source.x; + const dy = target.y - source.y; + + const vector_angle = Math.atan2(dy, dx); + const rot_angle = -Math.PI / 4.0; + const vect_rot = [ + Math.cos(vector_angle + rot_angle), + Math.sin(vector_angle + rot_angle) + ]; + + const angle_source: [number, number] = [ + source.x + dx / 2.0 + 15 * vect_rot[0], + source.y + dy / 2.0 + 15 * vect_rot[1] + ]; + + const angle_target: [number, number] = [ + target.x - dx / 2.0 - 15 * vect_rot[0], + target.y - dy / 2.0 - 15 * vect_rot[1] + ]; + + return [ + [source.x, source.y], + angle_source, + angle_target, + [target.x, target.y] + ]; + } + + d(link: MapLink): string { + const points = this.linkToPoints(link); + + const line_generator = path(); + line_generator.moveTo(points[0][0], points[0][1]); + line_generator.lineTo(points[1][0], points[1][1]); + line_generator.lineTo(points[2][0], points[2][1]); + line_generator.lineTo(points[3][0], points[3][1]); + return line_generator.toString(); + } +} diff --git a/src/app/cartography/components/experimental-map/node/node.component.html b/src/app/cartography/components/experimental-map/node/node.component.html new file mode 100644 index 00000000..eabce9c7 --- /dev/null +++ b/src/app/cartography/components/experimental-map/node/node.component.html @@ -0,0 +1,25 @@ + + + + {{ node.label.text }} + + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/node/node.component.scss b/src/app/cartography/components/experimental-map/node/node.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/node/node.component.spec.ts b/src/app/cartography/components/experimental-map/node/node.component.spec.ts new file mode 100644 index 00000000..4902d2ec --- /dev/null +++ b/src/app/cartography/components/experimental-map/node/node.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NodeComponent } from './node.component'; + +describe('NodeComponent', () => { + let component: NodeComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ NodeComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(NodeComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/node/node.component.ts b/src/app/cartography/components/experimental-map/node/node.component.ts new file mode 100644 index 00000000..2abc52b2 --- /dev/null +++ b/src/app/cartography/components/experimental-map/node/node.component.ts @@ -0,0 +1,119 @@ +import { + Component, OnInit, Input, ElementRef, + ViewChild, ChangeDetectorRef, ChangeDetectionStrategy, Output, + EventEmitter, OnDestroy, OnChanges, AfterViewInit } from '@angular/core'; +import { DomSanitizer } from '@angular/platform-browser'; +import { Subscription } from 'rxjs'; +import { CssFixer } from '../../../helpers/css-fixer'; +import { FontFixer } from '../../../helpers/font-fixer'; +import { Symbol } from '../../../../models/symbol'; +import { MapNode } from '../../../models/map/map-node'; +import { NodesEventSource } from '../../../events/nodes-event-source'; +import { DraggedDataEvent } from '../../../events/event-source'; + +@Component({ + selector: '[app-node]', + templateUrl: './node.component.html', + styleUrls: ['./node.component.scss'], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class NodeComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit { + static NODE_LABEL_MARGIN = 3; + + @ViewChild('label') label: ElementRef; + @ViewChild('image') imageRef: ElementRef; + + @Input('app-node') node: MapNode; + @Input('symbols') symbols: Symbol[]; + @Input('node-changed') nodeChanged: EventEmitter; + + // @Output() valueChange = new EventEmitter(); + + nodeChangedSubscription: Subscription; + + private labelHeight = 0; + + constructor( + private cssFixer: CssFixer, + private fontFixer: FontFixer, + private sanitizer: DomSanitizer, + protected element: ElementRef, + private cd: ChangeDetectorRef, + private nodesEventSource: NodesEventSource + ) { } + + ngOnInit() { + // this.nodeChangedSubscription = this.nodeChanged.subscribe((node: Node) => { + // if (node.node_id === this.node.node_id) { + // this.cd.detectChanges(); + // } + // }); + } + + ngOnDestroy() { + // this.nodeChangedSubscription.unsubscribe(); + } + + ngOnChanges(changes) { + this.cd.detectChanges(); + } + + ngAfterViewInit() { + this.labelHeight = this.getLabelHeight(); + // reload BBox + this.cd.detectChanges(); + } + + OnDragging(evt) { + this.node.x = evt.x; + this.node.y = evt.y; + this.cd.detectChanges(); + } + + OnDragged(evt) { + this.cd.detectChanges(); + this.nodesEventSource.dragged.emit(new DraggedDataEvent(this.node, evt.dx, evt.dy)) + } + + + get symbol(): string { + const symbol = this.symbols.find((s: Symbol) => s.symbol_id === this.node.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'; + } + + get label_style() { + + let styles = this.cssFixer.fix(this.node.label.style); + styles = this.fontFixer.fixStyles(styles); + return this.sanitizer.bypassSecurityTrustStyle(styles); + } + + get label_x(): number { + if (this.node.label.x === null) { + // center + const bbox = this.label.nativeElement.getBBox(); + + return -bbox.width / 2.; + } + return this.node.label.x + NodeComponent.NODE_LABEL_MARGIN; + } + + get label_y(): number { + this.labelHeight = this.getLabelHeight(); + + if (this.node.label.x === null) { + // center + return - this.node.height / 2. - this.labelHeight ; + } + return this.node.label.y + this.labelHeight - NodeComponent.NODE_LABEL_MARGIN; + } + + private getLabelHeight() { + const bbox = this.label.nativeElement.getBBox(); + return bbox.height; + } +} diff --git a/src/app/cartography/components/experimental-map/selection/selection.component.html b/src/app/cartography/components/experimental-map/selection/selection.component.html new file mode 100644 index 00000000..8dee5ced --- /dev/null +++ b/src/app/cartography/components/experimental-map/selection/selection.component.html @@ -0,0 +1,10 @@ + + + + diff --git a/src/app/cartography/components/experimental-map/selection/selection.component.scss b/src/app/cartography/components/experimental-map/selection/selection.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/selection/selection.component.spec.ts b/src/app/cartography/components/experimental-map/selection/selection.component.spec.ts new file mode 100644 index 00000000..111505c9 --- /dev/null +++ b/src/app/cartography/components/experimental-map/selection/selection.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SelectionComponent } from './selection.component'; + +describe('SelectionComponent', () => { + let component: SelectionComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SelectionComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/components/experimental-map/selection/selection.component.ts b/src/app/cartography/components/experimental-map/selection/selection.component.ts new file mode 100644 index 00000000..d7327a5d --- /dev/null +++ b/src/app/cartography/components/experimental-map/selection/selection.component.ts @@ -0,0 +1,122 @@ +import { Component, OnInit, Input, AfterViewInit, ChangeDetectorRef, Output, EventEmitter } from '@angular/core'; +import { Observable, Subscription, Subject } from 'rxjs'; +import { Rectangle } from '../../../models/rectangle'; + +@Component({ + selector: '[app-selection]', + templateUrl: './selection.component.html', + styleUrls: ['./selection.component.scss'] +}) +export class SelectionComponent implements OnInit, AfterViewInit { + @Input('app-selection') svg: SVGSVGElement; + + private startX: number; + private startY: number; + + private width: number; + private height: number; + + started = false; + visible = false; + draggable: Subscription; + + + @Output('selected') rectangleSelected = new EventEmitter(); + + constructor( + private ref: ChangeDetectorRef + ) { } + + ngOnInit() { + } + + + ngAfterViewInit() { + const down = Observable.fromEvent(this.svg, 'mousedown').do((e: MouseEvent) => e.preventDefault()); + + down.subscribe((e: MouseEvent) => { + if(e.target !== this.svg) { + return; + } + + this.started = true; + this.startX = e.clientX + window.scrollX; + this.startY = e.clientY + window.scrollY; + this.width = 0; + this.height = 0; + this.visible = true; + this.ref.detectChanges(); + }); + + const up = Observable.fromEvent(document, 'mouseup') + .do((e: MouseEvent) => { + e.preventDefault(); + }); + + const mouseMove = Observable.fromEvent(document, 'mousemove') + .do((e: MouseEvent) => e.stopPropagation()); + + const scrollWindow = Observable.fromEvent(document, 'scroll') + .startWith({}); + + const move = Observable.combineLatest(mouseMove, scrollWindow); + + const drag = down.mergeMap((md: MouseEvent) => { + return move + .map(([mm, s]) => mm) + .do((mm: MouseEvent) => { + if(!this.started) { + return; + } + this.visible = true; + this.width = mm.clientX - this.startX + window.scrollX; + this.height = mm.clientY - this.startY + window.scrollY; + + this.ref.detectChanges(); + + this.selectedEvent([this.startX, this.startY], [this.width, this.height]); + }) + .skipUntil(up + .take(1) + .do((e: MouseEvent) => { + if(!this.started) { + return; + } + this.visible = false; + this.started = false; + + this.width = e.clientX - this.startX + window.scrollX; + this.height = e.clientY - this.startY + window.scrollY; + + this.ref.detectChanges(); + + this.selectedEvent([this.startX, this.startY], [this.width, this.height]); + })) + .take(1); + }); + + this.draggable = drag.subscribe((e: MouseEvent) => { + // this.cd.detectChanges(); + }); + } + + ngOnDestroy() { + this.draggable.unsubscribe(); + } + + get d() { + return this.rect(this.startX, this.startY, this.width, this.height); + } + + private rect(x: number, y: number, w: number, h: number) { + return "M" + [x, y] + " l" + [w, 0] + " l" + [0, h] + " l" + [-w, 0] + "z"; + } + + private selectedEvent(start, end) { + const x = Math.min(start[0], end[0]); + const y = Math.min(start[1], end[1]); + const width = Math.abs(start[0] - end[0]); + const height = Math.abs(start[1] - end[1]); + this.rectangleSelected.emit(new Rectangle(x, y, width, height)); + } +} diff --git a/src/app/cartography/components/experimental-map/status/status.component.html b/src/app/cartography/components/experimental-map/status/status.component.html new file mode 100644 index 00000000..cfcef840 --- /dev/null +++ b/src/app/cartography/components/experimental-map/status/status.component.html @@ -0,0 +1,21 @@ + + + + + \ No newline at end of file diff --git a/src/app/cartography/components/experimental-map/status/status.component.scss b/src/app/cartography/components/experimental-map/status/status.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/experimental-map/status/status.component.spec.ts b/src/app/cartography/components/experimental-map/status/status.component.spec.ts new file mode 100644 index 00000000..ddf4f677 --- /dev/null +++ b/src/app/cartography/components/experimental-map/status/status.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { StatusComponent } from './status.component'; + +describe('StatusComponent', () => { + let component: StatusComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ StatusComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(StatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/cartography/components/experimental-map/status/status.component.ts b/src/app/cartography/components/experimental-map/status/status.component.ts new file mode 100644 index 00000000..05d58efc --- /dev/null +++ b/src/app/cartography/components/experimental-map/status/status.component.ts @@ -0,0 +1,83 @@ +import { Component, ElementRef, Input, ChangeDetectorRef } from '@angular/core'; + + +@Component({ + selector: '[app-status]', + templateUrl: './status.component.html', + styleUrls: ['./status.component.scss'], +}) +export class StatusComponent { + static STOPPED_STATUS_RECT_WIDTH = 10; + + data = { + 'status': '', + 'path': null, + 'direction': null, + 'd': null + }; + + constructor( + protected element: ElementRef, + private ref: ChangeDetectorRef + ) {} + + @Input('app-status') + set status(value) { + this.data.status = value; + this.ref.markForCheck(); + } + + @Input('path') + set path(value) { + this.data.path = value; + this.ref.markForCheck(); + } + + @Input('direction') + set direction(value) { + this.data.direction = value; + this.ref.markForCheck(); + } + + @Input('d') + set d(value) { + if (this.data.d !== value) { + this.data.d = value; + this.ref.markForCheck(); + } + } + + get status() { + return this.data.status; + } + + get direction() { + return this.data.direction; + } + + get path() { + return this.data.path; + } + + get sourceStatusPoint() { + if (!this.path) { + return null; + } + return this.path.nativeElement.getPointAtLength(45); + } + + get targetStatusPoint() { + if (!this.path) { + return null; + } + return this.path.nativeElement.getPointAtLength(this.path.nativeElement.getTotalLength() - 45); + } + + get point() { + if (this.direction === 'source') { + return this.sourceStatusPoint; + } + return this.targetStatusPoint; + } + +} diff --git a/src/app/cartography/components/map/map.component.html b/src/app/cartography/components/map/map.component.html deleted file mode 100644 index dc9747ac..00000000 --- a/src/app/cartography/components/map/map.component.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/src/app/cartography/components/selection-control/selection-control.component.html b/src/app/cartography/components/selection-control/selection-control.component.html new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/selection-control/selection-control.component.scss b/src/app/cartography/components/selection-control/selection-control.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/selection-control/selection-control.component.spec.ts b/src/app/cartography/components/selection-control/selection-control.component.spec.ts new file mode 100644 index 00000000..af1fecd6 --- /dev/null +++ b/src/app/cartography/components/selection-control/selection-control.component.spec.ts @@ -0,0 +1,72 @@ +import { fakeAsync, tick } from '@angular/core/testing'; + +import { SelectionControlComponent } from './selection-control.component'; +import { SelectionManager } from '../../managers/selection-manager'; +import { SelectionEventSource } from '../../events/selection-event-source'; +import { mock, when, instance } from 'ts-mockito'; +import { GraphDataManager } from '../../managers/graph-data-manager'; +import { MapNode } from '../../models/map/map-node'; +import { MapLink } from '../../models/map/map-link'; +import { InRectangleHelper } from '../../helpers/in-rectangle-helper'; +import { Rectangle } from '../../models/rectangle'; + +describe('SelectionControlComponent', () => { + let component: SelectionControlComponent; + let manager: SelectionManager; + let selectionEventSource: SelectionEventSource; + + 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(); + + selectionEventSource = new SelectionEventSource(); + manager = new SelectionManager(); + + component = new SelectionControlComponent(selectionEventSource, graphData, inRectangleHelper, manager); + component.ngOnInit(); + }); + + afterEach(() => { + component.ngOnDestroy(); + }) + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('node should be selected', fakeAsync(() => { + selectionEventSource.selected.next(new Rectangle(100, 100, 100, 100)); + tick(); + expect(manager.getSelected().length).toEqual(1); + })); + + it('node should be selected and deselected', fakeAsync(() => { + selectionEventSource.selected.next(new Rectangle(100, 100, 100, 100)); + tick(); + selectionEventSource.selected.next(new Rectangle(350, 350, 100, 100)); + tick(); + expect(manager.getSelected().length).toEqual(0); + })); +}); diff --git a/src/app/cartography/components/selection-control/selection-control.component.ts b/src/app/cartography/components/selection-control/selection-control.component.ts new file mode 100644 index 00000000..81abb854 --- /dev/null +++ b/src/app/cartography/components/selection-control/selection-control.component.ts @@ -0,0 +1,86 @@ +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { SelectionEventSource } from '../../events/selection-event-source'; +import { GraphDataManager } from '../../managers/graph-data-manager'; +import { InRectangleHelper } from '../../helpers/in-rectangle-helper'; +import { SelectionManager } from '../../managers/selection-manager'; +import { Rectangle } from '../../models/rectangle'; + +@Component({ + selector: 'app-selection-control', + templateUrl: './selection-control.component.html', + styleUrls: ['./selection-control.component.scss'] +}) +export class SelectionControlComponent implements OnInit, OnDestroy { + private onSelection: Subscription; + + constructor( + private selectionEventSource: SelectionEventSource, + private graphDataManager: GraphDataManager, + private inRectangleHelper: InRectangleHelper, + private selectionManager: SelectionManager + ) { } + + ngOnInit() { + this.onSelection = this.selectionEventSource.selected.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 selectedLabels = this.graphDataManager.getNodes().filter((node) => { + if (node.label === undefined) { + return false; + } + const labelX = node.x + node.label.x; + const labelY = node.y + node.label.y; + return this.inRectangleHelper.inRectangle(rectangle, labelX, labelY); + }).map((node) => node.label); + + const selectedInterfacesLabelsSources = this.graphDataManager.getLinks().filter((link) => { + if (link.source === undefined || link.nodes.length != 2 || link.nodes[0].label === undefined) { + return false; + } + const interfaceLabelX = link.source.x + link.nodes[0].label.x; + const interfaceLabelY = link.source.y + link.nodes[0].label.y; + return this.inRectangleHelper.inRectangle(rectangle, interfaceLabelX, interfaceLabelY); + }).map((link) => link.nodes[0]); + + const selectedInterfacesLabelsTargets = this.graphDataManager.getLinks().filter((link) => { + if (link.target === undefined || link.nodes.length != 2 || link.nodes[1].label === undefined) { + return false; + } + const interfaceLabelX = link.target.x + link.nodes[1].label.x; + const interfaceLabelY = link.target.y + link.nodes[1].label.y; + return this.inRectangleHelper.inRectangle(rectangle, interfaceLabelX, interfaceLabelY); + }).map((link) => link.nodes[1]); + + const selectedInterfaces = [ + ...selectedInterfacesLabelsSources, + ...selectedInterfacesLabelsTargets, + ] + + const selected = [ + ...selectedNodes, + ...selectedLinks, + ...selectedDrawings, + ...selectedLabels, + ...selectedInterfaces, + ]; + + this.selectionManager.setSelected(selected); + }); + } + + ngOnDestroy() { + this.onSelection.unsubscribe(); + } + +} diff --git a/src/app/cartography/components/selection-select/selection-select.component.html b/src/app/cartography/components/selection-select/selection-select.component.html new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/selection-select/selection-select.component.scss b/src/app/cartography/components/selection-select/selection-select.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/selection-select/selection-select.component.spec.ts b/src/app/cartography/components/selection-select/selection-select.component.spec.ts new file mode 100644 index 00000000..3af1b594 --- /dev/null +++ b/src/app/cartography/components/selection-select/selection-select.component.spec.ts @@ -0,0 +1,25 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; + +import { SelectionSelectComponent } from './selection-select.component'; + +describe('SelectionSelectComponent', () => { + let component: SelectionSelectComponent; + let fixture: ComponentFixture; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ SelectionSelectComponent ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SelectionSelectComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // it('should create', () => { + // expect(component).toBeTruthy(); + // }); +}); diff --git a/src/app/cartography/listeners/selection-update-listener.ts b/src/app/cartography/components/selection-select/selection-select.component.ts similarity index 50% rename from src/app/cartography/listeners/selection-update-listener.ts rename to src/app/cartography/components/selection-select/selection-select.component.ts index 8272c282..fffba5d7 100644 --- a/src/app/cartography/listeners/selection-update-listener.ts +++ b/src/app/cartography/components/selection-select/selection-select.component.ts @@ -1,21 +1,23 @@ -import { Injectable } from "@angular/core"; -import { Subscription } from "rxjs"; -import { MapChangeDetectorRef } from "../services/map-change-detector-ref"; -import { SelectionManager } from "../managers/selection-manager"; +import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Subscription } from 'rxjs'; +import { SelectionManager } from '../../managers/selection-manager'; +import { MapChangeDetectorRef } from '../../services/map-change-detector-ref'; - -@Injectable() -export class SelectionUpdateListener { +@Component({ + selector: 'app-selection-select', + templateUrl: './selection-select.component.html', + styleUrls: ['./selection-select.component.scss'] +}) +export class SelectionSelectComponent implements OnInit, OnDestroy { private onSelected: Subscription; private onUnselected: Subscription; - + constructor( private selectionManager: SelectionManager, private mapChangeDetectorRef: MapChangeDetectorRef - ) { - } + ) { } - public onInit(svg: any) { + ngOnInit() { this.onSelected = this.selectionManager.selected.subscribe(() => { this.mapChangeDetectorRef.detectChanges(); }); @@ -24,8 +26,9 @@ export class SelectionUpdateListener { }); } - public onDestroy() { + ngOnDestroy() { this.onSelected.unsubscribe(); this.onUnselected.unsubscribe(); } -} \ No newline at end of file + +} 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 index e6d2ff09..26a7af35 100644 --- a/src/app/cartography/converters/map/drawing-to-map-drawing-converter.ts +++ b/src/app/cartography/converters/map/drawing-to-map-drawing-converter.ts @@ -7,18 +7,18 @@ 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; - } + 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 index 0501978f..77a3b2e4 100644 --- a/src/app/cartography/converters/map/label-to-map-label-converter.ts +++ b/src/app/cartography/converters/map/label-to-map-label-converter.ts @@ -3,17 +3,45 @@ import { Injectable } from "@angular/core"; import { Converter } from "../converter"; import { Label } from "../../models/label"; import { MapLabel } from "../../models/map/map-label"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; +import { CssFixer } from '../../helpers/css-fixer'; +import { FontFixer } from '../../helpers/font-fixer'; @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; + constructor( + private fontBBoxCalculator: FontBBoxCalculator, + private cssFixer: CssFixer, + private fontFixer: FontFixer + ) {} + convert(label: Label, paramaters?: {[node_id: string]: string}) { + const mapLabel = new MapLabel(); + mapLabel.rotation = label.rotation; + mapLabel.style = label.style; + mapLabel.text = label.text; + mapLabel.x = label.x; + mapLabel.y = label.y; + mapLabel.originalX = label.x; + mapLabel.originalY = label.y; + + if (paramaters !== undefined) { + mapLabel.id = paramaters.node_id; + mapLabel.nodeId = paramaters.node_id; } + + const fixedCss = this.cssFixer.fix(mapLabel.style); + const fixedFont = this.fontFixer.fixStyles(fixedCss); + const box = this.fontBBoxCalculator.calculate(mapLabel.text, fixedFont); + + if (mapLabel.x !== null) { + mapLabel.x += 3; + } + + if (mapLabel.y !== null) { + mapLabel.y += box.height; + } + + 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 index 48f3cc2e..794ccc16 100644 --- 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 @@ -9,15 +9,21 @@ import { MapLinkNode } from "../../models/map/map-link-node"; @Injectable() export class LinkNodeToMapLinkNodeConverter implements Converter { constructor( - private labelToMapLabel: LabelToMapLabelConverter + private labelToMapLabel: LabelToMapLabelConverter, ) {} - convert(linkNode: LinkNode) { + convert(linkNode: LinkNode, paramaters?: {[link_id: string]: string}) { 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); + + if (paramaters !== undefined) { + mapLinkNode.linkId = paramaters.link_id; + mapLinkNode.id = `${mapLinkNode.nodeId}-${mapLinkNode.linkId}`; + } + 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 index 0000ad78..71c052f4 100644 --- a/src/app/cartography/converters/map/link-to-map-link-converter.ts +++ b/src/app/cartography/converters/map/link-to-map-link-converter.ts @@ -19,7 +19,7 @@ export class LinkToMapLinkConverter implements Converter { 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.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode,{ link_id: link.link_id })); mapLink.projectId = link.project_id; return mapLink; } 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 index 1a0ca88d..7ec55f1a 100644 --- a/src/app/cartography/converters/map/map-label-to-label-converter.ts +++ b/src/app/cartography/converters/map/map-label-to-label-converter.ts @@ -3,17 +3,33 @@ import { Injectable } from "@angular/core"; import { Converter } from "../converter"; import { Label } from "../../models/label"; import { MapLabel } from "../../models/map/map-label"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; @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; + constructor( + private fontBBoxCalculator: FontBBoxCalculator + ) {} + + convert(mapLabel: MapLabel) { + const box = this.fontBBoxCalculator.calculate(mapLabel.text, mapLabel.style); + + const label = new Label(); + label.rotation = mapLabel.rotation; + label.style = mapLabel.style; + label.text = mapLabel.text; + label.x = mapLabel.x; + label.y = mapLabel.y; + + if (label.x !== null) { + label.x += 3; } + + if (label.y !== null) { + label.y -= box.height; + } + + return label; + } } 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 index d8d02111..86e9fa40 100644 --- a/src/app/cartography/converters/map/map-node-to-node-converter.ts +++ b/src/app/cartography/converters/map/map-node-to-node-converter.ts @@ -5,38 +5,41 @@ 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"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; +import { CssFixer } from '../../helpers/css-fixer'; +import { FontFixer } from '../../helpers/font-fixer'; @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; - } + 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/node-to-map-node-converter.ts b/src/app/cartography/converters/map/node-to-map-node-converter.ts index 2019d1a5..c1a25e67 100644 --- a/src/app/cartography/converters/map/node-to-map-node-converter.ts +++ b/src/app/cartography/converters/map/node-to-map-node-converter.ts @@ -5,38 +5,56 @@ 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"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; +import { CssFixer } from '../../helpers/css-fixer'; +import { FontFixer } from '../../helpers/font-fixer'; @Injectable() export class NodeToMapNodeConverter implements Converter { - constructor( - private labelToMapLabel: LabelToMapLabelConverter, - private portToMapPort: PortToMapPortConverter - ) {} + constructor( + private labelToMapLabel: LabelToMapLabelConverter, + private portToMapPort: PortToMapPortConverter, + private fontBBoxCalculator: FontBBoxCalculator, + private cssFixer: CssFixer, + private fontFixer: FontFixer + ) {} - 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; + 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, { node_id: node.node_id }); + 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; + + + if (mapNode.label !== undefined) { + const fixedCss = this.cssFixer.fix(mapNode.label.style); + const fixedFont = this.fontFixer.fixStyles(fixedCss); + const box = this.fontBBoxCalculator.calculate(mapNode.label.text, fixedFont); + + if (node.label.x === null || node.label.y === null) { + mapNode.label.x = node.width / 2. - box.width / 2. + 3; + mapNode.label.y = -8; + } } + return mapNode; + } } diff --git a/src/app/cartography/converters/styles-to-font-converter.spec.ts b/src/app/cartography/converters/styles-to-font-converter.spec.ts new file mode 100644 index 00000000..5eda7947 --- /dev/null +++ b/src/app/cartography/converters/styles-to-font-converter.spec.ts @@ -0,0 +1,24 @@ +import { Font } from "../models/font"; +import { StylesToFontConverter } from './styles-to-font-converter'; + + +describe('StylesToFontConverter', () => { + let converter: StylesToFontConverter; + + beforeEach(() => { + converter = new StylesToFontConverter(); + }); + + it('should parse fonts from styles', () => { + const styles = "font-family: TypeWriter; font-size: 10px; font-weight: bold"; + + const expectedFont: Font = { + 'font_family': 'TypeWriter', + 'font_size': 10, + 'font_weight': 'bold' + }; + + expect(converter.convert(styles)).toEqual(expectedFont); + }); + +}); diff --git a/src/app/cartography/converters/styles-to-font-converter.ts b/src/app/cartography/converters/styles-to-font-converter.ts new file mode 100644 index 00000000..0c2f8826 --- /dev/null +++ b/src/app/cartography/converters/styles-to-font-converter.ts @@ -0,0 +1,47 @@ +import * as csstree from 'css-tree'; + +import { Injectable } from "@angular/core"; +import { Converter } from './converter'; +import { Font } from '../models/font'; + + +@Injectable() +export class StylesToFontConverter implements Converter { + convert(styles: string) { + const font: Font = { + 'font_family': undefined, + 'font_size': undefined, + 'font_weight': undefined + }; + + const ast = csstree.parse(styles, { + 'context': 'declarationList' + }); + + ast.children.forEach((child) => { + if (child.property === 'font-size') { + child.value.children.forEach((value) => { + if (value.type === 'Dimension') { + font.font_size = parseInt(value.value); + } + }); + } + if (child.property === 'font-family') { + child.value.children.forEach((value) => { + if (value.type === "Identifier") { + font.font_family = value.name; + } + }); + } + if (child.property === 'font-weight') { + child.value.children.forEach((value) => { + if (value.type === "Identifier") { + font.font_weight = value.name; + } + }); + } + }); + + return font; + } +} diff --git a/src/app/cartography/d3-map.imports.ts b/src/app/cartography/d3-map.imports.ts index 4fbe4f32..25aa4e27 100644 --- a/src/app/cartography/d3-map.imports.ts +++ b/src/app/cartography/d3-map.imports.ts @@ -16,12 +16,14 @@ import { TextDrawingWidget } from './widgets/drawings/text-drawing'; import { LineDrawingWidget } from './widgets/drawings/line-drawing'; import { NodeWidget } from './widgets/node'; import { DrawingWidget } from './widgets/drawing'; +import { LabelWidget } from './widgets/label'; export const D3_MAP_IMPORTS = [ GraphLayout, LinksWidget, NodesWidget, NodeWidget, + LabelWidget, DrawingsWidget, DrawingLineWidget, SelectionTool, diff --git a/src/app/cartography/events/draggable.ts b/src/app/cartography/events/draggable.ts index 1b616ab5..8bcd5f18 100644 --- a/src/app/cartography/events/draggable.ts +++ b/src/app/cartography/events/draggable.ts @@ -44,9 +44,13 @@ export class Draggable { private behaviour() { let startEvt; - + let lastX: number; + let lastY: number; return drag() .on('start', (datum: Datum) => { + lastX = event.sourceEvent.clientX; + lastY = event.sourceEvent.clientY; + startEvt = new DraggableStart(datum); startEvt.dx = event.dx; startEvt.dy = event.dy; @@ -56,18 +60,17 @@ export class Draggable { }) .on('drag', (datum: Datum) => { const evt = new DraggableDrag(datum); - evt.dx = event.dx; - evt.dy = event.dy; - evt.x = event.x; - evt.y = event.y; + evt.dx = event.sourceEvent.clientX - lastX; + evt.dy = event.sourceEvent.clientY - lastY; + lastX = event.sourceEvent.clientX; + lastY = event.sourceEvent.clientY; + this.drag.emit(evt); }) .on('end', (datum: Datum) => { const evt = new DraggableEnd(datum); 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/event-source.ts b/src/app/cartography/events/event-source.ts index c6a2368a..c108097c 100644 --- a/src/app/cartography/events/event-source.ts +++ b/src/app/cartography/events/event-source.ts @@ -7,3 +7,11 @@ export class DataEventSource { } export class DraggedDataEvent extends DataEventSource {} + +export class ClickedDataEvent { + constructor( + public datum: T, + public x: number, + public y: number + ) {} +} \ No newline at end of file diff --git a/src/app/cartography/events/links-event-source.ts b/src/app/cartography/events/links-event-source.ts index 5632a0e4..71d091cd 100644 --- a/src/app/cartography/events/links-event-source.ts +++ b/src/app/cartography/events/links-event-source.ts @@ -1,8 +1,11 @@ import { Injectable, EventEmitter } from "@angular/core"; import { MapLinkCreated } from "./links"; +import { MapLinkNode } from "../models/map/map-link-node"; +import { DraggedDataEvent } from "./event-source"; @Injectable() export class LinksEventSource { public created = new EventEmitter(); + public interfaceDragged = new EventEmitter>(); } diff --git a/src/app/cartography/events/nodes-event-source.ts b/src/app/cartography/events/nodes-event-source.ts index 1930828b..4f7d3c3e 100644 --- a/src/app/cartography/events/nodes-event-source.ts +++ b/src/app/cartography/events/nodes-event-source.ts @@ -1,9 +1,12 @@ import { Injectable, EventEmitter } from "@angular/core"; -import { DraggedDataEvent } from "./event-source"; +import { DraggedDataEvent, ClickedDataEvent } from "./event-source"; import { MapNode } from "../models/map/map-node"; +import { MapLabel } from "../models/map/map-label"; @Injectable() export class NodesEventSource { public dragged = new EventEmitter>(); + public labelDragged = new EventEmitter>(); + public clicked = new EventEmitter>(); } diff --git a/src/app/cartography/events/selection-event-source.ts b/src/app/cartography/events/selection-event-source.ts new file mode 100644 index 00000000..c97aef44 --- /dev/null +++ b/src/app/cartography/events/selection-event-source.ts @@ -0,0 +1,9 @@ +import { Injectable } from "@angular/core"; +import { Subject } from "rxjs"; +import { Rectangle } from "../models/rectangle"; + + +@Injectable() +export class SelectionEventSource { + public selected = new Subject(); +} \ No newline at end of file diff --git a/src/app/cartography/helpers/css-fixer.ts b/src/app/cartography/helpers/css-fixer.ts index fe213eb6..c5a8a226 100644 --- a/src/app/cartography/helpers/css-fixer.ts +++ b/src/app/cartography/helpers/css-fixer.ts @@ -6,7 +6,7 @@ import { Injectable } from "@angular/core"; @Injectable() export class CssFixer { - public fix(styles: string) { + public fix(styles: string): string { const ast = csstree.parse(styles, { 'context': 'declarationList' }); diff --git a/src/app/cartography/helpers/font-bbox-calculator.spec.ts b/src/app/cartography/helpers/font-bbox-calculator.spec.ts new file mode 100644 index 00000000..52417102 --- /dev/null +++ b/src/app/cartography/helpers/font-bbox-calculator.spec.ts @@ -0,0 +1,24 @@ +import { FontBBoxCalculator } from "./font-bbox-calculator"; + + +describe('FontBBoxCalculator', () => { + let calculator: FontBBoxCalculator; + + beforeEach(() => { + calculator = new FontBBoxCalculator(); + }); + + it('should calculate font width and height', () => { + const box = calculator.calculate("My text", "font-family:Arial; font-size: 12px; font-weight:bold"); + + expect(box.height).toEqual(14); + expect(box.width).toEqual(41.34375); + }); + + it('should calculate font width and height for different font', () => { + const box = calculator.calculate("My text", "font-family:Tahoma; font-size: 14px; font-weight:bold"); + + expect(box.height).toEqual(15); + expect(box.width).toEqual(46.25); + }); +}); diff --git a/src/app/cartography/helpers/font-bbox-calculator.ts b/src/app/cartography/helpers/font-bbox-calculator.ts new file mode 100644 index 00000000..cf689e9b --- /dev/null +++ b/src/app/cartography/helpers/font-bbox-calculator.ts @@ -0,0 +1,20 @@ +import { Injectable } from "@angular/core"; + +@Injectable() +export class FontBBoxCalculator { + calculate(text: string, styles: string) { + const element = document.createElement("text"); + element.innerText = text; + element.setAttribute("fill", "#00000"); + element.setAttribute("fill-opacity", "0"); + element.setAttribute("style", styles); + document.documentElement.appendChild(element); + const bbox = element.getBoundingClientRect(); + document.documentElement.removeChild(element); + + return { + width: bbox.width, + height: bbox.height + } + } +} \ No newline at end of file diff --git a/src/app/cartography/listeners/draggable-listener.ts b/src/app/cartography/listeners/draggable-listener.ts deleted file mode 100644 index c2406491..00000000 --- a/src/app/cartography/listeners/draggable-listener.ts +++ /dev/null @@ -1,105 +0,0 @@ -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/map-listener.ts b/src/app/cartography/listeners/map-listener.ts deleted file mode 100644 index 50c7a2bb..00000000 --- a/src/app/cartography/listeners/map-listener.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface MapListener { - onInit(svg: any); - onDestroy(); -} \ No newline at end of file diff --git a/src/app/cartography/listeners/map-listeners.ts b/src/app/cartography/listeners/map-listeners.ts deleted file mode 100644 index 40752896..00000000 --- a/src/app/cartography/listeners/map-listeners.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Injectable } from "@angular/core"; -import { MapListener } from "./map-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 nodesDraggableListener: DraggableListener, - private selectionUpdateListener: SelectionUpdateListener, - private selectionListener: SelectionListener - ) { - this.listeners.push(this.nodesDraggableListener); - this.listeners.push(this.selectionUpdateListener); - this.listeners.push(this.selectionListener); - } - - public onInit(svg: any) { - this.listeners.forEach((listener) => { - listener.onInit(svg); - }); - } - - public onDestroy() { - this.listeners.forEach((listener) => { - listener.onDestroy(); - }); - } -} \ 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 deleted file mode 100644 index 5c8436ef..00000000 --- a/src/app/cartography/listeners/selection-listener.spec.ts +++ /dev/null @@ -1,68 +0,0 @@ -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 deleted file mode 100644 index b97e366c..00000000 --- a/src/app/cartography/listeners/selection-listener.ts +++ /dev/null @@ -1,45 +0,0 @@ -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/managers/graph-data-manager.ts b/src/app/cartography/managers/graph-data-manager.ts index 7e6516d0..2b7e5621 100644 --- a/src/app/cartography/managers/graph-data-manager.ts +++ b/src/app/cartography/managers/graph-data-manager.ts @@ -11,6 +11,7 @@ import { Drawing } from "../models/drawing"; import { Symbol } from "../../models/symbol"; import { LayersManager } from "./layers-manager"; import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from "../datasources/map-datasource"; +import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper"; @Injectable() export class GraphDataManager { @@ -23,7 +24,8 @@ export class GraphDataManager { private linkToMapLink: LinkToMapLinkConverter, private drawingToMapDrawing: DrawingToMapDrawingConverter, private symbolToMapSymbol: SymbolToMapSymbolConverter, - private layersManager: LayersManager + private layersManager: LayersManager, + private multiLinkCalculator: MultiLinkCalculatorHelper ) {} public setNodes(nodes: Node[]) { @@ -98,6 +100,8 @@ export class GraphDataManager { link.y = link.source.y + (link.target.y - link.source.y) * 0.5; } }); + + this.multiLinkCalculator.assignDataToLinks(this.getLinks()); } } \ No newline at end of file diff --git a/src/app/cartography/managers/map-settings-manager.ts b/src/app/cartography/managers/map-settings-manager.ts new file mode 100644 index 00000000..b0cc4ffb --- /dev/null +++ b/src/app/cartography/managers/map-settings-manager.ts @@ -0,0 +1,7 @@ +import { Injectable } from "@angular/core"; + + +@Injectable() +export class MapSettingsManager { + public isReadOnly = false; +} \ No newline at end of file diff --git a/src/app/cartography/managers/selection-manager.spec.ts b/src/app/cartography/managers/selection-manager.spec.ts index 85746789..e2963a8c 100644 --- a/src/app/cartography/managers/selection-manager.spec.ts +++ b/src/app/cartography/managers/selection-manager.spec.ts @@ -1,6 +1,15 @@ import { MapNode } from "../models/map/map-node"; import { SelectionManager } from "./selection-manager"; +export class SelectionManagerMock { + public items = []; + setSelected(items: any) { + this.items = items; + } + getSelected() { + return this.items; + } +} describe('SelectionManager', () => { let manager: SelectionManager; diff --git a/src/app/cartography/managers/selection-manager.ts b/src/app/cartography/managers/selection-manager.ts index 2f7b0dd3..40817618 100644 --- a/src/app/cartography/managers/selection-manager.ts +++ b/src/app/cartography/managers/selection-manager.ts @@ -23,8 +23,13 @@ export class SelectionManager { this.selection = dictItems; - this.selected.emit(selected); - this.unselected.emit(unselected); + if (selected.length > 0) { + this.selected.emit(selected); + } + + if (unselected.length > 0) { + this.unselected.emit(unselected); + } } public getSelected(): Indexed[] { diff --git a/src/app/cartography/models/interface-label.ts b/src/app/cartography/models/interface-label.ts deleted file mode 100644 index 9f2e654c..00000000 --- a/src/app/cartography/models/interface-label.ts +++ /dev/null @@ -1,11 +0,0 @@ -export class InterfaceLabel { - constructor( - public link_id: string, - public direction: string, - public x: number, - public y: number, - public text: string, - public style: string, - public rotation = 0, - ) {} -} diff --git a/src/app/cartography/models/map/map-label.ts b/src/app/cartography/models/map/map-label.ts index fd1ffbd0..a42dd193 100644 --- a/src/app/cartography/models/map/map-label.ts +++ b/src/app/cartography/models/map/map-label.ts @@ -1,8 +1,13 @@ -export class MapLabel { - rotation: number; - style: string; - text: string; - x: number; - y: number; - isSelected: boolean; +import { Indexed } from "../../datasources/map-datasource"; + +export class MapLabel implements Indexed { + id: string; + rotation: number; + style: string; + text: string; + x: number; + y: number; + originalX: number; + originalY: number; + nodeId: string; } diff --git a/src/app/cartography/models/map/map-link-node.ts b/src/app/cartography/models/map/map-link-node.ts index baf81c73..9cf37ade 100644 --- a/src/app/cartography/models/map/map-link-node.ts +++ b/src/app/cartography/models/map/map-link-node.ts @@ -1,8 +1,11 @@ import { MapLabel } from "./map-label"; +import { Indexed } from "../../datasources/map-datasource"; -export class MapLinkNode { - nodeId: string; - adapterNumber: number; - portNumber: number; - label: MapLabel; +export class MapLinkNode implements Indexed { + id: string; + nodeId: string; + linkId: string; + adapterNumber: number; + portNumber: number; + label: MapLabel; } diff --git a/src/app/cartography/models/map/map-node.ts b/src/app/cartography/models/map/map-node.ts index c9141aaa..cfdc7544 100644 --- a/src/app/cartography/models/map/map-node.ts +++ b/src/app/cartography/models/map/map-node.ts @@ -25,5 +25,4 @@ export class MapNode implements Indexed { x: number; y: number; z: number; - isSelected = false; } diff --git a/src/app/cartography/tools/selection-tool.spec.ts b/src/app/cartography/tools/selection-tool.spec.ts index 914eada8..4e2efc59 100644 --- a/src/app/cartography/tools/selection-tool.spec.ts +++ b/src/app/cartography/tools/selection-tool.spec.ts @@ -1,10 +1,9 @@ -import { select } from "d3-selection"; - import { SelectionTool } from "./selection-tool"; import { Context } from "../models/context"; import { SVGSelection } from "../models/types"; import { Rectangle } from "../models/rectangle"; import { TestSVGCanvas } from "../testing"; +import { SelectionEventSource } from "../events/selection-event-source"; describe('SelectionTool', () => { @@ -14,11 +13,14 @@ describe('SelectionTool', () => { let selection_line_tool: SVGSelection; let path_selection: SVGSelection; let selected_rectangle: Rectangle; + let selection_event_source: SelectionEventSource; beforeEach(() => { context = new Context(); - tool = new SelectionTool(context); - tool.rectangleSelected.subscribe((rectangle: Rectangle) => { + selection_event_source = new SelectionEventSource(); + + tool = new SelectionTool(context, selection_event_source); + selection_event_source.selected.subscribe((rectangle: Rectangle) => { selected_rectangle = rectangle; }); diff --git a/src/app/cartography/tools/selection-tool.ts b/src/app/cartography/tools/selection-tool.ts index e5602dd4..228a1a53 100644 --- a/src/app/cartography/tools/selection-tool.ts +++ b/src/app/cartography/tools/selection-tool.ts @@ -5,6 +5,7 @@ import { Subject } from "rxjs"; import { SVGSelection } from "../models/types"; import { Context } from "../models/context"; import { Rectangle } from "../models/rectangle"; +import { SelectionEventSource } from "../events/selection-event-source"; @Injectable() @@ -15,11 +16,10 @@ export class SelectionTool { private path; private enabled = false; - private needsDeactivate = false; - private needsActivate = false; public constructor( - private context: Context + private context: Context, + private selectionEventSource: SelectionEventSource ) {} public setEnabled(enabled) { @@ -107,7 +107,7 @@ export class SelectionTool { const y = Math.min(start[1], end[1]); const width = Math.abs(start[0] - end[0]); const height = Math.abs(start[1] - end[1]); - this.rectangleSelected.next(new Rectangle(x, y, width, height)); + this.selectionEventSource.selected.next(new Rectangle(x, y, width, height)); } private rect(x: number, y: number, w: number, h: number) { diff --git a/src/app/cartography/widgets/drawings.ts b/src/app/cartography/widgets/drawings.ts index d20988a9..dad1c7af 100644 --- a/src/app/cartography/widgets/drawings.ts +++ b/src/app/cartography/widgets/drawings.ts @@ -7,12 +7,12 @@ import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter"; import { Draggable } from "../events/draggable"; import { DrawingWidget } from "./drawing"; import { MapDrawing } from "../models/map/map-drawing"; +import { MapSettingsManager } from "../managers/map-settings-manager"; @Injectable() export class DrawingsWidget implements Widget { public draggable = new Draggable(); - public draggingEnabled = false; // public onContextMenu = new EventEmitter(); // public onDrawingClicked = new EventEmitter(); @@ -22,6 +22,7 @@ export class DrawingsWidget implements Widget { constructor( private drawingWidget: DrawingWidget, private svgToDrawingConverter: SvgToDrawingConverter, + private mapSettings: MapSettingsManager ) { this.svgToDrawingConverter = new SvgToDrawingConverter(); } @@ -59,7 +60,7 @@ export class DrawingsWidget implements Widget { .exit() .remove(); - if (this.draggingEnabled) { + if (!this.mapSettings.isReadOnly) { this.draggable.call(merge); } } diff --git a/src/app/cartography/widgets/drawings/text-drawing.spec.ts b/src/app/cartography/widgets/drawings/text-drawing.spec.ts index 1d75f237..c99a87e2 100644 --- a/src/app/cartography/widgets/drawings/text-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/text-drawing.spec.ts @@ -67,7 +67,7 @@ describe('TextDrawingWidget', () => { expect(drew.nodes()[1].innerHTML).toEqual('IS TEXT'); expect(drew.nodes()[1].getAttribute('x')).toEqual('0'); - expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.2em'); + expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.4em'); }); it('should draw whitespaces', () => { diff --git a/src/app/cartography/widgets/drawings/text-drawing.ts b/src/app/cartography/widgets/drawings/text-drawing.ts index 49f79cf7..7db42cc2 100644 --- a/src/app/cartography/widgets/drawings/text-drawing.ts +++ b/src/app/cartography/widgets/drawings/text-drawing.ts @@ -64,7 +64,7 @@ export class TextDrawingWidget implements DrawingShapeWidget { .text((line) => line) .attr('xml:space', 'preserve') .attr('x', 0) - .attr("dy", (line, i) => i === 0 ? '0em' : '1.2em'); + .attr("dy", (line, i) => i === 0 ? '0em' : '1.4em'); lines .exit() diff --git a/src/app/cartography/widgets/interface-label.spec.ts b/src/app/cartography/widgets/interface-label.spec.ts index c007d4a5..9d1147e2 100644 --- a/src/app/cartography/widgets/interface-label.spec.ts +++ b/src/app/cartography/widgets/interface-label.spec.ts @@ -1,13 +1,15 @@ import { Selection } from "d3-selection"; import { TestSVGCanvas } from "../testing"; -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"; +import { FontFixer } from "../helpers/font-fixer"; +import { SelectionManager } from "../managers/selection-manager"; +import { MapSettingsManager } from "../managers/map-settings-manager"; describe('InterfaceLabelsWidget', () => { @@ -15,6 +17,7 @@ describe('InterfaceLabelsWidget', () => { let widget: InterfaceLabelWidget; let linksEnter: Selection; let links: MapLink[]; + let mapSettings: MapSettingsManager; beforeEach(() => { svg = new TestSVGCanvas(); @@ -67,7 +70,8 @@ describe('InterfaceLabelsWidget', () => { .exit() .remove(); - widget = new InterfaceLabelWidget(new CssFixer()); + mapSettings = new MapSettingsManager(); + widget = new InterfaceLabelWidget(new CssFixer(), new FontFixer(), new SelectionManager(), mapSettings); }); afterEach(() => { @@ -77,24 +81,22 @@ describe('InterfaceLabelsWidget', () => { it('should draw interface labels', () => { widget.draw(linksEnter); - const drew = svg.canvas.selectAll('g.interface_label_container'); + const drew = svg.canvas.selectAll('g.interface_label_container'); expect(drew.nodes().length).toEqual(2); const sourceInterface = drew.nodes()[0] as Element; - expect(sourceInterface.getAttribute('transform')).toEqual('translate(110, 220) rotate(5, 110, 220)'); const sourceIntefaceRect = sourceInterface.firstChild as Element; - expect(sourceIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_border'); + expect(sourceIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_selection'); const sourceIntefaceText = sourceInterface.children[1]; expect(sourceIntefaceText.attributes.getNamedItem('class').value).toEqual('interface_label noselect'); expect(sourceIntefaceText.attributes.getNamedItem('style').value).toEqual('font-size:12px'); const targetInterface = drew.nodes()[1]; - expect(targetInterface.getAttribute('transform')).toEqual('translate(270, 360) rotate(0, 270, 360)'); const targetIntefaceRect = targetInterface.firstChild as Element; - expect(targetIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_border'); + expect(targetIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_selection'); const targetIntefaceText = targetInterface.children[1] as Element; expect(targetIntefaceText.attributes.getNamedItem('class').value).toEqual('interface_label noselect'); expect(targetIntefaceText.attributes.getNamedItem('style').value).toEqual(''); @@ -105,7 +107,7 @@ describe('InterfaceLabelsWidget', () => { widget.setEnabled(false); widget.draw(linksEnter); - const drew = svg.canvas.selectAll('g.interface_label_container'); + const drew = svg.canvas.selectAll('g.interface_label_container'); expect(drew.nodes().length).toEqual(0); }); diff --git a/src/app/cartography/widgets/interface-label.ts b/src/app/cartography/widgets/interface-label.ts index 4a81ce30..527aaaae 100644 --- a/src/app/cartography/widgets/interface-label.ts +++ b/src/app/cartography/widgets/interface-label.ts @@ -1,53 +1,59 @@ import { Injectable } from "@angular/core"; import { SVGSelection } from "../models/types"; -import { InterfaceLabel } from "../models/interface-label"; import { CssFixer } from "../helpers/css-fixer"; import { select } from "d3-selection"; import { MapLink } from "../models/map/map-link"; +import { FontFixer } from "../helpers/font-fixer"; +import { SelectionManager } from "../managers/selection-manager"; +import { MapLinkNode } from "../models/map/map-link-node"; +import { MapNode } from "../models/map/map-node"; +import { Draggable } from "../events/draggable"; +import { MapSettingsManager } from "../managers/map-settings-manager"; @Injectable() export class InterfaceLabelWidget { - static SURROUNDING_TEXT_BORDER = 5; + public draggable = new Draggable(); + static SURROUNDING_TEXT_BORDER = 5; private enabled = true; constructor( - private cssFixer: CssFixer + private cssFixer: CssFixer, + private fontFixer: FontFixer, + private selectionManager: SelectionManager, + private mapSettings: MapSettingsManager ) { } - public setEnabled(enabled: boolean) { + public setEnabled(enabled) { this.enabled = enabled; } draw(selection: SVGSelection) { + const link_node_position = selection + .selectAll('g.link_node_position') + .data((link: MapLink) => [ + [link.source, link.nodes[0]], + [link.target, link.nodes[1]] + ]); - const labels = selection - .selectAll('g.interface_label_container') - .data((l: MapLink) => { - const sourceInterface = new InterfaceLabel( - 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 - ); + const enter_link_node_position = link_node_position + .enter() + .append('g') + .classed('link_node_position', true); - const targetInterface = new InterfaceLabel( - 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 - ); + const merge_link_node_position = link_node_position.merge(enter_link_node_position); + + merge_link_node_position.attr('transform', (nodeAndMapLinkNode: [MapNode, MapLinkNode]) => { + return `translate(${nodeAndMapLinkNode[0].x}, ${nodeAndMapLinkNode[0].y})`; + }); + const labels = merge_link_node_position + .selectAll('g.interface_label_container') + .data((nodeAndMapLinkNode: [MapNode, MapLinkNode]) => { if (this.enabled) { - return [sourceInterface, targetInterface]; + return [nodeAndMapLinkNode[1]]; } return []; }); @@ -60,59 +66,65 @@ export class InterfaceLabelWidget { // create surrounding rect enter .append('rect') - .attr('class', 'interface_label_border'); + .attr('class', 'interface_label_selection'); // create label enter .append('text') - .attr('class', 'interface_label noselect'); + .attr('class', 'interface_label noselect') + .attr('interface_label_id', (i: MapLinkNode) => `${i.id}`) const merge = labels .merge(enter); - merge - .attr('width', 100) - .attr('height', 100) - .attr('transform', function(this: SVGGElement, l: InterfaceLabel) { - const bbox = this.getBBox(); - const x = l.x; - const y = l.y + bbox.height; - return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`; - }) - .classed('selected', (l: InterfaceLabel) => false); - // update label merge .select('text.interface_label') - .text((l: InterfaceLabel) => l.text) - .attr('style', (l: InterfaceLabel) => this.cssFixer.fix(l.style)) - .attr('x', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER) - .attr('y', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER); + .text((l: MapLinkNode) => l.label.text) + .attr('style', (l: MapLinkNode) => { + let styles = this.cssFixer.fix(l.label.style); + styles = this.fontFixer.fixStyles(styles); + return styles; + }) + .attr('x', function (this: SVGTextElement, l: MapLinkNode) { + return l.label.x; + }) + .attr('y', function (this: SVGTextElement, l: MapLinkNode) { + let bbox = this.getBBox(); + return l.label.y + bbox.height; + }) + .attr('transform', (l: MapLinkNode) => { + return `rotate(${l.label.rotation}, ${l.label.x}, ${l.label.y})`; + }) // update surrounding rect merge - .select('rect.interface_label_border') - .attr('visibility', (l: InterfaceLabel) => false ? 'visible' : 'hidden') - .attr('stroke-dasharray', '3,3') - .attr('stroke-width', '0.5') - .each(function (this: SVGRectElement, l: InterfaceLabel) { - const current = select(this); - const parent = select(this.parentElement); - const text = parent.select('text'); - const bbox = text.node().getBBox(); + .select('rect.interface_label_selection') + .attr('visibility', (l: MapLinkNode) => this.selectionManager.isSelected(l) ? 'visible' : 'hidden') + .attr('stroke', 'black') + .attr('stroke-dasharray', '3,3') + .attr('stroke-width', '0.5') + .attr('fill', 'none') + .each(function (this: SVGRectElement, l: MapLinkNode) { + const current = select(this); + const textLabel = merge.select(`text[interface_label_id="${l.id}"]`); + const bbox = textLabel.node().getBBox(); + const border = 2; - const border = InterfaceLabelWidget.SURROUNDING_TEXT_BORDER; - - current.attr('width', bbox.width + border * 2); - current.attr('height', bbox.height + border); - current.attr('x', - border); - current.attr('y', - bbox.height); - }); + current.attr('width', bbox.width + border * 2); + current.attr('height', bbox.height + border * 2); + current.attr('x', bbox.x - border); + current.attr('y', bbox.y - border); + current.attr('transform', `rotate(${l.label.rotation}, ${bbox.x - border}, ${bbox.y - border})`); + }); labels .exit() .remove(); + if(!this.mapSettings.isReadOnly) { + this.draggable.call(merge); + } } } diff --git a/src/app/cartography/widgets/label.ts b/src/app/cartography/widgets/label.ts new file mode 100644 index 00000000..ffbc4093 --- /dev/null +++ b/src/app/cartography/widgets/label.ts @@ -0,0 +1,119 @@ +import { Injectable } from "@angular/core"; + +import { Widget } from "./widget"; +import { SVGSelection } from "../models/types"; +import { CssFixer } from "../helpers/css-fixer"; +import { FontFixer } from "../helpers/font-fixer"; +import { select } from "d3-selection"; +import { MapNode } from "../models/map/map-node"; +import { SelectionManager } from "../managers/selection-manager"; +import { Draggable } from "../events/draggable"; +import { MapLabel } from "../models/map/map-label"; +import { MapSettingsManager } from "../managers/map-settings-manager"; + + +@Injectable() +export class LabelWidget implements Widget { + public draggable = new Draggable(); + + static NODE_LABEL_MARGIN = 3; + + constructor( + private cssFixer: CssFixer, + private fontFixer: FontFixer, + private selectionManager: SelectionManager, + private mapSettings: MapSettingsManager + ) {} + + public redrawLabel(view: SVGSelection, label: MapLabel) { + this.drawLabel(this.selectLabel(view, label)); + } + + public draw(view: SVGSelection) { + const label_view = view + .selectAll("g.label_container") + .data((node: MapNode) => { + return [node.label]; + }); + + const label_enter = label_view.enter() + .append('g') + .attr('class', 'label_container') + .attr('label_id', (l: MapLabel) => l.id) + + const merge = label_view.merge(label_enter); + + this.drawLabel(merge); + + label_view + .exit() + .remove(); + + if(!this.mapSettings.isReadOnly) { + this.draggable.call(label_view); + } + } + + + private drawLabel(view: SVGSelection) { + const self = this; + + const label_body = view.selectAll("g.label_body") + .data((label) => [label]); + + const label_body_enter = label_body.enter() + .append('g') + .attr("class", "label_body"); + + // add label of node + label_body_enter + .append('text') + .attr('class', 'label'); + + label_body_enter + .append('rect') + .attr('class', 'label_selection'); + + const label_body_merge = label_body.merge(label_body_enter) + + label_body_merge + .select('text.label') + .attr('label_id', (l: MapLabel) => l.id) + .attr('style', (l: MapLabel) => { + let styles = this.cssFixer.fix(l.style); + styles = this.fontFixer.fixStyles(styles); + return styles; + }) + .text((l: MapLabel) => l.text) + .attr('x', (l: MapLabel) => l.x) + .attr('y', (l: MapLabel) => l.y) + .attr('transform', (l: MapLabel) => { + return `rotate(${l.rotation}, ${l.x}, ${l.y})`; + }) + + label_body_merge + .select('rect.label_selection') + .attr('visibility', (l: MapLabel) => this.selectionManager.isSelected(l) ? 'visible' : 'hidden') + .attr('stroke', 'black') + .attr('stroke-dasharray', '3,3') + .attr('stroke-width', '0.5') + .attr('fill', 'none') + .each(function (this: SVGRectElement, l: MapLabel) { + const current = select(this); + const textLabel = label_body_merge.select(`text[label_id="${l.id}"]`); + const bbox = textLabel.node().getBBox(); + const border = 2; + + current.attr('width', bbox.width + border * 2); + current.attr('height', bbox.height + border * 2); + current.attr('x', bbox.x - border); + current.attr('y', bbox.y - border); + current.attr('transform', `rotate(${l.rotation}, ${bbox.x - border}, ${bbox.y - border})`); + }); + } + + private selectLabel(view: SVGSelection, label: MapLabel) { + return view.selectAll(`g.label_container[label_id="${label.id}"]`); + } + +} diff --git a/src/app/cartography/widgets/node.ts b/src/app/cartography/widgets/node.ts index 9413e62d..42e12fec 100644 --- a/src/app/cartography/widgets/node.ts +++ b/src/app/cartography/widgets/node.ts @@ -3,27 +3,26 @@ import { Injectable, EventEmitter } from "@angular/core"; import { Widget } from "./widget"; import { SVGSelection } from "../models/types"; import { NodeContextMenu, NodeClicked } from "../events/nodes"; -import { CssFixer } from "../helpers/css-fixer"; -import { FontFixer } from "../helpers/font-fixer"; import { select, event } from "d3-selection"; 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"; +import { LabelWidget } from "./label"; +import { NodesEventSource } from '../events/nodes-event-source'; +import { ClickedDataEvent } from '../events/event-source'; @Injectable() export class NodeWidget implements Widget { - static NODE_LABEL_MARGIN = 3; - public onContextMenu = new EventEmitter(); public onNodeClicked = new EventEmitter(); constructor( - private cssFixer: CssFixer, - private fontFixer: FontFixer, private graphDataManager: GraphDataManager, - private selectionManager: SelectionManager + private selectionManager: SelectionManager, + private labelWidget: LabelWidget, + private nodesEventSource: NodesEventSource, ) {} public draw(view: SVGSelection) { @@ -39,10 +38,6 @@ export class NodeWidget implements Widget { node_body_enter .append('image'); - // add label of node - node_body_enter - .append('text') - .attr('class', 'label'); const node_body_merge = node_body.merge(node_body_enter) .classed('selected', (n: MapNode) => this.selectionManager.isSelected(n)) @@ -50,8 +45,9 @@ export class NodeWidget implements Widget { event.preventDefault(); self.onContextMenu.emit(new NodeContextMenu(event, n)); }) - .on('click', (n: MapNode) => { - this.onNodeClicked.emit(new NodeClicked(event, n)); + .on('click', (node: MapNode) => { + this.nodesEventSource.clicked.emit(new ClickedDataEvent(node, event.clientX, event.clientY)) + // this.onNodeClicked.emit(new NodeClicked(event, n)); }); // update image of node @@ -80,33 +76,7 @@ export class NodeWidget implements Widget { .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: MapNode) => { - let styles = this.cssFixer.fix(n.label.style); - styles = this.fontFixer.fixStyles(styles); - return styles; - }) - .text((n: MapNode) => n.label.text) - .attr('x', function (this: SVGTextElement, n: MapNode) { - if (n.label.x === null) { - // center - const bbox = this.getBBox(); - return -bbox.width / 2.; - } - return n.label.x + NodeWidget.NODE_LABEL_MARGIN; - }) - .attr('y', function (this: SVGTextElement, n: MapNode) { - let bbox = this.getBBox(); - if (n.label.x === null) { - // center - bbox = this.getBBox(); - return - n.height / 2. - bbox.height ; - } - return n.label.y + bbox.height - NodeWidget.NODE_LABEL_MARGIN; - }); + this.labelWidget.draw(node_body_merge); } } diff --git a/src/app/cartography/widgets/nodes.spec.ts b/src/app/cartography/widgets/nodes.spec.ts index f26236e4..77fe84e4 100644 --- a/src/app/cartography/widgets/nodes.spec.ts +++ b/src/app/cartography/widgets/nodes.spec.ts @@ -3,6 +3,7 @@ import { TestSVGCanvas } from "../testing"; import { NodesWidget } from "./nodes"; import { NodeWidget } from "./node"; import { instance, mock } from "ts-mockito"; +import { MapSettingsManager } from "../managers/map-settings-manager"; describe('NodesWidget', () => { @@ -13,7 +14,7 @@ describe('NodesWidget', () => { beforeEach(() => { svg = new TestSVGCanvas(); nodeWidget = instance(mock(NodeWidget)); - widget = new NodesWidget(nodeWidget); + widget = new NodesWidget(nodeWidget, new MapSettingsManager()); }); afterEach(() => { diff --git a/src/app/cartography/widgets/nodes.ts b/src/app/cartography/widgets/nodes.ts index 63496c93..60dd0ab7 100644 --- a/src/app/cartography/widgets/nodes.ts +++ b/src/app/cartography/widgets/nodes.ts @@ -6,6 +6,7 @@ import { Layer } from "../models/layer"; import { NodeWidget } from "./node"; import { Draggable } from "../events/draggable"; import { MapNode } from "../models/map/map-node"; +import { MapSettingsManager } from "../managers/map-settings-manager"; @Injectable() @@ -13,10 +14,10 @@ export class NodesWidget implements Widget { static NODE_LABEL_MARGIN = 3; public draggable = new Draggable(); - public draggingEnabled = false; constructor( - private nodeWidget: NodeWidget + private nodeWidget: NodeWidget, + private mapSettings: MapSettingsManager ) { } @@ -49,7 +50,7 @@ export class NodesWidget implements Widget { .exit() .remove(); - if (this.draggingEnabled) { + if (!this.mapSettings.isReadOnly) { this.draggable.call(merge); } } diff --git a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.html b/src/app/components/project-map/draw-link-tool/draw-link-tool.component.html similarity index 100% rename from src/app/cartography/components/draw-link-tool/draw-link-tool.component.html rename to src/app/components/project-map/draw-link-tool/draw-link-tool.component.html diff --git a/src/app/components/project-map/draw-link-tool/draw-link-tool.component.scss b/src/app/components/project-map/draw-link-tool/draw-link-tool.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.spec.ts b/src/app/components/project-map/draw-link-tool/draw-link-tool.component.spec.ts similarity index 100% rename from src/app/cartography/components/draw-link-tool/draw-link-tool.component.spec.ts rename to src/app/components/project-map/draw-link-tool/draw-link-tool.component.spec.ts diff --git a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts b/src/app/components/project-map/draw-link-tool/draw-link-tool.component.ts similarity index 51% rename from src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts rename to src/app/components/project-map/draw-link-tool/draw-link-tool.component.ts index b4452b08..880ff7ce 100644 --- a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts +++ b/src/app/components/project-map/draw-link-tool/draw-link-tool.component.ts @@ -1,13 +1,12 @@ -import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild } from '@angular/core'; -import { DrawingLineWidget } from '../../widgets/drawing-line'; +import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Subscription } from 'rxjs'; -import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component'; -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'; -import { LinksEventSource } from '../../events/links-event-source'; +import { NodeSelectInterfaceComponent } from '../../../components/project-map/node-select-interface/node-select-interface.component'; +import { DrawingLineWidget } from '../../../cartography/widgets/drawing-line'; +import { NodesEventSource } from '../../../cartography/events/nodes-event-source'; +import { LinksEventSource } from '../../../cartography/events/links-event-source'; +import { MapNode } from '../../../cartography/models/map/map-node'; +import { MapPort } from '../../../cartography/models/map/map-port'; +import { MapLinkCreated } from '../../../cartography/events/links'; @Component({ @@ -18,23 +17,21 @@ import { LinksEventSource } from '../../events/links-event-source'; export class DrawLinkToolComponent implements OnInit, OnDestroy { @ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent; - // @Output('linkCreated') linkCreated = new EventEmitter(); - - private onNodeClicked: Subscription; + private nodeClicked$: Subscription; constructor( private drawingLineTool: DrawingLineWidget, - private nodeWidget: NodeWidget, + private nodesEventSource: NodesEventSource, private linksEventSource: LinksEventSource ) { } ngOnInit() { - this.onNodeClicked = this.nodeWidget.onNodeClicked.subscribe((eventNode: NodeClicked) => { - this.nodeSelectInterfaceMenu.open( - eventNode.node, - eventNode.event.clientY, - eventNode.event.clientX - ); + this.nodeClicked$ = this.nodesEventSource.clicked.subscribe((clickedEvent) => { + this.nodeSelectInterfaceMenu.open( + clickedEvent.datum, + clickedEvent.y, + clickedEvent.x + ); }); } @@ -42,7 +39,7 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy { if (this.drawingLineTool.isDrawing()) { this.drawingLineTool.stop(); } - this.onNodeClicked.unsubscribe(); + this.nodeClicked$.unsubscribe(); } public onChooseInterface(event) { diff --git a/src/app/components/project-map/node-context-menu/node-context-menu.component.ts b/src/app/components/project-map/node-context-menu/node-context-menu.component.ts index 16d67795..ba89c12d 100644 --- a/src/app/components/project-map/node-context-menu/node-context-menu.component.ts +++ b/src/app/components/project-map/node-context-menu/node-context-menu.component.ts @@ -18,9 +18,9 @@ export class NodeContextMenuComponent implements OnInit { @ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger; - protected topPosition; - protected leftPosition; - public node: Node; + topPosition; + leftPosition; + node: Node; constructor( private sanitizer: DomSanitizer, diff --git a/src/app/cartography/components/node-select-interface/node-select-interface.component.html b/src/app/components/project-map/node-select-interface/node-select-interface.component.html similarity index 100% rename from src/app/cartography/components/node-select-interface/node-select-interface.component.html rename to src/app/components/project-map/node-select-interface/node-select-interface.component.html diff --git a/src/app/cartography/components/node-select-interface/node-select-interface.component.scss b/src/app/components/project-map/node-select-interface/node-select-interface.component.scss similarity index 100% rename from src/app/cartography/components/node-select-interface/node-select-interface.component.scss rename to src/app/components/project-map/node-select-interface/node-select-interface.component.scss diff --git a/src/app/cartography/components/node-select-interface/node-select-interface.component.spec.ts b/src/app/components/project-map/node-select-interface/node-select-interface.component.spec.ts similarity index 100% rename from src/app/cartography/components/node-select-interface/node-select-interface.component.spec.ts rename to src/app/components/project-map/node-select-interface/node-select-interface.component.spec.ts diff --git a/src/app/cartography/components/node-select-interface/node-select-interface.component.ts b/src/app/components/project-map/node-select-interface/node-select-interface.component.ts similarity index 65% rename from src/app/cartography/components/node-select-interface/node-select-interface.component.ts rename to src/app/components/project-map/node-select-interface/node-select-interface.component.ts index 4b773fc4..708874c5 100644 --- a/src/app/cartography/components/node-select-interface/node-select-interface.component.ts +++ b/src/app/components/project-map/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 { MapNode } from '../../models/map/map-node'; -import { MapPort } from '../../models/map/map-port'; +import {ChangeDetectorRef, Component, EventEmitter, Input, 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"; @Component({ @@ -17,13 +17,11 @@ export class NodeSelectInterfaceComponent implements OnInit { protected topPosition; protected leftPosition; - - public node: MapNode; + public node: Node; constructor( private sanitizer: DomSanitizer, - private changeDetector: ChangeDetectorRef, - ) {} + private changeDetector: ChangeDetectorRef) {} ngOnInit() { this.setPosition(0, 0); @@ -35,13 +33,13 @@ export class NodeSelectInterfaceComponent implements OnInit { this.changeDetector.detectChanges(); } - public open(node: MapNode, top: number, left: number) { + public open(node: Node, top: number, left: number) { this.node = node; this.setPosition(top, left); this.contextMenu.openMenu(); } - public chooseInterface(port: MapPort) { + public chooseInterface(port: Port) { this.onChooseInterface.emit({ 'node': this.node, 'port': port 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 4b609013..61a1cc1f 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 @@ -23,6 +23,9 @@ 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'; +import { FontBBoxCalculator } from '../../../cartography/helpers/font-bbox-calculator'; +import { CssFixer } from '../../../cartography/helpers/css-fixer'; +import { FontFixer } from '../../../cartography/helpers/font-fixer'; describe('ProjectMapShortcutsComponent', () => { @@ -56,7 +59,11 @@ describe('ProjectMapShortcutsComponent', () => { SettingsService, MapNodeToNodeConverter, MapLabelToLabelConverter, - MapPortToPortConverter + MapPortToPortConverter, + MapLabelToLabelConverter, + FontBBoxCalculator, + CssFixer, + FontFixer ], declarations: [ ProjectMapShortcutsComponent ] }) diff --git a/src/app/components/project-map/project-map.component.css b/src/app/components/project-map/project-map.component.css index 6faa7976..f10ff2a3 100644 --- a/src/app/components/project-map/project-map.component.css +++ b/src/app/components/project-map/project-map.component.css @@ -88,8 +88,13 @@ g.node text, height: 24px !important; font-size: 13px !important; padding: 0 6px; + outline: none !important; } .context-menu-items .mat-menu-item .mat-icon { margin-right: 3px; } + +.context-menu-items .mat-menu-item:focus { + background: none; +} \ No newline at end of file diff --git a/src/app/components/project-map/project-map.component.html b/src/app/components/project-map/project-map.component.html index f3e02314..9be28e62 100644 --- a/src/app/components/project-map/project-map.component.html +++ b/src/app/components/project-map/project-map.component.html @@ -1,5 +1,6 @@
- + > + + +
@@ -87,3 +104,5 @@ [project]="project" [server]="server"> + + \ No newline at end of file diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index a3ecd2de..ab75b33b 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -9,7 +9,6 @@ import { Project } from '../../models/project'; import { Node } from '../../cartography/models/node'; import { SymbolService } from '../../services/symbol.service'; import { Link } from "../../models/link"; -import { MapComponent } from "../../cartography/components/map/map.component"; import { ServerService } from "../../services/server.service"; import { ProjectService } from '../../services/project.service'; import { Server } from "../../models/server"; @@ -37,6 +36,10 @@ import { MapNode } from '../../cartography/models/map/map-node'; import { LinksEventSource } from '../../cartography/events/links-event-source'; import { MapDrawing } from '../../cartography/models/map/map-drawing'; import { MapPortToPortConverter } from '../../cartography/converters/map/map-port-to-port-converter'; +import { SettingsService, Settings } from '../../services/settings.service'; +import { MapLabel } from '../../cartography/models/map/map-label'; +import { MapLinkNode } from '../../cartography/models/map/map-link-node'; +import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter'; @Component({ @@ -56,20 +59,18 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private ws: Subject; - protected tools = { + tools = { 'selection': true, 'moving': false, 'draw_link': false }; + protected settings: Settings; private inReadOnlyMode = false; - @ViewChild(MapComponent) mapChild: MapComponent; - @ViewChild(NodeContextMenuComponent) nodeContextMenu: NodeContextMenuComponent; private subscriptions: Subscription[] = []; - constructor( private route: ActivatedRoute, private serverService: ServerService, @@ -89,12 +90,15 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private drawingsDataSource: DrawingsDataSource, private nodesEventSource: NodesEventSource, private drawingsEventSource: DrawingsEventSource, - private linksEventSource: LinksEventSource + private linksEventSource: LinksEventSource, + private settingsService: SettingsService, + private mapLabelToLabel: MapLabelToLabelConverter ) {} ngOnInit() { + this.settings = this.settingsService.getAll(); + this.progressService.activate(); - const routeSub = this.route.paramMap.subscribe((paramMap: ParamMap) => { const server_id = parseInt(paramMap.get('server_id'), 10); @@ -160,6 +164,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.nodesEventSource.dragged.subscribe((evt) => this.onNodeDragged(evt)) ); + this.subscriptions.push( + this.nodesEventSource.labelDragged.subscribe((evt) => this.onNodeLabelDragged(evt)) + ); + this.subscriptions.push( this.drawingsEventSource.dragged.subscribe((evt) => this.onDrawingDragged(evt)) ); @@ -167,6 +175,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy { this.subscriptions.push( this.linksEventSource.created.subscribe((evt) => this.onLinkCreated(evt)) ); + + this.subscriptions.push( + this.linksEventSource.interfaceDragged.subscribe((evt) => this.onInterfaceLabelDragged(evt)) + ); } onProjectLoad(project: Project) { @@ -224,7 +236,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { onNodeCreation(appliance: Appliance) { this.nodeService .createFromAppliance(this.server, this.project, appliance, 0, 0, 'local') - .subscribe(() => { + .subscribe((createdNode: Node) => { this.projectService .nodes(this.server, this.project.project_id) .subscribe((nodes: Node[]) => { @@ -245,6 +257,22 @@ export class ProjectMapComponent implements OnInit, OnDestroy { }); } + private onNodeLabelDragged(draggedEvent: DraggedDataEvent) { + const node = this.nodesDataSource.get(draggedEvent.datum.nodeId); + const mapLabel = draggedEvent.datum; + mapLabel.x += draggedEvent.dx; + mapLabel.y += draggedEvent.dy; + + const label = this.mapLabelToLabel.convert(mapLabel); + node.label = label; + + this.nodeService + .updateLabel(this.server, node, node.label) + .subscribe((serverNode: Node) => { + this.nodesDataSource.update(serverNode); + }); + } + private onDrawingDragged(draggedEvent: DraggedDataEvent) { const drawing = this.drawingsDataSource.get(draggedEvent.datum.id); drawing.x += draggedEvent.dx; @@ -257,6 +285,24 @@ export class ProjectMapComponent implements OnInit, OnDestroy { }); } + private onInterfaceLabelDragged(draggedEvent: DraggedDataEvent) { + const link = this.linksDataSource.get(draggedEvent.datum.linkId); + if (link.nodes[0].node_id === draggedEvent.datum.nodeId) { + link.nodes[0].label.x += draggedEvent.dx; + link.nodes[0].label.y += draggedEvent.dy; + } + if (link.nodes[1].node_id === draggedEvent.datum.nodeId) { + link.nodes[1].label.x += draggedEvent.dx; + link.nodes[1].label.y += draggedEvent.dy; + } + + this.linkService + .updateNodes(this.server, link, link.nodes) + .subscribe((serverLink: Link) => { + this.linksDataSource.update(serverLink); + }); + } + private onLinkCreated(linkCreated: MapLinkCreated) { const sourceNode = this.mapNodeToNode.convert(linkCreated.sourceNode); const sourcePort = this.mapPortToPort.convert(linkCreated.sourcePort); diff --git a/src/app/components/projects/projects.component.html b/src/app/components/projects/projects.component.html index 2726427b..28b7b962 100644 --- a/src/app/components/projects/projects.component.html +++ b/src/app/components/projects/projects.component.html @@ -2,8 +2,8 @@

Projects

- - + +
diff --git a/src/app/components/settings/settings.component.html b/src/app/components/settings/settings.component.html index 637fb104..b26d263d 100644 --- a/src/app/components/settings/settings.component.html +++ b/src/app/components/settings/settings.component.html @@ -23,6 +23,10 @@ Enable experimental features (WARNING: IT CAN BREAK YOU LABS!)
+
+ Enable experimental Angular Map (WARNING: IT CAN BREAK YOU LABS!) +
+
diff --git a/src/app/components/settings/settings.component.spec.ts b/src/app/components/settings/settings.component.spec.ts index 4b7ecaef..1229b8f7 100644 --- a/src/app/components/settings/settings.component.spec.ts +++ b/src/app/components/settings/settings.component.spec.ts @@ -45,7 +45,8 @@ describe('SettingsComponent', () => { it('should get and save new settings', () => { const settings = { 'crash_reports': true, - 'experimental_features': true + 'experimental_features': true, + 'angular_map': false }; const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings); const setAll = spyOn(settingsService, 'setAll'); diff --git a/src/app/services/link.service.ts b/src/app/services/link.service.ts index 06b262c3..5048cb8c 100644 --- a/src/app/services/link.service.ts +++ b/src/app/services/link.service.ts @@ -5,6 +5,8 @@ import 'rxjs/add/operator/map'; import { Server } from "../models/server"; import { HttpServer } from "./http-server.service"; import {Port} from "../models/port"; +import { Link } from '../models/link'; +import { LinkNode } from '../models/link-node'; @Injectable() export class LinkService { @@ -33,4 +35,27 @@ export class LinkService { ]}); } + updateNodes( + server: Server, link: Link, nodes: LinkNode[]) { + const requestNodes = nodes.map((linkNode) => { + return { + node_id: linkNode.node_id, + port_number: linkNode.port_number, + adapter_number: linkNode.adapter_number, + label: { + rotation: linkNode.label.rotation, + style: linkNode.label.style, + text: linkNode.label.text, + x: linkNode.label.x, + y: linkNode.label.y + } + } + }); + + return this.httpServer + .put( + server, + `/projects/${link.project_id}/links/${link.link_id}`, + {"nodes": requestNodes}); + } } diff --git a/src/app/services/node.service.spec.ts b/src/app/services/node.service.spec.ts index 565efe5b..b3b8ff39 100644 --- a/src/app/services/node.service.spec.ts +++ b/src/app/services/node.service.spec.ts @@ -10,6 +10,7 @@ import { NodeService } from './node.service'; import { Appliance } from '../models/appliance'; import { Project } from '../models/project'; import { AppTestingModule } from "../testing/app-testing/app-testing.module"; +import { Label } from '../cartography/models/label'; describe('NodeService', () => { let httpClient: HttpClient; @@ -102,6 +103,35 @@ describe('NodeService', () => { }); })); + it('should update label of node', inject([NodeService], (service: NodeService) => { + const node = new Node(); + node.project_id = "myproject"; + node.node_id = "id"; + + const label = new Label(); + label.rotation = 10; + label.style = "my style"; + label.text = "my text"; + label.x = 10; + label.y = 20; + + service.updateLabel(server, node, label).subscribe(); + + const req = httpTestingController.expectOne( + 'http://127.0.0.1:3080/v2/projects/myproject/nodes/id'); + expect(req.request.method).toEqual("PUT"); + expect(req.request.body).toEqual({ + 'label': { + 'rotation': 10, + 'style': 'my style', + 'text': 'my text', + 'x': 10, + 'y': 20, + } + }); + })); + + it('should update node', inject([NodeService], (service: NodeService) => { const node = new Node(); node.project_id = "myproject"; diff --git a/src/app/services/node.service.ts b/src/app/services/node.service.ts index a2c1b80b..28c5a4b7 100644 --- a/src/app/services/node.service.ts +++ b/src/app/services/node.service.ts @@ -7,6 +7,7 @@ import 'rxjs/add/operator/map'; import { Server } from "../models/server"; import { HttpServer } from "./http-server.service"; import {Appliance} from "../models/appliance"; +import { Label } from '../cartography/models/label'; @Injectable() @@ -42,6 +43,19 @@ export class NodeService { }); } + updateLabel(server: Server, node: Node, label: Label): Observable { + return this.httpServer + .put(server, `/projects/${node.project_id}/nodes/${node.node_id}`, { + 'label': { + 'rotation': label.rotation, + 'style': label.style, + 'text': label.text, + 'x': label.x, + 'y': label.y + } + }); + } + update(server: Server, node: Node): Observable { return this.httpServer .put(server, `/projects/${node.project_id}/nodes/${node.node_id}`, { diff --git a/src/app/services/settings.service.spec.ts b/src/app/services/settings.service.spec.ts index 941158f9..ad911a9d 100644 --- a/src/app/services/settings.service.spec.ts +++ b/src/app/services/settings.service.spec.ts @@ -53,7 +53,8 @@ describe('SettingsService', () => { it('should get all values', inject([SettingsService], (service: SettingsService) => { expect(service.getAll()).toEqual({ 'crash_reports': true, - 'experimental_features': false + 'experimental_features': false, + 'angular_map': false }); })); @@ -65,7 +66,8 @@ describe('SettingsService', () => { expect(service.getAll()).toEqual({ 'crash_reports': false, - 'experimental_features': false + 'experimental_features': false, + 'angular_map': false }); })); diff --git a/src/app/services/settings.service.ts b/src/app/services/settings.service.ts index aa74a5af..aca6dea2 100644 --- a/src/app/services/settings.service.ts +++ b/src/app/services/settings.service.ts @@ -1,12 +1,12 @@ import { Injectable } from '@angular/core'; import { PersistenceService, StorageType } from "angular-persistence"; -import { Subject } from "rxjs"; import { BehaviorSubject } from "rxjs"; export interface Settings { crash_reports: boolean; experimental_features: boolean; + angular_map: boolean; } @@ -14,7 +14,8 @@ export interface Settings { export class SettingsService { static DEFAULTS: Settings = { 'crash_reports': true, - 'experimental_features': false + 'experimental_features': false, + 'angular_map': false }; private settingsSubject: BehaviorSubject; diff --git a/src/environments/environment.ts b/src/environments/environment.ts index c7e44b00..02712c06 100644 --- a/src/environments/environment.ts +++ b/src/environments/environment.ts @@ -1,10 +1,18 @@ -// The file contents for the current environment will overwrite these during build. -// The build system defaults to the dev environment which uses `environment.ts`, but if you do -// `ng build --env=prod` then `environment.prod.ts` will be used instead. -// The list of which env maps to which file can be found in `.angular-cli.json`. +// This file can be replaced during build by using the `fileReplacements` array. +// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. +// The list of file replacements can be found in `angular.json`. export const environment = { production: false, electron: false, githubio: false }; + +/* + * For easier debugging in development mode, you can import the following file + * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. + * + * This import should be commented out in production mode because it will have a negative impact + * on performance if an error is thrown. + */ +// import 'zone.js/dist/zone-error'; // Included with Angular CLI. diff --git a/src/main.ts b/src/main.ts index a9ca1caf..c7b673cf 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,4 +8,5 @@ if (environment.production) { enableProdMode(); } -platformBrowserDynamic().bootstrapModule(AppModule); +platformBrowserDynamic().bootstrapModule(AppModule) + .catch(err => console.error(err)); diff --git a/src/polyfills.ts b/src/polyfills.ts index 7831e97b..2d143481 100644 --- a/src/polyfills.ts +++ b/src/polyfills.ts @@ -11,7 +11,7 @@ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * - * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html + * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** @@ -34,29 +34,47 @@ // import 'core-js/es6/weak-map'; // import 'core-js/es6/set'; +/** + * If the application will be indexed by Google Search, the following is required. + * Googlebot uses a renderer based on Chrome 41. + * https://developers.google.com/search/docs/guides/rendering + **/ +// import 'core-js/es6/array'; + /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. -/** Evergreen browsers require these. **/ -import 'core-js/es6/reflect'; -import 'core-js/es7/reflect'; - +/** IE10 and IE11 requires the following for the Reflect API. */ +// import 'core-js/es6/reflect'; /** - * Required to support Web Animations `@angular/animation`. - * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation + * Web Animations `@angular/platform-browser/animations` + * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. + * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). **/ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. +/** + * By default, zone.js will patch all possible macroTask and DomEvents + * user can disable parts of macroTask/DomEvents patch by setting following flags + */ + // (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame + // (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick + // (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames + + /* + * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js + * with the following flag, it will bypass `zone.js` patch for IE/Edge + */ +// (window as any).__Zone_enable_cross_context_check = true; /*************************************************************************************************** - * Zone JS is required by Angular itself. + * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. - /*************************************************************************************************** * APPLICATION IMPORTS */ diff --git a/src/test.ts b/src/test.ts index cd612eeb..16317897 100644 --- a/src/test.ts +++ b/src/test.ts @@ -1,24 +1,14 @@ // This file is required by karma.conf.js and loads recursively all the .spec and framework files -import 'zone.js/dist/long-stack-trace-zone'; -import 'zone.js/dist/proxy.js'; -import 'zone.js/dist/sync-test'; -import 'zone.js/dist/jasmine-patch'; -import 'zone.js/dist/async-test'; -import 'zone.js/dist/fake-async-test'; +import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; -// Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. -declare const __karma__: any; declare const require: any; -// Prevent Karma from running prematurely. -__karma__.loaded = function () {}; - // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, @@ -28,5 +18,3 @@ getTestBed().initTestEnvironment( const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); -// Finally, start Karma to run the tests. -__karma__.start(); diff --git a/tsconfig.json b/tsconfig.json index 72d54ded..90f316b6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,18 +1,21 @@ { "compileOnSave": false, "compilerOptions": { + "baseUrl": "./", "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, + "module": "es2015", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, + "importHelpers": true, "target": "es5", "typeRoots": [ "node_modules/@types" ], "lib": [ - "es2016", + "es2018", "dom" ], }, diff --git a/tslint.json b/tslint.json index b1ed5170..6ddb6b29 100644 --- a/tslint.json +++ b/tslint.json @@ -11,10 +11,14 @@ "check-space" ], "curly": true, + "deprecation": { + "severity": "warn" + }, "eofline": true, "forin": true, "import-blacklist": [ - true + true, + "rxjs/Rx" ], "import-spacing": true, "indent": [ @@ -61,11 +65,12 @@ ], "no-misused-new": true, "no-non-null-assertion": true, + "no-redundant-jsdoc": true, "no-shadowed-variable": true, "no-string-literal": false, "no-string-throw": true, "no-switch-case-fall-through": true, - "no-trailing-whitespace": false, + "no-trailing-whitespace": true, "no-unnecessary-initializer": true, "no-unused-expression": true, "no-use-before-declare": true, @@ -80,7 +85,7 @@ ], "prefer-const": true, "quotemark": [ - false, + true, "single" ], "radix": true, @@ -102,7 +107,6 @@ "variable-declaration": "nospace" } ], - "typeof-compare": true, "unified-signatures": true, "variable-name": false, "whitespace": [ @@ -113,18 +117,7 @@ "check-separator", "check-type" ], - "directive-selector": [ - true, - "attribute", - "app", - "camelCase" - ], - "component-selector": [ - true, - "element", - "app", - "kebab-case" - ], + "no-output-on-prefix": true, "use-input-property-decorator": true, "use-output-property-decorator": true, "use-host-property-decorator": true, @@ -133,9 +126,6 @@ "use-life-cycle-interface": true, "use-pipe-transform-interface": true, "component-class-suffix": true, - "directive-class-suffix": true, - "no-access-missing-member": true, - "templates-use-public": true, - "invoke-injectable": true + "directive-class-suffix": true } } diff --git a/yarn.lock b/yarn.lock index 16645d5d..307187ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -957,11 +957,6 @@ ansi-html@0.0.7: resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4= -ansi-regex@*: - version "4.0.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" - integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -2864,7 +2859,7 @@ debug@^3.0.0, debug@^3.1.0: dependencies: ms "^2.1.1" -debuglog@*, debuglog@^1.0.1: +debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -4631,7 +4626,7 @@ import-local@^2.0.0: pkg-dir "^3.0.0" resolve-cwd "^2.0.0" -imurmurhash@*, imurmurhash@^0.1.4: +imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -5648,11 +5643,6 @@ lockfile@~1.0.2: dependencies: signal-exit "^3.0.2" -lodash._baseindexof@*: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" - integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= - lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -5661,33 +5651,11 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@*: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" - integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= - -lodash._cacheindexof@*: - version "3.0.2" - resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" - integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= - -lodash._createcache@*: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" - integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= - dependencies: - lodash._getnative "^3.0.0" - lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@*, lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - lodash._root@~3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" @@ -5713,11 +5681,6 @@ lodash.mergewith@^4.6.0: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== -lodash.restparam@*: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" @@ -7722,7 +7685,7 @@ readable-stream@~2.1.5: string_decoder "~0.10.x" util-deprecate "~1.0.1" -readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0: +readdir-scoped-modules@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747" integrity sha1-n6+jfShr5dksuuve4DDcm19AZ0c= @@ -9457,7 +9420,7 @@ uuid@^3.0.1, uuid@^3.1.0, uuid@^3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -validate-npm-package-license@*, validate-npm-package-license@^3.0.1: +validate-npm-package-license@^3.0.1: version "3.0.4" resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==