diff --git a/src/app/cartography/shared/helpers/font-fixer.spec.ts b/src/app/cartography/shared/helpers/font-fixer.spec.ts new file mode 100644 index 00000000..80639920 --- /dev/null +++ b/src/app/cartography/shared/helpers/font-fixer.spec.ts @@ -0,0 +1,39 @@ +import { FontFixer } from "./font-fixer"; +import { Font } from "../models/font"; + + +describe('FontFixer', () => { + let fixer: FontFixer; + + beforeEach(() => { + fixer = new FontFixer(); + }); + + it('should fix TypeWriter font and 10px size', () => { + const font: Font = { + 'font_family': 'TypeWriter', + 'font_size': 10, + 'font_weight': 'bold' + }; + + expect(fixer.fix(font)).toEqual( { + 'font_family': 'Noto Sans', + 'font_size': 11, + 'font_weight': 'bold' + }); + }); + + it('should not fix other fonts', () => { + const font: Font = { + 'font_family': 'OtherFont', + 'font_size': 11, + 'font_weight': 'bold' + }; + + expect(fixer.fix(font)).toEqual( { + 'font_family': 'OtherFont', + 'font_size': 11, + 'font_weight': 'bold' + }); + }); +}); diff --git a/src/app/cartography/shared/helpers/font-fixer.ts b/src/app/cartography/shared/helpers/font-fixer.ts new file mode 100644 index 00000000..28056486 --- /dev/null +++ b/src/app/cartography/shared/helpers/font-fixer.ts @@ -0,0 +1,21 @@ +import { Injectable } from "@angular/core"; +import { Font } from "../models/font"; + +/** + * GNS3 GUI doesn't update font when cannot find font in user system, this fixer fixes it + */ +@Injectable() +export class FontFixer { + static DEFAULT_FONT = "TypeWriter"; + static DEFAULT_SIZE = 10; + static REPLACE_BY_FONT = "Noto Sans"; + static REPLACE_BY_SIZE = 11; + + public fix(font: Font): Font { + if (font.font_family === FontFixer.DEFAULT_FONT && font.font_size === FontFixer.DEFAULT_SIZE) { + font.font_family = FontFixer.REPLACE_BY_FONT; + font.font_size = FontFixer.REPLACE_BY_SIZE; + } + return font; + } +} diff --git a/src/app/cartography/shared/helpers/qt-dasharray-fixer.spec.ts b/src/app/cartography/shared/helpers/qt-dasharray-fixer.spec.ts new file mode 100644 index 00000000..60cabad5 --- /dev/null +++ b/src/app/cartography/shared/helpers/qt-dasharray-fixer.spec.ts @@ -0,0 +1,18 @@ +import { QtDasharrayFixer } from "./qt-dasharray-fixer"; + + +describe('QtDashArrayFixer', () => { + let fixer: QtDasharrayFixer; + + beforeEach(() => { + fixer = new QtDasharrayFixer(); + }); + + it('should fix when matches mapping', () => { + expect(fixer.fix('25, 25')).toEqual('10, 2'); + }); + + it('should not fix when do not match mapping', () => { + expect(fixer.fix('1, 2, 3')).toEqual('1, 2, 3'); + }); +}); diff --git a/src/app/cartography/shared/helpers/qt-dasharray-fixer.ts b/src/app/cartography/shared/helpers/qt-dasharray-fixer.ts new file mode 100644 index 00000000..7f204fe2 --- /dev/null +++ b/src/app/cartography/shared/helpers/qt-dasharray-fixer.ts @@ -0,0 +1,23 @@ +import { Injectable } from "@angular/core"; +import { Font } from "../models/font"; + +/** + * GNS3 GUI performs mapping from QT styles to SVG dasharray, but styles don't match + * what you can see, here are improvements; later on please adjust GUI for proper values. + */ +@Injectable() +export class QtDasharrayFixer { + static MAPPING = { + '25, 25': '10, 2', + '5, 25': '4, 2', + '5, 25, 25': '5, 5, 1, 5', + '25, 25, 5, 25, 5': '5, 2, 5, 2, 5' + }; + + public fix(dasharray: string): string { + if (dasharray in QtDasharrayFixer.MAPPING) { + return QtDasharrayFixer.MAPPING[dasharray]; + } + return dasharray; + } +} diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.spec.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.spec.ts index acef0085..5af1f29d 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.spec.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.spec.ts @@ -14,6 +14,7 @@ describe('EllipseConverter', () => { node.setAttribute("fill-opacity", "1.0"); node.setAttribute("stroke", "#000000"); node.setAttribute("stroke-width", "2"); + node.setAttribute("stroke-dasharray", "5,25,25"); node.setAttribute("cx", "63"); node.setAttribute("cy", "59"); node.setAttribute("rx", "63"); @@ -24,6 +25,7 @@ describe('EllipseConverter', () => { expect(drawing.fill_opacity).toEqual(1.0); expect(drawing.stroke).toEqual("#000000"); expect(drawing.stroke_width).toEqual(2.0); + expect(drawing.stroke_dasharray).toEqual("5,25,25"); expect(drawing.cx).toEqual(63); expect(drawing.cy).toEqual(59); expect(drawing.rx).toEqual(63); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.ts index 435d689b..a24d3929 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.ts @@ -13,7 +13,7 @@ export class EllipseConverter implements SvgConverter { const fill_opacity = node.attributes.getNamedItem("fill-opacity"); if (fill) { - drawing.fill_opacity = parseInt(fill_opacity.value, 10); + drawing.fill_opacity = parseFloat(fill_opacity.value); } const stroke = node.attributes.getNamedItem("stroke"); @@ -26,6 +26,11 @@ export class EllipseConverter implements SvgConverter { drawing.stroke_width = parseInt(stroke_width.value, 10); } + const stroke_dasharray = node.attributes.getNamedItem("stroke-dasharray"); + if (stroke_dasharray) { + drawing.stroke_dasharray = stroke_dasharray.value; + } + const cx = node.attributes.getNamedItem('cx'); if (cx) { drawing.cx = parseInt(cx.value, 10); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.spec.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.spec.ts index 377826a2..47bdb643 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.spec.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.spec.ts @@ -12,6 +12,7 @@ describe('LineConverter', () => { const node = document.createElement("line"); node.setAttribute("stroke", "#000000"); node.setAttribute("stroke-width", "2"); + node.setAttribute("stroke-dasharray", "5,25,25"); node.setAttribute("x1", "10.10"); node.setAttribute("x2", "20"); node.setAttribute("y1", "30"); @@ -20,6 +21,7 @@ describe('LineConverter', () => { const drawing = lineConverter.convert(node); expect(drawing.stroke).toEqual("#000000"); expect(drawing.stroke_width).toEqual(2.0); + expect(drawing.stroke_dasharray).toEqual("5,25,25"); expect(drawing.x1).toEqual(10); expect(drawing.x2).toEqual(20); expect(drawing.y1).toEqual(30); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.ts index a8227f0d..ecd9b19d 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.ts @@ -16,6 +16,11 @@ export class LineConverter implements SvgConverter { drawing.stroke_width = parseInt(stroke_width.value, 10); } + const stroke_dasharray = node.attributes.getNamedItem("stroke-dasharray"); + if (stroke_dasharray) { + drawing.stroke_dasharray = stroke_dasharray.value; + } + const x1 = node.attributes.getNamedItem('x1'); if (x1) { drawing.x1 = parseInt(x1.value, 10); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.spec.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.spec.ts index af904ced..83408307 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.spec.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.spec.ts @@ -11,18 +11,19 @@ describe('RectConverter', () => { it('should parse attributes', () => { const node = document.createElement("rect"); node.setAttribute("fill", "#ffffff"); - node.setAttribute("fill-opacity", "1.0"); + node.setAttribute("fill-opacity", "0.7"); node.setAttribute("stroke", "#000000"); node.setAttribute("stroke-width", "2"); + node.setAttribute("stroke-dasharray", "5,25,25"); node.setAttribute("width", "100px"); node.setAttribute("height", "200px"); const drawing = rectConverter.convert(node); expect(drawing.fill).toEqual("#ffffff"); - expect(drawing.fill_opacity).toEqual(1.0); + expect(drawing.fill_opacity).toEqual(0.7); expect(drawing.stroke).toEqual("#000000"); - expect(drawing.stroke_width).toEqual(2.0); + expect(drawing.stroke_dasharray).toEqual("5,25,25"); expect(drawing.width).toEqual(100); expect(drawing.height).toEqual(200); }); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.ts index 8815c960..504e2178 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.ts @@ -13,7 +13,7 @@ export class RectConverter implements SvgConverter { const fill_opacity = node.attributes.getNamedItem("fill-opacity"); if (fill) { - drawing.fill_opacity = parseInt(fill_opacity.value, 10); + drawing.fill_opacity = parseFloat(fill_opacity.value); } const stroke = node.attributes.getNamedItem("stroke"); @@ -26,6 +26,11 @@ export class RectConverter implements SvgConverter { drawing.stroke_width = parseInt(stroke_width.value, 10); } + const stroke_dasharray = node.attributes.getNamedItem("stroke-dasharray"); + if (stroke_dasharray) { + drawing.stroke_dasharray = stroke_dasharray.value; + } + const width = node.attributes.getNamedItem('width'); if (width) { drawing.width = parseInt(width.value, 10); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.spec.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.spec.ts index f2d18c58..860b4016 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.spec.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.spec.ts @@ -16,6 +16,7 @@ describe('TextConverter', () => { node.setAttribute("font-family", "TypeWriter"); node.setAttribute("font-size", "10.0"); node.setAttribute("font-weight", "bold"); + node.setAttribute("text-decoration", "line-through"); const drawing = textConverter.convert(node); expect(drawing.text).toEqual("Text"); @@ -24,6 +25,7 @@ describe('TextConverter', () => { expect(drawing.font_family).toEqual("TypeWriter"); expect(drawing.font_size).toEqual(10.0); expect(drawing.font_weight).toEqual("bold"); + expect(drawing.text_decoration).toEqual("line-through"); }); }); diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.ts index 78180d40..84a774e2 100644 --- a/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.ts +++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.ts @@ -15,7 +15,7 @@ export class TextConverter implements SvgConverter { const fill_opacity = node.attributes.getNamedItem('fill-opacity'); if (fill_opacity) { - drawing.fill_opacity = +fill_opacity.value; + drawing.fill_opacity = parseFloat(fill_opacity.value); } const font_family = node.attributes.getNamedItem('font-family'); @@ -33,6 +33,12 @@ export class TextConverter implements SvgConverter { drawing.font_weight = font_weight.value; } + const text_decoration = node.attributes.getNamedItem('text-decoration'); + if (text_decoration) { + drawing.text_decoration = text_decoration.value; + } + + return drawing; } } diff --git a/src/app/cartography/shared/managers/selection-manager.spec.ts b/src/app/cartography/shared/managers/selection-manager.spec.ts index 4be36928..4fc83d8c 100644 --- a/src/app/cartography/shared/managers/selection-manager.spec.ts +++ b/src/app/cartography/shared/managers/selection-manager.spec.ts @@ -83,7 +83,7 @@ describe('SelectionManager', () => { const node = new Node(); node.node_id = "test1"; const drawing = new Drawing(); - drawing.drawing_id = "test1" + drawing.drawing_id = "test1"; manager.setSelectedLinks([link]); manager.setSelectedNodes([node]); manager.setSelectedDrawings([drawing]); diff --git a/src/app/cartography/shared/models/drawings/ellipse-element.ts b/src/app/cartography/shared/models/drawings/ellipse-element.ts index 7b3b6235..f7ce4df2 100644 --- a/src/app/cartography/shared/models/drawings/ellipse-element.ts +++ b/src/app/cartography/shared/models/drawings/ellipse-element.ts @@ -12,4 +12,5 @@ export class EllipseElement implements DrawingElement { ry: number; stroke: string; stroke_width: number; + stroke_dasharray: string; } diff --git a/src/app/cartography/shared/models/drawings/line-element.ts b/src/app/cartography/shared/models/drawings/line-element.ts index 18aba742..936ab56c 100644 --- a/src/app/cartography/shared/models/drawings/line-element.ts +++ b/src/app/cartography/shared/models/drawings/line-element.ts @@ -6,6 +6,7 @@ export class LineElement implements DrawingElement { width: number; stroke: string; stroke_width: number; + stroke_dasharray: string; x1: number; x2: number; y1: number; diff --git a/src/app/cartography/shared/models/drawings/rect-element.ts b/src/app/cartography/shared/models/drawings/rect-element.ts index dce73480..4f086bf8 100644 --- a/src/app/cartography/shared/models/drawings/rect-element.ts +++ b/src/app/cartography/shared/models/drawings/rect-element.ts @@ -8,4 +8,5 @@ export class RectElement implements DrawingElement { fill_opacity: number; stroke: string; stroke_width: number; + stroke_dasharray: string; } diff --git a/src/app/cartography/shared/models/drawings/text-element.ts b/src/app/cartography/shared/models/drawings/text-element.ts index ccddf868..fdde23b2 100644 --- a/src/app/cartography/shared/models/drawings/text-element.ts +++ b/src/app/cartography/shared/models/drawings/text-element.ts @@ -1,7 +1,8 @@ import { DrawingElement } from "./drawing-element"; +import { Font } from "../font"; -export class TextElement implements DrawingElement { +export class TextElement implements DrawingElement, Font { height: number; width: number; text: string; @@ -10,4 +11,5 @@ export class TextElement implements DrawingElement { font_family: string; font_size: number; font_weight: string; + text_decoration: string; } diff --git a/src/app/cartography/shared/models/font.ts b/src/app/cartography/shared/models/font.ts new file mode 100644 index 00000000..6fc92ee3 --- /dev/null +++ b/src/app/cartography/shared/models/font.ts @@ -0,0 +1,5 @@ +export interface Font { + font_family: string; + font_size: number; + font_weight: string; +} diff --git a/src/app/cartography/shared/widgets/drawings.ts b/src/app/cartography/shared/widgets/drawings.ts index a2a43e24..36555d0d 100644 --- a/src/app/cartography/shared/widgets/drawings.ts +++ b/src/app/cartography/shared/widgets/drawings.ts @@ -44,7 +44,7 @@ export class DrawingsWidget implements Widget { const drawing_merge = drawing.merge(drawing_enter) .attr('transform', (d: Drawing) => { - return `translate(${d.x},${d.y})`; + return `translate(${d.x},${d.y}) rotate(${d.rotation})`; }); this.drawingWidgets.forEach((widget) => { diff --git a/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts index 16148437..800199f7 100644 --- a/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts +++ b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts @@ -26,6 +26,7 @@ describe('EllipseDrawingWidget', () => { ellipse.fill_opacity = 2.0; ellipse.stroke = "#000000"; ellipse.stroke_width = 2.0; + ellipse.stroke_dasharray = "5,25,25"; ellipse.cx = 10; ellipse.cy = 20; ellipse.rx = 30; @@ -45,6 +46,7 @@ describe('EllipseDrawingWidget', () => { expect(ellipse_element.getAttribute('fill-opacity')).toEqual('2'); expect(ellipse_element.getAttribute('stroke')).toEqual('#000000'); expect(ellipse_element.getAttribute('stroke-width')).toEqual('2'); + expect(ellipse_element.getAttribute('stroke-dasharray')).toEqual('5,25,25'); expect(ellipse_element.getAttribute('cx')).toEqual('10'); expect(ellipse_element.getAttribute('cy')).toEqual('20'); expect(ellipse_element.getAttribute('rx')).toEqual('30'); diff --git a/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts index bfabb479..c46809b3 100644 --- a/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts +++ b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts @@ -2,9 +2,16 @@ import { SVGSelection } from "../../models/types"; import { Drawing } from "../../models/drawing"; import { EllipseElement } from "../../models/drawings/ellipse-element"; import { DrawingWidget } from "./drawing-widget"; +import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; export class EllipseDrawingWidget implements DrawingWidget { + private qtDasharrayFixer: QtDasharrayFixer; + + constructor() { + this.qtDasharrayFixer = new QtDasharrayFixer(); + } + public draw(view: SVGSelection) { const drawing = view .selectAll('ellipse.ellipse_element') @@ -24,6 +31,7 @@ export class EllipseDrawingWidget implements DrawingWidget { .attr('fill-opacity', (ellipse) => ellipse.fill_opacity) .attr('stroke', (ellipse) => ellipse.stroke) .attr('stroke-width', (ellipse) => ellipse.stroke_width) + .attr('stroke-dasharray', (ellipse) => this.qtDasharrayFixer.fix(ellipse.stroke_dasharray)) .attr('cx', (ellipse) => ellipse.cx) .attr('cy', (ellipse) => ellipse.cy) .attr('rx', (ellipse) => ellipse.rx) diff --git a/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts index 9aa212fd..727129e7 100644 --- a/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts +++ b/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts @@ -24,6 +24,7 @@ describe('LineDrawingWidget', () => { const line = new LineElement(); line.stroke = "#000000"; line.stroke_width = 2.0; + line.stroke_dasharray = "5,25,25"; line.x1 = 10; line.x2 = 20; line.y1 = 30; @@ -41,6 +42,7 @@ describe('LineDrawingWidget', () => { const line_element = drew.nodes()[0]; expect(line_element.getAttribute('stroke')).toEqual('#000000'); expect(line_element.getAttribute('stroke-width')).toEqual('2'); + expect(line_element.getAttribute('stroke-dasharray')).toEqual('5,25,25'); expect(line_element.getAttribute('x1')).toEqual('10'); expect(line_element.getAttribute('x2')).toEqual('20'); expect(line_element.getAttribute('y1')).toEqual('30'); diff --git a/src/app/cartography/shared/widgets/drawings/line-drawing.ts b/src/app/cartography/shared/widgets/drawings/line-drawing.ts index 0fef6087..cf1fd9c2 100644 --- a/src/app/cartography/shared/widgets/drawings/line-drawing.ts +++ b/src/app/cartography/shared/widgets/drawings/line-drawing.ts @@ -2,9 +2,16 @@ import { SVGSelection } from "../../models/types"; import { Drawing } from "../../models/drawing"; import { LineElement } from "../../models/drawings/line-element"; import { DrawingWidget } from "./drawing-widget"; +import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; export class LineDrawingWidget implements DrawingWidget { + private qtDasharrayFixer: QtDasharrayFixer; + + constructor() { + this.qtDasharrayFixer = new QtDasharrayFixer(); + } + public draw(view: SVGSelection) { const drawing = view .selectAll('line.line_element') @@ -22,6 +29,7 @@ export class LineDrawingWidget implements DrawingWidget { merge .attr('stroke', (line) => line.stroke) .attr('stroke-width', (line) => line.stroke_width) + .attr('stroke-dasharray', (line) => this.qtDasharrayFixer.fix(line.stroke_dasharray)) .attr('x1', (line) => line.x1) .attr('x2', (line) => line.x2) .attr('y1', (line) => line.y1) diff --git a/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts index 012c383b..a77a4adb 100644 --- a/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts +++ b/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts @@ -26,6 +26,7 @@ describe('RectDrawingWidget', () => { rect.fill_opacity = 1.0; rect.stroke = "#000000"; rect.stroke_width = 2.0; + rect.stroke_dasharray = "5,25,25"; rect.width = 100; rect.height = 200; drawing.element = rect; @@ -43,6 +44,7 @@ describe('RectDrawingWidget', () => { expect(rect_element.getAttribute('fill-opacity')).toEqual('1'); expect(rect_element.getAttribute('stroke')).toEqual('#000000'); expect(rect_element.getAttribute('stroke-width')).toEqual('2'); + expect(rect_element.getAttribute('stroke-dasharray')).toEqual('5,25,25'); expect(rect_element.getAttribute('width')).toEqual('100'); expect(rect_element.getAttribute('height')).toEqual('200'); }); diff --git a/src/app/cartography/shared/widgets/drawings/rect-drawing.ts b/src/app/cartography/shared/widgets/drawings/rect-drawing.ts index 83f86746..c6c7ad8c 100644 --- a/src/app/cartography/shared/widgets/drawings/rect-drawing.ts +++ b/src/app/cartography/shared/widgets/drawings/rect-drawing.ts @@ -2,9 +2,16 @@ import { SVGSelection } from "../../models/types"; import { Drawing } from "../../models/drawing"; import { RectElement } from "../../models/drawings/rect-element"; import { DrawingWidget } from "./drawing-widget"; +import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer"; export class RectDrawingWidget implements DrawingWidget { + private qtDasharrayFixer: QtDasharrayFixer; + + constructor() { + this.qtDasharrayFixer = new QtDasharrayFixer(); + } + public draw(view: SVGSelection) { const drawing = view .selectAll('rect.rect_element') @@ -24,6 +31,7 @@ export class RectDrawingWidget implements DrawingWidget { .attr('fill-opacity', (rect) => rect.fill_opacity) .attr('stroke', (rect) => rect.stroke) .attr('stroke-width', (rect) => rect.stroke_width) + .attr('stroke-dasharray', (rect) => this.qtDasharrayFixer.fix(rect.stroke_dasharray)) .attr('width', (rect) => rect.width) .attr('height', (rect) => rect.height); diff --git a/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts index 344ae86c..dd49bdee 100644 --- a/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts +++ b/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts @@ -27,6 +27,7 @@ describe('TextDrawingWidget', () => { text.font_family = "TypeWriter"; text.font_size = 10.0; text.font_weight = "bold"; + text.text_decoration = "line-through"; drawing.element = text; @@ -39,8 +40,10 @@ describe('TextDrawingWidget', () => { const drew = drawings_merge.selectAll('text.text_element'); expect(drew.size()).toEqual(1); const text_element = drew.nodes()[0]; - expect(text_element.innerHTML).toEqual('THIS IS TEXT'); - expect(text_element.getAttribute('style')).toEqual('font-family: "TypeWriter"; font-size: 10pt; font-weight: bold'); + expect(text_element.innerHTML).toEqual('THIS IS TEXT'); + expect(text_element.getAttribute('fill')).toEqual("#000000"); + expect(text_element.getAttribute('style')).toEqual('font-family: "Noto Sans"; font-size: 11pt; font-weight: bold'); + expect(text_element.getAttribute('text-decoration')).toEqual("line-through"); }); it('should draw multiline text', () => { @@ -66,4 +69,24 @@ describe('TextDrawingWidget', () => { expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.2em'); }); + it('should draw whitespaces', () => { + const text = new TextElement(); + text.text = ' Text with whitespaces'; + drawing.element = text; + + const drawings = svg.canvas.selectAll('g.drawing').data([drawing]); + const drawings_enter = drawings.enter().append('g').classed('drawing', true); + const drawings_merge = drawings.merge(drawings_enter); + + widget.draw(drawings_merge); + + const drew = drawings_merge.selectAll('tspan'); + expect(drew.nodes().length).toEqual(1); + + expect(drew.nodes()[0].innerHTML).toEqual(' Text with whitespaces'); + expect(drew.nodes()[0].getAttribute('space')).toEqual('preserve'); + + }); + + }); diff --git a/src/app/cartography/shared/widgets/drawings/text-drawing.ts b/src/app/cartography/shared/widgets/drawings/text-drawing.ts index 927470c2..cccf1fee 100644 --- a/src/app/cartography/shared/widgets/drawings/text-drawing.ts +++ b/src/app/cartography/shared/widgets/drawings/text-drawing.ts @@ -2,10 +2,21 @@ import { SVGSelection } from "../../models/types"; import { TextElement } from "../../models/drawings/text-element"; import { Drawing } from "../../models/drawing"; import { DrawingWidget } from "./drawing-widget"; +import { FontFixer } from "../../helpers/font-fixer"; +import { select } from "d3-selection"; export class TextDrawingWidget implements DrawingWidget { + static MARGIN = 4; + + private fontFixer: FontFixer; + + constructor() { + this.fontFixer = new FontFixer(); + } + public draw(view: SVGSelection) { + const drawing = view .selectAll('text.text_element') .data((d: Drawing) => { @@ -18,21 +29,24 @@ export class TextDrawingWidget implements DrawingWidget { .attr('class', 'text_element noselect'); const merge = drawing.merge(drawing_enter); - merge .attr('style', (text: TextElement) => { + const font = this.fontFixer.fix(text); + const styles: string[] = []; - if (text.font_family) { + if (font.font_family) { styles.push(`font-family: "${text.font_family}"`); } - if (text.font_size) { + if (font.font_size) { styles.push(`font-size: ${text.font_size}pt`); } - if (text.font_weight) { + if (font.font_weight) { styles.push(`font-weight: ${text.font_weight}`); } return styles.join("; "); - }); + }) + .attr('fill', (text) => text.fill) + .attr('text-decoration', (text) => text.text_decoration); const lines = merge.selectAll('tspan') .data((text: TextElement) => { @@ -47,13 +61,22 @@ export class TextDrawingWidget implements DrawingWidget { lines_merge .text((line) => line) - .attr('x', 0) - .attr("dy", (line, i) => i === 0 ? '0em' : '1.2em'); + .attr('xml:space', 'preserve') + .attr('x', 0) + .attr("dy", (line, i) => i === 0 ? '0em' : '1.2em'); lines .exit() .remove(); + merge.attr('transform', function (this: SVGTextElement) { + // SVG calculates y pos by the /bottom/ of the first tspan, hence we need to make some + // approx and make it matching to GUI + const tspan = select(this).selectAll('tspan'); + const height = this.getBBox().height / tspan.size(); + return `translate(${TextDrawingWidget.MARGIN}, ${height - TextDrawingWidget.MARGIN})`; + }); + drawing .exit() .remove(); diff --git a/src/app/cartography/shared/widgets/ethernet-link.ts b/src/app/cartography/shared/widgets/ethernet-link.ts index fa46abc6..c93e2651 100644 --- a/src/app/cartography/shared/widgets/ethernet-link.ts +++ b/src/app/cartography/shared/widgets/ethernet-link.ts @@ -8,9 +8,10 @@ import { Link } from "../models/link"; export class EthernetLinkWidget implements Widget { public draw(view: SVGSelection, link: Link) { + const link_data = [[ [link.source.x + link.source.width / 2., link.source.y + link.source.height / 2.], - [link.target.x + link.target.width / 2., link.target.y + link.source.height / 2.] + [link.target.x + link.target.width / 2., link.target.y + link.target.height / 2.] ]]; const value_line = line(); diff --git a/src/app/cartography/shared/widgets/links.ts b/src/app/cartography/shared/widgets/links.ts index f8b50a56..a01f9735 100644 --- a/src/app/cartography/shared/widgets/links.ts +++ b/src/app/cartography/shared/widgets/links.ts @@ -63,7 +63,6 @@ export class LinksWidget implements Widget { ]; } - const status_started = link_group .selectAll('circle.status_started') .data(statuses.filter((link_status: LinkStatus) => link_status.status === 'started')); diff --git a/src/app/cartography/shared/widgets/nodes.spec.ts b/src/app/cartography/shared/widgets/nodes.spec.ts new file mode 100644 index 00000000..f2458482 --- /dev/null +++ b/src/app/cartography/shared/widgets/nodes.spec.ts @@ -0,0 +1,69 @@ + +import { TestSVGCanvas } from "../../testing"; +import { NodesWidget } from "./nodes"; +import { Node } from "../models/node"; +import { Label } from "../models/label"; + + +describe('NodesWidget', () => { + let svg: TestSVGCanvas; + let widget: NodesWidget; + + beforeEach(() => { + svg = new TestSVGCanvas(); + widget = new NodesWidget(); + }); + + afterEach(() => { + svg.destroy(); + }); + + describe('draggable behaviour', () => { + let node: Node; + const tryToDrag = () => { + const drew = svg.canvas.selectAll('g.node'); + const drewNode = drew.nodes()[0]; + + drewNode.dispatchEvent( + new MouseEvent('mousedown', { + clientX: 150, clientY: 250, relatedTarget: drewNode, + screenY: 1024, screenX: 1024, view: window + }) + ); + + window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300})); + window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300, view: window})); + }; + + beforeEach(() => { + node = new Node(); + node.x = 100; + node.y = 200; + node.width = 100; + node.height = 100; + node.label = new Label(); + }); + + it('should be draggable when enabled', () => { + widget.setDraggingEnabled(true); + widget.draw(svg.canvas, [node]); + + tryToDrag(); + + expect(node.x).toEqual(250); + expect(node.y).toEqual(250); + }); + + it('should be not draggable when disabled', () => { + widget.setDraggingEnabled(false); + widget.draw(svg.canvas, [node]); + + tryToDrag(); + + expect(node.x).toEqual(100); + expect(node.y).toEqual(200); + }); + }); + + +}); diff --git a/src/app/cartography/shared/widgets/nodes.ts b/src/app/cartography/shared/widgets/nodes.ts index f79bb640..fbdc6cef 100644 --- a/src/app/cartography/shared/widgets/nodes.ts +++ b/src/app/cartography/shared/widgets/nodes.ts @@ -11,6 +11,7 @@ import { CssFixer } from "../helpers/css-fixer"; export class NodesWidget implements Widget { private debug = false; + private draggingEnabled = false; private onContextMenuCallback: (event: any, node: Node) => void; private onNodeClickedCallback: (event: any, node: Node) => void; @@ -45,6 +46,10 @@ export class NodesWidget implements Widget { this.symbols = symbols; } + public setDraggingEnabled(enabled: boolean) { + this.draggingEnabled = enabled; + } + private executeOnNodeDraggingCallback(callback_event: any, node: Node) { this.onNodeDraggingCallbacks.forEach((callback: (e: any, n: Node) => void) => { callback(callback_event, node); @@ -190,7 +195,9 @@ export class NodesWidget implements Widget { }); }; - node_merge.call(dragging()); + if (this.draggingEnabled) { + node_merge.call(dragging()); + } nodes_selection .exit() diff --git a/src/app/cartography/shared/widgets/serial-link.ts b/src/app/cartography/shared/widgets/serial-link.ts index dd5fd0a2..fd6257f2 100644 --- a/src/app/cartography/shared/widgets/serial-link.ts +++ b/src/app/cartography/shared/widgets/serial-link.ts @@ -8,8 +8,17 @@ import { Link } from "../models/link"; export class SerialLinkWidget implements Widget { public draw(view: SVGSelection, link: Link) { - const dx = link.target.x - link.source.x; - const dy = link.target.y - link.source.y; + const source = { + 'x': link.source.x + link.source.width / 2, + 'y': link.source.y + link.source.height / 2 + }; + const target = { + 'x': link.target.x + link.target.width / 2, + 'y': link.target.y + link.target.height / 2 + }; + + const dx = target.x - source.x; + const dy = target.y - source.y; const vector_angle = Math.atan2(dy, dx); const rot_angle = -Math.PI / 4.0; @@ -19,20 +28,20 @@ export class SerialLinkWidget implements Widget { ]; const angle_source = [ - link.source.x + dx / 2.0 + 15 * vect_rot[0], - link.source.y + dy / 2.0 + 15 * vect_rot[1] + source.x + dx / 2.0 + 15 * vect_rot[0], + source.y + dy / 2.0 + 15 * vect_rot[1] ]; const angle_target = [ - link.target.x - dx / 2.0 - 15 * vect_rot[0], - link.target.y - dy / 2.0 - 15 * vect_rot[1] + target.x - dx / 2.0 - 15 * vect_rot[0], + target.y - dy / 2.0 - 15 * vect_rot[1] ]; const line_data = [ - [link.source.x, link.source.y], + [source.x, source.y], angle_source, angle_target, - [link.target.x, link.target.y] + [target.x, target.y] ]; let link_path = view.select('path'); diff --git a/src/app/project-map/project-map.component.css b/src/app/project-map/project-map.component.css index 1cbb00fe..472dceb9 100644 --- a/src/app/project-map/project-map.component.css +++ b/src/app/project-map/project-map.component.css @@ -31,9 +31,9 @@ g.node:hover { left: 50%; } -g.node text { - font-family: Roboto !important; -} +/*g.node text {*/ + /*font-family: Roboto !important;*/ +/*}*/ svg.map image:hover, svg.map image.chosen, g.selected { diff --git a/src/app/project-map/project-map.component.ts b/src/app/project-map/project-map.component.ts index 0645df2c..86e49ffb 100644 --- a/src/app/project-map/project-map.component.ts +++ b/src/app/project-map/project-map.component.ts @@ -1,6 +1,5 @@ import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, ParamMap } from '@angular/router'; -import { HotkeysService } from 'angular2-hotkeys'; import { Observable } from 'rxjs/Observable'; import { Subject } from "rxjs/Subject"; @@ -89,7 +88,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy { protected nodesDataSource: NodesDataSource, protected linksDataSource: LinksDataSource, protected drawingsDataSource: DrawingsDataSource, - protected hotkeysService: HotkeysService ) { this.selectionManager = new SelectionManager( this.nodesDataSource, this.linksDataSource, this.drawingsDataSource, new InRectangleHelper()); @@ -105,7 +103,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy { .fromPromise(this.serverService.get(server_id)) .flatMap((server: Server) => { this.server = server; - return this.projectService.get(server, paramMap.get('project_id')); + return this.projectService.get(server, paramMap.get('project_id')).map((project) => { + project.readonly = true; + return project; + }); }) .flatMap((project: Project) => { this.project = project; @@ -194,6 +195,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy { } setUpMapCallbacks(project: Project) { + if (this.project.readonly) { + this.mapChild.graphLayout.getSelectionTool().deactivate(); + } + this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.project.readonly); + this.mapChild.graphLayout.getNodesWidget().setOnContextMenuCallback((event: any, node: Node) => { this.nodeContextMenu.open(node, event.clientY, event.clientX); }); @@ -271,11 +277,15 @@ export class ProjectMapComponent implements OnInit, OnDestroy { public toggleMovingMode() { this.movingMode = !this.movingMode; if (this.movingMode) { - this.mapChild.graphLayout.getSelectionTool().deactivate(); + if (!this.project.readonly) { + this.mapChild.graphLayout.getSelectionTool().deactivate(); + } this.mapChild.graphLayout.getMovingTool().activate(); } else { this.mapChild.graphLayout.getMovingTool().deactivate(); - this.mapChild.graphLayout.getSelectionTool().activate(); + if (!this.project.readonly) { + this.mapChild.graphLayout.getSelectionTool().activate(); + } } }