mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-02-20 17:52:46 +00:00
Merge branch 'develop' into add-rectangle-change-border-position
This commit is contained in:
commit
6fc98e3577
@ -42,6 +42,7 @@ import { SelectionEventSource } from './events/selection-event-source';
|
||||
import { SelectionControlComponent } from './components/selection-control/selection-control.component';
|
||||
import { SelectionSelectComponent } from './components/selection-select/selection-select.component';
|
||||
import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component';
|
||||
import { MapSettingsManager } from './managers/map-settings-manager';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@ -94,6 +95,7 @@ import { DraggableSelectionComponent } from './components/draggable-selection/dr
|
||||
MapDrawingsDataSource,
|
||||
MapSymbolsDataSource,
|
||||
SelectionEventSource,
|
||||
MapSettingsManager,
|
||||
...D3_MAP_IMPORTS
|
||||
],
|
||||
exports: [ D3MapComponent, ExperimentalMapComponent ]
|
||||
|
@ -6,7 +6,6 @@ import { Selection, select } from 'd3-selection';
|
||||
import { GraphLayout } from "../../widgets/graph-layout";
|
||||
import { Context } from "../../models/context";
|
||||
import { Size } from "../../models/size";
|
||||
import { NodesWidget } from '../../widgets/nodes';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { InterfaceLabelWidget } from '../../widgets/interface-label';
|
||||
import { SelectionTool } from '../../tools/selection-tool';
|
||||
@ -14,13 +13,13 @@ import { MovingTool } from '../../tools/moving-tool';
|
||||
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
|
||||
import { MapLinkCreated } from '../../events/links';
|
||||
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
|
||||
import { DrawingsWidget } from '../../widgets/drawings';
|
||||
import { Node } from '../../models/node';
|
||||
import { Link } from '../../../models/link';
|
||||
import { Drawing } from '../../models/drawing';
|
||||
import { Symbol } from '../../../models/symbol';
|
||||
import { GraphDataManager } from '../../managers/graph-data-manager';
|
||||
import { DraggedDataEvent } from '../../events/event-source';
|
||||
import { MapSettingsManager } from '../../managers/map-settings-manager';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -56,9 +55,8 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
public context: Context,
|
||||
private mapChangeDetectorRef: MapChangeDetectorRef,
|
||||
private canvasSizeDetector: CanvasSizeDetector,
|
||||
private mapSettings: MapSettingsManager,
|
||||
protected element: ElementRef,
|
||||
protected nodesWidget: NodesWidget,
|
||||
protected drawingsWidget: DrawingsWidget,
|
||||
protected interfaceLabelWidget: InterfaceLabelWidget,
|
||||
protected selectionToolWidget: SelectionTool,
|
||||
protected movingToolWidget: MovingTool,
|
||||
@ -92,8 +90,7 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
public drawingSelected = "";
|
||||
|
||||
@Input('readonly') set readonly(value) {
|
||||
this.nodesWidget.draggingEnabled = !value;
|
||||
this.drawingsWidget.draggingEnabled = !value;
|
||||
this.mapSettings.isReadOnly = value;
|
||||
}
|
||||
|
||||
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
|
||||
|
@ -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() {
|
||||
|
@ -42,7 +42,7 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
||||
if (this.drawingLineTool.isDrawing()) {
|
||||
this.drawingLineTool.stop();
|
||||
}
|
||||
this.onNodeClicked.unsubscribe();
|
||||
// this.onNodeClicked.unsubscribe();
|
||||
}
|
||||
|
||||
public onChooseInterface(event) {
|
||||
|
@ -9,6 +9,6 @@
|
||||
*ngFor="let line of lines; index as i"
|
||||
xml:space="preserve"
|
||||
x="0"
|
||||
[attr.dy]="i == 0 ? '0em' : '1.2em'"
|
||||
[attr.dy]="i == 0 ? '0em' : '1.4em'"
|
||||
>{{line}}</svg:tspan>
|
||||
</svg:text>
|
Before Width: | Height: | Size: 344 B After Width: | Height: | Size: 344 B |
@ -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>>();
|
||||
}
|
||||
|
7
src/app/cartography/managers/map-settings-manager.ts
Normal file
7
src/app/cartography/managers/map-settings-manager.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class MapSettingsManager {
|
||||
public isReadOnly = false;
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
export class InterfaceLabel {
|
||||
constructor(
|
||||
public link_id: string,
|
||||
public direction: string,
|
||||
public x: number,
|
||||
public y: number,
|
||||
public text: string,
|
||||
public style: string,
|
||||
public rotation = 0,
|
||||
) {}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import { Context } from "../models/context";
|
||||
import { EllipseElement } from "../models/drawings/ellipse-element";
|
||||
import { ResizingEnd } from "../events/resizing";
|
||||
import { LineElement } from "../models/drawings/line-element";
|
||||
import { MapSettingsManager } from "../managers/map-settings-manager";
|
||||
|
||||
|
||||
@Injectable()
|
||||
@ -29,7 +30,8 @@ export class DrawingsWidget implements Widget {
|
||||
constructor(
|
||||
private drawingWidget: DrawingWidget,
|
||||
private svgToDrawingConverter: SvgToDrawingConverter,
|
||||
private context: Context
|
||||
private context: Context,
|
||||
private mapSettings: MapSettingsManager
|
||||
) {
|
||||
this.svgToDrawingConverter = new SvgToDrawingConverter();
|
||||
}
|
||||
@ -67,7 +69,7 @@ export class DrawingsWidget implements Widget {
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
if (this.draggingEnabled) {
|
||||
if (!this.mapSettings.isReadOnly) {
|
||||
this.draggable.call(merge);
|
||||
}
|
||||
|
||||
|
@ -67,7 +67,7 @@ describe('TextDrawingWidget', () => {
|
||||
|
||||
expect(drew.nodes()[1].innerHTML).toEqual('IS TEXT');
|
||||
expect(drew.nodes()[1].getAttribute('x')).toEqual('0');
|
||||
expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.2em');
|
||||
expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.4em');
|
||||
});
|
||||
|
||||
it('should draw whitespaces', () => {
|
||||
|
@ -64,7 +64,7 @@ export class TextDrawingWidget implements DrawingShapeWidget {
|
||||
.text((line) => line)
|
||||
.attr('xml:space', 'preserve')
|
||||
.attr('x', 0)
|
||||
.attr("dy", (line, i) => i === 0 ? '0em' : '1.2em');
|
||||
.attr("dy", (line, i) => i === 0 ? '0em' : '1.4em');
|
||||
|
||||
lines
|
||||
.exit()
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Selection } from "d3-selection";
|
||||
|
||||
import { TestSVGCanvas } from "../testing";
|
||||
import { InterfaceLabel } from "../models/interface-label";
|
||||
import { InterfaceLabelWidget } from "./interface-label";
|
||||
import { CssFixer } from "../helpers/css-fixer";
|
||||
import { MapNode } from "../models/map/map-node";
|
||||
@ -9,6 +8,8 @@ import { MapLink } from "../models/map/map-link";
|
||||
import { MapLinkNode } from "../models/map/map-link-node";
|
||||
import { MapLabel } from "../models/map/map-label";
|
||||
import { FontFixer } from "../helpers/font-fixer";
|
||||
import { SelectionManager } from "../managers/selection-manager";
|
||||
import { MapSettingsManager } from "../managers/map-settings-manager";
|
||||
|
||||
|
||||
describe('InterfaceLabelsWidget', () => {
|
||||
@ -16,6 +17,7 @@ describe('InterfaceLabelsWidget', () => {
|
||||
let widget: InterfaceLabelWidget;
|
||||
let linksEnter: Selection<SVGGElement, MapLink, SVGGElement, any>;
|
||||
let links: MapLink[];
|
||||
let mapSettings: MapSettingsManager;
|
||||
|
||||
beforeEach(() => {
|
||||
svg = new TestSVGCanvas();
|
||||
@ -68,7 +70,8 @@ describe('InterfaceLabelsWidget', () => {
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
widget = new InterfaceLabelWidget(new CssFixer(), new FontFixer());
|
||||
mapSettings = new MapSettingsManager();
|
||||
widget = new InterfaceLabelWidget(new CssFixer(), new FontFixer(), new SelectionManager(), mapSettings);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@ -78,24 +81,22 @@ describe('InterfaceLabelsWidget', () => {
|
||||
it('should draw interface labels', () => {
|
||||
widget.draw(linksEnter);
|
||||
|
||||
const drew = svg.canvas.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container');
|
||||
const drew = svg.canvas.selectAll<SVGGElement, MapLinkNode>('g.interface_label_container');
|
||||
|
||||
expect(drew.nodes().length).toEqual(2);
|
||||
|
||||
const sourceInterface = drew.nodes()[0] as Element;
|
||||
|
||||
expect(sourceInterface.getAttribute('transform')).toEqual('translate(110, 220) rotate(5, 110, 220)');
|
||||
const sourceIntefaceRect = sourceInterface.firstChild as Element;
|
||||
expect(sourceIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_border');
|
||||
expect(sourceIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_selection');
|
||||
const sourceIntefaceText = sourceInterface.children[1];
|
||||
expect(sourceIntefaceText.attributes.getNamedItem('class').value).toEqual('interface_label noselect');
|
||||
expect(sourceIntefaceText.attributes.getNamedItem('style').value).toEqual('font-size:12px');
|
||||
|
||||
const targetInterface = drew.nodes()[1];
|
||||
|
||||
expect(targetInterface.getAttribute('transform')).toEqual('translate(270, 360) rotate(0, 270, 360)');
|
||||
const targetIntefaceRect = targetInterface.firstChild as Element;
|
||||
expect(targetIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_border');
|
||||
expect(targetIntefaceRect.attributes.getNamedItem('class').value).toEqual('interface_label_selection');
|
||||
const targetIntefaceText = targetInterface.children[1] as Element;
|
||||
expect(targetIntefaceText.attributes.getNamedItem('class').value).toEqual('interface_label noselect');
|
||||
expect(targetIntefaceText.attributes.getNamedItem('style').value).toEqual('');
|
||||
@ -106,7 +107,7 @@ describe('InterfaceLabelsWidget', () => {
|
||||
widget.setEnabled(false);
|
||||
widget.draw(linksEnter);
|
||||
|
||||
const drew = svg.canvas.selectAll<SVGGElement, InterfaceLabel>('g.interface_label_container');
|
||||
const drew = svg.canvas.selectAll<SVGGElement, MapLinkNode>('g.interface_label_container');
|
||||
|
||||
expect(drew.nodes().length).toEqual(0);
|
||||
});
|
||||
|
@ -1,55 +1,59 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { InterfaceLabel } from "../models/interface-label";
|
||||
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";
|
||||
import { MapSettingsManager } from "../managers/map-settings-manager";
|
||||
|
||||
@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,
|
||||
private mapSettings: MapSettingsManager
|
||||
) {
|
||||
}
|
||||
|
||||
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
|
||||
.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 enter_link_node_position = link_node_position
|
||||
.enter()
|
||||
.append<SVGGElement>('g')
|
||||
.classed('link_node_position', true);
|
||||
|
||||
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
|
||||
);
|
||||
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, [MapNode, MapLinkNode]>('g.interface_label_container')
|
||||
.data((nodeAndMapLinkNode: [MapNode, MapLinkNode]) => {
|
||||
if (this.enabled) {
|
||||
return [sourceInterface, targetInterface];
|
||||
return [nodeAndMapLinkNode[1]];
|
||||
}
|
||||
return [];
|
||||
});
|
||||
@ -62,63 +66,65 @@ export class InterfaceLabelWidget {
|
||||
// create surrounding rect
|
||||
enter
|
||||
.append<SVGRectElement>('rect')
|
||||
.attr('class', 'interface_label_border');
|
||||
.attr('class', 'interface_label_selection');
|
||||
|
||||
// create label
|
||||
enter
|
||||
.append<SVGTextElement>('text')
|
||||
.attr('class', 'interface_label noselect');
|
||||
.attr('class', 'interface_label noselect')
|
||||
.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 bbox = this.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();
|
||||
.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();
|
||||
|
||||
if(!this.mapSettings.isReadOnly) {
|
||||
this.draggable.call(merge);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { MapNode } from "../models/map/map-node";
|
||||
import { SelectionManager } from "../managers/selection-manager";
|
||||
import { Draggable } from "../events/draggable";
|
||||
import { MapLabel } from "../models/map/map-label";
|
||||
import { MapSettingsManager } from "../managers/map-settings-manager";
|
||||
|
||||
|
||||
@Injectable()
|
||||
@ -20,7 +21,8 @@ export class LabelWidget implements Widget {
|
||||
constructor(
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer,
|
||||
private selectionManager: SelectionManager
|
||||
private selectionManager: SelectionManager,
|
||||
private mapSettings: MapSettingsManager,
|
||||
) {}
|
||||
|
||||
public redrawLabel(view: SVGSelection, label: MapLabel) {
|
||||
@ -47,7 +49,9 @@ export class LabelWidget implements Widget {
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
this.draggable.call(label_view);
|
||||
if(!this.mapSettings.isReadOnly) {
|
||||
this.draggable.call(label_view);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -96,7 +100,7 @@ export class LabelWidget implements Widget {
|
||||
// bbox = this.getBBox();
|
||||
// return - n.height / 2. - bbox.height ;
|
||||
// }
|
||||
return l.y + bbox.height - LabelWidget.NODE_LABEL_MARGIN;
|
||||
return l.y + bbox.height - LabelWidget.NODE_LABEL_MARGIN - bbox.height*0.14;
|
||||
})
|
||||
.attr('transform', (l: MapLabel) => {
|
||||
return `rotate(${l.rotation}, ${l.x}, ${l.y})`;
|
||||
|
@ -3,6 +3,7 @@ import { TestSVGCanvas } from "../testing";
|
||||
import { NodesWidget } from "./nodes";
|
||||
import { NodeWidget } from "./node";
|
||||
import { instance, mock } from "ts-mockito";
|
||||
import { MapSettingsManager } from "../managers/map-settings-manager";
|
||||
|
||||
|
||||
describe('NodesWidget', () => {
|
||||
@ -13,7 +14,7 @@ describe('NodesWidget', () => {
|
||||
beforeEach(() => {
|
||||
svg = new TestSVGCanvas();
|
||||
nodeWidget = instance(mock(NodeWidget));
|
||||
widget = new NodesWidget(nodeWidget);
|
||||
widget = new NodesWidget(nodeWidget, new MapSettingsManager());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -6,6 +6,7 @@ import { Layer } from "../models/layer";
|
||||
import { NodeWidget } from "./node";
|
||||
import { Draggable } from "../events/draggable";
|
||||
import { MapNode } from "../models/map/map-node";
|
||||
import { MapSettingsManager } from "../managers/map-settings-manager";
|
||||
|
||||
|
||||
@Injectable()
|
||||
@ -13,10 +14,10 @@ export class NodesWidget implements Widget {
|
||||
static NODE_LABEL_MARGIN = 3;
|
||||
|
||||
public draggable = new Draggable<SVGGElement, MapNode>();
|
||||
public draggingEnabled = false;
|
||||
|
||||
constructor(
|
||||
private nodeWidget: NodeWidget
|
||||
private nodeWidget: NodeWidget,
|
||||
private mapSettings: MapSettingsManager
|
||||
) {
|
||||
}
|
||||
|
||||
@ -49,7 +50,7 @@ export class NodesWidget implements Widget {
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
if (this.draggingEnabled) {
|
||||
if (!this.mapSettings.isReadOnly) {
|
||||
this.draggable.call(merge);
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ import { LineElement } from '../../cartography/models/drawings/line-element';
|
||||
import { SettingsService, Settings } from '../../services/settings.service';
|
||||
import { MapLabel } from '../../cartography/models/map/map-label';
|
||||
import { D3MapComponent } from '../../cartography/components/d3-map/d3-map.component';
|
||||
import { MapLinkNode } from '../../cartography/models/map/map-link-node';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -193,6 +194,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) {
|
||||
@ -295,6 +300,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);
|
||||
|
@ -2,8 +2,8 @@
|
||||
<div class="default-header">
|
||||
<div class="row">
|
||||
<h1 class="col">Projects</h1>
|
||||
<button class="col" mat-raised-button color="primary" (click)="addBlankProject()" class="add-button">Add blank project</button>
|
||||
<button class="col" mat-raised-button color="primary" (click)="importProject()" class="import-button">Import project</button>
|
||||
<button *ngIf="settings.experimental_features" class="col" mat-raised-button color="primary" (click)="addBlankProject()" class="add-button">Add blank project</button>
|
||||
<button *ngIf="settings.experimental_features" class="col" mat-raised-button color="primary" (click)="importProject()" class="import-button">Import project</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="default-content">
|
||||
|
@ -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