/** * Copyright (c) 2006-2013, JGraph Ltd * * Schema example * * This example demonstrates implementing * a SQL schema editor. */ import React from 'react'; import mxEvent from '../mxgraph/util/mxEvent'; import mxGraph from '../mxgraph/view/mxGraph'; import mxRubberband from '../mxgraph/handler/mxRubberband'; import { error } from '../../packages/core/src/util/gui/MaxWindow'; import { load } from '../../packages/core/src/util/network/MaxXmlRequest'; import { htmlEntities } from '../../packages/core/src/util/stringUtils'; import { setOpacity } from '../../packages/core/src/util/utils'; import { write, writeln } from '../../packages/core/src/util/domUtils'; import { createXmlDocument, getPrettyXml } from '../../packages/core/src/util/xmlUtils'; import { makeDraggable } from '../../packages/core/src/util/gestureUtils'; import { clone } from '../../packages/core/src/util/cloneUtils'; const HTML_TEMPLATE = `
Created with mxGraph
` function main(container, outline, toolbar, sidebar, status) { // Checks if the browser is supported if (!Client.isBrowserSupported()) { // Displays an error message if the browser is not supported. error('Browser is not supported!', 200, false); } else { // Specifies shadow opacity, color and offset mxConstants.SHADOW_OPACITY = 0.5; mxConstants.SHADOWCOLOR = '#C0C0C0'; mxConstants.SHADOW_OFFSET_X = 5; mxConstants.SHADOW_OFFSET_Y = 6; // Table icon dimensions and position SwimlaneShape.prototype.imageSize = 20; SwimlaneShape.prototype.imageDx = 16; SwimlaneShape.prototype.imageDy = 4; // Changes swimlane icon bounds SwimlaneShape.prototype.getImageBounds = function(x, y, w, h) { return new Rectangle(x + this.imageDx, y + this.imageDy, this.imageSize, this.imageSize); }; // Defines an icon for creating new connections in the connection handler. // This will automatically disable the highlighting of the source vertex. ConnectionHandler.prototype.connectImage = new Image('images/connector.gif', 16, 16); // Prefetches all images that appear in colums // to avoid problems with the auto-layout let keyImage = new Image(); keyImage.src = "images/key.png"; let plusImage = new Image(); plusImage.src = "images/plus.png"; let checkImage = new Image(); checkImage.src = "images/check.png"; // Creates the graph inside the given container. The // editor is used to create certain functionality for the // graph, such as the rubberband selection, but most parts // of the UI are custom in this example. let editor = new Editor(); let graph = editor.graph; let model = graph.model; // Disables some global features graph.setConnectable(true); graph.setCellsDisconnectable(false); graph.setCellsCloneable(false); graph.swimlaneNesting = false; graph.dropEnabled = true; // Does not allow dangling edges graph.setAllowDanglingEdges(false); // Forces use of default edge in mxConnectionHandler graph.getPlugin('ConnectionHandler').factoryMethod = null; // Only tables are resizable graph.isCellResizable = function(cell) { return this.isSwimlane(cell); }; // Only tables are movable graph.isCellMovable = function(cell) { return this.isSwimlane(cell); }; // Sets the graph container and configures the editor editor.setGraphContainer(container); let config = load( 'editors/config/keyhandler-minimal.xml'). getDocumentElement(); editor.configure(config); // Configures the automatic layout for the table columns editor.layoutSwimlanes = true; editor.createSwimlaneLayout = () => { let layout = new StackLayout(this.graph, false); layout.fill = true; layout.resizeParent = true; // Overrides the function to always return true layout.isVertexMovable = function(cell) { return true; }; return layout; }; // Text label changes will go into the name field of the user object graph.model.valueForCellChanged = function(cell, value) { if (value.name != null) { return GraphDataModel.prototype.valueForCellChanged.apply(this, arguments); } else { let old = cell.value.name; cell.value.name = value; return old; } }; // Columns are dynamically created HTML labels graph.isHtmlLabel = function(cell) { return !this.isSwimlane(cell) && !cell.isEdge(); }; // Edges are not editable graph.isCellEditable = function(cell) { return !cell.isEdge(); }; // Returns the name field of the user object for the label graph.convertValueToString = function(cell) { if (cell.value != null && cell.value.name != null) { return cell.value.name; } return mxGraph.prototype.convertValueToString.apply(this, arguments); // "supercall" }; // Returns the type as the tooltip for column cells graph.getTooltip = function(state) { if (this.isHtmlLabel(state.cell)) { return 'Type: '+state.cell.value.type; } else if (state.cell.isEdge()) { let source = state.cell.getTerminal(true); let parent = source.getParent(); return parent.value.name+'.'+source.value.name; } return mxGraph.prototype.getTooltip.apply(this, arguments); // "supercall" }; // Creates a dynamic HTML label for column fields graph.getLabel = function(cell){ if (this.isHtmlLabel(cell)) { let label = ''; if (cell.value.primaryKey) { label += ' '; } else { label += ' '; } if (cell.value.autoIncrement) { label += ' '; } else if (cell.value.unique) { label += ' '; } else { label += ' '; } return label + htmlEntities(cell.value.name, false) + ': ' + htmlEntities(cell.value.type, false); } return mxGraph.prototype.getLabel.apply(this, arguments); // "supercall" }; // Removes the source vertex if edges are removed graph.addListener(mxEvent.REMOVE_CELLS, function(sender, evt) { let cells = evt.getProperty('cells'); for (let i = 0; i < cells.length; i++) { let cell = cells[i]; if (cell.isEdge()) { let terminal = cell.getTerminal(true); let parent = terminal.getParent(); this.model.remove(terminal); } } }); // Disables drag-and-drop into non-swimlanes. graph.isValidDropTarget = function(cell, cells, evt) { return this.isSwimlane(cell); }; // Installs a popupmenu handler using local function (see below). graph.getPlugin('PopupMenuHandler').factoryMethod = function(menu, cell, evt) { createPopupMenu(editor, graph, menu, cell, evt); }; // Adds all required styles to the graph (see below) configureStylesheet(graph); // Adds sidebar icon for the table object let tableObject = new Table('TABLENAME'); let table = new Cell(tableObject, new Geometry(0, 0, 200, 28), 'table'); table.setVertex(true); addSidebarIcon(graph, sidebar, table, 'images/icons48/table.png'); // Adds sidebar icon for the column object let columnObject = new Column('COLUMNNAME'); let column = new Cell(columnObject, new Geometry(0, 0, 0, 26)); column.setVertex(true); column.setConnectable(false); addSidebarIcon(graph, sidebar, column, 'images/icons48/column.png'); // Adds primary key field into table let firstColumn = column.clone(); firstColumn.value.name = 'TABLENAME_ID'; firstColumn.value.type = 'INTEGER'; firstColumn.value.primaryKey = true; firstColumn.value.autoIncrement = true; table.insert(firstColumn); // Adds child columns for new connections between tables graph.addEdge = function(edge, parent, source, target, index) { // Finds the primary key child of the target table let primaryKey = null; let childCount = target.getChildCount(); for (var i=0; i < childCount; i++) { let child = target.getChildAt(i); if (child.value.primaryKey) { primaryKey = child; break; } } if (primaryKey == null) { alert('Target table must have a primary key'); return; } this.model.beginUpdate(); try { var col1 = this.model.cloneCell(column); col1.value.name = primaryKey.value.name; col1.value.type = primaryKey.value.type; this.addCell(col1, source); source = col1; target = primaryKey; return mxGraph.prototype.addEdge.apply(this, arguments); // "supercall" } finally { this.model.endUpdate(); } return null; }; // Displays useful hints in a small semi-transparent box. let hints = document.createElement('div'); hints.style.position = 'absolute'; hints.style.overflow = 'hidden'; hints.style.width = '230px'; hints.style.bottom = '56px'; hints.style.height = '86px'; hints.style.right = '20px'; hints.style.background = 'black'; hints.style.color = 'white'; hints.style.fontFamily = 'Arial'; hints.style.fontSize = '10px'; hints.style.padding = '4px'; setOpacity(hints, 50); writeln(hints, '- Drag an image from the sidebar to the graph'); writeln(hints, '- Doubleclick on a table or column to edit'); writeln(hints, '- Shift- or Rightclick and drag for panning'); writeln(hints, '- Move the mouse over a cell to see a tooltip'); writeln(hints, '- Click and drag a table to move and connect'); writeln(hints, '- Shift- or Rightclick to show a popup menu'); document.body.appendChild(hints); // Creates a new DIV that is used as a toolbar and adds // toolbar buttons. let spacer = document.createElement('div'); spacer.style.display = 'inline'; spacer.style.padding = '8px'; addToolbarButton(editor, toolbar, 'properties', 'Properties', 'editors/images/properties.gif'); // Defines a new export action editor.addAction('properties', function(editor, cell) { if (cell == null) { cell = graph.getSelectionCell(); } if (graph.isHtmlLabel(cell)) { showProperties(graph, cell); } }); addToolbarButton(editor, toolbar, 'delete', 'Delete', 'images/delete2.png'); toolbar.appendChild(spacer.cloneNode(true)); addToolbarButton(editor, toolbar, 'undo', '', 'images/undo.png'); addToolbarButton(editor, toolbar, 'redo', '', 'images/redo.png'); toolbar.appendChild(spacer.cloneNode(true)); addToolbarButton(editor, toolbar, 'show', 'Show', 'images/camera.png'); addToolbarButton(editor, toolbar, 'print', 'Print', 'images/printer.png'); toolbar.appendChild(spacer.cloneNode(true)); // Defines a create SQK action editor.addAction('showSql', function(editor, cell) { let sql = createSql(graph); if (sql.length > 0) { let textarea = document.createElement('textarea'); textarea.style.width = '400px'; textarea.style.height = '400px'; textarea.value = sql; showModalWindow('SQL', textarea, 410, 440); } else { alert('Schema is empty'); } }); addToolbarButton(editor, toolbar, 'showSql', 'Show SQL', 'images/export1.png'); // Defines export XML action editor.addAction('export', function(editor, cell) { let textarea = document.createElement('textarea'); textarea.style.width = '400px'; textarea.style.height = '400px'; let enc = new Codec(createXmlDocument()); let node = enc.encode(editor.graph.getDataModel()); textarea.value = getPrettyXml(node); showModalWindow('XML', textarea, 410, 440); }); addToolbarButton(editor, toolbar, 'export', 'Export XML', 'images/export1.png'); // Adds toolbar buttons into the status bar at the bottom // of the window. addToolbarButton(editor, status, 'collapseAll', 'Collapse All', 'images/navigate_minus.png', true); addToolbarButton(editor, status, 'expandAll', 'Expand All', 'images/navigate_plus.png', true); status.appendChild(spacer.cloneNode(true)); addToolbarButton(editor, status, 'zoomIn', '', 'images/zoom_in.png', true); addToolbarButton(editor, status, 'zoomOut', '', 'images/zoom_out.png', true); addToolbarButton(editor, status, 'actualSize', '', 'images/view_1_1.png', true); addToolbarButton(editor, status, 'fit', '', 'images/fit_to_size.png', true); // Creates the outline (navigator, overview) for moving // around the graph in the top, right corner of the window. let outln = new outline(graph, outline); // Fades-out the splash screen after the UI has been loaded. let splash = document.getElementById('splash'); if (splash != null) { try { mxEvent.release(splash); Effects.fadeOut(splash, 100, true); } catch (e) { // mxUtils is not available (library not loaded) splash.parentNode.removeChild(splash); } } } } function addToolbarButton(editor, toolbar, action, label, image, isTransparent) { let button = document.createElement('button'); button.style.fontSize = '10'; if (image != null) { let img = document.createElement('img'); img.setAttribute('src', image); img.style.width = '16px'; img.style.height = '16px'; img.style.verticalAlign = 'middle'; img.style.marginRight = '2px'; button.appendChild(img); } if (isTransparent) { button.style.background = 'transparent'; button.style.color = '#FFFFFF'; button.style.border = 'none'; } mxEvent.addListener(button, 'click', function(evt) { editor.execute(action); }); write(button, label); toolbar.appendChild(button); }; function showModalWindow(title, content, width, height) { let background = document.createElement('div'); background.style.position = 'absolute'; background.style.left = '0px'; background.style.top = '0px'; background.style.right = '0px'; background.style.bottom = '0px'; background.style.background = 'black'; setOpacity(background, 50); document.body.appendChild(background); let x = Math.max(0, document.body.scrollWidth/2-width/2); let y = Math.max(10, (document.body.scrollHeight || document.documentElement.scrollHeight)/2-height*2/3); let wnd = new MaxWindow(title, content, x, y, width, height, false, true); wnd.setClosable(true); // Fades the background out after after the window has been closed wnd.addListener(mxEvent.DESTROY, function(evt) { Effects.fadeOut(background, 50, true, 10, 30, true); }); wnd.setVisible(true); return wnd; }; function addSidebarIcon(graph, sidebar, prototype, image) { // Function that is executed when the image is dropped on // the graph. The cell argument points to the cell under // the mousepointer if there is one. let funct = function(graph, evt, cell) { graph.stopEditing(false); let pt = graph.getPointForEvent(evt); let parent = graph.getDefaultParent(); let model = graph.getDataModel(); let isTable = graph.isSwimlane(prototype); let name = null; if (!isTable) { parent = cell; let pstate = graph.getView().getState(parent); if (parent == null || pstate == null) { alert('Drop target must be a table'); return; } pt.x -= pstate.x; pt.y -= pstate.y; let columnCount = parent.getChildCount()+1; name = prompt('Enter name for new column', 'COLUMN'+columnCount); } else { let tableCount = 0; let childCount = parent.getChildCount(); for (var i=0; i 0) { for (var j=0; j