diff --git a/web/package.json b/web/package.json index 9d4c060dc..9e55eb674 100644 --- a/web/package.json +++ b/web/package.json @@ -46,6 +46,7 @@ "prop-types": "^15.7.2", "raw-loader": "^3.1.0", "resize-observer-polyfill": "^1.5.1", + "resolve-url-loader": "^3.1.2", "sass": "^1.24.4", "sass-loader": "^7.1.0", "sass-resources-loader": "^2.0.0", @@ -87,6 +88,7 @@ "dagre": "^0.8.4", "dropzone": "^5.5.1", "exports-loader": "~0.7.0", + "html2canvas": "^1.0.0-rc.7", "immutability-helper": "^3.0.0", "imports-loader": "^0.8.0", "ip-address": "^5.8.9", diff --git a/web/pgadmin/browser/static/js/frame.js b/web/pgadmin/browser/static/js/frame.js index b72df7b0f..0d3b91273 100644 --- a/web/pgadmin/browser/static/js/frame.js +++ b/web/pgadmin/browser/static/js/frame.js @@ -30,7 +30,7 @@ define([ height: 600, showTitle: true, isClosable: true, - isRenamable: true, + isRenamable: false, isPrivate: false, url: '', icon: '', diff --git a/web/pgadmin/static/js/chartjs/index.jsx b/web/pgadmin/static/js/chartjs/index.jsx index 52623bd24..022e63875 100644 --- a/web/pgadmin/static/js/chartjs/index.jsx +++ b/web/pgadmin/static/js/chartjs/index.jsx @@ -41,11 +41,11 @@ export default function BaseChart({type='line', id, options, data, redraw=false, options: optionsMerged, }); props.onInit && props.onInit(chartObj.current); - } + }; const destroyChart = function() { chartObj.current && chartObj.current.destroy(); - } + }; useEffect(()=>{ initChart(); @@ -72,7 +72,7 @@ export default function BaseChart({type='line', id, options, data, redraw=false, destroyChart(); initChart(); } - }, [redraw]) + }, [redraw]); return ( diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js index a7d056683..c9b8b5b7e 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js @@ -180,7 +180,7 @@ define('pgadmin.datagrid', [ name: 'frm_datagrid', showTitle: true, isCloseable: true, - isRenameable: true, + isRenamable: true, isPrivate: true, url: 'about:blank', }); diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid_panel_title.js b/web/pgadmin/tools/datagrid/static/js/datagrid_panel_title.js index 46db6a70a..b3bd793e0 100644 --- a/web/pgadmin/tools/datagrid/static/js/datagrid_panel_title.js +++ b/web/pgadmin/tools/datagrid/static/js/datagrid_panel_title.js @@ -78,11 +78,11 @@ export function setQueryToolDockerTitle(panel, is_query_tool, panel_title, is_fi export function set_renamable_option(panel, is_file) { if(is_file || is_file == 'true') { - panel._isRenamable = false; + panel.renamable(false); $('.conn-info-dd').hide(); $('.connection-data').css({pointerEvents: 'none', cursor: 'arrow'}); } else { - panel._isRenamable = true; + panel.renamable(true); } } diff --git a/web/pgadmin/tools/erd/__init__.py b/web/pgadmin/tools/erd/__init__.py index 301aca9c9..1ce742186 100644 --- a/web/pgadmin/tools/erd/__init__.py +++ b/web/pgadmin/tools/erd/__init__.py @@ -288,6 +288,24 @@ class ERDModule(PgAdminModule): fields=shortcut_fields ) + self.preference.register( + 'keyboard_shortcuts', + 'show_details', + gettext('Show more/fewer details'), + 'keyboardshortcut', + { + 'alt': True, + 'shift': False, + 'control': True, + 'key': { + 'key_code': 84, + 'char': 't' + } + }, + category_label=PREF_LABEL_KEYBOARD_SHORTCUTS, + fields=shortcut_fields + ) + self.preference.register( 'keyboard_shortcuts', 'zoom_to_fit', diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js index ea0e22a29..8faae4d01 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDCore.js @@ -212,14 +212,14 @@ export default class ERDCore { let sourcePort = sourceNode.getPort(portName); /* Create the port if not there */ if(!sourcePort) { - sourcePort = sourceNode.addPort(this.getNewPort(type, null, {name:portName, alignment:PortModelAlignment.RIGHT})); + sourcePort = sourceNode.addPort(this.getNewPort(type, null, {name:portName, subtype: 'one', alignment:PortModelAlignment.RIGHT})); } portName = targetNode.getPortName(data.local_column_attnum); let targetPort = targetNode.getPort(portName); /* Create the port if not there */ if(!targetPort) { - targetPort = targetNode.addPort(this.getNewPort(type, null, {name:portName, alignment:PortModelAlignment.RIGHT})); + targetPort = targetNode.addPort(this.getNewPort(type, null, {name:portName, subtype: 'many', alignment:PortModelAlignment.RIGHT})); } /* Link the ports */ diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js index e1fee815f..2b56ea1c4 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/TableDialog.js @@ -50,7 +50,7 @@ export default class TableDialog { return 'entity_dialog'; } - getDataModel(attributes, colTypes, schemas, sVersion) { + getDataModel(attributes, existingTables, colTypes, schemas, sVersion) { let dialogObj = this; let columnsModel = this.pgBrowser.DataModel.extend({ idAttribute: 'attnum', @@ -694,6 +694,10 @@ export default class TableDialog { msg = gettext('Table name cannot be empty.'); this.errorModel.set('name', msg); return msg; + } else if(_.findIndex(existingTables, (table)=>table[0]==schema&&table[1]==name) >= 0) { + msg = gettext('Table name already exists.'); + this.errorModel.set('name', msg); + return msg; } this.errorModel.unset('name'); if ( @@ -705,6 +709,8 @@ export default class TableDialog { return msg; } this.errorModel.unset('schema'); + + return null; }, }); @@ -731,9 +737,9 @@ export default class TableDialog { return Alertify[dialogName]; } - show(title, attributes, colTypes, schemas, sVersion, callback) { + show(title, attributes, existingTables, colTypes, schemas, sVersion, callback) { let dialogTitle = title || gettext('Unknown'); const dialog = this.createOrGetDialog('table_dialog'); - dialog(dialogTitle, this.getDataModel(attributes, colTypes, schemas, sVersion), callback).resizeTo(this.pgBrowser.stdW.md, this.pgBrowser.stdH.md); + dialog(dialogTitle, this.getDataModel(attributes, existingTables, colTypes, schemas, sVersion), callback).resizeTo(this.pgBrowser.stdW.md, this.pgBrowser.stdH.md); } } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/index.js b/web/pgadmin/tools/erd/static/js/erd_tool/index.js index 523ebd14f..e865c29bd 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/index.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/index.js @@ -9,10 +9,13 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import _ from 'lodash'; + import BodyWidget from './ui_components/BodyWidget'; import getDialog, {transformToSupported} from './dialogs'; import Alertify from 'pgadmin.alertifyjs'; import pgWindow from 'sources/window'; +import pgAdmin from 'sources/pgadmin'; export default class ERDTool { constructor(container, params) { @@ -20,10 +23,28 @@ export default class ERDTool { this.params = params; } + getPreferencesForModule() { + + } + render() { /* Mount the React ERD tool to the container */ + let panel = null; + _.each(pgWindow.pgAdmin.Browser.docker.findPanels('frm_erdtool'), function(p) { + if (p.isVisible()) { + panel = p; + } + }); + ReactDOM.render( - , + , this.container ); } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/links/OneToManyLink.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/links/OneToManyLink.jsx index be985afde..37b456825 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/links/OneToManyLink.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/links/OneToManyLink.jsx @@ -9,12 +9,12 @@ import React from 'react'; import { - RightAngleLinkModel, - RightAngleLinkWidget, - DefaultLinkFactory, - PortModelAlignment, - LinkWidget, - PointModel + RightAngleLinkModel, + RightAngleLinkWidget, + DefaultLinkFactory, + PortModelAlignment, + LinkWidget, + PointModel, } from '@projectstorm/react-diagrams'; import {Point} from '@projectstorm/geometry'; import _ from 'lodash'; @@ -24,7 +24,7 @@ export const OneToManyModel = { local_column_attnum: undefined, referenced_table_uid: undefined, referenced_column_attnum: undefined, -} +}; export class OneToManyLinkModel extends RightAngleLinkModel { constructor({data, ...options}) { @@ -33,7 +33,7 @@ export class OneToManyLinkModel extends RightAngleLinkModel { width: 1, class: 'link-onetomany', locked: true, - ...options + ...options, }); this._data = { @@ -62,13 +62,13 @@ export class OneToManyLinkModel extends RightAngleLinkModel { 'local_column': _.find(target.columns, (col)=>data.local_column_attnum == col.attnum).name, 'referenced': _.find(source.columns, (col)=>data.referenced_column_attnum == col.attnum).name, }], - } + }; } serialize() { return { ...super.serialize(), - data: this.getData() + data: this.getData(), }; } } @@ -83,13 +83,13 @@ const CustomLinkEndWidget = props => { - ) + ); } else if (type == 'one') { return ( - ) + ); } - } + }; return ( @@ -111,21 +111,21 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget { let degree = 0; let tx = 0, ty = 0; switch(alignment) { - case PortModelAlignment.BOTTOM: - ty = -offset; - break; - case PortModelAlignment.LEFT: - degree = 90; - tx = offset - break; - case PortModelAlignment.TOP: - degree = 180; - ty = offset; - break; - case PortModelAlignment.RIGHT: - degree = -90; - tx = -offset; - break; + case PortModelAlignment.BOTTOM: + ty = -offset; + break; + case PortModelAlignment.LEFT: + degree = 90; + tx = offset; + break; + case PortModelAlignment.TOP: + degree = 180; + ty = offset; + break; + case PortModelAlignment.RIGHT: + degree = -90; + tx = -offset; + break; } return [degree, tx, ty]; } @@ -146,8 +146,8 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget { point: point, rotation: rotation, tx: tx, - ty: ty - } + ty: ty, + }; } generateCustomEndWidget({type, point, rotation, tx, ty}) { @@ -183,7 +183,7 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget { } handleMove = function(event) { - this.props.link.getTargetPort() + this.props.link.getTargetPort(); this.draggingEvent(event, this.dragging_index); this.props.link.fireEvent({}, 'positionChanged'); }.bind(this); @@ -220,7 +220,7 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget { this.props.link.addPoint( new PointModel({ link: this.props.link, - position: new Point(onePoint.point.getX(), manyPoint.point.getY()) + position: new Point(onePoint.point.getX(), manyPoint.point.getY()), }) ); } @@ -246,7 +246,7 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget { onMouseEnter: (event) => { this.setState({ selected: true }); this.props.link.lastHoverIndexOfPath = j; - } + }, }, j ) diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx index 5e74d890b..383c621ce 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/nodes/TableNode.jsx @@ -19,7 +19,7 @@ export class TableNodeModel extends DefaultNodeModel { constructor({otherInfo, ...options}) { super({ ...options, - type: TYPE + type: TYPE, }); this._note = otherInfo.note || ''; @@ -60,22 +60,22 @@ export class TableNodeModel extends DefaultNodeModel { cloneData(name) { let newData = { - ...this.getData() + ...this.getData(), }; if(name) { - newData['name'] = name + newData['name'] = name; } return newData; } setData(data) { let self = this; - /* Remove the links if column dropped */ + /* Remove the links if column dropped or primary key removed */ _.differenceWith(this._data.columns, data.columns, function(existing, incoming) { - return existing.attnum == incoming.attnum; + return existing.attnum == incoming.attnum && incoming.is_primary_key == true; }).forEach((col)=>{ let existPort = self.getPort(self.getPortName(col.attnum)); - if(existPort) { + if(existPort && existPort.getSubtype() == 'one') { existPort.removeAllLinks(); self.removePort(existPort); } @@ -109,7 +109,7 @@ export class TableNodeModel extends DefaultNodeModel { otherInfo: { data: this.getData(), note: this.getNote(), - } + }, }; } } @@ -119,8 +119,8 @@ export class TableNodeWidget extends React.Component { super(props); this.state = { - show_details: true - } + show_details: true, + }; this.props.node.registerListener({ toggleDetails: (event) => { @@ -143,13 +143,13 @@ export class TableNodeWidget extends React.Component {
{this.generatePort(port)}
- ) + ); } generatePort = port => { if(port) { return ( - + ); } return <>; @@ -163,21 +163,21 @@ export class TableNodeWidget extends React.Component { render() { let node_data = this.props.node.getData(); return ( -
{this.props.node.fireEvent({}, 'editNode')}}> +
{this.props.node.fireEvent({}, 'editNode');}}>
{e.stopPropagation();}} /> {this.props.node.getNote() && { - this.props.node.fireEvent({}, 'showNote') + this.props.node.fireEvent({}, 'showNote'); }} title="Check note" />}
-
- - {node_data.schema} +
+
+
{node_data.schema}
-
- - {node_data.name} +
+
+
{node_data.name}
{_.map(node_data.columns, (col)=>this.generateColumn(col))} diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ports/OneToManyPort.js b/web/pgadmin/tools/erd/static/js/erd_tool/ports/OneToManyPort.js index c412acd77..9bcbdc415 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ports/OneToManyPort.js +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ports/OneToManyPort.js @@ -16,6 +16,7 @@ const TYPE = 'onetomany'; export default class OneToManyPortModel extends PortModel { constructor({options}) { super({ + subtype: 'notset', ...options, type: TYPE, }); @@ -30,6 +31,22 @@ export default class OneToManyPortModel extends PortModel { createLinkModel() { return new OneToManyLinkModel({}); } + + getSubtype() { + return this.options.subtype; + } + + deserialize(event) { + super.deserialize(event); + this.options.subtype = event.data.subtype || 'notset'; + } + + serialize() { + return { + ...super.serialize(), + subtype: this.options.subtype, + }; + } } export class OneToManyPortFactory extends AbstractModelFactory { diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx index 22701bae2..83f0491d6 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx @@ -12,6 +12,8 @@ import { CanvasWidget } from '@projectstorm/react-canvas-core'; import axios from 'axios'; import { Action, InputType } from '@projectstorm/react-canvas-core'; import PropTypes from 'prop-types'; +import _ from 'lodash'; +import html2canvas from 'html2canvas'; import ERDCore from '../ERDCore'; import ToolBar, {IconButton, DetailsToggleButton, ButtonGroup} from './ToolBar'; @@ -22,22 +24,25 @@ import {setPanelTitle} from '../../erd_module'; import gettext from 'sources/gettext'; import url_for from 'sources/url_for'; import {showERDSqlTool} from 'tools/datagrid/static/js/show_query_tool'; +import 'wcdocker'; /* Custom react-diagram action for keyboard events */ export class KeyboardShortcutAction extends Action { constructor(shortcut_handlers=[]) { - super({ - type: InputType.KEY_DOWN, - fire: ({ event })=>{ - this.callHandler(event); - } - }); - this.shortcuts = {}; + super({ + type: InputType.KEY_DOWN, + fire: ({ event })=>{ + this.callHandler(event); + }, + }); + this.shortcuts = {}; - for(let i=0; i { this.addEditNode(event.node); - } + }, }; Object.keys(diagramEvents).forEach(eventName => { this.diagram.registerModelEvent(eventName, diagramEvents[eventName]); @@ -157,9 +166,10 @@ export default class BodyWidget extends React.Component { [this.state.preferences.one_to_many, this.onOneToManyClick], [this.state.preferences.many_to_many, this.onManyToManyClick], [this.state.preferences.auto_align, this.onAutoDistribute], + [this.state.preferences.show_details, this.onDetailsToggle], [this.state.preferences.zoom_to_fit, this.diagram.zoomToFit], [this.state.preferences.zoom_in, this.diagram.zoomIn], - [this.state.preferences.zoom_out, this.diagram.zoomOut] + [this.state.preferences.zoom_out, this.diagram.zoomOut], ]); this.diagram.registerKeyAction(this.keyboardActionObj); @@ -182,11 +192,16 @@ export default class BodyWidget extends React.Component { } async componentDidMount() { - this.setLoading('Preparing'); - this.setTitle(this.state.current_file); + this.setLoading(gettext('Preparing...')); + this.setState({ - preferences: this.props.pgAdmin.Browser.get_preferences_for_module('erd') - }, this.registerKeyboardShortcuts); + preferences: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd'), + is_new_tab: (this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('browser').new_browser_tab_open || '') + .includes('erd_tool'), + }, ()=>{ + this.registerKeyboardShortcuts(); + this.setTitle(this.state.current_file); + }); this.registerModelEvents(); this.realignGrid({ backgroundSize: '45px 45px', @@ -197,10 +212,19 @@ export default class BodyWidget extends React.Component { this.props.pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:create_file', this.saveFile, this); this.props.pgAdmin.Browser.onPreferencesChange('erd', () => { this.setState({ - preferences: this.props.pgAdmin.Browser.get_preferences_for_module('erd') + preferences: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd'), }, ()=>this.registerKeyboardShortcuts()); }); + this.props.panel?.on(window.wcDocker?.EVENT.CLOSING, () => { + if(this.state.dirty) { + this.closeOnSave = false; + this.confirmBeforeClose(); + return false; + } + return true; + }); + let done = await this.initConnection(); if(!done) return; @@ -218,18 +242,78 @@ export default class BodyWidget extends React.Component { } } + confirmBeforeClose() { + let bodyObj = this; + this.props.alertify.confirmSave || this.props.alertify.dialog('confirmSave', function() { + return { + main: function(title, message) { + this.setHeader(title); + this.setContent(message); + }, + setup: function() { + return { + buttons: [{ + text: gettext('Cancel'), + key: 27, // ESC + invokeOnClose: true, + className: 'btn btn-secondary fa fa-lg fa-times pg-alertify-button', + }, { + text: gettext('Don\'t save'), + className: 'btn btn-secondary fa fa-lg fa-trash-alt pg-alertify-button', + }, { + text: gettext('Save'), + className: 'btn btn-primary fa fa-lg fa-save pg-alertify-button', + }], + focus: { + element: 0, + select: false, + }, + options: { + maximizable: false, + resizable: false, + }, + }; + }, + callback: function(closeEvent) { + switch (closeEvent.index) { + case 0: // Cancel + //Do nothing. + break; + case 1: // Don't Save + bodyObj.closePanel(); + break; + case 2: //Save + bodyObj.onSaveDiagram(false, true); + break; + } + }, + }; + }); + this.props.alertify.confirmSave(gettext('Save changes?'), gettext('The diagram has changed. Do you want to save changes?')); + return false; + } + + closePanel() { + window.onbeforeunload = null; + this.props.panel.off(wcDocker.EVENT.CLOSING); + this.props.pgWindow.pgAdmin.Browser.docker.removePanel(this.props.panel); + } + getDialog(dialogName) { if(dialogName === 'entity_dialog') { + let existingTables = this.diagram.getModel().getNodes().map((node)=>{ + return node.getSchemaTableName(); + }); return (title, attributes, callback)=>{ - this.props.getDialog(dialogName).show( - title, attributes, this.diagram.getCache('colTypes'), this.diagram.getCache('schemas'), this.state.server_version, callback - ); + this.props.getDialog(dialogName).show( + title, attributes, existingTables, this.diagram.getCache('colTypes'), this.diagram.getCache('schemas'), this.state.server_version, callback + ); }; } else if(dialogName === 'onetomany_dialog' || dialogName === 'manytomany_dialog') { return (title, attributes, callback)=>{ - this.props.getDialog(dialogName).show( - title, attributes, this.diagram.getModel().getNodesDict(), this.state.server_version, callback - ); + this.props.getDialog(dialogName).show( + title, attributes, this.diagram.getModel().getNodesDict(), this.state.server_version, callback + ); }; } } @@ -289,20 +373,20 @@ export default class BodyWidget extends React.Component { gettext('Delete ?'), gettext('You have selected %s tables and %s links.', this.diagram.getSelectedNodes().length, this.diagram.getSelectedLinks().length) + '
' + gettext('Are you sure you want to delete ?'), - () => { - this.diagram.getSelectedNodes().forEach((node)=>{ - node.setSelected(false); - node.remove(); - }); - this.diagram.getSelectedLinks().forEach((link)=>{ - link.getTargetPort().remove(); - link.getSourcePort().remove(); - link.setSelected(false); - link.remove(); - }); - this.diagram.repaint(); - }, - () => {} + () => { + this.diagram.getSelectedNodes().forEach((node)=>{ + node.setSelected(false); + node.remove(); + }); + this.diagram.getSelectedLinks().forEach((link)=>{ + link.getTargetPort().remove(); + link.getSourcePort().remove(); + link.setSelected(false); + link.remove(); + }); + this.diagram.repaint(); + }, + () => {} ); } @@ -312,11 +396,11 @@ export default class BodyWidget extends React.Component { onDetailsToggle() { this.setState((prevState)=>({ - show_details: !prevState.show_details + show_details: !prevState.show_details, }), ()=>{ this.diagram.getModel().getNodes().forEach((node)=>{ node.fireEvent({show_details: this.state.show_details}, 'toggleDetails'); - }) + }); }); } @@ -335,8 +419,9 @@ export default class BodyWidget extends React.Component { } openFile(fileName) { + this.setLoading(gettext('Loading project...')); axios.post(url_for('sqleditor.load_file'), { - 'file_name': decodeURI(fileName) + 'file_name': decodeURI(fileName), }).then((res)=>{ this.setState({ current_file: fileName, @@ -344,13 +429,17 @@ export default class BodyWidget extends React.Component { }); this.setTitle(fileName); this.diagram.deserialize(res.data); + this.diagram.clearSelection(); this.registerModelEvents(); }).catch((err)=>{ this.handleAxiosCatch(err); + }).then(()=>{ + this.setLoading(null); }); } - onSaveDiagram(isSaveAs=false) { + onSaveDiagram(isSaveAs=false, closeOnSave=false) { + this.closeOnSave = closeOnSave; if(this.state.current_file && !isSaveAs) { this.saveFile(this.state.current_file); } else { @@ -370,9 +459,10 @@ export default class BodyWidget extends React.Component { } saveFile(fileName) { + this.setLoading(gettext('Saving...')); axios.post(url_for('sqleditor.save_file'), { 'file_name': decodeURI(fileName), - 'file_content': JSON.stringify(this.diagram.serialize(this.props.pgAdmin.Browser.utils.app_version_int)) + 'file_content': JSON.stringify(this.diagram.serialize(this.props.pgAdmin.Browser.utils.app_version_int)), }).then(()=>{ this.props.alertify.success(gettext('Project saved successfully.')); this.setState({ @@ -380,7 +470,12 @@ export default class BodyWidget extends React.Component { dirty: false, }); this.setTitle(fileName); + this.setLoading(null); + if(this.closeOnSave) { + this.closePanel.call(this); + } }).catch((err)=>{ + this.setLoading(null); this.handleAxiosCatch(err); }); } @@ -395,14 +490,10 @@ export default class BodyWidget extends React.Component { title = 'Untitled'; } title = this.getCurrentProjectName(title) + (dirty ? '*': ''); - if (this.new_browser_tab) { + if (this.state.is_new_tab) { window.document.title = title; } else { - _.each(this.props.pgAdmin.Browser.docker.findPanels('frm_erdtool'), function(p) { - if (p.isVisible()) { - setPanelTitle(p, title); - } - }); + setPanelTitle(this.props.panel, title); } } @@ -414,7 +505,7 @@ export default class BodyWidget extends React.Component { trans_id: this.props.params.trans_id, sgid: this.props.params.sgid, sid: this.props.params.sid, - did: this.props.params.did + did: this.props.params.did, }); this.setLoading(gettext('Preparing the SQL...')); @@ -428,18 +519,53 @@ export default class BodyWidget extends React.Component { sid: this.props.params.sid, did: this.props.params.did, stype: this.props.params.server_type, - } + }; let sqlId = `erd${this.props.params.trans_id}`; localStorage.setItem(sqlId, sqlScript); - showERDSqlTool(parentData, sqlId, this.props.params.title, this.props.pgAdmin.DataGrid, this.props.alertify); + showERDSqlTool(parentData, sqlId, this.props.params.title, this.props.pgWindow.pgAdmin.DataGrid, this.props.alertify); }) .catch((error)=>{ this.handleAxiosCatch(error); }) .then(()=>{ this.setLoading(null); - }) + }); + } + + onImageClick() { + this.setLoading(gettext('Preparing the image...')); + + /* Change the styles for suiting html2canvas */ + this.canvasEle.classList.add('html2canvas-reset'); + this.canvasEle.style.width = this.canvasEle.scrollWidth + 'px'; + this.canvasEle.style.height = this.canvasEle.scrollHeight + 'px'; + + html2canvas(this.canvasEle, { + width: this.canvasEle.scrollWidth + 10, + height: this.canvasEle.scrollHeight + 10, + scrollX: 0, + scrollY: 0, + useCORS: true, + allowTaint: true, + backgroundColor: window.getComputedStyle(this.canvasEle).backgroundColor, + }).then((canvas)=>{ + let link = document.createElement('a'); + link.setAttribute('href', canvas.toDataURL('image/png')); + link.setAttribute('download', this.getCurrentProjectName() + '.png'); + link.click(); + }).catch((err)=>{ + console.error(err); + this.props.alertify.alert() + .set('title', gettext('Error')) + .set('message', err).show(); + }).then(()=>{ + /* Revert back to the original CSS styles */ + this.canvasEle.classList.remove('html2canvas-reset'); + this.canvasEle.style.width = ''; + this.canvasEle.style.height = ''; + this.setLoading(null); + }); } onOneToManyClick() { @@ -473,8 +599,8 @@ export default class BodyWidget extends React.Component { 'name': `${right_table.getData().name}_${right_table.getColumnAt(newData.right_table_column_attnum).name}`, 'is_primary_key': false, 'attnum': 1, - }] - } + }], + }; let newNode = this.diagram.addNode(tableData); this.diagram.clearSelection(); newNode.setSelected(true); @@ -484,7 +610,7 @@ export default class BodyWidget extends React.Component { local_column_attnum: newNode.getColumns()[0].attnum, referenced_table_uid: newData.left_table_uid, referenced_column_attnum : newData.left_table_column_attnum, - } + }; this.diagram.addLink(linkData, 'onetomany'); linkData = { @@ -492,7 +618,7 @@ export default class BodyWidget extends React.Component { local_column_attnum: newNode.getColumns()[1].attnum, referenced_table_uid: newData.right_table_uid, referenced_column_attnum : newData.right_table_column_attnum, - } + }; this.diagram.addLink(linkData, 'onetomany'); @@ -505,7 +631,7 @@ export default class BodyWidget extends React.Component { this.noteRefEle = this.diagram.getEngine().getNodeElement(noteNode); this.setState({ note_node: noteNode, - note_open: true + note_open: true, }); } } @@ -528,14 +654,14 @@ export default class BodyWidget extends React.Component { trans_id: this.props.params.trans_id, sgid: this.props.params.sgid, sid: this.props.params.sid, - did: this.props.params.did + did: this.props.params.did, }); try { let response = await axios.post(initUrl); this.setState({ conn_status: CONNECT_STATUS.CONNECTED, - server_version: response.data.data.serverVersion + server_version: response.data.data.serverVersion, }); return true; } catch (error) { @@ -556,7 +682,7 @@ export default class BodyWidget extends React.Component { trans_id: this.props.params.trans_id, sgid: this.props.params.sgid, sid: this.props.params.sid, - did: this.props.params.did + did: this.props.params.did, }); try { @@ -579,7 +705,7 @@ export default class BodyWidget extends React.Component { trans_id: this.props.params.trans_id, sgid: this.props.params.sgid, sid: this.props.params.sid, - did: this.props.params.did + did: this.props.params.did, }); try { @@ -604,7 +730,7 @@ export default class BodyWidget extends React.Component { - {this.onSaveDiagram()}} title={gettext('Save project')} + {this.onSaveDiagram();}} title={gettext('Save project')} shortcut={this.state.preferences.save_project} disabled={!this.state.dirty}/> @@ -612,6 +738,8 @@ export default class BodyWidget extends React.Component { + - +
- {this.canvasEle = ele?.ref?.current}} engine={this.diagram.getEngine()} /> + {this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
); @@ -672,10 +801,11 @@ BodyWidget.propTypes = { title: PropTypes.string.isRequired, bgcolor: PropTypes.string, fgcolor: PropTypes.string, - gen: PropTypes.bool.isRequired + gen: PropTypes.bool.isRequired, }), getDialog: PropTypes.func.isRequired, transformToSupported: PropTypes.func.isRequired, + pgWindow: PropTypes.object.isRequired, pgAdmin: PropTypes.object.isRequired, - alertify: PropTypes.object.isRequired + alertify: PropTypes.object.isRequired, }; diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ConnectionBar.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ConnectionBar.jsx index f191dc85e..3b71dbcc0 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ConnectionBar.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ConnectionBar.jsx @@ -16,7 +16,7 @@ export const STATUS = { DISCONNECTED: 2, CONNECTING: 3, FAILED: 4, -} +}; /* The connection bar component */ export default function ConnectionBar({statusId, status, bgcolor, fgcolor, title}) { @@ -33,19 +33,19 @@ export default function ConnectionBar({statusId, status, bgcolor, fgcolor, title + (status == STATUS.CONNECTED ? 'icon-query-tool-connected' : '') + (status == (STATUS.DISCONNECTED || STATUS.FAILED) ? 'icon-query-tool-disconnected ' : '') + (status == STATUS.CONNECTING ? 'obtaining-conn' : '')} - aria-hidden="true" title="" role="img"> + aria-hidden="true" title="" role="img">
- {status == STATUS.CONNECTING ? '(' + gettext('Obtaining connection...') + ') ' : ''} - {status == STATUS.FAILED ? '(' + gettext('Connection failed') + ') ' : ''} - {title} + {status == STATUS.CONNECTING ? '(' + gettext('Obtaining connection...') + ') ' : ''} + {status == STATUS.FAILED ? '(' + gettext('Connection failed') + ') ' : ''} + {title}
- ) + ); } ConnectionBar.propTypes = { diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/FloatingNote.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/FloatingNote.jsx index 19050512c..052829f93 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/FloatingNote.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/FloatingNote.jsx @@ -51,13 +51,13 @@ export default function FloatingNote({open, onClose, reference, rows, noteNode,
- )} - visible={open} - interactive={true} - animation={false} - reference={reference} - placement='auto-end' - {...tippyProps} + )} + visible={open} + interactive={true} + animation={false} + reference={reference} + placement='auto-end' + {...tippyProps} /> ); } diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ToolBar.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ToolBar.jsx index 2f376e4cb..fe4280af3 100644 --- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ToolBar.jsx +++ b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ToolBar.jsx @@ -34,7 +34,7 @@ BaseIconButton.propTypes = { text: PropTypes.string, className: PropTypes.string, ref: CustomPropTypes.ref, -} +}; /* The tooltip content to show shortcut details */ @@ -47,10 +47,10 @@ export function Shortcut({shortcut}) { return (
{keys.map((key, i)=>{ - return
{key}
+ return
{key}
; })}
- ) + ); } const shortcutPropType = PropTypes.shape({ @@ -85,7 +85,7 @@ export const IconButton = forwardRef((props, ref) => { ); } else { - return + return ; } }); @@ -93,21 +93,21 @@ IconButton.propTypes = { title: PropTypes.string, shortcut: shortcutPropType, className: PropTypes.string, -} +}; /* Toggle button, icon changes based on value */ export function DetailsToggleButton({showDetails, ...props}) { return ( ); } DetailsToggleButton.propTypes = { showDetails: PropTypes.bool, -} +}; /* Button group container */ export function ButtonGroup({className, children}) { @@ -115,12 +115,12 @@ export function ButtonGroup({className, children}) {
{children}
- ) + ); } ButtonGroup.propTypes = { className: PropTypes.string, -} +}; /* Toolbar container */ export default function ToolBar({id, children}) { @@ -128,9 +128,9 @@ export default function ToolBar({id, children}) { - ) + ); } ButtonGroup.propTypes = { id: PropTypes.string, -} +}; diff --git a/web/pgadmin/tools/erd/static/scss/_erd.scss b/web/pgadmin/tools/erd/static/scss/_erd.scss index 733dd53c6..4fffcf4be 100644 --- a/web/pgadmin/tools/erd/static/scss/_erd.scss +++ b/web/pgadmin/tools/erd/static/scss/_erd.scss @@ -28,6 +28,7 @@ position: relative; width: 100%; height: 100%; + min-height: 0; } .floating-note { @@ -56,6 +57,7 @@ } .note-body { + word-break: break-all; & textarea { width: 100%; border: none; @@ -69,11 +71,21 @@ } } + .html2canvas-reset { + background-image: none !important; + overflow: auto !important; + + & > svg, & > div { + transform: none !important; + } + } + .diagram-canvas{ width: 100%; height: 100%; color: $color-fg; font-family: sans-serif; + background-color: $erd-canvas-bg; background-image: $erd-bg-grid; cursor: unset; @@ -85,6 +97,22 @@ width: 175px; font-size: 0.8em; + .table-icon-schema { + background-image: url('~top/browser/server_groups/servers/databases/schemas/static/img/schema.svg') !important; + // background-repeat: no-repeat; + // // background-size: 20px !important; + // align-content: center; + // vertical-align: middle; + // height: 100%; + // background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDIwLjAuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCA2NCA2NCIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNjQgNjQ7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDpub25lO3N0cm9rZTojOTk5OTk5O3N0cm9rZS13aWR0aDoyO3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1taXRlcmxpbWl0OjEwO30KPC9zdHlsZT4KPHBhdGggY2xhc3M9InN0MCIgZD0iTTIxLjksMjIuMmMwLDMuNS0yLjksNi40LTYuNCw2LjRzLTYuNC0yLjktNi40LTYuNHMyLjktNi40LDYuNC02LjRTMjEuOSwxOC43LDIxLjksMjIuMnogTTQuMiw1MWwyMi4xLTE5CgkgTTQ2LjUsNTEuMUwyNi40LDMyIE0zNy42LDQyLjZsMTEuNi0xMCBNNjIuNCw0NS4xTDQ5LjIsMzIuNyBNNjMsMUgxdjYyaDYyVjF6IE0xLDUxLjRoNjIiLz4KPC9zdmc+Cg=='); + + width: 20px; + height: 20px; + // background: transparent url('~top/browser/server_groups/servers/databases/schemas/static/img/schema.svg') no-repeat center center; + // height: 100%; + // width: 20px; + } + &.selected { border-color: $input-focus-border-color; box-shadow: $input-btn-focus-box-shadow; @@ -105,16 +133,24 @@ } } - .table-schema { + .table-schema-data { border-bottom: $border-width solid $erd-node-border-color; padding: $erd-row-padding; - font-weight: bold; + + & .table-schema { + font-weight: bold; + word-break: break-all; + } } - .table-name { + .table-name-data { border-bottom: $border-width*2 solid $erd-node-border-color; padding: $erd-row-padding; - font-weight: bold; + + & .table-name { + font-weight: bold; + word-break: break-all; + } } .table-cols { @@ -123,10 +159,7 @@ .col-row-data { padding: $erd-row-padding; width: 100%; - - .col-name { - word-break: break-all; - } + word-break: break-all; } .col-row-port { padding: 0; diff --git a/web/regression/javascript/erd/table_node_spec.js b/web/regression/javascript/erd/table_node_spec.js index 1c8bf74ee..4818d2a7a 100644 --- a/web/regression/javascript/erd/table_node_spec.js +++ b/web/regression/javascript/erd/table_node_spec.js @@ -73,7 +73,10 @@ describe('ERD TableNodeModel', ()=>{ }); describe('setData', ()=>{ - let existPort = jasmine.createSpyObj('port', ['removeAllLinks']); + let existPort = jasmine.createSpyObj('port', { + 'removeAllLinks': jasmine.createSpy('removeAllLinks'), + 'getSubtype': 'notset', + }); beforeEach(()=>{ modelObj._data.columns = [ @@ -93,6 +96,7 @@ describe('ERD TableNodeModel', ()=>{ }); it('add columns', ()=>{ + spyOn(existPort, 'getSubtype').and.returnValue('many'); existPort.removeAllLinks.calls.reset(); modelObj.setData({ name: 'noname', @@ -118,29 +122,31 @@ describe('ERD TableNodeModel', ()=>{ }); it('update columns', ()=>{ + spyOn(existPort, 'getSubtype').and.returnValue('many'); existPort.removeAllLinks.calls.reset(); modelObj.setData({ name: 'noname', schema: 'erd', columns: [ - {name: 'col1', not_null:false, attnum: 0}, - {name: 'col2updated', not_null:false, attnum: 1}, - {name: 'col3', not_null:true, attnum: 2}, + {name: 'col1', not_null:false, attnum: 0, is_primary_key: false}, + {name: 'col2updated', not_null:false, attnum: 1, is_primary_key: false}, + {name: 'col3', not_null:true, attnum: 2, is_primary_key: false}, ], }); expect(modelObj.getData()).toEqual({ name: 'noname', schema: 'erd', columns: [ - {name: 'col1', not_null:false, attnum: 0}, - {name: 'col2updated', not_null:false, attnum: 1}, - {name: 'col3', not_null:true, attnum: 2}, + {name: 'col1', not_null:false, attnum: 0, is_primary_key: false}, + {name: 'col2updated', not_null:false, attnum: 1, is_primary_key: false}, + {name: 'col3', not_null:true, attnum: 2, is_primary_key: false}, ], }); expect(existPort.removeAllLinks).not.toHaveBeenCalled(); }); it('remove columns', ()=>{ + spyOn(existPort, 'getSubtype').and.returnValue('one'); existPort.removeAllLinks.calls.reset(); modelObj.setData({ name: 'noname', diff --git a/web/regression/javascript/erd/ui_components/body_widget_spec.js b/web/regression/javascript/erd/ui_components/body_widget_spec.js index 16928980f..3114ba627 100644 --- a/web/regression/javascript/erd/ui_components/body_widget_spec.js +++ b/web/regression/javascript/erd/ui_components/body_widget_spec.js @@ -41,6 +41,10 @@ let pgAdmin = { }, }; +let pgWindow = { + pgAdmin: pgAdmin, +}; + let alertify = jasmine.createSpyObj('alertify', { 'success': null, 'error': null, @@ -124,7 +128,7 @@ describe('ERD BodyWidget', ()=>{ beforeEach(()=>{ jasmineEnzyme(); - body = mount({}} alertify={alertify}/>); + body = mount({}} alertify={alertify}/>); bodyInstance = body.instance(); }); @@ -248,7 +252,7 @@ describe('ERD BodyWidget', ()=>{ bodyInstance.addEditNode(); expect(tableDialog.show).toHaveBeenCalled(); - let saveCallback = tableDialog.show.calls.mostRecent().args[5]; + let saveCallback = tableDialog.show.calls.mostRecent().args[6]; let newData = {key: 'value'}; saveCallback(newData); expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith(newData); @@ -263,7 +267,7 @@ describe('ERD BodyWidget', ()=>{ bodyInstance.addEditNode(node); expect(tableDialog.show).toHaveBeenCalled(); - saveCallback = tableDialog.show.calls.mostRecent().args[5]; + saveCallback = tableDialog.show.calls.mostRecent().args[6]; newData = {key: 'value'}; saveCallback(newData); expect(node.setData).toHaveBeenCalledWith(newData); diff --git a/web/yarn.lock b/web/yarn.lock index 551213a27..5106e84d1 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1281,6 +1281,14 @@ acorn@^7.0.0, acorn@^7.1.0, acorn@^7.1.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== +adjust-sourcemap-loader@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz#5ae12fb5b7b1c585e80bbb5a63ec163a1a45e61e" + integrity sha512-YBrGyT2/uVQ/c6Rr+t6ZJXniY03YtHGMJQYal368burRGYKqhx9qGTWqcBU5s1CwYY9E/ri63RYyG1IacMZtqw== + dependencies: + loader-utils "^2.0.0" + regex-parser "^2.2.11" + after@0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" @@ -1432,6 +1440,11 @@ argparse@^1.0.6, argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +arity-n@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" + integrity sha1-2edrEXM+CFacCEeuezmyhgswt0U= + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -1813,6 +1826,11 @@ base64-arraybuffer@0.1.5: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" integrity sha1-c5JncZI7Whl0etZmqlzUv5xunOg= +base64-arraybuffer@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.2.0.tgz#4b944fac0191aa5907afe2d8c999ccc57ce80f45" + integrity sha512-7emyCsu1/xiBXgQZrscw/8KPRT44I4Yq9Pe6EGs3aPRTsWuggML1/1DTuZUuIaJPIm1FTDUVXl4x/yW8s0kQDQ== + base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" @@ -2437,16 +2455,16 @@ camelcase-keys@^2.0.0: camelcase "^2.0.0" map-obj "^1.0.0" +camelcase@5.3.1, camelcase@^5.0.0: + version "5.3.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" + integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== + camelcase@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -2797,6 +2815,13 @@ component-inherit@0.0.3: resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM= +compose-function@3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/compose-function/-/compose-function-3.0.3.tgz#9ed675f13cc54501d30950a486ff6a7ba3ab185f" + integrity sha1-ntZ18TzFRQHTCVCkhv9qe6OrGF8= + dependencies: + arity-n "^1.0.4" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2857,13 +2882,18 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== -convert-source-map@^1.1.3, convert-source-map@^1.5.0, convert-source-map@^1.7.0: +convert-source-map@1.7.0, convert-source-map@^1.1.3, convert-source-map@^1.5.0, convert-source-map@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442" integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA== dependencies: safe-buffer "~5.1.1" +convert-source-map@^0.3.3: + version "0.3.5" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-0.3.5.tgz#f1d802950af7dd2631a1febe0596550c86ab3190" + integrity sha1-8dgClQr33SYxof6+BZZVDIarMZA= + convert-source-map@~1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.1.3.tgz#4829c877e9fe49b3161f3bf3673888e204699860" @@ -3051,6 +3081,13 @@ css-declaration-sorter@^4.0.1: postcss "^7.0.1" timsort "^0.3.0" +css-line-break@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/css-line-break/-/css-line-break-1.1.1.tgz#d5e9bdd297840099eb0503c7310fd34927a026ef" + integrity sha512-1feNVaM4Fyzdj4mKPIQNL2n70MmuYzAXZ1aytlROFX1JsOo070OsugwGjj7nl6jnDJWHDM8zRZswkmeYVWZJQA== + dependencies: + base64-arraybuffer "^0.2.0" + css-loader@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-2.1.0.tgz#42952ac22bca5d076978638e9813abce49b8f0cc" @@ -3119,6 +3156,16 @@ css-what@^4.0.0: resolved "https://registry.yarnpkg.com/css-what/-/css-what-4.0.0.tgz#35e73761cab2eeb3d3661126b23d7aa0e8432233" integrity sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A== +css@^2.0.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/css/-/css-2.2.4.tgz#c646755c73971f2bba6a601e2cf2fd71b1298929" + integrity sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw== + dependencies: + inherits "^2.0.3" + source-map "^0.6.1" + source-map-resolve "^0.5.2" + urix "^0.1.0" + cssesc@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" @@ -3235,6 +3282,14 @@ cyclist@^1.0.1: resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9" integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk= +d@1, d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dagre@^0.8.4: version "0.8.5" resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee" @@ -3664,6 +3719,11 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +emojis-list@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= + emojis-list@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" @@ -3870,6 +3930,32 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" +es5-ext@^0.10.35, es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + +es6-iterator@2.0.3, es6-iterator@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" + integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= + dependencies: + d "1" + es5-ext "^0.10.35" + es6-symbol "^3.1.1" + +es6-symbol@^3.1.1, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -4176,6 +4262,13 @@ ext-name@^5.0.0: ext-list "^2.0.0" sort-keys-length "^1.0.0" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -4982,6 +5075,13 @@ html-escaper@^2.0.0: resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html2canvas@^1.0.0-rc.7: + version "1.0.0-rc.7" + resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.0.0-rc.7.tgz#70c159ce0e63954a91169531894d08ad5627ac98" + integrity sha512-yvPNZGejB2KOyKleZspjK/NruXVQuowu8NnV2HYG7gW7ytzl+umffbtUI62v2dCHQLDdsK6HIDtyJZ0W3neerA== + dependencies: + css-line-break "1.1.1" + htmlescape@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/htmlescape/-/htmlescape-1.1.1.tgz#3a03edc2214bca3b66424a3e7959349509cb0351" @@ -6167,6 +6267,15 @@ loader-runner@^2.4.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== +loader-utils@1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== + dependencies: + big.js "^5.2.2" + emojis-list "^2.0.0" + json5 "^1.0.1" + loader-utils@1.4.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0, loader-utils@^1.2.1, loader-utils@^1.2.3, loader-utils@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.0.tgz#c579b5e34cb34b1a74edc6c1fb36bfa371d5a613" @@ -6837,6 +6946,11 @@ neo-async@^2.5.0, neo-async@^2.6.1: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== +next-tick@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" + integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -7854,6 +7968,15 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== +postcss@7.0.21: + version "7.0.21" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.21.tgz#06bb07824c19c2021c5d056d5b10c35b989f7e17" + integrity sha512-uIFtJElxJo29QC753JzhidoAhvp/e/Exezkdhfmt8AymWT6/5B7W1WmponYWkHk2eg6sONyTch0A3nkMPun3SQ== + dependencies: + chalk "^2.4.2" + source-map "^0.6.1" + supports-color "^6.1.0" + postcss@7.0.27: version "7.0.27" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.27.tgz#cc67cdc6b0daa375105b7c424a85567345fc54d9" @@ -8260,6 +8383,11 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" +regex-parser@^2.2.11: + version "2.2.11" + resolved "https://registry.yarnpkg.com/regex-parser/-/regex-parser-2.2.11.tgz#3b37ec9049e19479806e878cabe7c1ca83ccfe58" + integrity sha512-jbD/FT0+9MBU2XAZluI7w2OBs1RBi6p9M83nkoZayQXXU9e8Robt69FcZc7wU4eJD/YFTjn1JdCk3rbMJajz8Q== + regexp.prototype.flags@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz#7aba89b3c13a64509dabcf3ca8d9fbb9bdf5cb75" @@ -8374,6 +8502,22 @@ resolve-from@^4.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== +resolve-url-loader@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/resolve-url-loader/-/resolve-url-loader-3.1.2.tgz#235e2c28e22e3e432ba7a5d4e305c59a58edfc08" + integrity sha512-QEb4A76c8Mi7I3xNKXlRKQSlLBwjUV/ULFMP+G7n3/7tJZ8MG5wsZ3ucxP1Jz8Vevn6fnJsxDx9cIls+utGzPQ== + dependencies: + adjust-sourcemap-loader "3.0.0" + camelcase "5.3.1" + compose-function "3.0.3" + convert-source-map "1.7.0" + es6-iterator "2.0.3" + loader-utils "1.2.3" + postcss "7.0.21" + rework "1.0.1" + rework-visit "1.0.0" + source-map "0.6.1" + resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" @@ -8412,6 +8556,19 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== +rework-visit@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a" + integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo= + +rework@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/rework/-/rework-1.0.1.tgz#30806a841342b54510aa4110850cd48534144aa7" + integrity sha1-MIBqhBNCtUUQqkEQhQzUhTQUSqc= + dependencies: + convert-source-map "^0.3.3" + css "^2.0.0" + rfdc@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2" @@ -8890,7 +9047,7 @@ source-list-map@^2.0.0: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-resolve@^0.5.0: +source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== @@ -8919,16 +9076,16 @@ source-map@0.5.0: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86" integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY= +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + source-map@^0.5.0, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.3: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" - integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== - source-map@^0.7.3: version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" @@ -9653,6 +9810,16 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.1.0.tgz#9bdc22c648cf8cf86dd23d32336a41cfb6475e3f" + integrity sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"