/** * @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 { callImmediate } from '../../../@zema/ZISS-TypeScript-Library/src/Function-Call'; import { rsetattr, rgetattr } from '../../../@zema/ZISS-TypeScript-Library/src/Object-Methods'; import { generateGraphOptions } from './default.graph-options'; import { clusterSelected } from '../addons/cluster.selected'; import { IBaseNodeOptions } from '../../../@zema/ZISS-Network/type/IBaseNodeOptions'; import { IBaseEdgeOptions } from '../../../@zema/ZISS-Network/type/IBaseEdgeOptions'; import { stringify, stringifyWithFunctions } from '../../../@zema/ZISS-TypeScript-Library/src/JSON'; import { IVisjsOptions } from '../interfaces/IVisjsOptions'; import { IToolbarConfig } from '../../gui-components-basic-layout/types/interfaces'; import { createUploadPrompt } from '../../gui-components-basic-layout/helpers/upload.file'; import { ICallbackData } from '../interfaces/IGraphTool'; import { HelpComponent } from '../help/help'; import { writeToClipboard } from '../../../@zema/ZISS-Browser-Library/src/clipboard'; 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>(graphOptions: IVisjsOptions = generateGraphOptions(), additionalOptions: {[index: string]: any} = {}) { /** * Function to Determine the Graph-Layout, based on the Hierarchical Layout */ const getCurrentLayout = () => { let initalLayout = 'normal'; if (rgetattr(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 config: IToolbarConfig = { /** Set the File-Menu as initally active */ activeTab: 'fileMenu', /** Define the Tabs of the Toolbar */ tabs: { fileMenu: { text: 'File', menu: { items: [ { type: 'button', text: 'new', icon: 'fa fa-sticky-note', onClick: (data) => { /** Clearout the Graph and Delete the History */ data.network.clear(); data.network.resetHistory(); } }, { type: 'break', }, { type: 'button', text: 'load', icon: 'fa fa-upload', onClick: (data) => { createUploadPrompt({ callback(content){ /** Read In the JSON File */ data.component.loadJSON(content, true); }, title:'Load Network File' }) } }, { type: 'button', text: 'import', icon: 'fa fa-upload', onClick: (data) => { createUploadPrompt({ callback(content){ /** Read In the JSON File */ data.component.loadJSON(content, false); }, title:'Import Network File' }) } }, { type: 'button', text: 'save', icon: 'fa fa-download', onClick: (data) => { w2prompt({ label: 'Filename', value: 'graph.json', attrs: 'style="width: 200px"', title: 'Enter File Name', ok_text: 'save', cancel_text: 'cancel', width: 300, height: 200 }).ok((filename) => { data.zemaService.saveFile(filename, stringify(data.network.getData(), data.component.parseFunctions)); }); } }, { type: 'button', text: 'print', icon: 'fa fa-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'); } } ] } }, edit: { text: 'Edit', tooltip: 'Edit Actions for the Network', menu: { items: [ { id: 'undo', type: 'button', tooltip: 'Undo', icon: 'fa fa-arrow-left', onClick: (data) => { /** Undo the Last Action if Possible */ data.network.undo(); } }, { type: 'button', tooltip: 'Redo', icon: 'fa fa-arrow-right', onClick: (data) => { /** Redo the Last Action if Possible */ data.network.redo(); } }, { type: 'break' }, { type: 'button', text: '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).nodes && (data.component.template as ITemplate).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: 'button', text: '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).nodes && (data.component.template as ITemplate).nodes.length === 0 && (data.component.template as ITemplate).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: 'break', }, { type: 'button', text: 'export to clipboard', tooltip: 'Copy the selected elements to the Clipboard', icon: 'fa fa-clone', onClick: (data) => { // writeToClipboard(data.component.genCopyData()); } }, { type: 'button', text: '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: 'button', text: '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_text: 'save', cancel_text: 'cancel', width: 300, height: 200, onClose(){ data.component.enableHotkeys(); } }).ok((content) => { // data.component.copyTemplate = content; }); } }, { type: 'break', }, { // 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: 'break' }, { type: 'button', text: '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: { text: 'View', tooltip: 'Manipulates the View of the Graph', menu: { items: [ { type: 'button', text: 'fit', tooltip: 'Fits the Graph', icon: 'fa fa-expand', onClick: (data) => { data.network.network.fit({ animation: true }); } }, { type: 'button', text: '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: 'button', text: '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: 'break' }, { type: 'button', text: '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: { text: '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', text: 'Hierarchical: UD', icon: 'fa fa-arrow-down' }, { id: 'h_du', text: 'Hierarchical: DU', icon: 'fa fa-arrow-up' }, { id: 'h_lr', text: 'Hierarchical: LR', icon: 'fa fa-arrow-right' }, { id: 'h_rl', text: 'Hierarchical: RL', icon: 'fa fa-arrow-left' }, { id: 'normal', text: '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: { text: 'Clusters', tooltip: 'Add Clusters to the Node', // disabled: true, menu: { items: [ { type: 'button', text: 'cluster selected', tooltip: 'Clusterize the Selected Elements', icon: 'fa fa-cog', onClick: (data) => { clusterSelected(data.component, false) } }, { type: 'button', text: 'cluster outliners', tooltip: 'Clusterize the Selected Elements', icon: 'fa fa-cog', onClick: (data) => { data.network.network.clusterOutliers(); data.network.save(); } }, { type: 'break' }, { type: 'button', text: '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: 'button', text: '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: { text: 'Settings', tooltip: 'User Settings', // disabled: true, menu: { items: [ { type: 'check', text: '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: 'break' }, { type: 'check', text: '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: 'break' }, { id: 'physics', type: 'check', text: '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: 'break' }, { id: 'shadows', type: 'check', text: '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: 'break' }, { type: 'check', text: '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', text: '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: { text: 'Developer', tooltip: 'Extra Tools for a Developer', // disabled: true, menu: { items: [ { type: 'button', text: '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: '
', 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: 'button', text: 'generate Template', tooltip: 'Creates a Template Structure', icon: 'fa fa-code', onClick: (data) => { writeToClipboard(stringifyWithFunctions(data.component.generateTemplateData())); } }, { type: 'break' }, { type: 'check', text: '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: { text: 'Help', menu: { items: [ { type: 'button', text: 'Show Help', tooltip: 'Clusterize the Selected Elements', icon: 'fa fa-info', onClick: (data) => { // Load the Help Component. data.component.layout.openDialogComponent({ title: 'help', component: { component: HelpComponent }, buttons: [ { label: 'Close', callback(instance, close){ close(); }, status: 'danger' } ], closeOnBackdropClick: true, closeOnEsc: true, }); } }, ] } } } }; return config; }