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:
ziajka 2018-05-29 17:46:01 +02:00 committed by GitHub
commit a536452cd0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 347 additions and 37 deletions

View 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'
});
});
});

View 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;
}
}

View File

@ -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');
});
});

View 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;
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
});

View File

@ -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);

View File

@ -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");
});
});

View File

@ -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;
}
}

View File

@ -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]);

View File

@ -12,4 +12,5 @@ export class EllipseElement implements DrawingElement {
ry: number;
stroke: string;
stroke_width: number;
stroke_dasharray: string;
}

View File

@ -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;

View File

@ -8,4 +8,5 @@ export class RectElement implements DrawingElement {
fill_opacity: number;
stroke: string;
stroke_width: number;
stroke_dasharray: string;
}

View File

@ -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;
}

View File

@ -0,0 +1,5 @@
export interface Font {
font_family: string;
font_size: number;
font_weight: string;
}

View File

@ -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) => {

View File

@ -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');

View File

@ -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)

View File

@ -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');

View File

@ -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)

View File

@ -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');
});

View File

@ -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);

View File

@ -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');
});
});

View File

@ -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();

View File

@ -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();

View File

@ -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'));

View 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);
});
});
});

View File

@ -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()

View File

@ -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');

View File

@ -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 {

View File

@ -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();
}
}
}