498 lines
17 KiB
TypeScript
498 lines
17 KiB
TypeScript
import * as React from 'react';
|
|
import { LayoutProps, ModalSettings } from '../layout/layout';
|
|
import Layout from '../layout/layout';
|
|
|
|
import { IVisjsOptions } from './interfaces/IVisjsOptions';
|
|
import { INetwork } from './interfaces/INetwork';
|
|
import { IUndoRedoGraph } from './interfaces/IGraph';
|
|
|
|
// Graph related imports:
|
|
import { UndoRedoGraph } from './graph';
|
|
import { generateGraphOptions } from './defaults/default.graph-options';
|
|
|
|
// Import Addons:
|
|
import { makeMeMultiSelect } from './addons/selectionbox.extension';
|
|
import DynamicRenderer from '../dynamic/dynamicRenderer';
|
|
import { rgetattr, rsetattr } from '../../../lib/helpers/objectMethods';
|
|
|
|
|
|
export interface GraphicalEditorComponentProps<N,E> extends Partial<LayoutProps<any>>{
|
|
options?: IVisjsOptions
|
|
network?: INetwork<N,E>
|
|
}
|
|
|
|
export interface GraphicalEditorComponentState {
|
|
update: null
|
|
}
|
|
|
|
|
|
class GraphicalEditorComponent<N,E> extends React.Component<GraphicalEditorComponentProps<N,E>, GraphicalEditorComponentState> {
|
|
|
|
/**
|
|
* The Element containing the Network.
|
|
*/
|
|
public network: IUndoRedoGraph<N,E>;
|
|
public layout: Layout<any>;
|
|
|
|
constructor(props) {
|
|
super(props);
|
|
}
|
|
|
|
public tabs: {[index: string]: INetwork<N,E>} = {};
|
|
|
|
public addGraphAddons(network: IUndoRedoGraph<N,E>){
|
|
makeMeMultiSelect(network);
|
|
}
|
|
|
|
// public async initEditor() {
|
|
// const _this = this;
|
|
|
|
// /** Generate the Default Callback for adding a Node */
|
|
// rsetattr(
|
|
// this._visjsOptions,
|
|
// 'manipulation.addEdge',
|
|
// generateAddFunction(_this, (data, callback) => {
|
|
|
|
// /** Test if an Edge-Template exists */
|
|
// if (_this.template.type === 'elements' && _this.template.edges.length === 1 && _this.template.nodes.length === 0) {
|
|
// const edge = Object.assign(
|
|
// _this.template.edges[0],
|
|
// data
|
|
// );
|
|
|
|
// if (typeof _this.options.addEdgeCallback === 'function') {
|
|
// _this.options.addEdgeCallback(edge, callback);
|
|
// } else {
|
|
// callback(edge);
|
|
// }
|
|
// }
|
|
// }
|
|
// )
|
|
// );
|
|
|
|
// if (!rgetattr(this._visjsOptions, 'manipulation.enabled', false)) {
|
|
// rsetattr(this._visjsOptions, 'manipulation.enabled', false);
|
|
// }
|
|
|
|
// const editor = initEditor<N,E,D, BaseGraphEditor<N,E,D>>({
|
|
// component: this,
|
|
// element: this.layout.panels.main,
|
|
// networkOptions: this.visjsOptions,
|
|
// renderMinimap: false
|
|
// });
|
|
|
|
// 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];
|
|
|
|
// if (_this.network.network.isCluster(nodeID)) {
|
|
// // Uncluster the Nodes
|
|
// _this.network.network.openCluster(nodeID);
|
|
// } else {
|
|
// // Open up the Settings Tab
|
|
// const selection = getSubElements(this.network, false);
|
|
// _this.updateNode(selection);
|
|
// }
|
|
// } else if (params.edges.length === 1) {
|
|
// // A Node was Selected. Figure out, whether it is a cluster or not.
|
|
// const edges = _this.network.data.edges.get(params.edges[0]);
|
|
// _this.updateEdges(edges);
|
|
// }
|
|
// }
|
|
|
|
// // Make the Default behaviour adding new Nodes
|
|
// this.network.on('doubleClick', (params) => {
|
|
// if (this.options.enableEditing){
|
|
// // Test if no Element was Selected
|
|
// if (params.nodes.length === 0 && params.edges.length === 0) {
|
|
// // If so, just add the new Element
|
|
// _this.addNode(params.pointer.canvas);
|
|
// } else {
|
|
// editItemIfPossible(params);
|
|
// }
|
|
// }
|
|
// });
|
|
|
|
// this.network.on('oncopy', event => {
|
|
// try {
|
|
|
|
// } catch (e) {
|
|
|
|
// }
|
|
// });
|
|
|
|
// this.network.on('onpaste', async event => {
|
|
// console.log(event)
|
|
// try {
|
|
// const data = await _this.readDataFromClipboard()
|
|
// _this.paste(parseWithFunctions(data), _this.network.network.DOMtoCanvas(_this.mousePos), false);
|
|
// _this.zemaService.showToast('success', 'Clipboard', 'Copied Content to Clipboard');
|
|
// } catch (e) {
|
|
// _this.zemaService.showToast('warning', 'Clipboard', 'Failed Copying To Clipboard');
|
|
// }
|
|
|
|
// });
|
|
|
|
// let _mousePosOnContextMenu = this.mousePos;
|
|
// let rightClickActions: IRigthClickActions<ICallbackData<N,E>> = [];
|
|
// const _contextHandler = (params) => {
|
|
// let nodeID = null;
|
|
// if (params.nodes.length === 1) {
|
|
// nodeID = _this.network.network.getNodeAt(params.pointer.DOM);
|
|
// if (!nodeID)
|
|
// nodeID = params.nodes[0];
|
|
// }
|
|
|
|
// let edgeID = null;
|
|
// if (params.edges.length === 1) {
|
|
// edgeID = params.edges[0];
|
|
// }
|
|
|
|
// // If nothing is selected, try to select the element,
|
|
// // which is underneath the Pointer
|
|
// if (params.nodes.length === 0) {
|
|
// nodeID = _this.network.network.getNodeAt(params.pointer.DOM);
|
|
|
|
// // Test if a Node is underneath the Pointer
|
|
// if (nodeID) {
|
|
// // Make shure the Element is selected
|
|
// _this.network.network.selectNodes([nodeID]);
|
|
// }
|
|
// }
|
|
|
|
// // If nothing is selected, try to select the element,
|
|
// // which is underneath the Pointer
|
|
// if (params.edges.length === 0) {
|
|
// edgeID = _this.network.network.getEdgeAt(params.pointer.DOM);
|
|
|
|
// // Test if a Node is underneath the Pointer
|
|
// if (edgeID) {
|
|
// // Make shure the Element is selected
|
|
// _this.network.network.selectEdges([edgeID]);
|
|
// }
|
|
// }
|
|
|
|
// // Test whether multiple Nodes or just a single Node is selected.
|
|
// if (nodeID) {
|
|
// // A single Node is selected
|
|
// rightClickActions = _this.contextMenuGenerator.node(_this, params.pointer.DOM, nodeID);
|
|
// } else if (params.nodes.length > 1) {
|
|
// // The Default Right-Clickmenu must be selected
|
|
// rightClickActions = _this.contextMenuGenerator.default(_this, params.pointer.DOM, params.nodes, params.edges);
|
|
// } else if (edgeID) {
|
|
// // Only 1 Edge is selected
|
|
// rightClickActions = _this.contextMenuGenerator.edge(_this, params.pointer.DOM, edgeID);
|
|
// } else {
|
|
// rightClickActions = _this.contextMenuGenerator.background(_this, params.pointer.DOM);
|
|
// }
|
|
// }
|
|
|
|
// this.network.on('oncontext', (params) => {
|
|
// if (_this.options.enableContextMenu){
|
|
// // Make shure the Context-Menu is only Opened, if
|
|
// // The event isnt prevented
|
|
// if (params.event.prevent) {
|
|
// return;
|
|
// }
|
|
|
|
// // Call creating the Menu Entries.
|
|
// _contextHandler(params)
|
|
|
|
// // Decide, whether an Element is selected or not
|
|
// params.event.preventDefault();
|
|
|
|
// // Store the current Position
|
|
// _mousePosOnContextMenu = _this.mousePos;
|
|
|
|
// // Check after a View seconds whether the Mouse has been move
|
|
// // setTimeout(_contextHandler, 200, params);
|
|
// setTimeout(() => {
|
|
// if (Math.abs(_this.mousePos.x - _mousePosOnContextMenu.x) < 5 && Math.abs(_this.mousePos.y - _mousePosOnContextMenu.y) < 5) {
|
|
// if (rightClickActions.length > 0) {
|
|
// _this.layout.openContextMenu(params.event, rightClickActions);
|
|
// } else {
|
|
// _this.network.network.unselectAll();
|
|
// }
|
|
// } else {
|
|
// rightClickActions = [];
|
|
// }
|
|
// }, 200)
|
|
// }
|
|
// });
|
|
|
|
// // Make shure the Sidepanel is working correctly
|
|
// this.network.on('select', (params) => {
|
|
// if (_this.options.enableEditing){
|
|
// if (_this.options.editOnSelect && _this.options.hidePanelOnDeselect && params.nodes.length === 0 && params.edges.length === 0){
|
|
// _this.layout.panels.right.hide();
|
|
// } else if (_this.options.editOnSelect){
|
|
// editItemIfPossible(params);
|
|
// }
|
|
// }
|
|
// });
|
|
|
|
// enableClusterPreview(this);
|
|
|
|
// this.network.addNode(this._nodes);
|
|
// this.network.addEdge(this._edges);
|
|
|
|
// editor.resize();
|
|
// }
|
|
|
|
public initializeGraph(){
|
|
|
|
const _this = this;
|
|
|
|
// Assign a default Network:
|
|
const networkToRender = this.props.network || {
|
|
nodes: [],
|
|
edges: [],
|
|
clusters: [],
|
|
version: "20200713",
|
|
name: "new",
|
|
id: "new"
|
|
}
|
|
|
|
// Initally store the Network to Render
|
|
this.tabs[networkToRender.id] = networkToRender;
|
|
|
|
// Define the Options
|
|
const visjsOptions = this.props.options || generateGraphOptions();
|
|
|
|
const settings: GraphicalEditorComponentProps<N,E> = {
|
|
allowUserSelect: true,
|
|
|
|
network: networkToRender,
|
|
|
|
onItemSelected(item){
|
|
|
|
},
|
|
|
|
onMount(ref, layout){
|
|
// Define the Network.
|
|
_this.network = new UndoRedoGraph(ref, visjsOptions);
|
|
|
|
// Load the Networ:
|
|
_this.network.loadData(networkToRender);
|
|
|
|
_this.layout = layout;
|
|
|
|
// Resize the Network.
|
|
// _this.network.resize();
|
|
},
|
|
|
|
onResize(){
|
|
if (_this.network) {
|
|
_this.network.resize();
|
|
}
|
|
},
|
|
|
|
async onNewTab(){
|
|
// Function to create a new tab.
|
|
try {
|
|
const label = await _this.getDialogData({
|
|
header: 'Enter Network-Name',
|
|
content: {
|
|
component: 'DynamicForm',
|
|
props: {
|
|
schema: {
|
|
type: "string",
|
|
description: "Name of the Element"
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
const id = Date.now().toString();
|
|
// const label = id;
|
|
|
|
_this.tabs[id] = {
|
|
nodes: [{
|
|
id: 'test',
|
|
shape: 'ellipse',
|
|
label
|
|
}],
|
|
edges: [],
|
|
clusters: [],
|
|
version: "20200713",
|
|
name: label,
|
|
id: id
|
|
};
|
|
|
|
return {
|
|
id,
|
|
label,
|
|
delteable: true
|
|
}
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
},
|
|
|
|
async onTabSelect(oldTabId: string, newTabId: string){
|
|
// Contains the Main Logic for selecting another tab
|
|
// Therefore
|
|
_this.tabs[oldTabId] = _this.network.getData();
|
|
|
|
// Use the content of the new Tab as rendering:
|
|
_this.network.loadData(_this.tabs[newTabId]);
|
|
|
|
return true;
|
|
},
|
|
|
|
async onTabDelete(tabId: string) {
|
|
// A Tab should be deleted
|
|
|
|
return true;
|
|
},
|
|
|
|
async onNoTabSelected() {
|
|
// Nothing to Display!
|
|
|
|
// Use the content of the new Tab as rendering:
|
|
_this.network.loadData({
|
|
nodes: [],
|
|
edges: [],
|
|
clusters: [],
|
|
version: "20200713",
|
|
name: 'empty',
|
|
id: 'empty'
|
|
});
|
|
|
|
},
|
|
|
|
onUnmount(){
|
|
if (_this.network){
|
|
// Destroy the Network.
|
|
_this.network.destroy();
|
|
}
|
|
},
|
|
|
|
tabs: {
|
|
active: 'start',
|
|
items: [{
|
|
id: networkToRender.id,
|
|
label: networkToRender.name,
|
|
delteable: true
|
|
}],
|
|
allowNewTabs: true
|
|
}
|
|
};
|
|
|
|
return settings;
|
|
}
|
|
|
|
public getDialogData<T = any>(settings: ModalSettings) {
|
|
const _this = this;
|
|
let close: () => void;
|
|
|
|
return new Promise<T>((resolve, reject) => {
|
|
// Adapt the Settings:
|
|
const _onSubmit = rgetattr<(data) => Promise<void>>(settings, 'content.props.onSubmit', async (data) => { });
|
|
const _onCancel = rgetattr<(err) => Promise<void>>(settings, 'content.props.onCancel', async (err) => { });
|
|
|
|
const onSubmit = async (data) => {
|
|
close();
|
|
_onSubmit(data);
|
|
resolve(data);
|
|
}
|
|
|
|
const onCancel = async (data) => {
|
|
close();
|
|
_onCancel(data);
|
|
reject(data);
|
|
}
|
|
|
|
rsetattr(settings, 'content.props.onSubmit', onSubmit);
|
|
rsetattr(settings, 'content.props.onCancel', onCancel);
|
|
|
|
close = this.layout.openPopup(settings)
|
|
});
|
|
}
|
|
|
|
public requestRerender() {
|
|
this.setState({
|
|
update: null
|
|
})
|
|
}
|
|
|
|
public componentDidMount() {
|
|
const _this = this;
|
|
|
|
console.log('editor',this.props.children)
|
|
}
|
|
|
|
|
|
public render() {
|
|
|
|
const settings = this.initializeGraph();
|
|
|
|
|
|
|
|
const template: {
|
|
groupName: string,
|
|
items: {
|
|
label: string,
|
|
keywords: string,
|
|
item: string,
|
|
id: string | number,
|
|
}[]
|
|
}[] = [
|
|
{
|
|
groupName: 'GRP 1',
|
|
items: [
|
|
{
|
|
item: 'hello',
|
|
keywords: '1',
|
|
label: 'Hello',
|
|
id: 1,
|
|
},
|
|
{
|
|
item: 'world',
|
|
keywords: '2',
|
|
label: 'world',
|
|
id: 2,
|
|
|
|
}, {
|
|
item: '!',
|
|
keywords: '3',
|
|
label: '!',
|
|
id: 3,
|
|
}
|
|
|
|
]
|
|
},
|
|
{
|
|
groupName: 'GRP 2',
|
|
items: [
|
|
{
|
|
item: 'world',
|
|
keywords: '4',
|
|
label: 'world',
|
|
id: 4,
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
return (
|
|
<>
|
|
<Layout
|
|
selection={template}
|
|
allowUserSelect
|
|
onMount={settings.onMount}
|
|
onResize={settings.onResize}
|
|
onNewTab={settings.onNewTab}
|
|
onTabSelect={settings.onTabSelect}
|
|
onUnmount={settings.onUnmount}
|
|
onNoTabSelected={settings.onNoTabSelected}
|
|
onTabDelete={settings.onTabDelete}
|
|
tabs={settings.tabs}
|
|
></Layout>
|
|
</>
|
|
)
|
|
}
|
|
}
|
|
|
|
export default GraphicalEditorComponent; |