mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-05-08 11:38:38 +00:00
Merge pull request #122 from GNS3/draw-interfaces
Draw interfaces, Fixes: #104
This commit is contained in:
commit
3ddc2ee550
@ -1,29 +1,22 @@
|
|||||||
import { InRectangleHelper } from "./in-rectangle-helper";
|
import { InRectangleHelper } from "./in-rectangle-helper";
|
||||||
import { Selectable } from "../../shared/managers/selection-manager";
|
|
||||||
import { Rectangle } from "../../shared/models/rectangle";
|
import { Rectangle } from "../../shared/models/rectangle";
|
||||||
|
|
||||||
class ExampleNode implements Selectable {
|
|
||||||
constructor(public x: number, public y: number, public is_selected: boolean) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
describe('InRectangleHelper', () => {
|
describe('InRectangleHelper', () => {
|
||||||
let inRectangleHelper: InRectangleHelper;
|
let inRectangleHelper: InRectangleHelper;
|
||||||
let node: Selectable;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
inRectangleHelper = new InRectangleHelper();
|
inRectangleHelper = new InRectangleHelper();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be in rectangle', () => {
|
it('should be in rectangle', () => {
|
||||||
node = new ExampleNode(100, 100, false);
|
const isIn = inRectangleHelper.inRectangle(new Rectangle(10, 10, 150, 150), 100, 100);
|
||||||
const isIn = inRectangleHelper.inRectangle(node, new Rectangle(10, 10, 150, 150));
|
|
||||||
expect(isIn).toBeTruthy();
|
expect(isIn).toBeTruthy();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should be outside rectangle', () => {
|
it('should be outside rectangle', () => {
|
||||||
node = new ExampleNode(100, 100, false);
|
const isIn = inRectangleHelper.inRectangle(new Rectangle(10, 10, 50, 50), 100, 100);
|
||||||
const isIn = inRectangleHelper.inRectangle(node, new Rectangle(10, 10, 50, 50));
|
|
||||||
expect(isIn).toBeFalsy();
|
expect(isIn).toBeFalsy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -6,8 +6,8 @@ import { Rectangle } from "../../shared/models/rectangle";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class InRectangleHelper {
|
export class InRectangleHelper {
|
||||||
public inRectangle(item: Selectable, rectangle: Rectangle): boolean {
|
public inRectangle(rectangle: Rectangle, x: number, y: number): boolean {
|
||||||
return (rectangle.x <= item.x && item.x < (rectangle.x + rectangle.width)
|
return (rectangle.x <= x && x < (rectangle.x + rectangle.width)
|
||||||
&& rectangle.y <= item.y && item.y < (rectangle.y + rectangle.height));
|
&& rectangle.y <= y && y < (rectangle.y + rectangle.height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,18 @@ describe('TestDataSource', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Item can be added when key duplocates', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
dataSource.add(new Item("test1", "property1"));
|
||||||
|
dataSource.add(new Item("test1", "property2"));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('item should be in data', () => {
|
||||||
|
expect(data).toEqual([new Item("test1", "property2")]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Items can be set', () => {
|
describe('Items can be set', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
dataSource.set([new Item("test1", "property1"), new Item("test2", "property2")]);
|
dataSource.set([new Item("test1", "property1"), new Item("test2", "property2")]);
|
||||||
|
@ -9,8 +9,13 @@ export abstract class DataSource<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public add(item: T) {
|
public add(item: T) {
|
||||||
this.data.push(item);
|
const index = this.findIndex(item);
|
||||||
this.dataChange.next(this.data);
|
if (index >= 0) {
|
||||||
|
this.update(item);
|
||||||
|
} else {
|
||||||
|
this.data.push(item);
|
||||||
|
this.dataChange.next(this.data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public set(data: T[]) {
|
public set(data: T[]) {
|
||||||
|
@ -2,11 +2,13 @@ import { Subject} from "rxjs/Subject";
|
|||||||
|
|
||||||
import { Node } from "../models/node";
|
import { Node } from "../models/node";
|
||||||
import { Link } from "../models/link";
|
import { Link } from "../models/link";
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
import { Rectangle } from "../models/rectangle";
|
import { Rectangle } from "../models/rectangle";
|
||||||
import { SelectionManager } from "./selection-manager";
|
import { SelectionManager } from "./selection-manager";
|
||||||
import { NodesDataSource } from "../datasources/nodes-datasource";
|
import { NodesDataSource } from "../datasources/nodes-datasource";
|
||||||
import { LinksDataSource } from "../datasources/links-datasource";
|
import { LinksDataSource } from "../datasources/links-datasource";
|
||||||
import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper";
|
import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper";
|
||||||
|
import { DrawingsDataSource } from "../datasources/drawings-datasource";
|
||||||
|
|
||||||
|
|
||||||
describe('SelectionManager', () => {
|
describe('SelectionManager', () => {
|
||||||
@ -16,13 +18,14 @@ describe('SelectionManager', () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
const linksDataSource = new LinksDataSource();
|
const linksDataSource = new LinksDataSource();
|
||||||
|
const drawingsDataSource = new DrawingsDataSource();
|
||||||
const inRectangleHelper = new InRectangleHelper();
|
const inRectangleHelper = new InRectangleHelper();
|
||||||
|
|
||||||
selectedRectangleSubject = new Subject<Rectangle>();
|
selectedRectangleSubject = new Subject<Rectangle>();
|
||||||
|
|
||||||
nodesDataSource = new NodesDataSource();
|
nodesDataSource = new NodesDataSource();
|
||||||
|
|
||||||
manager = new SelectionManager(nodesDataSource, linksDataSource, inRectangleHelper);
|
manager = new SelectionManager(nodesDataSource, linksDataSource, drawingsDataSource, inRectangleHelper);
|
||||||
manager.subscribe(selectedRectangleSubject);
|
manager.subscribe(selectedRectangleSubject);
|
||||||
|
|
||||||
const node_1 = new Node();
|
const node_1 = new Node();
|
||||||
@ -73,4 +76,20 @@ describe('SelectionManager', () => {
|
|||||||
manager.setSelectedLinks([link]);
|
manager.setSelectedLinks([link]);
|
||||||
expect(manager.getSelectedLinks().length).toEqual(1);
|
expect(manager.getSelectedLinks().length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('items should be cleared', () => {
|
||||||
|
const link = new Link();
|
||||||
|
link.link_id = "test1";
|
||||||
|
const node = new Node();
|
||||||
|
node.node_id = "test1";
|
||||||
|
const drawing = new Drawing();
|
||||||
|
drawing.drawing_id = "test1"
|
||||||
|
manager.setSelectedLinks([link]);
|
||||||
|
manager.setSelectedNodes([node]);
|
||||||
|
manager.setSelectedDrawings([drawing]);
|
||||||
|
manager.clearSelection();
|
||||||
|
expect(manager.getSelectedLinks().length).toEqual(0);
|
||||||
|
expect(manager.getSelectedDrawings().length).toEqual(0);
|
||||||
|
expect(manager.getSelectedNodes().length).toEqual(0);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -10,6 +10,9 @@ import { InRectangleHelper } from "../../map/helpers/in-rectangle-helper";
|
|||||||
import { Rectangle } from "../models/rectangle";
|
import { Rectangle } from "../models/rectangle";
|
||||||
import { Link} from "../models/link";
|
import { Link} from "../models/link";
|
||||||
import { DataSource } from "../datasources/datasource";
|
import { DataSource } from "../datasources/datasource";
|
||||||
|
import { Drawing } from "../models/drawing";
|
||||||
|
import { InterfaceLabel } from "../models/interface-label";
|
||||||
|
import { DrawingsDataSource } from "../datasources/drawings-datasource";
|
||||||
|
|
||||||
|
|
||||||
export interface Selectable {
|
export interface Selectable {
|
||||||
@ -22,10 +25,14 @@ export interface Selectable {
|
|||||||
export class SelectionManager {
|
export class SelectionManager {
|
||||||
private selectedNodes: Node[] = [];
|
private selectedNodes: Node[] = [];
|
||||||
private selectedLinks: Link[] = [];
|
private selectedLinks: Link[] = [];
|
||||||
|
private selectedDrawings: Drawing[] = [];
|
||||||
|
private selectedInterfaceLabels: InterfaceLabel[] = [];
|
||||||
|
|
||||||
private subscription: Subscription;
|
private subscription: Subscription;
|
||||||
|
|
||||||
constructor(private nodesDataSource: NodesDataSource,
|
constructor(private nodesDataSource: NodesDataSource,
|
||||||
private linksDataSource: LinksDataSource,
|
private linksDataSource: LinksDataSource,
|
||||||
|
private drawingsDataSource: DrawingsDataSource,
|
||||||
private inRectangleHelper: InRectangleHelper) {}
|
private inRectangleHelper: InRectangleHelper) {}
|
||||||
|
|
||||||
|
|
||||||
@ -33,6 +40,9 @@ export class SelectionManager {
|
|||||||
this.subscription = subject.subscribe((rectangle: Rectangle) => {
|
this.subscription = subject.subscribe((rectangle: Rectangle) => {
|
||||||
this.selectedNodes = this.getSelectedItemsInRectangle<Node>(this.nodesDataSource, rectangle);
|
this.selectedNodes = this.getSelectedItemsInRectangle<Node>(this.nodesDataSource, rectangle);
|
||||||
this.selectedLinks = this.getSelectedItemsInRectangle<Link>(this.linksDataSource, rectangle);
|
this.selectedLinks = this.getSelectedItemsInRectangle<Link>(this.linksDataSource, rectangle);
|
||||||
|
this.selectedDrawings = this.getSelectedItemsInRectangle<Drawing>(this.drawingsDataSource, rectangle);
|
||||||
|
// don't select interfaces for now
|
||||||
|
// this.selectedInterfaceLabels = this.getSelectedInterfaceLabelsInRectangle(rectangle);
|
||||||
});
|
});
|
||||||
return this.subscription;
|
return this.subscription;
|
||||||
}
|
}
|
||||||
@ -45,6 +55,10 @@ export class SelectionManager {
|
|||||||
return this.selectedLinks;
|
return this.selectedLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSelectedDrawings() {
|
||||||
|
return this.selectedDrawings;
|
||||||
|
}
|
||||||
|
|
||||||
public setSelectedNodes(nodes: Node[]) {
|
public setSelectedNodes(nodes: Node[]) {
|
||||||
this.selectedNodes = this.setSelectedItems<Node>(this.nodesDataSource, (node: Node) => {
|
this.selectedNodes = this.setSelectedItems<Node>(this.nodesDataSource, (node: Node) => {
|
||||||
return !!nodes.find((n: Node) => node.node_id === n.node_id);
|
return !!nodes.find((n: Node) => node.node_id === n.node_id);
|
||||||
@ -57,12 +71,56 @@ export class SelectionManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setSelectedDrawings(drawings: Drawing[]) {
|
||||||
|
this.selectedDrawings = this.setSelectedItems<Drawing>(this.drawingsDataSource, (drawing: Drawing) => {
|
||||||
|
return !!drawings.find((d: Drawing) => drawing.drawing_id === d.drawing_id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearSelection() {
|
||||||
|
this.setSelectedDrawings([]);
|
||||||
|
this.setSelectedLinks([]);
|
||||||
|
this.setSelectedNodes([]);
|
||||||
|
}
|
||||||
|
|
||||||
private getSelectedItemsInRectangle<T extends Selectable>(dataSource: DataSource<T>, rectangle: Rectangle) {
|
private getSelectedItemsInRectangle<T extends Selectable>(dataSource: DataSource<T>, rectangle: Rectangle) {
|
||||||
return this.setSelectedItems<T>(dataSource, (item: T) => {
|
return this.setSelectedItems<T>(dataSource, (item: T) => {
|
||||||
return this.inRectangleHelper.inRectangle(item, rectangle);
|
return this.inRectangleHelper.inRectangle(rectangle, item.x, item.y);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getSelectedInterfaceLabelsInRectangle(rectangle: Rectangle) {
|
||||||
|
this.linksDataSource.getItems().forEach((link: Link) => {
|
||||||
|
if (!(link.source && link.target && link.nodes.length > 1)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let updated = false;
|
||||||
|
|
||||||
|
let x = link.source.x + link.nodes[0].label.x;
|
||||||
|
let y = link.source.y + link.nodes[0].label.y;
|
||||||
|
|
||||||
|
if (this.inRectangleHelper.inRectangle(rectangle, x, y)) {
|
||||||
|
link.nodes[0].label.is_selected = true;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = link.target.x + link.nodes[1].label.x;
|
||||||
|
y = link.target.y + link.nodes[1].label.y;
|
||||||
|
|
||||||
|
if (this.inRectangleHelper.inRectangle(rectangle, x, y)) {
|
||||||
|
link.nodes[1].label.is_selected = true;
|
||||||
|
updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updated) {
|
||||||
|
this.linksDataSource.update(link);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
private setSelected<T extends Selectable>(item: T, isSelected: boolean, dataSource: DataSource<T>): boolean {
|
private setSelected<T extends Selectable>(item: T, isSelected: boolean, dataSource: DataSource<T>): boolean {
|
||||||
if (item.is_selected !== isSelected) {
|
if (item.is_selected !== isSelected) {
|
||||||
item.is_selected = isSelected;
|
item.is_selected = isSelected;
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
export class Drawing {
|
import { Selectable } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
export class Drawing implements Selectable {
|
||||||
drawing_id: string;
|
drawing_id: string;
|
||||||
project_id: string;
|
project_id: string;
|
||||||
rotation: number;
|
rotation: number;
|
||||||
@ -6,4 +8,5 @@ export class Drawing {
|
|||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
|
is_selected = false;
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
export class InterfaceLabel {
|
import { Selectable } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
export class InterfaceLabel implements Selectable {
|
||||||
constructor(
|
constructor(
|
||||||
|
public link_id: string,
|
||||||
|
public direction: string,
|
||||||
public x: number,
|
public x: number,
|
||||||
public y: number,
|
public y: number,
|
||||||
public text: string,
|
public text: string,
|
||||||
public style: string,
|
public style: string,
|
||||||
public rotation = 0,
|
public rotation = 0,
|
||||||
|
public is_selected = false
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
export class Label {
|
import { Selectable } from "../managers/selection-manager";
|
||||||
|
|
||||||
|
export class Label implements Selectable {
|
||||||
rotation: number;
|
rotation: number;
|
||||||
style: string;
|
style: string;
|
||||||
text: string;
|
text: string;
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
|
is_selected: boolean;
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,8 @@ export class EthernetLinkWidget implements Widget {
|
|||||||
|
|
||||||
public draw(view: SVGSelection, link: Link) {
|
public draw(view: SVGSelection, link: Link) {
|
||||||
const link_data = [[
|
const link_data = [[
|
||||||
[link.source.x, link.source.y],
|
[link.source.x + link.source.width / 2., link.source.y + link.source.height / 2.],
|
||||||
[link.target.x, link.target.y]
|
[link.target.x + link.target.width / 2., link.target.y + link.source.height / 2.]
|
||||||
]];
|
]];
|
||||||
|
|
||||||
const value_line = line();
|
const value_line = line();
|
||||||
|
@ -83,7 +83,7 @@ describe('InterfaceLabelsWidget', () => {
|
|||||||
const sourceInterface = drew.nodes()[0];
|
const sourceInterface = drew.nodes()[0];
|
||||||
expect(sourceInterface.innerHTML).toEqual('Interface 1');
|
expect(sourceInterface.innerHTML).toEqual('Interface 1');
|
||||||
expect(sourceInterface.getAttribute('x')).toEqual('110');
|
expect(sourceInterface.getAttribute('x')).toEqual('110');
|
||||||
expect(sourceInterface.getAttribute('y')).toEqual('220');
|
expect(sourceInterface.getAttribute('y')).toEqual('237');
|
||||||
expect(sourceInterface.getAttribute('transform')).toEqual('rotate(5, 110, 220)');
|
expect(sourceInterface.getAttribute('transform')).toEqual('rotate(5, 110, 220)');
|
||||||
expect(sourceInterface.getAttribute('style')).toEqual('font-size:12px');
|
expect(sourceInterface.getAttribute('style')).toEqual('font-size:12px');
|
||||||
expect(sourceInterface.getAttribute('class')).toContain('noselect');
|
expect(sourceInterface.getAttribute('class')).toContain('noselect');
|
||||||
@ -92,10 +92,21 @@ describe('InterfaceLabelsWidget', () => {
|
|||||||
const targetInterface = drew.nodes()[1];
|
const targetInterface = drew.nodes()[1];
|
||||||
expect(targetInterface.innerHTML).toEqual('Interface 2');
|
expect(targetInterface.innerHTML).toEqual('Interface 2');
|
||||||
expect(targetInterface.getAttribute('x')).toEqual('270');
|
expect(targetInterface.getAttribute('x')).toEqual('270');
|
||||||
expect(targetInterface.getAttribute('y')).toEqual('360');
|
expect(targetInterface.getAttribute('y')).toEqual('377');
|
||||||
expect(targetInterface.getAttribute('transform')).toEqual('rotate(0, 270, 360)');
|
expect(targetInterface.getAttribute('transform')).toEqual('rotate(0, 270, 360)');
|
||||||
expect(targetInterface.getAttribute('style')).toEqual('');
|
expect(targetInterface.getAttribute('style')).toEqual('');
|
||||||
expect(targetInterface.getAttribute('class')).toContain('noselect');
|
expect(targetInterface.getAttribute('class')).toContain('noselect');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should draw interface label with class `selected` when selected', () => {
|
||||||
|
links[0].nodes[0].label.is_selected = true;
|
||||||
|
|
||||||
|
widget.draw(linksEnter);
|
||||||
|
|
||||||
|
const drew = svg.canvas.selectAll<SVGGElement, InterfaceLabel>('text.interface_label');
|
||||||
|
|
||||||
|
const sourceInterface = drew.nodes()[0];
|
||||||
|
expect(sourceInterface.getAttribute('class')).toContain('selected');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -2,9 +2,12 @@ import { SVGSelection } from "../models/types";
|
|||||||
import { Link } from "../models/link";
|
import { Link } from "../models/link";
|
||||||
import { InterfaceLabel } from "../models/interface-label";
|
import { InterfaceLabel } from "../models/interface-label";
|
||||||
import { CssFixer } from "../helpers/css-fixer";
|
import { CssFixer } from "../helpers/css-fixer";
|
||||||
|
import { select } from "d3-selection";
|
||||||
|
|
||||||
|
|
||||||
export class InterfaceLabelWidget {
|
export class InterfaceLabelWidget {
|
||||||
|
static SURROUNDING_TEXT_BORDER = 5;
|
||||||
|
|
||||||
private cssFixer: CssFixer;
|
private cssFixer: CssFixer;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -14,22 +17,28 @@ export class InterfaceLabelWidget {
|
|||||||
draw(selection: SVGSelection) {
|
draw(selection: SVGSelection) {
|
||||||
|
|
||||||
const labels = selection
|
const labels = selection
|
||||||
.selectAll<SVGTextElement, InterfaceLabel>('text.interface_label')
|
.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container')
|
||||||
.data((l: Link) => {
|
.data((l: Link) => {
|
||||||
const sourceInterface = new InterfaceLabel(
|
const sourceInterface = new InterfaceLabel(
|
||||||
Math.round(l.source.x + l.nodes[0].label.x),
|
l.link_id,
|
||||||
|
'source',
|
||||||
|
Math.round( l.source.x + l.nodes[0].label.x),
|
||||||
Math.round(l.source.y + l.nodes[0].label.y),
|
Math.round(l.source.y + l.nodes[0].label.y),
|
||||||
l.nodes[0].label.text,
|
l.nodes[0].label.text,
|
||||||
l.nodes[0].label.style,
|
l.nodes[0].label.style,
|
||||||
l.nodes[0].label.rotation
|
l.nodes[0].label.rotation,
|
||||||
|
l.nodes[0].label.is_selected
|
||||||
);
|
);
|
||||||
|
|
||||||
const targetInterface = new InterfaceLabel(
|
const targetInterface = new InterfaceLabel(
|
||||||
Math.round(l.target.x + l.nodes[1].label.x),
|
l.link_id,
|
||||||
Math.round(l.target.y + l.nodes[1].label.y),
|
'target',
|
||||||
|
Math.round( l.target.x + l.nodes[1].label.x),
|
||||||
|
Math.round( l.target.y + l.nodes[1].label.y),
|
||||||
l.nodes[1].label.text,
|
l.nodes[1].label.text,
|
||||||
l.nodes[1].label.style,
|
l.nodes[1].label.style,
|
||||||
l.nodes[1].label.rotation
|
l.nodes[1].label.rotation,
|
||||||
|
l.nodes[1].label.is_selected
|
||||||
);
|
);
|
||||||
|
|
||||||
return [sourceInterface, targetInterface];
|
return [sourceInterface, targetInterface];
|
||||||
@ -37,18 +46,61 @@ export class InterfaceLabelWidget {
|
|||||||
|
|
||||||
const enter = labels
|
const enter = labels
|
||||||
.enter()
|
.enter()
|
||||||
.append<SVGTextElement>('text')
|
.append<SVGGElement>('g')
|
||||||
.attr('class', 'interface_label noselect');
|
.classed('interface_label_container', true);
|
||||||
|
|
||||||
|
// create surrounding rect
|
||||||
|
enter
|
||||||
|
.append<SVGRectElement>('rect')
|
||||||
|
.attr('class', 'interface_label_border');
|
||||||
|
|
||||||
|
// create label
|
||||||
|
enter
|
||||||
|
.append<SVGTextElement>('text')
|
||||||
|
.attr('class', 'interface_label noselect');
|
||||||
|
|
||||||
const merge = labels
|
const merge = labels
|
||||||
.merge(enter);
|
.merge(enter);
|
||||||
|
|
||||||
merge
|
merge
|
||||||
.text((l: InterfaceLabel) => l.text)
|
.attr('width', 100)
|
||||||
.attr('x', (l: InterfaceLabel) => l.x)
|
.attr('height', 100)
|
||||||
.attr('y', (l: InterfaceLabel) => l.y)
|
.attr('transform', function(this: SVGGElement, l: InterfaceLabel) {
|
||||||
.attr('style', (l: InterfaceLabel) => this.cssFixer.fix(l.style))
|
const bbox = this.getBBox();
|
||||||
.attr('transform', (l: InterfaceLabel) => `rotate(${l.rotation}, ${l.x}, ${l.y})`);
|
const x = l.x;
|
||||||
|
const y = l.y + bbox.height;
|
||||||
|
return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`;
|
||||||
|
})
|
||||||
|
.classed('selected', (l: InterfaceLabel) => l.is_selected);
|
||||||
|
|
||||||
|
// update label
|
||||||
|
merge
|
||||||
|
.select<SVGTextElement>('text.interface_label')
|
||||||
|
.text((l: InterfaceLabel) => l.text)
|
||||||
|
.attr('style', (l: InterfaceLabel) => this.cssFixer.fix(l.style))
|
||||||
|
.attr('x', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER)
|
||||||
|
.attr('y', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER);
|
||||||
|
|
||||||
|
|
||||||
|
// update surrounding rect
|
||||||
|
merge
|
||||||
|
.select<SVGRectElement>('rect.interface_label_border')
|
||||||
|
.attr('visibility', (l: InterfaceLabel) => l.is_selected ? 'visible' : 'hidden')
|
||||||
|
.attr('stroke-dasharray', '3,3')
|
||||||
|
.attr('stroke-width', '0.5')
|
||||||
|
.each(function (this: SVGRectElement, l: InterfaceLabel) {
|
||||||
|
const current = select(this);
|
||||||
|
const parent = select(this.parentElement);
|
||||||
|
const text = parent.select<SVGTextElement>('text');
|
||||||
|
const bbox = text.node().getBBox();
|
||||||
|
|
||||||
|
const border = InterfaceLabelWidget.SURROUNDING_TEXT_BORDER;
|
||||||
|
|
||||||
|
current.attr('width', bbox.width + border*2);
|
||||||
|
current.attr('height', bbox.height + border);
|
||||||
|
current.attr('x', - border);
|
||||||
|
current.attr('y', - bbox.height);
|
||||||
|
});
|
||||||
|
|
||||||
labels
|
labels
|
||||||
.exit()
|
.exit()
|
||||||
|
@ -51,13 +51,18 @@ export class LinksWidget implements Widget {
|
|||||||
|
|
||||||
const link_path = link_group.select<SVGPathElement>('path');
|
const link_path = link_group.select<SVGPathElement>('path');
|
||||||
|
|
||||||
const start_point: SVGPoint = link_path.node().getPointAtLength(50);
|
const start_point: SVGPoint = link_path.node().getPointAtLength(45);
|
||||||
const end_point: SVGPoint = link_path.node().getPointAtLength(link_path.node().getTotalLength() - 50);
|
const end_point: SVGPoint = link_path.node().getPointAtLength(link_path.node().getTotalLength() - 45);
|
||||||
|
|
||||||
|
let statuses = [];
|
||||||
|
|
||||||
|
if (link_path.node().getTotalLength() > 2 * 45 + 10) {
|
||||||
|
statuses = [
|
||||||
|
new LinkStatus(start_point.x, start_point.y, l.source.status),
|
||||||
|
new LinkStatus(end_point.x, end_point.y, l.target.status)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const statuses = [
|
|
||||||
new LinkStatus(start_point.x, start_point.y, l.source.status),
|
|
||||||
new LinkStatus(end_point.x, end_point.y, l.target.status)
|
|
||||||
];
|
|
||||||
|
|
||||||
const status_started = link_group
|
const status_started = link_group
|
||||||
.selectAll<SVGCircleElement, LinkStatus>('circle.status_started')
|
.selectAll<SVGCircleElement, LinkStatus>('circle.status_started')
|
||||||
@ -72,7 +77,7 @@ export class LinksWidget implements Widget {
|
|||||||
.attr('class', 'status_started')
|
.attr('class', 'status_started')
|
||||||
.attr('cx', (ls: LinkStatus) => ls.x)
|
.attr('cx', (ls: LinkStatus) => ls.x)
|
||||||
.attr('cy', (ls: LinkStatus) => ls.y)
|
.attr('cy', (ls: LinkStatus) => ls.y)
|
||||||
.attr('r', 4)
|
.attr('r', 6)
|
||||||
.attr('fill', '#2ecc71');
|
.attr('fill', '#2ecc71');
|
||||||
|
|
||||||
status_started
|
status_started
|
||||||
@ -87,7 +92,7 @@ export class LinksWidget implements Widget {
|
|||||||
.enter()
|
.enter()
|
||||||
.append<SVGRectElement>('rect');
|
.append<SVGRectElement>('rect');
|
||||||
|
|
||||||
const STOPPED_STATUS_RECT_WIDTH = 6;
|
const STOPPED_STATUS_RECT_WIDTH = 10;
|
||||||
|
|
||||||
status_stopped
|
status_stopped
|
||||||
.merge(status_stopped_enter)
|
.merge(status_stopped_enter)
|
||||||
|
@ -68,15 +68,17 @@ export class NodesWidget implements Widget {
|
|||||||
const bbox = this.getBBox();
|
const bbox = this.getBBox();
|
||||||
return -bbox.width / 2.;
|
return -bbox.width / 2.;
|
||||||
}
|
}
|
||||||
return n.label.x - n.width / 2.;
|
return n.label.x;
|
||||||
})
|
})
|
||||||
.attr('y', function (this: SVGTextElement, n: Node) {
|
.attr('y', function (this: SVGTextElement, n: Node) {
|
||||||
|
let bbox = this.getBBox();
|
||||||
|
|
||||||
if (n.label.x === null) {
|
if (n.label.x === null) {
|
||||||
// center
|
// center
|
||||||
const bbox = this.getBBox();
|
bbox = this.getBBox();
|
||||||
return - n.height / 2. - bbox.height ;
|
return - n.height / 2. - bbox.height ;
|
||||||
}
|
}
|
||||||
return n.label.y - n.height / 2.;
|
return n.label.y + bbox.height;
|
||||||
});
|
});
|
||||||
|
|
||||||
selection
|
selection
|
||||||
@ -156,8 +158,8 @@ export class NodesWidget implements Widget {
|
|||||||
})
|
})
|
||||||
.attr('width', (n: Node) => n.width)
|
.attr('width', (n: Node) => n.width)
|
||||||
.attr('height', (n: Node) => n.height)
|
.attr('height', (n: Node) => n.height)
|
||||||
.attr('x', (n: Node) => -n.width / 2.)
|
.attr('x', (n: Node) => 0)
|
||||||
.attr('y', (n: Node) => -n.height / 2.)
|
.attr('y', (n: Node) => 0)
|
||||||
.on('mouseover', function (this, n: Node) {
|
.on('mouseover', function (this, n: Node) {
|
||||||
select(this).attr("class", "over");
|
select(this).attr("class", "over");
|
||||||
})
|
})
|
||||||
|
@ -44,6 +44,11 @@ path.selected {
|
|||||||
stroke: darkred;
|
stroke: darkred;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.selected > .interface_label_border {
|
||||||
|
stroke: black;
|
||||||
|
fill: none;
|
||||||
|
}
|
||||||
|
|
||||||
.selection-line-tool .selection {
|
.selection-line-tool .selection {
|
||||||
fill: #7ccbe1;
|
fill: #7ccbe1;
|
||||||
stroke: #66aec2 ;
|
stroke: #66aec2 ;
|
||||||
|
@ -92,7 +92,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
protected hotkeysService: HotkeysService
|
protected hotkeysService: HotkeysService
|
||||||
) {
|
) {
|
||||||
this.selectionManager = new SelectionManager(
|
this.selectionManager = new SelectionManager(
|
||||||
this.nodesDataSource, this.linksDataSource, new InRectangleHelper());
|
this.nodesDataSource, this.linksDataSource, this.drawingsDataSource, new InRectangleHelper());
|
||||||
|
|
||||||
this.subscriptions = [];
|
this.subscriptions = [];
|
||||||
}
|
}
|
||||||
@ -199,6 +199,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.mapChild.graphLayout.getNodesWidget().setOnNodeClickedCallback((event: any, node: Node) => {
|
this.mapChild.graphLayout.getNodesWidget().setOnNodeClickedCallback((event: any, node: Node) => {
|
||||||
|
this.selectionManager.clearSelection();
|
||||||
this.selectionManager.setSelectedNodes([node]);
|
this.selectionManager.setSelectedNodes([node]);
|
||||||
if (this.drawLineMode) {
|
if (this.drawLineMode) {
|
||||||
this.nodeSelectInterfaceMenu.open(node, event.clientY, event.clientX);
|
this.nodeSelectInterfaceMenu.open(node, event.clientY, event.clientX);
|
||||||
@ -286,7 +287,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
|||||||
const data = drawingLineTool.stop();
|
const data = drawingLineTool.stop();
|
||||||
this.onLineCreation(data['node'], data['port'], node, port);
|
this.onLineCreation(data['node'], data['port'], node, port);
|
||||||
} else {
|
} else {
|
||||||
drawingLineTool.start(node.x, node.y, {
|
drawingLineTool.start(node.x + node.width / 2., node.y + node.height / 2., {
|
||||||
'node': node,
|
'node': node,
|
||||||
'port': port
|
'port': port
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user