mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-02-21 18:06:38 +00:00
Separate node rendering from nodes; inital drag multiple nodes
This commit is contained in:
parent
5da90118c7
commit
0fe3d0e7ca
@ -2,11 +2,11 @@ import { Component, OnInit, Output, EventEmitter, OnDestroy, ViewChild } from '@
|
||||
import { Port } from '../../../models/port';
|
||||
import { DrawingLineWidget } from '../../widgets/drawing-line';
|
||||
import { Node } from '../../models/node';
|
||||
import { NodesWidget } from '../../widgets/nodes';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { NodeSelectInterfaceComponent } from '../node-select-interface/node-select-interface.component';
|
||||
import { LinkCreated } from '../../events/links';
|
||||
import { NodeClicked } from '../../events/nodes';
|
||||
import { NodeWidget } from '../../widgets/node';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -23,11 +23,11 @@ export class DrawLinkToolComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(
|
||||
private drawingLineTool: DrawingLineWidget,
|
||||
private nodesWidget: NodesWidget
|
||||
private nodeWidget: NodeWidget
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.onNodeClicked = this.nodesWidget.onNodeClicked.subscribe((eventNode: NodeClicked) => {
|
||||
this.onNodeClicked = this.nodeWidget.onNodeClicked.subscribe((eventNode: NodeClicked) => {
|
||||
this.nodeSelectInterfaceMenu.open(
|
||||
eventNode.node,
|
||||
eventNode.event.clientY,
|
||||
|
@ -20,6 +20,8 @@ import { MapChangeDetectorRef } from '../../services/map-change-detector-ref';
|
||||
import { NodeDragging, NodeDragged } from '../../events/nodes';
|
||||
import { LinkCreated } from '../../events/links';
|
||||
import { CanvasSizeDetector } from '../../helpers/canvas-size-detector';
|
||||
import { SelectionManager } from '../../managers/selection-manager';
|
||||
import { NodeWidget } from '../../widgets/node';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -33,6 +35,8 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
@Input() drawings: Drawing[] = [];
|
||||
@Input() symbols: Symbol[] = [];
|
||||
|
||||
@Input('selection-manager') selectionManager: SelectionManager;
|
||||
|
||||
@Input() width = 1500;
|
||||
@Input() height = 600;
|
||||
|
||||
@ -55,6 +59,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
private canvasSizeDetector: CanvasSizeDetector,
|
||||
protected element: ElementRef,
|
||||
protected nodesWidget: NodesWidget,
|
||||
protected nodeWidget: NodeWidget,
|
||||
protected linksWidget: LinksWidget,
|
||||
protected interfaceLabelWidget: InterfaceLabelWidget,
|
||||
protected selectionToolWidget: SelectionTool,
|
||||
@ -62,7 +67,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
public graphLayout: GraphLayout
|
||||
) {
|
||||
this.parentNativeElement = element.nativeElement;
|
||||
this.onNodeDragged = nodesWidget.onNodeDragged;
|
||||
this.onNodeDragged = nodeWidget.onNodeDragged;
|
||||
}
|
||||
|
||||
@Input('show-interface-labels')
|
||||
@ -122,14 +127,25 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
this.context.size = this.getSize();
|
||||
|
||||
this.onNodeDraggingSubscription = this.nodesWidget.onNodeDragging.subscribe((eventNode: NodeDragging) => {
|
||||
const links = this.links.filter((link) => link.target.node_id === eventNode.node.node_id || link.source.node_id === eventNode.node.node_id);
|
||||
this.onNodeDraggingSubscription = this.nodeWidget.onNodeDragging.subscribe((eventNode: NodeDragging) => {
|
||||
const nodes = this.selectionManager.getSelectedNodes();
|
||||
|
||||
nodes.forEach((node: Node) => {
|
||||
|
||||
node.x += eventNode.event.dx;
|
||||
node.y += eventNode.event.dy;
|
||||
|
||||
this.nodesWidget.redrawNode(this.svg, node);
|
||||
|
||||
const links = this.links.filter((link) => link.target.node_id === node.node_id || link.source.node_id === node.node_id);
|
||||
|
||||
links.forEach((link) => {
|
||||
this.linksWidget.redrawLink(this.svg, link);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
this.onChangesDetected = this.mapChangeDetectorRef.changesDetected.subscribe(() => {
|
||||
if (this.mapChangeDetectorRef.hasBeenDrawn) {
|
||||
this.reload();
|
||||
@ -193,7 +209,7 @@ export class MapComponent implements OnInit, OnChanges, OnDestroy {
|
||||
}
|
||||
|
||||
private onSymbolsChange(change: SimpleChange) {
|
||||
this.graphLayout.getNodesWidget().setSymbols(this.symbols);
|
||||
this.nodeWidget.setSymbols(this.symbols);
|
||||
}
|
||||
|
||||
public redraw() {
|
||||
|
@ -14,11 +14,13 @@ import { ImageDrawingWidget } from './widgets/drawings/image-drawing';
|
||||
import { RectDrawingWidget } from './widgets/drawings/rect-drawing';
|
||||
import { TextDrawingWidget } from './widgets/drawings/text-drawing';
|
||||
import { LineDrawingWidget } from './widgets/drawings/line-drawing';
|
||||
import { NodeWidget } from './widgets/node';
|
||||
|
||||
export const D3_MAP_IMPORTS = [
|
||||
GraphLayout,
|
||||
LinksWidget,
|
||||
NodesWidget,
|
||||
NodeWidget,
|
||||
DrawingsWidget,
|
||||
DrawingLineWidget,
|
||||
SelectionTool,
|
||||
|
@ -19,14 +19,6 @@ export class LinkWidget implements Widget {
|
||||
private interfaceStatusWidget: InterfaceStatusWidget
|
||||
) {}
|
||||
|
||||
public getInterfaceLabelWidget() {
|
||||
return this.interfaceLabelWidget;
|
||||
}
|
||||
|
||||
public getInterfaceStatusWidget() {
|
||||
return this.interfaceStatusWidget;
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
const link_body = view.selectAll<SVGGElement, Link>("g.link_body")
|
||||
.data((l) => [l]);
|
||||
@ -51,8 +43,8 @@ export class LinkWidget implements Widget {
|
||||
.select<SVGPathElement>('path')
|
||||
.classed('selected', (l: Link) => l.is_selected);
|
||||
|
||||
this.getInterfaceLabelWidget().draw(link_body_merge);
|
||||
this.getInterfaceStatusWidget().draw(link_body_merge);
|
||||
this.interfaceLabelWidget.draw(link_body_merge);
|
||||
this.interfaceStatusWidget.draw(link_body_merge);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -11,16 +11,12 @@ import { LinkWidget } from "./link";
|
||||
export class LinksWidget implements Widget {
|
||||
constructor(
|
||||
private multiLinkCalculatorHelper: MultiLinkCalculatorHelper,
|
||||
private linkWidget: LinkWidget,
|
||||
private linkWidget: LinkWidget
|
||||
) {
|
||||
}
|
||||
|
||||
public getLinkWidget() {
|
||||
return this.linkWidget;
|
||||
}
|
||||
|
||||
public redrawLink(view: SVGSelection, link: Link) {
|
||||
this.getLinkWidget().draw(this.selectLink(view, link));
|
||||
this.linkWidget.draw(this.selectLink(view, link));
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
@ -48,7 +44,7 @@ export class LinksWidget implements Widget {
|
||||
|
||||
const merge = link.merge(link_enter);
|
||||
|
||||
this.getLinkWidget().draw(merge);
|
||||
this.linkWidget.draw(merge);
|
||||
|
||||
link
|
||||
.exit()
|
||||
|
140
src/app/cartography/widgets/node.ts
Normal file
140
src/app/cartography/widgets/node.ts
Normal file
@ -0,0 +1,140 @@
|
||||
import { Injectable, EventEmitter } from "@angular/core";
|
||||
|
||||
import { Widget } from "./widget";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { Node } from "../models/node";
|
||||
import { NodeContextMenu, NodeClicked, NodeDragged, NodeDragging } from "../events/nodes";
|
||||
import { CssFixer } from "../helpers/css-fixer";
|
||||
import { FontFixer } from "../helpers/font-fixer";
|
||||
import { select, event } from "d3-selection";
|
||||
import { Symbol } from "../../models/symbol";
|
||||
import { D3DragEvent, drag } from "d3-drag";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class NodeWidget implements Widget {
|
||||
static NODE_LABEL_MARGIN = 3;
|
||||
|
||||
public onContextMenu = new EventEmitter<NodeContextMenu>();
|
||||
public onNodeClicked = new EventEmitter<NodeClicked>();
|
||||
public onNodeDragged = new EventEmitter<NodeDragged>();
|
||||
public onNodeDragging = new EventEmitter<NodeDragging>();
|
||||
|
||||
private symbols: Symbol[] = [];
|
||||
private draggingEnabled = false;
|
||||
|
||||
constructor(
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer,
|
||||
) {}
|
||||
|
||||
public setSymbols(symbols: Symbol[]) {
|
||||
this.symbols = symbols;
|
||||
}
|
||||
|
||||
public setDraggingEnabled(enabled: boolean) {
|
||||
this.draggingEnabled = enabled;
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection) {
|
||||
const self = this;
|
||||
|
||||
const node_body = view.selectAll<SVGGElement, Node>("g.node_body")
|
||||
.data((n) => [n]);
|
||||
|
||||
const node_body_enter = node_body.enter()
|
||||
.append<SVGGElement>('g')
|
||||
.attr("class", "node_body");
|
||||
|
||||
node_body_enter
|
||||
.append<SVGImageElement>('image');
|
||||
|
||||
// add label of node
|
||||
node_body_enter
|
||||
.append<SVGTextElement>('text')
|
||||
.attr('class', 'label');
|
||||
|
||||
const node_body_merge = node_body.merge(node_body_enter)
|
||||
.classed('selected', (n: Node) => n.is_selected)
|
||||
.on("contextmenu", function (n: Node, i: number) {
|
||||
event.preventDefault();
|
||||
self.onContextMenu.emit(new NodeContextMenu(event, n));
|
||||
})
|
||||
.on('click', (n: Node) => {
|
||||
this.onNodeClicked.emit(new NodeClicked(event, n));
|
||||
});
|
||||
|
||||
// update image of node
|
||||
node_body_merge
|
||||
.select<SVGImageElement>('image')
|
||||
.attr('xnode:href', (n: Node) => {
|
||||
const symbol = this.symbols.find((s: Symbol) => s.symbol_id === n.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';
|
||||
})
|
||||
.attr('width', (n: Node) => n.width)
|
||||
.attr('height', (n: Node) => n.height)
|
||||
.attr('x', (n: Node) => 0)
|
||||
.attr('y', (n: Node) => 0)
|
||||
.on('mouseover', function (this, n: Node) {
|
||||
select(this).attr("class", "over");
|
||||
})
|
||||
.on('mouseout', function (this, n: Node) {
|
||||
select(this).attr("class", "");
|
||||
});
|
||||
|
||||
node_body_merge
|
||||
.attr('transform', (n: Node) => {
|
||||
return `translate(${n.x},${n.y})`;
|
||||
});
|
||||
|
||||
node_body_merge
|
||||
.select<SVGTextElement>('text.label')
|
||||
// .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way
|
||||
.attr('style', (n: Node) => {
|
||||
let styles = this.cssFixer.fix(n.label.style);
|
||||
styles = this.fontFixer.fixStyles(styles);
|
||||
return styles;
|
||||
})
|
||||
.text((n: Node) => n.label.text)
|
||||
.attr('x', function (this: SVGTextElement, n: Node) {
|
||||
if (n.label.x === null) {
|
||||
// center
|
||||
const bbox = this.getBBox();
|
||||
return -bbox.width / 2.;
|
||||
}
|
||||
return n.label.x + NodeWidget.NODE_LABEL_MARGIN;
|
||||
})
|
||||
.attr('y', function (this: SVGTextElement, n: Node) {
|
||||
let bbox = this.getBBox();
|
||||
|
||||
if (n.label.x === null) {
|
||||
// center
|
||||
bbox = this.getBBox();
|
||||
return - n.height / 2. - bbox.height ;
|
||||
}
|
||||
return n.label.y + bbox.height - NodeWidget.NODE_LABEL_MARGIN;
|
||||
});
|
||||
|
||||
const callback = function (this: SVGGElement, n: Node) {
|
||||
const e: D3DragEvent<SVGGElement, Node, Node> = event;
|
||||
self.onNodeDragging.emit(new NodeDragging(e, n));
|
||||
};
|
||||
|
||||
const dragging = () => {
|
||||
return drag<SVGGElement, Node>()
|
||||
.on('drag', callback)
|
||||
.on('end', (n: Node) => {
|
||||
const e: D3DragEvent<SVGGElement, Node, Node> = event;
|
||||
self.onNodeDragged.emit(new NodeDragged(e, n));
|
||||
});
|
||||
};
|
||||
|
||||
if (this.draggingEnabled) {
|
||||
node_body_merge.call(dragging());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,190 +1,53 @@
|
||||
import { Injectable, EventEmitter } from "@angular/core";
|
||||
|
||||
import { event, select, Selection } from "d3-selection";
|
||||
import { D3DragEvent, drag } from "d3-drag";
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
import { Widget } from "./widget";
|
||||
import { Node } from "../models/node";
|
||||
import { SVGSelection } from "../models/types";
|
||||
import { Symbol } from "../../models/symbol";
|
||||
import { Layer } from "../models/layer";
|
||||
import { CssFixer } from "../helpers/css-fixer";
|
||||
import { FontFixer } from "../helpers/font-fixer";
|
||||
import { NodeDragging, NodeDragged, NodeContextMenu, NodeClicked } from "../events/nodes";
|
||||
import { NodeWidget } from "./node";
|
||||
|
||||
|
||||
@Injectable()
|
||||
export class NodesWidget implements Widget {
|
||||
static NODE_LABEL_MARGIN = 3;
|
||||
|
||||
private debug = false;
|
||||
private draggingEnabled = false;
|
||||
|
||||
private symbols: Symbol[] = [];
|
||||
|
||||
public onContextMenu = new EventEmitter<NodeContextMenu>();
|
||||
public onNodeClicked = new EventEmitter<NodeClicked>();
|
||||
public onNodeDragged = new EventEmitter<NodeDragged>();
|
||||
public onNodeDragging = new EventEmitter<NodeDragging>();
|
||||
|
||||
constructor(
|
||||
private cssFixer: CssFixer,
|
||||
private fontFixer: FontFixer
|
||||
private nodeWidget: NodeWidget
|
||||
) {
|
||||
this.symbols = [];
|
||||
}
|
||||
|
||||
public setSymbols(symbols: Symbol[]) {
|
||||
this.symbols = symbols;
|
||||
public redrawNode(view: SVGSelection, node: Node) {
|
||||
this.nodeWidget.draw(this.selectNode(view, node));
|
||||
}
|
||||
|
||||
public setDraggingEnabled(enabled: boolean) {
|
||||
this.draggingEnabled = enabled;
|
||||
public draw(view: SVGSelection) {
|
||||
const node = view
|
||||
.selectAll<SVGGElement, Node>("g.node")
|
||||
.data((layer: Layer) => {
|
||||
if (layer.nodes) {
|
||||
return layer.nodes;
|
||||
}
|
||||
|
||||
public revise(selection: SVGSelection) {
|
||||
selection
|
||||
.attr('transform', (n: Node) => {
|
||||
return `translate(${n.x},${n.y})`;
|
||||
});
|
||||
|
||||
selection
|
||||
.select<SVGTextElement>('text.label')
|
||||
// .attr('y', (n: Node) => n.label.y - n.height / 2. + 10) // @todo: server computes y in auto way
|
||||
.attr('style', (n: Node) => {
|
||||
let styles = this.cssFixer.fix(n.label.style);
|
||||
styles = this.fontFixer.fixStyles(styles);
|
||||
return styles;
|
||||
})
|
||||
.text((n: Node) => n.label.text)
|
||||
.attr('x', function (this: SVGTextElement, n: Node) {
|
||||
if (n.label.x === null) {
|
||||
// center
|
||||
const bbox = this.getBBox();
|
||||
return -bbox.width / 2.;
|
||||
}
|
||||
return n.label.x + NodesWidget.NODE_LABEL_MARGIN;
|
||||
})
|
||||
.attr('y', function (this: SVGTextElement, n: Node) {
|
||||
let bbox = this.getBBox();
|
||||
|
||||
if (n.label.x === null) {
|
||||
// center
|
||||
bbox = this.getBBox();
|
||||
return - n.height / 2. - bbox.height ;
|
||||
}
|
||||
return n.label.y + bbox.height - NodesWidget.NODE_LABEL_MARGIN;
|
||||
});
|
||||
|
||||
selection
|
||||
.select<SVGTextElement>('text.node_point_label')
|
||||
.text((n: Node) => `(${n.x}, ${n.y})`);
|
||||
|
||||
}
|
||||
|
||||
public draw(view: SVGSelection, nodes?: Node[]) {
|
||||
const self = this;
|
||||
|
||||
let nodes_selection: Selection<SVGGElement, Node, any, any> = view
|
||||
.selectAll<SVGGElement, Node>('g.node');
|
||||
|
||||
if (nodes) {
|
||||
nodes_selection = nodes_selection.data(nodes);
|
||||
} else {
|
||||
nodes_selection = nodes_selection.data((l: Layer) => {
|
||||
return l.nodes;
|
||||
return [];
|
||||
}, (n: Node) => {
|
||||
return n.node_id;
|
||||
});
|
||||
}
|
||||
|
||||
const node_enter = nodes_selection
|
||||
.enter()
|
||||
const node_enter = node.enter()
|
||||
.append<SVGGElement>('g')
|
||||
.attr('class', 'node');
|
||||
.attr('class', 'node')
|
||||
.attr('node_id', (n: Node) => n.node_id)
|
||||
|
||||
// add image to node
|
||||
node_enter
|
||||
.append<SVGImageElement>('image');
|
||||
const merge = node.merge(node_enter);
|
||||
|
||||
// add label of node
|
||||
node_enter
|
||||
.append<SVGTextElement>('text')
|
||||
.attr('class', 'label');
|
||||
this.nodeWidget.draw(merge);
|
||||
|
||||
if (this.debug) {
|
||||
node_enter
|
||||
.append<SVGCircleElement>('circle')
|
||||
.attr('class', 'node_point')
|
||||
.attr('r', 2);
|
||||
|
||||
node_enter
|
||||
.append<SVGTextElement>('text')
|
||||
.attr('class', 'node_point_label')
|
||||
.attr('x', '-100')
|
||||
.attr('y', '0');
|
||||
}
|
||||
|
||||
const node_merge = nodes_selection
|
||||
.merge(node_enter)
|
||||
.classed('selected', (n: Node) => n.is_selected)
|
||||
.on("contextmenu", function (n: Node, i: number) {
|
||||
event.preventDefault();
|
||||
self.onContextMenu.emit(new NodeContextMenu(event, n));
|
||||
})
|
||||
.on('click', (n: Node) => {
|
||||
this.onNodeClicked.emit(new NodeClicked(event, n));
|
||||
});
|
||||
|
||||
// update image of node
|
||||
node_merge
|
||||
.select<SVGImageElement>('image')
|
||||
.attr('xlink:href', (n: Node) => {
|
||||
const symbol = this.symbols.find((s: Symbol) => s.symbol_id === n.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';
|
||||
})
|
||||
.attr('width', (n: Node) => n.width)
|
||||
.attr('height', (n: Node) => n.height)
|
||||
.attr('x', (n: Node) => 0)
|
||||
.attr('y', (n: Node) => 0)
|
||||
.on('mouseover', function (this, n: Node) {
|
||||
select(this).attr("class", "over");
|
||||
})
|
||||
.on('mouseout', function (this, n: Node) {
|
||||
select(this).attr("class", "");
|
||||
});
|
||||
|
||||
this.revise(node_merge);
|
||||
|
||||
const callback = function (this: SVGGElement, n: Node) {
|
||||
const e: D3DragEvent<SVGGElement, Node, Node> = event;
|
||||
|
||||
n.x = e.x;
|
||||
n.y = e.y;
|
||||
|
||||
self.revise(select(this));
|
||||
self.onNodeDragging.emit(new NodeDragging(event, n));
|
||||
};
|
||||
|
||||
const dragging = () => {
|
||||
return drag<SVGGElement, Node>()
|
||||
.on('drag', callback)
|
||||
.on('end', (n: Node) => {
|
||||
const e: D3DragEvent<SVGGElement, Node, Node> = event;
|
||||
self.onNodeDragged.emit(new NodeDragged(e, n));
|
||||
});
|
||||
};
|
||||
|
||||
if (this.draggingEnabled) {
|
||||
node_merge.call(dragging());
|
||||
}
|
||||
|
||||
nodes_selection
|
||||
node
|
||||
.exit()
|
||||
.remove();
|
||||
}
|
||||
|
||||
private selectNode(view: SVGSelection, node: Node) {
|
||||
return view.selectAll<SVGGElement, Node>(`g.node[node_id="${node.node_id}"]`);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
[drawings]="drawings"
|
||||
[width]="project.scene_width"
|
||||
[height]="project.scene_height"
|
||||
[selection-manager]="selectionManager"
|
||||
[show-interface-labels]="project.show_interface_labels"
|
||||
[selection-tool]="tools.selection"
|
||||
[moving-tool]="tools.moving"
|
||||
|
@ -29,6 +29,7 @@ import { ProgressService } from "../../common/progress/progress.service";
|
||||
import { MapChangeDetectorRef } from '../../cartography/services/map-change-detector-ref';
|
||||
import { NodeContextMenu, NodeDragged } from '../../cartography/events/nodes';
|
||||
import { LinkCreated } from '../../cartography/events/links';
|
||||
import { NodeWidget } from '../../cartography/widgets/node';
|
||||
|
||||
|
||||
@Component({
|
||||
@ -74,6 +75,7 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
private progressService: ProgressService,
|
||||
private projectWebServiceHandler: ProjectWebServiceHandler,
|
||||
private mapChangeDetectorRef: MapChangeDetectorRef,
|
||||
private nodeWidget: NodeWidget,
|
||||
protected nodesDataSource: NodesDataSource,
|
||||
protected linksDataSource: LinksDataSource,
|
||||
protected drawingsDataSource: DrawingsDataSource,
|
||||
@ -188,9 +190,9 @@ export class ProjectMapComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
setUpMapCallbacks(project: Project) {
|
||||
this.mapChild.graphLayout.getNodesWidget().setDraggingEnabled(!this.readonly);
|
||||
this.nodeWidget.setDraggingEnabled(!this.readonly);
|
||||
|
||||
const onContextMenu = this.mapChild.graphLayout.getNodesWidget().onContextMenu.subscribe((eventNode: NodeContextMenu) => {
|
||||
const onContextMenu = this.nodeWidget.onContextMenu.subscribe((eventNode: NodeContextMenu) => {
|
||||
this.nodeContextMenu.open(
|
||||
eventNode.node,
|
||||
eventNode.event.clientY,
|
||||
|
Loading…
x
Reference in New Issue
Block a user