mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-06-02 07:20:42 +00:00
Select links and nodes
This commit is contained in:
parent
0cc1cd8db1
commit
6dc7f870b2
@ -57,6 +57,8 @@ import {ProjectWebServiceHandler} from "./shared/handlers/project-web-service-ha
|
|||||||
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 {InRectangleHelper} from "./cartography/map/helpers/in-rectangle-helper";
|
||||||
|
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
@ -116,7 +118,9 @@ import {SymbolsDataSource} from "./cartography/shared/datasources/symbols-dataso
|
|||||||
ProjectWebServiceHandler,
|
ProjectWebServiceHandler,
|
||||||
LinksDataSource,
|
LinksDataSource,
|
||||||
NodesDataSource,
|
NodesDataSource,
|
||||||
SymbolsDataSource
|
SymbolsDataSource,
|
||||||
|
SelectionManager,
|
||||||
|
InRectangleHelper
|
||||||
],
|
],
|
||||||
entryComponents: [
|
entryComponents: [
|
||||||
AddServerDialogComponent,
|
AddServerDialogComponent,
|
||||||
|
11
src/app/cartography/map/helpers/in-rectangle-helper.ts
Normal file
11
src/app/cartography/map/helpers/in-rectangle-helper.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import {Selectable} from "../../shared/managers/selection-manager";
|
||||||
|
import {Rectangle} from "../../shared/models/rectangle";
|
||||||
|
import {Injectable} from "@angular/core";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class InRectangleHelper {
|
||||||
|
public inRectangle(item: Selectable, rectangle: Rectangle): boolean {
|
||||||
|
return (rectangle.x <= item.x && item.x < (rectangle.x + rectangle.width)
|
||||||
|
&& rectangle.y <= item.y && item.y < (rectangle.y + rectangle.height));
|
||||||
|
}
|
||||||
|
}
|
@ -128,8 +128,6 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
.attr('height', this.graphContext.getSize().height);
|
.attr('height', this.graphContext.getSize().height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
this.graphLayout.setNodes(this.nodes);
|
this.graphLayout.setNodes(this.nodes);
|
||||||
this.graphLayout.setLinks(this.links);
|
this.graphLayout.setLinks(this.links);
|
||||||
this.graphLayout.setDrawings(this.drawings);
|
this.graphLayout.setDrawings(this.drawings);
|
||||||
@ -152,6 +150,11 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
|||||||
if (target_id in nodes_by_id) {
|
if (target_id in nodes_by_id) {
|
||||||
link.target = nodes_by_id[target_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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,10 @@ export abstract class DataSource<T> {
|
|||||||
protected data: T[] = [];
|
protected data: T[] = [];
|
||||||
protected dataChange: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);
|
protected dataChange: BehaviorSubject<T[]> = new BehaviorSubject<T[]>([]);
|
||||||
|
|
||||||
|
public getItems(): T[] {
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
public add(item: T) {
|
public add(item: T) {
|
||||||
this.data.push(item);
|
this.data.push(item);
|
||||||
this.dataChange.next(this.data);
|
this.dataChange.next(this.data);
|
||||||
|
@ -1,13 +1,51 @@
|
|||||||
import {Node} from "../models/node";
|
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";
|
||||||
|
|
||||||
export class SelectionManager {
|
export interface Selectable {
|
||||||
private selectedNodes: Node[] = [];
|
x: number;
|
||||||
|
y: number;
|
||||||
constructor() {}
|
is_selected: boolean;
|
||||||
|
}
|
||||||
public setSelectedNodes(nodes: Node[]) {
|
|
||||||
this.selectedNodes = nodes;
|
|
||||||
}
|
@Injectable()
|
||||||
|
export class SelectionManager {
|
||||||
|
private selectedItems: Selectable[] = [];
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {Node} from "./node";
|
import {Node} from "./node";
|
||||||
|
import {Selectable} from "../managers/selection-manager";
|
||||||
|
|
||||||
export class Link {
|
export class Link implements Selectable {
|
||||||
capture_file_name: string;
|
capture_file_name: string;
|
||||||
capture_file_path: string;
|
capture_file_path: string;
|
||||||
capturing: boolean;
|
capturing: boolean;
|
||||||
@ -12,4 +13,8 @@ export class Link {
|
|||||||
length: number; // this is not from server
|
length: number; // this is not from server
|
||||||
source: Node; // this is not from server
|
source: Node; // this is not from server
|
||||||
target: Node; // this is not from server
|
target: Node; // this is not from server
|
||||||
|
|
||||||
|
is_selected = false;
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import {Label} from "./label";
|
import {Label} from "./label";
|
||||||
import {Port} from "../../../shared/models/port";
|
import {Port} from "../../../shared/models/port";
|
||||||
|
import {Selectable} from "../managers/selection-manager";
|
||||||
|
|
||||||
export class Node {
|
export class Node implements Selectable {
|
||||||
command_line: string;
|
command_line: string;
|
||||||
compute_id: string;
|
compute_id: string;
|
||||||
console: number;
|
console: number;
|
||||||
@ -24,4 +25,5 @@ export class Node {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
|
is_selected = false;
|
||||||
}
|
}
|
||||||
|
8
src/app/cartography/shared/models/rectangle.ts
Normal file
8
src/app/cartography/shared/models/rectangle.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export class Rectangle {
|
||||||
|
constructor(
|
||||||
|
public x?: number,
|
||||||
|
public y?: number,
|
||||||
|
public width?: number,
|
||||||
|
public height?: number
|
||||||
|
) {}
|
||||||
|
}
|
@ -1,20 +1,22 @@
|
|||||||
import {SVGSelection} from "../../../map/models/types";
|
import {SVGSelection} from "../../../map/models/types";
|
||||||
import {mouse, select} from "d3-selection";
|
import {mouse, select} from "d3-selection";
|
||||||
import {Context} from "../../../map/models/context";
|
import {Context} from "../../../map/models/context";
|
||||||
import {Node} from "../models/node";
|
|
||||||
import {Subject} from "rxjs/Subject";
|
import {Subject} from "rxjs/Subject";
|
||||||
|
import {Rectangle} from "../models/rectangle";
|
||||||
|
|
||||||
|
|
||||||
export class SelectionTool {
|
export class SelectionTool {
|
||||||
static readonly SELECTABLE_CLASS = '.selectable';
|
static readonly SELECTABLE_CLASS = '.selectable';
|
||||||
|
|
||||||
|
public rectangleSelected = new Subject<Rectangle>();
|
||||||
|
|
||||||
private selection: SVGSelection;
|
private selection: SVGSelection;
|
||||||
private path;
|
private path;
|
||||||
private context: Context;
|
private context: Context;
|
||||||
public selectedSubject: Subject<Node[]>;
|
// public selectedSubject: Subject<Selectable[]>;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.selectedSubject = new Subject<Node[]>();
|
// this.selectedSubject = new Subject<Selectable[]>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public connect(selection: SVGSelection, context: Context) {
|
public connect(selection: SVGSelection, context: Context) {
|
||||||
@ -79,35 +81,45 @@ export class SelectionTool {
|
|||||||
|
|
||||||
private moveSelection(start, move) {
|
private moveSelection(start, move) {
|
||||||
this.path.attr("d", this.rect(start[0], start[1], move[0] - start[0], move[1] - start[1]));
|
this.path.attr("d", this.rect(start[0], start[1], move[0] - start[0], move[1] - start[1]));
|
||||||
this.getSelectedNodes(start, move);
|
this.selectedEvent(start, move);
|
||||||
|
// this.getSelectedItems(start, move);
|
||||||
}
|
}
|
||||||
|
|
||||||
private endSelection(start, end) {
|
private endSelection(start, end) {
|
||||||
this.path.attr("visibility", "hidden");
|
this.path.attr("visibility", "hidden");
|
||||||
const selected_nodes = this.getSelectedNodes(start, end);
|
// const selected_items = this.getSelectedItems(start, end);
|
||||||
this.selectedSubject.next(selected_nodes);
|
// this.selectedSubject.next(selected_items);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getSelectedNodes(start, end): Node[] {
|
private selectedEvent(start, end) {
|
||||||
const x = Math.min(start[0], end[0]);
|
const x = Math.min(start[0], end[0]);
|
||||||
const y = Math.min(start[1], end[1]);
|
const y = Math.min(start[1], end[1]);
|
||||||
const width = Math.abs(start[0] - end[0]);
|
const width = Math.abs(start[0] - end[0]);
|
||||||
const height = Math.abs(start[1] - end[1]);
|
const height = Math.abs(start[1] - end[1]);
|
||||||
const nodes: Node[] = [];
|
this.rectangleSelected.next(new Rectangle(x, y, width, height));
|
||||||
|
|
||||||
this.selection
|
|
||||||
.selectAll(SelectionTool.SELECTABLE_CLASS)
|
|
||||||
.classed('selected', (node: Node) => {
|
|
||||||
const in_rect = (x <= node.x && node.x < (x + width) && y <= node.y && node.y < (y + height));
|
|
||||||
if (in_rect) {
|
|
||||||
nodes.push(node);
|
|
||||||
}
|
|
||||||
return in_rect;
|
|
||||||
});
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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<null, Selectable>(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) {
|
private rect(x: number, y: number, w: number, h: number) {
|
||||||
return "M" + [x, y] + " l" + [w, 0] + " l" + [0, h] + " l" + [-w, 0] + "z";
|
return "M" + [x, y] + " l" + [w, 0] + " l" + [0, h] + " l" + [-w, 0] + "z";
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@ export class EthernetLinkWidget implements Widget {
|
|||||||
const value_line = line();
|
const value_line = line();
|
||||||
|
|
||||||
let link_path = view.select<SVGPathElement>('path');
|
let link_path = view.select<SVGPathElement>('path');
|
||||||
|
link_path.classed('selectable', true);
|
||||||
|
link_path.classed('selected', (l: Link) => l.is_selected);
|
||||||
|
|
||||||
if (!link_path.node()) {
|
if (!link_path.node()) {
|
||||||
link_path = view.append<SVGPathElement>('path');
|
link_path = view.append<SVGPathElement>('path');
|
||||||
|
@ -136,6 +136,7 @@ export class NodesWidget implements Widget {
|
|||||||
|
|
||||||
const node_merge = node
|
const node_merge = node
|
||||||
.merge(node_enter)
|
.merge(node_enter)
|
||||||
|
.classed('selected', (n: Node) => n.is_selected)
|
||||||
.on("contextmenu", function (n: Node, i: number) {
|
.on("contextmenu", function (n: Node, i: number) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (self.onContextMenuCallback !== null) {
|
if (self.onContextMenuCallback !== null) {
|
||||||
|
@ -43,12 +43,12 @@ g.node text {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
svg image:hover, svg image.chosen, .selectable.selected {
|
svg image:hover, svg image.chosen, g.selectable.selected {
|
||||||
filter: grayscale(100%);
|
filter: grayscale(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
link.selectable.selected {
|
path.selectable.selected {
|
||||||
color: darkred;
|
stroke: darkred;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selection-line-tool .selection {
|
.selection-line-tool .selection {
|
||||||
|
@ -33,9 +33,12 @@ import { NodeSelectInterfaceComponent } from "../shared/node-select-interface/no
|
|||||||
import { Port } from "../shared/models/port";
|
import { Port } from "../shared/models/port";
|
||||||
import { LinkService } from "../shared/services/link.service";
|
import { LinkService } from "../shared/services/link.service";
|
||||||
import { ToasterService } from '../shared/services/toaster.service';
|
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 { InRectangleHelper } from "../cartography/map/helpers/in-rectangle-helper";
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -176,6 +179,12 @@ export class ProjectMapComponent implements OnInit {
|
|||||||
this.nodesDataSource.update(n);
|
this.nodesDataSource.update(n);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const selectionManager = new SelectionManager(this.nodesDataSource, this.linksDataSource, new InRectangleHelper());
|
||||||
|
|
||||||
|
this.mapChild.graphLayout.getSelectionTool().rectangleSelected.subscribe((rectangle: Rectangle) => {
|
||||||
|
selectionManager.setSelectedItemsInRectangle(rectangle);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onNodeCreation(appliance: Appliance) {
|
onNodeCreation(appliance: Appliance) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user