diff --git a/src/app/cartography/shared/managers/selection-manager.spec.ts b/src/app/cartography/shared/managers/selection-manager.spec.ts new file mode 100644 index 00000000..59cb08bb --- /dev/null +++ b/src/app/cartography/shared/managers/selection-manager.spec.ts @@ -0,0 +1,57 @@ +import { Subject} from "rxjs/Subject"; + +import { Node } from "../models/node"; +import { Rectangle } from "../models/rectangle"; +import { SelectionManager } from "./selection-manager"; +import { NodesDataSource } from "../datasources/nodes-datasource"; +import { LinksDataSource } from "../datasources/links-datasource"; +import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper"; + + +describe('SelectionManager', () => { + let manager: SelectionManager; + let selectedRectangleSubject: Subject; + let nodesDataSource: NodesDataSource; + + beforeEach(() => { + const linksDataSourec = new LinksDataSource(); + const inRectangleHelper = new InRectangleHelper(); + + selectedRectangleSubject = new Subject(); + + nodesDataSource = new NodesDataSource(); + + manager = new SelectionManager(nodesDataSource, linksDataSourec, inRectangleHelper); + manager.subscribe(selectedRectangleSubject); + + const node_1 = new Node(); + node_1.name = "Node 1"; + node_1.x = 150; + node_1.y = 150; + + nodesDataSource.add(node_1); + + const node_2 = new Node(); + node_2.name = "Node 2"; + node_2.x = 300; + node_2.y = 300; + nodesDataSource.add(node_2); + + }); + + 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); + }); + +}); diff --git a/src/app/cartography/shared/managers/selection-manager.ts b/src/app/cartography/shared/managers/selection-manager.ts index 01a8c6ba..f95652ee 100644 --- a/src/app/cartography/shared/managers/selection-manager.ts +++ b/src/app/cartography/shared/managers/selection-manager.ts @@ -1,10 +1,16 @@ -import {Node} from "../models/node"; -import {NodesDataSource} from "../datasources/nodes-datasource"; -import {LinksDataSource} from "../datasources/links-datasource"; -import {Injectable} from "@angular/core"; -import {InRectangleHelper} from "../../map/helpers/in-rectangle-helper"; -import {Rectangle} from "../models/rectangle"; -import {Link} from "../models/link"; +import { Injectable } from "@angular/core"; + +import { Subject } from "rxjs/Subject"; +import { Subscription } from "rxjs/Subscription"; + +import { NodesDataSource } from "../datasources/nodes-datasource"; +import { LinksDataSource } from "../datasources/links-datasource"; +import { Node } from "../models/node"; +import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper"; +import { Rectangle } from "../models/rectangle"; +import { Link } from "../models/link"; +import { DataSource } from "../datasources/datasource"; + export interface Selectable { x: number; @@ -12,40 +18,44 @@ export interface Selectable { is_selected: boolean; } - @Injectable() export class SelectionManager { - private selectedItems: Selectable[] = []; + private selectedNodes: Node[] = []; + private selectedLinks: Link[] = []; + private subscription: Subscription; constructor(private nodesDataSource: NodesDataSource, private linksDataSource: LinksDataSource, private inRectangleHelper: InRectangleHelper) {} - public setSelectedItemsInRectangle(rectangle: Rectangle) { - const self = this; - const nodes: Node[] = []; - this.nodesDataSource.getItems().forEach((node: Node) => { - const is_selected = self.inRectangleHelper.inRectangle(node, rectangle); - if (node.is_selected !== is_selected) { - node.is_selected = is_selected; - self.nodesDataSource.update(node); - if (is_selected) { - nodes.push(node); - } - } - }); - - const links: Link[] = []; - this.linksDataSource.getItems().forEach((link: Link) => { - const is_selected = self.inRectangleHelper.inRectangle(link, rectangle); - if (link.is_selected !== is_selected) { - link.is_selected = is_selected; - self.linksDataSource.update(link); - if (is_selected) { - links.push(link); - } - } + public subscribe(subject: Subject) { + this.subscription = subject.subscribe((rectangle: Rectangle) => { + this.selectedNodes = this.getSelectedItemsInRectangle(this.nodesDataSource, rectangle); + this.selectedLinks = this.getSelectedItemsInRectangle(this.linksDataSource, rectangle); }); } + + public getSelectedNodes() { + return this.selectedNodes; + } + + public getSelectedLinks() { + return this.selectedLinks; + } + + private getSelectedItemsInRectangle(dataSource: DataSource, rectangle: Rectangle) { + const items: T[] = []; + dataSource.getItems().forEach((item: T) => { + const is_selected = this.inRectangleHelper.inRectangle(item, rectangle); + if (item.is_selected !== is_selected) { + item.is_selected = is_selected; + dataSource.update(item); + if (is_selected) { + items.push(item); + } + } + }); + return items; + } } diff --git a/src/app/cartography/shared/tools/selection-tool.spec.ts b/src/app/cartography/shared/tools/selection-tool.spec.ts index 37473880..50375d4b 100644 --- a/src/app/cartography/shared/tools/selection-tool.spec.ts +++ b/src/app/cartography/shared/tools/selection-tool.spec.ts @@ -1,17 +1,10 @@ -import {SelectionTool} from "./selection-tool"; -import {select} from "d3-selection"; -import {Context} from "../../../map/models/context"; -import {SVGSelection} from "../../../map/models/types"; -import {Node} from "../models/node"; +import { select } from "d3-selection"; +import { SelectionTool } from "./selection-tool"; +import { Context } from "../../../map/models/context"; +import { SVGSelection } from "../../../map/models/types"; +import { Rectangle } from "../models/rectangle"; -class OnSelectedListenerMock { - public constructor(public nodes: Node[] = []) {} - - public listen(nodes: Node[]) { - this.nodes = nodes; - } -} describe('SelectionTool', () => { let tool: SelectionTool; @@ -19,13 +12,13 @@ describe('SelectionTool', () => { let context: Context; let selection_line_tool: SVGSelection; let path_selection: SVGSelection; - let selected_nodes: Node[]; + let selected_rectangle: Rectangle; beforeEach(() => { tool = new SelectionTool(); - tool.selectedSubject.subscribe((nodes: Node[]) => { - selected_nodes = nodes; + tool.rectangleSelected.subscribe((rectangle: Rectangle) => { + selected_rectangle = rectangle; }); svg = select('body') @@ -33,25 +26,6 @@ describe('SelectionTool', () => { .attr('width', 1000) .attr('height', 1000); - const node_1 = new Node(); - node_1.name = "Node 1"; - node_1.x = 150; - node_1.y = 150; - - const node_2 = new Node(); - node_2.name = "Node 2"; - node_2.x = 300; - node_2.y = 300; - - svg.selectAll('g.node') - .data([node_1, node_2], (n: Node) => { - return n.node_id; - }) - .enter() - .append('g') - .attr('class', 'node selectable'); - - svg.append('g').attr('class', 'canvas'); context = new Context(); @@ -103,13 +77,8 @@ describe('SelectionTool', () => { expect(path_selection.attr('visibility')).toEqual('hidden'); }); - it('node should be selected', () => { - expect(svg.selectAll('.selected').size()).toEqual(1); - expect(svg.select('.selected').datum().name).toEqual("Node 1"); - }); - - it('selectedSubject should update nodes', () => { - expect(selected_nodes.length).toEqual(1); + it('rectangle should be selected', () => { + expect(selected_rectangle).toEqual(new Rectangle(95, 86, 100, 100)); }); describe('SelectionTool can deselect after click outside', () => { @@ -118,15 +87,10 @@ describe('SelectionTool', () => { window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300})); }); - it('should have no selection', () => { - expect(svg.selectAll('.selected').size()).toEqual(0); - }); - - it('selectedSubject should clear nodes', () => { - expect(selected_nodes.length).toEqual(0); + it('rectangle should be selected', () => { + expect(selected_rectangle).toEqual(new Rectangle(295, 286, 0, 0)); }); }); - }); describe('SelectionTool can handle end of selection in reverse direction', () => { @@ -136,8 +100,8 @@ describe('SelectionTool', () => { window.dispatchEvent(new MouseEvent('mouseup', {clientX: 100, clientY: 100})); }); - it('node should be selected', () => { - expect(svg.select('.selected').datum().name).toEqual("Node 1"); + it('rectangle should be selected', () => { + expect(selected_rectangle).toEqual(new Rectangle(95, 86, 100, 100)); }); }); diff --git a/src/app/cartography/shared/tools/selection-tool.ts b/src/app/cartography/shared/tools/selection-tool.ts index fb98bd24..8e05ff3d 100644 --- a/src/app/cartography/shared/tools/selection-tool.ts +++ b/src/app/cartography/shared/tools/selection-tool.ts @@ -3,20 +3,21 @@ import {mouse, select} from "d3-selection"; import {Context} from "../../../map/models/context"; import {Subject} from "rxjs/Subject"; import {Rectangle} from "../models/rectangle"; +import {Injectable} from "@angular/core"; +@Injectable() export class SelectionTool { static readonly SELECTABLE_CLASS = '.selectable'; - public rectangleSelected = new Subject(); + public rectangleSelected: Subject; private selection: SVGSelection; private path; private context: Context; - // public selectedSubject: Subject; public constructor() { - // this.selectedSubject = new Subject(); + this.rectangleSelected = new Subject(); } public connect(selection: SVGSelection, context: Context) { @@ -87,8 +88,7 @@ export class SelectionTool { private endSelection(start, end) { this.path.attr("visibility", "hidden"); - // const selected_items = this.getSelectedItems(start, end); - // this.selectedSubject.next(selected_items); + this.selectedEvent(start, end); } private selectedEvent(start, end) { @@ -99,27 +99,6 @@ export class SelectionTool { this.rectangleSelected.next(new Rectangle(x, y, width, height)); } - // private getSelectedItems(start, end): Selectable[] { - // const x = Math.min(start[0], end[0]); - // const y = Math.min(start[1], end[1]); - // const width = Math.abs(start[0] - end[0]); - // const height = Math.abs(start[1] - end[1]); - // const items: Selectable[] = []; - // - // this.selection - // .selectAll(SelectionTool.SELECTABLE_CLASS) - // .classed('selected', (item: Selectable) => { - // - // const in_rect = (x <= item.x && item.x < (x + width) && y <= item.y && item.y < (y + height)); - // if (in_rect) { - // items.push(item); - // } - // return in_rect; - // }); - // - // return items; - // } - private rect(x: number, y: number, w: number, h: number) { return "M" + [x, y] + " l" + [w, 0] + " l" + [0, h] + " l" + [-w, 0] + "z"; } diff --git a/src/app/project-map/project-map.component.ts b/src/app/project-map/project-map.component.ts index a390c05e..5c05e253 100644 --- a/src/app/project-map/project-map.component.ts +++ b/src/app/project-map/project-map.component.ts @@ -182,9 +182,7 @@ export class ProjectMapComponent implements OnInit { const selectionManager = new SelectionManager(this.nodesDataSource, this.linksDataSource, new InRectangleHelper()); - this.mapChild.graphLayout.getSelectionTool().rectangleSelected.subscribe((rectangle: Rectangle) => { - selectionManager.setSelectedItemsInRectangle(rectangle); - }); + selectionManager.subscribe(this.mapChild.graphLayout.getSelectionTool().rectangleSelected); } onNodeCreation(appliance: Appliance) {