From 8b91549984f79b0486468848a780e3fb050af022 Mon Sep 17 00:00:00 2001 From: ziajka Date: Mon, 26 Nov 2018 13:36:33 +0100 Subject: [PATCH] Drag interface labels --- .../draggable-selection.component.ts | 51 ++++++- .../selection-control.component.ts | 26 +++- .../link-node-to-map-link-node-converter.ts | 10 +- .../map/link-to-map-link-converter.ts | 2 +- .../map/map-label-to-label-converter.ts | 18 +-- .../cartography/events/links-event-source.ts | 3 + src/app/cartography/models/interface-label.ts | 5 +- .../cartography/models/map/map-link-node.ts | 13 +- .../cartography/widgets/interface-label.ts | 132 +++++++++--------- .../project-map/project-map.component.ts | 23 +++ src/app/services/link.service.ts | 25 ++++ 11 files changed, 220 insertions(+), 88 deletions(-) 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/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/models/interface-label.ts b/src/app/cartography/models/interface-label.ts index 9f2e654c..4ebc1a2c 100644 --- a/src/app/cartography/models/interface-label.ts +++ b/src/app/cartography/models/interface-label.ts @@ -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, 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/interface-label.ts b/src/app/cartography/widgets/interface-label.ts index d9c4de0f..7ba84489 100644 --- a/src/app/cartography/widgets/interface-label.ts +++ b/src/app/cartography/widgets/interface-label.ts @@ -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(); + 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('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('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('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('rect') - // .attr('class', 'interface_label_border'); + enter + .append('rect') + .attr('class', 'interface_label_selection'); // create label enter .append('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(`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('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(); + merge + .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(); + 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/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}); + } }