diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter.spec.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter.spec.ts
new file mode 100644
index 00000000..faf21bf8
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter.spec.ts
@@ -0,0 +1,46 @@
+import { SvgToDrawingConverter } from "./svg-to-drawing-converter";
+import { TextElement } from "../models/drawings/text-element";
+
+
+describe('SvgToDrawingHelper', () => {
+ let svgToDrawingConverter: SvgToDrawingConverter;
+
+ beforeEach(() => {
+ svgToDrawingConverter = new SvgToDrawingConverter();
+ });
+
+ it('should raise Error on empty string', () => {
+ expect(() => svgToDrawingConverter.convert("")).toThrowError(Error);
+ });
+
+ it('should raise Error on no children', () => {
+ expect(() => svgToDrawingConverter.convert("")).toThrowError(Error);
+ });
+
+ it('should raise Error on unknown parser', () => {
+ expect(() => svgToDrawingConverter.convert("")).toThrowError(Error);
+ });
+
+ it('should parse width and height if defined', () => {
+ const svg = '';
+
+ const drawing = svgToDrawingConverter.convert(svg);
+
+ expect(drawing.width).toBe(78);
+ expect(drawing.height).toBe(53);
+ });
+
+ it('should parse element even when is text between', () => {
+ const svg = '';
+ const drawing: TextElement = svgToDrawingConverter.convert(svg) as TextElement;
+ expect(drawing.text).toEqual('Label');
+ });
+
+ it('should match supported elements', () => {
+ expect(svgToDrawingConverter.supportedTags()).toEqual(['text', 'image', 'rect', 'line', 'ellipse']);
+ });
+});
diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter.ts
new file mode 100644
index 00000000..8653c0c0
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter.ts
@@ -0,0 +1,65 @@
+import { Injectable } from "@angular/core";
+import { DrawingElement } from "../models/drawings/drawing-element";
+import { SvgConverter } from "./svg-to-drawing-converter/svg-converter";
+import { TextConverter } from "./svg-to-drawing-converter/text-converter";
+import { ImageConverter } from "./svg-to-drawing-converter/image-converter";
+import { RectConverter } from "./svg-to-drawing-converter/rect-converter";
+import { LineConverter } from "./svg-to-drawing-converter/line-converter";
+import { EllipseConverter } from "./svg-to-drawing-converter/ellipse-converter";
+
+
+@Injectable()
+export class SvgToDrawingConverter {
+ private parser: DOMParser;
+ private elementParsers: { [key: string]: SvgConverter };
+
+ constructor() {
+ this.parser = new DOMParser();
+ this.elementParsers = {
+ 'text': new TextConverter(),
+ 'image': new ImageConverter(),
+ 'rect': new RectConverter(),
+ 'line': new LineConverter(),
+ 'ellipse': new EllipseConverter()
+ };
+ }
+
+ supportedTags() {
+ return Object.keys(this.elementParsers);
+ }
+
+ convert(svg: string): DrawingElement {
+ const svgDom = this.parser.parseFromString(svg, 'text/xml');
+ const roots = svgDom.getElementsByTagName('svg');
+ if (roots.length !== 1) {
+ throw new Error(`Cannot locate svg element root in '${svg}'`);
+ }
+ const svgRoot = roots[0];
+
+ let parser: SvgConverter = null;
+ let child: any = null;
+
+ // find matching tag
+ for (const i in svgRoot.children) {
+ child = svgRoot.children[i];
+ const name = child.nodeName;
+ if (name in this.elementParsers) {
+ parser = this.elementParsers[name];
+ break;
+ }
+ }
+
+ if (parser === null) {
+ throw new Error(`Cannot find parser for '${svg}'`);
+ }
+
+ const drawing = parser.convert(child);
+
+ drawing.width = +svgRoot.getAttribute('width');
+ drawing.height = +svgRoot.getAttribute('height');
+
+ return drawing;
+ }
+
+
+}
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
new file mode 100644
index 00000000..acef0085
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.spec.ts
@@ -0,0 +1,33 @@
+import { EllipseConverter } from "./ellipse-converter";
+
+
+describe('EllipseConverter', () => {
+ let ellipseConverter: EllipseConverter;
+
+ beforeEach(() => {
+ ellipseConverter = new EllipseConverter();
+ });
+
+ it('should parse attributes', () => {
+ const node = document.createElement("ellipse");
+ node.setAttribute("fill", "#ffffff");
+ node.setAttribute("fill-opacity", "1.0");
+ node.setAttribute("stroke", "#000000");
+ node.setAttribute("stroke-width", "2");
+ node.setAttribute("cx", "63");
+ node.setAttribute("cy", "59");
+ node.setAttribute("rx", "63");
+ node.setAttribute("ry", "59");
+
+ const drawing = ellipseConverter.convert(node);
+ expect(drawing.fill).toEqual("#ffffff");
+ expect(drawing.fill_opacity).toEqual(1.0);
+ expect(drawing.stroke).toEqual("#000000");
+ expect(drawing.stroke_width).toEqual(2.0);
+ expect(drawing.cx).toEqual(63);
+ expect(drawing.cy).toEqual(59);
+ expect(drawing.rx).toEqual(63);
+ expect(drawing.ry).toEqual(59);
+ });
+
+});
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
new file mode 100644
index 00000000..435d689b
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/ellipse-converter.ts
@@ -0,0 +1,51 @@
+import { SvgConverter } from "./svg-converter";
+import { EllipseElement } from "../../models/drawings/ellipse-element";
+
+
+export class EllipseConverter implements SvgConverter {
+ convert(node: Node): EllipseElement {
+ const drawing = new EllipseElement();
+
+ const fill = node.attributes.getNamedItem("fill");
+ if (fill) {
+ drawing.fill = fill.value;
+ }
+
+ const fill_opacity = node.attributes.getNamedItem("fill-opacity");
+ if (fill) {
+ drawing.fill_opacity = parseInt(fill_opacity.value, 10);
+ }
+
+ const stroke = node.attributes.getNamedItem("stroke");
+ if (stroke) {
+ drawing.stroke = stroke.value;
+ }
+
+ const stroke_width = node.attributes.getNamedItem("stroke-width");
+ if (stroke) {
+ drawing.stroke_width = parseInt(stroke_width.value, 10);
+ }
+
+ const cx = node.attributes.getNamedItem('cx');
+ if (cx) {
+ drawing.cx = parseInt(cx.value, 10);
+ }
+
+ const cy = node.attributes.getNamedItem('cy');
+ if (cy) {
+ drawing.cy = parseInt(cy.value, 10);
+ }
+
+ const rx = node.attributes.getNamedItem('rx');
+ if (rx) {
+ drawing.rx = parseInt(rx.value, 10);
+ }
+
+ const ry = node.attributes.getNamedItem('ry');
+ if (ry) {
+ drawing.ry = parseInt(ry.value, 10);
+ }
+
+ return drawing;
+ }
+}
diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/image-converter.spec.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/image-converter.spec.ts
new file mode 100644
index 00000000..a812c868
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/image-converter.spec.ts
@@ -0,0 +1,23 @@
+import { ImageConverter } from "./image-converter";
+
+
+describe('ImageConverter', () => {
+ let imageConverter: ImageConverter;
+
+ beforeEach(() => {
+ imageConverter = new ImageConverter();
+ });
+
+ it('should parse attributes', () => {
+ const node = document.createElement("image");
+ node.setAttribute("xlink:href", "data:image/png");
+ node.setAttribute("width", "100px");
+ node.setAttribute("height", "200px");
+
+ const drawing = imageConverter.convert(node);
+ expect(drawing.data).toEqual("data:image/png");
+ expect(drawing.width).toEqual(100);
+ expect(drawing.height).toEqual(200);
+ });
+
+});
diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/image-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/image-converter.ts
new file mode 100644
index 00000000..e0d9ed5c
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/image-converter.ts
@@ -0,0 +1,26 @@
+import { SvgConverter } from "./svg-converter";
+import { ImageElement } from "../../models/drawings/image-element";
+
+
+export class ImageConverter implements SvgConverter {
+ convert(node: Node): ImageElement {
+ const drawing = new ImageElement();
+
+ const data = node.attributes.getNamedItem("xlink:href");
+ if (data) {
+ drawing.data = data.value;
+ }
+
+ const width = node.attributes.getNamedItem('width');
+ if (width) {
+ drawing.width = parseInt(width.value, 10);
+ }
+
+ const height = node.attributes.getNamedItem('height');
+ if (height) {
+ drawing.height = parseInt(height.value, 10);
+ }
+
+ return drawing;
+ }
+}
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
new file mode 100644
index 00000000..377826a2
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.spec.ts
@@ -0,0 +1,29 @@
+import { LineConverter } from "./line-converter";
+
+
+describe('LineConverter', () => {
+ let lineConverter: LineConverter;
+
+ beforeEach(() => {
+ lineConverter = new LineConverter();
+ });
+
+ it('should parse attributes', () => {
+ const node = document.createElement("line");
+ node.setAttribute("stroke", "#000000");
+ node.setAttribute("stroke-width", "2");
+ node.setAttribute("x1", "10.10");
+ node.setAttribute("x2", "20");
+ node.setAttribute("y1", "30");
+ node.setAttribute("y2", "40");
+
+ const drawing = lineConverter.convert(node);
+ expect(drawing.stroke).toEqual("#000000");
+ expect(drawing.stroke_width).toEqual(2.0);
+ expect(drawing.x1).toEqual(10);
+ expect(drawing.x2).toEqual(20);
+ expect(drawing.y1).toEqual(30);
+ expect(drawing.y2).toEqual(40);
+ });
+
+});
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
new file mode 100644
index 00000000..a8227f0d
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/line-converter.ts
@@ -0,0 +1,41 @@
+import { SvgConverter } from "./svg-converter";
+import { LineElement } from "../../models/drawings/line-element";
+
+
+export class LineConverter implements SvgConverter {
+ convert(node: Node): LineElement {
+ const drawing = new LineElement();
+
+ const stroke = node.attributes.getNamedItem("stroke");
+ if (stroke) {
+ drawing.stroke = stroke.value;
+ }
+
+ const stroke_width = node.attributes.getNamedItem("stroke-width");
+ if (stroke) {
+ drawing.stroke_width = parseInt(stroke_width.value, 10);
+ }
+
+ const x1 = node.attributes.getNamedItem('x1');
+ if (x1) {
+ drawing.x1 = parseInt(x1.value, 10);
+ }
+
+ const x2 = node.attributes.getNamedItem('x2');
+ if (x2) {
+ drawing.x2 = parseInt(x2.value, 10);
+ }
+
+ const y1 = node.attributes.getNamedItem('y1');
+ if (y1) {
+ drawing.y1 = parseInt(y1.value, 10);
+ }
+
+ const y2 = node.attributes.getNamedItem('y2');
+ if (y2) {
+ drawing.y2 = parseInt(y2.value, 10);
+ }
+
+ return drawing;
+ }
+}
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
new file mode 100644
index 00000000..af904ced
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.spec.ts
@@ -0,0 +1,30 @@
+import { RectConverter } from "./rect-converter";
+
+
+describe('RectConverter', () => {
+ let rectConverter: RectConverter;
+
+ beforeEach(() => {
+ rectConverter = new RectConverter();
+ });
+
+ it('should parse attributes', () => {
+ const node = document.createElement("rect");
+ node.setAttribute("fill", "#ffffff");
+ node.setAttribute("fill-opacity", "1.0");
+ node.setAttribute("stroke", "#000000");
+ node.setAttribute("stroke-width", "2");
+
+ 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.stroke).toEqual("#000000");
+ expect(drawing.stroke_width).toEqual(2.0);
+ 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
new file mode 100644
index 00000000..8815c960
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/rect-converter.ts
@@ -0,0 +1,41 @@
+import { SvgConverter } from "./svg-converter";
+import { RectElement } from "../../models/drawings/rect-element";
+
+
+export class RectConverter implements SvgConverter {
+ convert(node: Node): RectElement {
+ const drawing = new RectElement();
+
+ const fill = node.attributes.getNamedItem("fill");
+ if (fill) {
+ drawing.fill = fill.value;
+ }
+
+ const fill_opacity = node.attributes.getNamedItem("fill-opacity");
+ if (fill) {
+ drawing.fill_opacity = parseInt(fill_opacity.value, 10);
+ }
+
+ const stroke = node.attributes.getNamedItem("stroke");
+ if (stroke) {
+ drawing.stroke = stroke.value;
+ }
+
+ const stroke_width = node.attributes.getNamedItem("stroke-width");
+ if (stroke) {
+ drawing.stroke_width = parseInt(stroke_width.value, 10);
+ }
+
+ const width = node.attributes.getNamedItem('width');
+ if (width) {
+ drawing.width = parseInt(width.value, 10);
+ }
+
+ const height = node.attributes.getNamedItem('height');
+ if (height) {
+ drawing.height = parseInt(height.value, 10);
+ }
+
+ return drawing;
+ }
+}
diff --git a/src/app/cartography/shared/helpers/svg-to-drawing-converter/svg-converter.ts b/src/app/cartography/shared/helpers/svg-to-drawing-converter/svg-converter.ts
new file mode 100644
index 00000000..7d052681
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/svg-converter.ts
@@ -0,0 +1,5 @@
+import { DrawingElement } from "../../models/drawings/drawing-element";
+
+export interface SvgConverter {
+ convert(node: Node): DrawingElement;
+}
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
new file mode 100644
index 00000000..f2d18c58
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.spec.ts
@@ -0,0 +1,29 @@
+import { TextConverter } from "./text-converter";
+
+
+describe('TextConverter', () => {
+ let textConverter: TextConverter;
+
+ beforeEach(() => {
+ textConverter = new TextConverter();
+ });
+
+ it('should parse attributes', () => {
+ const node = document.createElement("text");
+ node.innerText = "Text";
+ node.setAttribute("fill", "#00000");
+ node.setAttribute("fill-opacity", "1.0");
+ node.setAttribute("font-family", "TypeWriter");
+ node.setAttribute("font-size", "10.0");
+ node.setAttribute("font-weight", "bold");
+
+ const drawing = textConverter.convert(node);
+ expect(drawing.text).toEqual("Text");
+ expect(drawing.fill).toEqual("#00000");
+ expect(drawing.fill_opacity).toEqual(1.0);
+ expect(drawing.font_family).toEqual("TypeWriter");
+ expect(drawing.font_size).toEqual(10.0);
+ expect(drawing.font_weight).toEqual("bold");
+ });
+
+});
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
new file mode 100644
index 00000000..78180d40
--- /dev/null
+++ b/src/app/cartography/shared/helpers/svg-to-drawing-converter/text-converter.ts
@@ -0,0 +1,38 @@
+import { SvgConverter } from "./svg-converter";
+import { TextElement } from "../../models/drawings/text-element";
+
+
+export class TextConverter implements SvgConverter {
+ convert(node: Node): TextElement {
+ const drawing = new TextElement();
+
+ drawing.text = node.textContent;
+
+ const fill = node.attributes.getNamedItem('fill');
+ if (fill) {
+ drawing.fill = fill.value;
+ }
+
+ const fill_opacity = node.attributes.getNamedItem('fill-opacity');
+ if (fill_opacity) {
+ drawing.fill_opacity = +fill_opacity.value;
+ }
+
+ const font_family = node.attributes.getNamedItem('font-family');
+ if (font_family) {
+ drawing.font_family = font_family.value;
+ }
+
+ const font_size = node.attributes.getNamedItem('font-size');
+ if (font_size) {
+ drawing.font_size = +font_size.value;
+ }
+
+ const font_weight = node.attributes.getNamedItem('font-weight');
+ if (font_weight) {
+ drawing.font_weight = font_weight.value;
+ }
+
+ return drawing;
+ }
+}
diff --git a/src/app/cartography/shared/models/drawing.ts b/src/app/cartography/shared/models/drawing.ts
index b5d9826a..a94a6ccd 100644
--- a/src/app/cartography/shared/models/drawing.ts
+++ b/src/app/cartography/shared/models/drawing.ts
@@ -1,4 +1,5 @@
import { Selectable } from "../managers/selection-manager";
+import { DrawingElement } from "./drawings/drawing-element";
export class Drawing implements Selectable {
drawing_id: string;
@@ -9,4 +10,5 @@ export class Drawing implements Selectable {
y: number;
z: number;
is_selected = false;
+ element: DrawingElement; // @todo; move to context
}
diff --git a/src/app/cartography/shared/models/drawings/drawing-element.ts b/src/app/cartography/shared/models/drawings/drawing-element.ts
new file mode 100644
index 00000000..320531d5
--- /dev/null
+++ b/src/app/cartography/shared/models/drawings/drawing-element.ts
@@ -0,0 +1,4 @@
+export interface DrawingElement {
+ width: number;
+ height: number;
+}
diff --git a/src/app/cartography/shared/models/drawings/ellipse-element.ts b/src/app/cartography/shared/models/drawings/ellipse-element.ts
new file mode 100644
index 00000000..7b3b6235
--- /dev/null
+++ b/src/app/cartography/shared/models/drawings/ellipse-element.ts
@@ -0,0 +1,15 @@
+import { DrawingElement } from "./drawing-element";
+
+
+export class EllipseElement implements DrawingElement {
+ height: number;
+ width: number;
+ cx: number;
+ cy: number;
+ fill: string;
+ fill_opacity: number;
+ rx: number;
+ ry: number;
+ stroke: string;
+ stroke_width: number;
+}
diff --git a/src/app/cartography/shared/models/drawings/image-element.ts b/src/app/cartography/shared/models/drawings/image-element.ts
new file mode 100644
index 00000000..154e147b
--- /dev/null
+++ b/src/app/cartography/shared/models/drawings/image-element.ts
@@ -0,0 +1,8 @@
+import { DrawingElement } from "./drawing-element";
+
+
+export class ImageElement implements DrawingElement {
+ height: number;
+ width: number;
+ data: string;
+}
diff --git a/src/app/cartography/shared/models/drawings/line-element.ts b/src/app/cartography/shared/models/drawings/line-element.ts
new file mode 100644
index 00000000..18aba742
--- /dev/null
+++ b/src/app/cartography/shared/models/drawings/line-element.ts
@@ -0,0 +1,13 @@
+import { DrawingElement } from "./drawing-element";
+
+
+export class LineElement implements DrawingElement {
+ height: number;
+ width: number;
+ stroke: string;
+ stroke_width: number;
+ x1: number;
+ x2: number;
+ y1: number;
+ y2: number;
+}
diff --git a/src/app/cartography/shared/models/drawings/rect-element.ts b/src/app/cartography/shared/models/drawings/rect-element.ts
new file mode 100644
index 00000000..dce73480
--- /dev/null
+++ b/src/app/cartography/shared/models/drawings/rect-element.ts
@@ -0,0 +1,11 @@
+import { DrawingElement } from "./drawing-element";
+
+
+export class RectElement implements DrawingElement {
+ height: number;
+ width: number;
+ fill: string;
+ fill_opacity: number;
+ stroke: string;
+ stroke_width: number;
+}
diff --git a/src/app/cartography/shared/models/drawings/text-element.ts b/src/app/cartography/shared/models/drawings/text-element.ts
new file mode 100644
index 00000000..ccddf868
--- /dev/null
+++ b/src/app/cartography/shared/models/drawings/text-element.ts
@@ -0,0 +1,13 @@
+import { DrawingElement } from "./drawing-element";
+
+
+export class TextElement implements DrawingElement {
+ height: number;
+ width: number;
+ text: string;
+ fill: string;
+ fill_opacity: number;
+ 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 6c2c24b6..62004fa1 100644
--- a/src/app/cartography/shared/widgets/drawings.ts
+++ b/src/app/cartography/shared/widgets/drawings.ts
@@ -1,16 +1,33 @@
-import {Widget} from "./widget";
-import {Drawing} from "../models/drawing";
-import {SVGSelection} from "../models/types";
-import {Layer} from "../models/layer";
+import { Widget } from "./widget";
+import { Drawing } from "../models/drawing";
+import { SVGSelection } from "../models/types";
+import { Layer } from "../models/layer";
+import { TextDrawingWidget } from "./drawings/text-drawing";
+import { SvgToDrawingConverter } from "../helpers/svg-to-drawing-converter";
+import { ImageDrawingWidget } from "./drawings/image-drawing";
+import { RectDrawingWidget } from "./drawings/rect-drawing";
+import { LineDrawingWidget } from "./drawings/line-drawing";
+import { EllipseDrawingWidget } from "./drawings/ellipse-drawing";
export class DrawingsWidget implements Widget {
- constructor() {}
+ private svgToDrawingConverter: SvgToDrawingConverter;
+
+ constructor() {
+ this.svgToDrawingConverter = new SvgToDrawingConverter();
+ }
public draw(view: SVGSelection, drawings?: Drawing[]) {
const drawing = view
.selectAll('g.drawing')
.data((l: Layer) => {
+ l.drawings.forEach((d: Drawing) => {
+ try {
+ d.element = this.svgToDrawingConverter.convert(d.svg);
+ } catch (error) {
+ console.log(`Cannot convert due to Error: '${error}'`);
+ }
+ });
return l.drawings;
}, (d: Drawing) => {
return d.drawing_id;
@@ -20,43 +37,29 @@ export class DrawingsWidget implements Widget {
.append('g')
.attr('class', 'drawing');
- const parser = new DOMParser();
-
- const drawing_image = drawing_enter.append('image')
- .attr('xlink:href', (d: Drawing) => {
- let svg = d.svg;
- if (svg.indexOf("xmlns") < 0) {
- svg = svg.replace('svg', 'svg xmlns="http://www.w3.org/2000/svg"');
- }
-
- return 'data:image/svg+xml;base64,' + btoa(svg);
- })
- .attr('width', (d: Drawing) => {
- const svg_dom = parser.parseFromString(d.svg, 'text/xml');
- const roots = svg_dom.getElementsByTagName('svg');
- if (roots.length > 0) {
- if (roots[0].hasAttribute('width')) {
- return roots[0].getAttribute('width');
- }
- }
- return 0;
- })
- .attr('height', (d: Drawing) => {
- const svg_dom = parser.parseFromString(d.svg, 'text/xml');
- const roots = svg_dom.getElementsByTagName('svg');
- if (roots.length > 0) {
- if (roots[0].hasAttribute('height')) {
- return roots[0].getAttribute('height');
- }
- }
- return 0;
- });
-
const drawing_merge = drawing.merge(drawing_enter)
.attr('transform', (d: Drawing) => {
return `translate(${d.x},${d.y})`;
});
- drawing.exit().remove();
+ const text_drawing = new TextDrawingWidget();
+ text_drawing.draw(drawing_merge);
+
+ const image_drawing = new ImageDrawingWidget();
+ image_drawing.draw(drawing_merge);
+
+ const rect_drawing = new RectDrawingWidget();
+ rect_drawing.draw(drawing_merge);
+
+ const line_drawing = new LineDrawingWidget();
+ line_drawing.draw(drawing_merge);
+
+ const ellipse_drawing = new EllipseDrawingWidget();
+ ellipse_drawing.draw(drawing_merge);
+
+ drawing
+ .exit()
+ .remove();
+
}
}
diff --git a/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts
new file mode 100644
index 00000000..16148437
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.spec.ts
@@ -0,0 +1,53 @@
+import { TestSVGCanvas } from "../../../testing";
+import { Drawing } from "../../models/drawing";
+import { EllipseDrawingWidget } from "./ellipse-drawing";
+import { EllipseElement } from "../../models/drawings/ellipse-element";
+
+
+describe('EllipseDrawingWidget', () => {
+ let svg: TestSVGCanvas;
+ let widget: EllipseDrawingWidget;
+ let drawing: Drawing;
+
+
+ beforeEach(() => {
+ svg = new TestSVGCanvas();
+ drawing = new Drawing();
+ widget = new EllipseDrawingWidget();
+ });
+
+ afterEach(() => {
+ svg.destroy();
+ });
+
+ it('should draw ellipse drawing', () => {
+ const ellipse = new EllipseElement();
+ ellipse.fill = "#FFFFFFF";
+ ellipse.fill_opacity = 2.0;
+ ellipse.stroke = "#000000";
+ ellipse.stroke_width = 2.0;
+ ellipse.cx = 10;
+ ellipse.cy = 20;
+ ellipse.rx = 30;
+ ellipse.ry = 40;
+ drawing.element = ellipse;
+
+ 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('ellipse.ellipse_element');
+ expect(drew.size()).toEqual(1);
+ const ellipse_element = drew.nodes()[0];
+ expect(ellipse_element.getAttribute('fill')).toEqual('#FFFFFFF');
+ 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('cx')).toEqual('10');
+ expect(ellipse_element.getAttribute('cy')).toEqual('20');
+ expect(ellipse_element.getAttribute('rx')).toEqual('30');
+ expect(ellipse_element.getAttribute('ry')).toEqual('40');
+ });
+});
diff --git a/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts
new file mode 100644
index 00000000..aca1b342
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/ellipse-drawing.ts
@@ -0,0 +1,36 @@
+import { SVGSelection } from "../../models/types";
+import { Drawing } from "../../models/drawing";
+import { EllipseElement } from "../../models/drawings/ellipse-element";
+
+
+export class EllipseDrawingWidget {
+ public draw(view: SVGSelection) {
+ const drawing = view
+ .selectAll('ellipse.ellipse_element')
+ .data((d: Drawing) => {
+ return (d.element && d.element instanceof EllipseElement) ? [d.element] : [];
+ });
+
+ const drawing_enter = drawing
+ .enter()
+ .append('ellipse')
+ .attr('class', 'ellipse_element noselect');
+
+ const merge = drawing.merge(drawing_enter);
+
+ merge
+ .attr('fill', (ellipse) => ellipse.fill)
+ .attr('fill-opacity', (ellipse) => ellipse.fill_opacity)
+ .attr('stroke', (ellipse) => ellipse.stroke)
+ .attr('stroke-width', (ellipse) => ellipse.stroke_width)
+ .attr('cx', (ellipse) => ellipse.cx)
+ .attr('cy', (ellipse) => ellipse.cy)
+ .attr('rx', (ellipse) => ellipse.rx)
+ .attr('ry', (ellipse) => ellipse.ry);
+
+ drawing
+ .exit()
+ .remove();
+
+ }
+}
diff --git a/src/app/cartography/shared/widgets/drawings/image-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/image-drawing.spec.ts
new file mode 100644
index 00000000..a2fbfb43
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/image-drawing.spec.ts
@@ -0,0 +1,43 @@
+import { TestSVGCanvas } from "../../../testing";
+import { Drawing } from "../../models/drawing";
+import { ImageDrawingWidget } from "./image-drawing";
+import { ImageElement } from "../../models/drawings/image-element";
+
+
+describe('ImageDrawingWidget', () => {
+ let svg: TestSVGCanvas;
+ let widget: ImageDrawingWidget;
+ let drawing: Drawing;
+
+
+ beforeEach(() => {
+ svg = new TestSVGCanvas();
+ drawing = new Drawing();
+ widget = new ImageDrawingWidget();
+ });
+
+ afterEach(() => {
+ svg.destroy();
+ });
+
+ it('should draw image drawing', () => {
+ const image = new ImageElement();
+ image.width = 100;
+ image.height = 200;
+ image.data = "data:image/svg+xml;base64,DATA";
+ drawing.element = image;
+
+ 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('image.image_element');
+ expect(drew.size()).toEqual(1);
+ const image_element = drew.nodes()[0];
+ expect(image_element.getAttribute('width')).toEqual('100');
+ expect(image_element.getAttribute('height')).toEqual('200');
+ expect(image_element.getAttribute('href')).toEqual('data:image/svg+xml;base64,DATA');
+ });
+});
diff --git a/src/app/cartography/shared/widgets/drawings/image-drawing.ts b/src/app/cartography/shared/widgets/drawings/image-drawing.ts
new file mode 100644
index 00000000..cbf8dfd0
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/image-drawing.ts
@@ -0,0 +1,31 @@
+import { SVGSelection } from "../../models/types";
+import { Drawing } from "../../models/drawing";
+import { ImageElement } from "../../models/drawings/image-element";
+
+
+export class ImageDrawingWidget {
+ public draw(view: SVGSelection) {
+ const drawing = view
+ .selectAll('image.image_element')
+ .data((d: Drawing) => {
+ return (d.element && d.element instanceof ImageElement) ? [d.element] : [];
+ });
+
+ const drawing_enter = drawing
+ .enter()
+ .append('image')
+ .attr('class', 'image_element noselect');
+
+ const merge = drawing.merge(drawing_enter);
+
+ merge
+ .attr('xlink:href', (image: ImageElement) => image.data)
+ .attr('width', (image) => image.width)
+ .attr('height', (image) => image.height);
+
+ drawing
+ .exit()
+ .remove();
+
+ }
+}
diff --git a/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts
new file mode 100644
index 00000000..9aa212fd
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/line-drawing.spec.ts
@@ -0,0 +1,49 @@
+import { TestSVGCanvas } from "../../../testing";
+import { Drawing } from "../../models/drawing";
+import { LineDrawingWidget } from "./line-drawing";
+import { LineElement } from "../../models/drawings/line-element";
+
+
+describe('LineDrawingWidget', () => {
+ let svg: TestSVGCanvas;
+ let widget: LineDrawingWidget;
+ let drawing: Drawing;
+
+
+ beforeEach(() => {
+ svg = new TestSVGCanvas();
+ drawing = new Drawing();
+ widget = new LineDrawingWidget();
+ });
+
+ afterEach(() => {
+ svg.destroy();
+ });
+
+ it('should draw line drawing', () => {
+ const line = new LineElement();
+ line.stroke = "#000000";
+ line.stroke_width = 2.0;
+ line.x1 = 10;
+ line.x2 = 20;
+ line.y1 = 30;
+ line.y2 = 40;
+ drawing.element = line;
+
+ 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('line.line_element');
+ expect(drew.size()).toEqual(1);
+ 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('x1')).toEqual('10');
+ expect(line_element.getAttribute('x2')).toEqual('20');
+ expect(line_element.getAttribute('y1')).toEqual('30');
+ expect(line_element.getAttribute('y2')).toEqual('40');
+ });
+});
diff --git a/src/app/cartography/shared/widgets/drawings/line-drawing.ts b/src/app/cartography/shared/widgets/drawings/line-drawing.ts
new file mode 100644
index 00000000..6992eccd
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/line-drawing.ts
@@ -0,0 +1,34 @@
+import { SVGSelection } from "../../models/types";
+import { Drawing } from "../../models/drawing";
+import { LineElement } from "../../models/drawings/line-element";
+
+
+export class LineDrawingWidget {
+ public draw(view: SVGSelection) {
+ const drawing = view
+ .selectAll('line.line_element')
+ .data((d: Drawing) => {
+ return (d.element && d.element instanceof LineElement) ? [d.element] : [];
+ });
+
+ const drawing_enter = drawing
+ .enter()
+ .append('line')
+ .attr('class', 'line_element noselect');
+
+ const merge = drawing.merge(drawing_enter);
+
+ merge
+ .attr('stroke', (line) => line.stroke)
+ .attr('stroke-width', (line) => line.stroke_width)
+ .attr('x1', (line) => line.x1)
+ .attr('x2', (line) => line.x2)
+ .attr('y1', (line) => line.y1)
+ .attr('y2', (line) => line.y2);
+
+ drawing
+ .exit()
+ .remove();
+
+ }
+}
diff --git a/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts
new file mode 100644
index 00000000..012c383b
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/rect-drawing.spec.ts
@@ -0,0 +1,49 @@
+import { TestSVGCanvas } from "../../../testing";
+import { Drawing } from "../../models/drawing";
+import { RectDrawingWidget } from "./rect-drawing";
+import { RectElement } from "../../models/drawings/rect-element";
+
+
+describe('RectDrawingWidget', () => {
+ let svg: TestSVGCanvas;
+ let widget: RectDrawingWidget;
+ let drawing: Drawing;
+
+
+ beforeEach(() => {
+ svg = new TestSVGCanvas();
+ drawing = new Drawing();
+ widget = new RectDrawingWidget();
+ });
+
+ afterEach(() => {
+ svg.destroy();
+ });
+
+ it('should draw rect drawing', () => {
+ const rect = new RectElement();
+ rect.fill = "#FFFFFF";
+ rect.fill_opacity = 1.0;
+ rect.stroke = "#000000";
+ rect.stroke_width = 2.0;
+ rect.width = 100;
+ rect.height = 200;
+ drawing.element = rect;
+
+ 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('rect.rect_element');
+ expect(drew.size()).toEqual(1);
+ const rect_element = drew.nodes()[0];
+ expect(rect_element.getAttribute('fill')).toEqual('#FFFFFF');
+ 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('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
new file mode 100644
index 00000000..968a90be
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/rect-drawing.ts
@@ -0,0 +1,34 @@
+import { SVGSelection } from "../../models/types";
+import { Drawing } from "../../models/drawing";
+import { RectElement } from "../../models/drawings/rect-element";
+
+
+export class RectDrawingWidget {
+ public draw(view: SVGSelection) {
+ const drawing = view
+ .selectAll('rect.rect_element')
+ .data((d: Drawing) => {
+ return (d.element && d.element instanceof RectElement) ? [d.element] : [];
+ });
+
+ const drawing_enter = drawing
+ .enter()
+ .append('rect')
+ .attr('class', 'rect_element noselect');
+
+ const merge = drawing.merge(drawing_enter);
+
+ merge
+ .attr('fill', (rect) => rect.fill)
+ .attr('fill-opacity', (rect) => rect.fill_opacity)
+ .attr('stroke', (rect) => rect.stroke)
+ .attr('stroke-width', (rect) => rect.stroke_width)
+ .attr('width', (rect) => rect.width)
+ .attr('height', (rect) => rect.height);
+
+ drawing
+ .exit()
+ .remove();
+
+ }
+}
diff --git a/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts b/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts
new file mode 100644
index 00000000..344ae86c
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/text-drawing.spec.ts
@@ -0,0 +1,69 @@
+import { TestSVGCanvas } from "../../../testing";
+import { TextDrawingWidget } from "./text-drawing";
+import { Drawing } from "../../models/drawing";
+import { TextElement } from "../../models/drawings/text-element";
+
+describe('TextDrawingWidget', () => {
+ let svg: TestSVGCanvas;
+ let widget: TextDrawingWidget;
+ let drawing: Drawing;
+
+
+ beforeEach(() => {
+ svg = new TestSVGCanvas();
+ drawing = new Drawing();
+ widget = new TextDrawingWidget();
+ });
+
+ afterEach(() => {
+ svg.destroy();
+ });
+
+ it('should draw text drawing', () => {
+ const text = new TextElement();
+ text.text = "THIS IS TEXT";
+ text.fill = "#000000";
+ text.fill_opacity = 1.0;
+ text.font_family = "TypeWriter";
+ text.font_size = 10.0;
+ text.font_weight = "bold";
+
+ 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('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');
+ });
+
+ it('should draw multiline text', () => {
+ const text = new TextElement();
+ text.text = 'THIS' + "\n" + 'IS TEXT';
+ 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(2);
+
+ expect(drew.nodes()[0].innerHTML).toEqual('THIS');
+ expect(drew.nodes()[0].getAttribute('x')).toEqual('0');
+ expect(drew.nodes()[0].getAttribute('dy')).toEqual('0em');
+
+ 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');
+ });
+
+});
diff --git a/src/app/cartography/shared/widgets/drawings/text-drawing.ts b/src/app/cartography/shared/widgets/drawings/text-drawing.ts
new file mode 100644
index 00000000..51791e39
--- /dev/null
+++ b/src/app/cartography/shared/widgets/drawings/text-drawing.ts
@@ -0,0 +1,61 @@
+import { SVGSelection } from "../../models/types";
+import { TextElement } from "../../models/drawings/text-element";
+import { Drawing } from "../../models/drawing";
+
+
+export class TextDrawingWidget {
+ public draw(view: SVGSelection) {
+ const drawing = view
+ .selectAll('text.text_element')
+ .data((d: Drawing) => {
+ return (d.element && d.element instanceof TextElement) ? [d.element] : [];
+ });
+
+ const drawing_enter = drawing
+ .enter()
+ .append('text')
+ .attr('class', 'text_element noselect');
+
+ const merge = drawing.merge(drawing_enter);
+
+ merge
+ .attr('style', (text: TextElement) => {
+ const styles: string[] = [];
+ if (text.font_family) {
+ styles.push(`font-family: "${text.font_family}"`);
+ }
+ if (text.font_size) {
+ styles.push(`font-size: ${text.font_size}pt`);
+ }
+ if (text.font_weight) {
+ styles.push(`font-weight: ${text.font_weight}`);
+ }
+ return styles.join("; ");
+ });
+
+ const lines = merge.selectAll('tspan')
+ .data((text: TextElement) => {
+ return text.text.split(/\r?\n/);
+ });
+
+ const lines_enter = lines
+ .enter()
+ .append('tspan');
+
+ const lines_merge = lines.merge(lines_enter);
+
+ lines_merge
+ .text((line) => line)
+ .attr('x', 0)
+ .attr("dy", (line, i) => i === 0 ? '0em' : '1.2em');
+
+ lines
+ .exit()
+ .remove();
+
+ drawing
+ .exit()
+ .remove();
+
+ }
+}
diff --git a/src/app/cartography/shared/widgets/layers.ts b/src/app/cartography/shared/widgets/layers.ts
index 92bd133c..ffaa7158 100644
--- a/src/app/cartography/shared/widgets/layers.ts
+++ b/src/app/cartography/shared/widgets/layers.ts
@@ -18,7 +18,7 @@ export class LayersWidget implements Widget {
const layers_enter = layers_selection
.enter()
.append('g')
- .attr('class', 'layer')
+ .attr('class', 'layer');
// add container for links
layers_enter
diff --git a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.html b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.html
deleted file mode 100644
index e69de29b..00000000
diff --git a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts
index ca9f75ab..b3d0e43c 100644
--- a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts
+++ b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.spec.ts
@@ -1,25 +1,102 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
+import { HttpClientTestingModule } from "@angular/common/http/testing";
+import { inject } from "@angular/core/testing";
+
+import { mock, instance, capture, when } from "ts-mockito";
+import { HotkeyModule, HotkeysService, Hotkey } from "angular2-hotkeys";
+import { Observable } from "rxjs";
import { ProjectMapShortcutsComponent } from './project-map-shortcuts.component';
+import {
+ ToasterService,
+ MockedToasterService
+} from "../../shared/services/toaster.service";
+import { NodeService } from "../../shared/services/node.service";
+import { HttpServer } from "../../shared/services/http-server.service";
+import { SelectionManager } from "../../cartography/shared/managers/selection-manager";
+import { Server } from "../../shared/models/server";
+import { Node } from "../../cartography/shared/models/node";
+import { Project } from "../../shared/models/project";
+
describe('ProjectMapShortcutsComponent', () => {
let component: ProjectMapShortcutsComponent;
let fixture: ComponentFixture;
+ let hotkeyServiceMock: HotkeysService;
+ let hotkeyServiceInstanceMock: HotkeysService;
+ let nodeServiceMock: NodeService;
beforeEach(async(() => {
+ nodeServiceMock = mock(NodeService);
+ hotkeyServiceMock = mock(HotkeysService);
+ hotkeyServiceInstanceMock = instance(hotkeyServiceMock);
TestBed.configureTestingModule({
+ imports: [
+ HotkeyModule.forRoot(),
+ HttpClientTestingModule
+ ],
+ providers: [
+ HttpServer,
+ { provide: NodeService, useFactory: () => instance(nodeServiceMock)},
+ { provide: HotkeysService, useFactory: () => hotkeyServiceInstanceMock},
+ { provide: ToasterService, useClass: MockedToasterService},
+ ],
declarations: [ ProjectMapShortcutsComponent ]
})
.compileComponents();
}));
- // beforeEach(() => {
- // fixture = TestBed.createComponent(ProjectMapShortcutsComponent);
- // component = fixture.componentInstance;
- // fixture.detectChanges();
- // });
+ beforeEach(() => {
+ fixture = TestBed.createComponent(ProjectMapShortcutsComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+
+ it('should bind delete key', () => {
+ component.ngOnInit();
+ const [hotkey] = capture(hotkeyServiceMock.add).last();
+ expect((hotkey as Hotkey).combo).toEqual([ 'del' ]);
+ expect((hotkey as Hotkey).callback).toEqual(component.onDeleteHandler);
+ });
+
+ it('should remove binding', () => {
+ component.ngOnDestroy();
+ const [hotkey] = capture(hotkeyServiceMock.remove).last();
+ expect((hotkey as Hotkey).combo).toEqual([ 'del' ]);
+ });
+
+ describe('onDeleteHandler', () => {
+ beforeEach(() => {
+ const selectionManagerMock = mock(SelectionManager);
+ const node = new Node();
+ node.node_id = "nodeid";
+ const server = new Server();
+ const project = new Project();
+
+ when(selectionManagerMock.getSelectedNodes()).thenReturn([node]);
+ when(nodeServiceMock.delete(server, node))
+ .thenReturn(Observable.of(node).mapTo(null));
+
+ component.project = project;
+ component.server = server;
+ component.selectionManager = instance(selectionManagerMock);
+ });
+
+ it('should handle delete', inject([ToasterService], (toaster: MockedToasterService) => {
+ component.project.readonly = false;
+ component.onDeleteHandler(null);
+ expect(toaster.successes).toEqual(["Node has been deleted"]);
+ }));
+
+ it('should not delete when project in readonly mode', inject([ToasterService], (toaster: MockedToasterService) => {
+ component.project.readonly = true;
+ component.onDeleteHandler(null);
+ expect(toaster.successes).toEqual([]);
+ }));
+ });
- // it('should create', () => {
- // expect(component).toBeTruthy();
- // });
});
diff --git a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts
index bbd5d77f..6b64476e 100644
--- a/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts
+++ b/src/app/project-map/project-map-shortcuts/project-map-shortcuts.component.ts
@@ -5,13 +5,15 @@ import { SelectionManager } from '../../cartography/shared/managers/selection-ma
import { NodeService } from '../../shared/services/node.service';
import { Server } from '../../shared/models/server';
import { ToasterService } from '../../shared/services/toaster.service';
+import { Project } from "../../shared/models/project";
@Component({
selector: 'app-project-map-shortcuts',
- templateUrl: './project-map-shortcuts.component.html'
+ template: ''
})
export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
+ @Input() project: Project;
@Input() server: Server;
@Input() selectionManager: SelectionManager;
@@ -24,7 +26,12 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
) { }
ngOnInit() {
- this.deleteHotkey = new Hotkey('del', (event: KeyboardEvent): boolean => {
+ this.deleteHotkey = new Hotkey('del', this.onDeleteHandler);
+ this.hotkeysService.add(this.deleteHotkey);
+ }
+
+ onDeleteHandler(event: KeyboardEvent):boolean {
+ if (!this.project.readonly) {
const selectedNodes = this.selectionManager.getSelectedNodes();
if (selectedNodes) {
selectedNodes.forEach((node) => {
@@ -33,10 +40,8 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
});
});
}
- return false;
- });
-
- this.hotkeysService.add(this.deleteHotkey);
+ }
+ return false;
}
ngOnDestroy() {
diff --git a/src/app/project-map/project-map.component.html b/src/app/project-map/project-map.component.html
index 45900a09..8a437510 100644
--- a/src/app/project-map/project-map.component.html
+++ b/src/app/project-map/project-map.component.html
@@ -21,7 +21,7 @@
-
+
@@ -33,20 +33,20 @@
-
+
-
+
-
+
@@ -55,4 +55,4 @@
-
+
diff --git a/src/app/shared/models/project.ts b/src/app/shared/models/project.ts
index 3578f378..49301e55 100644
--- a/src/app/shared/models/project.ts
+++ b/src/app/shared/models/project.ts
@@ -9,4 +9,5 @@ export class Project {
scene_height: number;
scene_width: number;
status: string;
+ readonly: boolean = true;
}
diff --git a/src/app/shared/node-context-menu/node-context-menu.component.html b/src/app/shared/node-context-menu/node-context-menu.component.html
index 986aceaf..bd0a086b 100644
--- a/src/app/shared/node-context-menu/node-context-menu.component.html
+++ b/src/app/shared/node-context-menu/node-context-menu.component.html
@@ -3,7 +3,7 @@
-
-
+
+
diff --git a/src/app/shared/node-context-menu/node-context-menu.component.ts b/src/app/shared/node-context-menu/node-context-menu.component.ts
index f37b1a8f..1fda55c5 100644
--- a/src/app/shared/node-context-menu/node-context-menu.component.ts
+++ b/src/app/shared/node-context-menu/node-context-menu.component.ts
@@ -1,8 +1,9 @@
-import {ChangeDetectorRef, Component, Input, OnInit, ViewChild} from '@angular/core';
-import {MatMenuTrigger} from "@angular/material";
-import {DomSanitizer} from "@angular/platform-browser";
-import {Node} from "../../cartography/shared/models/node";
-import {Server} from "../models/server";
+import { ChangeDetectorRef, Component, Input, OnInit, ViewChild } from '@angular/core';
+import { MatMenuTrigger } from "@angular/material";
+import { DomSanitizer } from "@angular/platform-browser";
+import { Node } from "../../cartography/shared/models/node";
+import { Server } from "../models/server";
+import { Project } from "../models/project";
@Component({
@@ -11,6 +12,7 @@ import {Server} from "../models/server";
styleUrls: ['./node-context-menu.component.scss']
})
export class NodeContextMenuComponent implements OnInit {
+ @Input() project: Project;
@Input() server: Server;
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
diff --git a/src/app/shared/services/settings.service.spec.ts b/src/app/shared/services/settings.service.spec.ts
index d7b473a7..290cdaba 100644
--- a/src/app/shared/services/settings.service.spec.ts
+++ b/src/app/shared/services/settings.service.spec.ts
@@ -64,7 +64,9 @@ describe('SettingsService', () => {
let changedSettings: Settings;
service.set('crash_reports', true);
- service.subscribe(settings => changedSettings = settings);
+ service.subscribe(settings => {
+ changedSettings = settings
+ });
service.set('crash_reports', false);
expect(changedSettings.crash_reports).toEqual(false);
diff --git a/src/app/shared/services/symbol.service.spec.ts b/src/app/shared/services/symbol.service.spec.ts
index 38e36191..90a7ad3d 100644
--- a/src/app/shared/services/symbol.service.spec.ts
+++ b/src/app/shared/services/symbol.service.spec.ts
@@ -4,8 +4,6 @@ import { HttpClient } from '@angular/common/http';
import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing';
import { HttpServer } from './http-server.service';
import { Server } from '../models/server';
-import { Node } from '../../cartography/shared/models/node';
-import { Port } from '../models/port';
import { getTestServer } from './testing';
import { SymbolService } from './symbol.service';
import { Symbol } from '../../cartography/shared/models/symbol';
@@ -61,7 +59,6 @@ describe('SymbolService', () => {
}));
it('should load symbols', inject([SymbolService], (service: SymbolService) => {
- let call = 0;
service.load(server).subscribe();
const req = httpTestingController.expectOne(
diff --git a/src/app/shared/services/toaster.service.ts b/src/app/shared/services/toaster.service.ts
index eebf9dfd..472337a9 100644
--- a/src/app/shared/services/toaster.service.ts
+++ b/src/app/shared/services/toaster.service.ts
@@ -13,3 +13,23 @@ export class ToasterService {
this.snackbar.open(message, null, { duration: 2000 });
}
}
+
+
+@Injectable()
+export class MockedToasterService {
+ public errors: string[];
+ public successes: string[];
+
+ constructor() {
+ this.errors = [];
+ this.successes = [];
+ }
+
+ public error(message: string) {
+ this.errors.push(message);
+ }
+
+ public success(message: string) {
+ this.successes.push(message);
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 4a49358a..3099cca0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -594,14 +594,14 @@ ansi-html@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
-ansi-regex@*, ansi-regex@^3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
-
ansi-regex@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+
ansi-styles@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
@@ -2282,7 +2282,7 @@ debug@2, debug@2.6.9, debug@^2.1.2, debug@^2.1.3, debug@^2.2.0, debug@^2.3.3, de
dependencies:
ms "2.0.0"
-debuglog@*, debuglog@^1.0.1:
+debuglog@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492"
@@ -4061,7 +4061,7 @@ import-local@^1.0.0:
pkg-dir "^2.0.0"
resolve-cwd "^2.0.0"
-imurmurhash@*, imurmurhash@^0.1.4:
+imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -4892,10 +4892,6 @@ lockfile@~1.0.2:
dependencies:
signal-exit "^3.0.2"
-lodash._baseindexof@*:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c"
-
lodash._baseuniq@~4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8"
@@ -4903,28 +4899,10 @@ lodash._baseuniq@~4.6.0:
lodash._createset "~4.0.0"
lodash._root "~3.0.0"
-lodash._bindcallback@*:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e"
-
-lodash._cacheindexof@*:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92"
-
-lodash._createcache@*:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093"
- dependencies:
- lodash._getnative "^3.0.0"
-
lodash._createset@~4.0.0:
version "4.0.3"
resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26"
-lodash._getnative@*, lodash._getnative@^3.0.0:
- version "3.9.1"
- resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5"
-
lodash._root@~3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692"
@@ -4941,10 +4919,6 @@ lodash.mergewith@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
-lodash.restparam@*:
- version "3.6.1"
- resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805"
-
lodash.tail@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
@@ -6757,7 +6731,7 @@ readable-stream@~2.1.5:
string_decoder "~0.10.x"
util-deprecate "~1.0.1"
-readdir-scoped-modules@*, readdir-scoped-modules@^1.0.0:
+readdir-scoped-modules@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747"
dependencies:
@@ -8384,7 +8358,7 @@ uws@~9.14.0:
version "9.14.0"
resolved "https://registry.yarnpkg.com/uws/-/uws-9.14.0.tgz#fac8386befc33a7a3705cbd58dc47b430ca4dd95"
-validate-npm-package-license@*, validate-npm-package-license@^3.0.1:
+validate-npm-package-license@^3.0.1:
version "3.0.3"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz#81643bcbef1bdfecd4623793dc4648948ba98338"
dependencies: