Merge branch 'develop'
@ -1,4 +1,4 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
# Editor configuration, see https://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
|
@ -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
|
||||
|
16
.vscode/launch.json
vendored
Normal file
@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
@ -133,6 +133,9 @@
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
],
|
||||
"codeCoverageExclude": [
|
||||
"src/app/cartography/components/experimental-map/**/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
28
src/app/cartography/angular-map.imports.ts
Normal file
@ -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
|
||||
];
|
@ -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 { }
|
||||
|
13
src/app/cartography/components/d3-map/d3-map.component.html
Normal file
@ -0,0 +1,13 @@
|
||||
<svg
|
||||
#svg
|
||||
class="map"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<filter id="grayscale">
|
||||
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
|
||||
</filter>
|
||||
</svg>
|
||||
|
||||
<app-selection-control></app-selection-control>
|
||||
<app-selection-select></app-selection-select>
|
||||
<app-draggable-selection [svg]="svg"></app-draggable-selection>
|
After Width: | Height: | Size: 325 B |
@ -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<MapComponent>;
|
||||
describe('D3MapComponent', () => {
|
||||
let component: D3MapComponent;
|
||||
let fixture: ComponentFixture<D3MapComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ MapComponent ]
|
||||
declarations: [ D3MapComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
@ -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<SVGSVGElement, any, null, undefined>;
|
||||
|
||||
@ -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) {
|
@ -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<DraggableSelectionComponent>;
|
||||
let mockedGraphDataManager: MockedGraphDataManager;
|
||||
let nodesStartEventEmitter: EventEmitter<DraggableStart<MapNode>>;
|
||||
let nodesDragEventEmitter: EventEmitter<DraggableDrag<MapNode>>;
|
||||
let nodesEndEventEmitter: EventEmitter<DraggableEnd<MapNode>>;
|
||||
|
||||
let drawingsStartEventEmitter: EventEmitter<DraggableStart<MapDrawing>>;
|
||||
let drawingsDragEventEmitter: EventEmitter<DraggableDrag<MapDrawing>>;
|
||||
let drawingsEndEventEmitter: EventEmitter<DraggableEnd<MapDrawing>>;
|
||||
|
||||
let labelStartEventEmitter: EventEmitter<DraggableStart<MapLabel>>;
|
||||
let labelDragEventEmitter: EventEmitter<DraggableDrag<MapLabel>>;
|
||||
let labelEndEventEmitter: EventEmitter<DraggableEnd<MapLabel>>;
|
||||
|
||||
let interfaceLabelStartEventEmitter: EventEmitter<DraggableStart<MapLinkNode>>;
|
||||
let interfaceLabelDragEventEmitter: EventEmitter<DraggableDrag<MapLinkNode>>;
|
||||
let interfaceLabelEndEventEmitter: EventEmitter<DraggableEnd<MapLinkNode>>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
mockedGraphDataManager = new MockedGraphDataManager();
|
||||
|
||||
nodesStartEventEmitter = new EventEmitter<DraggableStart<MapNode>>();
|
||||
nodesDragEventEmitter = new EventEmitter<DraggableDrag<MapNode>>();
|
||||
nodesEndEventEmitter = new EventEmitter<DraggableEnd<MapNode>>();
|
||||
|
||||
drawingsStartEventEmitter = new EventEmitter<DraggableStart<MapDrawing>>();
|
||||
drawingsDragEventEmitter = new EventEmitter<DraggableDrag<MapDrawing>>();
|
||||
drawingsEndEventEmitter = new EventEmitter<DraggableEnd<MapDrawing>>();
|
||||
|
||||
labelStartEventEmitter = new EventEmitter<DraggableStart<MapLabel>>();
|
||||
labelDragEventEmitter = new EventEmitter<DraggableDrag<MapLabel>>();
|
||||
labelEndEventEmitter = new EventEmitter<DraggableEnd<MapLabel>>();
|
||||
|
||||
interfaceLabelStartEventEmitter = new EventEmitter<DraggableStart<MapLinkNode>>();
|
||||
interfaceLabelDragEventEmitter = new EventEmitter<DraggableDrag<MapLinkNode>>();
|
||||
interfaceLabelEndEventEmitter = new EventEmitter<DraggableEnd<MapLinkNode>>();
|
||||
|
||||
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<MapNode>(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<MapNode>(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<MapNode>(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<MapNode>(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<MapNode>(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<MapNode>(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<MapDrawing>(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<MapDrawing>(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<MapDrawing>(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<MapDrawing>(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<MapLabel>(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<MapLabel>(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<MapLabel>(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<MapLabel>(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<MapLabel>(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<MapLabel>(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<MapLinkNode>(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<MapLinkNode>(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<MapLinkNode>(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<MapLinkNode>(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<MapLinkNode>(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<MapLinkNode>(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<MapLinkNode>(linkNode);
|
||||
dragEvent.dx = 10;
|
||||
dragEvent.dy = 20;
|
||||
|
||||
interfaceLabelWidgetStub.draggable.end.emit(dragEvent);
|
||||
tick();
|
||||
expect(linksEventSourceStub.interfaceDragged.emit).not.toHaveBeenCalled();
|
||||
}));
|
||||
});
|
||||
});
|
@ -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<any>) => {
|
||||
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<any>) => {
|
||||
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<any>) => {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
const selectedNodes = selected.filter((item) => item instanceof MapNode);
|
||||
|
||||
selectedNodes.forEach((item: MapNode) => {
|
||||
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(item, evt.dx, evt.dy));
|
||||
})
|
||||
|
||||
selected.filter((item) => item instanceof MapDrawing).forEach((item: MapDrawing) => {
|
||||
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(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<MapLabel>(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<MapLinkNode>(label, evt.dx, evt.dy));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.start.unsubscribe();
|
||||
this.drag.unsubscribe();
|
||||
this.end.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
@ -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<DraggableComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DraggableComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DraggableComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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:`<ng-content></ng-content>`,
|
||||
styleUrls: ['./draggable.component.scss']
|
||||
})
|
||||
export class DraggableComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@Input('app-draggable') item: Point;
|
||||
@Output() dragging = new EventEmitter<DraggableDraggedEvent>();
|
||||
@Output() dragged = new EventEmitter<DraggableDraggedEvent>();
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
<svg:g
|
||||
class="drawing"
|
||||
[attr.transform]="transformation"
|
||||
[app-draggable]="drawing"
|
||||
(dragging)="OnDragging($event)"
|
||||
(dragged)="OnDragged($event)"
|
||||
>
|
||||
<svg:g
|
||||
*ngIf="is(drawing.element, 'ellipse')"
|
||||
[app-ellipse]="drawing.element"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
*ngIf="is(drawing.element, 'image')"
|
||||
[app-image]="drawing.element"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
*ngIf="is(drawing.element, 'line')"
|
||||
[app-line]="drawing.element"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
*ngIf="is(drawing.element, 'rect')"
|
||||
[app-rect]="drawing.element"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
*ngIf="is(drawing.element, 'text')"
|
||||
[app-text]="drawing.element"
|
||||
/>
|
||||
</svg:g>
|
After Width: | Height: | Size: 618 B |
@ -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<DrawingComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ DrawingComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(DrawingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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<MapDrawing>(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})`;
|
||||
}
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
<svg:ellipse
|
||||
class="ellipse_element noselect"
|
||||
[attr.fill]="ellipse.fill"
|
||||
[attr.fill-opacity]="fill_opacity"
|
||||
[attr.stroke]="ellipse.stroke"
|
||||
[attr.stroke-width]="stroke_width"
|
||||
[attr.stroke-dasharray]="stroke_dasharray"
|
||||
[attr.cx]="ellipse.cx"
|
||||
[attr.cy]="ellipse.cy"
|
||||
[attr.rx]="ellipse.rx"
|
||||
[attr.ry]="ellipse.ry"
|
||||
/>
|
After Width: | Height: | Size: 333 B |
@ -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<EllipseComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ EllipseComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EllipseComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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;
|
||||
}
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
<svg:image
|
||||
class="image_element noselect"
|
||||
[attr.xlink:href]="image.data"
|
||||
[attr.width]="image.width"
|
||||
[attr.height]="image.height"
|
||||
/>
|
After Width: | Height: | Size: 140 B |
@ -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<ImageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ImageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ImageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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() {
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<svg:line
|
||||
class="line_element noselect"
|
||||
[attr.stroke]="line.stroke"
|
||||
[attr.stroke-width]="stroke_width"
|
||||
[attr.stroke-dasharray]="stroke_dasharray"
|
||||
[attr.x1]="line.x1"
|
||||
[attr.x2]="line.x2"
|
||||
[attr.y1]="line.y1"
|
||||
[attr.y2]="line.y2"
|
||||
/>
|
After Width: | Height: | Size: 245 B |
@ -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<LineComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LineComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LineComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<svg:rect
|
||||
class="rect_element noselect"
|
||||
[attr.fill]="rect.fill"
|
||||
[attr.fill-opacity]="fill_opacity"
|
||||
[attr.stroke]="rect.stroke"
|
||||
[attr.stroke-width]="stroke_width"
|
||||
[attr.stroke-dasharray]="stroke_dasharray"
|
||||
[attr.width]="rect.width"
|
||||
[attr.height]="rect.height"
|
||||
/>
|
After Width: | Height: | Size: 278 B |
@ -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<RectComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ RectComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
<svg:text #text
|
||||
class="text_element noselect"
|
||||
[attr.style]="style"
|
||||
[attr.text-decoration]="textDecoration"
|
||||
[attr.fill]="text.fill"
|
||||
[attr.transform]="transformation"
|
||||
>
|
||||
<svg:tspan
|
||||
*ngFor="let line of lines; index as i"
|
||||
xml:space="preserve"
|
||||
x="0"
|
||||
[attr.dy]="i == 0 ? '0em' : '1.4em'"
|
||||
>{{line}}</svg:tspan>
|
||||
</svg:text>
|
After Width: | Height: | Size: 344 B |
@ -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<TextComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TextComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TextComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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/)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<svg #svg
|
||||
class="map"
|
||||
preserveAspectRatio="none"
|
||||
[attr.width]="width"
|
||||
[attr.height]="height"
|
||||
>
|
||||
<g [attr.transform]="transform">
|
||||
<g *ngFor="let layer of layers">
|
||||
<g class="links">
|
||||
<g
|
||||
*ngFor="let link of layer.links"
|
||||
[app-link]="link"
|
||||
[show-interface-labels]="settings.show_interface_labels"
|
||||
></g>
|
||||
<!-- [node-changed]="nodeChanged" -->
|
||||
</g>
|
||||
<g class="nodes">
|
||||
<g
|
||||
*ngFor="let node of layer.nodes"
|
||||
[app-node]="node"
|
||||
[symbols]="symbols"
|
||||
></g>
|
||||
<!-- [node-changed]="nodeChanged"
|
||||
(valueChange)="onNodeChanged($event)" -->
|
||||
</g>
|
||||
<g class="drawings">
|
||||
<g
|
||||
*ngFor="let drawing of layer.drawings"
|
||||
[app-drawing]="drawing" >
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g [app-selection]="svg"></g>
|
||||
<!-- (selected)="onSelection($event)" -->
|
||||
|
||||
<filter id="grayscale">
|
||||
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
|
||||
</filter>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
@ -0,0 +1,5 @@
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
|
@ -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<MapComponent>;
|
||||
|
||||
// beforeEach(async(() => {
|
||||
// TestBed.configureTestingModule({
|
||||
// declarations: [ MapComponent ]
|
||||
// })
|
||||
// .compileComponents();
|
||||
// }));
|
||||
|
||||
// // beforeEach(() => {
|
||||
// // fixture = TestBed.createComponent(MapComponent);
|
||||
// // component = fixture.componentInstance;
|
||||
// // fixture.detectChanges();
|
||||
// // });
|
||||
// //
|
||||
// // it('should create', () => {
|
||||
// // expect(component).toBeTruthy();
|
||||
// // });
|
||||
// });
|
||||
// //
|
@ -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<any>;
|
||||
|
||||
// @Input('node-updated') nodeUpdated: EventEmitter<any>;
|
||||
|
||||
@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) {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
<svg:g
|
||||
class="text_container"
|
||||
[attr.transform]="transform"
|
||||
width="100"
|
||||
height="100"
|
||||
>
|
||||
<svg:rect
|
||||
stroke-dasharray="3,3"
|
||||
stroke-width="0.5"
|
||||
fill="none"
|
||||
stroke="black"
|
||||
[attr.x]="rectX"
|
||||
[attr.y]="rectY"
|
||||
[attr.width]="rectWidth"
|
||||
[attr.height]="rectHeight"
|
||||
/>
|
||||
|
||||
<svg:text
|
||||
#textSvg
|
||||
class="interface_label"
|
||||
[attr.style]="sanitizedStyle"
|
||||
[attr.x]="borderSize"
|
||||
[attr.y]="-borderSize"
|
||||
>
|
||||
{{ text }}
|
||||
</svg:text>
|
||||
</svg:g>
|
||||
|
After Width: | Height: | Size: 484 B |
@ -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<InterfaceLabelComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ InterfaceLabelComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(InterfaceLabelComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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})`;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<svg:g
|
||||
class="link"
|
||||
[attr.link_id]="link.id"
|
||||
[attr.map-source]="link.source.id"
|
||||
[attr.map-target]="link.target.id"
|
||||
[attr.transform]="transform"
|
||||
>
|
||||
<svg:path #path
|
||||
*ngIf="link.linkType == 'ethernet'"
|
||||
class="ethernet_link"
|
||||
stroke="#000"
|
||||
stroke-width="2"
|
||||
[attr.d]="d"
|
||||
/>
|
||||
|
||||
<svg:path #path
|
||||
*ngIf="link.linkType == 'serial'"
|
||||
class="serial_link"
|
||||
stroke="#B22222"
|
||||
fill="none"
|
||||
stroke-width="2"
|
||||
[attr.d]="d"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
[app-status]="link.source.status"
|
||||
[direction]="'source'"
|
||||
[path]="path"
|
||||
[d]="d"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
[app-status]="link.target.status"
|
||||
[direction]="'target'"
|
||||
[path]="path"
|
||||
[d]="d"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
*ngIf="showInterfaceLabels"
|
||||
[app-interface-label]
|
||||
[x]="link.source.x+link.nodes[0].label.x"
|
||||
[y]="link.source.y+link.nodes[0].label.y"
|
||||
[text]="link.nodes[0].label.text"
|
||||
[style]="link.nodes[0].label.style"
|
||||
[rotation]="link.nodes[0].label.rotation"
|
||||
/>
|
||||
|
||||
<svg:g
|
||||
*ngIf="showInterfaceLabels"
|
||||
[app-interface-label]
|
||||
[x]="link.target.x+link.nodes[1].label.x"
|
||||
[y]="link.target.y+link.nodes[1].label.y"
|
||||
[text]="link.nodes[1].label.text"
|
||||
[style]="link.nodes[1].label.style"
|
||||
[rotation]="link.nodes[1].label.rotation"
|
||||
/>
|
||||
|
||||
</svg:g>
|
After Width: | Height: | Size: 1.2 KiB |
@ -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<LinkComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LinkComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LinkComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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<Node>;
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import { MapLink } from "../../../../models/map/map-link";
|
||||
|
||||
export interface LinkStrategy {
|
||||
d(link: MapLink): string;
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
<svg:g
|
||||
class="node"
|
||||
[attr.transform]="'translate(' + node.x + ',' + node.y + ')'"
|
||||
>
|
||||
<svg:image
|
||||
#image
|
||||
[attr.width]="node.width"
|
||||
[attr.height]="node.height"
|
||||
[attr.x]="0"
|
||||
[attr.y]="0"
|
||||
[attr.xlink:href]="symbol"
|
||||
[app-draggable]="node"
|
||||
(dragging)="OnDragging($event)"
|
||||
(dragged)="OnDragged($event)"
|
||||
/>
|
||||
<svg:text
|
||||
#label
|
||||
class="label"
|
||||
[attr.style]="label_style"
|
||||
[attr.x]="label_x"
|
||||
[attr.y]="label_y"
|
||||
>
|
||||
{{ node.label.text }}
|
||||
</svg:text>
|
||||
</svg:g>
|
After Width: | Height: | Size: 513 B |
@ -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<NodeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ NodeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NodeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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<Node>;
|
||||
|
||||
// @Output() valueChange = new EventEmitter<Node>();
|
||||
|
||||
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<MapNode>(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;
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
<svg:g
|
||||
class="selection-line-tool"
|
||||
>
|
||||
<svg:path
|
||||
class="selection"
|
||||
*ngIf="visible"
|
||||
[attr.d]="d"
|
||||
>
|
||||
</svg:path>
|
||||
</svg:g>
|
After Width: | Height: | Size: 137 B |
@ -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<SelectionComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SelectionComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SelectionComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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<Rectangle>();
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
<svg:g *ngIf="status && point && direction">
|
||||
<svg:circle
|
||||
*ngIf="status == 'started'"
|
||||
class="status_started"
|
||||
[attr.cx]="point.x"
|
||||
[attr.cy]="point.y"
|
||||
r="6"
|
||||
fill="#2ecc71"
|
||||
/>
|
||||
|
||||
<svg:rect
|
||||
*ngIf="status == 'stopped'"
|
||||
class="status_stopped"
|
||||
[attr.x]="point.x - 10/2"
|
||||
[attr.y]="point.y - 10/2"
|
||||
width="10"
|
||||
height="10"
|
||||
r="6"
|
||||
fill="red"
|
||||
/>
|
||||
</svg:g>
|
After Width: | Height: | Size: 401 B |
@ -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<StatusComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ StatusComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StatusComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
<svg
|
||||
class="map"
|
||||
preserveAspectRatio="none"
|
||||
>
|
||||
<filter id="grayscale">
|
||||
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
|
||||
</filter>
|
||||
</svg>
|
||||
|
||||
<app-draw-link-tool *ngIf="drawLinkTool"></app-draw-link-tool>
|
Before Width: | Height: | Size: 235 B |
@ -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);
|
||||
}));
|
||||
});
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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<SelectionSelectComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SelectionSelectComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SelectionSelectComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
// it('should create', () => {
|
||||
// expect(component).toBeTruthy();
|
||||
// });
|
||||
});
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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<Label, MapLabel> {
|
||||
convert(label: Label) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -9,15 +9,21 @@ import { MapLinkNode } from "../../models/map/map-link-node";
|
||||
@Injectable()
|
||||
export class LinkNodeToMapLinkNodeConverter implements Converter<LinkNode, MapLinkNode> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class LinkToMapLinkConverter implements Converter<Link, MapLink> {
|
||||
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;
|
||||
}
|
||||
|
@ -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<MapLabel, 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;
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,9 @@ 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()
|
||||
|
@ -5,13 +5,19 @@ 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<Node, MapNode> {
|
||||
constructor(
|
||||
private labelToMapLabel: LabelToMapLabelConverter,
|
||||
private portToMapPort: PortToMapPortConverter
|
||||
private portToMapPort: PortToMapPortConverter,
|
||||
private fontBBoxCalculator: FontBBoxCalculator,
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer
|
||||
) {}
|
||||
|
||||
convert(node: Node) {
|
||||
@ -23,7 +29,7 @@ export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
|
||||
mapNode.consoleHost = node.console_host;
|
||||
mapNode.firstPortName = node.first_port_name;
|
||||
mapNode.height = node.height;
|
||||
mapNode.label = this.labelToMapLabel.convert(node.label);
|
||||
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;
|
||||
@ -37,6 +43,18 @@ export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
47
src/app/cartography/converters/styles-to-font-converter.ts
Normal file
@ -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<string, Font> {
|
||||
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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -44,9 +44,13 @@ export class Draggable<GElement extends DraggedElementBaseType, Datum> {
|
||||
|
||||
private behaviour() {
|
||||
let startEvt;
|
||||
|
||||
let lastX: number;
|
||||
let lastY: number;
|
||||
return drag<GElement, Datum>()
|
||||
.on('start', (datum: Datum) => {
|
||||
lastX = event.sourceEvent.clientX;
|
||||
lastY = event.sourceEvent.clientY;
|
||||
|
||||
startEvt = new DraggableStart<Datum>(datum);
|
||||
startEvt.dx = event.dx;
|
||||
startEvt.dy = event.dy;
|
||||
@ -56,18 +60,17 @@ export class Draggable<GElement extends DraggedElementBaseType, Datum> {
|
||||
})
|
||||
.on('drag', (datum: Datum) => {
|
||||
const evt = new DraggableDrag<Datum>(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>(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);
|
||||
});
|
||||
}
|
||||
|
@ -7,3 +7,11 @@ export class DataEventSource<T> {
|
||||
}
|
||||
|
||||
export class DraggedDataEvent<T> extends DataEventSource<T> {}
|
||||
|
||||
export class ClickedDataEvent<T> {
|
||||
constructor(
|
||||
public datum: T,
|
||||
public x: number,
|
||||
public y: number
|
||||
) {}
|
||||
}
|
@ -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<MapLinkCreated>();
|
||||
public interfaceDragged = new EventEmitter<DraggedDataEvent<MapLinkNode>>();
|
||||
}
|
||||
|
@ -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<DraggedDataEvent<MapNode>>();
|
||||
public labelDragged = new EventEmitter<DraggedDataEvent<MapLabel>>();
|
||||
public clicked = new EventEmitter<ClickedDataEvent<MapNode>>();
|
||||
}
|
||||
|
9
src/app/cartography/events/selection-event-source.ts
Normal file
@ -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<Rectangle>();
|
||||
}
|
@ -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'
|
||||
});
|
||||
|
24
src/app/cartography/helpers/font-bbox-calculator.spec.ts
Normal file
@ -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);
|
||||
});
|
||||
});
|
20
src/app/cartography/helpers/font-bbox-calculator.ts
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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<any>) => {
|
||||
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<any>) => {
|
||||
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<any>) => {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
|
||||
selected.filter((item) => item instanceof MapNode).forEach((item: MapNode) => {
|
||||
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(item, evt.dx, evt.dy));
|
||||
})
|
||||
|
||||
selected.filter((item) => item instanceof MapDrawing).forEach((item: MapDrawing) => {
|
||||
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public onDestroy() {
|
||||
this.start.unsubscribe();
|
||||
this.drag.unsubscribe();
|
||||
this.end.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export interface MapListener {
|
||||
onInit(svg: any);
|
||||
onDestroy();
|
||||
}
|
@ -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();
|
||||
});
|
||||
}
|
||||
}
|
@ -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);
|
||||
}));
|
||||
|
||||
});
|