mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-05-02 08:42:50 +00:00
Merge
This commit is contained in:
commit
c81a1fa659
79
package.json
79
package.json
@ -26,65 +26,66 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^7.0.2",
|
"@angular/animations": "^6.0.7",
|
||||||
"@angular/cdk": "^7.0.2",
|
"@angular/cdk": "^6.3.2",
|
||||||
"@angular/common": "^7.0.2",
|
"@angular/common": "^6.0.7",
|
||||||
"@angular/compiler": "^7.0.2",
|
"@angular/compiler": "^6.0.7",
|
||||||
"@angular/core": "^7.0.2",
|
"@angular/core": "^6.0.7",
|
||||||
"@angular/forms": "^7.0.2",
|
"@angular/forms": "^6.0.7",
|
||||||
"@angular/http": "^7.0.2",
|
"@angular/http": "^6.0.7",
|
||||||
"@angular/material": "^7.0.2",
|
"@angular/material": "^6.3.2",
|
||||||
"@angular/platform-browser": "^7.0.2",
|
"@angular/platform-browser": "^6.0.7",
|
||||||
"@angular/platform-browser-dynamic": "^7.0.2",
|
"@angular/platform-browser-dynamic": "^6.0.7",
|
||||||
"@angular/router": "^7.0.2",
|
"@angular/router": "^6.0.7",
|
||||||
"@ng-bootstrap/ng-bootstrap": "^4.0.0",
|
"@ng-bootstrap/ng-bootstrap": "^2.2.0",
|
||||||
"angular-persistence": "^1.0.1",
|
"angular-persistence": "^1.0.1",
|
||||||
"angular2-hotkeys": "^2.1.2",
|
"angular2-hotkeys": "^2.1.2",
|
||||||
"angular2-indexeddb": "^1.2.2",
|
"angular2-indexeddb": "^1.2.2",
|
||||||
"bootstrap": "4.1.3",
|
"bootstrap": "4.1.1",
|
||||||
"core-js": "^2.5.7",
|
"core-js": "^2.5.7",
|
||||||
"css-tree": "^1.0.0-alpha.29",
|
"css-tree": "^1.0.0-alpha.29",
|
||||||
"d3-ng2-service": "^2.1.0",
|
"d3-ng2-service": "^2.1.0",
|
||||||
"electron-settings": "^3.2.0",
|
"electron-settings": "^3.2.0",
|
||||||
"material-design-icons": "^3.0.1",
|
"material-design-icons": "^3.0.1",
|
||||||
|
"ng2-file-upload": "^1.3.0",
|
||||||
"ngx-electron": "^1.0.4",
|
"ngx-electron": "^1.0.4",
|
||||||
"notosans-fontface": "^1.1.0",
|
"notosans-fontface": "^1.1.0",
|
||||||
"npm-check-updates": "^2.14.1",
|
"npm-check-updates": "^2.14.1",
|
||||||
"raven-js": "^3.27.0",
|
"raven-js": "^3.24.1",
|
||||||
"rxjs": "^6.3.3",
|
"rxjs": "^6.2.1",
|
||||||
"rxjs-compat": "^6.3.3",
|
"rxjs-compat": "^6.2.1",
|
||||||
"typeface-roboto": "^0.0.54",
|
"typeface-roboto": "^0.0.54",
|
||||||
"yargs": "^12.0.2",
|
"yargs": "^12.0.1",
|
||||||
"zone.js": "^0.8.26"
|
"zone.js": "^0.8.26"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~0.10.4",
|
"@angular-devkit/build-angular": "~0.6.8",
|
||||||
"@angular/cli": "^7.0.4",
|
"@angular/cli": "^6.0.8",
|
||||||
"@angular/compiler-cli": "^7.0.2",
|
"@angular/compiler-cli": "^6.0.7",
|
||||||
"@angular/language-service": "^7.0.2",
|
"@angular/language-service": "^6.0.7",
|
||||||
"@sentry/electron": "^0.13.0",
|
"@sentry/electron": "^0.7.0",
|
||||||
"@types/jasmine": "~2.8.11",
|
"@types/jasmine": "~2.8.8",
|
||||||
"@types/jasminewd2": "~2.0.6",
|
"@types/jasminewd2": "~2.0.2",
|
||||||
"@types/node": "~10.12.2",
|
"@types/node": "~10.5.2",
|
||||||
"codelyzer": "~4.5.0",
|
"codelyzer": "~4.4.2",
|
||||||
"electron": "3.0.8",
|
"electron": "2.0.4",
|
||||||
"electron-builder": "^20.31.2",
|
"electron-builder": "^20.19.2",
|
||||||
"jasmine-core": "~3.3.0",
|
"jasmine-core": "~3.1.0",
|
||||||
"jasmine-spec-reporter": "~4.2.1",
|
"jasmine-spec-reporter": "~4.2.1",
|
||||||
"jquery": "^3.3.1",
|
"jquery": "^3.3.1",
|
||||||
"karma": "~3.1.1",
|
"karma": "~2.0.4",
|
||||||
"karma-chrome-launcher": "~2.2.0",
|
"karma-chrome-launcher": "~2.2.0",
|
||||||
"karma-cli": "~1.0.1",
|
"karma-cli": "~1.0.1",
|
||||||
"karma-coverage-istanbul-reporter": "^2.0.4",
|
"karma-coverage-istanbul-reporter": "^2.0.1",
|
||||||
"karma-jasmine": "~1.1.0",
|
"karma-jasmine": "~1.1.0",
|
||||||
"karma-jasmine-html-reporter": "^1.4.0",
|
"karma-jasmine-html-reporter": "^1.2.0",
|
||||||
"node-sass": "^4.10.0",
|
"node-sass": "^4.9.1",
|
||||||
"popper.js": "^1.14.4",
|
"popper.js": "^1.14.3",
|
||||||
"protractor": "~5.4.1",
|
"protractor": "~5.4.0",
|
||||||
"ts-mockito": "^2.3.1",
|
"ts-mockito": "^2.3.0",
|
||||||
"ts-node": "~7.0.1",
|
"ts-node": "~7.0.0",
|
||||||
"tslint": "~5.11.0",
|
"tslint": "~5.10.0",
|
||||||
"typescript": "3.1.6"
|
"typescript": "<2.8.0"
|
||||||
},
|
},
|
||||||
"greenkeeper": {
|
"greenkeeper": {
|
||||||
"ignore": [
|
"ignore": [
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
setuptools==38.4
|
setuptools==38.4
|
||||||
cx_Freeze==5.1.1
|
cx_Freeze==5.1.1
|
||||||
requests==2.18.4
|
requests==2.20.0
|
||||||
packaging==16.8
|
packaging==16.8
|
||||||
appdirs==1.4.3
|
appdirs==1.4.3
|
||||||
psutil==5.4.0
|
psutil==5.4.0
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import * as Raven from 'raven-js';
|
import * as Raven from 'raven-js';
|
||||||
import { BrowserModule } from '@angular/platform-browser';
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
import { NgModule, ErrorHandler } from '@angular/core';
|
import { NgModule, ErrorHandler } from '@angular/core';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { CdkTableModule } from "@angular/cdk/table";
|
import { CdkTableModule } from "@angular/cdk/table";
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
@ -12,7 +12,7 @@ import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
|
|||||||
import { HotkeyModule } from 'angular2-hotkeys';
|
import { HotkeyModule } from 'angular2-hotkeys';
|
||||||
import { PersistenceModule } from 'angular-persistence';
|
import { PersistenceModule } from 'angular-persistence';
|
||||||
import { NgxElectronModule } from 'ngx-electron';
|
import { NgxElectronModule } from 'ngx-electron';
|
||||||
|
import { FileUploadModule } from 'ng2-file-upload';
|
||||||
import { AppRoutingModule } from './app-routing.module';
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
|
||||||
import { VersionService } from './services/version.service';
|
import { VersionService } from './services/version.service';
|
||||||
@ -28,6 +28,9 @@ import { ApplianceService } from "./services/appliance.service";
|
|||||||
import { LinkService } from "./services/link.service";
|
import { LinkService } from "./services/link.service";
|
||||||
|
|
||||||
import { ProjectsComponent } from './components/projects/projects.component';
|
import { ProjectsComponent } from './components/projects/projects.component';
|
||||||
|
import { AddBlankProjectDialogComponent } from './components/projects/add-blank-project-dialog/add-blank-project-dialog.component';
|
||||||
|
import { ImportProjectDialogComponent } from './components/projects/import-project-dialog/import-project-dialog.component';
|
||||||
|
import { ConfirmationDialogComponent} from './components/projects/confirmation-dialog/confirmation-dialog.component';
|
||||||
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
|
import { DefaultLayoutComponent } from './layouts/default-layout/default-layout.component';
|
||||||
import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component';
|
import { ProgressDialogComponent } from './common/progress-dialog/progress-dialog.component';
|
||||||
import { AppComponent } from './app.component';
|
import { AppComponent } from './app.component';
|
||||||
@ -67,6 +70,8 @@ import { CreateSnapshotDialogComponent } from './components/snapshots/create-sna
|
|||||||
import { SnapshotsComponent } from './components/snapshots/snapshots.component';
|
import { SnapshotsComponent } from './components/snapshots/snapshots.component';
|
||||||
import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-item/snapshot-menu-item.component';
|
import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-item/snapshot-menu-item.component';
|
||||||
import { MATERIAL_IMPORTS } from './material.imports';
|
import { MATERIAL_IMPORTS } from './material.imports';
|
||||||
|
import { DrawingService } from './services/drawing.service';
|
||||||
|
import { ProjectNameValidator } from './components/projects/models/projectNameValidator';
|
||||||
|
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
@ -91,6 +96,9 @@ if (environment.production) {
|
|||||||
SnapshotMenuItemComponent,
|
SnapshotMenuItemComponent,
|
||||||
SnapshotsComponent,
|
SnapshotsComponent,
|
||||||
ProjectsComponent,
|
ProjectsComponent,
|
||||||
|
AddBlankProjectDialogComponent,
|
||||||
|
ImportProjectDialogComponent,
|
||||||
|
ConfirmationDialogComponent,
|
||||||
DefaultLayoutComponent,
|
DefaultLayoutComponent,
|
||||||
ProgressDialogComponent,
|
ProgressDialogComponent,
|
||||||
NodeContextMenuComponent,
|
NodeContextMenuComponent,
|
||||||
@ -112,13 +120,15 @@ if (environment.production) {
|
|||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
AppRoutingModule,
|
AppRoutingModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
BrowserAnimationsModule,
|
BrowserAnimationsModule,
|
||||||
CdkTableModule,
|
CdkTableModule,
|
||||||
CartographyModule,
|
CartographyModule,
|
||||||
HotkeyModule.forRoot(),
|
HotkeyModule.forRoot(),
|
||||||
PersistenceModule,
|
PersistenceModule,
|
||||||
NgxElectronModule,
|
NgxElectronModule,
|
||||||
...MATERIAL_IMPORTS
|
FileUploadModule,
|
||||||
|
MATERIAL_IMPORTS
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
SettingsService,
|
SettingsService,
|
||||||
@ -131,6 +141,7 @@ if (environment.production) {
|
|||||||
ApplianceService,
|
ApplianceService,
|
||||||
NodeService,
|
NodeService,
|
||||||
LinkService,
|
LinkService,
|
||||||
|
DrawingService,
|
||||||
IndexedDbService,
|
IndexedDbService,
|
||||||
HttpServer,
|
HttpServer,
|
||||||
SnapshotService,
|
SnapshotService,
|
||||||
@ -145,13 +156,17 @@ if (environment.production) {
|
|||||||
InRectangleHelper,
|
InRectangleHelper,
|
||||||
DrawingsDataSource,
|
DrawingsDataSource,
|
||||||
ServerErrorHandler,
|
ServerErrorHandler,
|
||||||
ServerDatabase
|
ServerDatabase,
|
||||||
|
ProjectNameValidator
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddServerDialogComponent,
|
AddServerDialogComponent,
|
||||||
CreateSnapshotDialogComponent,
|
CreateSnapshotDialogComponent,
|
||||||
ProgressDialogComponent,
|
ProgressDialogComponent,
|
||||||
ApplianceListDialogComponent
|
ApplianceListDialogComponent,
|
||||||
|
AddBlankProjectDialogComponent,
|
||||||
|
ImportProjectDialogComponent,
|
||||||
|
ConfirmationDialogComponent
|
||||||
],
|
],
|
||||||
bootstrap: [ AppComponent ]
|
bootstrap: [ AppComponent ]
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { MatMenuModule, MatIconModule } from '@angular/material';
|
||||||
|
|
||||||
import { MapComponent } from './components/map/map.component';
|
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 { CssFixer } from './helpers/css-fixer';
|
||||||
import { FontFixer } from './helpers/font-fixer';
|
import { FontFixer } from './helpers/font-fixer';
|
||||||
import { MultiLinkCalculatorHelper } from './helpers/multi-link-calculator-helper';
|
import { MultiLinkCalculatorHelper } from './helpers/multi-link-calculator-helper';
|
||||||
@ -8,26 +13,33 @@ import { SvgToDrawingConverter } from './helpers/svg-to-drawing-converter';
|
|||||||
import { QtDasharrayFixer } from './helpers/qt-dasharray-fixer';
|
import { QtDasharrayFixer } from './helpers/qt-dasharray-fixer';
|
||||||
import { LayersManager } from './managers/layers-manager';
|
import { LayersManager } from './managers/layers-manager';
|
||||||
import { MapChangeDetectorRef } from './services/map-change-detector-ref';
|
import { MapChangeDetectorRef } from './services/map-change-detector-ref';
|
||||||
import { GraphLayout } from './widgets/graph-layout';
|
|
||||||
import { LinksWidget } from './widgets/links';
|
|
||||||
import { NodesWidget } from './widgets/nodes';
|
|
||||||
import { DrawingsWidget } from './widgets/drawings';
|
|
||||||
import { DrawingLineWidget } from './widgets/drawing-line';
|
|
||||||
import { SelectionTool } from './tools/selection-tool';
|
|
||||||
import { MovingTool } from './tools/moving-tool';
|
|
||||||
import { LayersWidget } from './widgets/layers';
|
|
||||||
import { LinkWidget } from './widgets/link';
|
|
||||||
import { InterfaceStatusWidget } from './widgets/interface-status';
|
|
||||||
import { InterfaceLabelWidget } from './widgets/interface-label';
|
|
||||||
import { EllipseDrawingWidget } from './widgets/drawings/ellipse-drawing';
|
|
||||||
import { ImageDrawingWidget } from './widgets/drawings/image-drawing';
|
|
||||||
import { RectDrawingWidget } from './widgets/drawings/rect-drawing';
|
|
||||||
import { TextDrawingWidget } from './widgets/drawings/text-drawing';
|
|
||||||
import { LineDrawingWidget } from './widgets/drawings/line-drawing';
|
|
||||||
import { Context } from './models/context';
|
import { Context } from './models/context';
|
||||||
import { DrawLinkToolComponent } from './components/draw-link-tool/draw-link-tool.component';
|
import { D3_MAP_IMPORTS } from './d3-map.imports';
|
||||||
import { NodeSelectInterfaceComponent } from './components/node-select-interface/node-select-interface.component';
|
import { CanvasSizeDetector } from './helpers/canvas-size-detector';
|
||||||
import { MatMenuModule, MatIconModule } from '@angular/material';
|
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';
|
||||||
|
import { LabelToMapLabelConverter } from './converters/map/label-to-map-label-converter';
|
||||||
|
import { LinkToMapLinkConverter } from './converters/map/link-to-map-link-converter';
|
||||||
|
import { MapDrawingToDrawingConverter } from './converters/map/map-drawing-to-drawing-converter';
|
||||||
|
import { MapLabelToLabelConverter } from './converters/map/map-label-to-label-converter';
|
||||||
|
import { MapLinkNodeToLinkNodeConverter } from './converters/map/map-link-node-to-link-node-converter';
|
||||||
|
import { MapLinkToLinkConverter } from './converters/map/map-link-to-link-converter';
|
||||||
|
import { MapNodeToNodeConverter } from './converters/map/map-node-to-node-converter';
|
||||||
|
import { MapPortToPortConverter } from './converters/map/map-port-to-port-converter';
|
||||||
|
import { MapSymbolToSymbolConverter } from './converters/map/map-symbol-to-symbol-converter';
|
||||||
|
import { NodeToMapNodeConverter } from './converters/map/node-to-map-node-converter';
|
||||||
|
import { PortToMapPortConverter } from './converters/map/port-to-map-port-converter';
|
||||||
|
import { SymbolToMapSymbolConverter } from './converters/map/symbol-to-map-symbol-converter';
|
||||||
|
import { LinkNodeToMapLinkNodeConverter } from './converters/map/link-node-to-map-link-node-converter';
|
||||||
|
import { GraphDataManager } from './managers/graph-data-manager';
|
||||||
|
import { SelectionUpdateListener } from './listeners/selection-update-listener';
|
||||||
|
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from './datasources/map-datasource';
|
||||||
|
import { SelectionListener } from './listeners/selection-listener';
|
||||||
|
import { LinksEventSource } from './events/links-event-source';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
@ -48,24 +60,36 @@ import { MatMenuModule, MatIconModule } from '@angular/material';
|
|||||||
QtDasharrayFixer,
|
QtDasharrayFixer,
|
||||||
LayersManager,
|
LayersManager,
|
||||||
MapChangeDetectorRef,
|
MapChangeDetectorRef,
|
||||||
GraphLayout,
|
CanvasSizeDetector,
|
||||||
LinksWidget,
|
|
||||||
NodesWidget,
|
|
||||||
DrawingsWidget,
|
|
||||||
DrawingLineWidget,
|
|
||||||
SelectionTool,
|
|
||||||
MovingTool,
|
|
||||||
LayersWidget,
|
|
||||||
LinkWidget,
|
|
||||||
InterfaceStatusWidget,
|
|
||||||
InterfaceLabelWidget,
|
|
||||||
EllipseDrawingWidget,
|
|
||||||
ImageDrawingWidget,
|
|
||||||
LineDrawingWidget,
|
|
||||||
RectDrawingWidget,
|
|
||||||
TextDrawingWidget,
|
|
||||||
Context,
|
Context,
|
||||||
|
SelectionUpdateListener,
|
||||||
|
MapListeners,
|
||||||
|
DraggableListener,
|
||||||
|
SelectionListener,
|
||||||
|
DrawingsEventSource,
|
||||||
|
NodesEventSource,
|
||||||
|
LinksEventSource,
|
||||||
|
DrawingToMapDrawingConverter,
|
||||||
|
LabelToMapLabelConverter,
|
||||||
|
LinkToMapLinkConverter,
|
||||||
|
LinkNodeToMapLinkNodeConverter,
|
||||||
|
MapDrawingToDrawingConverter,
|
||||||
|
MapLabelToLabelConverter,
|
||||||
|
MapLinkNodeToLinkNodeConverter,
|
||||||
|
MapLinkToLinkConverter,
|
||||||
|
MapNodeToNodeConverter,
|
||||||
|
MapPortToPortConverter,
|
||||||
|
MapSymbolToSymbolConverter,
|
||||||
|
NodeToMapNodeConverter,
|
||||||
|
PortToMapPortConverter,
|
||||||
|
SymbolToMapSymbolConverter,
|
||||||
|
GraphDataManager,
|
||||||
|
MapNodesDataSource,
|
||||||
|
MapLinksDataSource,
|
||||||
|
MapDrawingsDataSource,
|
||||||
|
MapSymbolsDataSource,
|
||||||
|
...D3_MAP_IMPORTS
|
||||||
],
|
],
|
||||||
exports: [MapComponent]
|
exports: [ MapComponent ]
|
||||||
})
|
})
|
||||||
export class CartographyModule { }
|
export class CartographyModule { }
|
||||||
|
@ -1,21 +1,15 @@
|
|||||||
import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild } from '@angular/core';
|
import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild } from '@angular/core';
|
||||||
import { Port } from '../../../models/port';
|
|
||||||
import { DrawingLineWidget } from '../../widgets/drawing-line';
|
import { DrawingLineWidget } from '../../widgets/drawing-line';
|
||||||
import { Node } from '../../models/node';
|
|
||||||
import { NodesWidget, NodeEvent } from '../../widgets/nodes';
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component';
|
import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component';
|
||||||
|
import { MapLinkCreated } from '../../events/links';
|
||||||
|
import { NodeClicked } from '../../events/nodes';
|
||||||
|
import { NodeWidget } from '../../widgets/node';
|
||||||
|
import { MapNode } from '../../models/map/map-node';
|
||||||
|
import { MapPort } from '../../models/map/map-port';
|
||||||
|
import { LinksEventSource } from '../../events/links-event-source';
|
||||||
|
|
||||||
|
|
||||||
export class LinkCreated {
|
|
||||||
constructor(
|
|
||||||
public sourceNode: Node,
|
|
||||||
public sourcePort: Port,
|
|
||||||
public targetNode: Node,
|
|
||||||
public targetPort: Port
|
|
||||||
){}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-draw-link-tool',
|
selector: 'app-draw-link-tool',
|
||||||
templateUrl: './draw-link-tool.component.html',
|
templateUrl: './draw-link-tool.component.html',
|
||||||
@ -24,17 +18,18 @@ export class LinkCreated {
|
|||||||
export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
||||||
@ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
|
@ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
|
||||||
|
|
||||||
@Output('linkCreated') linkCreated = new EventEmitter<LinkCreated>();
|
// @Output('linkCreated') linkCreated = new EventEmitter<MapLinkCreated>();
|
||||||
|
|
||||||
private onNodeClicked: Subscription;
|
private onNodeClicked: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private drawingLineTool: DrawingLineWidget,
|
private drawingLineTool: DrawingLineWidget,
|
||||||
private nodesWidget: NodesWidget
|
private nodeWidget: NodeWidget,
|
||||||
|
private linksEventSource: LinksEventSource
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.onNodeClicked = this.nodesWidget.onNodeClicked.subscribe((eventNode: NodeEvent) => {
|
this.onNodeClicked = this.nodeWidget.onNodeClicked.subscribe((eventNode: NodeClicked) => {
|
||||||
this.nodeSelectInterfaceMenu.open(
|
this.nodeSelectInterfaceMenu.open(
|
||||||
eventNode.node,
|
eventNode.node,
|
||||||
eventNode.event.clientY,
|
eventNode.event.clientY,
|
||||||
@ -44,18 +39,18 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
if(this.drawingLineTool.isDrawing()) {
|
if (this.drawingLineTool.isDrawing()) {
|
||||||
this.drawingLineTool.stop();
|
this.drawingLineTool.stop();
|
||||||
}
|
}
|
||||||
this.onNodeClicked.unsubscribe();
|
this.onNodeClicked.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onChooseInterface(event) {
|
public onChooseInterface(event) {
|
||||||
const node: Node = event.node;
|
const node: MapNode = event.node;
|
||||||
const port: Port = event.port;
|
const port: MapPort = event.port;
|
||||||
if (this.drawingLineTool.isDrawing()) {
|
if (this.drawingLineTool.isDrawing()) {
|
||||||
const data = this.drawingLineTool.stop();
|
const data = this.drawingLineTool.stop();
|
||||||
this.linkCreated.emit(new LinkCreated(data['node'], data['port'], node, port));
|
this.linksEventSource.created.emit(new MapLinkCreated(data['node'], data['port'], node, port));
|
||||||
} else {
|
} else {
|
||||||
this.drawingLineTool.start(node.x + node.width / 2., node.y + node.height / 2., {
|
this.drawingLineTool.start(node.x + node.width / 2., node.y + node.height / 2., {
|
||||||
'node': node,
|
'node': node,
|
||||||
|
@ -1,6 +1,10 @@
|
|||||||
<svg
|
<svg
|
||||||
class="map"
|
class="map"
|
||||||
preserveAspectRatio="none"
|
preserveAspectRatio="none"
|
||||||
></svg>
|
>
|
||||||
|
<filter id="grayscale">
|
||||||
|
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
|
||||||
|
</filter>
|
||||||
|
</svg>
|
||||||
|
|
||||||
<app-draw-link-tool *ngIf="drawLinkTool" (linkCreated)="linkCreated($event)"></app-draw-link-tool>
|
<app-draw-link-tool *ngIf="drawLinkTool"></app-draw-link-tool>
|
Before Width: | Height: | Size: 159 B After Width: | Height: | Size: 235 B |
@ -1,24 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChange, EventEmitter, Output
|
Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChange, EventEmitter, Output
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { D3, D3Service } from 'd3-ng2-service';
|
import { Selection, select } from 'd3-selection';
|
||||||
import { Selection } from 'd3-selection';
|
|
||||||
|
|
||||||
import { Node } from "../../models/node";
|
|
||||||
import { Link } from "../../../models/link";
|
|
||||||
import { GraphLayout } from "../../widgets/graph-layout";
|
import { GraphLayout } from "../../widgets/graph-layout";
|
||||||
import { Context } from "../../models/context";
|
import { Context } from "../../models/context";
|
||||||
import { Size } from "../../models/size";
|
import { Size } from "../../models/size";
|
||||||
import { Drawing } from "../../models/drawing";
|
import { NodesWidget } from '../../widgets/nodes';
|
||||||
import { Symbol } from '../../../models/symbol';
|
|
||||||
import { NodeEvent, NodesWidget } from '../../widgets/nodes';
|
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||||
import { SelectionTool } from '../../tools/selection-tool';
|
import { SelectionTool } from '../../tools/selection-tool';
|
||||||
import { MovingTool } from '../../tools/moving-tool';
|
import { MovingTool } from '../../tools/moving-tool';
|
||||||
import { LinksWidget } from '../../widgets/links';
|
|
||||||
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
|
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
|
||||||
import { LinkCreated } from '../draw-link-tool/draw-link-tool.component';
|
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';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -35,16 +36,9 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
@Input() width = 1500;
|
@Input() width = 1500;
|
||||||
@Input() height = 600;
|
@Input() height = 600;
|
||||||
|
|
||||||
@Output() onNodeDragged: EventEmitter<NodeEvent>;
|
|
||||||
@Output() onLinkCreated = new EventEmitter<LinkCreated>();
|
|
||||||
|
|
||||||
private d3: D3;
|
|
||||||
private parentNativeElement: any;
|
private parentNativeElement: any;
|
||||||
private svg: Selection<SVGSVGElement, any, null, undefined>;
|
private svg: Selection<SVGSVGElement, any, null, undefined>;
|
||||||
|
|
||||||
private isReady = false;
|
|
||||||
|
|
||||||
private onNodeDraggingSubscription: Subscription;
|
|
||||||
private onChangesDetected: Subscription;
|
private onChangesDetected: Subscription;
|
||||||
|
|
||||||
protected settings = {
|
protected settings = {
|
||||||
@ -52,20 +46,20 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
};
|
};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private graphDataManager: GraphDataManager,
|
||||||
private context: Context,
|
private context: Context,
|
||||||
private mapChangeDetectorRef: MapChangeDetectorRef,
|
private mapChangeDetectorRef: MapChangeDetectorRef,
|
||||||
|
private canvasSizeDetector: CanvasSizeDetector,
|
||||||
|
private mapListeners: MapListeners,
|
||||||
protected element: ElementRef,
|
protected element: ElementRef,
|
||||||
protected d3Service: D3Service,
|
|
||||||
protected nodesWidget: NodesWidget,
|
protected nodesWidget: NodesWidget,
|
||||||
protected linksWidget: LinksWidget,
|
protected drawingsWidget: DrawingsWidget,
|
||||||
protected interfaceLabelWidget: InterfaceLabelWidget,
|
protected interfaceLabelWidget: InterfaceLabelWidget,
|
||||||
protected selectionToolWidget: SelectionTool,
|
protected selectionToolWidget: SelectionTool,
|
||||||
protected movingToolWidget: MovingTool,
|
protected movingToolWidget: MovingTool,
|
||||||
public graphLayout: GraphLayout
|
public graphLayout: GraphLayout,
|
||||||
) {
|
) {
|
||||||
this.d3 = d3Service.getD3();
|
|
||||||
this.parentNativeElement = element.nativeElement;
|
this.parentNativeElement = element.nativeElement;
|
||||||
this.onNodeDragged = nodesWidget.onNodeDragged;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Input('show-interface-labels')
|
@Input('show-interface-labels')
|
||||||
@ -89,6 +83,11 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
|
|
||||||
@Input('draw-link-tool') drawLinkTool: boolean;
|
@Input('draw-link-tool') drawLinkTool: boolean;
|
||||||
|
|
||||||
|
@Input('readonly') set readonly(value) {
|
||||||
|
this.nodesWidget.draggingEnabled = !value;
|
||||||
|
this.drawingsWidget.draggingEnabled = !value;
|
||||||
|
}
|
||||||
|
|
||||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||||
if (
|
if (
|
||||||
(changes['width'] && !changes['width'].isFirstChange()) ||
|
(changes['width'] && !changes['width'].isFirstChange()) ||
|
||||||
@ -99,12 +98,6 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
(changes['symbols'] && !changes['symbols'].isFirstChange())
|
(changes['symbols'] && !changes['symbols'].isFirstChange())
|
||||||
) {
|
) {
|
||||||
if (this.svg.empty && !this.svg.empty()) {
|
if (this.svg.empty && !this.svg.empty()) {
|
||||||
if (changes['nodes']) {
|
|
||||||
this.onNodesChange(changes['nodes']);
|
|
||||||
}
|
|
||||||
if (changes['links']) {
|
|
||||||
this.onLinksChange(changes['links']);
|
|
||||||
}
|
|
||||||
if (changes['symbols']) {
|
if (changes['symbols']) {
|
||||||
this.onSymbolsChange(changes['symbols']);
|
this.onSymbolsChange(changes['symbols']);
|
||||||
}
|
}
|
||||||
@ -113,55 +106,37 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
this.graphLayout.disconnect(this.svg);
|
|
||||||
this.onNodeDraggingSubscription.unsubscribe();
|
|
||||||
this.onChangesDetected.unsubscribe();
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if (this.parentNativeElement !== null) {
|
if (this.parentNativeElement !== null) {
|
||||||
this.createGraph(this.parentNativeElement);
|
this.createGraph(this.parentNativeElement);
|
||||||
}
|
}
|
||||||
this.context.size = this.getSize();
|
this.context.size = this.getSize();
|
||||||
|
|
||||||
this.onNodeDraggingSubscription = this.nodesWidget.onNodeDragging.subscribe((eventNode: NodeEvent) => {
|
|
||||||
const links = this.links.filter((link) => link.target.node_id === eventNode.node.node_id || link.source.node_id === eventNode.node.node_id)
|
|
||||||
|
|
||||||
links.forEach((link) => {
|
|
||||||
this.linksWidget.redrawLink(this.svg, link);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
this.onChangesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => {
|
this.onChangesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => {
|
||||||
if (this.isReady) {
|
if (this.mapChangeDetectorRef.hasBeenDrawn) {
|
||||||
this.reload();
|
this.redraw();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.mapListeners.onInit(this.svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnDestroy() {
|
||||||
|
this.graphLayout.disconnect(this.svg);
|
||||||
|
this.onChangesDetected.unsubscribe();
|
||||||
|
this.mapListeners.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public createGraph(domElement: HTMLElement) {
|
public createGraph(domElement: HTMLElement) {
|
||||||
const rootElement = this.d3.select(domElement);
|
const rootElement = select(domElement);
|
||||||
this.svg = rootElement.select<SVGSVGElement>('svg');
|
this.svg = rootElement.select<SVGSVGElement>('svg');
|
||||||
this.graphLayout.connect(this.svg, this.context);
|
this.graphLayout.connect(this.svg, this.context);
|
||||||
this.graphLayout.draw(this.svg, this.context);
|
this.graphLayout.draw(this.svg, this.context);
|
||||||
this.isReady = true;
|
this.mapChangeDetectorRef.hasBeenDrawn = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getSize(): Size {
|
public getSize(): Size {
|
||||||
let width = document.documentElement.clientWidth;
|
return this.canvasSizeDetector.getOptimalSize(this.width, this.height);
|
||||||
let height = document.documentElement.clientHeight;
|
|
||||||
if (this.width > width) {
|
|
||||||
width = this.width;
|
|
||||||
}
|
|
||||||
if (this.height > height) {
|
|
||||||
height = this.height;
|
|
||||||
}
|
|
||||||
return new Size(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected linkCreated(evt) {
|
|
||||||
this.onLinkCreated.emit(evt);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private changeLayout() {
|
private changeLayout() {
|
||||||
@ -169,53 +144,22 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
this.context.size = this.getSize();
|
this.context.size = this.getSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.graphLayout.setNodes(this.nodes);
|
|
||||||
this.graphLayout.setLinks(this.links);
|
|
||||||
this.graphLayout.setDrawings(this.drawings);
|
|
||||||
|
|
||||||
this.redraw();
|
this.redraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
private onLinksChange(change: SimpleChange) {
|
|
||||||
const nodes_by_id = {};
|
|
||||||
this.nodes.forEach((n: Node) => {
|
|
||||||
nodes_by_id[n.node_id] = n;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.links.forEach((link: Link) => {
|
|
||||||
const source_id = link.nodes[0].node_id;
|
|
||||||
const target_id = link.nodes[1].node_id;
|
|
||||||
if (source_id in nodes_by_id) {
|
|
||||||
link.source = nodes_by_id[source_id];
|
|
||||||
}
|
|
||||||
if (target_id in nodes_by_id) {
|
|
||||||
link.target = nodes_by_id[target_id];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (link.source && link.target) {
|
|
||||||
link.x = link.source.x + (link.target.x - link.source.x) * 0.5;
|
|
||||||
link.y = link.source.y + (link.target.y - link.source.y) * 0.5;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private onNodesChange(change: SimpleChange) {
|
|
||||||
this.onLinksChange(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private onSymbolsChange(change: SimpleChange) {
|
private onSymbolsChange(change: SimpleChange) {
|
||||||
this.graphLayout.getNodesWidget().setSymbols(this.symbols);
|
this.graphDataManager.setSymbols(this.symbols);
|
||||||
}
|
}
|
||||||
|
|
||||||
public redraw() {
|
private redraw() {
|
||||||
|
this.graphDataManager.setNodes(this.nodes);
|
||||||
|
this.graphDataManager.setLinks(this.links);
|
||||||
|
this.graphDataManager.setDrawings(this.drawings);
|
||||||
|
|
||||||
this.graphLayout.draw(this.svg, this.context);
|
this.graphLayout.draw(this.svg, this.context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public reload() {
|
|
||||||
this.onLinksChange(null);
|
|
||||||
this.redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
@HostListener('window:resize', ['$event'])
|
@HostListener('window:resize', ['$event'])
|
||||||
onResize(event) {
|
onResize(event) {
|
||||||
this.changeLayout();
|
this.changeLayout();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild, OnDestroy} from '@angular/core';
|
import { ChangeDetectorRef, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
import {MatMenuTrigger} from "@angular/material";
|
import { MatMenuTrigger } from "@angular/material";
|
||||||
import {DomSanitizer} from "@angular/platform-browser";
|
import { DomSanitizer } from "@angular/platform-browser";
|
||||||
import {Node} from "../../../cartography/models/node";
|
import { MapNode } from '../../models/map/map-node';
|
||||||
import {Port} from "../../../models/port";
|
import { MapPort } from '../../models/map/map-port';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -18,7 +18,7 @@ export class NodeSelectInterfaceComponent implements OnInit {
|
|||||||
protected topPosition;
|
protected topPosition;
|
||||||
protected leftPosition;
|
protected leftPosition;
|
||||||
|
|
||||||
public node: Node;
|
public node: MapNode;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private sanitizer: DomSanitizer,
|
private sanitizer: DomSanitizer,
|
||||||
@ -35,13 +35,13 @@ export class NodeSelectInterfaceComponent implements OnInit {
|
|||||||
this.changeDetector.detectChanges();
|
this.changeDetector.detectChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
public open(node: Node, top: number, left: number) {
|
public open(node: MapNode, top: number, left: number) {
|
||||||
this.node = node;
|
this.node = node;
|
||||||
this.setPosition(top, left);
|
this.setPosition(top, left);
|
||||||
this.contextMenu.openMenu();
|
this.contextMenu.openMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
public chooseInterface(port: Port) {
|
public chooseInterface(port: MapPort) {
|
||||||
this.onChooseInterface.emit({
|
this.onChooseInterface.emit({
|
||||||
'node': this.node,
|
'node': this.node,
|
||||||
'port': port
|
'port': port
|
||||||
|
3
src/app/cartography/converters/converter.ts
Normal file
3
src/app/cartography/converters/converter.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export interface Converter<F, T> {
|
||||||
|
convert(obj: F): T;
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Drawing } from "../../models/drawing";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DrawingToMapDrawingConverter implements Converter<Drawing, MapDrawing> {
|
||||||
|
constructor(
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(drawing: Drawing) {
|
||||||
|
const mapDrawing = new MapDrawing();
|
||||||
|
mapDrawing.id = drawing.drawing_id;
|
||||||
|
mapDrawing.projectId = drawing.project_id;
|
||||||
|
mapDrawing.rotation = drawing.rotation;
|
||||||
|
mapDrawing.svg = drawing.svg;
|
||||||
|
mapDrawing.x = drawing.x;
|
||||||
|
mapDrawing.y = drawing.y;
|
||||||
|
mapDrawing.z = drawing.z;
|
||||||
|
return mapDrawing;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Label } from "../../models/label";
|
||||||
|
import { MapLabel } from "../../models/map/map-label";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LabelToMapLabelConverter implements Converter<Label, MapLabel> {
|
||||||
|
convert(label: Label) {
|
||||||
|
const mapLabel = new MapLabel();
|
||||||
|
mapLabel.rotation = label.rotation;
|
||||||
|
mapLabel.style = label.style;
|
||||||
|
mapLabel.text = label.text;
|
||||||
|
mapLabel.x = label.x;
|
||||||
|
mapLabel.y = label.y;
|
||||||
|
return mapLabel;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { LabelToMapLabelConverter } from "./label-to-map-label-converter";
|
||||||
|
import { LinkNode } from "../../../models/link-node";
|
||||||
|
import { MapLinkNode } from "../../models/map/map-link-node";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LinkNodeToMapLinkNodeConverter implements Converter<LinkNode, MapLinkNode> {
|
||||||
|
constructor(
|
||||||
|
private labelToMapLabel: LabelToMapLabelConverter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(linkNode: LinkNode) {
|
||||||
|
const mapLinkNode = new MapLinkNode();
|
||||||
|
mapLinkNode.nodeId = linkNode.node_id;
|
||||||
|
mapLinkNode.adapterNumber = linkNode.adapter_number;
|
||||||
|
mapLinkNode.portNumber = linkNode.port_number;
|
||||||
|
mapLinkNode.label = this.labelToMapLabel.convert(linkNode.label);
|
||||||
|
return mapLinkNode;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { LinkNodeToMapLinkNodeConverter } from "./link-node-to-map-link-node-converter";
|
||||||
|
import { Link } from "../../../models/link";
|
||||||
|
import { MapLink } from "../../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LinkToMapLinkConverter implements Converter<Link, MapLink> {
|
||||||
|
constructor(
|
||||||
|
private linkNodeToMapLinkNode: LinkNodeToMapLinkNodeConverter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(link: Link) {
|
||||||
|
const mapLink = new MapLink();
|
||||||
|
mapLink.id = link.link_id;
|
||||||
|
mapLink.captureFileName = link.capture_file_name;
|
||||||
|
mapLink.captureFilePath = link.capture_file_path;
|
||||||
|
mapLink.capturing = link.capturing;
|
||||||
|
mapLink.linkType = link.link_type;
|
||||||
|
mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode));
|
||||||
|
mapLink.projectId = link.project_id;
|
||||||
|
return mapLink;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Drawing } from "../../models/drawing";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapDrawingToDrawingConverter implements Converter<MapDrawing, Drawing> {
|
||||||
|
constructor(
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(mapDrawing: MapDrawing) {
|
||||||
|
const drawing = new Drawing();
|
||||||
|
drawing.drawing_id = mapDrawing.id;
|
||||||
|
drawing.project_id = mapDrawing.projectId;
|
||||||
|
drawing.rotation = mapDrawing.rotation;
|
||||||
|
drawing.svg = mapDrawing.svg;
|
||||||
|
drawing.x = mapDrawing.x;
|
||||||
|
drawing.y = mapDrawing.y;
|
||||||
|
drawing.z = mapDrawing.z;
|
||||||
|
return drawing;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Label } from "../../models/label";
|
||||||
|
import { MapLabel } from "../../models/map/map-label";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapLabelToLabelConverter implements Converter<MapLabel, Label> {
|
||||||
|
convert(mapLabel: MapLabel) {
|
||||||
|
const label = new Label();
|
||||||
|
label.rotation = mapLabel.rotation;
|
||||||
|
label.style = mapLabel.style;
|
||||||
|
label.text = mapLabel.text;
|
||||||
|
label.x = mapLabel.x;
|
||||||
|
label.y = mapLabel.y;
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { MapLinkNode } from "../../models/map/map-link-node";
|
||||||
|
import { MapLabelToLabelConverter } from "./map-label-to-label-converter";
|
||||||
|
import { LinkNode } from "../../../models/link-node";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapLinkNodeToLinkNodeConverter implements Converter<MapLinkNode, LinkNode> {
|
||||||
|
constructor(
|
||||||
|
private mapLabelToLabel: MapLabelToLabelConverter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(mapLinkNode: MapLinkNode) {
|
||||||
|
const linkNode = new LinkNode();
|
||||||
|
linkNode.node_id = mapLinkNode.nodeId;
|
||||||
|
linkNode.adapter_number = mapLinkNode.adapterNumber;
|
||||||
|
linkNode.port_number = mapLinkNode.portNumber;
|
||||||
|
linkNode.label = this.mapLabelToLabel.convert(mapLinkNode.label);
|
||||||
|
return linkNode;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { MapLinkNodeToLinkNodeConverter } from "./map-link-node-to-link-node-converter";
|
||||||
|
import { Link } from "../../../models/link";
|
||||||
|
import { MapLink } from "../../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapLinkToLinkConverter implements Converter<MapLink, Link> {
|
||||||
|
constructor(
|
||||||
|
private mapLinkNodeToMapLinkNode: MapLinkNodeToLinkNodeConverter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(mapLink: MapLink) {
|
||||||
|
const link = new Link();
|
||||||
|
link.link_id = mapLink.id;
|
||||||
|
link.capture_file_name = mapLink.captureFileName;
|
||||||
|
link.capture_file_path = mapLink.captureFilePath;
|
||||||
|
link.capturing = mapLink.capturing;
|
||||||
|
link.link_type = mapLink.linkType;
|
||||||
|
link.nodes = mapLink.nodes.map((mapLinkNode) => this.mapLinkNodeToMapLinkNode.convert(mapLinkNode));
|
||||||
|
link.project_id = mapLink.projectId;
|
||||||
|
return link;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { MapNode } from "../../models/map/map-node";
|
||||||
|
import { MapLabelToLabelConverter } from "./map-label-to-label-converter";
|
||||||
|
import { MapPortToPortConverter } from "./map-port-to-port-converter";
|
||||||
|
import { Node } from "../../models/node";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapNodeToNodeConverter implements Converter<MapNode, Node> {
|
||||||
|
constructor(
|
||||||
|
private mapLabelToLabel: MapLabelToLabelConverter,
|
||||||
|
private mapPortToPort: MapPortToPortConverter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(mapNode: MapNode) {
|
||||||
|
const node = new Node();
|
||||||
|
node.node_id = mapNode.id;
|
||||||
|
node.command_line = mapNode.commandLine;
|
||||||
|
node.compute_id = mapNode.computeId;
|
||||||
|
node.console = mapNode.console;
|
||||||
|
node.console_host = mapNode.consoleHost;
|
||||||
|
node.first_port_name = mapNode.firstPortName;
|
||||||
|
node.height = mapNode.height;
|
||||||
|
node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined;
|
||||||
|
node.name = mapNode.name;
|
||||||
|
node.node_directory = mapNode.nodeDirectory;
|
||||||
|
node.node_type = mapNode.nodeType;
|
||||||
|
node.port_name_format = mapNode.portNameFormat;
|
||||||
|
node.port_segment_size = mapNode.portSegmentSize;
|
||||||
|
node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : [];
|
||||||
|
node.project_id = mapNode.projectId;
|
||||||
|
node.status = mapNode.status;
|
||||||
|
node.symbol = mapNode.symbol;
|
||||||
|
node.width = mapNode.width;
|
||||||
|
node.x = mapNode.x;
|
||||||
|
node.y = mapNode.y;
|
||||||
|
node.z = mapNode.z;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Port } from "../../../models/port";
|
||||||
|
import { MapPort } from "../../models/map/map-port";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapPortToPortConverter implements Converter<MapPort, Port> {
|
||||||
|
convert(mapPort: MapPort) {
|
||||||
|
const port = new Port();
|
||||||
|
port.adapter_number = mapPort.adapterNumber;
|
||||||
|
port.link_type = mapPort.linkType;
|
||||||
|
port.name = mapPort.name;
|
||||||
|
port.port_number = mapPort.portNumber;
|
||||||
|
port.short_name = mapPort.shortName;
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { MapSymbol } from "../../models/map/map-symbol";
|
||||||
|
import { Symbol } from "../../../models/symbol";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapSymbolToSymbolConverter implements Converter<MapSymbol, Symbol> {
|
||||||
|
convert(mapSymbol: MapSymbol) {
|
||||||
|
const symbol = new Symbol();
|
||||||
|
symbol.symbol_id = mapSymbol.id;
|
||||||
|
symbol.builtin = mapSymbol.builtin;
|
||||||
|
symbol.filename = mapSymbol.filename;
|
||||||
|
symbol.raw = mapSymbol.raw;
|
||||||
|
return symbol;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { MapNode } from "../../models/map/map-node";
|
||||||
|
import { Node } from "../../models/node";
|
||||||
|
import { LabelToMapLabelConverter } from "./label-to-map-label-converter";
|
||||||
|
import { PortToMapPortConverter } from "./port-to-map-port-converter";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
|
||||||
|
constructor(
|
||||||
|
private labelToMapLabel: LabelToMapLabelConverter,
|
||||||
|
private portToMapPort: PortToMapPortConverter
|
||||||
|
) {}
|
||||||
|
|
||||||
|
convert(node: Node) {
|
||||||
|
const mapNode = new MapNode();
|
||||||
|
mapNode.id = node.node_id;
|
||||||
|
mapNode.commandLine = node.command_line;
|
||||||
|
mapNode.computeId = node.compute_id;
|
||||||
|
mapNode.console = node.console;
|
||||||
|
mapNode.consoleHost = node.console_host;
|
||||||
|
mapNode.firstPortName = node.first_port_name;
|
||||||
|
mapNode.height = node.height;
|
||||||
|
mapNode.label = this.labelToMapLabel.convert(node.label);
|
||||||
|
mapNode.name = node.name;
|
||||||
|
mapNode.nodeDirectory = node.node_directory;
|
||||||
|
mapNode.nodeType = node.node_type;
|
||||||
|
mapNode.portNameFormat = node.port_name_format;
|
||||||
|
mapNode.portSegmentSize = node.port_segment_size;
|
||||||
|
mapNode.ports = node.ports.map((port) => this.portToMapPort.convert(port));
|
||||||
|
mapNode.projectId = node.project_id;
|
||||||
|
mapNode.status = node.status;
|
||||||
|
mapNode.symbol = node.symbol;
|
||||||
|
mapNode.width = node.width;
|
||||||
|
mapNode.x = node.x;
|
||||||
|
mapNode.y = node.y;
|
||||||
|
mapNode.z = node.z;
|
||||||
|
return mapNode;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Port } from "../../../models/port";
|
||||||
|
import { MapPort } from "../../models/map/map-port";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PortToMapPortConverter implements Converter<Port, MapPort> {
|
||||||
|
convert(port: Port) {
|
||||||
|
const mapPort = new MapPort();
|
||||||
|
mapPort.adapterNumber = port.adapter_number;
|
||||||
|
mapPort.linkType = port.link_type;
|
||||||
|
mapPort.name = port.name;
|
||||||
|
mapPort.portNumber = port.port_number;
|
||||||
|
mapPort.shortName = port.short_name;
|
||||||
|
return mapPort;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Converter } from "../converter";
|
||||||
|
import { Symbol } from "../../../models/symbol";
|
||||||
|
import { MapSymbol } from "../../models/map/map-symbol";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SymbolToMapSymbolConverter implements Converter<Symbol, MapSymbol> {
|
||||||
|
convert(symbol: Symbol) {
|
||||||
|
const mapSymbol = new MapSymbol();
|
||||||
|
mapSymbol.id = symbol.symbol_id;
|
||||||
|
mapSymbol.builtin = symbol.builtin;
|
||||||
|
mapSymbol.filename = symbol.filename;
|
||||||
|
mapSymbol.raw = symbol.raw;
|
||||||
|
return mapSymbol;
|
||||||
|
}
|
||||||
|
}
|
39
src/app/cartography/d3-map.imports.ts
Normal file
39
src/app/cartography/d3-map.imports.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { GraphLayout } from './widgets/graph-layout';
|
||||||
|
import { LinksWidget } from './widgets/links';
|
||||||
|
import { NodesWidget } from './widgets/nodes';
|
||||||
|
import { DrawingsWidget } from './widgets/drawings';
|
||||||
|
import { DrawingLineWidget } from './widgets/drawing-line';
|
||||||
|
import { SelectionTool } from './tools/selection-tool';
|
||||||
|
import { MovingTool } from './tools/moving-tool';
|
||||||
|
import { LayersWidget } from './widgets/layers';
|
||||||
|
import { LinkWidget } from './widgets/link';
|
||||||
|
import { InterfaceStatusWidget } from './widgets/interface-status';
|
||||||
|
import { InterfaceLabelWidget } from './widgets/interface-label';
|
||||||
|
import { EllipseDrawingWidget } from './widgets/drawings/ellipse-drawing';
|
||||||
|
import { ImageDrawingWidget } from './widgets/drawings/image-drawing';
|
||||||
|
import { RectDrawingWidget } from './widgets/drawings/rect-drawing';
|
||||||
|
import { TextDrawingWidget } from './widgets/drawings/text-drawing';
|
||||||
|
import { LineDrawingWidget } from './widgets/drawings/line-drawing';
|
||||||
|
import { NodeWidget } from './widgets/node';
|
||||||
|
import { DrawingWidget } from './widgets/drawing';
|
||||||
|
|
||||||
|
export const D3_MAP_IMPORTS = [
|
||||||
|
GraphLayout,
|
||||||
|
LinksWidget,
|
||||||
|
NodesWidget,
|
||||||
|
NodeWidget,
|
||||||
|
DrawingsWidget,
|
||||||
|
DrawingLineWidget,
|
||||||
|
SelectionTool,
|
||||||
|
MovingTool,
|
||||||
|
LayersWidget,
|
||||||
|
LinkWidget,
|
||||||
|
InterfaceStatusWidget,
|
||||||
|
InterfaceLabelWidget,
|
||||||
|
EllipseDrawingWidget,
|
||||||
|
ImageDrawingWidget,
|
||||||
|
LineDrawingWidget,
|
||||||
|
RectDrawingWidget,
|
||||||
|
TextDrawingWidget,
|
||||||
|
DrawingWidget
|
||||||
|
];
|
@ -6,9 +6,9 @@ class Item {
|
|||||||
|
|
||||||
|
|
||||||
class TestDataSource extends DataSource<Item> {
|
class TestDataSource extends DataSource<Item> {
|
||||||
protected findIndex(item: Item) {
|
protected getItemKey(item: Item) {
|
||||||
return this.data.findIndex((i: Item) => i.id === item.id);
|
return item.id;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,10 +20,30 @@ export abstract class DataSource<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public set(data: T[]) {
|
public set(data: T[]) {
|
||||||
this.data = data;
|
data.forEach((item) => {
|
||||||
|
const index = this.findIndex(item);
|
||||||
|
if (index >= 0) {
|
||||||
|
const updated = Object.assign(this.data[index], item);
|
||||||
|
this.data[index] = updated;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.data.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const toRemove = this.data.filter((item) => data.filter((i) => this.getItemKey(i) === this.getItemKey(item)).length === 0);
|
||||||
|
toRemove.forEach((item) => this.remove(item));
|
||||||
|
|
||||||
this.dataChange.next(this.data);
|
this.dataChange.next(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get(key: string | number) {
|
||||||
|
const index = this.data.findIndex((i: T) => this.getItemKey(i) === key);
|
||||||
|
if (index >= 0) {
|
||||||
|
return this.data[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public update(item: T) {
|
public update(item: T) {
|
||||||
const index = this.findIndex(item);
|
const index = this.findIndex(item);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
@ -55,5 +75,9 @@ export abstract class DataSource<T> {
|
|||||||
this.dataChange.next(this.data);
|
this.dataChange.next(this.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract findIndex(item: T): number;
|
private findIndex(item: T) {
|
||||||
|
return this.data.findIndex((i: T) => this.getItemKey(i) === this.getItemKey(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract getItemKey(item: T): any;
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { Drawing } from "../models/drawing";
|
|
||||||
import { DataSource } from "./datasource";
|
import { DataSource } from "./datasource";
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DrawingsDataSource extends DataSource<Drawing> {
|
export class DrawingsDataSource extends DataSource<Drawing> {
|
||||||
protected findIndex(drawing: Drawing) {
|
protected getItemKey(drawing: Drawing) {
|
||||||
return this.data.findIndex((d: Drawing) => d.drawing_id === drawing.drawing_id);
|
return drawing.drawing_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { DataSource } from "./datasource";
|
import { DataSource } from "./datasource";
|
||||||
import { Link} from "../../models/link";
|
import { Link } from "../../models/link";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LinksDataSource extends DataSource<Link> {
|
export class LinksDataSource extends DataSource<Link> {
|
||||||
protected findIndex(link: Link) {
|
protected getItemKey(link: Link) {
|
||||||
return this.data.findIndex((l: Link) => l.link_id === link.link_id);
|
return link.link_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
28
src/app/cartography/datasources/map-datasource.ts
Normal file
28
src/app/cartography/datasources/map-datasource.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { DataSource } from "./datasource";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
|
import { MapSymbol } from "../models/map/map-symbol";
|
||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
export interface Indexed {
|
||||||
|
id: number | string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MapDataSource<T extends Indexed> extends DataSource<T> {
|
||||||
|
protected getItemKey(item: Indexed) {
|
||||||
|
return item.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapNodesDataSource extends MapDataSource<MapNode> {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapLinksDataSource extends MapDataSource<MapLink> {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapDrawingsDataSource extends MapDataSource<MapDrawing> {}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MapSymbolsDataSource extends MapDataSource<MapSymbol> {}
|
@ -6,7 +6,7 @@ import { DataSource } from "./datasource";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NodesDataSource extends DataSource<Node> {
|
export class NodesDataSource extends DataSource<Node> {
|
||||||
protected findIndex(node: Node) {
|
protected getItemKey(node: Node) {
|
||||||
return this.data.findIndex((n: Node) => n.node_id === node.node_id);
|
return node.node_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import { Symbol } from "../../models/symbol";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SymbolsDataSource extends DataSource<Symbol> {
|
export class SymbolsDataSource extends DataSource<Symbol> {
|
||||||
protected findIndex(symbol: Symbol) {
|
protected getItemKey(symbol: Symbol) {
|
||||||
return this.data.findIndex((s: Symbol) => s.symbol_id === symbol.symbol_id);
|
return symbol.symbol_id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
74
src/app/cartography/events/draggable.ts
Normal file
74
src/app/cartography/events/draggable.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { EventEmitter } from "@angular/core";
|
||||||
|
import { drag, DraggedElementBaseType } from "d3-drag";
|
||||||
|
import { event } from "d3-selection";
|
||||||
|
|
||||||
|
class DraggableEvent {
|
||||||
|
public x: number;
|
||||||
|
public y: number;
|
||||||
|
public dx: number;
|
||||||
|
public dy: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DraggableStart<T> extends DraggableEvent {
|
||||||
|
constructor(
|
||||||
|
public datum: T
|
||||||
|
){
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DraggableDrag<T> extends DraggableEvent {
|
||||||
|
constructor(
|
||||||
|
public datum: T
|
||||||
|
){
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DraggableEnd<T> extends DraggableEvent {
|
||||||
|
constructor(
|
||||||
|
public datum: T
|
||||||
|
){
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Draggable<GElement extends DraggedElementBaseType, Datum> {
|
||||||
|
public start = new EventEmitter<DraggableStart<Datum>>();
|
||||||
|
public drag = new EventEmitter<DraggableStart<Datum>>();
|
||||||
|
public end = new EventEmitter<DraggableStart<Datum>>();
|
||||||
|
|
||||||
|
public call(selection) {
|
||||||
|
selection.call(this.behaviour());
|
||||||
|
}
|
||||||
|
|
||||||
|
private behaviour() {
|
||||||
|
let startEvt;
|
||||||
|
|
||||||
|
return drag<GElement, Datum>()
|
||||||
|
.on('start', (datum: Datum) => {
|
||||||
|
startEvt = new DraggableStart<Datum>(datum);
|
||||||
|
startEvt.dx = event.dx;
|
||||||
|
startEvt.dy = event.dy;
|
||||||
|
startEvt.x = event.x;
|
||||||
|
startEvt.y = event.y;
|
||||||
|
this.start.emit(startEvt);
|
||||||
|
})
|
||||||
|
.on('drag', (datum: Datum) => {
|
||||||
|
const evt = new DraggableDrag<Datum>(datum);
|
||||||
|
evt.dx = event.dx;
|
||||||
|
evt.dy = event.dy;
|
||||||
|
evt.x = event.x;
|
||||||
|
evt.y = event.y;
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
9
src/app/cartography/events/drawings-event-source.ts
Normal file
9
src/app/cartography/events/drawings-event-source.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable, EventEmitter } from "@angular/core";
|
||||||
|
import { DraggedDataEvent } from "./event-source";
|
||||||
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DrawingsEventSource {
|
||||||
|
public dragged = new EventEmitter<DraggedDataEvent<MapDrawing>>();
|
||||||
|
}
|
9
src/app/cartography/events/event-source.ts
Normal file
9
src/app/cartography/events/event-source.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export class DataEventSource<T> {
|
||||||
|
constructor(
|
||||||
|
public datum: T,
|
||||||
|
public dx: number,
|
||||||
|
public dy: number
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DraggedDataEvent<T> extends DataEventSource<T> {}
|
8
src/app/cartography/events/links-event-source.ts
Normal file
8
src/app/cartography/events/links-event-source.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Injectable, EventEmitter } from "@angular/core";
|
||||||
|
import { MapLinkCreated } from "./links";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LinksEventSource {
|
||||||
|
public created = new EventEmitter<MapLinkCreated>();
|
||||||
|
}
|
12
src/app/cartography/events/links.ts
Normal file
12
src/app/cartography/events/links.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapPort } from "../models/map/map-port";
|
||||||
|
|
||||||
|
|
||||||
|
export class MapLinkCreated {
|
||||||
|
constructor(
|
||||||
|
public sourceNode: MapNode,
|
||||||
|
public sourcePort: MapPort,
|
||||||
|
public targetNode: MapNode,
|
||||||
|
public targetPort: MapPort
|
||||||
|
) {}
|
||||||
|
}
|
9
src/app/cartography/events/nodes-event-source.ts
Normal file
9
src/app/cartography/events/nodes-event-source.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Injectable, EventEmitter } from "@angular/core";
|
||||||
|
import { DraggedDataEvent } from "./event-source";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NodesEventSource {
|
||||||
|
public dragged = new EventEmitter<DraggedDataEvent<MapNode>>();
|
||||||
|
}
|
11
src/app/cartography/events/nodes.ts
Normal file
11
src/app/cartography/events/nodes.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
|
||||||
|
class NodeEvent {
|
||||||
|
constructor(
|
||||||
|
public event: any,
|
||||||
|
public node: MapNode
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NodeClicked extends NodeEvent {}
|
||||||
|
export class NodeContextMenu extends NodeEvent {}
|
18
src/app/cartography/helpers/canvas-size-detector.ts
Normal file
18
src/app/cartography/helpers/canvas-size-detector.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Size } from "../models/size";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CanvasSizeDetector {
|
||||||
|
public getOptimalSize(minWidth: number, minHeight: number) {
|
||||||
|
let width = document.documentElement.clientWidth;
|
||||||
|
let height = document.documentElement.clientHeight;
|
||||||
|
if (minWidth > width) {
|
||||||
|
width = minWidth;
|
||||||
|
}
|
||||||
|
if (minHeight > height) {
|
||||||
|
height = minHeight;
|
||||||
|
}
|
||||||
|
return new Size(width, height);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
import {Link} from "../../models/link";
|
|
||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MultiLinkCalculatorHelper {
|
export class MultiLinkCalculatorHelper {
|
||||||
@ -29,11 +30,11 @@ export class MultiLinkCalculatorHelper {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public assignDataToLinks(links: Link[]) {
|
public assignDataToLinks(links: MapLink[]) {
|
||||||
const links_from_nodes = {};
|
const links_from_nodes = {};
|
||||||
links.forEach((l: Link, i: number) => {
|
links.forEach((l: MapLink, i: number) => {
|
||||||
const sid = l.source.node_id;
|
const sid = l.source.id;
|
||||||
const tid = l.target.node_id;
|
const tid = l.target.id;
|
||||||
const key = (sid < tid ? sid + "," + tid : tid + "," + sid);
|
const key = (sid < tid ? sid + "," + tid : tid + "," + sid);
|
||||||
let idx = 1;
|
let idx = 1;
|
||||||
if (!(key in links_from_nodes)) {
|
if (!(key in links_from_nodes)) {
|
||||||
|
105
src/app/cartography/listeners/draggable-listener.ts
Normal file
105
src/app/cartography/listeners/draggable-listener.ts
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { NodesWidget } from "../widgets/nodes";
|
||||||
|
import { DraggableStart, DraggableDrag, DraggableEnd } from "../events/draggable";
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
import { LinksWidget } from "../widgets/links";
|
||||||
|
import { NodesEventSource } from "../events/nodes-event-source";
|
||||||
|
import { DraggedDataEvent } from "../events/event-source";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { GraphDataManager } from "../managers/graph-data-manager";
|
||||||
|
import { DrawingsWidget } from "../widgets/drawings";
|
||||||
|
import { merge } from "rxjs";
|
||||||
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
|
import { DrawingsEventSource } from "../events/drawings-event-source";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DraggableListener {
|
||||||
|
private start: Subscription;
|
||||||
|
private drag: Subscription;
|
||||||
|
private end: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private nodesWidget: NodesWidget,
|
||||||
|
private drawingsWidget: DrawingsWidget,
|
||||||
|
private linksWidget: LinksWidget,
|
||||||
|
private selectionManager: SelectionManager,
|
||||||
|
private nodesEventSource: NodesEventSource,
|
||||||
|
private drawingsEventSource: DrawingsEventSource,
|
||||||
|
private graphDataManager: GraphDataManager
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInit(svg: any) {
|
||||||
|
this.start = merge(
|
||||||
|
this.nodesWidget.draggable.start,
|
||||||
|
this.drawingsWidget.draggable.start
|
||||||
|
).subscribe((evt: DraggableStart<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();
|
||||||
|
}
|
||||||
|
}
|
4
src/app/cartography/listeners/map-listener.ts
Normal file
4
src/app/cartography/listeners/map-listener.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface MapListener {
|
||||||
|
onInit(svg: any);
|
||||||
|
onDestroy();
|
||||||
|
}
|
32
src/app/cartography/listeners/map-listeners.ts
Normal file
32
src/app/cartography/listeners/map-listeners.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
68
src/app/cartography/listeners/selection-listener.spec.ts
Normal file
68
src/app/cartography/listeners/selection-listener.spec.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Rectangle } from "../models/rectangle";
|
||||||
|
import { InRectangleHelper } from "../helpers/in-rectangle-helper";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
import { mock, instance, when } from "ts-mockito";
|
||||||
|
import { fakeAsync, tick } from "@angular/core/testing";
|
||||||
|
import { SelectionListener } from "./selection-listener";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
import { GraphDataManager } from "../managers/graph-data-manager";
|
||||||
|
import { SelectionTool } from "../tools/selection-tool";
|
||||||
|
import { Context } from "../models/context";
|
||||||
|
|
||||||
|
|
||||||
|
describe('SelectionListener', () => {
|
||||||
|
let selectionListener: SelectionListener;
|
||||||
|
let manager: SelectionManager;
|
||||||
|
let selectionTool: SelectionTool;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
const mockedGraphData = mock(GraphDataManager);
|
||||||
|
|
||||||
|
const node_1 = new MapNode();
|
||||||
|
node_1.id = "test1";
|
||||||
|
node_1.name = "Node 1";
|
||||||
|
node_1.x = 150;
|
||||||
|
node_1.y = 150;
|
||||||
|
|
||||||
|
const node_2 = new MapNode();
|
||||||
|
node_2.id = "test2";
|
||||||
|
node_2.name = "Node 2";
|
||||||
|
node_2.x = 300;
|
||||||
|
node_2.y = 300;
|
||||||
|
|
||||||
|
const link_1 = new MapLink();
|
||||||
|
link_1.id = "test1";
|
||||||
|
|
||||||
|
when(mockedGraphData.getNodes()).thenReturn([node_1, node_2]);
|
||||||
|
when(mockedGraphData.getLinks()).thenReturn([link_1]);
|
||||||
|
when(mockedGraphData.getDrawings()).thenReturn([]);
|
||||||
|
|
||||||
|
const graphData = instance(mockedGraphData);
|
||||||
|
const inRectangleHelper = new InRectangleHelper();
|
||||||
|
|
||||||
|
manager = new SelectionManager();
|
||||||
|
selectionTool = new SelectionTool(new Context());
|
||||||
|
selectionListener = new SelectionListener(selectionTool, graphData, inRectangleHelper, manager);
|
||||||
|
selectionListener.onInit(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
selectionListener.onDestroy();
|
||||||
|
})
|
||||||
|
|
||||||
|
it('node should be selected', fakeAsync(() => {
|
||||||
|
selectionTool.rectangleSelected.next(new Rectangle(100, 100, 100, 100));
|
||||||
|
tick();
|
||||||
|
expect(manager.getSelected().length).toEqual(1);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('node should be selected and deselected', fakeAsync(() => {
|
||||||
|
selectionTool.rectangleSelected.next(new Rectangle(100, 100, 100, 100));
|
||||||
|
tick();
|
||||||
|
selectionTool.rectangleSelected.next(new Rectangle(350, 350, 100, 100));
|
||||||
|
tick();
|
||||||
|
expect(manager.getSelected().length).toEqual(0);
|
||||||
|
}));
|
||||||
|
|
||||||
|
});
|
45
src/app/cartography/listeners/selection-listener.ts
Normal file
45
src/app/cartography/listeners/selection-listener.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { GraphDataManager } from "../managers/graph-data-manager";
|
||||||
|
import { InRectangleHelper } from "../helpers/in-rectangle-helper";
|
||||||
|
import { SelectionTool } from "../tools/selection-tool";
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
import { Rectangle } from "electron";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SelectionListener {
|
||||||
|
private onSelection: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private selectionTool: SelectionTool,
|
||||||
|
private graphDataManager: GraphDataManager,
|
||||||
|
private inRectangleHelper: InRectangleHelper,
|
||||||
|
private selectionManager: SelectionManager
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInit(svg: any) {
|
||||||
|
this.onSelection = this.selectionTool.rectangleSelected.subscribe((rectangle: Rectangle) => {
|
||||||
|
const selectedNodes = this.graphDataManager.getNodes().filter((node) => {
|
||||||
|
return this.inRectangleHelper.inRectangle(rectangle, node.x, node.y)
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedLinks = this.graphDataManager.getLinks().filter((link) => {
|
||||||
|
return this.inRectangleHelper.inRectangle(rectangle, link.x, link.y)
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedDrawings = this.graphDataManager.getDrawings().filter((drawing) => {
|
||||||
|
return this.inRectangleHelper.inRectangle(rectangle, drawing.x, drawing.y)
|
||||||
|
});
|
||||||
|
|
||||||
|
const selected = [...selectedNodes, ...selectedLinks, ...selectedDrawings];
|
||||||
|
|
||||||
|
this.selectionManager.setSelected(selected);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDestroy() {
|
||||||
|
this.onSelection.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
31
src/app/cartography/listeners/selection-update-listener.ts
Normal file
31
src/app/cartography/listeners/selection-update-listener.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
import { MapChangeDetectorRef } from "../services/map-change-detector-ref";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SelectionUpdateListener {
|
||||||
|
private onSelected: Subscription;
|
||||||
|
private onUnselected: Subscription;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private selectionManager: SelectionManager,
|
||||||
|
private mapChangeDetectorRef: MapChangeDetectorRef
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onInit(svg: any) {
|
||||||
|
this.onSelected = this.selectionManager.selected.subscribe(() => {
|
||||||
|
this.mapChangeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
this.onUnselected = this.selectionManager.unselected.subscribe(() => {
|
||||||
|
this.mapChangeDetectorRef.detectChanges();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public onDestroy() {
|
||||||
|
this.onSelected.unsubscribe();
|
||||||
|
this.onUnselected.unsubscribe();
|
||||||
|
}
|
||||||
|
}
|
38
src/app/cartography/managers/graph-data-manager.spec.ts
Normal file
38
src/app/cartography/managers/graph-data-manager.spec.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
export class MockedGraphDataManager {
|
||||||
|
private nodes = [];
|
||||||
|
private links = [];
|
||||||
|
private drawings = [];
|
||||||
|
private symbols = [];
|
||||||
|
|
||||||
|
public setNodes(value) {
|
||||||
|
this.nodes = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNodes() {
|
||||||
|
return this.nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLinks(value) {
|
||||||
|
this.links = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLinks() {
|
||||||
|
return this.links;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDrawings(value) {
|
||||||
|
this.drawings = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDrawings() {
|
||||||
|
return this.drawings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSymbols(value) {
|
||||||
|
this.symbols = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSymbols() {
|
||||||
|
return this.symbols;
|
||||||
|
}
|
||||||
|
}
|
103
src/app/cartography/managers/graph-data-manager.ts
Normal file
103
src/app/cartography/managers/graph-data-manager.ts
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
import { Node } from "../models/node";
|
||||||
|
import { NodeToMapNodeConverter } from "../converters/map/node-to-map-node-converter";
|
||||||
|
import { LinkToMapLinkConverter } from "../converters/map/link-to-map-link-converter";
|
||||||
|
import { DrawingToMapDrawingConverter } from "../converters/map/drawing-to-map-drawing-converter";
|
||||||
|
import { SymbolToMapSymbolConverter } from "../converters/map/symbol-to-map-symbol-converter";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
import { Link } from "../../models/link";
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
|
import { Symbol } from "../../models/symbol";
|
||||||
|
import { LayersManager } from "./layers-manager";
|
||||||
|
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from "../datasources/map-datasource";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GraphDataManager {
|
||||||
|
constructor(
|
||||||
|
private mapNodesDataSource: MapNodesDataSource,
|
||||||
|
private mapLinksDataSource: MapLinksDataSource,
|
||||||
|
private mapDrawingsDataSource: MapDrawingsDataSource,
|
||||||
|
private mapSymbolsDataSource: MapSymbolsDataSource,
|
||||||
|
private nodeToMapNode: NodeToMapNodeConverter,
|
||||||
|
private linkToMapLink: LinkToMapLinkConverter,
|
||||||
|
private drawingToMapDrawing: DrawingToMapDrawingConverter,
|
||||||
|
private symbolToMapSymbol: SymbolToMapSymbolConverter,
|
||||||
|
private layersManager: LayersManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public setNodes(nodes: Node[]) {
|
||||||
|
const mapNodes = nodes.map((n) => this.nodeToMapNode.convert(n));
|
||||||
|
this.mapNodesDataSource.set(mapNodes);
|
||||||
|
|
||||||
|
this.assignDataToLinks();
|
||||||
|
this.onDataUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLinks(links: Link[]) {
|
||||||
|
const mapLinks = links.map((l) => this.linkToMapLink.convert(l));
|
||||||
|
this.mapLinksDataSource.set(mapLinks);
|
||||||
|
|
||||||
|
this.assignDataToLinks();
|
||||||
|
this.onDataUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDrawings(drawings: Drawing[]) {
|
||||||
|
const mapDrawings = drawings.map((d) => this.drawingToMapDrawing.convert(d));
|
||||||
|
this.mapDrawingsDataSource.set(mapDrawings);
|
||||||
|
|
||||||
|
this.onDataUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
public setSymbols(symbols: Symbol[]) {
|
||||||
|
const mapSymbols = symbols.map((s) => this.symbolToMapSymbol.convert(s));
|
||||||
|
this.mapSymbolsDataSource.set(mapSymbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getNodes() {
|
||||||
|
return this.mapNodesDataSource.getItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLinks() {
|
||||||
|
return this.mapLinksDataSource.getItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getDrawings() {
|
||||||
|
return this.mapDrawingsDataSource.getItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSymbols() {
|
||||||
|
return this.mapSymbolsDataSource.getItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
private onDataUpdate() {
|
||||||
|
this.layersManager.clear();
|
||||||
|
this.layersManager.setNodes(this.getNodes());
|
||||||
|
this.layersManager.setLinks(this.getLinks());
|
||||||
|
this.layersManager.setDrawings(this.getDrawings());
|
||||||
|
}
|
||||||
|
|
||||||
|
private assignDataToLinks() {
|
||||||
|
const nodes_by_id = {};
|
||||||
|
this.getNodes().forEach((n: MapNode) => {
|
||||||
|
nodes_by_id[n.id] = n;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getLinks().forEach((link: MapLink) => {
|
||||||
|
const source_id = link.nodes[0].nodeId;
|
||||||
|
const target_id = link.nodes[1].nodeId;
|
||||||
|
if (source_id in nodes_by_id) {
|
||||||
|
link.source = nodes_by_id[source_id];
|
||||||
|
}
|
||||||
|
if (target_id in nodes_by_id) {
|
||||||
|
link.target = nodes_by_id[target_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link.source && link.target) {
|
||||||
|
link.x = link.source.x + (link.target.x - link.source.x) * 0.5;
|
||||||
|
link.y = link.source.y + (link.target.y - link.source.y) * 0.5;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import { LayersManager } from "./layers-manager";
|
import { LayersManager } from "./layers-manager";
|
||||||
import { Node } from "../models/node";
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
import { Drawing } from "../models/drawing";
|
import { MapLink } from "../models/map/map-link";
|
||||||
import { Link } from "../../models/link";
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
|
||||||
|
|
||||||
describe('LayersManager', () => {
|
describe('LayersManager', () => {
|
||||||
@ -12,9 +12,9 @@ describe('LayersManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('nodes should be added', () => {
|
it('nodes should be added', () => {
|
||||||
const node_1 = new Node();
|
const node_1 = new MapNode();
|
||||||
node_1.z = 1;
|
node_1.z = 1;
|
||||||
const node_2 = new Node();
|
const node_2 = new MapNode();
|
||||||
node_2.z = 2;
|
node_2.z = 2;
|
||||||
|
|
||||||
manager.setNodes([node_1, node_2]);
|
manager.setNodes([node_1, node_2]);
|
||||||
@ -27,9 +27,9 @@ describe('LayersManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('drawings should be added', () => {
|
it('drawings should be added', () => {
|
||||||
const drawing_1 = new Drawing();
|
const drawing_1 = new MapDrawing();
|
||||||
drawing_1.z = 1;
|
drawing_1.z = 1;
|
||||||
const drawing_2 = new Drawing();
|
const drawing_2 = new MapDrawing();
|
||||||
drawing_2.z = 2;
|
drawing_2.z = 2;
|
||||||
|
|
||||||
manager.setDrawings([drawing_1, drawing_2]);
|
manager.setDrawings([drawing_1, drawing_2]);
|
||||||
@ -42,17 +42,17 @@ describe('LayersManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('links should be added', () => {
|
it('links should be added', () => {
|
||||||
const node_1 = new Node();
|
const node_1 = new MapNode();
|
||||||
node_1.z = 1;
|
node_1.z = 1;
|
||||||
|
|
||||||
const node_2 = new Node();
|
const node_2 = new MapNode();
|
||||||
node_2.z = 2;
|
node_2.z = 2;
|
||||||
|
|
||||||
const link_1 = new Link();
|
const link_1 = new MapLink();
|
||||||
link_1.source = node_1;
|
link_1.source = node_1;
|
||||||
link_1.target = node_1;
|
link_1.target = node_1;
|
||||||
|
|
||||||
const link_2 = new Link();
|
const link_2 = new MapLink();
|
||||||
link_2.source = node_1;
|
link_2.source = node_1;
|
||||||
link_2.target = node_1;
|
link_2.target = node_1;
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ describe('LayersManager', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('layers should be cleared', () => {
|
it('layers should be cleared', () => {
|
||||||
const node_1 = new Node();
|
const node_1 = new MapNode();
|
||||||
node_1.z = 1;
|
node_1.z = 1;
|
||||||
const node_2 = new Node();
|
const node_2 = new MapNode();
|
||||||
node_2.z = 2;
|
node_2.z = 2;
|
||||||
|
|
||||||
manager.setNodes([node_1, node_2]);
|
manager.setNodes([node_1, node_2]);
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { Layer } from "../models/layer";
|
import { Layer } from "../models/layer";
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { Drawing } from "../models/drawing";
|
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { Dictionary } from "../models/types";
|
import { Dictionary } from "../models/types";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -23,26 +23,26 @@ export class LayersManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setNodes(nodes: Node[]) {
|
public setNodes(nodes: MapNode[]) {
|
||||||
nodes
|
nodes
|
||||||
.forEach((node: Node) => {
|
.forEach((node: MapNode) => {
|
||||||
const layer = this.getLayerForKey(node.z.toString());
|
const layer = this.getLayerForKey(node.z.toString());
|
||||||
layer.nodes.push(node);
|
layer.nodes.push(node);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setDrawings(drawings: Drawing[]) {
|
public setDrawings(drawings: MapDrawing[]) {
|
||||||
drawings
|
drawings
|
||||||
.forEach((drawing: Drawing) => {
|
.forEach((drawing: MapDrawing) => {
|
||||||
const layer = this.getLayerForKey(drawing.z.toString());
|
const layer = this.getLayerForKey(drawing.z.toString());
|
||||||
layer.drawings.push(drawing);
|
layer.drawings.push(drawing);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public setLinks(links: Link[]) {
|
public setLinks(links: MapLink[]) {
|
||||||
links
|
links
|
||||||
.filter((link: Link) => link.source && link.target)
|
.filter((link: MapLink) => link.source && link.target)
|
||||||
.forEach((link: Link) => {
|
.forEach((link: MapLink) => {
|
||||||
const key = Math.min(link.source.z, link.target.z).toString();
|
const key = Math.min(link.source.z, link.target.z).toString();
|
||||||
const layer = this.getLayerForKey(key);
|
const layer = this.getLayerForKey(key);
|
||||||
layer.links.push(link);
|
layer.links.push(link);
|
||||||
|
@ -1,95 +1,20 @@
|
|||||||
import { Subject} from "rxjs";
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { Drawing } from "../models/drawing";
|
|
||||||
import { Rectangle } from "../models/rectangle";
|
|
||||||
import { SelectionManager } from "./selection-manager";
|
import { SelectionManager } from "./selection-manager";
|
||||||
import { NodesDataSource } from "../datasources/nodes-datasource";
|
|
||||||
import { LinksDataSource } from "../datasources/links-datasource";
|
|
||||||
import { InRectangleHelper } from "../helpers/in-rectangle-helper";
|
|
||||||
import { DrawingsDataSource } from "../datasources/drawings-datasource";
|
|
||||||
|
|
||||||
|
|
||||||
describe('SelectionManager', () => {
|
describe('SelectionManager', () => {
|
||||||
let manager: SelectionManager;
|
let manager: SelectionManager;
|
||||||
let selectedRectangleSubject: Subject<Rectangle>;
|
|
||||||
let nodesDataSource: NodesDataSource;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const linksDataSource = new LinksDataSource();
|
manager = new SelectionManager();
|
||||||
const drawingsDataSource = new DrawingsDataSource();
|
|
||||||
const inRectangleHelper = new InRectangleHelper();
|
|
||||||
|
|
||||||
selectedRectangleSubject = new Subject<Rectangle>();
|
|
||||||
|
|
||||||
nodesDataSource = new NodesDataSource();
|
|
||||||
|
|
||||||
manager = new SelectionManager(nodesDataSource, linksDataSource, drawingsDataSource, inRectangleHelper);
|
|
||||||
manager.subscribe(selectedRectangleSubject);
|
|
||||||
|
|
||||||
const node_1 = new Node();
|
|
||||||
node_1.node_id = "test1";
|
|
||||||
node_1.name = "Node 1";
|
|
||||||
node_1.x = 150;
|
|
||||||
node_1.y = 150;
|
|
||||||
|
|
||||||
nodesDataSource.add(node_1);
|
|
||||||
|
|
||||||
const node_2 = new Node();
|
|
||||||
node_2.node_id = "test2";
|
|
||||||
node_2.name = "Node 2";
|
|
||||||
node_2.x = 300;
|
|
||||||
node_2.y = 300;
|
|
||||||
nodesDataSource.add(node_2);
|
|
||||||
|
|
||||||
const link_1 = new Link();
|
|
||||||
link_1.link_id = "test1";
|
|
||||||
linksDataSource.add(link_1);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('node should be selected', () => {
|
|
||||||
selectedRectangleSubject.next(new Rectangle(100, 100, 100, 100));
|
|
||||||
expect(nodesDataSource.getItems()[0].is_selected).toEqual(true);
|
|
||||||
expect(manager.getSelectedNodes().length).toEqual(1);
|
|
||||||
expect(manager.getSelectedLinks().length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('node should be selected and deselected', () => {
|
|
||||||
selectedRectangleSubject.next(new Rectangle(100, 100, 100, 100));
|
|
||||||
selectedRectangleSubject.next(new Rectangle(350, 350, 100, 100));
|
|
||||||
expect(nodesDataSource.getItems()[0].is_selected).toEqual(false);
|
|
||||||
expect(manager.getSelectedNodes().length).toEqual(0);
|
|
||||||
expect(manager.getSelectedLinks().length).toEqual(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('nodes should be manually selected', () => {
|
it('nodes should be manually selected', () => {
|
||||||
const node = new Node();
|
const node = new MapNode();
|
||||||
node.node_id = "test1";
|
node.id = "test1";
|
||||||
manager.setSelectedNodes([node]);
|
manager.setSelected([node]);
|
||||||
expect(manager.getSelectedNodes().length).toEqual(1);
|
expect(manager.getSelected().length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('links should be manually selected', () => {
|
|
||||||
const link = new Link();
|
|
||||||
link.link_id = "test1";
|
|
||||||
manager.setSelectedLinks([link]);
|
|
||||||
expect(manager.getSelectedLinks().length).toEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('items should be cleared', () => {
|
|
||||||
const link = new Link();
|
|
||||||
link.link_id = "test1";
|
|
||||||
const node = new Node();
|
|
||||||
node.node_id = "test1";
|
|
||||||
const drawing = new Drawing();
|
|
||||||
drawing.drawing_id = "test1";
|
|
||||||
manager.setSelectedLinks([link]);
|
|
||||||
manager.setSelectedNodes([node]);
|
|
||||||
manager.setSelectedDrawings([drawing]);
|
|
||||||
manager.clearSelection();
|
|
||||||
expect(manager.getSelectedLinks().length).toEqual(0);
|
|
||||||
expect(manager.getSelectedDrawings().length).toEqual(0);
|
|
||||||
expect(manager.getSelectedNodes().length).toEqual(0);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
@ -1,147 +1,55 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable, EventEmitter } from "@angular/core";
|
||||||
|
|
||||||
import { Subject } from "rxjs";
|
import { Indexed } from "../datasources/map-datasource";
|
||||||
import { Subscription } from "rxjs";
|
|
||||||
|
|
||||||
import { NodesDataSource } from "../datasources/nodes-datasource";
|
|
||||||
import { LinksDataSource } from "../datasources/links-datasource";
|
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { InRectangleHelper } from "../helpers/in-rectangle-helper";
|
|
||||||
import { Rectangle } from "../models/rectangle";
|
|
||||||
import { Link} from "../../models/link";
|
|
||||||
import { DataSource } from "../datasources/datasource";
|
|
||||||
import { Drawing } from "../models/drawing";
|
|
||||||
import { InterfaceLabel } from "../models/interface-label";
|
|
||||||
import { DrawingsDataSource } from "../datasources/drawings-datasource";
|
|
||||||
|
|
||||||
|
|
||||||
export interface Selectable {
|
|
||||||
x: number;
|
|
||||||
y: number;
|
|
||||||
is_selected: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SelectionManager {
|
export class SelectionManager {
|
||||||
private selectedNodes: Node[] = [];
|
private selection: {[id:string]: any} = {};
|
||||||
private selectedLinks: Link[] = [];
|
|
||||||
private selectedDrawings: Drawing[] = [];
|
|
||||||
private selectedInterfaceLabels: InterfaceLabel[] = [];
|
|
||||||
|
|
||||||
private subscription: Subscription;
|
public selected = new EventEmitter<any[]>();
|
||||||
|
public unselected = new EventEmitter<any[]>();
|
||||||
|
|
||||||
constructor(private nodesDataSource: NodesDataSource,
|
public setSelected(items: Indexed[]) {
|
||||||
private linksDataSource: LinksDataSource,
|
const dictItems = this.convertToKeyDict(items);
|
||||||
private drawingsDataSource: DrawingsDataSource,
|
|
||||||
private inRectangleHelper: InRectangleHelper) {}
|
|
||||||
|
|
||||||
|
const selected = Object.keys(dictItems).filter((key) => {
|
||||||
|
return !this.isSelectedByKey(key);
|
||||||
|
}).map(key => dictItems[key]);
|
||||||
|
|
||||||
public subscribe(subject: Subject<Rectangle>) {
|
const unselected = Object.keys(this.selection).filter((key) => {
|
||||||
this.subscription = subject.subscribe((rectangle: Rectangle) => {
|
return !(key in dictItems);
|
||||||
this.onSelection(rectangle);
|
}).map((key) => this.selection[key]);
|
||||||
|
|
||||||
|
this.selection = dictItems;
|
||||||
|
|
||||||
|
this.selected.emit(selected);
|
||||||
|
this.unselected.emit(unselected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getSelected(): Indexed[] {
|
||||||
|
return Object.keys(this.selection).map(key => this.selection[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isSelected(item): boolean {
|
||||||
|
const key = this.getKey(item);
|
||||||
|
return this.isSelectedByKey(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private isSelectedByKey(key): boolean {
|
||||||
|
return key in this.selection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getKey(item: Indexed): string {
|
||||||
|
const type = item.constructor.name;
|
||||||
|
return `${type}-${item.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private convertToKeyDict(items: Indexed[]) {
|
||||||
|
const dict = {};
|
||||||
|
items.forEach((item) => {
|
||||||
|
dict[this.getKey(item)] = item;
|
||||||
});
|
});
|
||||||
return this.subscription;
|
return dict;
|
||||||
}
|
|
||||||
|
|
||||||
public onSelection(rectangle: Rectangle) {
|
|
||||||
this.selectedNodes = this.getSelectedItemsInRectangle<Node>(this.nodesDataSource, rectangle);
|
|
||||||
this.selectedLinks = this.getSelectedItemsInRectangle<Link>(this.linksDataSource, rectangle);
|
|
||||||
this.selectedDrawings = this.getSelectedItemsInRectangle<Drawing>(this.drawingsDataSource, rectangle);
|
|
||||||
// don't select interfaces for now
|
|
||||||
// this.selectedInterfaceLabels = this.getSelectedInterfaceLabelsInRectangle(rectangle);
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSelectedNodes() {
|
|
||||||
return this.selectedNodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSelectedLinks() {
|
|
||||||
return this.selectedLinks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getSelectedDrawings() {
|
|
||||||
return this.selectedDrawings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSelectedNodes(nodes: Node[]) {
|
|
||||||
this.selectedNodes = this.setSelectedItems<Node>(this.nodesDataSource, (node: Node) => {
|
|
||||||
return !!nodes.find((n: Node) => node.node_id === n.node_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSelectedLinks(links: Link[]) {
|
|
||||||
this.selectedLinks = this.setSelectedItems<Link>(this.linksDataSource, (link: Link) => {
|
|
||||||
return !!links.find((l: Link) => link.link_id === l.link_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public setSelectedDrawings(drawings: Drawing[]) {
|
|
||||||
this.selectedDrawings = this.setSelectedItems<Drawing>(this.drawingsDataSource, (drawing: Drawing) => {
|
|
||||||
return !!drawings.find((d: Drawing) => drawing.drawing_id === d.drawing_id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public clearSelection() {
|
|
||||||
this.setSelectedDrawings([]);
|
|
||||||
this.setSelectedLinks([]);
|
|
||||||
this.setSelectedNodes([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSelectedItemsInRectangle<T extends Selectable>(dataSource: DataSource<T>, rectangle: Rectangle) {
|
|
||||||
return this.setSelectedItems<T>(dataSource, (item: T) => {
|
|
||||||
return this.inRectangleHelper.inRectangle(rectangle, item.x, item.y);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSelectedInterfaceLabelsInRectangle(rectangle: Rectangle) {
|
|
||||||
this.linksDataSource.getItems().forEach((link: Link) => {
|
|
||||||
if (!(link.source && link.target && link.nodes.length > 1)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let updated = false;
|
|
||||||
|
|
||||||
let x = link.source.x + link.nodes[0].label.x;
|
|
||||||
let y = link.source.y + link.nodes[0].label.y;
|
|
||||||
|
|
||||||
if (this.inRectangleHelper.inRectangle(rectangle, x, y)) {
|
|
||||||
link.nodes[0].label.is_selected = true;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
x = link.target.x + link.nodes[1].label.x;
|
|
||||||
y = link.target.y + link.nodes[1].label.y;
|
|
||||||
|
|
||||||
if (this.inRectangleHelper.inRectangle(rectangle, x, y)) {
|
|
||||||
link.nodes[1].label.is_selected = true;
|
|
||||||
updated = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updated) {
|
|
||||||
this.linksDataSource.update(link);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
private setSelected<T extends Selectable>(item: T, isSelected: boolean, dataSource: DataSource<T>): boolean {
|
|
||||||
if (item.is_selected !== isSelected) {
|
|
||||||
item.is_selected = isSelected;
|
|
||||||
dataSource.update(item);
|
|
||||||
}
|
|
||||||
return item.is_selected;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setSelectedItems<T extends Selectable>(dataSource: DataSource<T>, discriminator: (item: T) => boolean) {
|
|
||||||
const selected: T[] = [];
|
|
||||||
dataSource.getItems().forEach((item: T) => {
|
|
||||||
const isSelected = discriminator(item);
|
|
||||||
this.setSelected<T>(item, isSelected, dataSource);
|
|
||||||
if (isSelected) {
|
|
||||||
selected.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return selected;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import { Selectable } from "../managers/selection-manager";
|
|
||||||
import { DrawingElement } from "./drawings/drawing-element";
|
import { DrawingElement } from "./drawings/drawing-element";
|
||||||
|
|
||||||
export class Drawing implements Selectable {
|
export class Drawing {
|
||||||
drawing_id: string;
|
drawing_id: string;
|
||||||
project_id: string;
|
project_id: string;
|
||||||
rotation: number;
|
rotation: number;
|
||||||
@ -9,6 +8,5 @@ export class Drawing implements Selectable {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
is_selected = false;
|
|
||||||
element: DrawingElement; // @todo; move to context
|
element: DrawingElement; // @todo; move to context
|
||||||
}
|
}
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
export class GraphLink {
|
|
||||||
distance: number; // this is not from server
|
|
||||||
length: number; // this is not from server
|
|
||||||
source: Node; // this is not from server
|
|
||||||
target: Node; // this is not from server
|
|
||||||
x: number; // this is not from server
|
|
||||||
y: number; // this is not from server
|
|
||||||
}
|
|
@ -1,6 +1,4 @@
|
|||||||
import { Selectable } from "../managers/selection-manager";
|
export class InterfaceLabel {
|
||||||
|
|
||||||
export class InterfaceLabel implements Selectable {
|
|
||||||
constructor(
|
constructor(
|
||||||
public link_id: string,
|
public link_id: string,
|
||||||
public direction: string,
|
public direction: string,
|
||||||
@ -9,6 +7,5 @@ export class InterfaceLabel implements Selectable {
|
|||||||
public text: string,
|
public text: string,
|
||||||
public style: string,
|
public style: string,
|
||||||
public rotation = 0,
|
public rotation = 0,
|
||||||
public is_selected = false
|
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import { Selectable } from "../managers/selection-manager";
|
export class Label {
|
||||||
|
|
||||||
export class Label implements Selectable {
|
|
||||||
rotation: number;
|
rotation: number;
|
||||||
style: string;
|
style: string;
|
||||||
text: string;
|
text: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
is_selected: boolean;
|
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import {Drawing} from "./drawing";
|
import { MapNode } from "./map/map-node";
|
||||||
import {Link} from "../../models/link";
|
import { MapDrawing } from "./map/map-drawing";
|
||||||
import {Node} from "./node";
|
import { MapLink } from "./map/map-link";
|
||||||
|
|
||||||
export class Layer {
|
export class Layer {
|
||||||
constructor(
|
constructor(
|
||||||
public index?: number,
|
public index?: number,
|
||||||
public nodes: Node[] = [],
|
public nodes: MapNode[] = [],
|
||||||
public drawings: Drawing[] = [],
|
public drawings: MapDrawing[] = [],
|
||||||
public links: Link[] = []
|
public links: MapLink[] = []
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
src/app/cartography/models/map/map-drawing.ts
Normal file
13
src/app/cartography/models/map/map-drawing.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { DrawingElement } from "../drawings/drawing-element";
|
||||||
|
import { Indexed } from "../../datasources/map-datasource";
|
||||||
|
|
||||||
|
export class MapDrawing implements Indexed {
|
||||||
|
id: string;
|
||||||
|
projectId: string;
|
||||||
|
rotation: number;
|
||||||
|
svg: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
element: DrawingElement; // @todo; apply converters
|
||||||
|
}
|
8
src/app/cartography/models/map/map-label.ts
Normal file
8
src/app/cartography/models/map/map-label.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export class MapLabel {
|
||||||
|
rotation: number;
|
||||||
|
style: string;
|
||||||
|
text: string;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
isSelected: boolean;
|
||||||
|
}
|
8
src/app/cartography/models/map/map-link-node.ts
Normal file
8
src/app/cartography/models/map/map-link-node.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { MapLabel } from "./map-label";
|
||||||
|
|
||||||
|
export class MapLinkNode {
|
||||||
|
nodeId: string;
|
||||||
|
adapterNumber: number;
|
||||||
|
portNumber: number;
|
||||||
|
label: MapLabel;
|
||||||
|
}
|
22
src/app/cartography/models/map/map-link.ts
Normal file
22
src/app/cartography/models/map/map-link.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { MapLinkNode } from "./map-link-node";
|
||||||
|
import { MapNode } from "./map-node";
|
||||||
|
import { Indexed } from "../../datasources/map-datasource";
|
||||||
|
|
||||||
|
export class MapLink implements Indexed {
|
||||||
|
id: string;
|
||||||
|
captureFileName: string;
|
||||||
|
captureFilePath: string;
|
||||||
|
capturing: boolean;
|
||||||
|
linkType: string;
|
||||||
|
nodes: MapLinkNode[];
|
||||||
|
projectId: string;
|
||||||
|
|
||||||
|
distance: number; // this is not from server
|
||||||
|
length: number; // this is not from server
|
||||||
|
source: MapNode; // this is not from server
|
||||||
|
target: MapNode; // this is not from server
|
||||||
|
|
||||||
|
isSelected = false; // this is not from server
|
||||||
|
x: number; // this is not from server
|
||||||
|
y: number; // this is not from server
|
||||||
|
}
|
29
src/app/cartography/models/map/map-node.ts
Normal file
29
src/app/cartography/models/map/map-node.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { MapLabel } from "./map-label";
|
||||||
|
import { MapPort } from "./map-port";
|
||||||
|
import { Indexed } from "../../datasources/map-datasource";
|
||||||
|
|
||||||
|
export class MapNode implements Indexed {
|
||||||
|
id: string;
|
||||||
|
commandLine: string;
|
||||||
|
computeId: string;
|
||||||
|
console: number;
|
||||||
|
consoleHost: string;
|
||||||
|
consoleType: string;
|
||||||
|
firstPortName: string;
|
||||||
|
height: number;
|
||||||
|
label: MapLabel;
|
||||||
|
name: string;
|
||||||
|
nodeDirectory: string;
|
||||||
|
nodeType: string;
|
||||||
|
portNameFormat: string;
|
||||||
|
portSegmentSize: number;
|
||||||
|
ports: MapPort[];
|
||||||
|
projectId: string;
|
||||||
|
status: string;
|
||||||
|
symbol: string;
|
||||||
|
width: number;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
isSelected = false;
|
||||||
|
}
|
7
src/app/cartography/models/map/map-port.ts
Normal file
7
src/app/cartography/models/map/map-port.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export class MapPort {
|
||||||
|
adapterNumber: number;
|
||||||
|
linkType: string;
|
||||||
|
name: string;
|
||||||
|
portNumber: number;
|
||||||
|
shortName: string;
|
||||||
|
}
|
8
src/app/cartography/models/map/map-symbol.ts
Normal file
8
src/app/cartography/models/map/map-symbol.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Indexed } from "../../datasources/map-datasource";
|
||||||
|
|
||||||
|
export class MapSymbol implements Indexed {
|
||||||
|
id: string;
|
||||||
|
builtin: boolean;
|
||||||
|
filename: string;
|
||||||
|
raw: string;
|
||||||
|
}
|
@ -1,9 +1,8 @@
|
|||||||
import { Label } from "./label";
|
import { Label } from "./label";
|
||||||
import { Port } from "../../models/port";
|
import { Port } from "../../models/port";
|
||||||
import { Selectable } from "../managers/selection-manager";
|
|
||||||
|
|
||||||
|
|
||||||
export class Node implements Selectable {
|
export class Node {
|
||||||
command_line: string;
|
command_line: string;
|
||||||
compute_id: string;
|
compute_id: string;
|
||||||
console: number;
|
console: number;
|
||||||
@ -26,5 +25,4 @@ export class Node implements Selectable {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
is_selected = false;
|
|
||||||
}
|
}
|
||||||
|
@ -3,4 +3,4 @@ export class Point {
|
|||||||
public x?: number,
|
public x?: number,
|
||||||
public y?: number
|
public y?: number
|
||||||
) {}
|
) {}
|
||||||
};
|
}
|
||||||
|
@ -3,9 +3,11 @@ import { Injectable, EventEmitter } from "@angular/core";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MapChangeDetectorRef {
|
export class MapChangeDetectorRef {
|
||||||
public changesDetected = new EventEmitter<boolean>();
|
public changesDetected = new EventEmitter<boolean>();
|
||||||
|
|
||||||
public detectChanges() {
|
public hasBeenDrawn = false;
|
||||||
this.changesDetected.emit(true);
|
|
||||||
}
|
public detectChanges() {
|
||||||
|
this.changesDetected.emit(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,6 @@ export class SelectionTool {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public setEnabled(enabled) {
|
public setEnabled(enabled) {
|
||||||
if (this.enabled != enabled) {
|
|
||||||
if (enabled) {
|
|
||||||
this.needsActivate = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.needsDeactivate = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +72,17 @@ export class SelectionTool {
|
|||||||
.attr("visibility", "hidden");
|
.attr("visibility", "hidden");
|
||||||
}
|
}
|
||||||
|
|
||||||
if(this.needsActivate) {
|
const tool = canvas.select<SVGGElement>("g.selection-line-tool");
|
||||||
|
const status = tool.attr('status');
|
||||||
|
|
||||||
|
|
||||||
|
if(status !== 'activated' && this.enabled) {
|
||||||
this.activate(selection);
|
this.activate(selection);
|
||||||
this.needsActivate = false;
|
tool.attr('activated');
|
||||||
}
|
}
|
||||||
if(this.needsDeactivate) {
|
if(status !== 'deactivated' && !this.enabled) {
|
||||||
this.deactivate(selection);
|
this.deactivate(selection);
|
||||||
this.needsDeactivate = false;
|
tool.attr('deactivated');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
52
src/app/cartography/widgets/drawing.ts
Normal file
52
src/app/cartography/widgets/drawing.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Widget } from "./widget";
|
||||||
|
import { SVGSelection } from "../models/types";
|
||||||
|
import { DrawingShapeWidget } from "./drawings/drawing-shape-widget";
|
||||||
|
import { TextDrawingWidget } from "./drawings/text-drawing";
|
||||||
|
import { ImageDrawingWidget } from "./drawings/image-drawing";
|
||||||
|
import { RectDrawingWidget } from "./drawings/rect-drawing";
|
||||||
|
import { LineDrawingWidget } from "./drawings/line-drawing";
|
||||||
|
import { EllipseDrawingWidget } from "./drawings/ellipse-drawing";
|
||||||
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DrawingWidget implements Widget {
|
||||||
|
private drawingWidgets: DrawingShapeWidget[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private textDrawingWidget: TextDrawingWidget,
|
||||||
|
private imageDrawingWidget: ImageDrawingWidget,
|
||||||
|
private rectDrawingWidget: RectDrawingWidget,
|
||||||
|
private lineDrawingWidget: LineDrawingWidget,
|
||||||
|
private ellipseDrawingWidget: EllipseDrawingWidget
|
||||||
|
) {
|
||||||
|
this.drawingWidgets = [
|
||||||
|
this.textDrawingWidget,
|
||||||
|
this.imageDrawingWidget,
|
||||||
|
this.rectDrawingWidget,
|
||||||
|
this.lineDrawingWidget,
|
||||||
|
this.ellipseDrawingWidget
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public draw(view: SVGSelection) {
|
||||||
|
const drawing_body = view.selectAll<SVGGElement, MapDrawing>("g.drawing_body")
|
||||||
|
.data((l) => [l]);
|
||||||
|
|
||||||
|
const drawing_body_enter = drawing_body.enter()
|
||||||
|
.append<SVGGElement>('g')
|
||||||
|
.attr("class", "drawing_body");
|
||||||
|
|
||||||
|
const drawing_body_merge = drawing_body.merge(drawing_body_enter)
|
||||||
|
.attr('transform', (d: MapDrawing) => {
|
||||||
|
return `translate(${d.x},${d.y}) rotate(${d.rotation})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.drawingWidgets.forEach((widget) => {
|
||||||
|
widget.draw(drawing_body_merge);
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -1,73 +1,70 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { Widget } from "./widget";
|
import { Widget } from "./widget";
|
||||||
import { Drawing } from "../models/drawing";
|
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Layer } from "../models/layer";
|
import { Layer } from "../models/layer";
|
||||||
import { TextDrawingWidget } from "./drawings/text-drawing";
|
|
||||||
import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter";
|
import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter";
|
||||||
import { ImageDrawingWidget } from "./drawings/image-drawing";
|
import { Draggable } from "../events/draggable";
|
||||||
import { RectDrawingWidget } from "./drawings/rect-drawing";
|
import { DrawingWidget } from "./drawing";
|
||||||
import { LineDrawingWidget } from "./drawings/line-drawing";
|
import { MapDrawing } from "../models/map/map-drawing";
|
||||||
import { EllipseDrawingWidget } from "./drawings/ellipse-drawing";
|
|
||||||
import { DrawingWidget } from "./drawings/drawing-widget";
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class DrawingsWidget implements Widget {
|
export class DrawingsWidget implements Widget {
|
||||||
private drawingWidgets: DrawingWidget[] = [];
|
public draggable = new Draggable<SVGGElement, MapDrawing>();
|
||||||
|
public draggingEnabled = false;
|
||||||
|
|
||||||
|
// public onContextMenu = new EventEmitter<NodeContextMenu>();
|
||||||
|
// public onDrawingClicked = new EventEmitter<NodeClicked>();
|
||||||
|
// public onDrawingDragged = new EventEmitter<NodeDragged>();
|
||||||
|
// public onDrawingDragging = new EventEmitter<NodeDragging>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private drawingWidget: DrawingWidget,
|
||||||
private svgToDrawingConverter: SvgToDrawingConverter,
|
private svgToDrawingConverter: SvgToDrawingConverter,
|
||||||
private textDrawingWidget: TextDrawingWidget,
|
|
||||||
private imageDrawingWidget: ImageDrawingWidget,
|
|
||||||
private rectDrawingWidget: RectDrawingWidget,
|
|
||||||
private lineDrawingWidget: LineDrawingWidget,
|
|
||||||
private ellipseDrawingWidget: EllipseDrawingWidget
|
|
||||||
) {
|
) {
|
||||||
this.svgToDrawingConverter = new SvgToDrawingConverter();
|
this.svgToDrawingConverter = new SvgToDrawingConverter();
|
||||||
|
|
||||||
this.drawingWidgets = [
|
|
||||||
this.textDrawingWidget,
|
|
||||||
this.imageDrawingWidget,
|
|
||||||
this.rectDrawingWidget,
|
|
||||||
this.lineDrawingWidget,
|
|
||||||
this.ellipseDrawingWidget
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(view: SVGSelection, drawings?: Drawing[]) {
|
public redrawDrawing(view: SVGSelection, drawing: MapDrawing) {
|
||||||
|
this.drawingWidget.draw(this.selectDrawing(view, drawing));
|
||||||
|
}
|
||||||
|
|
||||||
|
public draw(view: SVGSelection) {
|
||||||
const drawing = view
|
const drawing = view
|
||||||
.selectAll<SVGGElement, Drawing>('g.drawing')
|
.selectAll<SVGGElement, MapDrawing>("g.drawing")
|
||||||
.data((l: Layer) => {
|
.data((layer: Layer) => {
|
||||||
l.drawings.forEach((d: Drawing) => {
|
layer.drawings.forEach((d: MapDrawing) => {
|
||||||
try {
|
try {
|
||||||
d.element = this.svgToDrawingConverter.convert(d.svg);
|
d.element = this.svgToDrawingConverter.convert(d.svg);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`Cannot convert due to Error: '${error}'`);
|
console.log(`Cannot convert due to Error: '${error}'`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return l.drawings;
|
return layer.drawings;
|
||||||
}, (d: Drawing) => {
|
}, (l: MapDrawing) => {
|
||||||
return d.drawing_id;
|
return l.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const drawing_enter = drawing.enter()
|
const drawing_enter = drawing.enter()
|
||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'drawing');
|
.attr('class', 'drawing')
|
||||||
|
.attr('drawing_id', (l: MapDrawing) => l.id)
|
||||||
|
|
||||||
const drawing_merge = drawing.merge(drawing_enter)
|
const merge = drawing.merge(drawing_enter);
|
||||||
.attr('transform', (d: Drawing) => {
|
|
||||||
return `translate(${d.x},${d.y}) rotate(${d.rotation})`;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.drawingWidgets.forEach((widget) => {
|
this.drawingWidget.draw(merge);
|
||||||
widget.draw(drawing_merge);
|
|
||||||
});
|
|
||||||
|
|
||||||
drawing
|
drawing
|
||||||
.exit()
|
.exit()
|
||||||
.remove();
|
.remove();
|
||||||
|
|
||||||
|
if (this.draggingEnabled) {
|
||||||
|
this.draggable.call(merge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectDrawing(view: SVGSelection, drawing: MapDrawing) {
|
||||||
|
return view.selectAll<SVGGElement, MapDrawing>(`g.drawing[drawing_id="${drawing.id}"]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
|
|
||||||
export interface DrawingWidget {
|
export interface DrawingShapeWidget {
|
||||||
draw(view: SVGSelection);
|
draw(view: SVGSelection);
|
||||||
}
|
}
|
@ -1,19 +1,19 @@
|
|||||||
import { TestSVGCanvas } from "../../testing";
|
import { TestSVGCanvas } from "../../testing";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { EllipseDrawingWidget } from "./ellipse-drawing";
|
|
||||||
import { EllipseElement } from "../../models/drawings/ellipse-element";
|
import { EllipseElement } from "../../models/drawings/ellipse-element";
|
||||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
import { EllipseDrawingWidget } from "./ellipse-drawing";
|
||||||
|
|
||||||
|
|
||||||
describe('EllipseDrawingWidget', () => {
|
describe('EllipseDrawingWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
let widget: EllipseDrawingWidget;
|
let widget: EllipseDrawingWidget;
|
||||||
let drawing: Drawing;
|
let drawing: MapDrawing;
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
drawing = new Drawing();
|
drawing = new MapDrawing();
|
||||||
widget = new EllipseDrawingWidget(new QtDasharrayFixer());
|
widget = new EllipseDrawingWidget(new QtDasharrayFixer());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -34,7 +34,7 @@ describe('EllipseDrawingWidget', () => {
|
|||||||
ellipse.ry = 40;
|
ellipse.ry = 40;
|
||||||
drawing.element = ellipse;
|
drawing.element = ellipse;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { EllipseElement } from "../../models/drawings/ellipse-element";
|
import { EllipseElement } from "../../models/drawings/ellipse-element";
|
||||||
import { DrawingWidget } from "./drawing-widget";
|
import { DrawingShapeWidget } from "./drawing-shape-widget";
|
||||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class EllipseDrawingWidget implements DrawingWidget {
|
export class EllipseDrawingWidget implements DrawingShapeWidget {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private qtDasharrayFixer: QtDasharrayFixer
|
private qtDasharrayFixer: QtDasharrayFixer
|
||||||
@ -17,7 +17,7 @@ export class EllipseDrawingWidget implements DrawingWidget {
|
|||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
const drawing = view
|
const drawing = view
|
||||||
.selectAll<SVGEllipseElement, EllipseElement>('ellipse.ellipse_element')
|
.selectAll<SVGEllipseElement, EllipseElement>('ellipse.ellipse_element')
|
||||||
.data((d: Drawing) => {
|
.data((d: MapDrawing) => {
|
||||||
return (d.element && d.element instanceof EllipseElement) ? [d.element] : [];
|
return (d.element && d.element instanceof EllipseElement) ? [d.element] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { TestSVGCanvas } from "../../testing";
|
import { TestSVGCanvas } from "../../testing";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { ImageDrawingWidget } from "./image-drawing";
|
import { ImageDrawingWidget } from "./image-drawing";
|
||||||
import { ImageElement } from "../../models/drawings/image-element";
|
import { ImageElement } from "../../models/drawings/image-element";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
describe('ImageDrawingWidget', () => {
|
describe('ImageDrawingWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
let widget: ImageDrawingWidget;
|
let widget: ImageDrawingWidget;
|
||||||
let drawing: Drawing;
|
let drawing: MapDrawing;
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
drawing = new Drawing();
|
drawing = new MapDrawing();
|
||||||
widget = new ImageDrawingWidget();
|
widget = new ImageDrawingWidget();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -27,7 +27,7 @@ describe('ImageDrawingWidget', () => {
|
|||||||
image.data = "data:image/svg+xml;base64,DATA";
|
image.data = "data:image/svg+xml;base64,DATA";
|
||||||
drawing.element = image;
|
drawing.element = image;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { ImageElement } from "../../models/drawings/image-element";
|
import { ImageElement } from "../../models/drawings/image-element";
|
||||||
import { DrawingWidget } from "./drawing-widget";
|
import { DrawingShapeWidget } from "./drawing-shape-widget";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ImageDrawingWidget implements DrawingWidget {
|
export class ImageDrawingWidget implements DrawingShapeWidget {
|
||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
const drawing = view
|
const drawing = view
|
||||||
.selectAll<SVGImageElement, ImageElement>('image.image_element')
|
.selectAll<SVGImageElement, ImageElement>('image.image_element')
|
||||||
.data((d: Drawing) => {
|
.data((d: MapDrawing) => {
|
||||||
return (d.element && d.element instanceof ImageElement) ? [d.element] : [];
|
return (d.element && d.element instanceof ImageElement) ? [d.element] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { TestSVGCanvas } from "../../testing";
|
import { TestSVGCanvas } from "../../testing";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { LineDrawingWidget } from "./line-drawing";
|
import { LineDrawingWidget } from "./line-drawing";
|
||||||
import { LineElement } from "../../models/drawings/line-element";
|
import { LineElement } from "../../models/drawings/line-element";
|
||||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
describe('LineDrawingWidget', () => {
|
describe('LineDrawingWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
let widget: LineDrawingWidget;
|
let widget: LineDrawingWidget;
|
||||||
let drawing: Drawing;
|
let drawing: MapDrawing;
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
drawing = new Drawing();
|
drawing = new MapDrawing();
|
||||||
widget = new LineDrawingWidget(new QtDasharrayFixer());
|
widget = new LineDrawingWidget(new QtDasharrayFixer());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ describe('LineDrawingWidget', () => {
|
|||||||
line.y2 = 40;
|
line.y2 = 40;
|
||||||
drawing.element = line;
|
drawing.element = line;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { LineElement } from "../../models/drawings/line-element";
|
import { LineElement } from "../../models/drawings/line-element";
|
||||||
import { DrawingWidget } from "./drawing-widget";
|
import { DrawingShapeWidget } from "./drawing-shape-widget";
|
||||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LineDrawingWidget implements DrawingWidget {
|
export class LineDrawingWidget implements DrawingShapeWidget {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private qtDasharrayFixer: QtDasharrayFixer
|
private qtDasharrayFixer: QtDasharrayFixer
|
||||||
@ -17,7 +17,7 @@ export class LineDrawingWidget implements DrawingWidget {
|
|||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
const drawing = view
|
const drawing = view
|
||||||
.selectAll<SVGLineElement, LineElement>('line.line_element')
|
.selectAll<SVGLineElement, LineElement>('line.line_element')
|
||||||
.data((d: Drawing) => {
|
.data((d: MapDrawing) => {
|
||||||
return (d.element && d.element instanceof LineElement) ? [d.element] : [];
|
return (d.element && d.element instanceof LineElement) ? [d.element] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import { TestSVGCanvas } from "../../testing";
|
import { TestSVGCanvas } from "../../testing";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { RectDrawingWidget } from "./rect-drawing";
|
import { RectDrawingWidget } from "./rect-drawing";
|
||||||
import { RectElement } from "../../models/drawings/rect-element";
|
import { RectElement } from "../../models/drawings/rect-element";
|
||||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
describe('RectDrawingWidget', () => {
|
describe('RectDrawingWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
let widget: RectDrawingWidget;
|
let widget: RectDrawingWidget;
|
||||||
let drawing: Drawing;
|
let drawing: MapDrawing;
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
drawing = new Drawing();
|
drawing = new MapDrawing();
|
||||||
widget = new RectDrawingWidget(new QtDasharrayFixer());
|
widget = new RectDrawingWidget(new QtDasharrayFixer());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ describe('RectDrawingWidget', () => {
|
|||||||
rect.height = 200;
|
rect.height = 200;
|
||||||
drawing.element = rect;
|
drawing.element = rect;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { RectElement } from "../../models/drawings/rect-element";
|
import { RectElement } from "../../models/drawings/rect-element";
|
||||||
import { DrawingWidget } from "./drawing-widget";
|
import { DrawingShapeWidget } from "./drawing-shape-widget";
|
||||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class RectDrawingWidget implements DrawingWidget {
|
export class RectDrawingWidget implements DrawingShapeWidget {
|
||||||
constructor(
|
constructor(
|
||||||
private qtDasharrayFixer: QtDasharrayFixer
|
private qtDasharrayFixer: QtDasharrayFixer
|
||||||
) {}
|
) {}
|
||||||
@ -16,7 +16,7 @@ export class RectDrawingWidget implements DrawingWidget {
|
|||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
const drawing = view
|
const drawing = view
|
||||||
.selectAll<SVGRectElement, RectElement>('rect.rect_element')
|
.selectAll<SVGRectElement, RectElement>('rect.rect_element')
|
||||||
.data((d: Drawing) => {
|
.data((d: MapDrawing) => {
|
||||||
return (d.element && d.element instanceof RectElement) ? [d.element] : [];
|
return (d.element && d.element instanceof RectElement) ? [d.element] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import { TestSVGCanvas } from "../../testing";
|
import { TestSVGCanvas } from "../../testing";
|
||||||
import { TextDrawingWidget } from "./text-drawing";
|
import { TextDrawingWidget } from "./text-drawing";
|
||||||
import { Drawing } from "../../models/drawing";
|
|
||||||
import { TextElement } from "../../models/drawings/text-element";
|
import { TextElement } from "../../models/drawings/text-element";
|
||||||
import { FontFixer } from "../../helpers/font-fixer";
|
import { FontFixer } from "../../helpers/font-fixer";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
describe('TextDrawingWidget', () => {
|
describe('TextDrawingWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
let widget: TextDrawingWidget;
|
let widget: TextDrawingWidget;
|
||||||
let drawing: Drawing;
|
let drawing: MapDrawing;
|
||||||
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
drawing = new Drawing();
|
drawing = new MapDrawing();
|
||||||
widget = new TextDrawingWidget(new FontFixer());
|
widget = new TextDrawingWidget(new FontFixer());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ describe('TextDrawingWidget', () => {
|
|||||||
|
|
||||||
drawing.element = text;
|
drawing.element = text;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ describe('TextDrawingWidget', () => {
|
|||||||
text.text = 'THIS' + "\n" + 'IS TEXT';
|
text.text = 'THIS' + "\n" + 'IS TEXT';
|
||||||
drawing.element = text;
|
drawing.element = text;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ describe('TextDrawingWidget', () => {
|
|||||||
text.text = ' Text with whitespaces';
|
text.text = ' Text with whitespaces';
|
||||||
drawing.element = text;
|
drawing.element = text;
|
||||||
|
|
||||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
const drawings = svg.canvas.selectAll<SVGGElement, MapDrawing>('g.drawing').data([drawing]);
|
||||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||||
const drawings_merge = drawings.merge(drawings_enter);
|
const drawings_merge = drawings.merge(drawings_enter);
|
||||||
|
|
||||||
|
@ -2,14 +2,14 @@ import { Injectable } from "@angular/core";
|
|||||||
|
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { TextElement } from "../../models/drawings/text-element";
|
import { TextElement } from "../../models/drawings/text-element";
|
||||||
import { Drawing } from "../../models/drawing";
|
import { DrawingShapeWidget } from "./drawing-shape-widget";
|
||||||
import { DrawingWidget } from "./drawing-widget";
|
|
||||||
import { FontFixer } from "../../helpers/font-fixer";
|
import { FontFixer } from "../../helpers/font-fixer";
|
||||||
import { select } from "d3-selection";
|
import { select } from "d3-selection";
|
||||||
|
import { MapDrawing } from "../../models/map/map-drawing";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TextDrawingWidget implements DrawingWidget {
|
export class TextDrawingWidget implements DrawingShapeWidget {
|
||||||
static MARGIN = 4;
|
static MARGIN = 4;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -20,7 +20,7 @@ export class TextDrawingWidget implements DrawingWidget {
|
|||||||
|
|
||||||
const drawing = view
|
const drawing = view
|
||||||
.selectAll<SVGTextElement, TextElement>('text.text_element')
|
.selectAll<SVGTextElement, TextElement>('text.text_element')
|
||||||
.data((d: Drawing) => {
|
.data((d: MapDrawing) => {
|
||||||
return (d.element && d.element instanceof TextElement) ? [d.element] : [];
|
return (d.element && d.element instanceof TextElement) ? [d.element] : [];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,12 +1,7 @@
|
|||||||
import { Context } from "../models/context";
|
import { Context } from "../models/context";
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { NodesWidget } from "./nodes";
|
import { NodesWidget } from "./nodes";
|
||||||
import { Widget } from "./widget";
|
import { Widget } from "./widget";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { LinksWidget } from "./links";
|
|
||||||
import { Drawing } from "../models/drawing";
|
|
||||||
import { DrawingsWidget } from "./drawings";
|
|
||||||
import { DrawingLineWidget } from "./drawing-line";
|
import { DrawingLineWidget } from "./drawing-line";
|
||||||
import { SelectionTool } from "../tools/selection-tool";
|
import { SelectionTool } from "../tools/selection-tool";
|
||||||
import { MovingTool } from "../tools/moving-tool";
|
import { MovingTool } from "../tools/moving-tool";
|
||||||
@ -17,33 +12,16 @@ import { Injectable } from "@angular/core";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GraphLayout implements Widget {
|
export class GraphLayout implements Widget {
|
||||||
private nodes: Node[] = [];
|
|
||||||
private links: Link[] = [];
|
|
||||||
private drawings: Drawing[] = [];
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private linksWidget: LinksWidget,
|
|
||||||
private nodesWidget: NodesWidget,
|
private nodesWidget: NodesWidget,
|
||||||
private drawingsWidget: DrawingsWidget,
|
|
||||||
private drawingLineTool: DrawingLineWidget,
|
private drawingLineTool: DrawingLineWidget,
|
||||||
private selectionTool: SelectionTool,
|
private selectionTool: SelectionTool,
|
||||||
private movingTool: MovingTool,
|
private movingTool: MovingTool,
|
||||||
private layersWidget: LayersWidget
|
private layersWidget: LayersWidget,
|
||||||
|
private layersManager: LayersManager
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public setNodes(nodes: Node[]) {
|
|
||||||
this.nodes = nodes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setLinks(links: Link[]) {
|
|
||||||
this.links = links;
|
|
||||||
}
|
|
||||||
|
|
||||||
public setDrawings(drawings: Drawing[]) {
|
|
||||||
this.drawings = drawings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getNodesWidget() {
|
public getNodesWidget() {
|
||||||
return this.nodesWidget;
|
return this.nodesWidget;
|
||||||
}
|
}
|
||||||
@ -84,13 +62,7 @@ export class GraphLayout implements Widget {
|
|||||||
return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`;
|
return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`;
|
||||||
});
|
});
|
||||||
|
|
||||||
// @fix me
|
this.layersWidget.draw(canvas, this.layersManager.getLayersList());
|
||||||
const layersManager = new LayersManager();
|
|
||||||
layersManager.setNodes(this.nodes);
|
|
||||||
layersManager.setDrawings(this.drawings);
|
|
||||||
layersManager.setLinks(this.links);
|
|
||||||
|
|
||||||
this.layersWidget.draw(canvas, layersManager.getLayersList());
|
|
||||||
|
|
||||||
this.drawingLineTool.draw(view, context);
|
this.drawingLineTool.draw(view, context);
|
||||||
this.selectionTool.draw(view, context);
|
this.selectionTool.draw(view, context);
|
||||||
|
@ -1,61 +1,61 @@
|
|||||||
import { Selection } from "d3-selection";
|
import { Selection } from "d3-selection";
|
||||||
|
|
||||||
import { TestSVGCanvas } from "../testing";
|
import { TestSVGCanvas } from "../testing";
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { LinkNode } from "../../models/link-node";
|
|
||||||
import { Label } from "../models/label";
|
|
||||||
import { InterfaceLabel } from "../models/interface-label";
|
import { InterfaceLabel } from "../models/interface-label";
|
||||||
import { InterfaceLabelWidget } from "./interface-label";
|
import { InterfaceLabelWidget } from "./interface-label";
|
||||||
import { CssFixer } from "../helpers/css-fixer";
|
import { CssFixer } from "../helpers/css-fixer";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
import { MapLinkNode } from "../models/map/map-link-node";
|
||||||
|
import { MapLabel } from "../models/map/map-label";
|
||||||
|
|
||||||
|
|
||||||
describe('InterfaceLabelsWidget', () => {
|
describe('InterfaceLabelsWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
let widget: InterfaceLabelWidget;
|
let widget: InterfaceLabelWidget;
|
||||||
let linksEnter: Selection<SVGGElement, Link, SVGGElement, any>;
|
let linksEnter: Selection<SVGGElement, MapLink, SVGGElement, any>;
|
||||||
let links: Link[];
|
let links: MapLink[];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
|
|
||||||
const node_1 = new Node();
|
const node_1 = new MapNode();
|
||||||
node_1.node_id = "1";
|
node_1.id = "1";
|
||||||
node_1.x = 100;
|
node_1.x = 100;
|
||||||
node_1.y = 200;
|
node_1.y = 200;
|
||||||
|
|
||||||
const node_2 = new Node();
|
const node_2 = new MapNode();
|
||||||
node_2.node_id = "2";
|
node_2.id = "2";
|
||||||
node_2.x = 300;
|
node_2.x = 300;
|
||||||
node_2.y = 400;
|
node_2.y = 400;
|
||||||
|
|
||||||
const link_node_1 = new LinkNode();
|
const link_node_1 = new MapLinkNode();
|
||||||
link_node_1.label = new Label();
|
link_node_1.label = new MapLabel();
|
||||||
link_node_1.label.rotation = 5;
|
link_node_1.label.rotation = 5;
|
||||||
link_node_1.label.text = "Interface 1";
|
link_node_1.label.text = "Interface 1";
|
||||||
link_node_1.label.x = 10;
|
link_node_1.label.x = 10;
|
||||||
link_node_1.label.y = 20;
|
link_node_1.label.y = 20;
|
||||||
link_node_1.label.style = "font-size: 12px";
|
link_node_1.label.style = "font-size: 12px";
|
||||||
|
|
||||||
const link_node_2 = new LinkNode();
|
const link_node_2 = new MapLinkNode();
|
||||||
link_node_2.label = new Label();
|
link_node_2.label = new MapLabel();
|
||||||
link_node_2.label.rotation = 0;
|
link_node_2.label.rotation = 0;
|
||||||
link_node_2.label.text = "Interface 2";
|
link_node_2.label.text = "Interface 2";
|
||||||
link_node_2.label.x = -30;
|
link_node_2.label.x = -30;
|
||||||
link_node_2.label.y = -40;
|
link_node_2.label.y = -40;
|
||||||
link_node_2.label.style = "";
|
link_node_2.label.style = "";
|
||||||
|
|
||||||
const link_1 = new Link();
|
const link_1 = new MapLink();
|
||||||
link_1.link_id = "link1";
|
link_1.id = "link1";
|
||||||
link_1.source = node_1;
|
link_1.source = node_1;
|
||||||
link_1.target = node_2;
|
link_1.target = node_2;
|
||||||
link_1.nodes = [link_node_1, link_node_2];
|
link_1.nodes = [link_node_1, link_node_2];
|
||||||
link_1.link_type = "ethernet";
|
link_1.linkType = "ethernet";
|
||||||
|
|
||||||
links = [link_1];
|
links = [link_1];
|
||||||
|
|
||||||
const linksSelection = svg.canvas
|
const linksSelection = svg.canvas
|
||||||
.selectAll<SVGGElement, Link>('g.link')
|
.selectAll<SVGGElement, MapLink>('g.link')
|
||||||
.data(links);
|
.data(links);
|
||||||
|
|
||||||
linksEnter = linksSelection
|
linksEnter = linksSelection
|
||||||
@ -110,14 +110,14 @@ describe('InterfaceLabelsWidget', () => {
|
|||||||
expect(drew.nodes().length).toEqual(0);
|
expect(drew.nodes().length).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should draw interface label with class `selected` when selected', () => {
|
// it('should draw interface label with class `selected` when selected', () => {
|
||||||
links[0].nodes[0].label.is_selected = true;
|
// links[0].nodes[0].label.isSelected = true;
|
||||||
|
|
||||||
widget.draw(linksEnter);
|
// widget.draw(linksEnter);
|
||||||
|
|
||||||
const drew = svg.canvas.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container');
|
// const drew = svg.canvas.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container');
|
||||||
const sourceInterface = drew.nodes()[0];
|
// const sourceInterface = drew.nodes()[0];
|
||||||
expect(sourceInterface.getAttribute('class')).toContain('selected');
|
// expect(sourceInterface.getAttribute('class')).toContain('selected');
|
||||||
});
|
// });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { Injectable } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { InterfaceLabel } from "../models/interface-label";
|
import { InterfaceLabel } from "../models/interface-label";
|
||||||
import { CssFixer } from "../helpers/css-fixer";
|
import { CssFixer } from "../helpers/css-fixer";
|
||||||
import { select } from "d3-selection";
|
import { select } from "d3-selection";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InterfaceLabelWidget {
|
export class InterfaceLabelWidget {
|
||||||
@ -25,27 +25,25 @@ export class InterfaceLabelWidget {
|
|||||||
|
|
||||||
const labels = selection
|
const labels = selection
|
||||||
.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container')
|
.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container')
|
||||||
.data((l: Link) => {
|
.data((l: MapLink) => {
|
||||||
const sourceInterface = new InterfaceLabel(
|
const sourceInterface = new InterfaceLabel(
|
||||||
l.link_id,
|
l.id,
|
||||||
'source',
|
'source',
|
||||||
Math.round(l.source.x + l.nodes[0].label.x),
|
Math.round(l.source.x + l.nodes[0].label.x),
|
||||||
Math.round(l.source.y + l.nodes[0].label.y),
|
Math.round(l.source.y + l.nodes[0].label.y),
|
||||||
l.nodes[0].label.text,
|
l.nodes[0].label.text,
|
||||||
l.nodes[0].label.style,
|
l.nodes[0].label.style,
|
||||||
l.nodes[0].label.rotation,
|
l.nodes[0].label.rotation
|
||||||
l.nodes[0].label.is_selected
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetInterface = new InterfaceLabel(
|
const targetInterface = new InterfaceLabel(
|
||||||
l.link_id,
|
l.id,
|
||||||
'target',
|
'target',
|
||||||
Math.round( l.target.x + l.nodes[1].label.x),
|
Math.round( l.target.x + l.nodes[1].label.x),
|
||||||
Math.round( l.target.y + l.nodes[1].label.y),
|
Math.round( l.target.y + l.nodes[1].label.y),
|
||||||
l.nodes[1].label.text,
|
l.nodes[1].label.text,
|
||||||
l.nodes[1].label.style,
|
l.nodes[1].label.style,
|
||||||
l.nodes[1].label.rotation,
|
l.nodes[1].label.rotation
|
||||||
l.nodes[1].label.is_selected
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (this.enabled) {
|
if (this.enabled) {
|
||||||
@ -81,7 +79,7 @@ export class InterfaceLabelWidget {
|
|||||||
const y = l.y + bbox.height;
|
const y = l.y + bbox.height;
|
||||||
return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`;
|
return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`;
|
||||||
})
|
})
|
||||||
.classed('selected', (l: InterfaceLabel) => l.is_selected);
|
.classed('selected', (l: InterfaceLabel) => false);
|
||||||
|
|
||||||
// update label
|
// update label
|
||||||
merge
|
merge
|
||||||
@ -95,7 +93,7 @@ export class InterfaceLabelWidget {
|
|||||||
// update surrounding rect
|
// update surrounding rect
|
||||||
merge
|
merge
|
||||||
.select<SVGRectElement>('rect.interface_label_border')
|
.select<SVGRectElement>('rect.interface_label_border')
|
||||||
.attr('visibility', (l: InterfaceLabel) => l.is_selected ? 'visible' : 'hidden')
|
.attr('visibility', (l: InterfaceLabel) => false ? 'visible' : 'hidden')
|
||||||
.attr('stroke-dasharray', '3,3')
|
.attr('stroke-dasharray', '3,3')
|
||||||
.attr('stroke-width', '0.5')
|
.attr('stroke-width', '0.5')
|
||||||
.each(function (this: SVGRectElement, l: InterfaceLabel) {
|
.each(function (this: SVGRectElement, l: InterfaceLabel) {
|
||||||
@ -106,7 +104,7 @@ export class InterfaceLabelWidget {
|
|||||||
|
|
||||||
const border = InterfaceLabelWidget.SURROUNDING_TEXT_BORDER;
|
const border = InterfaceLabelWidget.SURROUNDING_TEXT_BORDER;
|
||||||
|
|
||||||
current.attr('width', bbox.width + border*2);
|
current.attr('width', bbox.width + border * 2);
|
||||||
current.attr('height', bbox.height + border);
|
current.attr('height', bbox.height + border);
|
||||||
current.attr('x', - border);
|
current.attr('x', - border);
|
||||||
current.attr('y', - bbox.height);
|
current.attr('y', - bbox.height);
|
||||||
|
@ -4,8 +4,8 @@ import { select } from "d3-selection";
|
|||||||
|
|
||||||
import { Widget } from "./widget";
|
import { Widget } from "./widget";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { LinkStatus } from "../models/link-status";
|
import { LinkStatus } from "../models/link-status";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -13,8 +13,8 @@ export class InterfaceStatusWidget implements Widget {
|
|||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
view.each(function (this: SVGGElement, l: Link) {
|
view.each(function (this: SVGGElement, l: MapLink) {
|
||||||
const link_group = select<SVGGElement, Link>(this);
|
const link_group = select<SVGGElement, MapLink>(this);
|
||||||
const link_path = link_group.select<SVGPathElement>('path');
|
const link_path = link_group.select<SVGPathElement>('path');
|
||||||
|
|
||||||
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
|
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
|
||||||
|
@ -2,12 +2,13 @@ import { Injectable } from "@angular/core";
|
|||||||
|
|
||||||
import { Widget } from "./widget";
|
import { Widget } from "./widget";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { SerialLinkWidget } from "./links/serial-link";
|
import { SerialLinkWidget } from "./links/serial-link";
|
||||||
import { EthernetLinkWidget } from "./links/ethernet-link";
|
import { EthernetLinkWidget } from "./links/ethernet-link";
|
||||||
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
|
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
|
||||||
import { InterfaceLabelWidget } from "./interface-label";
|
import { InterfaceLabelWidget } from "./interface-label";
|
||||||
import { InterfaceStatusWidget } from "./interface-status";
|
import { InterfaceStatusWidget } from "./interface-status";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -16,19 +17,12 @@ export class LinkWidget implements Widget {
|
|||||||
constructor(
|
constructor(
|
||||||
private multiLinkCalculatorHelper: MultiLinkCalculatorHelper,
|
private multiLinkCalculatorHelper: MultiLinkCalculatorHelper,
|
||||||
private interfaceLabelWidget: InterfaceLabelWidget,
|
private interfaceLabelWidget: InterfaceLabelWidget,
|
||||||
private interfaceStatusWidget: InterfaceStatusWidget
|
private interfaceStatusWidget: InterfaceStatusWidget,
|
||||||
|
private selectionManager: SelectionManager
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
public getInterfaceLabelWidget() {
|
|
||||||
return this.interfaceLabelWidget;
|
|
||||||
}
|
|
||||||
|
|
||||||
public getInterfaceStatusWidget() {
|
|
||||||
return this.interfaceStatusWidget;
|
|
||||||
}
|
|
||||||
|
|
||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
const link_body = view.selectAll<SVGGElement, Link>("g.link_body")
|
const link_body = view.selectAll<SVGGElement, MapLink>("g.link_body")
|
||||||
.data((l) => [l]);
|
.data((l) => [l]);
|
||||||
|
|
||||||
const link_body_enter = link_body.enter()
|
const link_body_enter = link_body.enter()
|
||||||
@ -49,10 +43,10 @@ export class LinkWidget implements Widget {
|
|||||||
|
|
||||||
link_body_merge
|
link_body_merge
|
||||||
.select<SVGPathElement>('path')
|
.select<SVGPathElement>('path')
|
||||||
.classed('selected', (l: Link) => l.is_selected);
|
.classed('selected', (l: MapLink) => this.selectionManager.isSelected(l));
|
||||||
|
|
||||||
this.getInterfaceLabelWidget().draw(link_body_merge);
|
this.interfaceLabelWidget.draw(link_body_merge);
|
||||||
this.getInterfaceStatusWidget().draw(link_body_merge);
|
this.interfaceStatusWidget.draw(link_body_merge);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,10 +5,10 @@ import { Selection } from "d3-selection";
|
|||||||
import { TestSVGCanvas } from "../testing";
|
import { TestSVGCanvas } from "../testing";
|
||||||
import { Layer } from "../models/layer";
|
import { Layer } from "../models/layer";
|
||||||
import { LinksWidget } from "./links";
|
import { LinksWidget } from "./links";
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { LinkWidget } from "./link";
|
import { LinkWidget } from "./link";
|
||||||
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
|
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
describe('LinksWidget', () => {
|
describe('LinksWidget', () => {
|
||||||
@ -16,28 +16,28 @@ describe('LinksWidget', () => {
|
|||||||
let widget: LinksWidget;
|
let widget: LinksWidget;
|
||||||
let layersEnter: Selection<SVGGElement, Layer, SVGGElement, any>;
|
let layersEnter: Selection<SVGGElement, Layer, SVGGElement, any>;
|
||||||
let layer: Layer;
|
let layer: Layer;
|
||||||
let mockedLinkWidget: LinkWidget;
|
let linkWidget: LinkWidget;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
mockedLinkWidget = instance(mock(LinkWidget));
|
linkWidget = instance(mock(LinkWidget));
|
||||||
widget = new LinksWidget(new MultiLinkCalculatorHelper(), mockedLinkWidget);
|
widget = new LinksWidget(new MultiLinkCalculatorHelper(), linkWidget);
|
||||||
|
|
||||||
const node_1 = new Node();
|
const node_1 = new MapNode();
|
||||||
node_1.node_id = "1";
|
node_1.id = "1";
|
||||||
node_1.x = 10;
|
node_1.x = 10;
|
||||||
node_1.y = 10;
|
node_1.y = 10;
|
||||||
|
|
||||||
const node_2 = new Node();
|
const node_2 = new MapNode();
|
||||||
node_2.node_id = "2";
|
node_2.id = "2";
|
||||||
node_2.x = 100;
|
node_2.x = 100;
|
||||||
node_2.y = 100;
|
node_2.y = 100;
|
||||||
|
|
||||||
const link_1 = new Link();
|
const link_1 = new MapLink();
|
||||||
link_1.link_id = "link1";
|
link_1.id = "link1";
|
||||||
link_1.source = node_1;
|
link_1.source = node_1;
|
||||||
link_1.target = node_2;
|
link_1.target = node_2;
|
||||||
link_1.link_type = "ethernet";
|
link_1.linkType = "ethernet";
|
||||||
|
|
||||||
layer = new Layer();
|
layer = new Layer();
|
||||||
layer.index = 1;
|
layer.index = 1;
|
||||||
@ -65,13 +65,9 @@ describe('LinksWidget', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should draw links', () => {
|
it('should draw links', () => {
|
||||||
const linkWidgetMock = mock(LinkWidget);
|
|
||||||
const linkWidget = instance(linkWidgetMock);
|
|
||||||
spyOn(widget, 'getLinkWidget').and.returnValue(linkWidget);
|
|
||||||
|
|
||||||
widget.draw(layersEnter);
|
widget.draw(layersEnter);
|
||||||
|
|
||||||
const drew = svg.canvas.selectAll<SVGGElement, Link>('g.link');
|
const drew = svg.canvas.selectAll<SVGGElement, MapLink>('g.link');
|
||||||
const linkNode = drew.nodes()[0];
|
const linkNode = drew.nodes()[0];
|
||||||
expect(linkNode.getAttribute('link_id')).toEqual('link1');
|
expect(linkNode.getAttribute('link_id')).toEqual('link1');
|
||||||
expect(linkNode.getAttribute('map-source')).toEqual('1');
|
expect(linkNode.getAttribute('map-source')).toEqual('1');
|
||||||
|
@ -2,10 +2,10 @@ import { Injectable } from "@angular/core";
|
|||||||
|
|
||||||
import { Widget } from "./widget";
|
import { Widget } from "./widget";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Link } from "../../models/link";
|
|
||||||
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
|
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
|
||||||
import { Layer } from "../models/layer";
|
import { Layer } from "../models/layer";
|
||||||
import { LinkWidget } from "./link";
|
import { LinkWidget } from "./link";
|
||||||
|
import { MapLink } from "../models/map/map-link";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class LinksWidget implements Widget {
|
export class LinksWidget implements Widget {
|
||||||
@ -15,47 +15,43 @@ export class LinksWidget implements Widget {
|
|||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public getLinkWidget() {
|
public redrawLink(view: SVGSelection, link: MapLink) {
|
||||||
return this.linkWidget;
|
this.linkWidget.draw(this.selectLink(view, link));
|
||||||
}
|
|
||||||
|
|
||||||
public redrawLink(view: SVGSelection, link: Link) {
|
|
||||||
this.getLinkWidget().draw(this.selectLink(view, link));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(view: SVGSelection) {
|
public draw(view: SVGSelection) {
|
||||||
const link = view
|
const link = view
|
||||||
.selectAll<SVGGElement, Link>("g.link")
|
.selectAll<SVGGElement, MapLink>("g.link")
|
||||||
.data((layer: Layer) => {
|
.data((layer: Layer) => {
|
||||||
if (layer.links) {
|
if (layer.links) {
|
||||||
const layer_links = layer.links.filter((l: Link) => {
|
const layer_links = layer.links.filter((l: MapLink) => {
|
||||||
return l.target && l.source;
|
return l.target && l.source;
|
||||||
});
|
});
|
||||||
this.multiLinkCalculatorHelper.assignDataToLinks(layer_links);
|
this.multiLinkCalculatorHelper.assignDataToLinks(layer_links);
|
||||||
return layer_links;
|
return layer_links;
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
}, (l: Link) => {
|
}, (l: MapLink) => {
|
||||||
return l.link_id;
|
return l.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
const link_enter = link.enter()
|
const link_enter = link.enter()
|
||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'link')
|
.attr('class', 'link')
|
||||||
.attr('link_id', (l: Link) => l.link_id)
|
.attr('link_id', (l: MapLink) => l.id)
|
||||||
.attr('map-source', (l: Link) => l.source.node_id)
|
.attr('map-source', (l: MapLink) => l.source.id)
|
||||||
.attr('map-target', (l: Link) => l.target.node_id);
|
.attr('map-target', (l: MapLink) => l.target.id);
|
||||||
|
|
||||||
const merge = link.merge(link_enter);
|
const merge = link.merge(link_enter);
|
||||||
|
|
||||||
this.getLinkWidget().draw(merge);
|
this.linkWidget.draw(merge);
|
||||||
|
|
||||||
link
|
link
|
||||||
.exit()
|
.exit()
|
||||||
.remove();
|
.remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectLink(view: SVGSelection, link: Link) {
|
private selectLink(view: SVGSelection, link: MapLink) {
|
||||||
return view.selectAll<SVGGElement, Link>(`g.link[link_id="${link.link_id}"]`);
|
return view.selectAll<SVGGElement, MapLink>(`g.link[link_id="${link.id}"]`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import { path } from "d3-path";
|
|||||||
|
|
||||||
import { Widget } from "../widget";
|
import { Widget } from "../widget";
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { Link } from "../../../models/link";
|
import { MapLink } from "../../models/map/map-link";
|
||||||
|
|
||||||
class EthernetLinkPath {
|
class EthernetLinkPath {
|
||||||
constructor(
|
constructor(
|
||||||
@ -13,7 +13,7 @@ class EthernetLinkPath {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class EthernetLinkWidget implements Widget {
|
export class EthernetLinkWidget implements Widget {
|
||||||
private linktoEthernetLink(link: Link) {
|
private linktoEthernetLink(link: MapLink) {
|
||||||
return new EthernetLinkPath(
|
return new EthernetLinkPath(
|
||||||
[link.source.x + link.source.width / 2., link.source.y + link.source.height / 2.],
|
[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.]
|
[link.target.x + link.target.width / 2., link.target.y + link.target.height / 2.]
|
||||||
@ -24,9 +24,9 @@ export class EthernetLinkWidget implements Widget {
|
|||||||
|
|
||||||
const link = view
|
const link = view
|
||||||
.selectAll<SVGPathElement, EthernetLinkPath>('path.ethernet_link')
|
.selectAll<SVGPathElement, EthernetLinkPath>('path.ethernet_link')
|
||||||
.data((link) => {
|
.data((l) => {
|
||||||
if(link.link_type === 'ethernet') {
|
if (l.linkType === 'ethernet') {
|
||||||
return [this.linktoEthernetLink(link)];
|
return [this.linktoEthernetLink(l)];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
@ -2,7 +2,7 @@ import { path } from "d3-path";
|
|||||||
|
|
||||||
import { Widget } from "../widget";
|
import { Widget } from "../widget";
|
||||||
import { SVGSelection } from "../../models/types";
|
import { SVGSelection } from "../../models/types";
|
||||||
import { Link } from "../../../models/link";
|
import { MapLink } from "../../models/map/map-link";
|
||||||
|
|
||||||
|
|
||||||
class SerialLinkPath {
|
class SerialLinkPath {
|
||||||
@ -18,7 +18,7 @@ class SerialLinkPath {
|
|||||||
|
|
||||||
export class SerialLinkWidget implements Widget {
|
export class SerialLinkWidget implements Widget {
|
||||||
|
|
||||||
private linkToSerialLink(link: Link) {
|
private linkToSerialLink(link: MapLink) {
|
||||||
const source = {
|
const source = {
|
||||||
'x': link.source.x + link.source.width / 2,
|
'x': link.source.x + link.source.width / 2,
|
||||||
'y': link.source.y + link.source.height / 2
|
'y': link.source.y + link.source.height / 2
|
||||||
@ -60,9 +60,9 @@ export class SerialLinkWidget implements Widget {
|
|||||||
|
|
||||||
const link = view
|
const link = view
|
||||||
.selectAll<SVGPathElement, SerialLinkPath>('path.serial_link')
|
.selectAll<SVGPathElement, SerialLinkPath>('path.serial_link')
|
||||||
.data((link) => {
|
.data((l) => {
|
||||||
if(link.link_type === 'serial') {
|
if (l.linkType === 'serial') {
|
||||||
return [this.linkToSerialLink(link)];
|
return [this.linkToSerialLink(l)];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
});
|
});
|
||||||
|
81
src/app/cartography/widgets/node.spec.ts
Normal file
81
src/app/cartography/widgets/node.spec.ts
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
|
||||||
|
import { TestSVGCanvas } from "../testing";
|
||||||
|
import { CssFixer } from "../helpers/css-fixer";
|
||||||
|
import { FontFixer } from "../helpers/font-fixer";
|
||||||
|
import { NodeWidget } from "./node";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { MapLabel } from "../models/map/map-label";
|
||||||
|
import { MockedGraphDataManager } from "../managers/graph-data-manager.spec";
|
||||||
|
import { GraphDataManager } from "../managers/graph-data-manager";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
|
||||||
|
describe('NodesWidget', () => {
|
||||||
|
let svg: TestSVGCanvas;
|
||||||
|
let widget: NodeWidget;
|
||||||
|
let graphData: MockedGraphDataManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
svg = new TestSVGCanvas();
|
||||||
|
graphData = new MockedGraphDataManager();
|
||||||
|
|
||||||
|
// widget = new NodeWidget(
|
||||||
|
// new CssFixer(),
|
||||||
|
// new FontFixer(),
|
||||||
|
// <GraphDataManager> <unknown> graphData,
|
||||||
|
// new SelectionManager()
|
||||||
|
// );
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
svg.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('draggable behaviour', () => {
|
||||||
|
let node: MapNode;
|
||||||
|
const tryToDrag = () => {
|
||||||
|
const drew = svg.canvas.selectAll<SVGGElement, MapNode>('g.node');
|
||||||
|
const drewNode = drew.nodes()[0];
|
||||||
|
|
||||||
|
drewNode.dispatchEvent(
|
||||||
|
new MouseEvent('mousedown', {
|
||||||
|
clientX: 150, clientY: 250, relatedTarget: drewNode,
|
||||||
|
screenY: 1024, screenX: 1024, view: window
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300}));
|
||||||
|
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300, view: window}));
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
node = new MapNode();
|
||||||
|
node.x = 100;
|
||||||
|
node.y = 200;
|
||||||
|
node.width = 100;
|
||||||
|
node.height = 100;
|
||||||
|
node.label = new MapLabel();
|
||||||
|
});
|
||||||
|
|
||||||
|
// it('should be draggable when enabled', () => {
|
||||||
|
// widget.setDraggingEnabled(true);
|
||||||
|
// widget.draw(svg.canvas);
|
||||||
|
|
||||||
|
// tryToDrag();
|
||||||
|
|
||||||
|
// expect(node.x).toEqual(250);
|
||||||
|
// expect(node.y).toEqual(250);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should be not draggable when disabled', () => {
|
||||||
|
// widget.setDraggingEnabled(false);
|
||||||
|
// widget.draw(svg.canvas);
|
||||||
|
|
||||||
|
// tryToDrag();
|
||||||
|
|
||||||
|
// expect(node.x).toEqual(100);
|
||||||
|
// expect(node.y).toEqual(200);
|
||||||
|
// });
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
112
src/app/cartography/widgets/node.ts
Normal file
112
src/app/cartography/widgets/node.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import { Injectable, EventEmitter } from "@angular/core";
|
||||||
|
|
||||||
|
import { Widget } from "./widget";
|
||||||
|
import { SVGSelection } from "../models/types";
|
||||||
|
import { NodeContextMenu, NodeClicked } from "../events/nodes";
|
||||||
|
import { CssFixer } from "../helpers/css-fixer";
|
||||||
|
import { FontFixer } from "../helpers/font-fixer";
|
||||||
|
import { select, event } from "d3-selection";
|
||||||
|
import { MapSymbol } from "../models/map/map-symbol";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
import { GraphDataManager } from "../managers/graph-data-manager";
|
||||||
|
import { SelectionManager } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class NodeWidget implements Widget {
|
||||||
|
static NODE_LABEL_MARGIN = 3;
|
||||||
|
|
||||||
|
public onContextMenu = new EventEmitter<NodeContextMenu>();
|
||||||
|
public onNodeClicked = new EventEmitter<NodeClicked>();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private cssFixer: CssFixer,
|
||||||
|
private fontFixer: FontFixer,
|
||||||
|
private graphDataManager: GraphDataManager,
|
||||||
|
private selectionManager: SelectionManager
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public draw(view: SVGSelection) {
|
||||||
|
const self = this;
|
||||||
|
|
||||||
|
const node_body = view.selectAll<SVGGElement, MapNode>("g.node_body")
|
||||||
|
.data((n) => [n]);
|
||||||
|
|
||||||
|
const node_body_enter = node_body.enter()
|
||||||
|
.append<SVGGElement>('g')
|
||||||
|
.attr("class", "node_body");
|
||||||
|
|
||||||
|
node_body_enter
|
||||||
|
.append<SVGImageElement>('image');
|
||||||
|
|
||||||
|
// add label of node
|
||||||
|
node_body_enter
|
||||||
|
.append<SVGTextElement>('text')
|
||||||
|
.attr('class', 'label');
|
||||||
|
|
||||||
|
const node_body_merge = node_body.merge(node_body_enter)
|
||||||
|
.classed('selected', (n: MapNode) => this.selectionManager.isSelected(n))
|
||||||
|
.on("contextmenu", function (n: MapNode, i: number) {
|
||||||
|
event.preventDefault();
|
||||||
|
self.onContextMenu.emit(new NodeContextMenu(event, n));
|
||||||
|
})
|
||||||
|
.on('click', (n: MapNode) => {
|
||||||
|
this.onNodeClicked.emit(new NodeClicked(event, n));
|
||||||
|
});
|
||||||
|
|
||||||
|
// update image of node
|
||||||
|
node_body_merge
|
||||||
|
.select<SVGImageElement>('image')
|
||||||
|
.attr('xnode:href', (n: MapNode) => {
|
||||||
|
const symbol = this.graphDataManager.getSymbols().find((s: MapSymbol) => s.id === n.symbol);
|
||||||
|
if (symbol) {
|
||||||
|
return 'data:image/svg+xml;base64,' + btoa(symbol.raw);
|
||||||
|
}
|
||||||
|
// @todo; we need to have default image
|
||||||
|
return 'data:image/svg+xml;base64,none';
|
||||||
|
})
|
||||||
|
.attr('width', (n: MapNode) => n.width)
|
||||||
|
.attr('height', (n: MapNode) => n.height)
|
||||||
|
.attr('x', (n: MapNode) => 0)
|
||||||
|
.attr('y', (n: MapNode) => 0)
|
||||||
|
.on('mouseover', function (this, n: MapNode) {
|
||||||
|
select(this).attr("class", "over");
|
||||||
|
})
|
||||||
|
.on('mouseout', function (this, n: MapNode) {
|
||||||
|
select(this).attr("class", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
node_body_merge
|
||||||
|
.attr('transform', (n: MapNode) => {
|
||||||
|
return `translate(${n.x},${n.y})`;
|
||||||
|
});
|
||||||
|
|
||||||
|
node_body_merge
|
||||||
|
.select<SVGTextElement>('text.label')
|
||||||
|
// .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way
|
||||||
|
.attr('style', (n: MapNode) => {
|
||||||
|
let styles = this.cssFixer.fix(n.label.style);
|
||||||
|
styles = this.fontFixer.fixStyles(styles);
|
||||||
|
return styles;
|
||||||
|
})
|
||||||
|
.text((n: MapNode) => n.label.text)
|
||||||
|
.attr('x', function (this: SVGTextElement, n: MapNode) {
|
||||||
|
if (n.label.x === null) {
|
||||||
|
// center
|
||||||
|
const bbox = this.getBBox();
|
||||||
|
return -bbox.width / 2.;
|
||||||
|
}
|
||||||
|
return n.label.x + NodeWidget.NODE_LABEL_MARGIN;
|
||||||
|
})
|
||||||
|
.attr('y', function (this: SVGTextElement, n: MapNode) {
|
||||||
|
let bbox = this.getBBox();
|
||||||
|
|
||||||
|
if (n.label.x === null) {
|
||||||
|
// center
|
||||||
|
bbox = this.getBBox();
|
||||||
|
return - n.height / 2. - bbox.height ;
|
||||||
|
}
|
||||||
|
return n.label.y + bbox.height - NodeWidget.NODE_LABEL_MARGIN;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -1,74 +1,23 @@
|
|||||||
|
|
||||||
import { TestSVGCanvas } from "../testing";
|
import { TestSVGCanvas } from "../testing";
|
||||||
import { NodesWidget } from "./nodes";
|
import { NodesWidget } from "./nodes";
|
||||||
import { Node } from "../models/node";
|
import { NodeWidget } from "./node";
|
||||||
import { Label } from "../models/label";
|
import { instance, mock } from "ts-mockito";
|
||||||
import { CssFixer } from "../helpers/css-fixer";
|
|
||||||
import { FontFixer } from "../helpers/font-fixer";
|
|
||||||
|
|
||||||
|
|
||||||
describe('NodesWidget', () => {
|
describe('NodesWidget', () => {
|
||||||
let svg: TestSVGCanvas;
|
let svg: TestSVGCanvas;
|
||||||
|
let nodeWidget: NodeWidget;
|
||||||
let widget: NodesWidget;
|
let widget: NodesWidget;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg = new TestSVGCanvas();
|
svg = new TestSVGCanvas();
|
||||||
widget = new NodesWidget(
|
nodeWidget = instance(mock(NodeWidget));
|
||||||
new CssFixer(),
|
widget = new NodesWidget(nodeWidget);
|
||||||
new FontFixer()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
svg.destroy();
|
svg.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('draggable behaviour', () => {
|
|
||||||
let node: Node;
|
|
||||||
const tryToDrag = () => {
|
|
||||||
const drew = svg.canvas.selectAll<SVGGElement, Node>('g.node');
|
|
||||||
const drewNode = drew.nodes()[0];
|
|
||||||
|
|
||||||
drewNode.dispatchEvent(
|
|
||||||
new MouseEvent('mousedown', {
|
|
||||||
clientX: 150, clientY: 250, relatedTarget: drewNode,
|
|
||||||
screenY: 1024, screenX: 1024, view: window
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300}));
|
|
||||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300, view: window}));
|
|
||||||
};
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
node = new Node();
|
|
||||||
node.x = 100;
|
|
||||||
node.y = 200;
|
|
||||||
node.width = 100;
|
|
||||||
node.height = 100;
|
|
||||||
node.label = new Label();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be draggable when enabled', () => {
|
|
||||||
widget.setDraggingEnabled(true);
|
|
||||||
widget.draw(svg.canvas, [node]);
|
|
||||||
|
|
||||||
tryToDrag();
|
|
||||||
|
|
||||||
expect(node.x).toEqual(250);
|
|
||||||
expect(node.y).toEqual(250);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be not draggable when disabled', () => {
|
|
||||||
widget.setDraggingEnabled(false);
|
|
||||||
widget.draw(svg.canvas, [node]);
|
|
||||||
|
|
||||||
tryToDrag();
|
|
||||||
|
|
||||||
expect(node.x).toEqual(100);
|
|
||||||
expect(node.y).toEqual(200);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,197 +1,61 @@
|
|||||||
import { Injectable, EventEmitter } from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
import { event, select, Selection } from "d3-selection";
|
|
||||||
import { D3DragEvent, drag } from "d3-drag";
|
|
||||||
|
|
||||||
import { Widget } from "./widget";
|
import { Widget } from "./widget";
|
||||||
import { Node } from "../models/node";
|
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Symbol } from "../../models/symbol";
|
|
||||||
import { Layer } from "../models/layer";
|
import { Layer } from "../models/layer";
|
||||||
import { CssFixer } from "../helpers/css-fixer";
|
import { NodeWidget } from "./node";
|
||||||
import { FontFixer } from "../helpers/font-fixer";
|
import { Draggable } from "../events/draggable";
|
||||||
|
import { MapNode } from "../models/map/map-node";
|
||||||
|
|
||||||
export class NodeEvent {
|
|
||||||
constructor(
|
|
||||||
public event: any,
|
|
||||||
public node: Node
|
|
||||||
) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class NodesWidget implements Widget {
|
export class NodesWidget implements Widget {
|
||||||
static NODE_LABEL_MARGIN = 3;
|
static NODE_LABEL_MARGIN = 3;
|
||||||
|
|
||||||
private debug = false;
|
public draggable = new Draggable<SVGGElement, MapNode>();
|
||||||
private draggingEnabled = false;
|
public draggingEnabled = false;
|
||||||
|
|
||||||
private symbols: Symbol[] = [];
|
|
||||||
|
|
||||||
public onContextMenu = new EventEmitter<NodeEvent>();
|
|
||||||
public onNodeClicked = new EventEmitter<NodeEvent>();
|
|
||||||
public onNodeDragged = new EventEmitter<NodeEvent>();
|
|
||||||
public onNodeDragging = new EventEmitter<NodeEvent>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private cssFixer: CssFixer,
|
private nodeWidget: NodeWidget
|
||||||
private fontFixer: FontFixer
|
|
||||||
) {
|
) {
|
||||||
this.symbols = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public setSymbols(symbols: Symbol[]) {
|
public redrawNode(view: SVGSelection, node: MapNode) {
|
||||||
this.symbols = symbols;
|
this.nodeWidget.draw(this.selectNode(view, node));
|
||||||
}
|
}
|
||||||
|
|
||||||
public setDraggingEnabled(enabled: boolean) {
|
public draw(view: SVGSelection) {
|
||||||
this.draggingEnabled = enabled;
|
const node = view
|
||||||
}
|
.selectAll<SVGGElement, MapNode>("g.node")
|
||||||
|
.data((layer: Layer) => {
|
||||||
public revise(selection: SVGSelection) {
|
if (layer.nodes) {
|
||||||
selection
|
return layer.nodes;
|
||||||
.attr('transform', (n: Node) => {
|
}
|
||||||
return `translate(${n.x},${n.y})`;
|
return [];
|
||||||
|
}, (n: MapNode) => {
|
||||||
|
return n.id;
|
||||||
});
|
});
|
||||||
|
|
||||||
selection
|
const node_enter = node.enter()
|
||||||
.select<SVGTextElement>('text.label')
|
.append<SVGGElement>('g')
|
||||||
// .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way
|
.attr('class', 'node')
|
||||||
.attr('style', (n: Node) => {
|
.attr('node_id', (n: MapNode) => n.id)
|
||||||
let styles = this.cssFixer.fix(n.label.style);
|
|
||||||
styles = this.fontFixer.fixStyles(styles);
|
|
||||||
return styles;
|
|
||||||
})
|
|
||||||
.text((n: Node) => n.label.text)
|
|
||||||
.attr('x', function (this: SVGTextElement, n: Node) {
|
|
||||||
if (n.label.x === null) {
|
|
||||||
// center
|
|
||||||
const bbox = this.getBBox();
|
|
||||||
return -bbox.width / 2.;
|
|
||||||
}
|
|
||||||
return n.label.x + NodesWidget.NODE_LABEL_MARGIN;
|
|
||||||
})
|
|
||||||
.attr('y', function (this: SVGTextElement, n: Node) {
|
|
||||||
let bbox = this.getBBox();
|
|
||||||
|
|
||||||
if (n.label.x === null) {
|
const merge = node.merge(node_enter);
|
||||||
// center
|
|
||||||
bbox = this.getBBox();
|
|
||||||
return - n.height / 2. - bbox.height ;
|
|
||||||
}
|
|
||||||
return n.label.y + bbox.height - NodesWidget.NODE_LABEL_MARGIN;
|
|
||||||
});
|
|
||||||
|
|
||||||
selection
|
this.nodeWidget.draw(merge);
|
||||||
.select<SVGTextElement>('text.node_point_label')
|
|
||||||
.text((n: Node) => `(${n.x}, ${n.y})`);
|
|
||||||
|
|
||||||
}
|
node
|
||||||
|
|
||||||
public draw(view: SVGSelection, nodes?: Node[]) {
|
|
||||||
const self = this;
|
|
||||||
|
|
||||||
let nodes_selection: Selection<SVGGElement, Node, any, any> = view
|
|
||||||
.selectAll<SVGGElement, Node>('g.node');
|
|
||||||
|
|
||||||
if (nodes) {
|
|
||||||
nodes_selection = nodes_selection.data(nodes);
|
|
||||||
} else {
|
|
||||||
nodes_selection = nodes_selection.data((l: Layer) => {
|
|
||||||
return l.nodes;
|
|
||||||
}, (n: Node) => {
|
|
||||||
return n.node_id;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const node_enter = nodes_selection
|
|
||||||
.enter()
|
|
||||||
.append<SVGGElement>('g')
|
|
||||||
.attr('class', 'node');
|
|
||||||
|
|
||||||
// add image to node
|
|
||||||
node_enter
|
|
||||||
.append<SVGImageElement>('image');
|
|
||||||
|
|
||||||
// add label of node
|
|
||||||
node_enter
|
|
||||||
.append<SVGTextElement>('text')
|
|
||||||
.attr('class', 'label');
|
|
||||||
|
|
||||||
if (this.debug) {
|
|
||||||
node_enter
|
|
||||||
.append<SVGCircleElement>('circle')
|
|
||||||
.attr('class', 'node_point')
|
|
||||||
.attr('r', 2);
|
|
||||||
|
|
||||||
node_enter
|
|
||||||
.append<SVGTextElement>('text')
|
|
||||||
.attr('class', 'node_point_label')
|
|
||||||
.attr('x', '-100')
|
|
||||||
.attr('y', '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
const node_merge = nodes_selection
|
|
||||||
.merge(node_enter)
|
|
||||||
.classed('selected', (n: Node) => n.is_selected)
|
|
||||||
.on("contextmenu", function (n: Node, i: number) {
|
|
||||||
event.preventDefault();
|
|
||||||
self.onContextMenu.emit(new NodeEvent(event, n));
|
|
||||||
})
|
|
||||||
.on('click', (n: Node) => {
|
|
||||||
this.onNodeClicked.emit(new NodeEvent(event, n));
|
|
||||||
});
|
|
||||||
|
|
||||||
// update image of node
|
|
||||||
node_merge
|
|
||||||
.select<SVGImageElement>('image')
|
|
||||||
.attr('xlink:href', (n: Node) => {
|
|
||||||
const symbol = this.symbols.find((s: Symbol) => s.symbol_id === n.symbol);
|
|
||||||
if (symbol) {
|
|
||||||
return 'data:image/svg+xml;base64,' + btoa(symbol.raw);
|
|
||||||
}
|
|
||||||
// @todo; we need to have default image
|
|
||||||
return 'data:image/svg+xml;base64,none';
|
|
||||||
})
|
|
||||||
.attr('width', (n: Node) => n.width)
|
|
||||||
.attr('height', (n: Node) => n.height)
|
|
||||||
.attr('x', (n: Node) => 0)
|
|
||||||
.attr('y', (n: Node) => 0)
|
|
||||||
.on('mouseover', function (this, n: Node) {
|
|
||||||
select(this).attr("class", "over");
|
|
||||||
})
|
|
||||||
.on('mouseout', function (this, n: Node) {
|
|
||||||
select(this).attr("class", "");
|
|
||||||
});
|
|
||||||
|
|
||||||
this.revise(node_merge);
|
|
||||||
|
|
||||||
const callback = function (this: SVGGElement, n: Node) {
|
|
||||||
const e: D3DragEvent<SVGGElement, Node, Node> = event;
|
|
||||||
|
|
||||||
n.x = e.x;
|
|
||||||
n.y = e.y;
|
|
||||||
|
|
||||||
self.revise(select(this));
|
|
||||||
self.onNodeDragging.emit(new NodeEvent(event, n));
|
|
||||||
};
|
|
||||||
|
|
||||||
const dragging = () => {
|
|
||||||
return drag<SVGGElement, Node>()
|
|
||||||
.on('drag', callback)
|
|
||||||
.on('end', (n: Node) => {
|
|
||||||
const e: D3DragEvent<SVGGElement, Node, Node> = event;
|
|
||||||
self.onNodeDragged.emit(new NodeEvent(e, n));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.draggingEnabled) {
|
|
||||||
node_merge.call(dragging());
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes_selection
|
|
||||||
.exit()
|
.exit()
|
||||||
.remove();
|
.remove();
|
||||||
|
|
||||||
|
if (this.draggingEnabled) {
|
||||||
|
this.draggable.call(merge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private selectNode(view: SVGSelection, node: MapNode) {
|
||||||
|
return view.selectAll<SVGGElement, MapNode>(`g.node[node_id="${node.id}"]`);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -34,4 +34,3 @@ export class ApplianceComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ export class MoveLayerUpActionComponent implements OnInit {
|
|||||||
|
|
||||||
moveLayerUp() {
|
moveLayerUp() {
|
||||||
this.node.z++;
|
this.node.z++;
|
||||||
|
this.nodesDataSource.update(this.node);
|
||||||
this.nodeService
|
this.nodeService
|
||||||
.update(this.server, this.node)
|
.update(this.server, this.node)
|
||||||
.subscribe((node: Node) => {});
|
.subscribe((node: Node) => {});
|
||||||
|
@ -2,7 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
|||||||
import { HttpClientTestingModule } from "@angular/common/http/testing";
|
import { HttpClientTestingModule } from "@angular/common/http/testing";
|
||||||
import { inject } from "@angular/core/testing";
|
import { inject } from "@angular/core/testing";
|
||||||
|
|
||||||
import { mock, instance, capture, when } from "ts-mockito";
|
import { mock, instance, capture, when, anything } from "ts-mockito";
|
||||||
import { HotkeyModule, HotkeysService, Hotkey } from "angular2-hotkeys";
|
import { HotkeyModule, HotkeysService, Hotkey } from "angular2-hotkeys";
|
||||||
import { of } from "rxjs";
|
import { of } from "rxjs";
|
||||||
|
|
||||||
@ -12,13 +12,17 @@ import { NodeService } from "../../../services/node.service";
|
|||||||
import { HttpServer } from "../../../services/http-server.service";
|
import { HttpServer } from "../../../services/http-server.service";
|
||||||
import { SelectionManager } from "../../../cartography/managers/selection-manager";
|
import { SelectionManager } from "../../../cartography/managers/selection-manager";
|
||||||
import { Server } from "../../../models/server";
|
import { Server } from "../../../models/server";
|
||||||
import { Node } from "../../../cartography/models/node";
|
|
||||||
import { Project } from "../../../models/project";
|
import { Project } from "../../../models/project";
|
||||||
import { ProjectService } from "../../../services/project.service";
|
import { ProjectService } from "../../../services/project.service";
|
||||||
import { MockedProjectService } from "../../../services/project.service.spec";
|
import { MockedProjectService } from "../../../services/project.service.spec";
|
||||||
import { SettingsService } from "../../../services/settings.service";
|
import { SettingsService } from "../../../services/settings.service";
|
||||||
import { MockedToasterService } from "../../../services/toaster.service.spec";
|
import { MockedToasterService } from "../../../services/toaster.service.spec";
|
||||||
import { mapTo } from "rxjs/internal/operators";
|
import { mapTo } from "rxjs/internal/operators";
|
||||||
|
import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter';
|
||||||
|
import { MapNode } from '../../../cartography/models/map/map-node';
|
||||||
|
import { MapLabelToLabelConverter } from '../../../cartography/converters/map/map-label-to-label-converter';
|
||||||
|
import { MapPortToPortConverter } from '../../../cartography/converters/map/map-port-to-port-converter';
|
||||||
|
import { Node } from '../../../cartography/models/node';
|
||||||
|
|
||||||
|
|
||||||
describe('ProjectMapShortcutsComponent', () => {
|
describe('ProjectMapShortcutsComponent', () => {
|
||||||
@ -27,8 +31,13 @@ describe('ProjectMapShortcutsComponent', () => {
|
|||||||
let hotkeyServiceMock: HotkeysService;
|
let hotkeyServiceMock: HotkeysService;
|
||||||
let hotkeyServiceInstanceMock: HotkeysService;
|
let hotkeyServiceInstanceMock: HotkeysService;
|
||||||
let nodeServiceMock: NodeService;
|
let nodeServiceMock: NodeService;
|
||||||
|
let node: MapNode;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
|
node = new MapNode();
|
||||||
|
const selectionManagerMock = mock(SelectionManager);
|
||||||
|
when(selectionManagerMock.getSelected()).thenReturn([node]);
|
||||||
|
|
||||||
nodeServiceMock = mock(NodeService);
|
nodeServiceMock = mock(NodeService);
|
||||||
hotkeyServiceMock = mock(HotkeysService);
|
hotkeyServiceMock = mock(HotkeysService);
|
||||||
hotkeyServiceInstanceMock = instance(hotkeyServiceMock);
|
hotkeyServiceInstanceMock = instance(hotkeyServiceMock);
|
||||||
@ -43,7 +52,11 @@ describe('ProjectMapShortcutsComponent', () => {
|
|||||||
{ provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock },
|
{ provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock },
|
||||||
{ provide: ToasterService, useClass: MockedToasterService },
|
{ provide: ToasterService, useClass: MockedToasterService },
|
||||||
{ provide: ProjectService, useClass: MockedProjectService },
|
{ provide: ProjectService, useClass: MockedProjectService },
|
||||||
{ provide: SettingsService, useClass: SettingsService }
|
{ provide: SelectionManager, useValue: instance(selectionManagerMock)},
|
||||||
|
SettingsService,
|
||||||
|
MapNodeToNodeConverter,
|
||||||
|
MapLabelToLabelConverter,
|
||||||
|
MapPortToPortConverter
|
||||||
],
|
],
|
||||||
declarations: [ ProjectMapShortcutsComponent ]
|
declarations: [ ProjectMapShortcutsComponent ]
|
||||||
})
|
})
|
||||||
@ -75,19 +88,14 @@ describe('ProjectMapShortcutsComponent', () => {
|
|||||||
|
|
||||||
describe('onDeleteHandler', () => {
|
describe('onDeleteHandler', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const selectionManagerMock = mock(SelectionManager);
|
|
||||||
const node = new Node();
|
|
||||||
node.node_id = "nodeid";
|
|
||||||
const server = new Server();
|
const server = new Server();
|
||||||
const project = new Project();
|
const project = new Project();
|
||||||
|
|
||||||
when(selectionManagerMock.getSelectedNodes()).thenReturn([node]);
|
when(nodeServiceMock.delete(server, anything()))
|
||||||
when(nodeServiceMock.delete(server, node))
|
.thenReturn(of(new Node()).pipe(mapTo(null)));
|
||||||
.thenReturn(of(node).pipe(mapTo(null)));
|
|
||||||
|
|
||||||
component.project = project;
|
component.project = project;
|
||||||
component.server = server;
|
component.server = server;
|
||||||
component.selectionManager = instance(selectionManagerMock);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle delete', inject([ToasterService], (toaster: MockedToasterService) => {
|
it('should handle delete', inject([ToasterService], (toaster: MockedToasterService) => {
|
||||||
|
@ -7,6 +7,8 @@ import { Server } from '../../../models/server';
|
|||||||
import { ToasterService } from '../../../services/toaster.service';
|
import { ToasterService } from '../../../services/toaster.service';
|
||||||
import { Project } from "../../../models/project";
|
import { Project } from "../../../models/project";
|
||||||
import { ProjectService } from "../../../services/project.service";
|
import { ProjectService } from "../../../services/project.service";
|
||||||
|
import { MapNode } from '../../../cartography/models/map/map-node';
|
||||||
|
import { MapNodeToNodeConverter } from '../../../cartography/converters/map/map-node-to-node-converter';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -16,7 +18,6 @@ import { ProjectService } from "../../../services/project.service";
|
|||||||
export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
|
export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
|
||||||
@Input() project: Project;
|
@Input() project: Project;
|
||||||
@Input() server: Server;
|
@Input() server: Server;
|
||||||
@Input() selectionManager: SelectionManager;
|
|
||||||
|
|
||||||
private deleteHotkey: Hotkey;
|
private deleteHotkey: Hotkey;
|
||||||
|
|
||||||
@ -24,7 +25,9 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
|
|||||||
private hotkeysService: HotkeysService,
|
private hotkeysService: HotkeysService,
|
||||||
private toaster: ToasterService,
|
private toaster: ToasterService,
|
||||||
private nodesService: NodeService,
|
private nodesService: NodeService,
|
||||||
private projectService: ProjectService
|
private projectService: ProjectService,
|
||||||
|
private mapNodeToNode: MapNodeToNodeConverter,
|
||||||
|
private selectionManager: SelectionManager
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -37,14 +40,14 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
onDeleteHandler(event: KeyboardEvent): boolean {
|
onDeleteHandler(event: KeyboardEvent): boolean {
|
||||||
if (!this.projectService.isReadOnly(this.project)) {
|
if (!this.projectService.isReadOnly(this.project)) {
|
||||||
const selectedNodes = this.selectionManager.getSelectedNodes();
|
const selected = this.selectionManager.getSelected();
|
||||||
if (selectedNodes) {
|
|
||||||
selectedNodes.forEach((node) => {
|
selected.filter((item) => item instanceof MapNode).forEach((item: MapNode) => {
|
||||||
this.nodesService.delete(this.server, node).subscribe(data => {
|
const node = this.mapNodeToNode.convert(item);
|
||||||
this.toaster.success("Node has been deleted");
|
this.nodesService.delete(this.server, node).subscribe(data => {
|
||||||
});
|
this.toaster.success("Node has been deleted");
|
||||||
});
|
});
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -31,17 +31,14 @@ g.node:hover {
|
|||||||
left: 50%;
|
left: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*g.node text {*/
|
|
||||||
/*font-family: Roboto !important;*/
|
|
||||||
/*}*/
|
|
||||||
|
|
||||||
|
|
||||||
svg.map image:hover, svg.map image.chosen, g.selected {
|
svg.map image:hover, svg.map image.chosen, g.selected {
|
||||||
-webkit-filter: grayscale(100%);
|
-webkit-filter: grayscale(100%);
|
||||||
-moz-filter: grayscale(100%);
|
-moz-filter: grayscale(100%);
|
||||||
-ms-filter: grayscale(100%);
|
-ms-filter: grayscale(100%);
|
||||||
-o-filter: grayscale(100%);
|
-o-filter: grayscale(100%);
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
|
filter: gray;
|
||||||
|
filter: url("#grayscale"); /* Chrome doesn't support CSS filters on SVG */
|
||||||
}
|
}
|
||||||
|
|
||||||
path.selected {
|
path.selected {
|
||||||
|
@ -10,8 +10,7 @@
|
|||||||
[selection-tool]="tools.selection"
|
[selection-tool]="tools.selection"
|
||||||
[moving-tool]="tools.moving"
|
[moving-tool]="tools.moving"
|
||||||
[draw-link-tool]="tools.draw_link"
|
[draw-link-tool]="tools.draw_link"
|
||||||
(onNodeDragged)="onNodeDragged($event)"
|
[readonly]="inReadOnlyMode"
|
||||||
(onLinkCreated)="onLinkCreated($event)"
|
|
||||||
></app-map>
|
></app-map>
|
||||||
<div class="project-toolbar">
|
<div class="project-toolbar">
|
||||||
<mat-toolbar color="primary" class="project-toolbar">
|
<mat-toolbar color="primary" class="project-toolbar">
|
||||||
@ -86,6 +85,5 @@
|
|||||||
<app-project-map-shortcuts
|
<app-project-map-shortcuts
|
||||||
*ngIf="project"
|
*ngIf="project"
|
||||||
[project]="project"
|
[project]="project"
|
||||||
[server]="server"
|
[server]="server">
|
||||||
[selectionManager]="selectionManager">
|
|
||||||
</app-project-map-shortcuts>
|
</app-project-map-shortcuts>
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user