2020-10-25 20:14:51 +00:00
|
|
|
import * as React from 'react';
|
2020-10-29 18:20:42 +00:00
|
|
|
import { Button, ListGroup } from 'react-bootstrap';
|
|
|
|
import { v4 as generateID } from 'uuid';
|
|
|
|
import { deepClone, rgetattr, rsetattr } from '../../../lib/helpers/objectMethods';
|
2020-10-30 18:30:59 +00:00
|
|
|
import DynamicLayout, { IDynamicLayoutProps } from '../layout/dynamicLayout';
|
2020-10-29 18:20:42 +00:00
|
|
|
import { ILayout } from '../layout/interfaces/ILayout';
|
2020-10-30 18:30:59 +00:00
|
|
|
import { ISelection } from '../layout/interfaces/ISelection';
|
2020-10-29 18:20:42 +00:00
|
|
|
import { ITab } from '../layout/interfaces/ITab';
|
2020-10-30 18:30:59 +00:00
|
|
|
import Layout from '../layout/layout';
|
|
|
|
|
2020-10-25 20:14:51 +00:00
|
|
|
// Import Addons:
|
|
|
|
import { makeMeMultiSelect } from './addons/selectionbox.extension';
|
2020-10-29 18:20:42 +00:00
|
|
|
import { generateDefaultSelection } from './defaults/default.elements';
|
|
|
|
import { generateGraphOptions } from './defaults/default.graph-options';
|
|
|
|
import { defaultHotkeys } from './defaults/default.hotkeys';
|
|
|
|
import { defaultToolbar } from './defaults/default.toolbar';
|
|
|
|
// Graph related imports:
|
|
|
|
import { UndoRedoGraph } from './graph';
|
2020-10-30 18:30:59 +00:00
|
|
|
import { adaptIDS, adaptPositions, getSelectedElements } from './helpers/data.handlers';
|
2020-10-29 18:20:42 +00:00
|
|
|
import { IBaseEdgeOptions } from './interfaces/IBaseEdgeOptions';
|
|
|
|
import { IBaseNodeOptions } from './interfaces/IBaseNodeOptions';
|
2020-10-30 18:30:59 +00:00
|
|
|
import { IBasicTemplate } from './interfaces/IBasicTemplate';
|
2020-10-29 18:20:42 +00:00
|
|
|
import { IEdgeTypeDefinition } from './interfaces/IEdgeTypeDefinition';
|
|
|
|
import { IUndoRedoGraph } from './interfaces/IGraph';
|
|
|
|
import { IGraphCallbackData } from './interfaces/IGraphCallbackData';
|
|
|
|
import { IGraphTemplate } from './interfaces/IGraphTemplate';
|
|
|
|
import { DEFAULT_NETWORK, INetwork } from './interfaces/INetwork';
|
|
|
|
import { IVisjsOptions } from './interfaces/IVisjsOptions';
|
2020-10-30 18:30:59 +00:00
|
|
|
import Selection, { SelectionProps } from '../layout/selection';
|
|
|
|
import DynamicRenderer from '../dynamic/dynamicRenderer';
|
|
|
|
import TabEntry, { ITabProps } from '../layout/tabs';
|
|
|
|
import { IJsonSchema } from '../../types/IJSONSchema';
|
|
|
|
import { ITabEntry } from '../layout/interfaces/ITabEntry';
|
|
|
|
import { parseWithFunctions, stringifyWithFunctions } from '../../../lib/helpers/jsonMethods';
|
|
|
|
import { readDataFromClipboard, writeToClipboard } from '../helpers/clipboard';
|
|
|
|
import { Network, __esModule } from '../../visjs/vis';
|
|
|
|
import { DH_UNABLE_TO_CHECK_GENERATOR } from 'constants';
|
|
|
|
import {getCurrentThemeColors} from '../helpers/colors';
|
|
|
|
import {generateColors } from './helpers/color.theme';
|
2020-11-01 12:01:57 +00:00
|
|
|
import { IEvent } from './interfaces/IEvents';
|
|
|
|
import { resolve } from 'path';
|
2020-10-30 18:30:59 +00:00
|
|
|
|
|
|
|
export interface GraphicalEditorComponentProps<N, E> {
|
2020-10-29 18:20:42 +00:00
|
|
|
graphOptions?: IVisjsOptions;
|
|
|
|
network?: INetwork<N, E>;
|
|
|
|
addEdgeCallback?: (edgeData: E, callback: (edgeData: E) => void) => void;
|
|
|
|
editOnSelect?: boolean;
|
|
|
|
editOnChange?: boolean;
|
|
|
|
parseFunctions?: boolean;
|
|
|
|
enableContextMenu?: boolean;
|
|
|
|
enableEditing?: boolean;
|
|
|
|
hidePanelOnDeselect?: boolean;
|
|
|
|
hideToolbar?: boolean;
|
|
|
|
hideRightPanel?: boolean;
|
|
|
|
allowSelfConnection?: boolean;
|
|
|
|
allowDoubleConnections?: boolean;
|
|
|
|
edgeTypes?: IEdgeTypeDefinition<N, E>[];
|
|
|
|
showEdgeSelction?: boolean
|
2020-10-25 20:14:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export interface GraphicalEditorComponentState {
|
2020-10-26 07:39:34 +00:00
|
|
|
update: null
|
2020-10-25 20:14:51 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
class GraphicalEditorComponent<N, E> extends React.Component<GraphicalEditorComponentProps<N, E>, GraphicalEditorComponentState> {
|
2020-10-25 20:14:51 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The Element containing the Network.
|
|
|
|
*/
|
2020-10-30 18:30:59 +00:00
|
|
|
public Network: IUndoRedoGraph<N, E>;
|
|
|
|
public LayoutHandler: ILayout;
|
|
|
|
public TabHandler: ITabEntry;
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
public graphOptions: IVisjsOptions;
|
2020-10-30 18:30:59 +00:00
|
|
|
public theme: {
|
|
|
|
colors: {
|
|
|
|
primary: string;
|
|
|
|
secondary: string;
|
|
|
|
success: string;
|
|
|
|
info: string;
|
|
|
|
warning: string;
|
|
|
|
danger: string;
|
|
|
|
light: string;
|
|
|
|
dark: string;
|
|
|
|
};
|
|
|
|
font: {
|
|
|
|
size: number;
|
|
|
|
type: string;
|
|
|
|
};
|
|
|
|
}
|
2020-10-25 20:14:51 +00:00
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
2020-10-30 18:30:59 +00:00
|
|
|
this.theme = getCurrentThemeColors();
|
2020-10-25 20:14:51 +00:00
|
|
|
}
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
public tabs: { [index: string]: INetwork<N, E> } = {};
|
2020-10-25 20:14:51 +00:00
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
/**
|
|
|
|
* Function to add the Addons of the Graph:
|
|
|
|
* @param network
|
|
|
|
*/
|
|
|
|
public addGraphAddons(network: IUndoRedoGraph<N, E>) {
|
2020-10-25 20:14:51 +00:00
|
|
|
makeMeMultiSelect(network);
|
|
|
|
}
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
/**
|
|
|
|
* Function to add an Edge.
|
|
|
|
* @param edgeData
|
|
|
|
* @param callback
|
|
|
|
*/
|
|
|
|
protected async _addEdge(edgeData: IBaseEdgeOptions, callback: (edge: IBaseEdgeOptions) => void) {
|
|
|
|
|
|
|
|
// Defaulty prevent self connections.
|
|
|
|
if (this.props.allowSelfConnection || edgeData.from !== edgeData.to) {
|
|
|
|
|
|
|
|
let addEdge = true;
|
|
|
|
|
|
|
|
// If multiple Connections from one node to the same node are
|
|
|
|
// for bidden, test if there already exists such an edge.
|
|
|
|
if (!this.props.allowDoubleConnections) {
|
2020-10-30 18:30:59 +00:00
|
|
|
this.Network.edges.forEach((edge) => {
|
2020-10-29 18:20:42 +00:00
|
|
|
if (edge.from === edgeData.from && edge.to === edgeData.to) {
|
|
|
|
addEdge = false;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// If adding an Edge is allowed => Add the Edge.
|
|
|
|
if (addEdge) {
|
|
|
|
|
|
|
|
// Manually add an ID to the Edge
|
|
|
|
edgeData.id = generateID();
|
|
|
|
|
|
|
|
// Extract the From and To Node.
|
2020-10-30 18:30:59 +00:00
|
|
|
const from = this.Network.getNode(edgeData.from);
|
|
|
|
const to = this.Network.getNode(edgeData.to);
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
const _this = this;
|
|
|
|
const _addEdge = (type: IEdgeTypeDefinition<N, E>) => {
|
|
|
|
if (typeof type.customAddFunction == 'function') {
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
type.customAddFunction(from, to, _this.Network);
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
} else {
|
|
|
|
// Define the Edge Object
|
|
|
|
let edge = Object.assign(deepClone(type.visual), {
|
|
|
|
from: edgeData.from,
|
|
|
|
to: edgeData.to
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add the Edge.
|
|
|
|
callback(edge);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (edgeData.type) {
|
|
|
|
case 'automatic':
|
|
|
|
// Search for Possible Connection Types.
|
|
|
|
const possibleEdges: IEdgeTypeDefinition<N, E>[] = [];
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
for (const edgeType of this.props.edgeTypes || []) {
|
2020-10-29 18:20:42 +00:00
|
|
|
if ((edgeType.fromTypes.includes(from.type) || edgeType.fromTypes.length === 0) && (edgeType.toTypes.includes(to.type) || edgeType.toTypes.length === 0)) {
|
|
|
|
possibleEdges.push(edgeType);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test if Edges has been found
|
|
|
|
switch (possibleEdges.length) {
|
|
|
|
case 0:
|
2020-10-30 18:30:59 +00:00
|
|
|
this.LayoutHandler.showToast('No Matching Edge Type has been found. Please select the corresponding Edge Type manually', 'info');
|
2020-10-29 18:20:42 +00:00
|
|
|
return;
|
|
|
|
case 1:
|
|
|
|
// Define the New Edge.
|
|
|
|
return _addEdge(possibleEdges[0]);
|
|
|
|
default:
|
|
|
|
if (this.props.showEdgeSelction) {
|
|
|
|
// Create a Popup, on which the corresponding edge can be selected:
|
|
|
|
try {
|
2020-10-30 18:30:59 +00:00
|
|
|
const type: IEdgeTypeDefinition<N, E> = await this.LayoutHandler.getDialogData({
|
2020-10-29 18:20:42 +00:00
|
|
|
content: {
|
|
|
|
props: {
|
|
|
|
possibleEdges
|
|
|
|
},
|
|
|
|
component(props: {
|
|
|
|
possibleEdges: IEdgeTypeDefinition<N, E>[],
|
|
|
|
onSubmit: (data: IEdgeTypeDefinition<N, E>) => void;
|
|
|
|
onCancel: (error: any) => void
|
|
|
|
}) {
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
Select a possible edge by clicking on the item
|
|
|
|
<ListGroup>
|
|
|
|
{
|
|
|
|
props.possibleEdges.map((item, idx) => {
|
|
|
|
<ListGroup.Item key={idx} onClick={_ => props.onSubmit(item)}>{item.type}</ListGroup.Item>
|
|
|
|
})
|
|
|
|
}
|
|
|
|
</ListGroup>
|
|
|
|
<Button variant="danger" onClick={e => props.onCancel(e)}>Cancel </Button>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
header: 'Please select an edge-type!',
|
|
|
|
closeButton: true
|
|
|
|
});
|
|
|
|
|
|
|
|
// Add the Edge itself.
|
|
|
|
return _addEdge(type);
|
|
|
|
} catch (e) {
|
|
|
|
// The User has aborted the selection.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2020-10-30 18:30:59 +00:00
|
|
|
this.LayoutHandler.showToast('Mulitple Valid Edge Type has been found. Please select the corresponding Edge Type manually', 'info');
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
default:
|
2020-10-30 18:30:59 +00:00
|
|
|
for (const type of this.props.edgeTypes || []) {
|
2020-10-29 18:20:42 +00:00
|
|
|
if ((edgeData.type === type.type) && (type.fromTypes.includes(from.type) || type.fromTypes.length === 0) && (type.toTypes.includes(to.type) || type.toTypes.length === 0)) {
|
|
|
|
// Quit the Method
|
|
|
|
return _addEdge(type);
|
|
|
|
}
|
|
|
|
}
|
2020-10-30 18:30:59 +00:00
|
|
|
this.LayoutHandler.showToast('You are not allowed to connect the desired Nodes with this edges', 'error');
|
2020-10-29 18:20:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
2020-10-30 18:30:59 +00:00
|
|
|
this.LayoutHandler.showToast('Double Connections not allowed! You can not connect these elements again', 'error');
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
} else {
|
2020-10-30 18:30:59 +00:00
|
|
|
this.LayoutHandler.showToast('Self-Connections forbidden! You can not connect elements to itself', 'error');
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
protected async _editNodes(_template: IBasicTemplate<N,E>, mode: 'add' | 'update' = 'update') {
|
|
|
|
|
|
|
|
// Defaulty prevent self connections.
|
|
|
|
if (_template?.nodes.length > 0) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
if (_template.nodes[0].editorComponentSelector && false){
|
|
|
|
|
|
|
|
} else {
|
|
|
|
// Open up the corresponding Node Panel:
|
|
|
|
_template.nodes[0] = await this.LayoutHandler.getDialogData({
|
|
|
|
content: {
|
|
|
|
component: 'DynamicForm',
|
|
|
|
props: {
|
|
|
|
schema: {
|
|
|
|
type: 'object',
|
|
|
|
properties:{
|
|
|
|
label: {
|
|
|
|
description: 'Label of the Node',
|
|
|
|
type: 'string'
|
|
|
|
},
|
|
|
|
title: {
|
|
|
|
description: 'Tooltip, presented on Hovering',
|
|
|
|
type: 'string'
|
|
|
|
},
|
|
|
|
// color: {
|
|
|
|
// description: 'Color of the Node.',
|
|
|
|
// type: 'string'
|
|
|
|
// }
|
|
|
|
},
|
|
|
|
required: [
|
|
|
|
"label"
|
|
|
|
]
|
|
|
|
} as IJsonSchema,
|
|
|
|
uiSchema: {
|
|
|
|
// color: {
|
|
|
|
// "ui:widget": "color"
|
|
|
|
// }
|
|
|
|
},
|
|
|
|
data: Object.assign(generateColors(_template.nodes[0].shape, this.theme), _template.nodes[0])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
switch (mode) {
|
|
|
|
case 'add':
|
|
|
|
this.Network.addNode(_template.nodes);
|
|
|
|
this.Network.addEdge(_template.edges);
|
|
|
|
break;
|
|
|
|
case 'update':
|
|
|
|
this.Network.updateNode(_template.nodes);
|
|
|
|
this.Network.updateEdge(_template.edges);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tabs
|
|
|
|
this.tabs[this.TabSettings.tabs.active] = this.Network.getData();
|
|
|
|
} catch(error) {
|
|
|
|
console.log(error)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.LayoutHandler.showToast('Please Select a Node Before.', 'error');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public initEditor(ref: React.RefObject<any>) {
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
if (process.browser){
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Based on the Provided Options =>
|
|
|
|
// select the corresponding graph options,
|
|
|
|
// either use th provided one or use the
|
|
|
|
// default options.
|
|
|
|
if (this.props.graphOptions) {
|
|
|
|
this.graphOptions = this.props.graphOptions;
|
|
|
|
} else {
|
|
|
|
this.graphOptions = generateGraphOptions();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!rgetattr(this.graphOptions, 'manipulation.enabled', false)) {
|
|
|
|
rsetattr(this.graphOptions, 'manipulation.enabled', false);
|
|
|
|
}
|
2020-10-30 18:30:59 +00:00
|
|
|
|
|
|
|
// Generate the Default Callback for adding an Edge
|
|
|
|
rsetattr(
|
|
|
|
this.graphOptions,
|
|
|
|
'manipulation.addEdge',
|
|
|
|
(data,callback) => {
|
|
|
|
_this._addEdge(data,callback).catch(console.error)
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
// Generate the Default Callback for adding a Node
|
|
|
|
rsetattr(
|
|
|
|
this.graphOptions,
|
|
|
|
'manipulation.addNode',
|
|
|
|
(data,callback) => {
|
|
|
|
_this._addNode(data).catch(console.error)
|
|
|
|
}
|
|
|
|
);
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
// Create the Network
|
2020-10-30 18:30:59 +00:00
|
|
|
this.Network = new UndoRedoGraph(ref, this.graphOptions);
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
// Add a new item if possible.
|
|
|
|
const editItemIfPossible = (params) => {
|
|
|
|
if (params.nodes.length === 1) {
|
|
|
|
// A Node was Selected. Figure out, whether it is a cluster or not.
|
|
|
|
const nodeID = params.nodes[0];
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
if (_this.Network.network.isCluster(nodeID)) {
|
2020-10-29 18:20:42 +00:00
|
|
|
// Uncluster the Nodes
|
2020-10-30 18:30:59 +00:00
|
|
|
_this.Network.network.openCluster(nodeID);
|
2020-10-29 18:20:42 +00:00
|
|
|
} else {
|
|
|
|
// Open up the Settings Tab
|
2020-10-30 18:30:59 +00:00
|
|
|
const nodes = getSelectedElements(_this.Network, false);
|
|
|
|
_this._editNodes({
|
|
|
|
edges: [],
|
|
|
|
nodes,
|
|
|
|
type: 'element'
|
|
|
|
});
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
} else if (params.edges.length === 1) {
|
|
|
|
// A Node was Selected. Figure out, whether it is a cluster or not.
|
2020-10-30 18:30:59 +00:00
|
|
|
const edges = _this.Network.data.edges.get(params.edges[0]);
|
2020-10-29 18:20:42 +00:00
|
|
|
_this.updateEdges(edges);
|
|
|
|
}
|
|
|
|
}
|
2020-10-30 18:30:59 +00:00
|
|
|
|
|
|
|
if (!this.Network){
|
|
|
|
throw Error('Something went wrong')
|
|
|
|
}
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
// Make the Default behaviour adding new Nodes
|
2020-10-30 18:30:59 +00:00
|
|
|
this.Network.on('doubleClick', (params) => {
|
|
|
|
if (_this.props.enableEditing || true) {
|
2020-10-29 18:20:42 +00:00
|
|
|
// Test if no Element was Selected
|
|
|
|
if (params.nodes.length === 0 && params.edges.length === 0) {
|
|
|
|
// If so, just add the new Element
|
2020-10-30 18:30:59 +00:00
|
|
|
_this._addNode(params.pointer.canvas);
|
2020-10-29 18:20:42 +00:00
|
|
|
} else {
|
|
|
|
editItemIfPossible(params);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
this.Network.on('oncopy', event => {
|
2020-10-29 18:20:42 +00:00
|
|
|
try {
|
2020-10-30 18:30:59 +00:00
|
|
|
_this.LayoutHandler.showToast('Copied');
|
|
|
|
writeToClipboard(stringifyWithFunctions(_this.createTemplateOfSelectedElements()))
|
2020-10-29 18:20:42 +00:00
|
|
|
} catch (e) {
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
this.Network.on('onpaste', async event => {
|
2020-10-29 18:20:42 +00:00
|
|
|
try {
|
2020-10-30 18:30:59 +00:00
|
|
|
const data = await readDataFromClipboard();
|
|
|
|
const mousePosition = _this.Network.network.DOMtoCanvas({
|
|
|
|
x: _this.LayoutHandler.currentMousePosition.offsetX,
|
|
|
|
y: _this.LayoutHandler.currentMousePosition.offsetY
|
|
|
|
})
|
|
|
|
_this.paste(parseWithFunctions(data), mousePosition, false);
|
|
|
|
_this.LayoutHandler.showToast('Pasted');
|
2020-10-29 18:20:42 +00:00
|
|
|
} catch (e) {
|
2020-10-30 18:30:59 +00:00
|
|
|
_this.LayoutHandler.showToast('Failed Pasting','error');
|
|
|
|
console.error(e)
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
2020-10-30 18:30:59 +00:00
|
|
|
});
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
// // Make shure the Sidepanel is working correctly
|
2020-10-30 18:30:59 +00:00
|
|
|
this.Network.on('select', (params) => {
|
|
|
|
// if (_this.graphOptions.enableEditing){
|
|
|
|
// if (_this.graphOptions.editOnSelect && _this.graphOptions.hidePanelOnDeselect && params.nodes.length === 0 && params.edges.length === 0){
|
|
|
|
// _this.layout.panels.right.hide();
|
|
|
|
// } else if (_this.graphOptions.editOnSelect){
|
|
|
|
// editItemIfPossible(params);
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
});
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
// enableClusterPreview(this);
|
|
|
|
|
|
|
|
// this.network.addNode(this._nodes);
|
|
|
|
// this.network.addEdge(this._edges);
|
|
|
|
|
|
|
|
// editor.resize();
|
2020-10-30 18:30:59 +00:00
|
|
|
console.log('TABS',this.tabs)
|
|
|
|
this.Network.loadData(this.tabs[this.TabSettings?.tabs?.active] || DEFAULT_NETWORK())
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
// Add all Addons as required.
|
2020-10-30 18:30:59 +00:00
|
|
|
this.addGraphAddons(this.Network);
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
return this.Network;
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* Function, which is used to provide a
|
|
|
|
*/
|
|
|
|
public createTemplateOfSelectedElements() {
|
|
|
|
const selected: IBasicTemplate<N,E> = {
|
|
|
|
nodes: [],
|
|
|
|
edges: [],
|
|
|
|
type: 'element'
|
|
|
|
};
|
|
|
|
|
|
|
|
const selection = this.Network.network.getSelection(true);
|
|
|
|
|
|
|
|
selected.nodes = deepClone(this.Network.nodes.filter(item => selection.nodes.indexOf(item.id) !== -1));
|
|
|
|
selected.edges = deepClone(this.Network.edges.filter(item => selection.edges.indexOf(item.id) !== -1));
|
|
|
|
|
|
|
|
return selected;
|
|
|
|
}
|
|
|
|
|
|
|
|
public paste(
|
|
|
|
template: IBasicTemplate<N,E>,
|
|
|
|
position: {
|
|
|
|
x: number,
|
|
|
|
y: number
|
|
|
|
},
|
|
|
|
useExistingNodesForEdges: boolean) {
|
|
|
|
|
|
|
|
const data = adaptPositions(adaptIDS(template, useExistingNodesForEdges), position);
|
|
|
|
|
|
|
|
this.Network.addNode(data.nodes);
|
|
|
|
this.Network.addEdge(data.edges);
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to Update the Data of a Node.
|
|
|
|
* @param selection The Selected Node.
|
|
|
|
*/
|
|
|
|
public async updateNode(selection: Array<IBaseNodeOptions<N>>) {
|
|
|
|
const _self = this;
|
|
|
|
|
|
|
|
/** Extract the Component, which should be used in the Prompt */
|
|
|
|
let componentSelector = 'default';
|
|
|
|
if (selection.length > 0 && selection[0].editorComponentSelector) {
|
|
|
|
componentSelector = selection[0].editorComponentSelector;
|
|
|
|
}
|
|
|
|
|
|
|
|
// const comp = this.editPanelDict.nodes[componentSelector];
|
|
|
|
|
|
|
|
// if (comp) {
|
|
|
|
// /** Open the Window, with the Edit-Prompt */
|
|
|
|
// this.openEditInterface(this.editPanelDict.nodes[componentSelector], {
|
|
|
|
// inputTemplate: {
|
|
|
|
// nodes: selection,
|
|
|
|
// edges: [],
|
|
|
|
// type: 'elements'
|
|
|
|
// },
|
|
|
|
// },
|
|
|
|
// 'Edit Node',
|
|
|
|
// (data) => {
|
|
|
|
// _self.network.updateNode(data.template.nodes);
|
|
|
|
// });
|
|
|
|
// } else {
|
|
|
|
// this.layout.showToast('Editor is Trying to open an Unkown Edit-Component', 'error');
|
|
|
|
// }
|
|
|
|
}
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
protected async _addNode(pos: { x: number, y: number }) {
|
|
|
|
// Defaulty prevent self connections.
|
|
|
|
if (this._template?.nodes.length > 0) {
|
|
|
|
try {
|
|
|
|
// Adapt its IDs. (Creates a Copy)
|
|
|
|
let _template = adaptIDS(this._template);
|
|
|
|
// Adapt its Position
|
|
|
|
_template = adaptPositions(_template, pos);
|
|
|
|
|
|
|
|
// Remove the Initial Label:
|
|
|
|
delete _template.nodes[0].label;
|
|
|
|
|
|
|
|
await this._editNodes(_template, 'add');
|
|
|
|
} catch(error) {
|
|
|
|
console.error(error)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
this.LayoutHandler.showToast('Please Select a Node Before.', 'error');
|
|
|
|
}
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
public EditorSettings: GraphicalEditorComponentProps<N, E>
|
|
|
|
public LayoutSettings: IDynamicLayoutProps<IGraphCallbackData<N,E>>
|
|
|
|
public TabSettings: ITabProps;
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
protected _template: IBasicTemplate<N,E>
|
|
|
|
public selectTemplate(template: IBasicTemplate<N,E>){
|
|
|
|
this._template = template;
|
|
|
|
}
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
public initializeGraph() {
|
2020-10-25 20:14:51 +00:00
|
|
|
|
|
|
|
const _this = this;
|
|
|
|
|
|
|
|
// Assign a default Network:
|
2020-10-30 18:30:59 +00:00
|
|
|
const networkToRender: INetwork<N,E> = this.props.network || this.tabs[this.TabSettings?.tabs?.active] || DEFAULT_NETWORK();
|
2020-10-29 18:20:42 +00:00
|
|
|
|
2020-10-25 20:14:51 +00:00
|
|
|
// Initally store the Network to Render
|
|
|
|
this.tabs[networkToRender.id] = networkToRender;
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
this.TabSettings = {
|
|
|
|
onMount(item){
|
|
|
|
_this.TabHandler = item;
|
2020-10-25 20:14:51 +00:00
|
|
|
},
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
async onNewTab() {
|
2020-10-25 20:14:51 +00:00
|
|
|
// Function to create a new tab.
|
2020-10-26 07:39:34 +00:00
|
|
|
try {
|
2020-10-30 18:30:59 +00:00
|
|
|
|
|
|
|
if (_this.TabHandler.tabs.length == 0) {
|
|
|
|
const tab = {
|
|
|
|
delteable: true,
|
|
|
|
id: generateID(),
|
|
|
|
label: 'Unsaved Content'
|
|
|
|
};
|
|
|
|
|
|
|
|
await _this.TabHandler.createTab(tab);
|
|
|
|
|
|
|
|
// Store the Content of the tab:
|
|
|
|
_this.tabs[tab.id] = _this.Network.getData();
|
|
|
|
}
|
|
|
|
|
|
|
|
const label = await _this.LayoutHandler.getDialogData({
|
2020-10-26 07:39:34 +00:00
|
|
|
header: 'Enter Network-Name',
|
|
|
|
content: {
|
|
|
|
component: 'DynamicForm',
|
|
|
|
props: {
|
|
|
|
schema: {
|
|
|
|
type: "string",
|
|
|
|
description: "Name of the Element"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
2020-10-29 18:20:42 +00:00
|
|
|
|
2020-10-26 07:39:34 +00:00
|
|
|
const id = Date.now().toString();
|
|
|
|
// const label = id;
|
2020-10-29 18:20:42 +00:00
|
|
|
|
|
|
|
_this.tabs[id] = DEFAULT_NETWORK();
|
2020-10-26 07:39:34 +00:00
|
|
|
|
|
|
|
return {
|
|
|
|
id,
|
|
|
|
label,
|
|
|
|
delteable: true
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
return false;
|
2020-10-25 20:14:51 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
async onTabSelect(oldTab: ITab, newTab: ITab) {
|
2020-10-30 18:30:59 +00:00
|
|
|
if (_this.Network) {
|
|
|
|
// Contains the Main Logic for selecting another tab
|
|
|
|
// Therefore
|
|
|
|
if (oldTab) {
|
|
|
|
_this.tabs[oldTab.id] = _this.Network.getData();
|
|
|
|
}
|
|
|
|
// Use the content of the new Tab as rendering:
|
|
|
|
_this.Network.loadData(_this.tabs[newTab.id]);
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
return true;
|
2020-10-25 20:14:51 +00:00
|
|
|
},
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
async onTabDelete(tab: ITab, force: boolean) {
|
2020-10-25 20:14:51 +00:00
|
|
|
// A Tab should be deleted
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
if (force) {
|
2020-10-29 18:20:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-10-26 08:54:30 +00:00
|
|
|
try {
|
2020-10-30 18:30:59 +00:00
|
|
|
const saveData = await _this.LayoutHandler.getDialogData<boolean>({
|
2020-10-26 08:54:30 +00:00
|
|
|
header: 'Close Tab',
|
|
|
|
content: {
|
|
|
|
component: (props) => {
|
|
|
|
return (<>
|
|
|
|
<p>
|
2020-10-29 18:20:42 +00:00
|
|
|
You are about to close the tab "{tab.label}". Do you want to save changes?
|
2020-10-30 18:30:59 +00:00
|
|
|
</p>
|
2020-10-29 18:20:42 +00:00
|
|
|
<Button variant="success" onClick={_ => props.onSubmit(true)}>Yes</Button>{' '}
|
|
|
|
<Button variant="danger" onClick={_ => props.onSubmit(false)}>No</Button>{' '}
|
|
|
|
<Button variant="secondary" onClick={_ => props.onCancel(new Error("Canceled"))}>Cancel</Button>{' '}
|
2020-10-26 08:54:30 +00:00
|
|
|
</>)
|
|
|
|
},
|
|
|
|
props: {}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// The variable save Data contains the info, whether the user wants to save or discard the changes
|
|
|
|
return true;
|
|
|
|
|
2020-10-29 18:20:42 +00:00
|
|
|
} catch (error) {
|
2020-10-26 08:54:30 +00:00
|
|
|
return false
|
|
|
|
}
|
2020-10-25 20:14:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async onNoTabSelected() {
|
|
|
|
// Nothing to Display!
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
if (_this.Network) {
|
|
|
|
console.log('No Tab')
|
|
|
|
// Use the content of the new Tab as rendering:
|
|
|
|
_this.Network.loadData(DEFAULT_NETWORK());
|
|
|
|
}
|
|
|
|
|
2020-10-25 20:14:51 +00:00
|
|
|
},
|
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
async onTabEdit(tab){
|
|
|
|
const label = await _this.LayoutHandler.getDialogData<string>({
|
|
|
|
header: 'Enter Network-Name',
|
|
|
|
content: {
|
|
|
|
component: 'DynamicForm',
|
|
|
|
props: {
|
|
|
|
schema: {
|
|
|
|
type: "string",
|
|
|
|
description: "Name of the Element"
|
|
|
|
},
|
|
|
|
data: tab.label
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
tab.label = label;
|
|
|
|
|
|
|
|
return tab;
|
2020-10-25 20:14:51 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
tabs: {
|
2020-10-30 18:30:59 +00:00
|
|
|
active: '-1',
|
|
|
|
allowNewTabs: true,
|
2020-10-29 18:20:42 +00:00
|
|
|
items: [],
|
2020-10-30 18:30:59 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
this.LayoutSettings = {
|
|
|
|
components: [
|
|
|
|
{
|
|
|
|
component() {
|
|
|
|
|
|
|
|
const graph = React.useRef(null);
|
|
|
|
// Similar to componentDidMount and componentDidUpdate:
|
|
|
|
// Createa a Function, that if mounted defines the Network.
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
|
|
// Define the Network.
|
|
|
|
_this.initEditor(graph)
|
|
|
|
|
|
|
|
// Return a Function, that will destroy the network.
|
|
|
|
return () => {
|
|
|
|
_this.tabs[_this.TabSettings.tabs.active] = _this.Network.getData();
|
|
|
|
// Destroy the Network.
|
|
|
|
_this.Network.destroy();
|
|
|
|
_this.Network = null;
|
|
|
|
|
|
|
|
}
|
|
|
|
});
|
2020-10-26 07:39:34 +00:00
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
return (<>
|
|
|
|
{React.createElement(TabEntry, { ..._this.TabSettings })}
|
|
|
|
<div ref={graph} style={{ height: '100%' }} onMouseEnter={e => {
|
|
|
|
// Enable Hotkeys
|
|
|
|
_this.LayoutHandler.hotkeysEnabled = true;
|
|
|
|
}} onMouseLeave={e => {
|
|
|
|
// Disable Hotkeys
|
|
|
|
_this.LayoutHandler.hotkeysEnabled = false;
|
|
|
|
}}></div>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
},
|
|
|
|
gridSettings: {
|
|
|
|
h: 5,
|
|
|
|
w: 10,
|
|
|
|
x: 5,
|
|
|
|
y: 0,
|
|
|
|
minW: 3,
|
|
|
|
minH: 3,
|
|
|
|
},
|
|
|
|
id: 'main',
|
|
|
|
label: 'Editor',
|
|
|
|
props: {
|
|
|
|
},
|
|
|
|
visible: true,
|
|
|
|
bg: 'light',
|
|
|
|
text: 'dark'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
component(props: {selection: ISelection<IBasicTemplate<N,E>>}){
|
|
|
|
return (<Selection<IBasicTemplate<N,E>> selection={props.selection} allowUserSelect={true} onItemSelected={template =>_this.selectTemplate(template)}></Selection>)
|
|
|
|
},
|
|
|
|
gridSettings: {
|
|
|
|
h: 5,
|
|
|
|
w: 4,
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
maxW: 5,
|
|
|
|
},
|
|
|
|
id: 'selection',
|
|
|
|
label: 'Selection',
|
|
|
|
props: {
|
|
|
|
selection: generateDefaultSelection()
|
|
|
|
},
|
|
|
|
hideable: true,
|
|
|
|
bg:'light',
|
|
|
|
visible: true,
|
|
|
|
text: 'dark',
|
|
|
|
showLabel: true,
|
|
|
|
},
|
|
|
|
{
|
2020-11-01 12:01:57 +00:00
|
|
|
component() {
|
|
|
|
const [img, setImg] = React.useState();
|
|
|
|
const [pos, setPos] = React.useState<{left: number, top: number, height: number, width: number}>({
|
|
|
|
left: 0,
|
|
|
|
top: 0,
|
|
|
|
width: 0,
|
|
|
|
height: 0,
|
|
|
|
});
|
|
|
|
const [style, setStyle] = React.useState<any>();
|
|
|
|
const radar = React.useRef(null);
|
|
|
|
const image = React.useRef(null);
|
|
|
|
|
|
|
|
|
|
|
|
// Similar to componentDidMount and componentDidUpdate:
|
|
|
|
// Createa a Function, that if mounted subscribe to changes
|
|
|
|
// of the Nework.
|
|
|
|
React.useEffect(() => {
|
|
|
|
|
|
|
|
const wait = () => {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
_this.Network.once('afterDrawing', resolve)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateImage = async () => {
|
|
|
|
|
|
|
|
// Store the current View Settings
|
|
|
|
const scale = _this.Network.network.getScale();
|
|
|
|
const translate = _this.Network.network.getViewPosition();
|
|
|
|
|
|
|
|
// Zoom out for the Image.
|
|
|
|
_this.Network.fit({animation: false});
|
|
|
|
|
|
|
|
// Wait for the Network to Redraw
|
|
|
|
await wait();
|
|
|
|
|
|
|
|
const canvas = _this.Network.network.body.container.getElementsByTagName('canvas')[0];
|
|
|
|
const imgData = canvas.toDataURL('image/png');
|
|
|
|
setImg(imgData);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract the Widht and Height
|
|
|
|
*/
|
|
|
|
const {
|
|
|
|
clientWidth,
|
|
|
|
clientHeight
|
|
|
|
} = _this.Network.network.body.container;
|
|
|
|
|
|
|
|
// Store the Position.
|
|
|
|
setPos({
|
|
|
|
left: translate.x - clientWidth/ scale / 2,
|
|
|
|
top: translate.y - clientHeight/ scale / 2,
|
|
|
|
width: clientWidth/ scale,
|
|
|
|
height: clientHeight / scale,
|
|
|
|
});
|
|
|
|
|
|
|
|
// Use inital View
|
|
|
|
_this.Network.network.moveTo({
|
|
|
|
position: translate,
|
|
|
|
scale: scale,
|
|
|
|
animation: false
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
const getScaling = (start,size, current, scale = true) => {
|
|
|
|
if (scale){
|
|
|
|
return Math.min(1, Math.max(0, (current - start) / size));
|
|
|
|
}
|
|
|
|
return (current - start) / size;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Function to draw the current Positon of the View.
|
|
|
|
*/
|
|
|
|
const drawRadar = () => {
|
|
|
|
if (radar?.current && image?.current){
|
|
|
|
|
|
|
|
const displayedWidth = image.current.clientWidth;
|
|
|
|
const displayedHeight = image.current.clientHeight;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Extract the Widht and Height
|
|
|
|
*/
|
|
|
|
const {
|
|
|
|
clientWidth,
|
|
|
|
clientHeight
|
|
|
|
} = _this.Network.network.body.container;
|
|
|
|
|
|
|
|
// Determine visible Nodes:
|
|
|
|
const scale = _this.Network.network.getScale();
|
|
|
|
const translate = _this.Network.network.getViewPosition();
|
|
|
|
|
|
|
|
// Contains the Left Position of the View.
|
|
|
|
// The following Transformation is used:
|
|
|
|
// _____
|
|
|
|
// |(top,left)-Original = 0,0
|
|
|
|
// | ___
|
|
|
|
// | |(top, left of the View)
|
|
|
|
//
|
|
|
|
|
|
|
|
const current = {
|
|
|
|
left: translate.x - clientWidth/ scale / 2,
|
|
|
|
top: translate.y - clientHeight/ scale / 2,
|
|
|
|
width: clientWidth/ scale,
|
|
|
|
height: clientHeight / scale,
|
|
|
|
};
|
|
|
|
|
|
|
|
const top = getScaling(pos.top, pos.height, current.top);
|
|
|
|
const bottom = getScaling(pos.top, pos.height, current.top + current.height);
|
|
|
|
const height = bottom-top;
|
|
|
|
const left = getScaling(pos.left, pos.width, current.left);
|
|
|
|
const right = getScaling(pos.left, pos.width, current.left + current.width)
|
|
|
|
const width = right - left;
|
|
|
|
|
|
|
|
setStyle({
|
|
|
|
position: 'absolute',
|
|
|
|
backgroundColor: 'rgba(16, 84, 154, 0.26)',
|
|
|
|
top: (image.current.offsetTop + top * displayedHeight).toString() + 'px',
|
|
|
|
left: (image.current.offsetLeft + left * displayedWidth).toString() + 'px',
|
|
|
|
height: (height* displayedHeight).toString() + 'px',
|
|
|
|
width: (width * displayedWidth).toString() + 'px'
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
const listeners: Partial<{[K in IEvent]: any}> = {
|
|
|
|
'dataUpdate': updateImage,
|
|
|
|
'afterDrawing' : drawRadar
|
|
|
|
}
|
|
|
|
|
|
|
|
let subscriptions: Array<() => void> = []
|
|
|
|
|
|
|
|
if (_this.Network) {
|
|
|
|
for (const key in listeners){
|
|
|
|
subscriptions.push(_this.Network.on(key as IEvent, listeners[key]));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Return a Function, that will destroy the network.
|
|
|
|
return () => {
|
|
|
|
for (const unsubscribe of subscriptions){
|
|
|
|
unsubscribe();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div style={{overflow: 'hidden'}}>
|
|
|
|
<img ref={image} src={img} style={{maxHeight:'100%', maxWidth: '100%'}}></img>
|
|
|
|
<div ref={radar} style={style}></div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
},
|
2020-10-30 18:30:59 +00:00
|
|
|
gridSettings: {
|
|
|
|
h: 3,
|
|
|
|
w: 3,
|
|
|
|
x: 0,
|
|
|
|
y: 5,
|
|
|
|
maxW: 5,
|
|
|
|
},
|
|
|
|
id: 'minimap',
|
|
|
|
label: 'Minimap',
|
|
|
|
props: {},
|
|
|
|
hideable: true,
|
|
|
|
bg:'light',
|
|
|
|
visible: false,
|
|
|
|
text: 'dark',
|
|
|
|
showLabel: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
component(){
|
|
|
|
return (<>Preview</>)
|
|
|
|
},
|
|
|
|
gridSettings: {
|
|
|
|
h: 3,
|
|
|
|
w: 3,
|
|
|
|
x: 3,
|
|
|
|
y: 5,
|
|
|
|
},
|
|
|
|
id: 'preview',
|
|
|
|
label: 'Preview',
|
|
|
|
props: {},
|
|
|
|
hideable: true,
|
|
|
|
bg:'light',
|
|
|
|
visible: false,
|
|
|
|
text: 'dark'
|
|
|
|
},
|
|
|
|
],
|
|
|
|
layoutSettings: {
|
|
|
|
width: process.browser ? window.innerWidth : 1920,
|
|
|
|
autoSize: false,
|
|
|
|
preventCollision: false,
|
|
|
|
cols: 15,
|
|
|
|
compactType: 'horizontal',
|
|
|
|
onLayoutChange(){
|
|
|
|
if (_this.Network) {
|
|
|
|
_this.Network.resize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
generateData(){
|
2020-10-29 18:20:42 +00:00
|
|
|
// Get the Current Selection.
|
2020-10-30 18:30:59 +00:00
|
|
|
const selection = _this.Network.network.getSelection(true);
|
2020-10-26 07:39:34 +00:00
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
const ret: IGraphCallbackData<N,E> = {
|
|
|
|
network: _this.Network,
|
|
|
|
layout: _this.LayoutHandler,
|
2020-10-29 18:20:42 +00:00
|
|
|
selectedEdges: selection.edges,
|
2020-10-30 18:30:59 +00:00
|
|
|
selectedNodes: selection.nodes,
|
|
|
|
tabs: _this.TabHandler
|
|
|
|
};
|
|
|
|
|
|
|
|
Object.defineProperty(ret, 'network', {
|
|
|
|
get(){
|
|
|
|
return _this.Network;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
Object.defineProperty(ret, 'layout', {
|
|
|
|
get(){
|
|
|
|
return _this.LayoutHandler;
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
return ret
|
|
|
|
},
|
|
|
|
onResize() {
|
|
|
|
if (_this.Network) {
|
|
|
|
_this.Network.resize();
|
2020-10-29 18:20:42 +00:00
|
|
|
}
|
|
|
|
},
|
2020-10-30 18:30:59 +00:00
|
|
|
onMount(layout){
|
|
|
|
_this.LayoutHandler = layout;
|
|
|
|
},
|
2020-10-29 18:20:42 +00:00
|
|
|
toolbar: defaultToolbar(),
|
|
|
|
hotkeys: defaultHotkeys()
|
2020-10-30 18:30:59 +00:00
|
|
|
}
|
2020-10-26 07:39:34 +00:00
|
|
|
|
2020-10-30 18:30:59 +00:00
|
|
|
return this.LayoutSettings;
|
2020-10-26 07:39:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
public requestRerender() {
|
|
|
|
this.setState({
|
|
|
|
update: null
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
public componentDidMount() {
|
|
|
|
const _this = this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public render() {
|
2020-10-30 18:30:59 +00:00
|
|
|
this.initializeGraph();
|
|
|
|
return React.createElement(DynamicLayout, {... this.LayoutSettings});
|
2020-10-25 20:14:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default GraphicalEditorComponent;
|