mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-01-31 08:25:35 +00:00
Drag interface labels
This commit is contained in:
parent
01cefc5c79
commit
8b91549984
@ -12,9 +12,11 @@ import { MapNode } from '../../models/map/map-node';
|
||||
import { MapDrawing } from '../../models/map/map-drawing';
|
||||
import { DraggedDataEvent } from '../../events/event-source';
|
||||
import { select } from 'd3-selection';
|
||||
import { NodeWidget } from '../../widgets/node';
|
||||
import { MapLabel } from '../../models/map/map-label';
|
||||
import { LabelWidget } from '../../widgets/label';
|
||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||
import { MapLinkNode } from '../../models/map/map-link-node';
|
||||
import { LinksEventSource } from '../../events/links-event-source';
|
||||
|
||||
@Component({
|
||||
selector: 'app-draggable-selection',
|
||||
@ -33,10 +35,12 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
||||
private drawingsWidget: DrawingsWidget,
|
||||
private linksWidget: LinksWidget,
|
||||
private labelWidget: LabelWidget,
|
||||
private interfaceWidget: InterfaceLabelWidget,
|
||||
private selectionManager: SelectionManager,
|
||||
private nodesEventSource: NodesEventSource,
|
||||
private drawingsEventSource: DrawingsEventSource,
|
||||
private graphDataManager: GraphDataManager
|
||||
private graphDataManager: GraphDataManager,
|
||||
private linksEventSource: LinksEventSource
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
@ -45,7 +49,8 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
||||
this.start = merge(
|
||||
this.nodesWidget.draggable.start,
|
||||
this.drawingsWidget.draggable.start,
|
||||
this.labelWidget.draggable.start
|
||||
this.labelWidget.draggable.start,
|
||||
this.interfaceWidget.draggable.start
|
||||
).subscribe((evt: DraggableStart<any>) => {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
if (evt.datum instanceof MapNode) {
|
||||
@ -65,12 +70,19 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
||||
this.selectionManager.setSelected([evt.datum]);
|
||||
}
|
||||
}
|
||||
|
||||
if (evt.datum instanceof MapLinkNode) {
|
||||
if (selected.filter((item) => item instanceof MapLinkNode && item.id === evt.datum.id).length === 0) {
|
||||
this.selectionManager.setSelected([evt.datum]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.drag = merge(
|
||||
this.nodesWidget.draggable.drag,
|
||||
this.drawingsWidget.draggable.drag,
|
||||
this.labelWidget.draggable.drag
|
||||
this.labelWidget.draggable.drag,
|
||||
this.interfaceWidget.draggable.drag
|
||||
).subscribe((evt: DraggableDrag<any>) => {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
const selectedNodes = selected.filter((item) => item instanceof MapNode);
|
||||
@ -108,12 +120,33 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
||||
this.labelWidget.redrawLabel(svg, label);
|
||||
});
|
||||
|
||||
// update interface labels
|
||||
selected.filter((item) => item instanceof MapLinkNode).forEach((interfaceLabel: MapLinkNode) => {
|
||||
const isParentNodeSelected = selectedNodes.filter((node) => node.id === interfaceLabel.nodeId).length > 0;
|
||||
if (isParentNodeSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
const link = this.graphDataManager.getLinks().filter((link) => link.nodes[0].id === interfaceLabel.id || link.nodes[1].id === interfaceLabel.id)[0];
|
||||
if(link.nodes[0].id === interfaceLabel.id) {
|
||||
link.nodes[0].label.x += evt.dx;
|
||||
link.nodes[0].label.y += evt.dy;
|
||||
}
|
||||
if(link.nodes[1].id === interfaceLabel.id) {
|
||||
link.nodes[1].label.x += evt.dx;
|
||||
link.nodes[1].label.y += evt.dy;
|
||||
}
|
||||
|
||||
this.linksWidget.redrawLink(svg, link);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this.end = merge(
|
||||
this.nodesWidget.draggable.end,
|
||||
this.drawingsWidget.draggable.end,
|
||||
this.labelWidget.draggable.end,
|
||||
this.interfaceWidget.draggable.end
|
||||
).subscribe((evt: DraggableEnd<any>) => {
|
||||
const selected = this.selectionManager.getSelected();
|
||||
const selectedNodes = selected.filter((item) => item instanceof MapNode);
|
||||
@ -135,7 +168,17 @@ export class DraggableSelectionComponent implements OnInit, OnDestroy {
|
||||
this.nodesEventSource.labelDragged.emit(new DraggedDataEvent<MapLabel>(label, evt.dx, evt.dy));
|
||||
});
|
||||
|
||||
selected.filter((item) => item instanceof MapLinkNode).forEach((label: MapLinkNode) => {
|
||||
const isParentNodeSelected = selectedNodes.filter((node) => node.id === label.nodeId).length > 0;
|
||||
if (isParentNodeSelected) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.linksEventSource.interfaceDragged.emit(new DraggedDataEvent<MapLinkNode>(label, evt.dx, evt.dy));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
|
@ -44,11 +44,35 @@ export class SelectionControlComponent implements OnInit, OnDestroy {
|
||||
return this.inRectangleHelper.inRectangle(rectangle, labelX, labelY);
|
||||
}).map((node) => node.label);
|
||||
|
||||
const selectedInterfacesLabelsSources = this.graphDataManager.getLinks().filter((link) => {
|
||||
if (link.source === undefined || link.nodes.length != 2 || link.nodes[0].label === undefined) {
|
||||
return false;
|
||||
}
|
||||
const interfaceLabelX = link.source.x + link.nodes[0].label.x;
|
||||
const interfaceLabelY = link.source.y + link.nodes[0].label.y;
|
||||
return this.inRectangleHelper.inRectangle(rectangle, interfaceLabelX, interfaceLabelY);
|
||||
}).map((link) => link.nodes[0]);
|
||||
|
||||
const selectedInterfacesLabelsTargets = this.graphDataManager.getLinks().filter((link) => {
|
||||
if (link.target === undefined || link.nodes.length != 2 || link.nodes[1].label === undefined) {
|
||||
return false;
|
||||
}
|
||||
const interfaceLabelX = link.target.x + link.nodes[1].label.x;
|
||||
const interfaceLabelY = link.target.y + link.nodes[1].label.y;
|
||||
return this.inRectangleHelper.inRectangle(rectangle, interfaceLabelX, interfaceLabelY);
|
||||
}).map((link) => link.nodes[1]);
|
||||
|
||||
const selectedInterfaces = [
|
||||
...selectedInterfacesLabelsSources,
|
||||
...selectedInterfacesLabelsTargets,
|
||||
]
|
||||
|
||||
const selected = [
|
||||
...selectedNodes,
|
||||
...selectedLinks,
|
||||
...selectedDrawings,
|
||||
...selectedLabels
|
||||
...selectedLabels,
|
||||
...selectedInterfaces,
|
||||
];
|
||||
|
||||
this.selectionManager.setSelected(selected);
|
||||
|
@ -9,15 +9,21 @@ import { MapLinkNode } from "../../models/map/map-link-node";
|
||||
@Injectable()
|
||||
export class LinkNodeToMapLinkNodeConverter implements Converter<LinkNode, MapLinkNode> {
|
||||
constructor(
|
||||
private labelToMapLabel: LabelToMapLabelConverter
|
||||
private labelToMapLabel: LabelToMapLabelConverter,
|
||||
) {}
|
||||
|
||||
convert(linkNode: LinkNode) {
|
||||
convert(linkNode: LinkNode, paramaters?: {[link_id: string]: string}) {
|
||||
const mapLinkNode = new MapLinkNode();
|
||||
mapLinkNode.nodeId = linkNode.node_id;
|
||||
mapLinkNode.adapterNumber = linkNode.adapter_number;
|
||||
mapLinkNode.portNumber = linkNode.port_number;
|
||||
mapLinkNode.label = this.labelToMapLabel.convert(linkNode.label);
|
||||
|
||||
if (paramaters !== undefined) {
|
||||
mapLinkNode.linkId = paramaters.link_id;
|
||||
mapLinkNode.id = `${mapLinkNode.nodeId}-${mapLinkNode.linkId}`;
|
||||
}
|
||||
|
||||
return mapLinkNode;
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,7 @@ export class LinkToMapLinkConverter implements Converter<Link, MapLink> {
|
||||
mapLink.captureFilePath = link.capture_file_path;
|
||||
mapLink.capturing = link.capturing;
|
||||
mapLink.linkType = link.link_type;
|
||||
mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode));
|
||||
mapLink.nodes = link.nodes.map((linkNode) => this.linkNodeToMapLinkNode.convert(linkNode,{ link_id: link.link_id }));
|
||||
mapLink.projectId = link.project_id;
|
||||
return mapLink;
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ import { MapLabel } from "../../models/map/map-label";
|
||||
|
||||
@Injectable()
|
||||
export class MapLabelToLabelConverter implements Converter<MapLabel, Label> {
|
||||
convert(mapLabel: MapLabel, paramters?: any) {
|
||||
const label = new Label();
|
||||
label.rotation = mapLabel.rotation;
|
||||
label.style = mapLabel.style;
|
||||
label.text = mapLabel.text;
|
||||
label.x = mapLabel.x;
|
||||
label.y = mapLabel.y;
|
||||
return label;
|
||||
}
|
||||
convert(mapLabel: MapLabel) {
|
||||
const label = new Label();
|
||||
label.rotation = mapLabel.rotation;
|
||||
label.style = mapLabel.style;
|
||||
label.text = mapLabel.text;
|
||||
label.x = mapLabel.x;
|
||||
label.y = mapLabel.y;
|
||||
return label;
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Injectable, EventEmitter } from "@angular/core";
|
||||
import { MapLinkCreated } from "./links";
|
||||
import { MapLinkNode } from "../models/map/map-link-node";
|
||||
import { DraggedDataEvent } from "./event-source";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class LinksEventSource {
|
||||
public created = new EventEmitter<MapLinkCreated>();
|
||||
public interfaceDragged = new EventEmitter<DraggedDataEvent<MapLinkNode>>();
|
||||
}
|
||||
|
@ -1,5 +1,8 @@
|
||||
export class InterfaceLabel {
|
||||
import { Indexed } from "../datasources/map-datasource";
|
||||
|
||||
export class InterfaceLabel implements Indexed {
|
||||
constructor(
|
||||
public id: string,
|
||||
public link_id: string,
|
||||
public direction: string,
|
||||
public x: number,
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { MapLabel } from "./map-label";
|
||||
import { Indexed } from "../../datasources/map-datasource";
|
||||
|
||||
export class MapLinkNode {
|
||||
nodeId: string;
|
||||
adapterNumber: number;
|
||||
portNumber: number;
|
||||
label: MapLabel;
|
||||
export class MapLinkNode implements Indexed {
|
||||
id: string;
|
||||
nodeId: string;
|
||||
linkId: string;
|
||||
adapterNumber: number;
|
||||
portNumber: number;
|
||||
label: MapLabel;
|
||||
}
|
||||
|
@ -6,50 +6,53 @@ import { CssFixer } from "../helpers/css-fixer";
|
||||
import { select } from "d3-selection";
|
||||
import { MapLink } from "../models/map/map-link";
|
||||
import { FontFixer } from "../helpers/font-fixer";
|
||||
import { SelectionManager } from "../managers/selection-manager";
|
||||
import { MapLinkNode } from "../models/map/map-link-node";
|
||||
import { MapNode } from "../models/map/map-node";
|
||||
import { Draggable } from "../events/draggable";
|
||||
|
||||
@Injectable()
|
||||
export class InterfaceLabelWidget {
|
||||
static SURROUNDING_TEXT_BORDER = 5;
|
||||
public draggable = new Draggable<SVGGElement, MapLinkNode>();
|
||||
|
||||
static SURROUNDING_TEXT_BORDER = 5;
|
||||
private enabled = true;
|
||||
|
||||
constructor(
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer
|
||||
private fontFixer: FontFixer,
|
||||
private selectionManager: SelectionManager
|
||||
) {
|
||||
}
|
||||
|
||||
public setEnabled(enabled: boolean) {
|
||||
public setEnabled(enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
draw(selection: SVGSelection) {
|
||||
const link_node_position = selection
|
||||
.selectAll<SVGGElement, MapLinkNode>('g.link_node_position')
|
||||
.data((link: MapLink) => [
|
||||
[link.source, link.nodes[0]],
|
||||
[link.target, link.nodes[1]]
|
||||
]);
|
||||
|
||||
const labels = selection
|
||||
const enter_link_node_position = link_node_position
|
||||
.enter()
|
||||
.append<SVGGElement>('g')
|
||||
.classed('link_node_position', true);
|
||||
|
||||
const merge_link_node_position = link_node_position.merge(enter_link_node_position);
|
||||
|
||||
merge_link_node_position.attr('transform', (nodeAndMapLinkNode: [MapNode, MapLinkNode]) => {
|
||||
return `translate(${nodeAndMapLinkNode[0].x}, ${nodeAndMapLinkNode[0].y})`;
|
||||
});
|
||||
|
||||
const labels = merge_link_node_position
|
||||
.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container')
|
||||
.data((l: MapLink) => {
|
||||
const sourceInterface = new InterfaceLabel(
|
||||
l.id,
|
||||
'source',
|
||||
Math.round(l.source.x + l.nodes[0].label.x),
|
||||
Math.round(l.source.y + l.nodes[0].label.y),
|
||||
l.nodes[0].label.text,
|
||||
l.nodes[0].label.style,
|
||||
l.nodes[0].label.rotation
|
||||
);
|
||||
|
||||
const targetInterface = new InterfaceLabel(
|
||||
l.id,
|
||||
'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.style,
|
||||
l.nodes[1].label.rotation
|
||||
);
|
||||
|
||||
.data((nodeAndMapLinkNode: [MapNode, MapLinkNode]) => {
|
||||
if (this.enabled) {
|
||||
return [sourceInterface, targetInterface];
|
||||
return [nodeAndMapLinkNode[1]];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
@ -60,67 +63,66 @@ export class InterfaceLabelWidget {
|
||||
.classed('interface_label_container', true);
|
||||
|
||||
// create surrounding rect
|
||||
// enter
|
||||
// .append<SVGRectElement>('rect')
|
||||
// .attr('class', 'interface_label_border');
|
||||
enter
|
||||
.append<SVGRectElement>('rect')
|
||||
.attr('class', 'interface_label_selection');
|
||||
|
||||
// create label
|
||||
enter
|
||||
.append<SVGTextElement>('text')
|
||||
.attr('class', 'interface_label noselect')
|
||||
.attr('interface_label_id', (i: InterfaceLabel) => `${i.direction}-${i.link_id}`)
|
||||
.attr('interface_label_id', (i: MapLinkNode) => `${i.id}`)
|
||||
|
||||
const merge = labels
|
||||
.merge(enter);
|
||||
|
||||
merge
|
||||
.attr('width', 100)
|
||||
.attr('height', 100)
|
||||
.attr('transform', function(this: SVGGElement, l: InterfaceLabel) {
|
||||
const textLabel = merge.select<SVGTextElement>(`text[interface_label_id="${l.direction}-${l.link_id}"]`);
|
||||
const bbox = textLabel.node().getBBox();
|
||||
const x = l.x ;
|
||||
const y = l.y + bbox.height;
|
||||
return `translate(${x}, ${y}) rotate(${l.rotation}, ${x}, ${y})`;
|
||||
})
|
||||
.classed('selected', (l: InterfaceLabel) => false);
|
||||
|
||||
// update label
|
||||
merge
|
||||
.select<SVGTextElement>('text.interface_label')
|
||||
.text((l: InterfaceLabel) => l.text)
|
||||
.attr('style', (l: InterfaceLabel) => {
|
||||
let styles = this.cssFixer.fix(l.style);
|
||||
.text((l: MapLinkNode) => l.label.text)
|
||||
.attr('style', (l: MapLinkNode) => {
|
||||
let styles = this.cssFixer.fix(l.label.style);
|
||||
styles = this.fontFixer.fixStyles(styles);
|
||||
return styles;
|
||||
});
|
||||
// .attr('x', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER)
|
||||
// .attr('y', -InterfaceLabelWidget.SURROUNDING_TEXT_BORDER);
|
||||
})
|
||||
.attr('x', function (this: SVGTextElement, l: MapLinkNode) {
|
||||
return l.label.x;
|
||||
})
|
||||
.attr('y', function (this: SVGTextElement, l: MapLinkNode) {
|
||||
let bbox = this.getBBox();
|
||||
return l.label.y + bbox.height;
|
||||
})
|
||||
.attr('transform', (l: MapLinkNode) => {
|
||||
return `rotate(${l.label.rotation}, ${l.label.x}, ${l.label.y})`;
|
||||
})
|
||||
|
||||
|
||||
// update surrounding rect
|
||||
// merge
|
||||
// .select<SVGRectElement>('rect.interface_label_border')
|
||||
// .attr('visibility', (l: InterfaceLabel) => false ? '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();
|
||||
merge
|
||||
.select<SVGRectElement>('rect.interface_label_selection')
|
||||
.attr('visibility', (l: MapLinkNode) => this.selectionManager.isSelected(l) ? 'visible' : 'hidden')
|
||||
.attr('stroke', 'black')
|
||||
.attr('stroke-dasharray', '3,3')
|
||||
.attr('stroke-width', '0.5')
|
||||
.attr('fill', 'none')
|
||||
.each(function (this: SVGRectElement, l: MapLinkNode) {
|
||||
const current = select(this);
|
||||
const textLabel = merge.select<SVGTextElement>(`text[interface_label_id="${l.id}"]`);
|
||||
const bbox = textLabel.node().getBBox();
|
||||
const border = 2;
|
||||
|
||||
// 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);
|
||||
// });
|
||||
current.attr('width', bbox.width + border * 2);
|
||||
current.attr('height', bbox.height + border * 2);
|
||||
current.attr('x', bbox.x - border);
|
||||
current.attr('y', bbox.y - border);
|
||||
current.attr('transform', `rotate(${l.label.rotation}, ${bbox.x - border}, ${bbox.y - border})`);
|
||||
});
|
||||
|
||||
labels
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
this.draggable.call(merge);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ import { MapDrawing } from '../../cartography/models/map/map-drawing';
|
||||
import { MapPortToPortConverter } from '../../cartography/converters/map/map-port-to-port-converter';
|
||||
import { SettingsService, Settings } from '../../services/settings.service';
|
||||
import { MapLabel } from '../../cartography/models/map/map-label';
|
||||
import { MapLinkNode } from '../../cartography/models/map/map-link-node';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -172,6 +173,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
this.subscriptions.push(
|
||||
this.linksEventSource.created.subscribe((evt) => this.onLinkCreated(evt))
|
||||
);
|
||||
|
||||
this.subscriptions.push(
|
||||
this.linksEventSource.interfaceDragged.subscribe((evt) => this.onInterfaceLabelDragged(evt))
|
||||
);
|
||||
}
|
||||
|
||||
onProjectLoad(project: Project) {
|
||||
@ -274,6 +279,24 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private onInterfaceLabelDragged(draggedEvent: DraggedDataEvent<MapLinkNode>) {
|
||||
const link = this.linksDataSource.get(draggedEvent.datum.linkId);
|
||||
if (link.nodes[0].node_id === draggedEvent.datum.nodeId) {
|
||||
link.nodes[0].label.x += draggedEvent.dx;
|
||||
link.nodes[0].label.y += draggedEvent.dy;
|
||||
}
|
||||
if (link.nodes[1].node_id === draggedEvent.datum.nodeId) {
|
||||
link.nodes[1].label.x += draggedEvent.dx;
|
||||
link.nodes[1].label.y += draggedEvent.dy;
|
||||
}
|
||||
|
||||
this.linkService
|
||||
.updateNodes(this.server, link, link.nodes)
|
||||
.subscribe((serverLink: Link) => {
|
||||
this.linksDataSource.update(serverLink);
|
||||
});
|
||||
}
|
||||
|
||||
private onLinkCreated(linkCreated: MapLinkCreated) {
|
||||
const sourceNode = this.mapNodeToNode.convert(linkCreated.sourceNode);
|
||||
const sourcePort = this.mapPortToPort.convert(linkCreated.sourcePort);
|
||||
|
@ -5,6 +5,8 @@ import 'rxjs/add/operator/map';
|
||||
import { Server } from "../models/server";
|
||||
import { HttpServer } from "./http-server.service";
|
||||
import {Port} from "../models/port";
|
||||
import { Link } from '../models/link';
|
||||
import { LinkNode } from '../models/link-node';
|
||||
|
||||
@Injectable()
|
||||
export class LinkService {
|
||||
@ -33,4 +35,27 @@ export class LinkService {
|
||||
]});
|
||||
}
|
||||
|
||||
updateNodes(
|
||||
server: Server, link: Link, nodes: LinkNode[]) {
|
||||
const requestNodes = nodes.map((linkNode) => {
|
||||
return {
|
||||
node_id: linkNode.node_id,
|
||||
port_number: linkNode.port_number,
|
||||
adapter_number: linkNode.adapter_number,
|
||||
label: {
|
||||
rotation: linkNode.label.rotation,
|
||||
style: linkNode.label.style,
|
||||
text: linkNode.label.text,
|
||||
x: linkNode.label.x,
|
||||
y: linkNode.label.y
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return this.httpServer
|
||||
.put(
|
||||
server,
|
||||
`/projects/${link.project_id}/links/${link.link_id}`,
|
||||
{"nodes": requestNodes});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user