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 = '' + + '' + + 'Line' + + '' + + ''; + + 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 = ' Label '; + 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: