mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2024-12-19 04:57:51 +00:00
Merge pull request #92 from GNS3/different-layers
Support of drawing elements on different layers, Fixes: #11
This commit is contained in:
commit
1040db4308
@ -66,6 +66,7 @@
|
|||||||
"node-sass": "^4.5.3",
|
"node-sass": "^4.5.3",
|
||||||
"popper.js": "^1.12.3",
|
"popper.js": "^1.12.3",
|
||||||
"protractor": "~5.3.0",
|
"protractor": "~5.3.0",
|
||||||
|
"ts-mockito": "^2.3.0",
|
||||||
"ts-node": "~5.0.0",
|
"ts-node": "~5.0.0",
|
||||||
"tslint": "~5.9.1",
|
"tslint": "~5.9.1",
|
||||||
"typescript": ">=2.4.0 <2.6.0"
|
"typescript": ">=2.4.0 <2.6.0"
|
||||||
|
@ -53,12 +53,15 @@ import { ApplianceListDialogComponent } from './appliance/appliance-list-dialog/
|
|||||||
import { NodeSelectInterfaceComponent } from './shared/node-select-interface/node-select-interface.component';
|
import { NodeSelectInterfaceComponent } from './shared/node-select-interface/node-select-interface.component';
|
||||||
import { CartographyModule } from './cartography/cartography.module';
|
import { CartographyModule } from './cartography/cartography.module';
|
||||||
import { ToasterService } from './shared/services/toaster.service';
|
import { ToasterService } from './shared/services/toaster.service';
|
||||||
import {ProjectWebServiceHandler} from "./shared/handlers/project-web-service-handler";
|
import { ProjectWebServiceHandler } from "./shared/handlers/project-web-service-handler";
|
||||||
import {LinksDataSource} from "./cartography/shared/datasources/links-datasource";
|
import { LinksDataSource } from "./cartography/shared/datasources/links-datasource";
|
||||||
import {NodesDataSource} from "./cartography/shared/datasources/nodes-datasource";
|
import { NodesDataSource } from "./cartography/shared/datasources/nodes-datasource";
|
||||||
import {SymbolsDataSource} from "./cartography/shared/datasources/symbols-datasource";
|
import { SymbolsDataSource } from "./cartography/shared/datasources/symbols-datasource";
|
||||||
import {SelectionManager} from "./cartography/shared/managers/selection-manager";
|
import { SelectionManager } from "./cartography/shared/managers/selection-manager";
|
||||||
import {InRectangleHelper} from "./cartography/map/helpers/in-rectangle-helper";
|
import { InRectangleHelper } from "./cartography/map/helpers/in-rectangle-helper";
|
||||||
|
import { DrawingsDataSource } from "./cartography/shared/datasources/drawings-datasource";
|
||||||
|
import { MoveLayerDownActionComponent } from './shared/node-context-menu/actions/move-layer-down-action/move-layer-down-action.component';
|
||||||
|
import { MoveLayerUpActionComponent } from './shared/node-context-menu/actions/move-layer-up-action/move-layer-up-action.component';
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -77,6 +80,8 @@ import {InRectangleHelper} from "./cartography/map/helpers/in-rectangle-helper";
|
|||||||
ApplianceComponent,
|
ApplianceComponent,
|
||||||
ApplianceListDialogComponent,
|
ApplianceListDialogComponent,
|
||||||
NodeSelectInterfaceComponent,
|
NodeSelectInterfaceComponent,
|
||||||
|
MoveLayerDownActionComponent,
|
||||||
|
MoveLayerUpActionComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
NgbModule.forRoot(),
|
NgbModule.forRoot(),
|
||||||
@ -120,7 +125,8 @@ import {InRectangleHelper} from "./cartography/map/helpers/in-rectangle-helper";
|
|||||||
NodesDataSource,
|
NodesDataSource,
|
||||||
SymbolsDataSource,
|
SymbolsDataSource,
|
||||||
SelectionManager,
|
SelectionManager,
|
||||||
InRectangleHelper
|
InRectangleHelper,
|
||||||
|
DrawingsDataSource
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddServerDialogComponent,
|
AddServerDialogComponent,
|
||||||
|
29
src/app/cartography/map/helpers/in-rectangle-helper.spec.ts
Normal file
29
src/app/cartography/map/helpers/in-rectangle-helper.spec.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { InRectangleHelper } from "./in-rectangle-helper";
|
||||||
|
import { Selectable } from "../../shared/managers/selection-manager";
|
||||||
|
import { Rectangle } from "../../shared/models/rectangle";
|
||||||
|
|
||||||
|
class ExampleNode implements Selectable {
|
||||||
|
constructor(public x: number, public y: number, public is_selected: boolean) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
describe('InRectangleHelper', () => {
|
||||||
|
let inRectangleHelper: InRectangleHelper;
|
||||||
|
let node: Selectable;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
inRectangleHelper = new InRectangleHelper();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be in rectangle', () => {
|
||||||
|
node = new ExampleNode(100, 100, false);
|
||||||
|
const isIn = inRectangleHelper.inRectangle(node, new Rectangle(10, 10, 150, 150));
|
||||||
|
expect(isIn).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be outside rectangle', () => {
|
||||||
|
node = new ExampleNode(100, 100, false);
|
||||||
|
const isIn = inRectangleHelper.inRectangle(node, new Rectangle(10, 10, 50, 50));
|
||||||
|
expect(isIn).toBeFalsy();
|
||||||
|
});
|
||||||
|
});
|
@ -1,6 +1,8 @@
|
|||||||
import {Selectable} from "../../shared/managers/selection-manager";
|
import { Injectable } from "@angular/core";
|
||||||
import {Rectangle} from "../../shared/models/rectangle";
|
|
||||||
import {Injectable} from "@angular/core";
|
import { Selectable } from "../../shared/managers/selection-manager";
|
||||||
|
import { Rectangle } from "../../shared/models/rectangle";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InRectangleHelper {
|
export class InRectangleHelper {
|
||||||
|
@ -30,8 +30,8 @@ export class MultiLinkCalculatorHelper {
|
|||||||
public assignDataToLinks(links: Link[]) {
|
public assignDataToLinks(links: Link[]) {
|
||||||
const links_from_nodes = {};
|
const links_from_nodes = {};
|
||||||
links.forEach((l: Link, i: number) => {
|
links.forEach((l: Link, i: number) => {
|
||||||
const sid = l.nodes[0].node_id;
|
const sid = l.source.node_id;
|
||||||
const tid = l.nodes[1].node_id;
|
const tid = l.target.node_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)) {
|
||||||
|
@ -6,7 +6,7 @@ import {select, Selection} from 'd3-selection';
|
|||||||
|
|
||||||
import { Node } from "../shared/models/node";
|
import { Node } from "../shared/models/node";
|
||||||
import { Link } from "../shared/models/link";
|
import { Link } from "../shared/models/link";
|
||||||
import { GraphLayout } from "../shared/widgets/graph.widget";
|
import { GraphLayout } from "../shared/widgets/graph-layout";
|
||||||
import { Context } from "../shared/models/context";
|
import { Context } from "../shared/models/context";
|
||||||
import { Size } from "../shared/models/size";
|
import { Size } from "../shared/models/size";
|
||||||
import { Drawing } from "../shared/models/drawing";
|
import { Drawing } from "../shared/models/drawing";
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {DataSource} from "./datasource";
|
import { DataSource } from "./datasource";
|
||||||
|
|
||||||
class Item {
|
class Item {
|
||||||
constructor(public id: string, public property1?: string, public property2?: string) {}
|
constructor(public id: string, public property1?: string, public property2?: string) {}
|
||||||
@ -12,7 +12,6 @@ class TestDataSource extends DataSource<Item> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('TestDataSource', () => {
|
describe('TestDataSource', () => {
|
||||||
let dataSource: TestDataSource;
|
let dataSource: TestDataSource;
|
||||||
let data: Item[];
|
let data: Item[];
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {BehaviorSubject} from "rxjs/BehaviorSubject";
|
import { BehaviorSubject } from "rxjs/BehaviorSubject";
|
||||||
|
|
||||||
export abstract class DataSource<T> {
|
export abstract class DataSource<T> {
|
||||||
protected data: T[] = [];
|
protected data: T[] = [];
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
import { DrawingsDataSource } from "./drawings-datasource";
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
|
|
||||||
|
|
||||||
|
describe('DrawingsDataSource', () => {
|
||||||
|
let dataSource: DrawingsDataSource;
|
||||||
|
let data: Drawing[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dataSource = new DrawingsDataSource();
|
||||||
|
dataSource.connect().subscribe((drawings: Drawing[]) => {
|
||||||
|
data = drawings;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Drawing can be updated', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const drawing = new Drawing();
|
||||||
|
drawing.drawing_id = "1";
|
||||||
|
drawing.project_id = "project1";
|
||||||
|
dataSource.add(drawing);
|
||||||
|
|
||||||
|
drawing.project_id = "project2";
|
||||||
|
dataSource.update(drawing);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('project_id should change', () => {
|
||||||
|
expect(data[0].drawing_id).toEqual("1");
|
||||||
|
expect(data[0].project_id).toEqual("project2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -0,0 +1,12 @@
|
|||||||
|
import { Injectable } from "@angular/core";
|
||||||
|
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
|
import { DataSource } from "./datasource";
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class DrawingsDataSource extends DataSource<Drawing> {
|
||||||
|
protected findIndex(drawing: Drawing) {
|
||||||
|
return this.data.findIndex((d: Drawing) => d.drawing_id === drawing.drawing_id);
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import {LinksDataSource} from "./links-datasource";
|
import { LinksDataSource } from "./links-datasource";
|
||||||
import {Link} from "../models/link";
|
import { Link } from "../models/link";
|
||||||
|
|
||||||
|
|
||||||
describe('LinksDataSource', () => {
|
describe('LinksDataSource', () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
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()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {NodesDataSource} from "./nodes-datasource";
|
import { NodesDataSource } from "./nodes-datasource";
|
||||||
import {Node} from "../models/node";
|
import { Node } from "../models/node";
|
||||||
|
|
||||||
|
|
||||||
describe('NodesDataSource', () => {
|
describe('NodesDataSource', () => {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Node} from "../models/node";
|
import { Injectable } from "@angular/core";
|
||||||
import {DataSource} from "./datasource";
|
|
||||||
import {Injectable} from "@angular/core";
|
import { Node } from "../models/node";
|
||||||
|
import { DataSource } from "./datasource";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {SymbolsDataSource} from "./symbols-datasource";
|
import { SymbolsDataSource } from "./symbols-datasource";
|
||||||
import {Symbol} from "../models/symbol";
|
import { Symbol } from "../models/symbol";
|
||||||
|
|
||||||
|
|
||||||
describe('SymbolsDataSource', () => {
|
describe('SymbolsDataSource', () => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import {Node} from "../models/node";
|
import { Injectable } from "@angular/core";
|
||||||
import {DataSource} from "./datasource";
|
|
||||||
import {Injectable} from "@angular/core";
|
import { DataSource } from "./datasource";
|
||||||
import {Symbol} from "../models/symbol";
|
import { Symbol } from "../models/symbol";
|
||||||
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
76
src/app/cartography/shared/managers/layers-manager.spec.ts
Normal file
76
src/app/cartography/shared/managers/layers-manager.spec.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import { LayersManager } from "./layers-manager";
|
||||||
|
import { Node } from "../models/node";
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
|
import { Link } from "../models/link";
|
||||||
|
|
||||||
|
|
||||||
|
describe('LayersManager', () => {
|
||||||
|
let manager: LayersManager;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
manager = new LayersManager();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('nodes should be added', () => {
|
||||||
|
const node_1 = new Node();
|
||||||
|
node_1.z = 1;
|
||||||
|
const node_2 = new Node();
|
||||||
|
node_2.z = 2;
|
||||||
|
|
||||||
|
manager.setNodes([node_1, node_2]);
|
||||||
|
const layers = manager.getLayersList();
|
||||||
|
expect(layers.length).toEqual(2);
|
||||||
|
expect(layers[0].nodes.length).toEqual(1);
|
||||||
|
expect(layers[0].index).toEqual(1);
|
||||||
|
expect(layers[1].nodes.length).toEqual(1);
|
||||||
|
expect(layers[1].index).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('drawings should be added', () => {
|
||||||
|
const drawing_1 = new Drawing();
|
||||||
|
drawing_1.z = 1;
|
||||||
|
const drawing_2 = new Drawing();
|
||||||
|
drawing_2.z = 2;
|
||||||
|
|
||||||
|
manager.setDrawings([drawing_1, drawing_2]);
|
||||||
|
const layers = manager.getLayersList();
|
||||||
|
expect(layers.length).toEqual(2);
|
||||||
|
expect(layers[0].drawings.length).toEqual(1);
|
||||||
|
expect(layers[0].index).toEqual(1);
|
||||||
|
expect(layers[1].drawings.length).toEqual(1);
|
||||||
|
expect(layers[1].index).toEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('links should be added', () => {
|
||||||
|
const node_1 = new Node();
|
||||||
|
node_1.z = 1;
|
||||||
|
|
||||||
|
const node_2 = new Node();
|
||||||
|
node_2.z = 2;
|
||||||
|
|
||||||
|
const link_1 = new Link();
|
||||||
|
link_1.source = node_1;
|
||||||
|
link_1.target = node_1;
|
||||||
|
|
||||||
|
const link_2 = new Link();
|
||||||
|
link_2.source = node_1;
|
||||||
|
link_2.target = node_1;
|
||||||
|
|
||||||
|
manager.setLinks([link_1, link_2]);
|
||||||
|
const layers = manager.getLayersList();
|
||||||
|
expect(layers.length).toEqual(1);
|
||||||
|
expect(layers[0].links.length).toEqual(2);
|
||||||
|
expect(layers[0].index).toEqual(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('layers should be cleared', () => {
|
||||||
|
const node_1 = new Node();
|
||||||
|
node_1.z = 1;
|
||||||
|
const node_2 = new Node();
|
||||||
|
node_2.z = 2;
|
||||||
|
|
||||||
|
manager.setNodes([node_1, node_2]);
|
||||||
|
manager.clear();
|
||||||
|
expect(manager.getLayersList().length).toEqual(0);
|
||||||
|
});
|
||||||
|
});
|
60
src/app/cartography/shared/managers/layers-manager.ts
Normal file
60
src/app/cartography/shared/managers/layers-manager.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
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";
|
||||||
|
|
||||||
|
|
||||||
|
export class LayersManager {
|
||||||
|
private layers: Dictionary<Layer>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.layers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLayersList(): Layer[] {
|
||||||
|
return Object.keys(this.layers).sort((a: string, b: string) => {
|
||||||
|
return Number(a) - Number(b);
|
||||||
|
}).map((key: string) => {
|
||||||
|
return this.layers[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public setNodes(nodes: Node[]) {
|
||||||
|
nodes
|
||||||
|
.forEach((node: Node) => {
|
||||||
|
const layer = this.getLayerForKey(node.z.toString());
|
||||||
|
layer.nodes.push(node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public setDrawings(drawings: Drawing[]) {
|
||||||
|
drawings
|
||||||
|
.forEach((drawing: Drawing) => {
|
||||||
|
const layer = this.getLayerForKey(drawing.z.toString());
|
||||||
|
layer.drawings.push(drawing);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public setLinks(links: Link[]) {
|
||||||
|
links
|
||||||
|
.filter((link: Link) => link.source && link.target)
|
||||||
|
.forEach((link: Link) => {
|
||||||
|
const key = Math.min(link.source.z, link.target.z).toString();
|
||||||
|
const layer = this.getLayerForKey(key);
|
||||||
|
layer.links.push(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear() {
|
||||||
|
this.layers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLayerForKey(key: string): Layer {
|
||||||
|
if (!(key in this.layers)) {
|
||||||
|
this.layers[key] = new Layer();
|
||||||
|
this.layers[key].index = Number(key);
|
||||||
|
}
|
||||||
|
return this.layers[key];
|
||||||
|
}
|
||||||
|
}
|
16
src/app/cartography/shared/models/layer.ts
Normal file
16
src/app/cartography/shared/models/layer.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import {Drawing} from "./drawing";
|
||||||
|
import {Link} from "./link";
|
||||||
|
import {Node} from "./node";
|
||||||
|
|
||||||
|
export class Layer {
|
||||||
|
index: number;
|
||||||
|
nodes: Node[];
|
||||||
|
drawings: Drawing[];
|
||||||
|
links: Link[];
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.nodes = [];
|
||||||
|
this.drawings = [];
|
||||||
|
this.links = [];
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import {Label} from "./label";
|
|||||||
import {Port} from "../../../shared/models/port";
|
import {Port} from "../../../shared/models/port";
|
||||||
import {Selectable} from "../managers/selection-manager";
|
import {Selectable} from "../managers/selection-manager";
|
||||||
|
|
||||||
|
|
||||||
export class Node implements Selectable {
|
export class Node implements Selectable {
|
||||||
command_line: string;
|
command_line: string;
|
||||||
compute_id: string;
|
compute_id: string;
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
import {BaseType, Selection} from "d3-selection";
|
import {BaseType, Selection} from "d3-selection";
|
||||||
|
|
||||||
export type SVGSelection = Selection<SVGElement, any, BaseType, any>;
|
export type SVGSelection = Selection<SVGElement, any, BaseType, any>;
|
||||||
|
|
||||||
|
export interface Dictionary<T> {
|
||||||
|
[Key: string]: T;
|
||||||
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import {SVGSelection} from "./models/types";
|
import { SVGSelection } from "./models/types";
|
||||||
|
|
||||||
export interface Tool {
|
export interface Tool {
|
||||||
connect(selection: SVGSelection);
|
connect(selection: SVGSelection);
|
||||||
|
@ -1,46 +1,41 @@
|
|||||||
import { select } from "d3-selection";
|
|
||||||
import { Context } from "../models/context";
|
import { Context } from "../models/context";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { MovingTool } from "./moving-tool";
|
import { MovingTool } from "./moving-tool";
|
||||||
|
import { TestSVGCanvas } from "../../testing";
|
||||||
|
|
||||||
|
|
||||||
describe('MovingTool', () => {
|
describe('MovingTool', () => {
|
||||||
let tool: MovingTool;
|
let tool: MovingTool;
|
||||||
let svg: SVGSelection;
|
let svg: TestSVGCanvas;
|
||||||
let context: Context;
|
let context: Context;
|
||||||
let node: SVGSelection;
|
let node: SVGSelection;
|
||||||
let canvas: SVGSelection;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tool = new MovingTool();
|
tool = new MovingTool();
|
||||||
|
svg = new TestSVGCanvas();
|
||||||
|
|
||||||
svg = select('body')
|
node = svg.canvas
|
||||||
.append<SVGSVGElement>('svg')
|
|
||||||
.attr('width', 1000)
|
|
||||||
.attr('height', 1000);
|
|
||||||
|
|
||||||
canvas = svg.append<SVGGElement>('g').attr('class', 'canvas');
|
|
||||||
|
|
||||||
node = canvas
|
|
||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'node')
|
.attr('class', 'node')
|
||||||
.attr('x', 10)
|
.attr('x', 10)
|
||||||
.attr('y', 20);
|
.attr('y', 20);
|
||||||
|
|
||||||
|
|
||||||
context = new Context();
|
context = new Context();
|
||||||
|
|
||||||
tool.connect(svg, context);
|
tool.connect(svg.svg, context);
|
||||||
tool.draw(svg, context);
|
tool.draw(svg.svg, context);
|
||||||
tool.activate();
|
tool.activate();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
svg.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('MovingTool can move canvas', () => {
|
describe('MovingTool can move canvas', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg.node().dispatchEvent(
|
svg.svg.node().dispatchEvent(
|
||||||
new MouseEvent('mousedown', {
|
new MouseEvent('mousedown', {
|
||||||
clientX: 100, clientY: 100, relatedTarget: svg.node(),
|
clientX: 100, clientY: 100, relatedTarget: svg.svg.node(),
|
||||||
screenY: 1024, screenX: 1024, view: window
|
screenY: 1024, screenX: 1024, view: window
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -50,7 +45,7 @@ describe('MovingTool', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('canvas should transformed', () => {
|
it('canvas should transformed', () => {
|
||||||
expect(canvas.attr('transform')).toEqual('translate(100, 100) scale(1)');
|
expect(svg.canvas.attr('transform')).toEqual('translate(100, 100) scale(1)');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -58,9 +53,9 @@ describe('MovingTool', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tool.deactivate();
|
tool.deactivate();
|
||||||
|
|
||||||
svg.node().dispatchEvent(
|
svg.svg.node().dispatchEvent(
|
||||||
new MouseEvent('mousedown', {
|
new MouseEvent('mousedown', {
|
||||||
clientX: 100, clientY: 100, relatedTarget: svg.node(),
|
clientX: 100, clientY: 100, relatedTarget: svg.svg.node(),
|
||||||
screenY: 1024, screenX: 1024, view: window
|
screenY: 1024, screenX: 1024, view: window
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -69,7 +64,7 @@ describe('MovingTool', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('canvas cannot be transformed', () => {
|
it('canvas cannot be transformed', () => {
|
||||||
expect(canvas.attr('transform')).toBeNull();
|
expect(svg.canvas.attr('transform')).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -4,11 +4,12 @@ import { SelectionTool } from "./selection-tool";
|
|||||||
import { Context } from "../models/context";
|
import { Context } from "../models/context";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import { Rectangle } from "../models/rectangle";
|
import { Rectangle } from "../models/rectangle";
|
||||||
|
import { TestSVGCanvas } from "../../testing";
|
||||||
|
|
||||||
|
|
||||||
describe('SelectionTool', () => {
|
describe('SelectionTool', () => {
|
||||||
let tool: SelectionTool;
|
let tool: SelectionTool;
|
||||||
let svg: SVGSelection;
|
let svg: TestSVGCanvas;
|
||||||
let context: Context;
|
let context: Context;
|
||||||
let selection_line_tool: SVGSelection;
|
let selection_line_tool: SVGSelection;
|
||||||
let path_selection: SVGSelection;
|
let path_selection: SVGSelection;
|
||||||
@ -16,28 +17,25 @@ describe('SelectionTool', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tool = new SelectionTool();
|
tool = new SelectionTool();
|
||||||
|
|
||||||
tool.rectangleSelected.subscribe((rectangle: Rectangle) => {
|
tool.rectangleSelected.subscribe((rectangle: Rectangle) => {
|
||||||
selected_rectangle = rectangle;
|
selected_rectangle = rectangle;
|
||||||
});
|
});
|
||||||
|
|
||||||
svg = select('body')
|
svg = new TestSVGCanvas();
|
||||||
.append<SVGSVGElement>('svg')
|
|
||||||
.attr('width', 1000)
|
|
||||||
.attr('height', 1000);
|
|
||||||
|
|
||||||
svg.append<SVGGElement>('g').attr('class', 'canvas');
|
|
||||||
|
|
||||||
context = new Context();
|
context = new Context();
|
||||||
|
|
||||||
tool.connect(svg, context);
|
tool.connect(svg.svg, context);
|
||||||
tool.draw(svg, context);
|
tool.draw(svg.svg, context);
|
||||||
tool.activate();
|
tool.activate();
|
||||||
|
|
||||||
selection_line_tool = svg.select('g.selection-line-tool');
|
selection_line_tool = svg.svg.select('g.selection-line-tool');
|
||||||
path_selection = selection_line_tool.select('path.selection');
|
path_selection = selection_line_tool.select('path.selection');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
svg.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
it('creates selection-line-tool container with path', () => {
|
it('creates selection-line-tool container with path', () => {
|
||||||
expect(selection_line_tool.node()).not.toBeNull();
|
expect(selection_line_tool.node()).not.toBeNull();
|
||||||
expect(selection_line_tool.select('path')).not.toBeNull();
|
expect(selection_line_tool.select('path')).not.toBeNull();
|
||||||
@ -46,7 +44,7 @@ describe('SelectionTool', () => {
|
|||||||
|
|
||||||
describe('SelectionTool can handle start of selection', () => {
|
describe('SelectionTool can handle start of selection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
svg.svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('path should be visible and have parameters', () => {
|
it('path should be visible and have parameters', () => {
|
||||||
@ -57,7 +55,7 @@ describe('SelectionTool', () => {
|
|||||||
|
|
||||||
describe('SelectionTool can handle move of selection', () => {
|
describe('SelectionTool can handle move of selection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
svg.svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300}));
|
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300}));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,7 +66,7 @@ describe('SelectionTool', () => {
|
|||||||
|
|
||||||
describe('SelectionTool can handle end of selection', () => {
|
describe('SelectionTool can handle end of selection', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
svg.svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 200, clientY: 200}));
|
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 200, clientY: 200}));
|
||||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 200, clientY: 200}));
|
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 200, clientY: 200}));
|
||||||
});
|
});
|
||||||
@ -83,7 +81,7 @@ describe('SelectionTool', () => {
|
|||||||
|
|
||||||
describe('SelectionTool can deselect after click outside', () => {
|
describe('SelectionTool can deselect after click outside', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 300, clientY: 300}));
|
svg.svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 300, clientY: 300}));
|
||||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300}));
|
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300}));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -95,7 +93,7 @@ describe('SelectionTool', () => {
|
|||||||
|
|
||||||
describe('SelectionTool can handle end of selection in reverse direction', () => {
|
describe('SelectionTool can handle end of selection in reverse direction', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 200, clientY: 200}));
|
svg.svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 200, clientY: 200}));
|
||||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 100, clientY: 100}));
|
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 100, clientY: 100}));
|
||||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 100, clientY: 100}));
|
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 100, clientY: 100}));
|
||||||
});
|
});
|
||||||
@ -108,13 +106,11 @@ describe('SelectionTool', () => {
|
|||||||
describe('SelectionTool can be deactivated', () => {
|
describe('SelectionTool can be deactivated', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
tool.deactivate();
|
tool.deactivate();
|
||||||
svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
svg.svg.node().dispatchEvent(new MouseEvent('mousedown', {clientX: 100, clientY: 100}));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('path should be still hiden', () => {
|
it('path should be still hiden', () => {
|
||||||
expect(path_selection.attr('visibility')).toEqual('hidden');
|
expect(path_selection.attr('visibility')).toEqual('hidden');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import {DrawingLine} from "../models/drawing-line";
|
import { line } from "d3-shape";
|
||||||
import {SVGSelection} from "../models/types";
|
import { mouse } from "d3-selection";
|
||||||
import {Point} from "../models/point";
|
|
||||||
import {line} from "d3-shape";
|
import { DrawingLine } from "../models/drawing-line";
|
||||||
import {mouse} from "d3-selection";
|
import { SVGSelection } from "../models/types";
|
||||||
import {Context} from "../models/context";
|
import { Point } from "../models/point";
|
||||||
|
import { Context } from "../models/context";
|
||||||
|
|
||||||
|
|
||||||
export class DrawingLineWidget {
|
export class DrawingLineWidget {
|
||||||
private drawingLine: DrawingLine = new DrawingLine();
|
private drawingLine: DrawingLine = new DrawingLine();
|
@ -1,14 +1,20 @@
|
|||||||
import {Widget} from "./widget";
|
import {Widget} from "./widget";
|
||||||
import {Drawing} from "../models/drawing";
|
import {Drawing} from "../models/drawing";
|
||||||
import {SVGSelection} from "../models/types";
|
import {SVGSelection} from "../models/types";
|
||||||
|
import {Layer} from "../models/layer";
|
||||||
|
|
||||||
|
|
||||||
export class DrawingsWidget implements Widget {
|
export class DrawingsWidget implements Widget {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
public draw(view: SVGSelection, drawings: Drawing[]) {
|
public draw(view: SVGSelection, drawings?: Drawing[]) {
|
||||||
const drawing = view.selectAll<SVGGElement, Drawing>('g.drawing')
|
const drawing = view
|
||||||
.data(drawings);
|
.selectAll<SVGGElement, Drawing>('g.drawing')
|
||||||
|
.data((l: Layer) => {
|
||||||
|
return l.drawings;
|
||||||
|
}, (d: Drawing) => {
|
||||||
|
return d.drawing_id;
|
||||||
|
});
|
||||||
|
|
||||||
const drawing_enter = drawing.enter()
|
const drawing_enter = drawing.enter()
|
||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
@ -53,8 +59,4 @@ export class DrawingsWidget implements Widget {
|
|||||||
|
|
||||||
drawing.exit().remove();
|
drawing.exit().remove();
|
||||||
}
|
}
|
||||||
|
|
||||||
private appendSVG(svg: string) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import {Widget} from "./widget";
|
|
||||||
import {SVGSelection} from "../models/types";
|
|
||||||
|
|
||||||
import { line } from "d3-shape";
|
import { line } from "d3-shape";
|
||||||
import {Link} from "../models/link";
|
|
||||||
|
import { Widget } from "./widget";
|
||||||
|
import { SVGSelection } from "../models/types";
|
||||||
|
import { Link } from "../models/link";
|
||||||
|
|
||||||
|
|
||||||
export class EthernetLinkWidget implements Widget {
|
export class EthernetLinkWidget implements Widget {
|
||||||
|
|
@ -1,15 +1,18 @@
|
|||||||
import { Context } from "../models/context";
|
import { Context } from "../models/context";
|
||||||
import { Node } from "../models/node";
|
import { Node } from "../models/node";
|
||||||
import { Link } from "../models/link";
|
import { Link } from "../models/link";
|
||||||
import { NodesWidget } from "./nodes.widget";
|
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.widget";
|
import { LinksWidget } from "./links";
|
||||||
import { Drawing } from "../models/drawing";
|
import { Drawing } from "../models/drawing";
|
||||||
import { DrawingsWidget } from "./drawings.widget";
|
import { DrawingsWidget } from "./drawings";
|
||||||
import { DrawingLineWidget } from "./drawing-line.widget";
|
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";
|
||||||
|
import { LayersWidget } from "./layers";
|
||||||
|
import { LayersManager } from "../managers/layers-manager";
|
||||||
|
|
||||||
|
|
||||||
export class GraphLayout implements Widget {
|
export class GraphLayout implements Widget {
|
||||||
private nodes: Node[] = [];
|
private nodes: Node[] = [];
|
||||||
@ -22,6 +25,7 @@ export class GraphLayout implements Widget {
|
|||||||
private drawingLineTool: DrawingLineWidget;
|
private drawingLineTool: DrawingLineWidget;
|
||||||
private selectionTool: SelectionTool;
|
private selectionTool: SelectionTool;
|
||||||
private movingTool: MovingTool;
|
private movingTool: MovingTool;
|
||||||
|
private layersWidget: LayersWidget;
|
||||||
|
|
||||||
private centerZeroZeroPoint = true;
|
private centerZeroZeroPoint = true;
|
||||||
|
|
||||||
@ -32,6 +36,7 @@ export class GraphLayout implements Widget {
|
|||||||
this.drawingLineTool = new DrawingLineWidget();
|
this.drawingLineTool = new DrawingLineWidget();
|
||||||
this.selectionTool = new SelectionTool();
|
this.selectionTool = new SelectionTool();
|
||||||
this.movingTool = new MovingTool();
|
this.movingTool = new MovingTool();
|
||||||
|
this.layersWidget = new LayersWidget();
|
||||||
}
|
}
|
||||||
|
|
||||||
public setNodes(nodes: Node[]) {
|
public setNodes(nodes: Node[]) {
|
||||||
@ -54,6 +59,10 @@ export class GraphLayout implements Widget {
|
|||||||
return this.linksWidget;
|
return this.linksWidget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getDrawingsWidget() {
|
||||||
|
return this.drawingsWidget;
|
||||||
|
}
|
||||||
|
|
||||||
public getDrawingLineTool() {
|
public getDrawingLineTool() {
|
||||||
return this.drawingLineTool;
|
return this.drawingLineTool;
|
||||||
}
|
}
|
||||||
@ -89,9 +98,13 @@ export class GraphLayout implements Widget {
|
|||||||
(ctx: Context) => `translate(${ctx.getSize().width / 2}, ${ctx.getSize().height / 2})`);
|
(ctx: Context) => `translate(${ctx.getSize().width / 2}, ${ctx.getSize().height / 2})`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.linksWidget.draw(canvas, this.links);
|
const layersManager = new LayersManager();
|
||||||
this.nodesWidget.draw(canvas, this.nodes);
|
layersManager.setNodes(this.nodes);
|
||||||
this.drawingsWidget.draw(canvas, this.drawings);
|
layersManager.setDrawings(this.drawings);
|
||||||
|
layersManager.setLinks(this.links);
|
||||||
|
|
||||||
|
this.layersWidget.graphLayout = this;
|
||||||
|
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);
|
65
src/app/cartography/shared/widgets/layers.spec.ts
Normal file
65
src/app/cartography/shared/widgets/layers.spec.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { instance, mock, when } from "ts-mockito";
|
||||||
|
|
||||||
|
import { TestSVGCanvas } from "../../testing";
|
||||||
|
import { LayersWidget } from "./layers";
|
||||||
|
import { Layer } from "../models/layer";
|
||||||
|
import { LinksWidget } from "./links";
|
||||||
|
import { NodesWidget } from "./nodes";
|
||||||
|
import { DrawingsWidget } from "./drawings";
|
||||||
|
import { GraphLayout } from "./graph-layout";
|
||||||
|
|
||||||
|
|
||||||
|
describe('LayersWidget', () => {
|
||||||
|
let svg: TestSVGCanvas;
|
||||||
|
let widget: LayersWidget;
|
||||||
|
let mockedGraphLayout: GraphLayout;
|
||||||
|
let mockedLinksWidget: LinksWidget;
|
||||||
|
let mockedNodesWidget: NodesWidget;
|
||||||
|
let mockedDrawingsWidget: DrawingsWidget;
|
||||||
|
let layers: Layer[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
svg = new TestSVGCanvas();
|
||||||
|
widget = new LayersWidget();
|
||||||
|
mockedGraphLayout = mock(GraphLayout);
|
||||||
|
mockedLinksWidget = mock(LinksWidget);
|
||||||
|
mockedNodesWidget = mock(NodesWidget);
|
||||||
|
mockedDrawingsWidget = mock(DrawingsWidget);
|
||||||
|
when(mockedGraphLayout.getLinksWidget()).thenReturn(instance(mockedLinksWidget));
|
||||||
|
when(mockedGraphLayout.getNodesWidget()).thenReturn(instance(mockedNodesWidget));
|
||||||
|
when(mockedGraphLayout.getDrawingsWidget()).thenReturn(instance(mockedDrawingsWidget));
|
||||||
|
|
||||||
|
widget.graphLayout = instance(mockedGraphLayout);
|
||||||
|
|
||||||
|
const layer_1 = new Layer();
|
||||||
|
layer_1.index = 1;
|
||||||
|
const layer_2 = new Layer();
|
||||||
|
layer_2.index = 2;
|
||||||
|
layers = [layer_1, layer_2];
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
svg.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should draw layers', () => {
|
||||||
|
widget.draw(svg.canvas, layers);
|
||||||
|
|
||||||
|
const drew = svg.canvas.selectAll<SVGGElement, Layer>('g.layer');
|
||||||
|
expect(drew.size()).toEqual(2);
|
||||||
|
expect(drew.nodes()[0].getAttribute('data-index')).toEqual('1');
|
||||||
|
expect(drew.nodes()[1].getAttribute('data-index')).toEqual('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should redraw on layers change', () => {
|
||||||
|
widget.draw(svg.canvas, layers);
|
||||||
|
layers[0].index = 3;
|
||||||
|
widget.draw(svg.canvas, layers);
|
||||||
|
|
||||||
|
const drew = svg.canvas.selectAll<SVGGElement, Layer>('g.layer');
|
||||||
|
expect(drew.size()).toEqual(2);
|
||||||
|
expect(drew.nodes()[0].getAttribute('data-index')).toEqual('3');
|
||||||
|
expect(drew.nodes()[1].getAttribute('data-index')).toEqual('2');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
45
src/app/cartography/shared/widgets/layers.ts
Normal file
45
src/app/cartography/shared/widgets/layers.ts
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import { Widget } from "./widget";
|
||||||
|
import { SVGSelection } from "../models/types";
|
||||||
|
import { GraphLayout } from "./graph-layout";
|
||||||
|
import { Layer } from "../models/layer";
|
||||||
|
|
||||||
|
|
||||||
|
export class LayersWidget implements Widget {
|
||||||
|
public graphLayout: GraphLayout;
|
||||||
|
|
||||||
|
public draw(view: SVGSelection, layers: Layer[]) {
|
||||||
|
|
||||||
|
const layers_selection = view
|
||||||
|
.selectAll<SVGGElement, Layer>('g.layer')
|
||||||
|
.data(layers, (layer: Layer) => {
|
||||||
|
return layer.index.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
const layers_enter = layers_selection
|
||||||
|
.enter()
|
||||||
|
.append<SVGGElement>('g')
|
||||||
|
.attr('class', 'layer')
|
||||||
|
|
||||||
|
const merge = layers_selection.merge(layers_enter);
|
||||||
|
|
||||||
|
merge
|
||||||
|
.attr('data-index', (layer: Layer) => layer.index);
|
||||||
|
|
||||||
|
layers_selection
|
||||||
|
.exit()
|
||||||
|
.remove();
|
||||||
|
|
||||||
|
this.graphLayout
|
||||||
|
.getLinksWidget()
|
||||||
|
.draw(merge);
|
||||||
|
|
||||||
|
this.graphLayout
|
||||||
|
.getNodesWidget()
|
||||||
|
.draw(merge);
|
||||||
|
|
||||||
|
this.graphLayout
|
||||||
|
.getDrawingsWidget()
|
||||||
|
.draw(merge);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
74
src/app/cartography/shared/widgets/links.spec.ts
Normal file
74
src/app/cartography/shared/widgets/links.spec.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Selection } from "d3-selection";
|
||||||
|
|
||||||
|
import { TestSVGCanvas } from "../../testing";
|
||||||
|
import { LayersWidget } from "./layers";
|
||||||
|
import { Layer } from "../models/layer";
|
||||||
|
import { LinksWidget } from "./links";
|
||||||
|
import { Node } from "../models/node";
|
||||||
|
import { Link } from "../models/link";
|
||||||
|
|
||||||
|
|
||||||
|
describe('LinksWidget', () => {
|
||||||
|
let svg: TestSVGCanvas;
|
||||||
|
let widget: LinksWidget;
|
||||||
|
let layersWidget: LayersWidget;
|
||||||
|
let layersEnter: Selection<SVGGElement, Layer, SVGGElement, any>;
|
||||||
|
let layer: Layer;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
svg = new TestSVGCanvas();
|
||||||
|
widget = new LinksWidget();
|
||||||
|
|
||||||
|
const node_1 = new Node();
|
||||||
|
node_1.node_id = "1";
|
||||||
|
node_1.x = 10;
|
||||||
|
node_1.y = 10;
|
||||||
|
|
||||||
|
const node_2 = new Node();
|
||||||
|
node_2.node_id = "2";
|
||||||
|
node_2.x = 100;
|
||||||
|
node_2.y = 100;
|
||||||
|
|
||||||
|
const link_1 = new Link();
|
||||||
|
link_1.link_id = "link1";
|
||||||
|
link_1.source = node_1;
|
||||||
|
link_1.target = node_2;
|
||||||
|
link_1.link_type = "ethernet";
|
||||||
|
|
||||||
|
layer = new Layer();
|
||||||
|
layer.index = 1;
|
||||||
|
|
||||||
|
layer.links = [link_1];
|
||||||
|
|
||||||
|
const layers = [layer];
|
||||||
|
|
||||||
|
const layersSelection = svg.canvas
|
||||||
|
.selectAll<SVGGElement, Layer>('g.layer')
|
||||||
|
.data(layers);
|
||||||
|
|
||||||
|
layersEnter = layersSelection
|
||||||
|
.enter()
|
||||||
|
.append<SVGGElement>('g')
|
||||||
|
.attr('class', 'layer');
|
||||||
|
|
||||||
|
layersSelection
|
||||||
|
.exit()
|
||||||
|
.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
svg.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should draw links', () => {
|
||||||
|
widget.draw(layersEnter);
|
||||||
|
|
||||||
|
const drew = svg.canvas.selectAll<SVGGElement, Link>('g.link');
|
||||||
|
const linkNode = drew.nodes()[0];
|
||||||
|
expect(linkNode.getAttribute('link_id')).toEqual('link1');
|
||||||
|
expect(linkNode.getAttribute('map-source')).toEqual('1');
|
||||||
|
expect(linkNode.getAttribute('map-target')).toEqual('2');
|
||||||
|
expect(linkNode.getAttribute('transform')).toEqual('translate (0, 0)');
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -1,12 +1,13 @@
|
|||||||
import {BaseType, select, Selection} from "d3-selection";
|
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 { Link } from "../models/link";
|
||||||
import { LinkStatus } from "../models/link-status";
|
import { LinkStatus } from "../models/link-status";
|
||||||
import { MultiLinkCalculatorHelper } from "../../map/helpers/multi-link-calculator-helper";
|
import { MultiLinkCalculatorHelper } from "../../map/helpers/multi-link-calculator-helper";
|
||||||
import {SerialLinkWidget} from "./serial-link.widget";
|
import { SerialLinkWidget } from "./serial-link";
|
||||||
import {EthernetLinkWidget} from "./ethernet-link.widget";
|
import { EthernetLinkWidget } from "./ethernet-link";
|
||||||
|
import { Layer } from "../models/layer";
|
||||||
|
|
||||||
|
|
||||||
export class LinksWidget implements Widget {
|
export class LinksWidget implements Widget {
|
||||||
@ -96,29 +97,28 @@ export class LinksWidget implements Widget {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(view: SVGSelection, links: Link[]) {
|
public draw(view: SVGSelection, links?: Link[]) {
|
||||||
const self = this;
|
const link = view
|
||||||
|
|
||||||
this.multiLinkCalculatorHelper.assignDataToLinks(links);
|
|
||||||
|
|
||||||
const linksLayer = view.selectAll("g.links").data([{}]);
|
|
||||||
linksLayer
|
|
||||||
.enter()
|
|
||||||
.append<SVGGElement>('g')
|
|
||||||
.attr("class", "links");
|
|
||||||
|
|
||||||
const link = linksLayer
|
|
||||||
.selectAll<SVGGElement, Link>("g.link")
|
.selectAll<SVGGElement, Link>("g.link")
|
||||||
.data(links.filter((l: Link) => {
|
.data((layer: Layer) => {
|
||||||
return l.target && l.source;
|
if (layer.links) {
|
||||||
}));
|
const layer_links = layer.links.filter((l: Link) => {
|
||||||
|
return l.target && l.source;
|
||||||
|
});
|
||||||
|
this.multiLinkCalculatorHelper.assignDataToLinks(layer_links);
|
||||||
|
return layer_links;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}, (l: Link) => {
|
||||||
|
return l.link_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: Link) => l.link_id)
|
||||||
.attr('map-source', (l: Link) => l.source.node_id)
|
.attr('map-source', (l: Link) => l.source.node_id)
|
||||||
.attr('map-target', (l: Link) => l.target.node_id)
|
.attr('map-target', (l: Link) => l.target.node_id);
|
||||||
|
|
||||||
const merge = link.merge(link_enter);
|
const merge = link.merge(link_enter);
|
||||||
|
|
@ -1,9 +1,11 @@
|
|||||||
|
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 { Node } from "../models/node";
|
||||||
import { SVGSelection } from "../models/types";
|
import { SVGSelection } from "../models/types";
|
||||||
import {event, select} from "d3-selection";
|
import { Symbol } from "../models/symbol";
|
||||||
import {D3DragEvent, drag} from "d3-drag";
|
import { Layer } from "../models/layer";
|
||||||
import {Symbol} from "../models/symbol";
|
|
||||||
|
|
||||||
|
|
||||||
export class NodesWidget implements Widget {
|
export class NodesWidget implements Widget {
|
||||||
@ -14,9 +16,11 @@ export class NodesWidget implements Widget {
|
|||||||
private onNodeDraggedCallback: (event: any, node: Node) => void;
|
private onNodeDraggedCallback: (event: any, node: Node) => void;
|
||||||
private onNodeDraggingCallbacks: ((event: any, node: Node) => void)[] = [];
|
private onNodeDraggingCallbacks: ((event: any, node: Node) => void)[] = [];
|
||||||
|
|
||||||
private symbols: Symbol[] = [];
|
private symbols: Symbol[];
|
||||||
|
|
||||||
constructor() {}
|
constructor() {
|
||||||
|
this.symbols = [];
|
||||||
|
}
|
||||||
|
|
||||||
public setOnContextMenuCallback(onContextMenuCallback: (event: any, node: Node) => void) {
|
public setOnContextMenuCallback(onContextMenuCallback: (event: any, node: Node) => void) {
|
||||||
this.onContextMenuCallback = onContextMenuCallback;
|
this.onContextMenuCallback = onContextMenuCallback;
|
||||||
@ -78,45 +82,32 @@ export class NodesWidget implements Widget {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public draw(view: SVGSelection, nodes: Node[]) {
|
public draw(view: SVGSelection, nodes?: Node[]) {
|
||||||
const self = this;
|
const self = this;
|
||||||
|
|
||||||
const node = view
|
let nodes_selection: Selection<SVGGElement, Node, any, any> = view
|
||||||
.selectAll<SVGGElement, any>('g.node')
|
.selectAll<SVGGElement, Node>('g.node');
|
||||||
.data(nodes, (n: Node) => {
|
|
||||||
return n.node_id;
|
|
||||||
});
|
|
||||||
|
|
||||||
const node_enter = 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()
|
.enter()
|
||||||
.append<SVGGElement>('g')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'node');
|
.attr('class', 'node');
|
||||||
|
|
||||||
|
// add image to node
|
||||||
node_enter
|
node_enter
|
||||||
.append<SVGImageElement>('image')
|
.append<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) => -n.width / 2.)
|
|
||||||
.attr('y', (n: Node) => -n.height / 2.)
|
|
||||||
.on('mouseover', function (this, n: Node) {
|
|
||||||
select(this).attr("class", "over");
|
|
||||||
})
|
|
||||||
.on('mouseout', function (this, n: Node) {
|
|
||||||
select(this).attr("class", "");
|
|
||||||
});
|
|
||||||
|
|
||||||
// .attr('width', (n: Node) => n.width)
|
|
||||||
// .attr('height', (n: Node) => n.height);
|
|
||||||
|
|
||||||
|
|
||||||
|
// add label of node
|
||||||
node_enter
|
node_enter
|
||||||
.append<SVGTextElement>('text')
|
.append<SVGTextElement>('text')
|
||||||
.attr('class', 'label');
|
.attr('class', 'label');
|
||||||
@ -134,7 +125,7 @@ export class NodesWidget implements Widget {
|
|||||||
.attr('y', '0');
|
.attr('y', '0');
|
||||||
}
|
}
|
||||||
|
|
||||||
const node_merge = node
|
const node_merge = nodes_selection
|
||||||
.merge(node_enter)
|
.merge(node_enter)
|
||||||
.classed('selected', (n: Node) => n.is_selected)
|
.classed('selected', (n: Node) => n.is_selected)
|
||||||
.on("contextmenu", function (n: Node, i: number) {
|
.on("contextmenu", function (n: Node, i: number) {
|
||||||
@ -149,6 +140,27 @@ export class NodesWidget implements Widget {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 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) => -n.width / 2.)
|
||||||
|
.attr('y', (n: Node) => -n.height / 2.)
|
||||||
|
.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);
|
this.revise(node_merge);
|
||||||
|
|
||||||
@ -175,7 +187,7 @@ export class NodesWidget implements Widget {
|
|||||||
|
|
||||||
node_merge.call(dragging());
|
node_merge.call(dragging());
|
||||||
|
|
||||||
node
|
nodes_selection
|
||||||
.exit()
|
.exit()
|
||||||
.remove();
|
.remove();
|
||||||
}
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import {Widget} from "./widget";
|
|
||||||
import {SVGSelection} from "../models/types";
|
|
||||||
import {Link} from "../models/link";
|
|
||||||
import { path } from "d3-path";
|
import { path } from "d3-path";
|
||||||
|
|
||||||
|
import { Widget } from "./widget";
|
||||||
|
import { SVGSelection } from "../models/types";
|
||||||
|
import { Link } from "../models/link";
|
||||||
|
|
||||||
|
|
||||||
export class SerialLinkWidget implements Widget {
|
export class SerialLinkWidget implements Widget {
|
||||||
|
|
26
src/app/cartography/testing.ts
Normal file
26
src/app/cartography/testing.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { select, Selection } from "d3-selection";
|
||||||
|
|
||||||
|
|
||||||
|
export class TestSVGCanvas {
|
||||||
|
public svg: Selection<SVGSVGElement, any, HTMLElement, any>;
|
||||||
|
public canvas: Selection<SVGGElement, any, HTMLElement, any>;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public create() {
|
||||||
|
this.svg = select('body')
|
||||||
|
.append<SVGSVGElement>('svg')
|
||||||
|
.attr('width', 1000)
|
||||||
|
.attr('height', 1000);
|
||||||
|
|
||||||
|
this.canvas = this.svg
|
||||||
|
.append<SVGGElement>('g')
|
||||||
|
.attr('class', 'canvas');
|
||||||
|
}
|
||||||
|
|
||||||
|
public destroy() {
|
||||||
|
select('body').selectAll('svg').remove();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import {Component, Inject, OnInit, ViewChild, ViewEncapsulation} from '@angular/core';
|
import {Component, Inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||||
|
|
||||||
import { Observable } from 'rxjs/Observable';
|
import { Observable } from 'rxjs/Observable';
|
||||||
@ -19,7 +19,7 @@ import { MapComponent } from "../cartography/map/map.component";
|
|||||||
import { ServerService } from "../shared/services/server.service";
|
import { ServerService } from "../shared/services/server.service";
|
||||||
import { ProjectService } from '../shared/services/project.service';
|
import { ProjectService } from '../shared/services/project.service';
|
||||||
import { Server } from "../shared/models/server";
|
import { Server } from "../shared/models/server";
|
||||||
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef} from "@angular/material";
|
import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from "@angular/material";
|
||||||
import { SnapshotService } from "../shared/services/snapshot.service";
|
import { SnapshotService } from "../shared/services/snapshot.service";
|
||||||
import { Snapshot } from "../shared/models/snapshot";
|
import { Snapshot } from "../shared/models/snapshot";
|
||||||
import { ProgressDialogService } from "../shared/progress-dialog/progress-dialog.service";
|
import { ProgressDialogService } from "../shared/progress-dialog/progress-dialog.service";
|
||||||
@ -36,9 +36,9 @@ import { ToasterService } from '../shared/services/toaster.service';
|
|||||||
import { NodesDataSource } from "../cartography/shared/datasources/nodes-datasource";
|
import { NodesDataSource } from "../cartography/shared/datasources/nodes-datasource";
|
||||||
import { LinksDataSource } from "../cartography/shared/datasources/links-datasource";
|
import { LinksDataSource } from "../cartography/shared/datasources/links-datasource";
|
||||||
import { ProjectWebServiceHandler } from "../shared/handlers/project-web-service-handler";
|
import { ProjectWebServiceHandler } from "../shared/handlers/project-web-service-handler";
|
||||||
import { Rectangle } from "../cartography/shared/models/rectangle";
|
|
||||||
import { SelectionManager } from "../cartography/shared/managers/selection-manager";
|
import { SelectionManager } from "../cartography/shared/managers/selection-manager";
|
||||||
import { InRectangleHelper } from "../cartography/map/helpers/in-rectangle-helper";
|
import { InRectangleHelper } from "../cartography/map/helpers/in-rectangle-helper";
|
||||||
|
import { DrawingsDataSource } from "../cartography/shared/datasources/drawings-datasource";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -81,6 +81,7 @@ export class ProjectMapComponent implements OnInit {
|
|||||||
private projectWebServiceHandler: ProjectWebServiceHandler,
|
private projectWebServiceHandler: ProjectWebServiceHandler,
|
||||||
protected nodesDataSource: NodesDataSource,
|
protected nodesDataSource: NodesDataSource,
|
||||||
protected linksDataSource: LinksDataSource,
|
protected linksDataSource: LinksDataSource,
|
||||||
|
protected drawingsDataSource: DrawingsDataSource
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,6 +115,13 @@ export class ProjectMapComponent implements OnInit {
|
|||||||
this.symbols = symbols;
|
this.symbols = symbols;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.drawingsDataSource.connect().subscribe((drawings: Drawing[]) => {
|
||||||
|
this.drawings = drawings;
|
||||||
|
if (this.mapChild) {
|
||||||
|
this.mapChild.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.nodesDataSource.connect().subscribe((nodes: Node[]) => {
|
this.nodesDataSource.connect().subscribe((nodes: Node[]) => {
|
||||||
this.nodes = nodes;
|
this.nodes = nodes;
|
||||||
if (this.mapChild) {
|
if (this.mapChild) {
|
||||||
@ -133,18 +141,18 @@ export class ProjectMapComponent implements OnInit {
|
|||||||
this.symbolService
|
this.symbolService
|
||||||
.load(this.server)
|
.load(this.server)
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
return this.projectService.drawings(this.server, project.project_id);
|
return this.projectService.nodes(this.server, project.project_id);
|
||||||
})
|
})
|
||||||
.flatMap((drawings: Drawing[]) => {
|
.flatMap((nodes: Node[]) => {
|
||||||
this.drawings = drawings;
|
this.nodesDataSource.set(nodes);
|
||||||
return this.projectService.links(this.server, project.project_id);
|
return this.projectService.links(this.server, project.project_id);
|
||||||
})
|
})
|
||||||
.flatMap((links: Link[]) => {
|
.flatMap((links: Link[]) => {
|
||||||
this.linksDataSource.set(links);
|
this.linksDataSource.set(links);
|
||||||
return this.projectService.nodes(this.server, project.project_id);
|
return this.projectService.drawings(this.server, project.project_id);
|
||||||
})
|
})
|
||||||
.subscribe((nodes: Node[]) => {
|
.subscribe((drawings: Drawing[]) => {
|
||||||
this.nodesDataSource.set(nodes);
|
this.drawingsDataSource.set(drawings);
|
||||||
|
|
||||||
this.setUpMapCallbacks(project);
|
this.setUpMapCallbacks(project);
|
||||||
this.setUpWS(project);
|
this.setUpWS(project);
|
||||||
|
@ -1,11 +1,9 @@
|
|||||||
import {Component, Inject, OnInit} from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
|
import { DataSource } from "@angular/cdk/collections";
|
||||||
|
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||||
|
|
||||||
import { Server } from "../shared/models/server";
|
import { Observable } from "rxjs/Observable";
|
||||||
import { ServerService } from "../shared/services/server.service";
|
import { BehaviorSubject } from "rxjs/BehaviorSubject";
|
||||||
import {DataSource} from "@angular/cdk/collections";
|
|
||||||
import {Observable} from "rxjs/Observable";
|
|
||||||
import {MatDialog, MatDialogRef, MAT_DIALOG_DATA} from '@angular/material';
|
|
||||||
import {BehaviorSubject} from "rxjs/BehaviorSubject";
|
|
||||||
|
|
||||||
import 'rxjs/add/operator/startWith';
|
import 'rxjs/add/operator/startWith';
|
||||||
import 'rxjs/add/observable/merge';
|
import 'rxjs/add/observable/merge';
|
||||||
@ -14,6 +12,9 @@ import 'rxjs/add/operator/debounceTime';
|
|||||||
import 'rxjs/add/operator/distinctUntilChanged';
|
import 'rxjs/add/operator/distinctUntilChanged';
|
||||||
import 'rxjs/add/observable/fromEvent';
|
import 'rxjs/add/observable/fromEvent';
|
||||||
|
|
||||||
|
import { Server } from "../shared/models/server";
|
||||||
|
import { ServerService } from "../shared/services/server.service";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-server-list',
|
selector: 'app-server-list',
|
||||||
@ -105,7 +106,7 @@ export class ServerDatabase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ServerDataSource extends DataSource<any> {
|
export class ServerDataSource extends DataSource<Server> {
|
||||||
constructor(private serverDatabase: ServerDatabase) {
|
constructor(private serverDatabase: ServerDatabase) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
import {ProjectWebServiceHandler, WebServiceMessage} from "./project-web-service-handler";
|
import { inject, TestBed } from "@angular/core/testing";
|
||||||
import {Subject} from "rxjs/Subject";
|
|
||||||
import {inject, TestBed} from "@angular/core/testing";
|
|
||||||
import {NodesDataSource} from "../../cartography/shared/datasources/nodes-datasource";
|
|
||||||
import {LinksDataSource} from "../../cartography/shared/datasources/links-datasource";
|
|
||||||
import {Node} from "../../cartography/shared/models/node";
|
|
||||||
import {Link} from "../../cartography/shared/models/link";
|
|
||||||
|
|
||||||
|
import { Subject } from "rxjs/Subject";
|
||||||
|
|
||||||
|
import { ProjectWebServiceHandler, WebServiceMessage } from "./project-web-service-handler";
|
||||||
|
import { NodesDataSource } from "../../cartography/shared/datasources/nodes-datasource";
|
||||||
|
import { LinksDataSource } from "../../cartography/shared/datasources/links-datasource";
|
||||||
|
import { DrawingsDataSource } from "../../cartography/shared/datasources/drawings-datasource";
|
||||||
|
import { Node } from "../../cartography/shared/models/node";
|
||||||
|
import { Link } from "../../cartography/shared/models/link";
|
||||||
|
import { Drawing } from "../../cartography/shared/models/drawing";
|
||||||
|
|
||||||
|
|
||||||
describe('ProjectWebServiceHandler', () => {
|
describe('ProjectWebServiceHandler', () => {
|
||||||
@ -13,7 +16,7 @@ describe('ProjectWebServiceHandler', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
providers: [ProjectWebServiceHandler, NodesDataSource, LinksDataSource]
|
providers: [ProjectWebServiceHandler, NodesDataSource, LinksDataSource, DrawingsDataSource]
|
||||||
});
|
});
|
||||||
|
|
||||||
ws = new Subject<WebServiceMessage>();
|
ws = new Subject<WebServiceMessage>();
|
||||||
@ -120,4 +123,53 @@ describe('ProjectWebServiceHandler', () => {
|
|||||||
expect(service).toBeTruthy();
|
expect(service).toBeTruthy();
|
||||||
expect(linksDataSource.remove).toHaveBeenCalledWith(message.event);
|
expect(linksDataSource.remove).toHaveBeenCalledWith(message.event);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
it('drawing should be added', inject([ProjectWebServiceHandler, DrawingsDataSource],
|
||||||
|
(service: ProjectWebServiceHandler, drawingsDataSource: DrawingsDataSource) => {
|
||||||
|
spyOn(drawingsDataSource, 'add');
|
||||||
|
|
||||||
|
service.connect(ws);
|
||||||
|
|
||||||
|
const message = new WebServiceMessage();
|
||||||
|
message.action = "drawing.created";
|
||||||
|
message.event = new Drawing();
|
||||||
|
|
||||||
|
ws.next(message);
|
||||||
|
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
expect(drawingsDataSource.add).toHaveBeenCalledWith(message.event);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('drawing should be updated', inject([ProjectWebServiceHandler, DrawingsDataSource],
|
||||||
|
(service: ProjectWebServiceHandler, drawingsDataSource: DrawingsDataSource) => {
|
||||||
|
spyOn(drawingsDataSource, 'update');
|
||||||
|
|
||||||
|
service.connect(ws);
|
||||||
|
|
||||||
|
const message = new WebServiceMessage();
|
||||||
|
message.action = "drawing.updated";
|
||||||
|
message.event = new Drawing();
|
||||||
|
|
||||||
|
ws.next(message);
|
||||||
|
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
expect(drawingsDataSource.update).toHaveBeenCalledWith(message.event);
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
it('drawing should be removed', inject([ProjectWebServiceHandler, DrawingsDataSource],
|
||||||
|
(service: ProjectWebServiceHandler, drawingsDataSource: DrawingsDataSource) => {
|
||||||
|
spyOn(drawingsDataSource, 'remove');
|
||||||
|
|
||||||
|
service.connect(ws);
|
||||||
|
|
||||||
|
const message = new WebServiceMessage();
|
||||||
|
message.action = "drawing.deleted";
|
||||||
|
message.event = new Drawing();
|
||||||
|
|
||||||
|
ws.next(message);
|
||||||
|
|
||||||
|
expect(service).toBeTruthy();
|
||||||
|
expect(drawingsDataSource.remove).toHaveBeenCalledWith(message.event);
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import { Injectable } from "@angular/core";
|
||||||
import {NodesDataSource} from "../../cartography/shared/datasources/nodes-datasource";
|
import { Subject } from "rxjs/Subject";
|
||||||
import {LinksDataSource} from "../../cartography/shared/datasources/links-datasource";
|
|
||||||
import {Subject} from "rxjs/Subject";
|
import { NodesDataSource } from "../../cartography/shared/datasources/nodes-datasource";
|
||||||
import {Link} from "../../cartography/shared/models/link";
|
import { LinksDataSource } from "../../cartography/shared/datasources/links-datasource";
|
||||||
import {Node} from "../../cartography/shared/models/node";
|
import { DrawingsDataSource } from "../../cartography/shared/datasources/drawings-datasource";
|
||||||
|
import { Link } from "../../cartography/shared/models/link";
|
||||||
|
import { Node } from "../../cartography/shared/models/node";
|
||||||
|
import { Drawing } from "../../cartography/shared/models/drawing";
|
||||||
|
|
||||||
|
|
||||||
export class WebServiceMessage {
|
export class WebServiceMessage {
|
||||||
action: string;
|
action: string;
|
||||||
event: Node | Link;
|
event: Node | Link | Drawing;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ProjectWebServiceHandler {
|
export class ProjectWebServiceHandler {
|
||||||
constructor(private nodesDataSource: NodesDataSource,
|
constructor(private nodesDataSource: NodesDataSource,
|
||||||
private linksDataSource: LinksDataSource) {}
|
private linksDataSource: LinksDataSource,
|
||||||
|
private drawingsDataSource: DrawingsDataSource) {}
|
||||||
|
|
||||||
public connect(ws: Subject<WebServiceMessage>) {
|
public connect(ws: Subject<WebServiceMessage>) {
|
||||||
ws.subscribe((message: WebServiceMessage) => {
|
ws.subscribe((message: WebServiceMessage) => {
|
||||||
@ -36,6 +40,15 @@ export class ProjectWebServiceHandler {
|
|||||||
if (message.action === 'link.deleted') {
|
if (message.action === 'link.deleted') {
|
||||||
this.linksDataSource.remove(message.event as Link);
|
this.linksDataSource.remove(message.event as Link);
|
||||||
}
|
}
|
||||||
|
if (message.action === 'drawing.created') {
|
||||||
|
this.drawingsDataSource.add(message.event as Drawing);
|
||||||
|
}
|
||||||
|
if (message.action === 'drawing.updated') {
|
||||||
|
this.drawingsDataSource.update(message.event as Drawing);
|
||||||
|
}
|
||||||
|
if (message.action === 'drawing.deleted') {
|
||||||
|
this.drawingsDataSource.remove(message.event as Drawing);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="moveLayerDown()">
|
||||||
|
<mat-icon>keyboard_arrow_down</mat-icon>
|
||||||
|
<span>Move layer down</span>
|
||||||
|
</button>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MoveLayerDownActionComponent } from './move-layer-down-action.component';
|
||||||
|
|
||||||
|
describe('MoveLayerDownActionComponent', () => {
|
||||||
|
let component: MoveLayerDownActionComponent;
|
||||||
|
let fixture: ComponentFixture<MoveLayerDownActionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ MoveLayerDownActionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// beforeEach(() => {
|
||||||
|
// fixture = TestBed.createComponent(MoveLayerDownActionComponent);
|
||||||
|
// component = fixture.componentInstance;
|
||||||
|
// fixture.detectChanges();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should create', () => {
|
||||||
|
// expect(component).toBeTruthy();
|
||||||
|
// });
|
||||||
|
});
|
@ -0,0 +1,29 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { Node } from '../../../../cartography/shared/models/node';
|
||||||
|
import { NodesDataSource } from '../../../../cartography/shared/datasources/nodes-datasource';
|
||||||
|
import { NodeService } from '../../../services/node.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-move-layer-down-action',
|
||||||
|
templateUrl: './move-layer-down-action.component.html'
|
||||||
|
})
|
||||||
|
export class MoveLayerDownActionComponent implements OnInit {
|
||||||
|
@Input() server: Server;
|
||||||
|
@Input() node: Node;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private nodeService: NodeService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
moveLayerDown() {
|
||||||
|
this.node.z--;
|
||||||
|
this.nodesDataSource.update(this.node);
|
||||||
|
this.nodeService
|
||||||
|
.update(this.server, this.node)
|
||||||
|
.subscribe((node: Node) => {});
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,4 @@
|
|||||||
|
<button mat-menu-item (click)="moveLayerUp()">
|
||||||
|
<mat-icon>keyboard_arrow_up</mat-icon>
|
||||||
|
<span>Move layer up</span>
|
||||||
|
</button>
|
@ -0,0 +1,25 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MoveLayerUpActionComponent } from './move-layer-up-action.component';
|
||||||
|
|
||||||
|
describe('MoveLayerUpActionComponent', () => {
|
||||||
|
let component: MoveLayerUpActionComponent;
|
||||||
|
let fixture: ComponentFixture<MoveLayerUpActionComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
declarations: [ MoveLayerUpActionComponent ]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// beforeEach(() => {
|
||||||
|
// fixture = TestBed.createComponent(MoveLayerUpActionComponent);
|
||||||
|
// component = fixture.componentInstance;
|
||||||
|
// fixture.detectChanges();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// it('should create', () => {
|
||||||
|
// expect(component).toBeTruthy();
|
||||||
|
// });
|
||||||
|
});
|
@ -0,0 +1,28 @@
|
|||||||
|
import { Component, OnInit, Input } from '@angular/core';
|
||||||
|
import { Server } from '../../../models/server';
|
||||||
|
import { Node } from '../../../../cartography/shared/models/node';
|
||||||
|
import { NodesDataSource } from '../../../../cartography/shared/datasources/nodes-datasource';
|
||||||
|
import { NodeService } from '../../../services/node.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-move-layer-up-action',
|
||||||
|
templateUrl: './move-layer-up-action.component.html'
|
||||||
|
})
|
||||||
|
export class MoveLayerUpActionComponent implements OnInit {
|
||||||
|
@Input() server: Server;
|
||||||
|
@Input() node: Node;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private nodesDataSource: NodesDataSource,
|
||||||
|
private nodeService: NodeService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
ngOnInit() {}
|
||||||
|
|
||||||
|
moveLayerUp() {
|
||||||
|
this.node.z++;
|
||||||
|
this.nodeService
|
||||||
|
.update(this.server, this.node)
|
||||||
|
.subscribe((node: Node) => {});
|
||||||
|
}
|
||||||
|
}
|
@ -3,5 +3,7 @@
|
|||||||
<mat-menu #contextMenu="matMenu">
|
<mat-menu #contextMenu="matMenu">
|
||||||
<app-start-node-action [server]="server" [node]="node"></app-start-node-action>
|
<app-start-node-action [server]="server" [node]="node"></app-start-node-action>
|
||||||
<app-stop-node-action [server]="server" [node]="node"></app-stop-node-action>
|
<app-stop-node-action [server]="server" [node]="node"></app-stop-node-action>
|
||||||
|
<app-move-layer-up-action [server]="server" [node]="node"></app-move-layer-up-action>
|
||||||
|
<app-move-layer-down-action [server]="server" [node]="node"></app-move-layer-down-action>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
</div>
|
</div>
|
||||||
|
@ -6,6 +6,11 @@ import { Server } from '../models/server';
|
|||||||
import { HttpServer } from './http-server.service';
|
import { HttpServer } from './http-server.service';
|
||||||
|
|
||||||
|
|
||||||
|
class MyType {
|
||||||
|
id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
describe('HttpServer', () => {
|
describe('HttpServer', () => {
|
||||||
let httpClient: HttpClient;
|
let httpClient: HttpClient;
|
||||||
let httpTestingController: HttpTestingController;
|
let httpTestingController: HttpTestingController;
|
||||||
@ -44,6 +49,37 @@ describe('HttpServer', () => {
|
|||||||
expect(req.request.responseType).toEqual("json");
|
expect(req.request.responseType).toEqual("json");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should make GET query for get method and return instance of type', () => {
|
||||||
|
const testData: MyType = {id: 3};
|
||||||
|
|
||||||
|
service.get<MyType>(server, '/test').subscribe(data => {
|
||||||
|
expect(data instanceof MyType).toBeFalsy();
|
||||||
|
expect(data).toEqual(testData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = httpTestingController.expectOne('http://127.0.0.1:3080/v2/test');
|
||||||
|
expect(req.request.method).toEqual("GET");
|
||||||
|
expect(req.request.responseType).toEqual("json");
|
||||||
|
|
||||||
|
req.flush({id: 3});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('HttpClient should make GET query for get method and return instance of type', () => {
|
||||||
|
const testData: MyType = {id: 3};
|
||||||
|
|
||||||
|
httpClient.get<MyType>('http://localhost/test').subscribe(data => {
|
||||||
|
// when this condition is true, it would be great
|
||||||
|
expect(data instanceof MyType).toBeFalsy();
|
||||||
|
expect(data).toEqual(testData);
|
||||||
|
});
|
||||||
|
|
||||||
|
const req = httpTestingController.expectOne('http://localhost/test');
|
||||||
|
expect(req.request.method).toEqual("GET");
|
||||||
|
expect(req.request.responseType).toEqual("json");
|
||||||
|
|
||||||
|
req.flush(testData);
|
||||||
|
});
|
||||||
|
|
||||||
it('should make GET query for getText method', () => {
|
it('should make GET query for getText method', () => {
|
||||||
service.getText(server, '/test').subscribe();
|
service.getText(server, '/test').subscribe();
|
||||||
|
|
||||||
|
@ -41,4 +41,14 @@ export class NodeService {
|
|||||||
'y': y
|
'y': y
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
update(server: Server, node: Node): Observable<Node> {
|
||||||
|
return this.httpServer
|
||||||
|
.put<Node>(server, `/projects/${node.project_id}/nodes/${node.node_id}`, {
|
||||||
|
'x': node.x,
|
||||||
|
'y': node.y,
|
||||||
|
'z': node.z
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -8565,6 +8565,12 @@ truncate-utf8-bytes@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
utf8-byte-length "^1.0.1"
|
utf8-byte-length "^1.0.1"
|
||||||
|
|
||||||
|
ts-mockito@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ts-mockito/-/ts-mockito-2.3.0.tgz#c1836f04ec037cad82cfd7b3b66d52fc75adfcc7"
|
||||||
|
dependencies:
|
||||||
|
lodash "^4.17.5"
|
||||||
|
|
||||||
ts-node@~5.0.0:
|
ts-node@~5.0.0:
|
||||||
version "5.0.0"
|
version "5.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.0.tgz#9aa573889ad7949411f972981c209e064705e36f"
|
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-5.0.0.tgz#9aa573889ad7949411f972981c209e064705e36f"
|
||||||
|
Loading…
Reference in New Issue
Block a user