Decouple project-map from map

This commit is contained in:
ziajka 2018-11-05 14:21:53 +01:00
parent 48dcfcbf1c
commit 1d1cf149bb
11 changed files with 149 additions and 90 deletions

View File

@ -10,7 +10,7 @@ import { LayersManager } from './managers/layers-manager';
import { MapChangeDetectorRef } from './services/map-change-detector-ref'; import { MapChangeDetectorRef } from './services/map-change-detector-ref';
import { GraphLayout } from './widgets/graph-layout'; import { GraphLayout } from './widgets/graph-layout';
import { LinksWidget } from './widgets/links'; import { LinksWidget } from './widgets/links';
import { NodesWidget, NodeEvent } from './widgets/nodes'; import { NodesWidget } from './widgets/nodes';
import { DrawingsWidget } from './widgets/drawings'; import { DrawingsWidget } from './widgets/drawings';
import { DrawingLineWidget } from './widgets/drawing-line'; import { DrawingLineWidget } from './widgets/drawing-line';
import { SelectionTool } from './tools/selection-tool'; import { SelectionTool } from './tools/selection-tool';
@ -24,6 +24,7 @@ import { ImageDrawingWidget } from './widgets/drawings/image-drawing';
import { RectDrawingWidget } from './widgets/drawings/rect-drawing'; import { RectDrawingWidget } from './widgets/drawings/rect-drawing';
import { TextDrawingWidget } from './widgets/drawings/text-drawing'; import { TextDrawingWidget } from './widgets/drawings/text-drawing';
import { LineDrawingWidget } from './widgets/drawings/line-drawing'; import { LineDrawingWidget } from './widgets/drawings/line-drawing';
import { Context } from './models/context';
@NgModule({ @NgModule({
imports: [ imports: [
@ -55,7 +56,8 @@ import { LineDrawingWidget } from './widgets/drawings/line-drawing';
ImageDrawingWidget, ImageDrawingWidget,
LineDrawingWidget, LineDrawingWidget,
RectDrawingWidget, RectDrawingWidget,
TextDrawingWidget TextDrawingWidget,
Context
], ],
exports: [MapComponent] exports: [MapComponent]
}) })

View File

@ -13,6 +13,9 @@ import { Drawing } from "../../models/drawing";
import { Symbol } from '../../../models/symbol'; import { Symbol } from '../../../models/symbol';
import { NodeEvent, NodesWidget } from '../../widgets/nodes'; import { NodeEvent, NodesWidget } from '../../widgets/nodes';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { InterfaceLabelWidget } from '../../widgets/interface-label';
import { SelectionTool } from '../../tools/selection-tool';
import { MovingTool } from '../../tools/moving-tool';
@Component({ @Component({
@ -34,7 +37,8 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
private d3: D3; private d3: D3;
private parentNativeElement: any; private parentNativeElement: any;
private svg: Selection<SVGSVGElement, any, null, undefined>; private svg: Selection<SVGSVGElement, any, null, undefined>;
private graphContext: Context;
private isReady = false;
private onNodeDraggingSubscription: Subscription; private onNodeDraggingSubscription: Subscription;
@ -43,9 +47,13 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
}; };
constructor( constructor(
private context: Context,
protected element: ElementRef, protected element: ElementRef,
protected d3Service: D3Service, protected d3Service: D3Service,
protected nodesWidget: NodesWidget, protected nodesWidget: NodesWidget,
protected interfaceLabelWidget: InterfaceLabelWidget,
protected selectionToolWidget: SelectionTool,
protected movingToolWidget: MovingTool,
public graphLayout: GraphLayout public graphLayout: GraphLayout
) { ) {
this.d3 = d3Service.getD3(); this.d3 = d3Service.getD3();
@ -56,6 +64,28 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
@Input('show-interface-labels') @Input('show-interface-labels')
set showInterfaceLabels(value) { set showInterfaceLabels(value) {
this.settings.show_interface_labels = value; this.settings.show_interface_labels = value;
this.interfaceLabelWidget.setEnabled(value);
if (this.isReady) {
this.redraw();
}
}
@Input('moving-tool')
set movingTool(value) {
this.movingToolWidget.setEnabled(value);
if(this.isReady) {
this.redraw();
}
}
@Input('selection-tool')
set selectionTool(value) {
this.selectionToolWidget.setEnabled(value);
if(this.isReady) {
this.redraw();
}
} }
ngOnChanges(changes: { [propKey: string]: SimpleChange }) { ngOnChanges(changes: { [propKey: string]: SimpleChange }) {
@ -88,22 +118,17 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
} }
ngOnInit() { ngOnInit() {
const d3 = this.d3;
if (this.parentNativeElement !== null) { if (this.parentNativeElement !== null) {
this.createGraph(this.parentNativeElement); this.createGraph(this.parentNativeElement);
} }
this.context.size = this.getSize();
} }
public createGraph(domElement: HTMLElement) { public createGraph(domElement: HTMLElement) {
const rootElement = this.d3.select(domElement); const rootElement = this.d3.select(domElement);
this.svg = rootElement.select<SVGSVGElement>('svg'); this.svg = rootElement.select<SVGSVGElement>('svg');
this.graphContext = new Context(true); this.graphLayout.connect(this.svg, this.context);
this.graphContext.size = this.getSize();
this.graphLayout.connect(this.svg, this.graphContext);
this.onNodeDraggingSubscription = this.graphLayout.getNodesWidget().onNodeDragging.subscribe((eventNode: NodeEvent) => { this.onNodeDraggingSubscription = this.graphLayout.getNodesWidget().onNodeDragging.subscribe((eventNode: NodeEvent) => {
const linksWidget = this.graphLayout.getLinksWidget(); const linksWidget = this.graphLayout.getLinksWidget();
@ -115,7 +140,9 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
}); });
}); });
this.graphLayout.draw(this.svg, this.graphContext); this.graphLayout.draw(this.svg, this.context);
this.isReady = true;
} }
public getSize(): Size { public getSize(): Size {
@ -132,7 +159,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
private changeLayout() { private changeLayout() {
if (this.parentNativeElement != null) { if (this.parentNativeElement != null) {
this.graphContext.size = this.getSize(); this.context.size = this.getSize();
} }
this.graphLayout.setNodes(this.nodes); this.graphLayout.setNodes(this.nodes);
@ -174,7 +201,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
} }
public redraw() { public redraw() {
this.graphLayout.draw(this.svg, this.graphContext); this.graphLayout.draw(this.svg, this.context);
} }
public reload() { public reload() {

View File

@ -1,5 +1,6 @@
import { Size } from "./size"; import { Size } from "./size";
import { Point } from "./point"; import { Point } from "./point";
import { Injectable } from "@angular/core";
export class Transformation { export class Transformation {
constructor( constructor(
@ -9,12 +10,13 @@ export class Transformation {
) {} ) {}
} }
@Injectable()
export class Context { export class Context {
public transformation: Transformation; public transformation: Transformation;
public size: Size; public size: Size;
public centerZeroZeroPoint = true;
constructor(public centerZeroZeroPoint = true) {
constructor() {
this.size = new Size(0, 0); this.size = new Size(0, 0);
this.transformation = new Transformation(0, 0, 1); this.transformation = new Transformation(0, 0, 1);
} }

View File

@ -9,32 +9,49 @@ import { Context} from "../models/context";
@Injectable() @Injectable()
export class MovingTool { export class MovingTool {
private selection: SVGSelection; // private selection: SVGSelection;
private context: Context;
private zoom: ZoomBehavior<SVGSVGElement, any>; private zoom: ZoomBehavior<SVGSVGElement, any>;
private enabled = false;
constructor() { private needsDeactivate = false;
private needsActivate = false;
constructor(
private context: Context
) {
this.zoom = zoom<SVGSVGElement, any>() this.zoom = zoom<SVGSVGElement, any>()
.scaleExtent([1 / 2, 8]); .scaleExtent([1 / 2, 8]);
} }
public connect(selection: SVGSelection, context: Context) { public setEnabled(enabled) {
this.selection = selection; if (this.enabled != enabled) {
this.context = context; if (enabled) {
this.needsActivate = true;
}
else {
this.needsDeactivate = true;
}
}
this.enabled = enabled;
} }
public draw(selection: SVGSelection, context: Context) { public draw(selection: SVGSelection, context: Context) {
this.selection = selection; if(this.needsActivate) {
this.context = context; this.activate(selection);
this.needsActivate = false;
}
if(this.needsDeactivate) {
this.deactivate(selection);
this.needsDeactivate = false;
}
} }
public activate() { private activate(selection: SVGSelection) {
const self = this; const self = this;
const onZoom = function(this: SVGSVGElement) { const onZoom = function(this: SVGSVGElement) {
const canvas = self.selection.select<SVGGElement>("g.canvas"); const canvas = selection.select<SVGGElement>("g.canvas");
const e: D3ZoomEvent<SVGSVGElement, any> = event; const e: D3ZoomEvent<SVGSVGElement, any> = event;
canvas.attr( canvas.attr(
'transform', 'transform',
@ -51,12 +68,12 @@ export class MovingTool {
}; };
this.zoom.on('zoom', onZoom); this.zoom.on('zoom', onZoom);
this.selection.call(this.zoom); selection.call(this.zoom);
} }
public deactivate() { private deactivate(selection: SVGSelection) {
// d3.js preserves event `mousedown.zoom` and blocks selection // d3.js preserves event `mousedown.zoom` and blocks selection
this.selection.on('mousedown.zoom', null); selection.on('mousedown.zoom', null);
this.zoom.on('zoom', null); this.zoom.on('zoom', null);
} }
} }

View File

@ -11,26 +11,33 @@ import { Rectangle } from "../models/rectangle";
export class SelectionTool { export class SelectionTool {
static readonly SELECTABLE_CLASS = '.selectable'; static readonly SELECTABLE_CLASS = '.selectable';
public rectangleSelected: Subject<Rectangle>; public rectangleSelected = new Subject<Rectangle>();
private selection: SVGSelection;
private path; private path;
private context: Context; private enabled = false;
private needsDeactivate = false;
private needsActivate = false;
public constructor() { public constructor(
this.rectangleSelected = new Subject<Rectangle>(); private context: Context
) {}
public setEnabled(enabled) {
if (this.enabled != enabled) {
if (enabled) {
this.needsActivate = true;
}
else {
this.needsDeactivate = true;
}
}
this.enabled = enabled;
} }
public connect(selection: SVGSelection, context: Context) { private activate(selection) {
this.selection = selection;
this.context = context;
}
public activate() {
const self = this; const self = this;
selection.on("mousedown", function() {
this.selection.on("mousedown", function() {
const subject = select(window); const subject = select(window);
const parent = this.parentElement; const parent = this.parentElement;
@ -38,7 +45,7 @@ export class SelectionTool {
self.startSelection(start); self.startSelection(start);
// clear selection // clear selection
self.selection selection
.selectAll(SelectionTool.SELECTABLE_CLASS) .selectAll(SelectionTool.SELECTABLE_CLASS)
.classed("selected", false); .classed("selected", false);
@ -56,8 +63,8 @@ export class SelectionTool {
}); });
} }
public deactivate() { private deactivate(selection) {
this.selection.on('mousedown', null); selection.on('mousedown', null);
} }
public draw(selection: SVGSelection, context: Context) { public draw(selection: SVGSelection, context: Context) {
@ -72,7 +79,15 @@ export class SelectionTool {
.attr("class", "selection") .attr("class", "selection")
.attr("visibility", "hidden"); .attr("visibility", "hidden");
} }
this.selection = selection;
if(this.needsActivate) {
this.activate(selection);
this.needsActivate = false;
}
if(this.needsDeactivate) {
this.deactivate(selection);
this.needsDeactivate = false;
}
} }
private startSelection(start) { private startSelection(start) {

View File

@ -60,20 +60,12 @@ export class GraphLayout implements Widget {
return this.drawingLineTool; return this.drawingLineTool;
} }
public getMovingTool() {
return this.movingTool;
}
public getSelectionTool() { public getSelectionTool() {
return this.selectionTool; return this.selectionTool;
} }
connect(view: SVGSelection, context: Context) { connect(view: SVGSelection, context: Context) {
this.drawingLineTool.connect(view, context); this.drawingLineTool.connect(view, context);
this.selectionTool.connect(view, context);
this.movingTool.connect(view, context);
this.selectionTool.activate();
} }
draw(view: SVGSelection, context: Context) { draw(view: SVGSelection, context: Context) {

View File

@ -1,4 +1,5 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Widget } from "./widget"; import { Widget } from "./widget";
import { SVGSelection } from "../models/types"; import { SVGSelection } from "../models/types";
import { Link } from "../../models/link"; import { Link } from "../../models/link";

View File

@ -15,8 +15,8 @@ export class NodeSelectInterfaceComponent implements OnInit {
@ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger; @ViewChild(MatMenuTrigger) contextMenu: MatMenuTrigger;
private topPosition; protected topPosition;
private leftPosition; protected leftPosition;
public node: Node; public node: Node;
constructor( constructor(

View File

@ -28,7 +28,10 @@ export class ProjectMapShortcutsComponent implements OnInit, OnDestroy {
) { } ) { }
ngOnInit() { ngOnInit() {
this.deleteHotkey = new Hotkey('del', this.onDeleteHandler); const self = this;
this.deleteHotkey = new Hotkey('del', (event: KeyboardEvent) => {
return self.onDeleteHandler(event);
});
this.hotkeysService.add(this.deleteHotkey); this.hotkeysService.add(this.deleteHotkey);
} }

View File

@ -7,6 +7,8 @@
[width]="project.scene_width" [width]="project.scene_width"
[height]="project.scene_height" [height]="project.scene_height"
[show-interface-labels]="project.show_interface_labels" [show-interface-labels]="project.show_interface_labels"
[selection-tool]="tools.selection"
[moving-tool]="tools.moving"
(onNodeDragged)="onNodeDragged($event)" (onNodeDragged)="onNodeDragged($event)"
></app-map> ></app-map>
@ -53,7 +55,7 @@
</mat-toolbar-row> </mat-toolbar-row>
<mat-toolbar-row> <mat-toolbar-row>
<button mat-icon-button [color]="movingMode ? 'primary': 'basic'" (click)="toggleMovingMode()"> <button mat-icon-button [color]="tools.moving ? 'primary': 'basic'" (click)="toggleMovingMode()">
<mat-icon>zoom_out_map</mat-icon> <mat-icon>zoom_out_map</mat-icon>
</button> </button>
</mat-toolbar-row> </mat-toolbar-row>

View File

@ -48,8 +48,13 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
private ws: Subject<any>; private ws: Subject<any>;
private drawLineMode = false; private drawLineMode = false;
private movingMode = false;
private readonly = false; protected tools = {
'selection': true,
'moving': false
};
private inReadOnlyMode = false;
protected selectionManager: SelectionManager; protected selectionManager: SelectionManager;
@ -189,13 +194,8 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
} }
setUpMapCallbacks(project: Project) { setUpMapCallbacks(project: Project) {
if (this.readonly) {
this.mapChild.graphLayout.getSelectionTool().deactivate();
}
this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.readonly); this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.readonly);
const onContextMenu = this.mapChild.graphLayout.getNodesWidget().onContextMenu.subscribe((eventNode: NodeEvent) => { const onContextMenu = this.mapChild.graphLayout.getNodesWidget().onContextMenu.subscribe((eventNode: NodeEvent) => {
this.nodeContextMenu.open(eventNode.node, eventNode.event.clientY, eventNode.event.clientX); this.nodeContextMenu.open(eventNode.node, eventNode.event.clientY, eventNode.event.clientX);
}); });
@ -217,9 +217,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
this.mapChild.graphLayout.getSelectionTool().rectangleSelected) this.mapChild.graphLayout.getSelectionTool().rectangleSelected)
); );
this.mapChild.graphLayout
.getLinksWidget().getLinkWidget().getInterfaceLabelWidget().setEnabled(this.project.show_interface_labels);
this.mapChild.reload(); this.mapChild.reload();
} }
@ -244,6 +241,27 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
}); });
} }
public set readonly(value) {
this.inReadOnlyMode = value;
if (value) {
this.tools.selection = false;
}
else {
this.tools.selection = true;
}
}
public get readonly() {
return this.inReadOnlyMode;
}
public toggleMovingMode() {
this.tools.moving = !this.tools.moving;
if (!this.readonly) {
this.tools.selection = !this.tools.moving;
}
}
public toggleDrawLineMode() { public toggleDrawLineMode() {
this.drawLineMode = !this.drawLineMode; this.drawLineMode = !this.drawLineMode;
if (!this.drawLineMode) { if (!this.drawLineMode) {
@ -251,22 +269,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
} }
} }
public toggleMovingMode() {
this.movingMode = !this.movingMode;
if (this.movingMode) {
if (!this.readonly) {
this.mapChild.graphLayout.getSelectionTool().deactivate();
}
this.mapChild.graphLayout.getMovingTool().activate();
} else {
this.mapChild.graphLayout.getMovingTool().deactivate();
if (!this.readonly) {
this.mapChild.graphLayout.getSelectionTool().activate();
}
}
}
public onChooseInterface(event) { public onChooseInterface(event) {
const node: Node = event.node; const node: Node = event.node;
const port: Port = event.port; const port: Port = event.port;
@ -294,10 +296,6 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
public toggleShowInterfaceLabels(enabled: boolean) { public toggleShowInterfaceLabels(enabled: boolean) {
this.project.show_interface_labels = enabled; this.project.show_interface_labels = enabled;
this.mapChild.graphLayout.getLinksWidget().getLinkWidget().getInterfaceLabelWidget()
.setEnabled(this.project.show_interface_labels);
this.mapChild.reload();
} }
public ngOnDestroy() { public ngOnDestroy() {