mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-04-12 04:49:55 +00:00
Merge pull request #141 from GNS3/read-only-mode
Read-only mode for WebUI per project and other features, Fixes: #113
This commit is contained in:
commit
a536452cd0
39
src/app/cartography/shared/helpers/font-fixer.spec.ts
Normal file
39
src/app/cartography/shared/helpers/font-fixer.spec.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { FontFixer } from "./font-fixer";
|
||||
import { Font } from "../models/font";
|
||||
|
||||
|
||||
describe('FontFixer', () => {
|
||||
let fixer: FontFixer;
|
||||
|
||||
beforeEach(() => {
|
||||
fixer = new FontFixer();
|
||||
});
|
||||
|
||||
it('should fix TypeWriter font and 10px size', () => {
|
||||
const font: Font = {
|
||||
'font_family': 'TypeWriter',
|
||||
'font_size': 10,
|
||||
'font_weight': 'bold'
|
||||
};
|
||||
|
||||
expect(fixer.fix(font)).toEqual( {
|
||||
'font_family': 'Noto Sans',
|
||||
'font_size': 11,
|
||||
'font_weight': 'bold'
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fix other fonts', () => {
|
||||
const font: Font = {
|
||||
'font_family': 'OtherFont',
|
||||
'font_size': 11,
|
||||
'font_weight': 'bold'
|
||||
};
|
||||
|
||||
expect(fixer.fix(font)).toEqual( {
|
||||
'font_family': 'OtherFont',
|
||||
'font_size': 11,
|
||||
'font_weight': 'bold'
|
||||
});
|
||||
});
|
||||
});
|
21
src/app/cartography/shared/helpers/font-fixer.ts
Normal file
21
src/app/cartography/shared/helpers/font-fixer.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Font } from "../models/font";
|
||||
|
||||
/**
|
||||
* GNS3 GUI doesn't update font when cannot find font in user system, this fixer fixes it
|
||||
*/
|
||||
@Injectable()
|
||||
export class FontFixer {
|
||||
static DEFAULT_FONT = "TypeWriter";
|
||||
static DEFAULT_SIZE = 10;
|
||||
static REPLACE_BY_FONT = "Noto Sans";
|
||||
static REPLACE_BY_SIZE = 11;
|
||||
|
||||
public fix(font: Font): Font {
|
||||
if (font.font_family === FontFixer.DEFAULT_FONT && font.font_size === FontFixer.DEFAULT_SIZE) {
|
||||
font.font_family = FontFixer.REPLACE_BY_FONT;
|
||||
font.font_size = FontFixer.REPLACE_BY_SIZE;
|
||||
}
|
||||
return font;
|
||||
}
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
import { QtDasharrayFixer } from "./qt-dasharray-fixer";
|
||||
|
||||
|
||||
describe('QtDashArrayFixer', () => {
|
||||
let fixer: QtDasharrayFixer;
|
||||
|
||||
beforeEach(() => {
|
||||
fixer = new QtDasharrayFixer();
|
||||
});
|
||||
|
||||
it('should fix when matches mapping', () => {
|
||||
expect(fixer.fix('25, 25')).toEqual('10, 2');
|
||||
});
|
||||
|
||||
it('should not fix when do not match mapping', () => {
|
||||
expect(fixer.fix('1, 2, 3')).toEqual('1, 2, 3');
|
||||
});
|
||||
});
|
23
src/app/cartography/shared/helpers/qt-dasharray-fixer.ts
Normal file
23
src/app/cartography/shared/helpers/qt-dasharray-fixer.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
import { Font } from "../models/font";
|
||||
|
||||
/**
|
||||
* GNS3 GUI performs mapping from QT styles to SVG dasharray, but styles don't match
|
||||
* what you can see, here are improvements; later on please adjust GUI for proper values.
|
||||
*/
|
||||
@Injectable()
|
||||
export class QtDasharrayFixer {
|
||||
static MAPPING = {
|
||||
'25, 25': '10, 2',
|
||||
'5, 25': '4, 2',
|
||||
'5, 25, 25': '5, 5, 1, 5',
|
||||
'25, 25, 5, 25, 5': '5, 2, 5, 2, 5'
|
||||
};
|
||||
|
||||
public fix(dasharray: string): string {
|
||||
if (dasharray in QtDasharrayFixer.MAPPING) {
|
||||
return QtDasharrayFixer.MAPPING[dasharray];
|
||||
}
|
||||
return dasharray;
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ describe('EllipseConverter', () => {
|
||||
node.setAttribute("fill-opacity", "1.0");
|
||||
node.setAttribute("stroke", "#000000");
|
||||
node.setAttribute("stroke-width", "2");
|
||||
node.setAttribute("stroke-dasharray", "5,25,25");
|
||||
node.setAttribute("cx", "63");
|
||||
node.setAttribute("cy", "59");
|
||||
node.setAttribute("rx", "63");
|
||||
@ -24,6 +25,7 @@ describe('EllipseConverter', () => {
|
||||
expect(drawing.fill_opacity).toEqual(1.0);
|
||||
expect(drawing.stroke).toEqual("#000000");
|
||||
expect(drawing.stroke_width).toEqual(2.0);
|
||||
expect(drawing.stroke_dasharray).toEqual("5,25,25");
|
||||
expect(drawing.cx).toEqual(63);
|
||||
expect(drawing.cy).toEqual(59);
|
||||
expect(drawing.rx).toEqual(63);
|
||||
|
@ -13,7 +13,7 @@ export class EllipseConverter implements SvgConverter {
|
||||
|
||||
const fill_opacity = node.attributes.getNamedItem("fill-opacity");
|
||||
if (fill) {
|
||||
drawing.fill_opacity = parseInt(fill_opacity.value, 10);
|
||||
drawing.fill_opacity = parseFloat(fill_opacity.value);
|
||||
}
|
||||
|
||||
const stroke = node.attributes.getNamedItem("stroke");
|
||||
@ -26,6 +26,11 @@ export class EllipseConverter implements SvgConverter {
|
||||
drawing.stroke_width = parseInt(stroke_width.value, 10);
|
||||
}
|
||||
|
||||
const stroke_dasharray = node.attributes.getNamedItem("stroke-dasharray");
|
||||
if (stroke_dasharray) {
|
||||
drawing.stroke_dasharray = stroke_dasharray.value;
|
||||
}
|
||||
|
||||
const cx = node.attributes.getNamedItem('cx');
|
||||
if (cx) {
|
||||
drawing.cx = parseInt(cx.value, 10);
|
||||
|
@ -12,6 +12,7 @@ describe('LineConverter', () => {
|
||||
const node = document.createElement("line");
|
||||
node.setAttribute("stroke", "#000000");
|
||||
node.setAttribute("stroke-width", "2");
|
||||
node.setAttribute("stroke-dasharray", "5,25,25");
|
||||
node.setAttribute("x1", "10.10");
|
||||
node.setAttribute("x2", "20");
|
||||
node.setAttribute("y1", "30");
|
||||
@ -20,6 +21,7 @@ describe('LineConverter', () => {
|
||||
const drawing = lineConverter.convert(node);
|
||||
expect(drawing.stroke).toEqual("#000000");
|
||||
expect(drawing.stroke_width).toEqual(2.0);
|
||||
expect(drawing.stroke_dasharray).toEqual("5,25,25");
|
||||
expect(drawing.x1).toEqual(10);
|
||||
expect(drawing.x2).toEqual(20);
|
||||
expect(drawing.y1).toEqual(30);
|
||||
|
@ -16,6 +16,11 @@ export class LineConverter implements SvgConverter {
|
||||
drawing.stroke_width = parseInt(stroke_width.value, 10);
|
||||
}
|
||||
|
||||
const stroke_dasharray = node.attributes.getNamedItem("stroke-dasharray");
|
||||
if (stroke_dasharray) {
|
||||
drawing.stroke_dasharray = stroke_dasharray.value;
|
||||
}
|
||||
|
||||
const x1 = node.attributes.getNamedItem('x1');
|
||||
if (x1) {
|
||||
drawing.x1 = parseInt(x1.value, 10);
|
||||
|
@ -11,18 +11,19 @@ describe('RectConverter', () => {
|
||||
it('should parse attributes', () => {
|
||||
const node = document.createElement("rect");
|
||||
node.setAttribute("fill", "#ffffff");
|
||||
node.setAttribute("fill-opacity", "1.0");
|
||||
node.setAttribute("fill-opacity", "0.7");
|
||||
node.setAttribute("stroke", "#000000");
|
||||
node.setAttribute("stroke-width", "2");
|
||||
node.setAttribute("stroke-dasharray", "5,25,25");
|
||||
|
||||
node.setAttribute("width", "100px");
|
||||
node.setAttribute("height", "200px");
|
||||
|
||||
const drawing = rectConverter.convert(node);
|
||||
expect(drawing.fill).toEqual("#ffffff");
|
||||
expect(drawing.fill_opacity).toEqual(1.0);
|
||||
expect(drawing.fill_opacity).toEqual(0.7);
|
||||
expect(drawing.stroke).toEqual("#000000");
|
||||
expect(drawing.stroke_width).toEqual(2.0);
|
||||
expect(drawing.stroke_dasharray).toEqual("5,25,25");
|
||||
expect(drawing.width).toEqual(100);
|
||||
expect(drawing.height).toEqual(200);
|
||||
});
|
||||
|
@ -13,7 +13,7 @@ export class RectConverter implements SvgConverter {
|
||||
|
||||
const fill_opacity = node.attributes.getNamedItem("fill-opacity");
|
||||
if (fill) {
|
||||
drawing.fill_opacity = parseInt(fill_opacity.value, 10);
|
||||
drawing.fill_opacity = parseFloat(fill_opacity.value);
|
||||
}
|
||||
|
||||
const stroke = node.attributes.getNamedItem("stroke");
|
||||
@ -26,6 +26,11 @@ export class RectConverter implements SvgConverter {
|
||||
drawing.stroke_width = parseInt(stroke_width.value, 10);
|
||||
}
|
||||
|
||||
const stroke_dasharray = node.attributes.getNamedItem("stroke-dasharray");
|
||||
if (stroke_dasharray) {
|
||||
drawing.stroke_dasharray = stroke_dasharray.value;
|
||||
}
|
||||
|
||||
const width = node.attributes.getNamedItem('width');
|
||||
if (width) {
|
||||
drawing.width = parseInt(width.value, 10);
|
||||
|
@ -16,6 +16,7 @@ describe('TextConverter', () => {
|
||||
node.setAttribute("font-family", "TypeWriter");
|
||||
node.setAttribute("font-size", "10.0");
|
||||
node.setAttribute("font-weight", "bold");
|
||||
node.setAttribute("text-decoration", "line-through");
|
||||
|
||||
const drawing = textConverter.convert(node);
|
||||
expect(drawing.text).toEqual("Text");
|
||||
@ -24,6 +25,7 @@ describe('TextConverter', () => {
|
||||
expect(drawing.font_family).toEqual("TypeWriter");
|
||||
expect(drawing.font_size).toEqual(10.0);
|
||||
expect(drawing.font_weight).toEqual("bold");
|
||||
expect(drawing.text_decoration).toEqual("line-through");
|
||||
});
|
||||
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ export class TextConverter implements SvgConverter {
|
||||
|
||||
const fill_opacity = node.attributes.getNamedItem('fill-opacity');
|
||||
if (fill_opacity) {
|
||||
drawing.fill_opacity = +fill_opacity.value;
|
||||
drawing.fill_opacity = parseFloat(fill_opacity.value);
|
||||
}
|
||||
|
||||
const font_family = node.attributes.getNamedItem('font-family');
|
||||
@ -33,6 +33,12 @@ export class TextConverter implements SvgConverter {
|
||||
drawing.font_weight = font_weight.value;
|
||||
}
|
||||
|
||||
const text_decoration = node.attributes.getNamedItem('text-decoration');
|
||||
if (text_decoration) {
|
||||
drawing.text_decoration = text_decoration.value;
|
||||
}
|
||||
|
||||
|
||||
return drawing;
|
||||
}
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ describe('SelectionManager', () => {
|
||||
const node = new Node();
|
||||
node.node_id = "test1";
|
||||
const drawing = new Drawing();
|
||||
drawing.drawing_id = "test1"
|
||||
drawing.drawing_id = "test1";
|
||||
manager.setSelectedLinks([link]);
|
||||
manager.setSelectedNodes([node]);
|
||||
manager.setSelectedDrawings([drawing]);
|
||||
|
@ -12,4 +12,5 @@ export class EllipseElement implements DrawingElement {
|
||||
ry: number;
|
||||
stroke: string;
|
||||
stroke_width: number;
|
||||
stroke_dasharray: string;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ export class LineElement implements DrawingElement {
|
||||
width: number;
|
||||
stroke: string;
|
||||
stroke_width: number;
|
||||
stroke_dasharray: string;
|
||||
x1: number;
|
||||
x2: number;
|
||||
y1: number;
|
||||
|
@ -8,4 +8,5 @@ export class RectElement implements DrawingElement {
|
||||
fill_opacity: number;
|
||||
stroke: string;
|
||||
stroke_width: number;
|
||||
stroke_dasharray: string;
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { DrawingElement } from "./drawing-element";
|
||||
import { Font } from "../font";
|
||||
|
||||
|
||||
export class TextElement implements DrawingElement {
|
||||
export class TextElement implements DrawingElement, Font {
|
||||
height: number;
|
||||
width: number;
|
||||
text: string;
|
||||
@ -10,4 +11,5 @@ export class TextElement implements DrawingElement {
|
||||
font_family: string;
|
||||
font_size: number;
|
||||
font_weight: string;
|
||||
text_decoration: string;
|
||||
}
|
||||
|
5
src/app/cartography/shared/models/font.ts
Normal file
5
src/app/cartography/shared/models/font.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export interface Font {
|
||||
font_family: string;
|
||||
font_size: number;
|
||||
font_weight: string;
|
||||
}
|
@ -44,7 +44,7 @@ export class DrawingsWidget implements Widget {
|
||||
|
||||
const drawing_merge = drawing.merge(drawing_enter)
|
||||
.attr('transform', (d: Drawing) => {
|
||||
return `translate(${d.x},${d.y})`;
|
||||
return `translate(${d.x},${d.y}) rotate(${d.rotation})`;
|
||||
});
|
||||
|
||||
this.drawingWidgets.forEach((widget) => {
|
||||
|
@ -26,6 +26,7 @@ describe('EllipseDrawingWidget', () => {
|
||||
ellipse.fill_opacity = 2.0;
|
||||
ellipse.stroke = "#000000";
|
||||
ellipse.stroke_width = 2.0;
|
||||
ellipse.stroke_dasharray = "5,25,25";
|
||||
ellipse.cx = 10;
|
||||
ellipse.cy = 20;
|
||||
ellipse.rx = 30;
|
||||
@ -45,6 +46,7 @@ describe('EllipseDrawingWidget', () => {
|
||||
expect(ellipse_element.getAttribute('fill-opacity')).toEqual('2');
|
||||
expect(ellipse_element.getAttribute('stroke')).toEqual('#000000');
|
||||
expect(ellipse_element.getAttribute('stroke-width')).toEqual('2');
|
||||
expect(ellipse_element.getAttribute('stroke-dasharray')).toEqual('5,25,25');
|
||||
expect(ellipse_element.getAttribute('cx')).toEqual('10');
|
||||
expect(ellipse_element.getAttribute('cy')).toEqual('20');
|
||||
expect(ellipse_element.getAttribute('rx')).toEqual('30');
|
||||
|
@ -2,9 +2,16 @@ import { SVGSelection } from "../../models/types";
|
||||
import { Drawing } from "../../models/drawing";
|
||||
import { EllipseElement } from "../../models/drawings/ellipse-element";
|
||||
import { DrawingWidget } from "./drawing-widget";
|
||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||
|
||||
|
||||
export class EllipseDrawingWidget implements DrawingWidget {
|
||||
private qtDasharrayFixer: QtDasharrayFixer;
|
||||
|
||||
constructor() {
|
||||
this.qtDasharrayFixer = new QtDasharrayFixer();
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
const drawing = view
|
||||
.selectAll<SVGEllipseElement, EllipseElement>('ellipse.ellipse_element')
|
||||
@ -24,6 +31,7 @@ export class EllipseDrawingWidget implements DrawingWidget {
|
||||
.attr('fill-opacity', (ellipse) => ellipse.fill_opacity)
|
||||
.attr('stroke', (ellipse) => ellipse.stroke)
|
||||
.attr('stroke-width', (ellipse) => ellipse.stroke_width)
|
||||
.attr('stroke-dasharray', (ellipse) => this.qtDasharrayFixer.fix(ellipse.stroke_dasharray))
|
||||
.attr('cx', (ellipse) => ellipse.cx)
|
||||
.attr('cy', (ellipse) => ellipse.cy)
|
||||
.attr('rx', (ellipse) => ellipse.rx)
|
||||
|
@ -24,6 +24,7 @@ describe('LineDrawingWidget', () => {
|
||||
const line = new LineElement();
|
||||
line.stroke = "#000000";
|
||||
line.stroke_width = 2.0;
|
||||
line.stroke_dasharray = "5,25,25";
|
||||
line.x1 = 10;
|
||||
line.x2 = 20;
|
||||
line.y1 = 30;
|
||||
@ -41,6 +42,7 @@ describe('LineDrawingWidget', () => {
|
||||
const line_element = drew.nodes()[0];
|
||||
expect(line_element.getAttribute('stroke')).toEqual('#000000');
|
||||
expect(line_element.getAttribute('stroke-width')).toEqual('2');
|
||||
expect(line_element.getAttribute('stroke-dasharray')).toEqual('5,25,25');
|
||||
expect(line_element.getAttribute('x1')).toEqual('10');
|
||||
expect(line_element.getAttribute('x2')).toEqual('20');
|
||||
expect(line_element.getAttribute('y1')).toEqual('30');
|
||||
|
@ -2,9 +2,16 @@ import { SVGSelection } from "../../models/types";
|
||||
import { Drawing } from "../../models/drawing";
|
||||
import { LineElement } from "../../models/drawings/line-element";
|
||||
import { DrawingWidget } from "./drawing-widget";
|
||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||
|
||||
|
||||
export class LineDrawingWidget implements DrawingWidget {
|
||||
private qtDasharrayFixer: QtDasharrayFixer;
|
||||
|
||||
constructor() {
|
||||
this.qtDasharrayFixer = new QtDasharrayFixer();
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
const drawing = view
|
||||
.selectAll<SVGLineElement, LineElement>('line.line_element')
|
||||
@ -22,6 +29,7 @@ export class LineDrawingWidget implements DrawingWidget {
|
||||
merge
|
||||
.attr('stroke', (line) => line.stroke)
|
||||
.attr('stroke-width', (line) => line.stroke_width)
|
||||
.attr('stroke-dasharray', (line) => this.qtDasharrayFixer.fix(line.stroke_dasharray))
|
||||
.attr('x1', (line) => line.x1)
|
||||
.attr('x2', (line) => line.x2)
|
||||
.attr('y1', (line) => line.y1)
|
||||
|
@ -26,6 +26,7 @@ describe('RectDrawingWidget', () => {
|
||||
rect.fill_opacity = 1.0;
|
||||
rect.stroke = "#000000";
|
||||
rect.stroke_width = 2.0;
|
||||
rect.stroke_dasharray = "5,25,25";
|
||||
rect.width = 100;
|
||||
rect.height = 200;
|
||||
drawing.element = rect;
|
||||
@ -43,6 +44,7 @@ describe('RectDrawingWidget', () => {
|
||||
expect(rect_element.getAttribute('fill-opacity')).toEqual('1');
|
||||
expect(rect_element.getAttribute('stroke')).toEqual('#000000');
|
||||
expect(rect_element.getAttribute('stroke-width')).toEqual('2');
|
||||
expect(rect_element.getAttribute('stroke-dasharray')).toEqual('5,25,25');
|
||||
expect(rect_element.getAttribute('width')).toEqual('100');
|
||||
expect(rect_element.getAttribute('height')).toEqual('200');
|
||||
});
|
||||
|
@ -2,9 +2,16 @@ import { SVGSelection } from "../../models/types";
|
||||
import { Drawing } from "../../models/drawing";
|
||||
import { RectElement } from "../../models/drawings/rect-element";
|
||||
import { DrawingWidget } from "./drawing-widget";
|
||||
import { QtDasharrayFixer } from "../../helpers/qt-dasharray-fixer";
|
||||
|
||||
|
||||
export class RectDrawingWidget implements DrawingWidget {
|
||||
private qtDasharrayFixer: QtDasharrayFixer;
|
||||
|
||||
constructor() {
|
||||
this.qtDasharrayFixer = new QtDasharrayFixer();
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
const drawing = view
|
||||
.selectAll<SVGRectElement, RectElement>('rect.rect_element')
|
||||
@ -24,6 +31,7 @@ export class RectDrawingWidget implements DrawingWidget {
|
||||
.attr('fill-opacity', (rect) => rect.fill_opacity)
|
||||
.attr('stroke', (rect) => rect.stroke)
|
||||
.attr('stroke-width', (rect) => rect.stroke_width)
|
||||
.attr('stroke-dasharray', (rect) => this.qtDasharrayFixer.fix(rect.stroke_dasharray))
|
||||
.attr('width', (rect) => rect.width)
|
||||
.attr('height', (rect) => rect.height);
|
||||
|
||||
|
@ -27,6 +27,7 @@ describe('TextDrawingWidget', () => {
|
||||
text.font_family = "TypeWriter";
|
||||
text.font_size = 10.0;
|
||||
text.font_weight = "bold";
|
||||
text.text_decoration = "line-through";
|
||||
|
||||
drawing.element = text;
|
||||
|
||||
@ -39,8 +40,10 @@ describe('TextDrawingWidget', () => {
|
||||
const drew = drawings_merge.selectAll<SVGTextElement, TextElement>('text.text_element');
|
||||
expect(drew.size()).toEqual(1);
|
||||
const text_element = drew.nodes()[0];
|
||||
expect(text_element.innerHTML).toEqual('<tspan x="0" dy="0em">THIS IS TEXT</tspan>');
|
||||
expect(text_element.getAttribute('style')).toEqual('font-family: "TypeWriter"; font-size: 10pt; font-weight: bold');
|
||||
expect(text_element.innerHTML).toEqual('<tspan xml:space="preserve" x="0" dy="0em">THIS IS TEXT</tspan>');
|
||||
expect(text_element.getAttribute('fill')).toEqual("#000000");
|
||||
expect(text_element.getAttribute('style')).toEqual('font-family: "Noto Sans"; font-size: 11pt; font-weight: bold');
|
||||
expect(text_element.getAttribute('text-decoration')).toEqual("line-through");
|
||||
});
|
||||
|
||||
it('should draw multiline text', () => {
|
||||
@ -66,4 +69,24 @@ describe('TextDrawingWidget', () => {
|
||||
expect(drew.nodes()[1].getAttribute('dy')).toEqual('1.2em');
|
||||
});
|
||||
|
||||
it('should draw whitespaces', () => {
|
||||
const text = new TextElement();
|
||||
text.text = ' Text with whitespaces';
|
||||
drawing.element = text;
|
||||
|
||||
const drawings = svg.canvas.selectAll<SVGGElement, Drawing>('g.drawing').data([drawing]);
|
||||
const drawings_enter = drawings.enter().append<SVGGElement>('g').classed('drawing', true);
|
||||
const drawings_merge = drawings.merge(drawings_enter);
|
||||
|
||||
widget.draw(drawings_merge);
|
||||
|
||||
const drew = drawings_merge.selectAll<SVGTSpanElement, string>('tspan');
|
||||
expect(drew.nodes().length).toEqual(1);
|
||||
|
||||
expect(drew.nodes()[0].innerHTML).toEqual(' Text with whitespaces');
|
||||
expect(drew.nodes()[0].getAttribute('space')).toEqual('preserve');
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
@ -2,10 +2,21 @@ import { SVGSelection } from "../../models/types";
|
||||
import { TextElement } from "../../models/drawings/text-element";
|
||||
import { Drawing } from "../../models/drawing";
|
||||
import { DrawingWidget } from "./drawing-widget";
|
||||
import { FontFixer } from "../../helpers/font-fixer";
|
||||
import { select } from "d3-selection";
|
||||
|
||||
|
||||
export class TextDrawingWidget implements DrawingWidget {
|
||||
static MARGIN = 4;
|
||||
|
||||
private fontFixer: FontFixer;
|
||||
|
||||
constructor() {
|
||||
this.fontFixer = new FontFixer();
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
|
||||
const drawing = view
|
||||
.selectAll<SVGTextElement, TextElement>('text.text_element')
|
||||
.data((d: Drawing) => {
|
||||
@ -18,21 +29,24 @@ export class TextDrawingWidget implements DrawingWidget {
|
||||
.attr('class', 'text_element noselect');
|
||||
|
||||
const merge = drawing.merge(drawing_enter);
|
||||
|
||||
merge
|
||||
.attr('style', (text: TextElement) => {
|
||||
const font = this.fontFixer.fix(text);
|
||||
|
||||
const styles: string[] = [];
|
||||
if (text.font_family) {
|
||||
if (font.font_family) {
|
||||
styles.push(`font-family: "${text.font_family}"`);
|
||||
}
|
||||
if (text.font_size) {
|
||||
if (font.font_size) {
|
||||
styles.push(`font-size: ${text.font_size}pt`);
|
||||
}
|
||||
if (text.font_weight) {
|
||||
if (font.font_weight) {
|
||||
styles.push(`font-weight: ${text.font_weight}`);
|
||||
}
|
||||
return styles.join("; ");
|
||||
});
|
||||
})
|
||||
.attr('fill', (text) => text.fill)
|
||||
.attr('text-decoration', (text) => text.text_decoration);
|
||||
|
||||
const lines = merge.selectAll<SVGTSpanElement, string>('tspan')
|
||||
.data((text: TextElement) => {
|
||||
@ -47,13 +61,22 @@ export class TextDrawingWidget implements DrawingWidget {
|
||||
|
||||
lines_merge
|
||||
.text((line) => line)
|
||||
.attr('x', 0)
|
||||
.attr("dy", (line, i) => i === 0 ? '0em' : '1.2em');
|
||||
.attr('xml:space', 'preserve')
|
||||
.attr('x', 0)
|
||||
.attr("dy", (line, i) => i === 0 ? '0em' : '1.2em');
|
||||
|
||||
lines
|
||||
.exit()
|
||||
.remove();
|
||||
|
||||
merge.attr('transform', function (this: SVGTextElement) {
|
||||
// SVG calculates y pos by the /bottom/ of the first tspan, hence we need to make some
|
||||
// approx and make it matching to GUI
|
||||
const tspan = select(this).selectAll<SVGTSpanElement, string>('tspan');
|
||||
const height = this.getBBox().height / tspan.size();
|
||||
return `translate(${TextDrawingWidget.MARGIN}, ${height - TextDrawingWidget.MARGIN})`;
|
||||
});
|
||||
|
||||
drawing
|
||||
.exit()
|
||||
.remove();
|
||||
|
@ -8,9 +8,10 @@ import { Link } from "../models/link";
|
||||
export class EthernetLinkWidget implements Widget {
|
||||
|
||||
public draw(view: SVGSelection, link: Link) {
|
||||
|
||||
const link_data = [[
|
||||
[link.source.x + link.source.width / 2., link.source.y + link.source.height / 2.],
|
||||
[link.target.x + link.target.width / 2., link.target.y + link.source.height / 2.]
|
||||
[link.target.x + link.target.width / 2., link.target.y + link.target.height / 2.]
|
||||
]];
|
||||
|
||||
const value_line = line();
|
||||
|
@ -63,7 +63,6 @@ export class LinksWidget implements Widget {
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
const status_started = link_group
|
||||
.selectAll<SVGCircleElement, LinkStatus>('circle.status_started')
|
||||
.data(statuses.filter((link_status: LinkStatus) => link_status.status === 'started'));
|
||||
|
69
src/app/cartography/shared/widgets/nodes.spec.ts
Normal file
69
src/app/cartography/shared/widgets/nodes.spec.ts
Normal file
@ -0,0 +1,69 @@
|
||||
|
||||
import { TestSVGCanvas } from "../../testing";
|
||||
import { NodesWidget } from "./nodes";
|
||||
import { Node } from "../models/node";
|
||||
import { Label } from "../models/label";
|
||||
|
||||
|
||||
describe('NodesWidget', () => {
|
||||
let svg: TestSVGCanvas;
|
||||
let widget: NodesWidget;
|
||||
|
||||
beforeEach(() => {
|
||||
svg = new TestSVGCanvas();
|
||||
widget = new NodesWidget();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
svg.destroy();
|
||||
});
|
||||
|
||||
describe('draggable behaviour', () => {
|
||||
let node: Node;
|
||||
const tryToDrag = () => {
|
||||
const drew = svg.canvas.selectAll<SVGGElement, Node>('g.node');
|
||||
const drewNode = drew.nodes()[0];
|
||||
|
||||
drewNode.dispatchEvent(
|
||||
new MouseEvent('mousedown', {
|
||||
clientX: 150, clientY: 250, relatedTarget: drewNode,
|
||||
screenY: 1024, screenX: 1024, view: window
|
||||
})
|
||||
);
|
||||
|
||||
window.dispatchEvent(new MouseEvent('mousemove', {clientX: 300, clientY: 300}));
|
||||
window.dispatchEvent(new MouseEvent('mouseup', {clientX: 300, clientY: 300, view: window}));
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
node = new Node();
|
||||
node.x = 100;
|
||||
node.y = 200;
|
||||
node.width = 100;
|
||||
node.height = 100;
|
||||
node.label = new Label();
|
||||
});
|
||||
|
||||
it('should be draggable when enabled', () => {
|
||||
widget.setDraggingEnabled(true);
|
||||
widget.draw(svg.canvas, [node]);
|
||||
|
||||
tryToDrag();
|
||||
|
||||
expect(node.x).toEqual(250);
|
||||
expect(node.y).toEqual(250);
|
||||
});
|
||||
|
||||
it('should be not draggable when disabled', () => {
|
||||
widget.setDraggingEnabled(false);
|
||||
widget.draw(svg.canvas, [node]);
|
||||
|
||||
tryToDrag();
|
||||
|
||||
expect(node.x).toEqual(100);
|
||||
expect(node.y).toEqual(200);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
@ -11,6 +11,7 @@ import { CssFixer } from "../helpers/css-fixer";
|
||||
|
||||
export class NodesWidget implements Widget {
|
||||
private debug = false;
|
||||
private draggingEnabled = false;
|
||||
|
||||
private onContextMenuCallback: (event: any, node: Node) => void;
|
||||
private onNodeClickedCallback: (event: any, node: Node) => void;
|
||||
@ -45,6 +46,10 @@ export class NodesWidget implements Widget {
|
||||
this.symbols = symbols;
|
||||
}
|
||||
|
||||
public setDraggingEnabled(enabled: boolean) {
|
||||
this.draggingEnabled = enabled;
|
||||
}
|
||||
|
||||
private executeOnNodeDraggingCallback(callback_event: any, node: Node) {
|
||||
this.onNodeDraggingCallbacks.forEach((callback: (e: any, n: Node) => void) => {
|
||||
callback(callback_event, node);
|
||||
@ -190,7 +195,9 @@ export class NodesWidget implements Widget {
|
||||
});
|
||||
};
|
||||
|
||||
node_merge.call(dragging());
|
||||
if (this.draggingEnabled) {
|
||||
node_merge.call(dragging());
|
||||
}
|
||||
|
||||
nodes_selection
|
||||
.exit()
|
||||
|
@ -8,8 +8,17 @@ import { Link } from "../models/link";
|
||||
export class SerialLinkWidget implements Widget {
|
||||
|
||||
public draw(view: SVGSelection, link: Link) {
|
||||
const dx = link.target.x - link.source.x;
|
||||
const dy = link.target.y - link.source.y;
|
||||
const source = {
|
||||
'x': link.source.x + link.source.width / 2,
|
||||
'y': link.source.y + link.source.height / 2
|
||||
};
|
||||
const target = {
|
||||
'x': link.target.x + link.target.width / 2,
|
||||
'y': link.target.y + link.target.height / 2
|
||||
};
|
||||
|
||||
const dx = target.x - source.x;
|
||||
const dy = target.y - source.y;
|
||||
|
||||
const vector_angle = Math.atan2(dy, dx);
|
||||
const rot_angle = -Math.PI / 4.0;
|
||||
@ -19,20 +28,20 @@ export class SerialLinkWidget implements Widget {
|
||||
];
|
||||
|
||||
const angle_source = [
|
||||
link.source.x + dx / 2.0 + 15 * vect_rot[0],
|
||||
link.source.y + dy / 2.0 + 15 * vect_rot[1]
|
||||
source.x + dx / 2.0 + 15 * vect_rot[0],
|
||||
source.y + dy / 2.0 + 15 * vect_rot[1]
|
||||
];
|
||||
|
||||
const angle_target = [
|
||||
link.target.x - dx / 2.0 - 15 * vect_rot[0],
|
||||
link.target.y - dy / 2.0 - 15 * vect_rot[1]
|
||||
target.x - dx / 2.0 - 15 * vect_rot[0],
|
||||
target.y - dy / 2.0 - 15 * vect_rot[1]
|
||||
];
|
||||
|
||||
const line_data = [
|
||||
[link.source.x, link.source.y],
|
||||
[source.x, source.y],
|
||||
angle_source,
|
||||
angle_target,
|
||||
[link.target.x, link.target.y]
|
||||
[target.x, target.y]
|
||||
];
|
||||
|
||||
let link_path = view.select<SVGPathElement>('path');
|
||||
|
@ -31,9 +31,9 @@ g.node:hover {
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
g.node text {
|
||||
font-family: Roboto !important;
|
||||
}
|
||||
/*g.node text {*/
|
||||
/*font-family: Roboto !important;*/
|
||||
/*}*/
|
||||
|
||||
|
||||
svg.map image:hover, svg.map image.chosen, g.selected {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Component, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { HotkeysService } from 'angular2-hotkeys';
|
||||
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from "rxjs/Subject";
|
||||
@ -89,7 +88,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
protected nodesDataSource: NodesDataSource,
|
||||
protected linksDataSource: LinksDataSource,
|
||||
protected drawingsDataSource: DrawingsDataSource,
|
||||
protected hotkeysService: HotkeysService
|
||||
) {
|
||||
this.selectionManager = new SelectionManager(
|
||||
this.nodesDataSource, this.linksDataSource, this.drawingsDataSource, new InRectangleHelper());
|
||||
@ -105,7 +103,10 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
.fromPromise(this.serverService.get(server_id))
|
||||
.flatMap((server: Server) => {
|
||||
this.server = server;
|
||||
return this.projectService.get(server, paramMap.get('project_id'));
|
||||
return this.projectService.get(server, paramMap.get('project_id')).map((project) => {
|
||||
project.readonly = true;
|
||||
return project;
|
||||
});
|
||||
})
|
||||
.flatMap((project: Project) => {
|
||||
this.project = project;
|
||||
@ -194,6 +195,11 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
setUpMapCallbacks(project: Project) {
|
||||
if (this.project.readonly) {
|
||||
this.mapChild.graphLayout.getSelectionTool().deactivate();
|
||||
}
|
||||
this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.project.readonly);
|
||||
|
||||
this.mapChild.graphLayout.getNodesWidget().setOnContextMenuCallback((event: any, node: Node) => {
|
||||
this.nodeContextMenu.open(node, event.clientY, event.clientX);
|
||||
});
|
||||
@ -271,11 +277,15 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
public toggleMovingMode() {
|
||||
this.movingMode = !this.movingMode;
|
||||
if (this.movingMode) {
|
||||
this.mapChild.graphLayout.getSelectionTool().deactivate();
|
||||
if (!this.project.readonly) {
|
||||
this.mapChild.graphLayout.getSelectionTool().deactivate();
|
||||
}
|
||||
this.mapChild.graphLayout.getMovingTool().activate();
|
||||
} else {
|
||||
this.mapChild.graphLayout.getMovingTool().deactivate();
|
||||
this.mapChild.graphLayout.getSelectionTool().activate();
|
||||
if (!this.project.readonly) {
|
||||
this.mapChild.graphLayout.getSelectionTool().activate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user