Merge with different approach

This commit is contained in:
ziajka 2018-11-20 13:43:59 +01:00
parent 27946c6a8e
commit 311ef8c348
90 changed files with 2133 additions and 84 deletions

View File

@ -72,6 +72,7 @@ import { SnapshotMenuItemComponent } from './components/snapshots/snapshot-menu-
import { MATERIAL_IMPORTS } from './material.imports';
import { DrawingService } from './services/drawing.service';
import { ProjectNameValidator } from './components/projects/models/projectNameValidator';
import { NodeSelectInterfaceComponent } from './components/project-map/node-select-interface/node-select-interface.component';
if (environment.production) {
@ -113,6 +114,7 @@ if (environment.production) {
LocalServerComponent,
ProgressComponent,
ServerDiscoveryComponent,
NodeSelectInterfaceComponent
],
imports: [
NgbModule.forRoot(),

View File

@ -0,0 +1,28 @@
import { DraggableComponent } from './components/draggable/draggable.component';
import { SelectionComponent } from './components/selection/selection.component';
import { NodeComponent } from './components/experimental-map/node/node.component';
import { LinkComponent } from './components/experimental-map/link/link.component';
import { StatusComponent } from './components/experimental-map/status/status.component';
import { DrawingComponent } from './components/experimental-map/drawing/drawing.component';
import { EllipseComponent } from './components/experimental-map/drawing/drawings/ellipse/ellipse.component';
import { ImageComponent } from './components/experimental-map/drawing/drawings/image/image.component';
import { LineComponent } from './components/experimental-map/drawing/drawings/line/line.component';
import { RectComponent } from './components/experimental-map/drawing/drawings/rect/rect.component';
import { TextComponent } from './components/experimental-map/drawing/drawings/text/text.component';
import { InterfaceLabelComponent } from './components/experimental-map/interface-label/interface-label.component';
export const ANGULAR_MAP_DECLARATIONS = [
NodeComponent,
LinkComponent,
StatusComponent,
DrawingComponent,
EllipseComponent,
ImageComponent,
LineComponent,
RectComponent,
TextComponent,
DraggableComponent,
SelectionComponent,
InterfaceLabelComponent
];

View File

@ -2,9 +2,7 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { MatMenuModule, MatIconModule } from '@angular/material';
import { MapComponent } from './components/map/map.component';
import { DrawLinkToolComponent } from './components/draw-link-tool/draw-link-tool.component';
import { NodeSelectInterfaceComponent } from './components/node-select-interface/node-select-interface.component';
import { CssFixer } from './helpers/css-fixer';
import { FontFixer } from './helpers/font-fixer';
@ -14,10 +12,9 @@ import { QtDasharrayFixer } from './helpers/qt-dasharray-fixer';
import { LayersManager } from './managers/layers-manager';
import { MapChangeDetectorRef } from './services/map-change-detector-ref';
import { Context } from './models/context';
import { ANGULAR_MAP_DECLARATIONS } from './angular-map.imports';
import { D3_MAP_IMPORTS } from './d3-map.imports';
import { CanvasSizeDetector } from './helpers/canvas-size-detector';
import { MapListeners } from './listeners/map-listeners';
import { DraggableListener } from './listeners/draggable-listener';
import { DrawingsEventSource } from './events/drawings-event-source';
import { NodesEventSource } from './events/nodes-event-source';
import { DrawingToMapDrawingConverter } from './converters/map/drawing-to-map-drawing-converter';
@ -35,10 +32,14 @@ import { PortToMapPortConverter } from './converters/map/port-to-map-port-conver
import { SymbolToMapSymbolConverter } from './converters/map/symbol-to-map-symbol-converter';
import { LinkNodeToMapLinkNodeConverter } from './converters/map/link-node-to-map-link-node-converter';
import { GraphDataManager } from './managers/graph-data-manager';
import { SelectionUpdateListener } from './listeners/selection-update-listener';
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from './datasources/map-datasource';
import { SelectionListener } from './listeners/selection-listener';
import { LinksEventSource } from './events/links-event-source';
import { D3MapComponent } from './components/d3-map/d3-map.component';
import { ExperimentalMapComponent } from './components/experimental-map/experimental-map.component';
import { SelectionEventSource } from './events/selection-event-source';
import { SelectionControlComponent } from './components/selection-control/selection-control.component';
import { SelectionSelectComponent } from './components/selection-select/selection-select.component';
import { DraggableSelectionComponent } from './components/draggable-selection/draggable-selection.component';
@NgModule({
@ -48,9 +49,13 @@ import { LinksEventSource } from './events/links-event-source';
MatIconModule
],
declarations: [
MapComponent,
D3MapComponent,
ExperimentalMapComponent,
DrawLinkToolComponent,
NodeSelectInterfaceComponent
...ANGULAR_MAP_DECLARATIONS,
SelectionControlComponent,
SelectionSelectComponent,
DraggableSelectionComponent
],
providers: [
CssFixer,
@ -62,10 +67,6 @@ import { LinksEventSource } from './events/links-event-source';
MapChangeDetectorRef,
CanvasSizeDetector,
Context,
SelectionUpdateListener,
MapListeners,
DraggableListener,
SelectionListener,
DrawingsEventSource,
NodesEventSource,
LinksEventSource,
@ -88,8 +89,9 @@ import { LinksEventSource } from './events/links-event-source';
MapLinksDataSource,
MapDrawingsDataSource,
MapSymbolsDataSource,
SelectionEventSource,
...D3_MAP_IMPORTS
],
exports: [ MapComponent ]
exports: [ D3MapComponent, ExperimentalMapComponent ]
})
export class CartographyModule { }

View File

@ -0,0 +1,14 @@
<svg
#svg
class="map"
preserveAspectRatio="none"
>
<filter id="grayscale">
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
</filter>
</svg>
<app-draw-link-tool *ngIf="drawLinkTool"></app-draw-link-tool>
<app-selection-control></app-selection-control>
<app-selection-select></app-selection-select>
<app-draggable-selection [svg]="svg"></app-draggable-selection>

After

Width:  |  Height:  |  Size: 388 B

View File

@ -1,14 +1,14 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MapComponent } from './map.component';
import { D3MapComponent } from './d3-map.component';
describe('MapComponent', () => {
let component: MapComponent;
let fixture: ComponentFixture<MapComponent>;
describe('D3MapComponent', () => {
let component: D3MapComponent;
let fixture: ComponentFixture<D3MapComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MapComponent ]
declarations: [ D3MapComponent ]
})
.compileComponents();
}));

View File

@ -1,5 +1,5 @@
import {
Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChange, EventEmitter, Output
Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit, SimpleChange, EventEmitter, Output, ViewChild
} from '@angular/core';
import { Selection, select } from 'd3-selection';
@ -13,7 +13,6 @@ import { SelectionTool } from '../../tools/selection-tool';
import { MovingTool } from '../../tools/moving-tool';
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
import { MapListeners } from '../../listeners/map-listeners';
import { DrawingsWidget } from '../../widgets/drawings';
import { Node } from '../../models/node';
import { Link } from '../../../models/link';
@ -23,11 +22,11 @@ import { GraphDataManager } from '../../managers/graph-data-manager';
@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
selector: 'app-d3-map',
templateUrl: './d3-map.component.html',
styleUrls: ['./d3-map.component.scss']
})
export class MapComponent implements OnInit, OnChanges, OnDestroy {
export class D3MapComponent implements OnInit, OnChanges, OnDestroy {
@Input() nodes: Node[] = [];
@Input() links: Link[] = [];
@Input() drawings: Drawing[] = [];
@ -36,6 +35,8 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
@Input() width = 1500;
@Input() height = 600;
@ViewChild('svg') svgRef: ElementRef;
private parentNativeElement: any;
private svg: Selection<SVGSVGElement, any, null, undefined>;
@ -50,7 +51,6 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
private context: Context,
private mapChangeDetectorRef: MapChangeDetectorRef,
private canvasSizeDetector: CanvasSizeDetector,
private mapListeners: MapListeners,
protected element: ElementRef,
protected nodesWidget: NodesWidget,
protected drawingsWidget: DrawingsWidget,
@ -117,14 +117,11 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
this.redraw();
}
});
this.mapListeners.onInit(this.svg);
}
ngOnDestroy() {
this.graphLayout.disconnect(this.svg);
this.onChangesDetected.unsubscribe();
this.mapListeners.onDestroy();
}
public createGraph(domElement: HTMLElement) {

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DraggableSelectionComponent } from './draggable-selection.component';
describe('DraggableSelectionComponent', () => {
let component: DraggableSelectionComponent;
let fixture: ComponentFixture<DraggableSelectionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DraggableSelectionComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DraggableSelectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,111 @@
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
import { Subscription, merge } from 'rxjs';
import { NodesWidget } from '../../widgets/nodes';
import { DrawingsWidget } from '../../widgets/drawings';
import { LinksWidget } from '../../widgets/links';
import { SelectionManager } from '../../managers/selection-manager';
import { NodesEventSource } from '../../events/nodes-event-source';
import { DrawingsEventSource } from '../../events/drawings-event-source';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { DraggableStart, DraggableDrag, DraggableEnd } from '../../events/draggable';
import { MapNode } from '../../models/map/map-node';
import { MapDrawing } from '../../models/map/map-drawing';
import { DraggedDataEvent } from '../../events/event-source';
import { select } from 'd3-selection';
@Component({
selector: 'app-draggable-selection',
templateUrl: './draggable-selection.component.html',
styleUrls: ['./draggable-selection.component.scss']
})
export class DraggableSelectionComponent implements OnInit, OnDestroy {
private start: Subscription;
private drag: Subscription;
private end: Subscription;
@Input('svg') svg: SVGSVGElement;
constructor(
private nodesWidget: NodesWidget,
private drawingsWidget: DrawingsWidget,
private linksWidget: LinksWidget,
private selectionManager: SelectionManager,
private nodesEventSource: NodesEventSource,
private drawingsEventSource: DrawingsEventSource,
private graphDataManager: GraphDataManager
) { }
ngOnInit() {
const svg = select(this.svg);
this.start = merge(
this.nodesWidget.draggable.start,
this.drawingsWidget.draggable.start
).subscribe((evt: DraggableStart<any>) => {
const selected = this.selectionManager.getSelected();
if (evt.datum instanceof MapNode) {
if (selected.filter((item) => item instanceof MapNode && item.id === evt.datum.id).length === 0) {
this.selectionManager.setSelected([evt.datum]);
}
}
if (evt.datum instanceof MapDrawing) {
if (selected.filter((item) => item instanceof MapDrawing && item.id === evt.datum.id).length === 0) {
this.selectionManager.setSelected([evt.datum]);
}
}
});
this.drag = merge(
this.nodesWidget.draggable.drag,
this.drawingsWidget.draggable.drag
).subscribe((evt: DraggableDrag<any>) => {
const selected = this.selectionManager.getSelected();
// update nodes
selected.filter((item) => item instanceof MapNode).forEach((node: MapNode) => {
node.x += evt.dx;
node.y += evt.dy;
this.nodesWidget.redrawNode(svg, node);
const links = this.graphDataManager.getLinks().filter(
(link) => link.target.id === node.id || link.source.id === node.id);
links.forEach((link) => {
this.linksWidget.redrawLink(svg, link);
});
});
// update drawings
selected.filter((item) => item instanceof MapDrawing).forEach((drawing: MapDrawing) => {
drawing.x += evt.dx;
drawing.y += evt.dy;
this.drawingsWidget.redrawDrawing(svg, drawing);
});
});
this.end = merge(
this.nodesWidget.draggable.end,
this.drawingsWidget.draggable.end
).subscribe((evt: DraggableEnd<any>) => {
const selected = this.selectionManager.getSelected();
selected.filter((item) => item instanceof MapNode).forEach((item: MapNode) => {
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(item, evt.dx, evt.dy));
})
selected.filter((item) => item instanceof MapDrawing).forEach((item: MapDrawing) => {
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(item, evt.dx, evt.dy));
});
});
}
ngOnDestroy() {
this.start.unsubscribe();
this.drag.unsubscribe();
this.end.unsubscribe();
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DraggableComponent } from './draggable.component';
describe('DraggableComponent', () => {
let component: DraggableComponent;
let fixture: ComponentFixture<DraggableComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DraggableComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DraggableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,101 @@
import { Component, OnInit, ElementRef, AfterViewInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
import { Observable, Subscription } from 'rxjs';
import { Point } from '../../models/point';
export class DraggableDraggedEvent {
constructor(
public x: number,
public y: number,
public dx: number,
public dy: number
) {}
}
@Component({
selector: '[app-draggable]',
template:`<ng-content></ng-content>`,
styleUrls: ['./draggable.component.scss']
})
export class DraggableComponent implements OnInit, AfterViewInit, OnDestroy {
@Input('app-draggable') item: Point;
@Output() dragging = new EventEmitter<DraggableDraggedEvent>();
@Output() dragged = new EventEmitter<DraggableDraggedEvent>();
draggable: Subscription;
private startX: number;
private startY: number;
private posX: number;
private posY: number;
constructor(
private elementRef: ElementRef
) { }
ngOnInit() {
}
ngAfterViewInit() {
const down = Observable.fromEvent(this.elementRef.nativeElement, 'mousedown').do((e: MouseEvent) => e.preventDefault())
down.subscribe((e: MouseEvent) => {
this.posX = this.item.x;
this.posY = this.item.y;
this.startX = e.clientX;
this.startY = e.clientY;
});
const up = Observable
.fromEvent(document, 'mouseup')
.do((e: MouseEvent) => {
e.preventDefault();
});
const mouseMove = Observable
.fromEvent(document, 'mousemove')
.do((e: MouseEvent) => e.stopPropagation());
const scrollWindow = Observable
.fromEvent(document, 'scroll')
.startWith({});
const move = Observable.combineLatest(mouseMove, scrollWindow);
const drag = down.mergeMap((md: MouseEvent) => {
return move
.map(([mm, s]) => mm)
.do((mm: MouseEvent) => {
const x = this.startX - mm.clientX;
const y = this.startY - mm.clientY;
this.item.x = Math.round(this.posX - x);
this.item.y = Math.round(this.posY - y);
this.dragging.emit(new DraggableDraggedEvent(this.item.x, this.item.y, -x, -y));
})
.skipUntil(up
.take(1)
.do((e: MouseEvent) => {
const x = this.startX - e.clientX;
const y = this.startY - e.clientY;
this.item.x = Math.round(this.posX - x);
this.item.y = Math.round(this.posY - y);
this.dragged.emit(new DraggableDraggedEvent(this.item.x, this.item.y, -x, -y));
}))
.take(1);
});
this.draggable = drag.subscribe((e: MouseEvent) => {
// this.cd.detectChanges();
});
}
ngOnDestroy() {
this.draggable.unsubscribe();
}
}

View File

@ -1 +1 @@
<app-node-select-interface (onChooseInterface)="onChooseInterface($event)"></app-node-select-interface>
<!-- <app-node-select-interface (onChooseInterface)="onChooseInterface($event)"></app-node-select-interface> -->

View File

@ -1,7 +1,7 @@
import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild } from '@angular/core';
import { DrawingLineWidget } from '../../widgets/drawing-line';
import { Subscription } from 'rxjs';
import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component';
// import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component';
import { MapLinkCreated } from '../../events/links';
import { NodeClicked } from '../../events/nodes';
import { NodeWidget } from '../../widgets/node';
@ -16,7 +16,7 @@ import { LinksEventSource } from '../../events/links-event-source';
styleUrls: ['./draw-link-tool.component.scss']
})
export class DrawLinkToolComponent implements OnInit, OnDestroy {
@ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
// @ViewChild(NodeSelectInterfaceComponent) nodeSelectInterfaceMenu: NodeSelectInterfaceComponent;
// @Output('linkCreated') linkCreated = new EventEmitter<MapLinkCreated>();
@ -29,13 +29,13 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
) { }
ngOnInit() {
this.onNodeClicked = this.nodeWidget.onNodeClicked.subscribe((eventNode: NodeClicked) => {
this.nodeSelectInterfaceMenu.open(
eventNode.node,
eventNode.event.clientY,
eventNode.event.clientX
);
});
// this.onNodeClicked = this.nodeWidget.onNodeClicked.subscribe((eventNode: NodeClicked) => {
// this.nodeSelectInterfaceMenu.open(
// eventNode.node,
// eventNode.event.clientY,
// eventNode.event.clientX
// );
// });
}
ngOnDestroy() {

View File

@ -0,0 +1,32 @@
<svg:g
class="drawing"
[attr.transform]="transformation"
[app-draggable]="drawing"
(dragging)="OnDragging($event)"
(dragged)="OnDragged($event)"
>
<svg:g
*ngIf="is(drawing.element, 'ellipse')"
[app-ellipse]="drawing.element"
/>
<svg:g
*ngIf="is(drawing.element, 'image')"
[app-image]="drawing.element"
/>
<svg:g
*ngIf="is(drawing.element, 'line')"
[app-line]="drawing.element"
/>
<svg:g
*ngIf="is(drawing.element, 'rect')"
[app-rect]="drawing.element"
/>
<svg:g
*ngIf="is(drawing.element, 'text')"
[app-text]="drawing.element"
/>
</svg:g>

After

Width:  |  Height:  |  Size: 618 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DrawingComponent } from './drawing.component';
describe('DrawingComponent', () => {
let component: DrawingComponent;
let fixture: ComponentFixture<DrawingComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DrawingComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DrawingComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,71 @@
import { Component, OnInit, Input, ChangeDetectorRef } from '@angular/core';
import { EllipseElement } from '../../../models/drawings/ellipse-element';
import { ImageElement } from '../../../models/drawings/image-element';
import { LineElement } from '../../../models/drawings/line-element';
import { RectElement } from '../../../models/drawings/rect-element';
import { TextElement } from '../../../models/drawings/text-element';
import { SvgToDrawingConverter } from '../../../helpers/svg-to-drawing-converter';
import { DraggedDataEvent } from '../../../events/event-source';
import { MapDrawing } from '../../../models/map/map-drawing';
import { DrawingsEventSource } from '../../../events/drawings-event-source';
@Component({
selector: '[app-drawing]',
templateUrl: './drawing.component.html',
styleUrls: ['./drawing.component.scss']
})
export class DrawingComponent implements OnInit {
@Input('app-drawing') drawing: MapDrawing;
constructor(
private svgToDrawingConverter: SvgToDrawingConverter,
private drawingsEventSource: DrawingsEventSource,
private cd: ChangeDetectorRef,
) { }
ngOnInit() {
try {
this.drawing.element = this.svgToDrawingConverter.convert(this.drawing.svg);
} catch (error) {
console.log(`Cannot convert due to Error: '${error}'`);
}
}
OnDragging(evt) {
this.drawing.x = evt.x;
this.drawing.y = evt.y;
this.cd.detectChanges();
}
OnDragged(evt) {
this.cd.detectChanges();
this.drawingsEventSource.dragged.emit(new DraggedDataEvent<MapDrawing>(this.drawing, evt.dx, evt.dy))
}
is(element, type: string) {
if (!element) {
return false;
}
if (type === "ellipse") {
return element instanceof EllipseElement;
}
if (type === "image") {
return element instanceof ImageElement;
}
if (type === "line") {
return element instanceof LineElement;
}
if (type === "rect") {
return element instanceof RectElement;
}
if (type === "text") {
return element instanceof TextElement;
}
return false;
}
get transformation() {
return `translate(${this.drawing.x},${this.drawing.y}) rotate(${this.drawing.rotation})`;
}
}

View File

@ -0,0 +1,12 @@
<svg:ellipse
class="ellipse_element noselect"
[attr.fill]="ellipse.fill"
[attr.fill-opacity]="fill_opacity"
[attr.stroke]="ellipse.stroke"
[attr.stroke-width]="stroke_width"
[attr.stroke-dasharray]="stroke_dasharray"
[attr.cx]="ellipse.cx"
[attr.cy]="ellipse.cy"
[attr.rx]="ellipse.rx"
[attr.ry]="ellipse.ry"
/>

After

Width:  |  Height:  |  Size: 333 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EllipseComponent } from './ellipse.component';
describe('EllipseComponent', () => {
let component: EllipseComponent;
let fixture: ComponentFixture<EllipseComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ EllipseComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(EllipseComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,40 @@
import { Component, OnInit, Input } from '@angular/core';
import { EllipseElement } from '../../../../../models/drawings/ellipse-element';
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
@Component({
selector: '[app-ellipse]',
templateUrl: './ellipse.component.html',
styleUrls: ['./ellipse.component.scss']
})
export class EllipseComponent implements OnInit {
@Input('app-ellipse') ellipse: EllipseElement;
constructor(
private qtDasharrayFixer: QtDasharrayFixer
) { }
ngOnInit() {
}
get fill_opacity() {
if(isFinite(this.ellipse.fill_opacity)) {
return this.ellipse.fill_opacity;
}
return null;
}
get stroke_width() {
if(isFinite(this.ellipse.stroke_width)) {
return this.ellipse.stroke_width;
}
return null
}
get stroke_dasharray() {
if(this.ellipse.stroke_dasharray) {
return this.qtDasharrayFixer.fix(this.ellipse.stroke_dasharray);
}
return null;
}
}

View File

@ -0,0 +1,6 @@
<svg:image
class="image_element noselect"
[attr.xlink:href]="image.data"
[attr.width]="image.width"
[attr.height]="image.height"
/>

After

Width:  |  Height:  |  Size: 140 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ImageComponent } from './image.component';
describe('ImageComponent', () => {
let component: ImageComponent;
let fixture: ComponentFixture<ImageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ImageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ImageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,17 @@
import { Component, OnInit, Input } from '@angular/core';
import { ImageElement } from '../../../../../models/drawings/image-element';
@Component({
selector: '[app-image]',
templateUrl: './image.component.html',
styleUrls: ['./image.component.scss']
})
export class ImageComponent implements OnInit {
@Input('app-image') image: ImageElement;
constructor() { }
ngOnInit() {
}
}

View File

@ -0,0 +1,10 @@
<svg:line
class="line_element noselect"
[attr.stroke]="line.stroke"
[attr.stroke-width]="stroke_width"
[attr.stroke-dasharray]="stroke_dasharray"
[attr.x1]="line.x1"
[attr.x2]="line.x2"
[attr.y1]="line.y1"
[attr.y2]="line.y2"
/>

After

Width:  |  Height:  |  Size: 245 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LineComponent } from './line.component';
describe('LineComponent', () => {
let component: LineComponent;
let fixture: ComponentFixture<LineComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LineComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LineComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,34 @@
import { Component, OnInit, Input } from '@angular/core';
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
import { LineElement } from '../../../../../models/drawings/line-element';
@Component({
selector: '[app-line]',
templateUrl: './line.component.html',
styleUrls: ['./line.component.scss']
})
export class LineComponent implements OnInit {
@Input('app-line') line: LineElement;
constructor(
private qtDasharrayFixer: QtDasharrayFixer
) { }
ngOnInit() {
}
get stroke_width() {
if(isFinite(this.line.stroke_width)) {
return this.line.stroke_width;
}
return null
}
get stroke_dasharray() {
if(this.line.stroke_dasharray) {
return this.qtDasharrayFixer.fix(this.line.stroke_dasharray);
}
return null;
}
}

View File

@ -0,0 +1,10 @@
<svg:rect
class="rect_element noselect"
[attr.fill]="rect.fill"
[attr.fill-opacity]="fill_opacity"
[attr.stroke]="rect.stroke"
[attr.stroke-width]="stroke_width"
[attr.stroke-dasharray]="stroke_dasharray"
[attr.width]="rect.width"
[attr.height]="rect.height"
/>

After

Width:  |  Height:  |  Size: 278 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { RectComponent } from './rect.component';
describe('RectComponent', () => {
let component: RectComponent;
let fixture: ComponentFixture<RectComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ RectComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(RectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,41 @@
import { Component, OnInit, Input } from '@angular/core';
import { RectElement } from '../../../../../models/drawings/rect-element';
import { QtDasharrayFixer } from '../../../../../helpers/qt-dasharray-fixer';
@Component({
selector: '[app-rect]',
templateUrl: './rect.component.html',
styleUrls: ['./rect.component.scss']
})
export class RectComponent implements OnInit {
@Input('app-rect') rect: RectElement;
constructor(
private qtDasharrayFixer: QtDasharrayFixer
) { }
ngOnInit() {
}
get fill_opacity() {
if(isFinite(this.rect.fill_opacity)) {
return this.rect.fill_opacity;
}
return null;
}
get stroke_width() {
if(isFinite(this.rect.stroke_width)) {
return this.rect.stroke_width;
}
return null
}
get stroke_dasharray() {
if(this.rect.stroke_dasharray) {
return this.qtDasharrayFixer.fix(this.rect.stroke_dasharray);
}
return null;
}
}

View File

@ -0,0 +1,14 @@
<svg:text #text
class="text_element noselect"
[attr.style]="style"
[attr.text-decoration]="textDecoration"
[attr.fill]="text.fill"
[attr.transform]="transformation"
>
<svg:tspan
*ngFor="let line of lines; index as i"
xml:space="preserve"
x="0"
[attr.dy]="i == 0 ? '0em' : '1.2em'"
>{{line}}</svg:tspan>
</svg:text>

After

Width:  |  Height:  |  Size: 344 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TextComponent } from './text.component';
describe('TextComponent', () => {
let component: TextComponent;
let fixture: ComponentFixture<TextComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TextComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TextComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,68 @@
import { Component, OnInit, Input, ViewChild, ElementRef, DoCheck } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { TextElement } from '../../../../../models/drawings/text-element';
import { FontFixer } from '../../../../../helpers/font-fixer';
@Component({
selector: '[app-text]',
templateUrl: './text.component.html',
styleUrls: ['./text.component.scss']
})
export class TextComponent implements OnInit, DoCheck {
static MARGIN = 4;
@Input('app-text') text: TextElement;
@ViewChild('text') textRef: ElementRef;
lines: string[] = [];
transformation = "";
constructor(
private fontFixer: FontFixer,
private sanitizer: DomSanitizer
) { }
ngOnInit() {
this.lines = this.getLines(this.text.text);
}
ngDoCheck() {
this.transformation = this.calculateTransformation();
}
get style() {
const font = this.fontFixer.fix(this.text);
const styles: string[] = [];
if (font.font_family) {
styles.push(`font-family: "${this.text.font_family}"`);
}
if (font.font_size) {
styles.push(`font-size: ${this.text.font_size}pt`);
}
if (font.font_weight) {
styles.push(`font-weight: ${this.text.font_weight}`);
}
return this.sanitizer.bypassSecurityTrustStyle(styles.join("; "));
}
get textDecoration() {
return this.text.text_decoration;
}
calculateTransformation() {
const tspans = this.textRef.nativeElement.getElementsByTagName('tspan');
if(tspans.length > 0) {
const height = this.textRef.nativeElement.getBBox().height / tspans.length;
return `translate(${TextComponent.MARGIN}, ${height - TextComponent.MARGIN})`;
}
return '';
}
getLines(text: string) {
return text.split(/\r?\n/)
}
}

View File

@ -0,0 +1,41 @@
<svg #svg
class="map"
preserveAspectRatio="none"
[attr.width]="width"
[attr.height]="height"
>
<g [attr.transform]="transform">
<g *ngFor="let layer of layers">
<g class="links">
<g
*ngFor="let link of layer.links"
[app-link]="link"
[show-interface-labels]="settings.show_interface_labels"
></g>
<!-- [node-changed]="nodeChanged" -->
</g>
<g class="nodes">
<g
*ngFor="let node of layer.nodes"
[app-node]="node"
[symbols]="symbols"
></g>
<!-- [node-changed]="nodeChanged"
(valueChange)="onNodeChanged($event)" -->
</g>
<g class="drawings">
<g
*ngFor="let drawing of layer.drawings"
[app-drawing]="drawing" >
</g>
</g>
</g>
</g>
<g [app-selection]="svg"></g>
<!-- (selected)="onSelection($event)" -->
<filter id="grayscale">
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
</filter>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,5 @@
svg {
display: block;
}

View File

@ -0,0 +1,26 @@
// import { async, ComponentFixture, TestBed } from '@angular/core/testing';
// import { MapComponent } from './map.component';
// describe('MapComponent', () => {
// let component: MapComponent;
// let fixture: ComponentFixture<MapComponent>;
// beforeEach(async(() => {
// TestBed.configureTestingModule({
// declarations: [ MapComponent ]
// })
// .compileComponents();
// }));
// // beforeEach(() => {
// // fixture = TestBed.createComponent(MapComponent);
// // component = fixture.componentInstance;
// // fixture.detectChanges();
// // });
// //
// // it('should create', () => {
// // expect(component).toBeTruthy();
// // });
// });
// //

View File

@ -0,0 +1,131 @@
import {
Component, ElementRef, HostListener, Input, OnChanges, OnDestroy, OnInit,
SimpleChange, ChangeDetectionStrategy, ChangeDetectorRef, ViewChild
} from '@angular/core';
import { GraphLayout } from "../../widgets/graph-layout";
import { Context } from "../../models/context";
import { Size } from "../../models/size";
import { Subscription } from 'rxjs';
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
import { Node } from '../../models/node';
import { Link } from '../../../models/link';
import { Drawing } from '../../models/drawing';
import { Symbol } from '../../../models/symbol';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { LayersManager } from '../../managers/layers-manager';
@Component({
selector: 'app-experimental-map',
templateUrl: './experimental-map.component.html',
styleUrls: ['./experimental-map.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ExperimentalMapComponent implements OnInit, OnChanges, OnDestroy {
@Input() nodes: Node[] = [];
@Input() links: Link[] = [];
@Input() drawings: Drawing[] = [];
@Input() symbols: Symbol[] = [];
// @Input() changed: EventEmitter<any>;
// @Input('node-updated') nodeUpdated: EventEmitter<any>;
@Input() width = 1500;
@Input() height = 600;
@ViewChild('svg') svg: ElementRef;
private changesDetected: Subscription;
protected settings = {
'show_interface_labels': true
};
constructor(
private graphDataManager: GraphDataManager,
private context: Context,
private mapChangeDetectorRef: MapChangeDetectorRef,
private canvasSizeDetector: CanvasSizeDetector,
private changeDetectorRef: ChangeDetectorRef,
private layersManger: LayersManager,
public graphLayout: GraphLayout,
) {
}
@Input('show-interface-labels')
set showInterfaceLabels(value) {
this.settings.show_interface_labels = value;
this.mapChangeDetectorRef.detectChanges();
}
@Input('moving-tool')
set movingTool(value) {
this.mapChangeDetectorRef.detectChanges();
}
@Input('selection-tool')
set selectionTool(value) {
this.mapChangeDetectorRef.detectChanges();
}
@Input('draw-link-tool') drawLinkTool: boolean;
@Input('readonly') set readonly(value) {
}
ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
}
ngOnInit() {
// this.changeDetectorRef.detach();
this.changesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => {
this.graphDataManager.setNodes(this.nodes);
this.graphDataManager.setLinks(this.links);
this.graphDataManager.setDrawings(this.drawings);
this.graphDataManager.setSymbols(this.symbols);
this.changeDetectorRef.detectChanges();
});
// this.changedSubscription = this.changed.subscribe(() => {
// this.changeDetectorRef.detectChanges();
// });
// this.nodeUpdated.subscribe((node: Node) => {
// this.nodeChanged.emit(node);
// });
}
ngOnDestroy() {
this.changesDetected.unsubscribe();
// this.changedSubscription.unsubscribe();
}
public getSize(): Size {
return this.canvasSizeDetector.getOptimalSize(this.width, this.height);
}
public get layers() {
return this.layersManger.getLayersList();
}
public get transform() {
const ctx = new Context();
ctx.size = this.getSize();
const xTrans = ctx.getZeroZeroTransformationPoint().x + ctx.transformation.x;
const yTrans = ctx.getZeroZeroTransformationPoint().y + ctx.transformation.y;
const kTrans = ctx.transformation.k;
return `translate(${xTrans}, ${yTrans}) scale(${kTrans})`;
}
@HostListener('window:resize', ['$event'])
onResize(event) {
}
}

View File

@ -0,0 +1,28 @@
<svg:g
class="text_container"
[attr.transform]="transform"
width="100"
height="100"
>
<svg:rect
stroke-dasharray="3,3"
stroke-width="0.5"
fill="none"
stroke="black"
[attr.x]="rectX"
[attr.y]="rectY"
[attr.width]="rectWidth"
[attr.height]="rectHeight"
/>
<svg:text
#textSvg
class="interface_label"
[attr.style]="sanitizedStyle"
[attr.x]="borderSize"
[attr.y]="-borderSize"
>
{{ text }}
</svg:text>
</svg:g>

After

Width:  |  Height:  |  Size: 484 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { InterfaceLabelComponent } from './interface-label.component';
describe('InterfaceLabelComponent', () => {
let component: InterfaceLabelComponent;
let fixture: ComponentFixture<InterfaceLabelComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ InterfaceLabelComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(InterfaceLabelComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,98 @@
import { Component, OnInit, Input, ChangeDetectorRef, ElementRef, ViewChild } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { CssFixer } from '../../../helpers/css-fixer';
@Component({
selector: '[app-interface-label]',
templateUrl: './interface-label.component.html',
styleUrls: ['./interface-label.component.scss']
})
export class InterfaceLabelComponent implements OnInit {
@Input('app-interface-label') ignore: any;
@ViewChild('textSvg') textRef: ElementRef;
private label = {
'x': 0,
'y': 0,
'text': '',
'style': '',
'rotation': 0
};
borderSize = 5;
textWidth = 0;
textHeight = 0;
constructor(
private elementRef: ElementRef,
private ref: ChangeDetectorRef,
private sanitizer: DomSanitizer,
private cssFixer: CssFixer
) { }
ngOnInit() {
}
@Input('x')
set x(value) {
this.label['x'] = value;
this.ref.detectChanges();
}
@Input('y')
set y(value) {
this.label['y'] = value;
this.ref.detectChanges();
}
@Input('text')
set text(value) {
this.label['text'] = value;
this.ref.detectChanges();
}
@Input('style')
set style(value) {
this.label['style'] = this.cssFixer.fix(value);
this.ref.detectChanges();
}
@Input('rotation')
set rotation(value) {
this.label['rotation'] = value;
this.ref.detectChanges();
}
get text() {
return this.label.text;
}
get sanitizedStyle() {
return this.sanitizer.bypassSecurityTrustStyle(this.label.style);
}
get rectX() {
return 0;
}
get rectY() {
return -this.textRef.nativeElement.getBBox().height - this.borderSize;
}
get rectWidth() {
return this.textRef.nativeElement.getBBox().width + this.borderSize*2;
}
get rectHeight() {
return this.textRef.nativeElement.getBBox().height + this.borderSize;
}
get transform() {
const bbox = this.elementRef.nativeElement.getBBox();
const x = this.label.x;
const y = this.label.y + bbox.height;
return `translate(${x}, ${y}) rotate(${this.label.rotation}, ${x}, ${y})`;
}
}

View File

@ -0,0 +1,59 @@
<svg:g
class="link"
[attr.link_id]="link.id"
[attr.map-source]="link.source.id"
[attr.map-target]="link.target.id"
[attr.transform]="transform"
>
<svg:path #path
*ngIf="link.linkType == 'ethernet'"
class="ethernet_link"
stroke="#000"
stroke-width="2"
[attr.d]="d"
/>
<svg:path #path
*ngIf="link.linkType == 'serial'"
class="serial_link"
stroke="#B22222"
fill="none"
stroke-width="2"
[attr.d]="d"
/>
<svg:g
[app-status]="link.source.status"
[direction]="'source'"
[path]="path"
[d]="d"
/>
<svg:g
[app-status]="link.target.status"
[direction]="'target'"
[path]="path"
[d]="d"
/>
<svg:g
*ngIf="showInterfaceLabels"
[app-interface-label]
[x]="link.source.x+link.nodes[0].label.x"
[y]="link.source.y+link.nodes[0].label.y"
[text]="link.nodes[0].label.text"
[style]="link.nodes[0].label.style"
[rotation]="link.nodes[0].label.rotation"
/>
<svg:g
*ngIf="showInterfaceLabels"
[app-interface-label]
[x]="link.target.x+link.nodes[1].label.x"
[y]="link.target.y+link.nodes[1].label.y"
[text]="link.nodes[1].label.text"
[style]="link.nodes[1].label.style"
[rotation]="link.nodes[1].label.rotation"
/>
</svg:g>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LinkComponent } from './link.component';
describe('LinkComponent', () => {
let component: LinkComponent;
let fixture: ComponentFixture<LinkComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ LinkComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(LinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,65 @@
import {
Component, OnInit, Input, ViewChild,
ElementRef, EventEmitter, ChangeDetectorRef,
OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { LinkStrategy } from './strategies/link-strategy';
import { EthernetLinkStrategy } from './strategies/ethernet-link-strategy';
import { SerialLinkStrategy } from './strategies/serial-link-strategy';
import { MultiLinkCalculatorHelper } from '../../../helpers/multi-link-calculator-helper';
import { Node } from '../../../models/node';
import { MapLink } from '../../../models/map/map-link';
@Component({
selector: '[app-link]',
templateUrl: './link.component.html',
styleUrls: ['./link.component.scss'],
})
export class LinkComponent implements OnInit, OnDestroy {
@Input('app-link') link: MapLink;
@Input('node-changed') nodeChanged: EventEmitter<Node>;
@Input('show-interface-labels') showInterfaceLabels: boolean;
@ViewChild('path') path: ElementRef;
private ethernetLinkStrategy = new EthernetLinkStrategy();
private serialLinkStrategy = new SerialLinkStrategy();
private nodeChangedSubscription: Subscription;
constructor(
private multiLinkCalculatorHelper: MultiLinkCalculatorHelper,
private ref: ChangeDetectorRef
) {}
ngOnInit() {
this.ref.detectChanges();
// this.nodeChangedSubscription = this.nodeChanged.subscribe((node: Node) => {
// if (this.link.source.node_id === node.node_id || this.link.target.node_id === node.node_id) {
// this.ref.detectChanges();
// }
// });
}
ngOnDestroy() {
// this.nodeChangedSubscription.unsubscribe();
}
get strategy(): LinkStrategy {
if (this.link.linkType === 'serial') {
return this.serialLinkStrategy;
}
return this.ethernetLinkStrategy;
}
get transform() {
const translation = this.multiLinkCalculatorHelper.linkTranslation(this.link.distance, this.link.source, this.link.target);
return `translate (${translation.dx}, ${translation.dy})`;
}
get d() {
return this.strategy.d(this.link);
}
}

View File

@ -0,0 +1,18 @@
import { LinkStrategy } from "./link-strategy";
import { path } from "d3-path";
import { MapLink } from "../../../../models/map/map-link";
export class EthernetLinkStrategy implements LinkStrategy {
public d(link: MapLink): string {
const points = [
[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.target.height / 2.]
];
const line_generator = path();
line_generator.moveTo(points[0][0], points[0][1]);
line_generator.lineTo(points[1][0], points[1][1]);
return line_generator.toString();
}
}

View File

@ -0,0 +1,5 @@
import { MapLink } from "../../../../models/map/map-link";
export interface LinkStrategy {
d(link: MapLink): string;
}

View File

@ -0,0 +1,55 @@
import { path } from "d3-path";
import { LinkStrategy } from "./link-strategy";
import { MapLink } from "../../../../models/map/map-link";
export class SerialLinkStrategy implements LinkStrategy {
private linkToPoints(link: MapLink) {
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;
const vect_rot = [
Math.cos(vector_angle + rot_angle),
Math.sin(vector_angle + rot_angle)
];
const angle_source: [number, number] = [
source.x + dx / 2.0 + 15 * vect_rot[0],
source.y + dy / 2.0 + 15 * vect_rot[1]
];
const angle_target: [number, number] = [
target.x - dx / 2.0 - 15 * vect_rot[0],
target.y - dy / 2.0 - 15 * vect_rot[1]
];
return [
[source.x, source.y],
angle_source,
angle_target,
[target.x, target.y]
];
}
d(link: MapLink): string {
const points = this.linkToPoints(link);
const line_generator = path();
line_generator.moveTo(points[0][0], points[0][1]);
line_generator.lineTo(points[1][0], points[1][1]);
line_generator.lineTo(points[2][0], points[2][1]);
line_generator.lineTo(points[3][0], points[3][1]);
return line_generator.toString();
}
}

View File

@ -0,0 +1,25 @@
<svg:g
class="node"
[attr.transform]="'translate(' + node.x + ',' + node.y + ')'"
>
<svg:image
#image
[attr.width]="node.width"
[attr.height]="node.height"
[attr.x]="0"
[attr.y]="0"
[attr.xlink:href]="symbol"
[app-draggable]="node"
(dragging)="OnDragging($event)"
(dragged)="OnDragged($event)"
/>
<svg:text
#label
class="label"
[attr.style]="label_style"
[attr.x]="label_x"
[attr.y]="label_y"
>
{{ node.label.text }}
</svg:text>
</svg:g>

After

Width:  |  Height:  |  Size: 513 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NodeComponent } from './node.component';
describe('NodeComponent', () => {
let component: NodeComponent;
let fixture: ComponentFixture<NodeComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ NodeComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NodeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,119 @@
import {
Component, OnInit, Input, ElementRef,
ViewChild, ChangeDetectorRef, ChangeDetectionStrategy, Output,
EventEmitter, OnDestroy, OnChanges, AfterViewInit } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import { CssFixer } from '../../../helpers/css-fixer';
import { FontFixer } from '../../../helpers/font-fixer';
import { Symbol } from '../../../../models/symbol';
import { MapNode } from '../../../models/map/map-node';
import { NodesEventSource } from '../../../events/nodes-event-source';
import { DraggedDataEvent } from '../../../events/event-source';
@Component({
selector: '[app-node]',
templateUrl: './node.component.html',
styleUrls: ['./node.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class NodeComponent implements OnInit, OnDestroy, OnChanges, AfterViewInit {
static NODE_LABEL_MARGIN = 3;
@ViewChild('label') label: ElementRef;
@ViewChild('image') imageRef: ElementRef;
@Input('app-node') node: MapNode;
@Input('symbols') symbols: Symbol[];
@Input('node-changed') nodeChanged: EventEmitter<Node>;
// @Output() valueChange = new EventEmitter<Node>();
nodeChangedSubscription: Subscription;
private labelHeight = 0;
constructor(
private cssFixer: CssFixer,
private fontFixer: FontFixer,
private sanitizer: DomSanitizer,
protected element: ElementRef,
private cd: ChangeDetectorRef,
private nodesEventSource: NodesEventSource
) { }
ngOnInit() {
// this.nodeChangedSubscription = this.nodeChanged.subscribe((node: Node) => {
// if (node.node_id === this.node.node_id) {
// this.cd.detectChanges();
// }
// });
}
ngOnDestroy() {
// this.nodeChangedSubscription.unsubscribe();
}
ngOnChanges(changes) {
this.cd.detectChanges();
}
ngAfterViewInit() {
this.labelHeight = this.getLabelHeight();
// reload BBox
this.cd.detectChanges();
}
OnDragging(evt) {
this.node.x = evt.x;
this.node.y = evt.y;
this.cd.detectChanges();
}
OnDragged(evt) {
this.cd.detectChanges();
this.nodesEventSource.dragged.emit(new DraggedDataEvent<MapNode>(this.node, evt.dx, evt.dy))
}
get symbol(): string {
const symbol = this.symbols.find((s: Symbol) => s.symbol_id === this.node.symbol);
if (symbol) {
return 'data:image/svg+xml;base64,' + btoa(symbol.raw);
}
// @todo; we need to have default image
return 'data:image/svg+xml;base64,none';
}
get label_style() {
let styles = this.cssFixer.fix(this.node.label.style);
styles = this.fontFixer.fixStyles(styles);
return this.sanitizer.bypassSecurityTrustStyle(styles);
}
get label_x(): number {
if (this.node.label.x === null) {
// center
const bbox = this.label.nativeElement.getBBox();
return -bbox.width / 2.;
}
return this.node.label.x + NodeComponent.NODE_LABEL_MARGIN;
}
get label_y(): number {
this.labelHeight = this.getLabelHeight();
if (this.node.label.x === null) {
// center
return - this.node.height / 2. - this.labelHeight ;
}
return this.node.label.y + this.labelHeight - NodeComponent.NODE_LABEL_MARGIN;
}
private getLabelHeight() {
const bbox = this.label.nativeElement.getBBox();
return bbox.height;
}
}

View File

@ -0,0 +1,21 @@
<svg:g *ngIf="status && point && direction">
<svg:circle
*ngIf="status == 'started'"
class="status_started"
[attr.cx]="point.x"
[attr.cy]="point.y"
r="6"
fill="#2ecc71"
/>
<svg:rect
*ngIf="status == 'stopped'"
class="status_stopped"
[attr.x]="point.x - 10/2"
[attr.y]="point.y - 10/2"
width="10"
height="10"
r="6"
fill="red"
/>
</svg:g>

After

Width:  |  Height:  |  Size: 401 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { StatusComponent } from './status.component';
describe('StatusComponent', () => {
let component: StatusComponent;
let fixture: ComponentFixture<StatusComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ StatusComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(StatusComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,83 @@
import { Component, ElementRef, Input, ChangeDetectorRef } from '@angular/core';
@Component({
selector: '[app-status]',
templateUrl: './status.component.html',
styleUrls: ['./status.component.scss'],
})
export class StatusComponent {
static STOPPED_STATUS_RECT_WIDTH = 10;
data = {
'status': '',
'path': null,
'direction': null,
'd': null
};
constructor(
protected element: ElementRef,
private ref: ChangeDetectorRef
) {}
@Input('app-status')
set status(value) {
this.data.status = value;
this.ref.markForCheck();
}
@Input('path')
set path(value) {
this.data.path = value;
this.ref.markForCheck();
}
@Input('direction')
set direction(value) {
this.data.direction = value;
this.ref.markForCheck();
}
@Input('d')
set d(value) {
if (this.data.d !== value) {
this.data.d = value;
this.ref.markForCheck();
}
}
get status() {
return this.data.status;
}
get direction() {
return this.data.direction;
}
get path() {
return this.data.path;
}
get sourceStatusPoint() {
if (!this.path) {
return null;
}
return this.path.nativeElement.getPointAtLength(45);
}
get targetStatusPoint() {
if (!this.path) {
return null;
}
return this.path.nativeElement.getPointAtLength(this.path.nativeElement.getTotalLength() - 45);
}
get point() {
if (this.direction === 'source') {
return this.sourceStatusPoint;
}
return this.targetStatusPoint;
}
}

View File

@ -1,10 +0,0 @@
<svg
class="map"
preserveAspectRatio="none"
>
<filter id="grayscale">
<feColorMatrix id="feGrayscale" type="saturate" values="0"/>
</filter>
</svg>
<app-draw-link-tool *ngIf="drawLinkTool"></app-draw-link-tool>

Before

Width:  |  Height:  |  Size: 235 B

View File

@ -0,0 +1,72 @@
import { fakeAsync, tick } from '@angular/core/testing';
import { SelectionControlComponent } from './selection-control.component';
import { SelectionManager } from '../../managers/selection-manager';
import { SelectionEventSource } from '../../events/selection-event-source';
import { mock, when, instance } from 'ts-mockito';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { MapNode } from '../../models/map/map-node';
import { MapLink } from '../../models/map/map-link';
import { InRectangleHelper } from '../../helpers/in-rectangle-helper';
import { Rectangle } from '../../models/rectangle';
describe('SelectionControlComponent', () => {
let component: SelectionControlComponent;
let manager: SelectionManager;
let selectionEventSource: SelectionEventSource;
beforeEach(() => {
const mockedGraphData = mock(GraphDataManager);
const node_1 = new MapNode();
node_1.id = "test1";
node_1.name = "Node 1";
node_1.x = 150;
node_1.y = 150;
const node_2 = new MapNode();
node_2.id = "test2";
node_2.name = "Node 2";
node_2.x = 300;
node_2.y = 300;
const link_1 = new MapLink();
link_1.id = "test1";
when(mockedGraphData.getNodes()).thenReturn([node_1, node_2]);
when(mockedGraphData.getLinks()).thenReturn([link_1]);
when(mockedGraphData.getDrawings()).thenReturn([]);
const graphData = instance(mockedGraphData);
const inRectangleHelper = new InRectangleHelper();
selectionEventSource = new SelectionEventSource();
manager = new SelectionManager();
component = new SelectionControlComponent(selectionEventSource, graphData, inRectangleHelper, manager);
component.ngOnInit();
});
afterEach(() => {
component.ngOnDestroy();
})
it('should create', () => {
expect(component).toBeTruthy();
});
it('node should be selected', fakeAsync(() => {
selectionEventSource.selected.next(new Rectangle(100, 100, 100, 100));
tick();
expect(manager.getSelected().length).toEqual(1);
}));
it('node should be selected and deselected', fakeAsync(() => {
selectionEventSource.selected.next(new Rectangle(100, 100, 100, 100));
tick();
selectionEventSource.selected.next(new Rectangle(350, 350, 100, 100));
tick();
expect(manager.getSelected().length).toEqual(0);
}));
});

View File

@ -0,0 +1,48 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { SelectionEventSource } from '../../events/selection-event-source';
import { GraphDataManager } from '../../managers/graph-data-manager';
import { InRectangleHelper } from '../../helpers/in-rectangle-helper';
import { SelectionManager } from '../../managers/selection-manager';
import { Rectangle } from '../../models/rectangle';
@Component({
selector: 'app-selection-control',
templateUrl: './selection-control.component.html',
styleUrls: ['./selection-control.component.scss']
})
export class SelectionControlComponent implements OnInit, OnDestroy {
private onSelection: Subscription;
constructor(
private selectionEventSource: SelectionEventSource,
private graphDataManager: GraphDataManager,
private inRectangleHelper: InRectangleHelper,
private selectionManager: SelectionManager
) { }
ngOnInit() {
this.onSelection = this.selectionEventSource.selected.subscribe((rectangle: Rectangle) => {
const selectedNodes = this.graphDataManager.getNodes().filter((node) => {
return this.inRectangleHelper.inRectangle(rectangle, node.x, node.y)
});
const selectedLinks = this.graphDataManager.getLinks().filter((link) => {
return this.inRectangleHelper.inRectangle(rectangle, link.x, link.y)
});
const selectedDrawings = this.graphDataManager.getDrawings().filter((drawing) => {
return this.inRectangleHelper.inRectangle(rectangle, drawing.x, drawing.y)
});
const selected = [...selectedNodes, ...selectedLinks, ...selectedDrawings];
this.selectionManager.setSelected(selected);
});
}
ngOnDestroy() {
this.onSelection.unsubscribe();
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectionSelectComponent } from './selection-select.component';
describe('SelectionSelectComponent', () => {
let component: SelectionSelectComponent;
let fixture: ComponentFixture<SelectionSelectComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SelectionSelectComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SelectionSelectComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,34 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { SelectionManager } from '../../managers/selection-manager';
import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
@Component({
selector: 'app-selection-select',
templateUrl: './selection-select.component.html',
styleUrls: ['./selection-select.component.scss']
})
export class SelectionSelectComponent implements OnInit, OnDestroy {
private onSelected: Subscription;
private onUnselected: Subscription;
constructor(
private selectionManager: SelectionManager,
private mapChangeDetectorRef: MapChangeDetectorRef
) { }
ngOnInit() {
this.onSelected = this.selectionManager.selected.subscribe(() => {
this.mapChangeDetectorRef.detectChanges();
});
this.onUnselected = this.selectionManager.unselected.subscribe(() => {
this.mapChangeDetectorRef.detectChanges();
});
}
ngOnDestroy() {
this.onSelected.unsubscribe();
this.onUnselected.unsubscribe();
}
}

View File

@ -0,0 +1,10 @@
<svg:g
class="selection-line-tool"
>
<svg:path
class="selection"
*ngIf="visible"
[attr.d]="d"
>
</svg:path>
</svg:g>

After

Width:  |  Height:  |  Size: 137 B

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { SelectionComponent } from './selection.component';
describe('SelectionComponent', () => {
let component: SelectionComponent;
let fixture: ComponentFixture<SelectionComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SelectionComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(SelectionComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
// it('should create', () => {
// expect(component).toBeTruthy();
// });
});

View File

@ -0,0 +1,122 @@
import { Component, OnInit, Input, AfterViewInit, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
import { Observable, Subscription, Subject } from 'rxjs';
import { Rectangle } from '../../models/rectangle';
@Component({
selector: '[app-selection]',
templateUrl: './selection.component.html',
styleUrls: ['./selection.component.scss']
})
export class SelectionComponent implements OnInit, AfterViewInit {
@Input('app-selection') svg: SVGSVGElement;
private startX: number;
private startY: number;
private width: number;
private height: number;
started = false;
visible = false;
draggable: Subscription;
@Output('selected') rectangleSelected = new EventEmitter<Rectangle>();
constructor(
private ref: ChangeDetectorRef
) { }
ngOnInit() {
}
ngAfterViewInit() {
const down = Observable.fromEvent(this.svg, 'mousedown').do((e: MouseEvent) => e.preventDefault());
down.subscribe((e: MouseEvent) => {
if(e.target !== this.svg) {
return;
}
this.started = true;
this.startX = e.clientX + window.scrollX;
this.startY = e.clientY + window.scrollY;
this.width = 0;
this.height = 0;
this.visible = true;
this.ref.detectChanges();
});
const up = Observable.fromEvent(document, 'mouseup')
.do((e: MouseEvent) => {
e.preventDefault();
});
const mouseMove = Observable.fromEvent(document, 'mousemove')
.do((e: MouseEvent) => e.stopPropagation());
const scrollWindow = Observable.fromEvent(document, 'scroll')
.startWith({});
const move = Observable.combineLatest(mouseMove, scrollWindow);
const drag = down.mergeMap((md: MouseEvent) => {
return move
.map(([mm, s]) => mm)
.do((mm: MouseEvent) => {
if(!this.started) {
return;
}
this.visible = true;
this.width = mm.clientX - this.startX + window.scrollX;
this.height = mm.clientY - this.startY + window.scrollY;
this.ref.detectChanges();
this.selectedEvent([this.startX, this.startY], [this.width, this.height]);
})
.skipUntil(up
.take(1)
.do((e: MouseEvent) => {
if(!this.started) {
return;
}
this.visible = false;
this.started = false;
this.width = e.clientX - this.startX + window.scrollX;
this.height = e.clientY - this.startY + window.scrollY;
this.ref.detectChanges();
this.selectedEvent([this.startX, this.startY], [this.width, this.height]);
}))
.take(1);
});
this.draggable = drag.subscribe((e: MouseEvent) => {
// this.cd.detectChanges();
});
}
ngOnDestroy() {
this.draggable.unsubscribe();
}
get d() {
return this.rect(this.startX, this.startY, this.width, this.height);
}
private rect(x: number, y: number, w: number, h: number) {
return "M" + [x, y] + " l" + [w, 0] + " l" + [0, h] + " l" + [-w, 0] + "z";
}
private selectedEvent(start, end) {
const x = Math.min(start[0], end[0]);
const y = Math.min(start[1], end[1]);
const width = Math.abs(start[0] - end[0]);
const height = Math.abs(start[1] - end[1]);
this.rectangleSelected.emit(new Rectangle(x, y, width, height));
}
}

View File

@ -11,6 +11,7 @@ import { Drawing } from "../models/drawing";
import { Symbol } from "../../models/symbol";
import { LayersManager } from "./layers-manager";
import { MapNodesDataSource, MapLinksDataSource, MapDrawingsDataSource, MapSymbolsDataSource } from "../datasources/map-datasource";
import { MultiLinkCalculatorHelper } from "../helpers/multi-link-calculator-helper";
@Injectable()
export class GraphDataManager {
@ -23,7 +24,8 @@ export class GraphDataManager {
private linkToMapLink: LinkToMapLinkConverter,
private drawingToMapDrawing: DrawingToMapDrawingConverter,
private symbolToMapSymbol: SymbolToMapSymbolConverter,
private layersManager: LayersManager
private layersManager: LayersManager,
private multiLinkCalculator: MultiLinkCalculatorHelper
) {}
public setNodes(nodes: Node[]) {
@ -98,6 +100,8 @@ export class GraphDataManager {
link.y = link.source.y + (link.target.y - link.source.y) * 0.5;
}
});
this.multiLinkCalculator.assignDataToLinks(this.getLinks());
}
}

View File

@ -1,10 +1,9 @@
import { select } from "d3-selection";
import { SelectionTool } from "./selection-tool";
import { Context } from "../models/context";
import { SVGSelection } from "../models/types";
import { Rectangle } from "../models/rectangle";
import { TestSVGCanvas } from "../testing";
import { SelectionEventSource } from "../events/selection-event-source";
describe('SelectionTool', () => {
@ -14,11 +13,14 @@ describe('SelectionTool', () => {
let selection_line_tool: SVGSelection;
let path_selection: SVGSelection;
let selected_rectangle: Rectangle;
let selection_event_source: SelectionEventSource;
beforeEach(() => {
context = new Context();
tool = new SelectionTool(context);
tool.rectangleSelected.subscribe((rectangle: Rectangle) => {
selection_event_source = new SelectionEventSource();
tool = new SelectionTool(context, selection_event_source);
selection_event_source.selected.subscribe((rectangle: Rectangle) => {
selected_rectangle = rectangle;
});

View File

@ -5,6 +5,7 @@ import { Subject } from "rxjs";
import { SVGSelection } from "../models/types";
import { Context } from "../models/context";
import { Rectangle } from "../models/rectangle";
import { SelectionEventSource } from "../events/selection-event-source";
@Injectable()
@ -15,11 +16,10 @@ export class SelectionTool {
private path;
private enabled = false;
private needsDeactivate = false;
private needsActivate = false;
public constructor(
private context: Context
private context: Context,
private selectionEventSource: SelectionEventSource
) {}
public setEnabled(enabled) {
@ -107,7 +107,7 @@ export class SelectionTool {
const y = Math.min(start[1], end[1]);
const width = Math.abs(start[0] - end[0]);
const height = Math.abs(start[1] - end[1]);
this.rectangleSelected.next(new Rectangle(x, y, width, height));
this.selectionEventSource.selected.next(new Rectangle(x, y, width, height));
}
private rect(x: number, y: number, w: number, h: number) {

View File

@ -1,6 +1,6 @@
<div class="context-menu" [style.left]="leftPosition" [style.top]="topPosition" *ngIf="node">
<span [matMenuTriggerFor]="selectInterfaceMenu"></span>
<mat-menu #selectInterfaceMenu="matMenu" class="context-menu-items">
<mat-menu #selectInterfaceMenu="matMenu">
<button mat-menu-item *ngFor="let port of node.ports" (click)="chooseInterface(port)">
<mat-icon>add_circle_outline</mat-icon>
<span>{{ port.name }}</span>

View File

@ -1,8 +1,8 @@
import { ChangeDetectorRef, Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core';
import { MatMenuTrigger } from "@angular/material";
import { DomSanitizer } from "@angular/platform-browser";
import { MapNode } from '../../models/map/map-node';
import { MapPort } from '../../models/map/map-port';
import {ChangeDetectorRef, Component, EventEmitter, Input, OnInit, Output, ViewChild} from '@angular/core';
import {MatMenuTrigger} from "@angular/material";
import {DomSanitizer} from "@angular/platform-browser";
import {Node} from "../../../cartography/models/node";
import {Port} from "../../../models/port";
@Component({
@ -15,15 +15,13 @@ export class NodeSelectInterfaceComponent implements OnInit {
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
protected topPosition;
protected leftPosition;
public node: MapNode;
private topPosition;
private leftPosition;
public node: Node;
constructor(
private sanitizer: DomSanitizer,
private changeDetector: ChangeDetectorRef,
) {}
private changeDetector: ChangeDetectorRef) {}
ngOnInit() {
this.setPosition(0, 0);
@ -35,13 +33,13 @@ export class NodeSelectInterfaceComponent implements OnInit {
this.changeDetector.detectChanges();
}
public open(node: MapNode, top: number, left: number) {
public open(node: Node, top: number, left: number) {
this.node = node;
this.setPosition(top, left);
this.contextMenu.openMenu();
}
public chooseInterface(port: MapPort) {
public chooseInterface(port: Port) {
this.onChooseInterface.emit({
'node': this.node,
'port': port

View File

@ -88,8 +88,13 @@ g.node text,
height: 24px !important;
font-size: 13px !important;
padding: 0 6px;
outline: none !important;
}
.context-menu-items .mat-menu-item .mat-icon {
margin-right: 3px;
}
.context-menu-items .mat-menu-item:focus {
background: none;
}

View File

@ -1,5 +1,6 @@
<div *ngIf="project" class="project-map">
<app-map
<app-d3-map
*ngIf="!settings.angular_map"
[symbols]="symbols"
[nodes]="nodes"
[links]="links"
@ -11,7 +12,23 @@
[moving-tool]="tools.moving"
[draw-link-tool]="tools.draw_link"
[readonly]="inReadOnlyMode"
></app-map>
></app-d3-map>
<app-experimental-map
*ngIf="settings.angular_map"
[symbols]="symbols"
[nodes]="nodes"
[links]="links"
[drawings]="drawings"
[width]="project.scene_width"
[height]="project.scene_height"
[show-interface-labels]="project.show_interface_labels"
[selection-tool]="tools.selection"
[moving-tool]="tools.moving"
[draw-link-tool]="tools.draw_link"
[readonly]="inReadOnlyMode"
></app-experimental-map>
<div class="project-toolbar">
<mat-toolbar color="primary" class="project-toolbar">

View File

@ -9,7 +9,6 @@ import { Project } from '../../models/project';
import { Node } from '../../cartography/models/node';
import { SymbolService } from '../../services/symbol.service';
import { Link } from "../../models/link";
import { MapComponent } from "../../cartography/components/map/map.component";
import { ServerService } from "../../services/server.service";
import { ProjectService } from '../../services/project.service';
import { Server } from "../../models/server";
@ -37,6 +36,7 @@ import { MapNode } from '../../cartography/models/map/map-node';
import { LinksEventSource } from '../../cartography/events/links-event-source';
import { MapDrawing } from '../../cartography/models/map/map-drawing';
import { MapPortToPortConverter } from '../../cartography/converters/map/map-port-to-port-converter';
import { SettingsService, Settings } from '../../services/settings.service';
@Component({
@ -61,15 +61,13 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
'moving': false,
'draw_link': false
};
protected settings: Settings;
private inReadOnlyMode = false;
@ViewChild(MapComponent) mapChild: MapComponent;
@ViewChild(NodeContextMenuComponent) nodeContextMenu: NodeContextMenuComponent;
private subscriptions: Subscription[] = [];
constructor(
private route: ActivatedRoute,
private serverService: ServerService,
@ -89,12 +87,14 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private drawingsDataSource: DrawingsDataSource,
private nodesEventSource: NodesEventSource,
private drawingsEventSource: DrawingsEventSource,
private linksEventSource: LinksEventSource
private linksEventSource: LinksEventSource,
private settingsService: SettingsService,
) {}
ngOnInit() {
this.settings = this.settingsService.getAll();
this.progressService.activate();
const routeSub = this.route.paramMap.subscribe((paramMap: ParamMap) => {
const server_id = parseInt(paramMap.get('server_id'), 10);

View File

@ -23,6 +23,10 @@
<mat-checkbox class="example-margin" [(ngModel)]="settings.experimental_features">Enable experimental features (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox>
</div>
<div>
<mat-checkbox class="example-margin" [(ngModel)]="settings.angular_map">Enable experimental Angular Map (WARNING: IT CAN BREAK YOU LABS!)</mat-checkbox>
</div>
</mat-expansion-panel>
</mat-accordion>
</div>

View File

@ -45,7 +45,8 @@ describe('SettingsComponent', () => {
it('should get and save new settings', () => {
const settings = {
'crash_reports': true,
'experimental_features': true
'experimental_features': true,
'angular_map': false
};
const getAll = spyOn(settingsService, 'getAll').and.returnValue(settings);
const setAll = spyOn(settingsService, 'setAll');

View File

@ -53,7 +53,8 @@ describe('SettingsService', () => {
it('should get all values', inject([SettingsService], (service: SettingsService) => {
expect(service.getAll()).toEqual({
'crash_reports': true,
'experimental_features': false
'experimental_features': false,
'angular_map': false
});
}));
@ -65,7 +66,8 @@ describe('SettingsService', () => {
expect(service.getAll()).toEqual({
'crash_reports': false,
'experimental_features': false
'experimental_features': false,
'angular_map': false
});
}));

View File

@ -1,12 +1,12 @@
import { Injectable } from '@angular/core';
import { PersistenceService, StorageType } from "angular-persistence";
import { Subject } from "rxjs";
import { BehaviorSubject } from "rxjs";
export interface Settings {
crash_reports: boolean;
experimental_features: boolean;
angular_map: boolean;
}
@ -14,7 +14,8 @@ export interface Settings {
export class SettingsService {
static DEFAULTS: Settings = {
'crash_reports': true,
'experimental_features': false
'experimental_features': false,
'angular_map': false
};
private settingsSubject: BehaviorSubject<Settings>;