nope/resources/ui/graph/defaults/default.toolbar.ts
2020-10-27 20:22:41 +01:00

930 lines
43 KiB
TypeScript

/**
* @author Martin Karkowski
* @email m.karkowski@zema.de
* @create date 2019-04-29 11:30:22
* @modify date 2020-07-22 21:57:09
* @desc [description]
*/
import { generateGraphOptions } from './default.graph-options';
import { clusterSelected } from '../addons/cluster.selected';
import { IVisjsOptions } from '../interfaces/IVisjsOptions';
import { ICallbackData } from '../interfaces/IGraphTool';
import { IToolbar } from '../../layout/toolbar'
declare const $: any;
declare const w2prompt: any;
declare const w2popup: any;
declare const jsPDF: any;
declare const JSONEditor: any;
/**
* Function to extract the Default Toolbar Menu
*/
export function defaultToolbar<N,E, D extends ICallbackData<N,E>>(graphOptions: IVisjsOptions = generateGraphOptions(), additionalOptions: {[index: string]: any} = {}) {
/**
* Function to Determine the Graph-Layout, based on the Hierarchical Layout
*/
const getCurrentLayout = () => {
let initalLayout = 'normal';
if (rgettattr(graphOptions, 'layout.hierarchical', false) === true || rgetattr(graphOptions, 'layout.hierarchical.enabled', false) === true) {
initalLayout = 'h_' + (rgetattr(graphOptions, 'layout.hierarchical.direction', 'ud') as string).toLowerCase()
}
return initalLayout;
}
const toolbar: IToolbar<D> = {
items: [
{
type: 'menu',
id: 'fileMenu',
items: [
{
type: 'action',
label: 'New File',
onClick: (data) => {
/** Clearout the Graph and Delete the History */
data.network.clear();
data.network.resetHistory();
}
},
{
type: 'divider',
},
{
type: 'action',
label: 'Load File',
onClick: (data) => {
// Todo
}
},
{
type: 'action',
label: 'Import File',
onClick: (data) => {
}
},
{
type: 'action',
label: 'Save File',
onClick: (data) => {
// Todo
}
},
{
type: 'action',
label: 'print',
onClick: (data) => {
/** Function, which should render the CANVAS as PDF */
/** Therefore extract the real Canvas-Object */
const canvas = data.network.network.body.container.getElementsByTagName('canvas')[0];
const imgData = canvas.toDataURL('image/jpeg', 1.0);
const pdf = new jsPDF();
pdf.addImage(imgData, 'JPEG', 0, 0);
pdf.save('download.pdf');
}
}
],
label: 'File'
},
{
type: 'menu',
id: 'editMenu',
label: 'edit',
items: [
{
type: 'action',
label: 'Undo',
onClick: (data) => {
/** Undo the Last Action if Possible */
data.network.undo();
}
},
{
type: 'action',
label: 'Redo',
icon: 'fa fa-arrow-right',
onClick: (data) => {
/** Redo the Last Action if Possible */
data.network.redo();
}
},
{
type: 'divider'
},
{
type: 'action',
label: 'add node',
tooltip: 'Add a new Node Element in the Center of the current View',
icon: 'fa fa-plus-square',
onClick: (data) => {
/**
* Add a new Node in the Center of the Screen.
* Therefore extract the Center-Position of the
* Graph and call the addNode Function.
*
* This will result in opening up the corresponding
* Editor.
*/
// if (( data.component.template as ITemplate<N,E>).nodes && (data.component.template as ITemplate<N,E>).nodes.length > 0){
// const pos = data.network.network.getViewPosition();
// data.component.addNode(pos);
// } else {
// data.network.showMessage('info', 'No Template Selected!', 'Please select a template for a Node in the Sidebar!', 5000);
// }
}
},
{
type: 'action',
label: 'add edge',
tooltip: 'Add a new Node Element',
icon: 'fa fa-arrow-right',
onClick: (data) => {
/**
* Set the graph in the Add-Edge Mode
*/
// if ((data.eid as ITemplate<N,E>).nodes && (data.component.template as ITemplate<N,E>).nodes.length === 0 && (data.component.template as ITemplate<N,E>).edges.length === 1){
// data.network.network.addEdgeMode();
// } else {
// data.network.showMessage('info', 'No Template Selected!', 'Please select a template for an Edge in the Sidebar!', 5000);
// }
}
},
{
type: 'divider',
},
{
type: 'action',
label: 'export to clipboard',
tooltip: 'Copy the selected elements to the Clipboard',
icon: 'fa fa-clone',
onClick: (data) => {
// writeToClipboard(data.component.genCopyData());
}
},
{
type: 'action',
label: 'import from clipboard',
tooltip: 'Imports elements from the Clipboard. They can be pasted by pressing "ctrl+v" later on.',
icon: 'fa fa-clone',
onClick: (data) => {
data.component.pasteFromClipboard();
}
},
{
type: 'action',
label: 'upload project-file',
tooltip: 'Opens ',
icon: 'fa fa-clone',
onClick: (data) => {
// Iterate over the selected
data.component.disableHotkeys();
w2prompt({
label: 'name',
value: 'default',
attrs: 'style="width: 200px"',
title: 'Paste the Template below',
ok_label: 'save',
cancel_label: 'cancel',
width: 300,
height: 200,
onClose(){
data.component.enableHotkeys();
}
}).ok((content) => {
// data.component.copyTemplate = content;
});
}
},
{
type: 'divider',
},
{
// id: 'color1',
type: 'color',
onRefresh: (data) => {
if (data.event && data.event.item && typeof data.event.item.color === 'string') {
data.network.disableStoringContent = true;
// Extract the Base Color
const color = data.event.item.color.startsWith('#') ? data.event.item.color : '#' + data.event.item.color;
let mustUpdate = false;
// Update the New Color
for (const id of data.selectedNodes) {
const node = data.network.getNode(id);
if (node.color !== color) {
node.color = color;
data.network.updateNode(node);
mustUpdate = true;
}
}
data.network.disableStoringContent = false;
callImmediate(() => {
if (mustUpdate) {
data.network.save();
}
});
}
}
}, {
type: 'divider'
},
{
type: 'action',
label: 'hide selected',
tooltip: 'Hide the Selected Element',
icon: 'fa fa-eye-slash',
onClick: (data) => {
/**
* Callback which whill hide the selected Elements.
* Therefore disable storingContent on Updates, cause
* every data.options of the Selected Elements are updated.
*/
data.network.disableStoringContent = true;
data.network.network.setVisibilityOfNodes(data.selectedNodes, false);
data.network.network.storePositions();
/** Iterate over the selected Elements and hide the Node */
for (const id of data.selectedNodes) {
const node = data.network.getNode(id);
node.hidden = true;
/** Update the data.options */
data.network.updateNode(node);
}
/** Iterate over the selected Elements and hide the Edge */
for (const id of data.selectedEdges) {
const edge = data.network.getEdge(id);
edge.hidden = true;
/** Update the data.options */
data.network.updateEdge(edge);
}
/** Activate Redo/Undo Tracking again */
data.network.disableStoringContent = false;
/** Store the current state */
data.network.save();
}
},
]
}
]
}
const config: IToolbar<D> = {
/** Set the File-Menu as initally active */
activeTab: 'fileMenu',
/** Define the Tabs of the Toolbar */
tabs: {
edit: {
label: 'Edit',
tooltip: 'Edit Actions for the Network',
menu: {
items: [
{
id: 'undo',
type: 'action',
tooltip: 'Undo',
icon: 'fa fa-arrow-left',
onClick: (data) => {
/** Undo the Last Action if Possible */
data.network.undo();
}
},
{
type: 'action',
tooltip: 'Redo',
icon: 'fa fa-arrow-right',
onClick: (data) => {
/** Redo the Last Action if Possible */
data.network.redo();
}
},
{
type: 'divider'
},
{
type: 'action',
label: 'add node',
tooltip: 'Add a new Node Element in the Center of the current View',
icon: 'fa fa-plus-square',
onClick: (data) => {
/**
* Add a new Node in the Center of the Screen.
* Therefore extract the Center-Position of the
* Graph and call the addNode Function.
*
* This will result in opening up the corresponding
* Editor.
*/
// if (( data.component.template as ITemplate<N,E>).nodes && (data.component.template as ITemplate<N,E>).nodes.length > 0){
// const pos = data.network.network.getViewPosition();
// data.component.addNode(pos);
// } else {
// data.network.showMessage('info', 'No Template Selected!', 'Please select a template for a Node in the Sidebar!', 5000);
// }
}
},
{
type: 'action',
label: 'add edge',
tooltip: 'Add a new Node Element',
icon: 'fa fa-arrow-right',
onClick: (data) => {
/**
* Set the graph in the Add-Edge Mode
*/
// if ((data.eid as ITemplate<N,E>).nodes && (data.component.template as ITemplate<N,E>).nodes.length === 0 && (data.component.template as ITemplate<N,E>).edges.length === 1){
// data.network.network.addEdgeMode();
// } else {
// data.network.showMessage('info', 'No Template Selected!', 'Please select a template for an Edge in the Sidebar!', 5000);
// }
}
},
{
type: 'divider',
},
{
type: 'action',
label: 'export to clipboard',
tooltip: 'Copy the selected elements to the Clipboard',
icon: 'fa fa-clone',
onClick: (data) => {
// writeToClipboard(data.component.genCopyData());
}
},
{
type: 'action',
label: 'import from clipboard',
tooltip: 'Imports elements from the Clipboard. They can be pasted by pressing "ctrl+v" later on.',
icon: 'fa fa-clone',
onClick: (data) => {
data.component.pasteFromClipboard();
}
},
{
type: 'action',
label: 'upload project-file',
tooltip: 'Opens ',
icon: 'fa fa-clone',
onClick: (data) => {
// Iterate over the selected
data.component.disableHotkeys();
w2prompt({
label: 'name',
value: 'default',
attrs: 'style="width: 200px"',
title: 'Paste the Template below',
ok_label: 'save',
cancel_label: 'cancel',
width: 300,
height: 200,
onClose(){
data.component.enableHotkeys();
}
}).ok((content) => {
// data.component.copyTemplate = content;
});
}
},
{
type: 'divider',
},
{
// id: 'color1',
type: 'color',
onRefresh: (data) => {
if (data.event && data.event.item && typeof data.event.item.color === 'string') {
data.network.disableStoringContent = true;
// Extract the Base Color
const color = data.event.item.color.startsWith('#') ? data.event.item.color : '#' + data.event.item.color;
let mustUpdate = false;
// Update the New Color
for (const id of data.selectedNodes) {
const node = data.network.getNode(id);
if (node.color !== color) {
node.color = color;
data.network.updateNode(node);
mustUpdate = true;
}
}
data.network.disableStoringContent = false;
callImmediate(() => {
if (mustUpdate) {
data.network.save();
}
});
}
}
}, {
type: 'divider'
},
{
type: 'action',
label: 'hide selected',
tooltip: 'Hide the Selected Element',
icon: 'fa fa-eye-slash',
onClick: (data) => {
/**
* Callback which whill hide the selected Elements.
* Therefore disable storingContent on Updates, cause
* every data.options of the Selected Elements are updated.
*/
data.network.disableStoringContent = true;
data.network.network.setVisibilityOfNodes(data.selectedNodes, false);
data.network.network.storePositions();
/** Iterate over the selected Elements and hide the Node */
for (const id of data.selectedNodes) {
const node = data.network.getNode(id);
node.hidden = true;
/** Update the data.options */
data.network.updateNode(node);
}
/** Iterate over the selected Elements and hide the Edge */
for (const id of data.selectedEdges) {
const edge = data.network.getEdge(id);
edge.hidden = true;
/** Update the data.options */
data.network.updateEdge(edge);
}
/** Activate Redo/Undo Tracking again */
data.network.disableStoringContent = false;
/** Store the current state */
data.network.save();
}
},
]
}
},
view: {
label: 'View',
tooltip: 'Manipulates the View of the Graph',
menu: {
items: [
{
type: 'action',
label: 'fit',
tooltip: 'Fits the Graph',
icon: 'fa fa-expand',
onClick: (data) => {
data.network.network.fit({
animation: true
});
}
},
{
type: 'action',
label: 'zoom in',
tooltip: 'Zoom in the Graph',
icon: 'fa fa-search-plus',
onClick: (data) => {
const position = data.network.network.getViewPosition();
const currentScale = data.network.network.getScale();
data.network.network.moveTo({
position,
scale: currentScale + 0.1,
animation: {
duration: 100,
}
});
}
},
{
type: 'action',
label: 'zoom out',
tooltip: 'Zooms out the Graph',
icon: 'fa fa-search-minus',
onClick: (data) => {
const position = data.network.network.getViewPosition();
const currentScale = data.network.network.getScale();
data.network.network.moveTo({
position,
scale: currentScale - 0.1,
animation: {
duration: 100,
}
});
}
}, {
type: 'divider'
},
{
type: 'action',
label: 'view all',
tooltip: 'Show all hidden Elements',
icon: 'fa fa-eye',
onClick: (data) => {
const nodes = data.network.nodes;
const edges = data.network.edges;
for (const node of nodes){
node.hidden = false;
}
for (const edge of edges){
edge.hidden = false;
}
data.network.updateNode(nodes);
data.network.updateEdge(edges);
}
},
]
}
},
layout: {
label: 'Layout',
tooltip: 'Options to Manipulate the Layout of the Graph',
// disabled: true,
menu: {
items: [
{
id: 'item2',
type: 'menu-radio',
text(item) {
const text = item.selected;
const el = this.get('item2:' + item.selected);
return el.text;
},
selected: getCurrentLayout(),
items: [
{ id: 'h_ud', label: 'Hierarchical: UD', icon: 'fa fa-arrow-down' },
{ id: 'h_du', label: 'Hierarchical: DU', icon: 'fa fa-arrow-up' },
{ id: 'h_lr', label: 'Hierarchical: LR', icon: 'fa fa-arrow-right' },
{ id: 'h_rl', label: 'Hierarchical: RL', icon: 'fa fa-arrow-left' },
{ id: 'normal', label: 'Normal View-Mode' }
],
onRefresh(data) {
data.event.done(() => {
const before = getCurrentLayout();
if (data.event.item.selected != before) {
// Update the data.options object
if (typeof rgetattr(data.options, 'layout.hierarchical', {}) === 'boolean') {
rsetattr(data.options, 'layout.hierarchical', {})
}
// Based on the Selection, update the Elements
switch (data.event.item.selected) {
case 'normal':
rsetattr(data.options, 'layout.hierarchical.enabled', false)
break;
default:
const direction = (data.event.item.selected as string).slice(2).toUpperCase();
rsetattr(data.options, 'layout.hierarchical.enabled', true);
rsetattr(data.options, 'layout.hierarchical.direction', direction);
break;
}
data.component.visjsOptions = data.options;
// Store the New data.options
data.network.network.storePositions();
const nodes = data.network.nodes;
const edges = data.network.edges;
const clusters = data.network.getClusters();
data.network.clear();
data.network.network.setOptions(data.options);
data.network.addNode(nodes);
data.network.addEdge(edges);
data.network.readinClusters(clusters);
}
});
}
},
]
}
},
cluster: {
label: 'Clusters',
tooltip: 'Add Clusters to the Node',
// disabled: true,
menu: {
items: [
{
type: 'action',
label: 'cluster selected',
tooltip: 'Clusterize the Selected Elements',
icon: 'fa fa-cog',
onClick: (data) => {
clusterSelected(data.component, false)
}
},
{
type: 'action',
label: 'cluster outliners',
tooltip: 'Clusterize the Selected Elements',
icon: 'fa fa-cog',
onClick: (data) => {
data.network.network.clusterOutliers();
data.network.save();
}
},
{
type: 'divider'
},
{
type: 'action',
label: 'uncluster selected',
tooltip: '',
icon: 'fa fa-cog',
onClick: (data) => {
for (const node of data.selectedNodes) {
if (data.network.network.isCluster(node)) {
data.network.network.openCluster(node)
}
}
data.network.save();
}
},
{
type: 'action',
label: 'view all',
tooltip: 'Show all hidden Elements',
icon: 'fa fa-eye',
onClick: (data) => {
const nodesToUpdate = data.network.nodes;
for (const node of nodesToUpdate){
node.hidden = false;
}
data.network.updateNode(nodesToUpdate);
}
},
]
}
},
settings: {
label: 'Settings',
tooltip: 'User Settings',
// disabled: true,
menu: {
items: [
{
type: 'check',
label: 'Multiselect',
icon: 'fa fa-check-square',
checked: rgetattr(graphOptions, 'interaction.multiselect', false),
onClick: (data) => {
data.event.done(() => {
rsetattr(data.options, 'interaction.multiselect', data.event.item.checked);
data.component.visjsOptions = data.options;
data.network.network.setOptions(data.options);
})
}
},
{
type: 'divider'
},
{
type: 'check',
label: 'Navigation Buttons',
icon: 'fa fa-check-square',
checked: rgetattr(graphOptions, 'interaction.navigationButtons', false),
onClick: (data) => {
data.event.done(() => {
rsetattr(data.options, 'interaction.navigationButtons', data.event.item.checked);
data.component.visjsOptions = data.options;
data.network.network.setOptions(data.options);
})
}
},
{
type: 'divider'
},
{
id: 'physics',
type: 'check',
label: 'physics',
icon: 'fa fa-check-square',
checked: rgetattr(graphOptions, 'physics', false) === true || rgetattr(graphOptions, 'physics.enabled', false) === true,
onClick: (data) => {
data.event.done(() => {
if (typeof data.options.physics == 'boolean') {
data.options.physics = data.event.item.checked;
rsetattr(data.options, 'physics', data.event.item.checked);
data.component.visjsOptions = data.options;
data.network.network.setOptions(data.options);
} else {
rsetattr(data.options, 'physics.enabled', data.event.item.checked);
data.component.visjsOptions = data.options;
data.network.network.setOptions(data.options);
}
})
}
},
{
type: 'divider'
},
{
id: 'shadows',
type: 'check',
label: 'shadows',
icon: 'fa fa-check-square',
checked: rgetattr(graphOptions, 'nodes.shadow', false) === true || rgetattr(graphOptions, 'nodes.shadow.enabled', false) === true,
onClick: (data) => {
data.event.done(() => {
if (data.options.nodes && typeof data.options.nodes.shadow == 'boolean') {
data.options.nodes.shadow = data.event.item.checked;
rsetattr(data.options, 'nodes.shadow', data.event.item.checked);
} else {
rsetattr(data.options, 'nodes.shadow.enabled', data.event.item.checked);
}
if (data.options.edges && typeof data.options.edges.shadow == 'boolean') {
data.options.edges.shadow = data.event.item.checked;
rsetattr(data.options, 'edges.shadow', data.event.item.checked);
} else {
rsetattr(data.options, 'edges.shadow.enabled', data.event.item.checked);
}
data.component.visjsOptions = data.options;
data.network.network.setOptions(data.options);
})
}
},{
type: 'divider'
},
{
type: 'check',
label: 'Edit on Select',
icon: 'fa fa-check-square',
checked: additionalOptions.editOnSelect || false,
onClick: (data) => {
data.event.done(() => {
data.component.options.editOnSelect = data.event.item.checked;
})
}
},
{
type: 'check',
label: 'Hide Panel on Deselect',
icon: 'fa fa-check-square',
checked: additionalOptions.hidePanelOnDeselect || false,
onClick: (data) => {
data.event.done(() => {
data.component.options.hidePanelOnDeselect = data.event.item.checked;
})
}
},
]
}
},
developer: {
label: 'Developer',
tooltip: 'Extra Tools for a Developer',
// disabled: true,
menu: {
items: [
{
type: 'action',
label: 'json',
tooltip: 'Shows the current data as JSON',
icon: 'fa fa-code',
onClick: (data) => {
let editor: any = null;
const speed = 0.3;
w2popup.open({
id: 'test',
title: 'Debug window',
maximized: true,
body: '<div id="jsonviewer" style="width:100%;height:100%"></div>',
showMax: true,
speed,
width: 500,
height: 500,
onOpen(event) {
setTimeout(() => {
// Generate the Editor
const editorOptions = {
// modes: ['tree', 'view', 'code', 'text']
mode: 'view',
search: 'true',
mainMenuBar: false
};
editor = new JSONEditor(document.getElementById('jsonviewer'), editorOptions, {
nodes: data.network.nodes,
edges: data.network.edges
});
}, speed * 1000 + 0.050)
},
onClose(event) {
if (editor) {
editor.destroy();
}
}
});
}
},
{
type: 'action',
label: 'generate Template',
tooltip: 'Creates a Template Structure',
icon: 'fa fa-code',
onClick: (data) => {
writeToClipboard(stringifyWithFunctions(data.component.generateTemplateData()));
}
},
{
type: 'divider'
},
{
type: 'check',
label: 'Use Version Control',
icon: 'fa fa-check-square',
checked: rgetattr(additionalOptions, 'useVersionControl', true),
onClick: (data) => {
data.event.done(() => {
data.component.network.useVersionControl = data.event.item.checked;
})
}
},
]
}
},
help: {
label: 'Help',
menu: {
items: [
{
type: 'action',
label: 'Show Help',
tooltip: 'Clusterize the Selected Elements',
icon: 'fa fa-info',
onClick: (data) => {
// Load the Help Component.
data.component.layout.openDialogComponent<HelpComponent>({
title: 'help',
component: {
component: HelpComponent
},
buttons: [
{
label: 'Close',
callback(instance, close){
close();
},
status: 'danger'
}
],
closeOnBackdropClick: true,
closeOnEsc: true,
});
}
},
]
}
}
}
};
return config;
}