diff --git a/package.json b/package.json index 2c6dcfca..fce0d3e0 100644 --- a/package.json +++ b/package.json @@ -92,8 +92,5 @@ "ignore": [ "typescript" ] - }, - "comments": [ - "Typescript should remain below 2.8.0, @todo: check later if packages were adjusted" - ] + } } diff --git a/src/app/cartography/cartography.module.ts b/src/app/cartography/cartography.module.ts index 2461f999..790e979a 100644 --- a/src/app/cartography/cartography.module.ts +++ b/src/app/cartography/cartography.module.ts @@ -41,6 +41,8 @@ import { SelectionControlComponent } from './components/selection-control/select 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'; +import { FontBBoxCalculator } from './helpers/font-bbox-calculator'; +import { StylesToFontConverter } from './converters/styles-to-font-converter'; @NgModule({ @@ -93,6 +95,8 @@ import { MapSettingsManager } from './managers/map-settings-manager'; MapSymbolsDataSource, SelectionEventSource, MapSettingsManager, + FontBBoxCalculator, + StylesToFontConverter, ...D3_MAP_IMPORTS ], exports: [ D3MapComponent, ExperimentalMapComponent ] diff --git a/src/app/cartography/converters/map/label-to-map-label-converter.ts b/src/app/cartography/converters/map/label-to-map-label-converter.ts index 5b154696..77a3b2e4 100644 --- a/src/app/cartography/converters/map/label-to-map-label-converter.ts +++ b/src/app/cartography/converters/map/label-to-map-label-converter.ts @@ -3,10 +3,18 @@ import { Injectable } from "@angular/core"; import { Converter } from "../converter"; import { Label } from "../../models/label"; import { MapLabel } from "../../models/map/map-label"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; +import { CssFixer } from '../../helpers/css-fixer'; +import { FontFixer } from '../../helpers/font-fixer'; @Injectable() export class LabelToMapLabelConverter implements Converter { + constructor( + private fontBBoxCalculator: FontBBoxCalculator, + private cssFixer: CssFixer, + private fontFixer: FontFixer + ) {} convert(label: Label, paramaters?: {[node_id: string]: string}) { const mapLabel = new MapLabel(); mapLabel.rotation = label.rotation; @@ -14,10 +22,26 @@ export class LabelToMapLabelConverter implements Converter { mapLabel.text = label.text; mapLabel.x = label.x; mapLabel.y = label.y; + mapLabel.originalX = label.x; + mapLabel.originalY = label.y; + if (paramaters !== undefined) { mapLabel.id = paramaters.node_id; mapLabel.nodeId = paramaters.node_id; } + + const fixedCss = this.cssFixer.fix(mapLabel.style); + const fixedFont = this.fontFixer.fixStyles(fixedCss); + const box = this.fontBBoxCalculator.calculate(mapLabel.text, fixedFont); + + if (mapLabel.x !== null) { + mapLabel.x += 3; + } + + if (mapLabel.y !== null) { + mapLabel.y += box.height; + } + return mapLabel; } } 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 512dccb8..7ec55f1a 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 @@ -3,17 +3,33 @@ import { Injectable } from "@angular/core"; import { Converter } from "../converter"; import { Label } from "../../models/label"; import { MapLabel } from "../../models/map/map-label"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; @Injectable() export class MapLabelToLabelConverter implements Converter { + constructor( + private fontBBoxCalculator: FontBBoxCalculator + ) {} + convert(mapLabel: MapLabel) { + const box = this.fontBBoxCalculator.calculate(mapLabel.text, mapLabel.style); + const label = new Label(); label.rotation = mapLabel.rotation; label.style = mapLabel.style; label.text = mapLabel.text; label.x = mapLabel.x; label.y = mapLabel.y; + + if (label.x !== null) { + label.x += 3; + } + + if (label.y !== null) { + label.y -= box.height; + } + return label; } } diff --git a/src/app/cartography/converters/map/map-node-to-node-converter.ts b/src/app/cartography/converters/map/map-node-to-node-converter.ts index d8d02111..86e9fa40 100644 --- a/src/app/cartography/converters/map/map-node-to-node-converter.ts +++ b/src/app/cartography/converters/map/map-node-to-node-converter.ts @@ -5,38 +5,41 @@ import { MapNode } from "../../models/map/map-node"; import { MapLabelToLabelConverter } from "./map-label-to-label-converter"; import { MapPortToPortConverter } from "./map-port-to-port-converter"; import { Node } from "../../models/node"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; +import { CssFixer } from '../../helpers/css-fixer'; +import { FontFixer } from '../../helpers/font-fixer'; @Injectable() export class MapNodeToNodeConverter implements Converter { - constructor( - private mapLabelToLabel: MapLabelToLabelConverter, - private mapPortToPort: MapPortToPortConverter - ) {} - - convert(mapNode: MapNode) { - const node = new Node(); - node.node_id = mapNode.id; - node.command_line = mapNode.commandLine; - node.compute_id = mapNode.computeId; - node.console = mapNode.console; - node.console_host = mapNode.consoleHost; - node.first_port_name = mapNode.firstPortName; - node.height = mapNode.height; - node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined; - node.name = mapNode.name; - node.node_directory = mapNode.nodeDirectory; - node.node_type = mapNode.nodeType; - node.port_name_format = mapNode.portNameFormat; - node.port_segment_size = mapNode.portSegmentSize; - node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : []; - node.project_id = mapNode.projectId; - node.status = mapNode.status; - node.symbol = mapNode.symbol; - node.width = mapNode.width; - node.x = mapNode.x; - node.y = mapNode.y; - node.z = mapNode.z; - return node; - } + constructor( + private mapLabelToLabel: MapLabelToLabelConverter, + private mapPortToPort: MapPortToPortConverter + ) {} + + convert(mapNode: MapNode) { + const node = new Node(); + node.node_id = mapNode.id; + node.command_line = mapNode.commandLine; + node.compute_id = mapNode.computeId; + node.console = mapNode.console; + node.console_host = mapNode.consoleHost; + node.first_port_name = mapNode.firstPortName; + node.height = mapNode.height; + node.label = mapNode.label ? this.mapLabelToLabel.convert(mapNode.label) : undefined; + node.name = mapNode.name; + node.node_directory = mapNode.nodeDirectory; + node.node_type = mapNode.nodeType; + node.port_name_format = mapNode.portNameFormat; + node.port_segment_size = mapNode.portSegmentSize; + node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : []; + node.project_id = mapNode.projectId; + node.status = mapNode.status; + node.symbol = mapNode.symbol; + node.width = mapNode.width; + node.x = mapNode.x; + node.y = mapNode.y; + node.z = mapNode.z; + return node; + } } diff --git a/src/app/cartography/converters/map/node-to-map-node-converter.ts b/src/app/cartography/converters/map/node-to-map-node-converter.ts index 3ea82974..c1a25e67 100644 --- a/src/app/cartography/converters/map/node-to-map-node-converter.ts +++ b/src/app/cartography/converters/map/node-to-map-node-converter.ts @@ -5,38 +5,56 @@ import { MapNode } from "../../models/map/map-node"; import { Node } from "../../models/node"; import { LabelToMapLabelConverter } from "./label-to-map-label-converter"; import { PortToMapPortConverter } from "./port-to-map-port-converter"; +import { FontBBoxCalculator } from '../../helpers/font-bbox-calculator'; +import { CssFixer } from '../../helpers/css-fixer'; +import { FontFixer } from '../../helpers/font-fixer'; @Injectable() export class NodeToMapNodeConverter implements Converter { - constructor( - private labelToMapLabel: LabelToMapLabelConverter, - private portToMapPort: PortToMapPortConverter - ) {} + constructor( + private labelToMapLabel: LabelToMapLabelConverter, + private portToMapPort: PortToMapPortConverter, + private fontBBoxCalculator: FontBBoxCalculator, + private cssFixer: CssFixer, + private fontFixer: FontFixer + ) {} - convert(node: Node) { - const mapNode = new MapNode(); - mapNode.id = node.node_id; - mapNode.commandLine = node.command_line; - mapNode.computeId = node.compute_id; - mapNode.console = node.console; - mapNode.consoleHost = node.console_host; - mapNode.firstPortName = node.first_port_name; - mapNode.height = node.height; - mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id }); - mapNode.name = node.name; - mapNode.nodeDirectory = node.node_directory; - mapNode.nodeType = node.node_type; - mapNode.portNameFormat = node.port_name_format; - mapNode.portSegmentSize = node.port_segment_size; - mapNode.ports = node.ports.map((port) => this.portToMapPort.convert(port)); - mapNode.projectId = node.project_id; - mapNode.status = node.status; - mapNode.symbol = node.symbol; - mapNode.width = node.width; - mapNode.x = node.x; - mapNode.y = node.y; - mapNode.z = node.z; - return mapNode; + convert(node: Node) { + const mapNode = new MapNode(); + mapNode.id = node.node_id; + mapNode.commandLine = node.command_line; + mapNode.computeId = node.compute_id; + mapNode.console = node.console; + mapNode.consoleHost = node.console_host; + mapNode.firstPortName = node.first_port_name; + mapNode.height = node.height; + mapNode.label = this.labelToMapLabel.convert(node.label, { node_id: node.node_id }); + mapNode.name = node.name; + mapNode.nodeDirectory = node.node_directory; + mapNode.nodeType = node.node_type; + mapNode.portNameFormat = node.port_name_format; + mapNode.portSegmentSize = node.port_segment_size; + mapNode.ports = node.ports.map((port) => this.portToMapPort.convert(port)); + mapNode.projectId = node.project_id; + mapNode.status = node.status; + mapNode.symbol = node.symbol; + mapNode.width = node.width; + mapNode.x = node.x; + mapNode.y = node.y; + mapNode.z = node.z; + + + if (mapNode.label !== undefined) { + const fixedCss = this.cssFixer.fix(mapNode.label.style); + const fixedFont = this.fontFixer.fixStyles(fixedCss); + const box = this.fontBBoxCalculator.calculate(mapNode.label.text, fixedFont); + + if (node.label.x === null || node.label.y === null) { + mapNode.label.x = node.width / 2. - box.width / 2. + 3; + mapNode.label.y = -8; + } } + return mapNode; + } } diff --git a/src/app/cartography/converters/styles-to-font-converter.spec.ts b/src/app/cartography/converters/styles-to-font-converter.spec.ts new file mode 100644 index 00000000..5eda7947 --- /dev/null +++ b/src/app/cartography/converters/styles-to-font-converter.spec.ts @@ -0,0 +1,24 @@ +import { Font } from "../models/font"; +import { StylesToFontConverter } from './styles-to-font-converter'; + + +describe('StylesToFontConverter', () => { + let converter: StylesToFontConverter; + + beforeEach(() => { + converter = new StylesToFontConverter(); + }); + + it('should parse fonts from styles', () => { + const styles = "font-family: TypeWriter; font-size: 10px; font-weight: bold"; + + const expectedFont: Font = { + 'font_family': 'TypeWriter', + 'font_size': 10, + 'font_weight': 'bold' + }; + + expect(converter.convert(styles)).toEqual(expectedFont); + }); + +}); diff --git a/src/app/cartography/converters/styles-to-font-converter.ts b/src/app/cartography/converters/styles-to-font-converter.ts new file mode 100644 index 00000000..0c2f8826 --- /dev/null +++ b/src/app/cartography/converters/styles-to-font-converter.ts @@ -0,0 +1,47 @@ +import * as csstree from 'css-tree'; + +import { Injectable } from "@angular/core"; +import { Converter } from './converter'; +import { Font } from '../models/font'; + + +@Injectable() +export class StylesToFontConverter implements Converter { + convert(styles: string) { + const font: Font = { + 'font_family': undefined, + 'font_size': undefined, + 'font_weight': undefined + }; + + const ast = csstree.parse(styles, { + 'context': 'declarationList' + }); + + ast.children.forEach((child) => { + if (child.property === 'font-size') { + child.value.children.forEach((value) => { + if (value.type === 'Dimension') { + font.font_size = parseInt(value.value); + } + }); + } + if (child.property === 'font-family') { + child.value.children.forEach((value) => { + if (value.type === "Identifier") { + font.font_family = value.name; + } + }); + } + if (child.property === 'font-weight') { + child.value.children.forEach((value) => { + if (value.type === "Identifier") { + font.font_weight = value.name; + } + }); + } + }); + + return font; + } +} diff --git a/src/app/cartography/events/draggable.ts b/src/app/cartography/events/draggable.ts index 2d5f057a..8bcd5f18 100644 --- a/src/app/cartography/events/draggable.ts +++ b/src/app/cartography/events/draggable.ts @@ -62,7 +62,6 @@ export class Draggable { const evt = new DraggableDrag(datum); evt.dx = event.sourceEvent.clientX - lastX; evt.dy = event.sourceEvent.clientY - lastY; - lastX = event.sourceEvent.clientX; lastY = event.sourceEvent.clientY; diff --git a/src/app/cartography/helpers/css-fixer.ts b/src/app/cartography/helpers/css-fixer.ts index fe213eb6..c5a8a226 100644 --- a/src/app/cartography/helpers/css-fixer.ts +++ b/src/app/cartography/helpers/css-fixer.ts @@ -6,7 +6,7 @@ import { Injectable } from "@angular/core"; @Injectable() export class CssFixer { - public fix(styles: string) { + public fix(styles: string): string { const ast = csstree.parse(styles, { 'context': 'declarationList' }); diff --git a/src/app/cartography/helpers/font-bbox-calculator.spec.ts b/src/app/cartography/helpers/font-bbox-calculator.spec.ts new file mode 100644 index 00000000..52417102 --- /dev/null +++ b/src/app/cartography/helpers/font-bbox-calculator.spec.ts @@ -0,0 +1,24 @@ +import { FontBBoxCalculator } from "./font-bbox-calculator"; + + +describe('FontBBoxCalculator', () => { + let calculator: FontBBoxCalculator; + + beforeEach(() => { + calculator = new FontBBoxCalculator(); + }); + + it('should calculate font width and height', () => { + const box = calculator.calculate("My text", "font-family:Arial; font-size: 12px; font-weight:bold"); + + expect(box.height).toEqual(14); + expect(box.width).toEqual(41.34375); + }); + + it('should calculate font width and height for different font', () => { + const box = calculator.calculate("My text", "font-family:Tahoma; font-size: 14px; font-weight:bold"); + + expect(box.height).toEqual(15); + expect(box.width).toEqual(46.25); + }); +}); diff --git a/src/app/cartography/helpers/font-bbox-calculator.ts b/src/app/cartography/helpers/font-bbox-calculator.ts new file mode 100644 index 00000000..cf689e9b --- /dev/null +++ b/src/app/cartography/helpers/font-bbox-calculator.ts @@ -0,0 +1,20 @@ +import { Injectable } from "@angular/core"; + +@Injectable() +export class FontBBoxCalculator { + calculate(text: string, styles: string) { + const element = document.createElement("text"); + element.innerText = text; + element.setAttribute("fill", "#00000"); + element.setAttribute("fill-opacity", "0"); + element.setAttribute("style", styles); + document.documentElement.appendChild(element); + const bbox = element.getBoundingClientRect(); + document.documentElement.removeChild(element); + + return { + width: bbox.width, + height: bbox.height + } + } +} \ No newline at end of file diff --git a/src/app/cartography/models/map/map-label.ts b/src/app/cartography/models/map/map-label.ts index 6dd1dd1d..a42dd193 100644 --- a/src/app/cartography/models/map/map-label.ts +++ b/src/app/cartography/models/map/map-label.ts @@ -7,5 +7,7 @@ export class MapLabel implements Indexed { text: string; x: number; y: number; + originalX: number; + originalY: number; nodeId: string; } diff --git a/src/app/cartography/widgets/label.ts b/src/app/cartography/widgets/label.ts index 1f15664c..ffbc4093 100644 --- a/src/app/cartography/widgets/label.ts +++ b/src/app/cartography/widgets/label.ts @@ -22,7 +22,7 @@ export class LabelWidget implements Widget { private cssFixer: CssFixer, private fontFixer: FontFixer, private selectionManager: SelectionManager, - private mapSettings: MapSettingsManager, + private mapSettings: MapSettingsManager ) {} public redrawLabel(view: SVGSelection, label: MapLabel) { @@ -56,6 +56,8 @@ export class LabelWidget implements Widget { private drawLabel(view: SVGSelection) { + const self = this; + const label_body = view.selectAll("g.label_body") .data((label) => [label]); @@ -77,31 +79,14 @@ export class LabelWidget implements Widget { label_body_merge .select('text.label') .attr('label_id', (l: MapLabel) => l.id) - // .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way .attr('style', (l: MapLabel) => { let styles = this.cssFixer.fix(l.style); styles = this.fontFixer.fixStyles(styles); return styles; }) .text((l: MapLabel) => l.text) - .attr('x', function (this: SVGTextElement, l: MapLabel) { - // if (l.x === null) { - // // center - // const bbox = this.getBBox(); - // return -bbox.width / 2.; - // } - return l.x + LabelWidget.NODE_LABEL_MARGIN; - }) - .attr('y', function (this: SVGTextElement, l: MapLabel) { - let bbox = this.getBBox(); - - // if (n.label.x === null) { - // // center - // bbox = this.getBBox(); - // return - n.height / 2. - bbox.height ; - // } - return l.y + bbox.height - LabelWidget.NODE_LABEL_MARGIN - bbox.height*0.14; - }) + .attr('x', (l: MapLabel) => l.x) + .attr('y', (l: MapLabel) => l.y) .attr('transform', (l: MapLabel) => { return `rotate(${l.rotation}, ${l.x}, ${l.y})`; }) diff --git a/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts b/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts index 4b609013..61a1cc1f 100644 --- a/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts +++ b/src/app/components/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts @@ -23,6 +23,9 @@ import { MapNode } from '../../../cartography/models/map/map-node'; import { MapLabelToLabelConverter } from '../../../cartography/converters/map/map-label-to-label-converter'; import { MapPortToPortConverter } from '../../../cartography/converters/map/map-port-to-port-converter'; import { Node } from '../../../cartography/models/node'; +import { FontBBoxCalculator } from '../../../cartography/helpers/font-bbox-calculator'; +import { CssFixer } from '../../../cartography/helpers/css-fixer'; +import { FontFixer } from '../../../cartography/helpers/font-fixer'; describe('ProjectMapShortcutsComponent', () => { @@ -56,7 +59,11 @@ describe('ProjectMapShortcutsComponent', () => { SettingsService, MapNodeToNodeConverter, MapLabelToLabelConverter, - MapPortToPortConverter + MapPortToPortConverter, + MapLabelToLabelConverter, + FontBBoxCalculator, + CssFixer, + FontFixer ], declarations: [ ProjectMapShortcutsComponent ] }) diff --git a/src/app/components/project-map/project-map.component.ts b/src/app/components/project-map/project-map.component.ts index 6e62d8be..82da3f5b 100644 --- a/src/app/components/project-map/project-map.component.ts +++ b/src/app/components/project-map/project-map.component.ts @@ -46,8 +46,8 @@ 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'; import { TextElement } from '../../cartography/models/drawings/text-element'; -import { select } from 'd3-selection'; import { FontFixer } from '../../cartography/helpers/font-fixer'; +import { MapLabelToLabelConverter } from '../../cartography/converters/map/map-label-to-label-converter'; @Component({ @@ -112,6 +112,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private linksEventSource: LinksEventSource, private mapDrawingToSvgConverter: MapDrawingToSvgConverter, private settingsService: SettingsService, + private mapLabelToLabel: MapLabelToLabelConverter ) {} ngOnInit() { @@ -259,7 +260,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy { onNodeCreation(appliance: Appliance) { this.nodeService .createFromAppliance(this.server, this.project, appliance, 0, 0, 'local') - .subscribe(() => { + .subscribe((createdNode: Node) => { this.projectService .nodes(this.server, this.project.project_id) .subscribe((nodes: Node[]) => { @@ -282,8 +283,12 @@ export class ProjectMapComponent implements OnInit, OnDestroy { private onNodeLabelDragged(draggedEvent: DraggedDataEvent) { const node = this.nodesDataSource.get(draggedEvent.datum.nodeId); - node.label.x += draggedEvent.dx; - node.label.y += draggedEvent.dy; + const mapLabel = draggedEvent.datum; + mapLabel.x += draggedEvent.dx; + mapLabel.y += draggedEvent.dy; + + const label = this.mapLabelToLabel.convert(mapLabel); + node.label = label; this.nodeService .updateLabel(this.server, node, node.label)