diff --git a/src/app/cartography/cartography.module.ts b/src/app/cartography/cartography.module.ts index 5898a32c..bf50f5ec 100644 --- a/src/app/cartography/cartography.module.ts +++ b/src/app/cartography/cartography.module.ts @@ -40,6 +40,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({ @@ -90,6 +91,7 @@ import { DraggableSelectionComponent } from './components/draggable-selection/dr MapDrawingsDataSource, MapSymbolsDataSource, SelectionEventSource, + MapSettingsManager, ...D3_MAP_IMPORTS ], exports: [ D3MapComponent, ExperimentalMapComponent ] diff --git a/src/app/cartography/components/d3-map/d3-map.component.ts b/src/app/cartography/components/d3-map/d3-map.component.ts index 416df23a..77949d08 100644 --- a/src/app/cartography/components/d3-map/d3-map.component.ts +++ b/src/app/cartography/components/d3-map/d3-map.component.ts @@ -6,19 +6,18 @@ 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'; import { MovingTool } from '../../tools/moving-tool'; import { MapChangeDetectorRef } from '../../services/map-change-detector-ref'; 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 { MapSettingsManager } from '../../managers/map-settings-manager'; @Component({ @@ -51,9 +50,8 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy { private 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, @@ -84,8 +82,7 @@ export class D3MapComponent implements OnInit, OnChanges, OnDestroy { @Input('draw-link-tool') drawLinkTool: boolean; @Input('readonly') set readonly(value) { - this.nodesWidget.draggingEnabled = !value; - this.drawingsWidget.draggingEnabled = !value; + this.mapSettings.isReadOnly = value; } ngOnChanges(changes: { [propKey: string]: SimpleChange }) { diff --git a/src/app/cartography/components/draggable-selection/draggable-selection.component.ts b/src/app/cartography/components/draggable-selection/draggable-selection.component.ts index bdca40e1..d7289289 100644 --- a/src/app/cartography/components/draggable-selection/draggable-selection.component.ts +++ b/src/app/cartography/components/draggable-selection/draggable-selection.component.ts @@ -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) => { 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) => { 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) => { 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(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(label, evt.dx, evt.dy)); + }); + }); + } ngOnDestroy() { diff --git a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts b/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts index 66845a31..7fd0e837 100644 --- a/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts +++ b/src/app/cartography/components/draw-link-tool/draw-link-tool.component.ts @@ -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) { diff --git a/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html index ad6d71c2..a9f62c84 100644 --- a/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html +++ b/src/app/cartography/components/experimental-map/drawing/drawings/text/text.component.html @@ -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}} \ No newline at end of file diff --git a/src/app/cartography/components/selection-control/selection-control.component.ts b/src/app/cartography/components/selection-control/selection-control.component.ts index 6d603cd7..81abb854 100644 --- a/src/app/cartography/components/selection-control/selection-control.component.ts +++ b/src/app/cartography/components/selection-control/selection-control.component.ts @@ -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); diff --git a/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts b/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts index 48f3cc2e..794ccc16 100644 --- a/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts +++ b/src/app/cartography/converters/map/link-node-to-map-link-node-converter.ts @@ -9,15 +9,21 @@ import { MapLinkNode } from "../../models/map/map-link-node"; @Injectable() export class LinkNodeToMapLinkNodeConverter implements Converter { 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; } } diff --git a/src/app/cartography/converters/map/link-to-map-link-converter.ts b/src/app/cartography/converters/map/link-to-map-link-converter.ts index 0000ad78..71c052f4 100644 --- a/src/app/cartography/converters/map/link-to-map-link-converter.ts +++ b/src/app/cartography/converters/map/link-to-map-link-converter.ts @@ -19,7 +19,7 @@ export class LinkToMapLinkConverter implements Converter { 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; } diff --git a/src/app/cartography/converters/map/map-label-to-label-converter.ts b/src/app/cartography/converters/map/map-label-to-label-converter.ts index 39b17e4f..512dccb8 100644 --- a/src/app/cartography/converters/map/map-label-to-label-converter.ts +++ b/src/app/cartography/converters/map/map-label-to-label-converter.ts @@ -7,13 +7,13 @@ import { MapLabel } from "../../models/map/map-label"; @Injectable() export class MapLabelToLabelConverter implements Converter { - 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; + } } diff --git a/src/app/cartography/events/links-event-source.ts b/src/app/cartography/events/links-event-source.ts index 5632a0e4..71d091cd 100644 --- a/src/app/cartography/events/links-event-source.ts +++ b/src/app/cartography/events/links-event-source.ts @@ -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(); + public interfaceDragged = new EventEmitter>(); } diff --git a/src/app/cartography/managers/map-settings-manager.ts b/src/app/cartography/managers/map-settings-manager.ts new file mode 100644 index 00000000..b0cc4ffb --- /dev/null +++ b/src/app/cartography/managers/map-settings-manager.ts @@ -0,0 +1,7 @@ +import { Injectable } from "@angular/core"; + + +@Injectable() +export class MapSettingsManager { + public isReadOnly = false; +} \ No newline at end of file diff --git a/src/app/cartography/models/interface-label.ts b/src/app/cartography/models/interface-label.ts deleted file mode 100644 index 9f2e654c..00000000 --- a/src/app/cartography/models/interface-label.ts +++ /dev/null @@ -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, - ) {} -} diff --git a/src/app/cartography/models/map/map-link-node.ts b/src/app/cartography/models/map/map-link-node.ts index baf81c73..9cf37ade 100644 --- a/src/app/cartography/models/map/map-link-node.ts +++ b/src/app/cartography/models/map/map-link-node.ts @@ -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; } diff --git a/src/app/cartography/widgets/drawings.ts b/src/app/cartography/widgets/drawings.ts index d20988a9..dad1c7af 100644 --- a/src/app/cartography/widgets/drawings.ts +++ b/src/app/cartography/widgets/drawings.ts @@ -7,12 +7,12 @@ import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter"; import { Draggable } from "../events/draggable"; import { DrawingWidget } from "./drawing"; import { MapDrawing } from "../models/map/map-drawing"; +import { MapSettingsManager } from "../managers/map-settings-manager"; @Injectable() export class DrawingsWidget implements Widget { public draggable = new Draggable(); - public draggingEnabled = false; // public onContextMenu = new EventEmitter(); // public onDrawingClicked = new EventEmitter(); @@ -22,6 +22,7 @@ export class DrawingsWidget implements Widget { constructor( private drawingWidget: DrawingWidget, private svgToDrawingConverter: SvgToDrawingConverter, + private mapSettings: MapSettingsManager ) { this.svgToDrawingConverter = new SvgToDrawingConverter(); } @@ -59,7 +60,7 @@ export class DrawingsWidget implements Widget { .exit() .remove(); - if (this.draggingEnabled) { + if (!this.mapSettings.isReadOnly) { this.draggable.call(merge); } } diff --git a/src/app/cartography/widgets/drawings/text-drawing.spec.ts b/src/app/cartography/widgets/drawings/text-drawing.spec.ts index 1d75f237..c99a87e2 100644 --- a/src/app/cartography/widgets/drawings/text-drawing.spec.ts +++ b/src/app/cartography/widgets/drawings/text-drawing.spec.ts @@ -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', () => { diff --git a/src/app/cartography/widgets/drawings/text-drawing.ts b/src/app/cartography/widgets/drawings/text-drawing.ts index 49f79cf7..7db42cc2 100644 --- a/src/app/cartography/widgets/drawings/text-drawing.ts +++ b/src/app/cartography/widgets/drawings/text-drawing.ts @@ -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() diff --git a/src/app/cartography/widgets/interface-label.spec.ts b/src/app/cartography/widgets/interface-label.spec.ts index cffd5428..9d1147e2 100644 --- a/src/app/cartography/widgets/interface-label.spec.ts +++ b/src/app/cartography/widgets/interface-label.spec.ts @@ -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; 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('g.interface_label_container'); + const drew = svg.canvas.selectAll('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('g.interface_label_container'); + const drew = svg.canvas.selectAll('g.interface_label_container'); expect(drew.nodes().length).toEqual(0); }); diff --git a/src/app/cartography/widgets/interface-label.ts b/src/app/cartography/widgets/interface-label.ts index d719583d..527aaaae 100644 --- a/src/app/cartography/widgets/interface-label.ts +++ b/src/app/cartography/widgets/interface-label.ts @@ -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(); + 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('g.link_node_position') + .data((link: MapLink) => [ + [link.source, link.nodes[0]], + [link.target, link.nodes[1]] + ]); - const labels = selection - .selectAll('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('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('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('rect') - .attr('class', 'interface_label_border'); + .attr('class', 'interface_label_selection'); // create label enter .append('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('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('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('text'); - const bbox = text.node().getBBox(); + .select('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(`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); + } } } diff --git a/src/app/cartography/widgets/label.ts b/src/app/cartography/widgets/label.ts index 2a016963..1f15664c 100644 --- a/src/app/cartography/widgets/label.ts +++ b/src/app/cartography/widgets/label.ts @@ -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})`; diff --git a/src/app/cartography/widgets/nodes.spec.ts b/src/app/cartography/widgets/nodes.spec.ts index f26236e4..77fe84e4 100644 --- a/src/app/cartography/widgets/nodes.spec.ts +++ b/src/app/cartography/widgets/nodes.spec.ts @@ -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(() => { diff --git a/src/app/cartography/widgets/nodes.ts b/src/app/cartography/widgets/nodes.ts index 63496c93..60dd0ab7 100644 --- a/src/app/cartography/widgets/nodes.ts +++ b/src/app/cartography/widgets/nodes.ts @@ -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(); - 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); } } diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index dd516fe7..ebf277cd 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -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) { + 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); diff --git a/src/app/components/projects/projects.component.html b/src/app/components/projects/projects.component.html index 2726427b..28b7b962 100644 --- a/src/app/components/projects/projects.component.html +++ b/src/app/components/projects/projects.component.html @@ -2,8 +2,8 @@

Projects

- - + +
diff --git a/src/app/services/link.service.ts b/src/app/services/link.service.ts index 06b262c3..5048cb8c 100644 --- a/src/app/services/link.service.ts +++ b/src/app/services/link.service.ts @@ -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}); + } }