public inbox for [email protected]  
help / color / mirror / Atom feed
From: Aditya Toshniwal <[email protected]>
To: pgadmin-hackers <[email protected]>
Cc: Akshay Joshi <[email protected]>
Cc: Khushboo Vashi <[email protected]>
Cc: Dave Page <[email protected]>
Subject: Re: [pgAdmin][RM1802] ERD Tool (Beta)
Date: Mon, 25 Jan 2021 17:17:40 +0530
Message-ID: <CAM9w-_kYsHOi+AEDxVKAEG5z9m=OhisSYfPKTDQjG8ogu03DUA@mail.gmail.com> (raw)
In-Reply-To: <CAM9w-_nuo6d-xO1a2PASSBLeb9Q2603cXeoGrqUQioCc1=7x_g@mail.gmail.com>
References: <CAM9w-_kowrenfKpiRLYWEpHJOOT2k9zRg6KCVpA0FR4Q=CA4hw@mail.gmail.com>
	<CANxoLDda-CPNuU76FM1kOTdhNpRW170fr3FFQnaWxOpG=aeu2A@mail.gmail.com>
	<CAFOhELd4JNuX82Vw3ZNdiKHHwBgu+rTdm9RfwY-qNjGkH47taQ@mail.gmail.com>
	<CAFOhELdL0-M_itS4z-qNKUq6=RieWqc9UPos=oJTJFjyLrYQQw@mail.gmail.com>
	<CAM9w-_kRdox8wvsHTwJ+vTOT2-kY1M4LvWFkm-799pEqJvHMsw@mail.gmail.com>
	<CANxoLDcKjG-X=i7mrxVunS3oA+tBM3QmHgJN7y69CzT63y7a9A@mail.gmail.com>
	<CAM9w-_=AFpLB-7aUVF2NZn4xRXe0o-LALJSjMsN8yBHqwd19fg@mail.gmail.com>
	<CANxoLDcB4=x6qfL3HhW2E30c4N0pDLZtrKooMcocuRePM6v+sA@mail.gmail.com>
	<CAM9w-_=Yn9wyEBxV2iBj5CLtyuqohLxD_263Z996jRNioXXGfQ@mail.gmail.com>
	<CANxoLDcPBDfvXiPzLQGUL7tFxCkDmRWFD0vyirAQsbHR6Zzfdg@mail.gmail.com>
	<CAM9w-_nmJP5aTpLqc02GmhpvDmesF5TCiOJkuR6jzTmgs7SuvQ@mail.gmail.com>
	<CANxoLDfWqayrwbZkiCZ2dn+3m75hg+xN+LYzQwqVTb5o=Q21dw@mail.gmail.com>
	<CA+OCxowvnkU-5kZK+ccxv_jGX=2Q_uHEGcYEZ9KEieSMnVuYvA@mail.gmail.com>
	<CAM9w-_nr1mJdnnGj3UBWNNcJCCcFunOzZTu+_q+PeMWixyUnVA@mail.gmail.com>
	<CA+OCxoz7zny2JW3uxft7ZBBeOjm45pWkj_L97Z8n+bUXT3v=OQ@mail.gmail.com>
	<CAM9w-_nJDL0v4xb4U6RJNbpL-frSf7zVou4osB3EA9nqx8CyNA@mail.gmail.com>
	<CAM9w-_nuo6d-xO1a2PASSBLeb9Q2603cXeoGrqUQioCc1=7x_g@mail.gmail.com>

Hi,

Please find the rebased patch from the latest pull.

On Mon, Jan 25, 2021 at 5:12 PM Aditya Toshniwal <
[email protected]> wrote:

> Hi Hackers,
>
> Attached is the patch to fix below issues in ERD:
>
>    1. After opening an existing project, the first table is already
>    selected but edit, clone, delete buttons are disable. Fixed.
>    2. ERD project title gets changed when 2 ERD projects are open &
>    anyone of it edited. Fixed.
>    3. Closing ERD tab, does not ask for confirmation pop up. Added.
>    4. Shortcut for 'Show more/Fewer details' is missing. Added.
>    5. Deleting primary key does not delete associated links. Fixed.
>    6. Long table & schema name are getting out of box. Fixed.
>    7. Long table name in notes pop-up need re-alignment. Fixed.
>    8. Same table name present in ERD/canvas is allowed in Add Table
>    dialogue. Added validation in the dialog.
>    9. Download image option is added, but it is not perfect yet. Image
>    icons (table, schema, etc.) are not showing up.
>    10. Rename panel option should be disabled by default. It should be
>    enabled for the tools which implement rename functionality.
>    11. The Toolbar is not visible in Safari for the ERD tool. Fixed.
>
> Please review.
>
> On Thu, Jan 21, 2021 at 4:32 PM Aditya Toshniwal <
> [email protected]> wrote:
>
>> Hi,
>>
>>
>> On Thu, Jan 21, 2021 at 3:08 PM Dave Page <[email protected]> wrote:
>>
>>>
>>>
>>> On Thu, Jan 21, 2021 at 4:48 AM Aditya Toshniwal <
>>> [email protected]> wrote:
>>>
>>>> Hi Dave,
>>>>
>>>> On Wed, Jan 20, 2021 at 9:20 PM Dave Page <[email protected]> wrote:
>>>>
>>>>> Hi
>>>>>
>>>>> Where's the Save Image button gone? I know Aditya was removing it
>>>>> whilst working on other things, but it's still required for phase 1 release.
>>>>>
>>>> It was not working 100% right. :(
>>>> So I've removed it for the time being. I'm still working on it.
>>>>
>>>
>>> OK, so that work will be completed in time for the build next week?
>>>
>> I'm trying my best to make it available before release. I'm struggling to
>> make it work perfectly.
>>
>>>
>>>
>>>>
>>>>> On Mon, Jan 18, 2021 at 11:45 AM Akshay Joshi <
>>>>> [email protected]> wrote:
>>>>>
>>>>>> Thanks, patch applied.
>>>>>>
>>>>>> On Mon, Jan 18, 2021 at 5:08 PM Aditya Toshniwal <
>>>>>> [email protected]> wrote:
>>>>>>
>>>>>>> OK, So the changes have worked. But still failing at one more place.
>>>>>>> Attached the patch fixes it.
>>>>>>>
>>>>>>> On Mon, Jan 18, 2021 at 4:40 PM Akshay Joshi <
>>>>>>> [email protected]> wrote:
>>>>>>>
>>>>>>>> Thanks, patch applied.
>>>>>>>>
>>>>>>>> On Mon, Jan 18, 2021 at 2:58 PM Aditya Toshniwal <
>>>>>>>> [email protected]> wrote:
>>>>>>>>
>>>>>>>>> Hi,
>>>>>>>>>
>>>>>>>>> The jasmine test cases are working fine on my local machine. The
>>>>>>>>> test cases are successful on jenkins other than on linux, not sure why.
>>>>>>>>> I have made some fixes by looking at the log. Please review and
>>>>>>>>> try.
>>>>>>>>>
>>>>>>>>> On Mon, Jan 18, 2021 at 1:10 PM Akshay Joshi <
>>>>>>>>> [email protected]> wrote:
>>>>>>>>>
>>>>>>>>>> Thanks, patch applied.
>>>>>>>>>>
>>>>>>>>>> On Mon, Jan 18, 2021 at 10:34 AM Aditya Toshniwal <
>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Akshay,
>>>>>>>>>>>
>>>>>>>>>>> I forgot to remove few of the dependencies which are not
>>>>>>>>>>> required as of now (may be in future). Attached patch removes those
>>>>>>>>>>> dependencies from package.json.
>>>>>>>>>>>
>>>>>>>>>>> On Sat, Jan 16, 2021 at 5:08 PM Akshay Joshi <
>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Thanks, patch applied.
>>>>>>>>>>>>
>>>>>>>>>>>> On Fri, Jan 15, 2021 at 7:01 PM Aditya Toshniwal <
>>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>> Hi,
>>>>>>>>>>>>>
>>>>>>>>>>>>> I've fixed the issues. You can find the comments inline.
>>>>>>>>>>>>> I've also added PropTypes for the components for increased
>>>>>>>>>>>>> validation.
>>>>>>>>>>>>>
>>>>>>>>>>>>> On Tue, Jan 12, 2021 at 12:18 PM Khushboo Vashi <
>>>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Hi Aditya,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> The functionalities and the code looks good to me, however some of the comments as below:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - Correct the comments at some places (3 occurrences found  in /erd/__init__.py)  which mention Schema diff instead of ERD.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Some comments in the JS/JSX file regarding components/functions (For example, IconButton (forwardRef), Bodywidget etc.) would
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> be great help as we all are new to React.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Done. Added comments to the components.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - Remove the unused imports (for ex bad_request) in /erd/__init__.py
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Removed.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - Remove commented code
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> # req_args = request.args
>>>>>>>>>>>>>> # if ('recreate' in req_args and
>>>>>>>>>>>>>> #     req_args['recreate'] == '1'):
>>>>>>>>>>>>>> #     connect = False
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Removed.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - TableNode.jsx, below two lines can be combined.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> import { PortModelAlignment, DefaultNodeModel } from
>>>>>>>>>>>>>> '@projectstorm/react-diagrams';
>>>>>>>>>>>>>> import { PortWidget } from '@projectstorm/react-diagrams';
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Done.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - onImageClick function in BodyWidget.jsx is no use I think, so it should be removed.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I wanted to keep the code as it will be used in future.
>>>>>>>>>>>>> Anyway, I've removed the code.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - I got some console errors while adding/editing tables. Refer to the attached screenshot.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I tried but I didn't get any. Looking at the screenshot, the
>>>>>>>>>>>>> error is from the underlying library. Can't do much in this.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - In the column Edit Mode, while deleting the primary key, it gives the error, which does not go away with any further modifications.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Fixed.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - While generating the SQL, if the server is disconnected, a proper error message should be thrown, right now some server side error is coming.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> It will show connection lost error now. Fixed.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - Please remove ... from the menu title (New ERD Project(Beta)...) as it is not opening a dialog.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Done.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - For large data sets, generate ERD hangs.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> It shows the spinner and waits for the response to come from
>>>>>>>>>>>>> the back end. I've used the existing table fetching code which is used at
>>>>>>>>>>>>> other places. I'll create an RM to improve the back end code for fetching
>>>>>>>>>>>>> the tables data which will help the schema diff tool as well.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - Opening the ERD panel in a new window is not working, it opens in the same tab even if you have set the Preference "Open in new browser tab" to True.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Fixed. Added the setting in "Tab settings".
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - No shortcut is provided to open the ERD Tool.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> A shortcut is helpful if we are using it frequently. ERD tool
>>>>>>>>>>>>> won't be used that frequently. We already have a limited
>>>>>>>>>>>>> number of keys available for shortcuts. I think we should roll out without
>>>>>>>>>>>>> shortcut for now. If there is a user demand for it then we can think of
>>>>>>>>>>>>> adding it.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    - SonarQube fixes required.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Fixed.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>    -
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> *Suggestion:*
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> While removal of the FK link, If any of the table is selected, it is being deleted with FK link.
>>>>>>>>>>>>>> Either we should warn the user OR make 2 different buttons for FK removal and table removal as the user may be confused if the selected table is also removed with the FK.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> I've added a confirmation dialog which will show the number
>>>>>>>>>>>>> of tables and links selected. This way user will know what he has selected
>>>>>>>>>>>>> before deleting.
>>>>>>>>>>>>>
>>>>>>>>>>>>>> *Observations:*
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Lodash has been used in this module in place of Underscore,
>>>>>>>>>>>>>> though the dependency is already introduced by another module,
>>>>>>>>>>>>>> but we have mentioned it in the package.json file, which is
>>>>>>>>>>>>>> somewhat not convincing to me.
>>>>>>>>>>>>>> Lodash is more advanced than Underscore but we should pick
>>>>>>>>>>>>>> anyone as it will be easy to manage.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> TL;DR; we cannot.
>>>>>>>>>>>>> lodash is a peer dependency for react-diagrams (and some
>>>>>>>>>>>>> existing modules in pgAdmin) so it will come to package.json without
>>>>>>>>>>>>> choice. We cannot remove underscore because it is a dependency of backbone.
>>>>>>>>>>>>> Underscore is outdated, and I cannot migrate the complete pgAdmin code. So,
>>>>>>>>>>>>> I decided to go with 100/0 method. All the new codes will use lodash only
>>>>>>>>>>>>> as we'll phase out underscore with time. Just like jQuery vs ReactJS.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Table dialog code is duplicate of the table node, as it was
>>>>>>>>>>>>>> difficult to extend it because it was attached to the tree.
>>>>>>>>>>>>>> So, we need to keep in mind that while implementing React in
>>>>>>>>>>>>>> pgAdmin, the nodes should be properly detached from the tree itself, so we
>>>>>>>>>>>>>> can reuse it.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Yes. I agree. We need to separate out data source from UI
>>>>>>>>>>>>> going forward with React.
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Thanks,
>>>>>>>>>>>>>> Khushboo
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On Mon, Dec 28, 2020 at 10:53 AM Khushboo Vashi <
>>>>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> On Fri, Dec 25, 2020 at 4:34 PM Akshay Joshi <
>>>>>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Hi Khushboo,
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> Can you please review it?
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On it.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> On Fri, Dec 25, 2020 at 3:31 PM Aditya Toshniwal <
>>>>>>>>>>>>>>>> [email protected]> wrote:
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Hi Hackers,
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Attached patch introduces ERD Tool(Beta) to pgAdmin. Below
>>>>>>>>>>>>>>>>> are the details:
>>>>>>>>>>>>>>>>> 1) Create a diagram from scratch or generate for an
>>>>>>>>>>>>>>>>> existing DB.
>>>>>>>>>>>>>>>>> 2) Generate "Create" DDL from the diagram.
>>>>>>>>>>>>>>>>> 3) Save the diagram and resume it later.
>>>>>>>>>>>>>>>>> 4) Supports basic table fields, one-to-many relationships,
>>>>>>>>>>>>>>>>> many-to-many relationships, adding notes.
>>>>>>>>>>>>>>>>> 5) Test cases added with 75-80% test coverage.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> Please review.
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>>> Thanks,
>>>>>>>>>>>>>>>>> Aditya Toshniwal
>>>>>>>>>>>>>>>>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>>>>>>>>>>>>>>>>> <http://edbpostgres.com;
>>>>>>>>>>>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> --
>>>>>>>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>>>>>>> *pgAdmin Hacker | Principal Software Architect*
>>>>>>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>> --
>>>>>>>>>>>>> Thanks,
>>>>>>>>>>>>> Aditya Toshniwal
>>>>>>>>>>>>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>>>>>>>>>>>>> <http://edbpostgres.com;
>>>>>>>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>>
>>>>>>>>>>>> --
>>>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>>>> *pgAdmin Hacker | Principal Software Architect*
>>>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>>>>
>>>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> --
>>>>>>>>>>> Thanks,
>>>>>>>>>>> Aditya Toshniwal
>>>>>>>>>>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>>>>>>>>>>> <http://edbpostgres.com;
>>>>>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> --
>>>>>>>>>> *Thanks & Regards*
>>>>>>>>>> *Akshay Joshi*
>>>>>>>>>> *pgAdmin Hacker | Principal Software Architect*
>>>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>>>
>>>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>>>
>>>>>>>>>
>>>>>>>>>
>>>>>>>>> --
>>>>>>>>> Thanks,
>>>>>>>>> Aditya Toshniwal
>>>>>>>>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>>>>>>>>> <http://edbpostgres.com;
>>>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> --
>>>>>>>> *Thanks & Regards*
>>>>>>>> *Akshay Joshi*
>>>>>>>> *pgAdmin Hacker | Principal Software Architect*
>>>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>>>
>>>>>>>> *Mobile: +91 976-788-8246*
>>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> --
>>>>>>> Thanks,
>>>>>>> Aditya Toshniwal
>>>>>>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>>>>>>> <http://edbpostgres.com;
>>>>>>> "Don't Complain about Heat, Plant a TREE"
>>>>>>>
>>>>>>
>>>>>>
>>>>>> --
>>>>>> *Thanks & Regards*
>>>>>> *Akshay Joshi*
>>>>>> *pgAdmin Hacker | Principal Software Architect*
>>>>>> *EDB Postgres <http://edbpostgres.com>*
>>>>>>
>>>>>> *Mobile: +91 976-788-8246*
>>>>>>
>>>>>
>>>>>
>>>>> --
>>>>> Dave Page
>>>>> Blog: http://pgsnake.blogspot.com
>>>>> Twitter: @pgsnake
>>>>>
>>>>> EDB: http://www.enterprisedb.com
>>>>>
>>>>>
>>>>
>>>> --
>>>> Thanks,
>>>> Aditya Toshniwal
>>>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>>>> <http://edbpostgres.com;
>>>> "Don't Complain about Heat, Plant a TREE"
>>>>
>>>
>>>
>>> --
>>> Dave Page
>>> Blog: http://pgsnake.blogspot.com
>>> Twitter: @pgsnake
>>>
>>> EDB: http://www.enterprisedb.com
>>>
>>>
>>
>> --
>> Thanks,
>> Aditya Toshniwal
>> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
>> <http://edbpostgres.com;
>> "Don't Complain about Heat, Plant a TREE"
>>
>
>
> --
> Thanks,
> Aditya Toshniwal
> pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
> <http://edbpostgres.com;
> "Don't Complain about Heat, Plant a TREE"
>


-- 
Thanks,
Aditya Toshniwal
pgAdmin hacker | Sr. Software Engineer | *edbpostgres.com*
<http://edbpostgres.com;
"Don't Complain about Heat, Plant a TREE"


Attachments:

  [application/octet-stream] RM1802.fixes_v2.patch (68.6K, 3-RM1802.fixes_v2.patch)
  download | inline diff:
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 (
     <canvas id={id} ref={chartRef}></canvas>
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(
-      <BodyWidget params={this.params} getDialog={getDialog} transformToSupported={transformToSupported} pgAdmin={pgWindow.pgAdmin} alertify={Alertify} />,
+      <BodyWidget
+        params={this.params}
+        getDialog={getDialog}
+        transformToSupported={transformToSupported}
+        pgWindow={pgWindow}
+        pgAdmin={pgAdmin}
+        panel={panel}
+        alertify={Alertify} />,
       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 => {
           <circle className="svg-link-ele svg-otom-circle" cx="0" cy="16" r={props.width*1.75} strokeWidth={props.width} />
           <polyline className="svg-link-ele" points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
         </>
-      )
+      );
     } else if (type == 'one') {
       return (
         <polyline className="svg-link-ele" points="-8,15 0,15 0,0 0,30 0,15 8,15" fill="none" strokeWidth={props.width} />
-      )
+      );
     }
-  }
+  };
 
   return (
     <g transform={'translate(' + point.getPosition().x + ', ' + point.getPosition().y + ')'}>
@@ -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 {
         </div>
         <div className="ml-auto col-row-port">{this.generatePort(port)}</div>
       </div>
-    )
+    );
   }
 
   generatePort = port => {
     if(port) {
       return (
-        <PortWidget engine={this.props.engine} port={port} key={port.getID()} className={"port-" + port.options.alignment} />
+        <PortWidget engine={this.props.engine} port={port} key={port.getID()} className={'port-' + port.options.alignment} />
       );
     }
     return <></>;
@@ -163,21 +163,21 @@ export class TableNodeWidget extends React.Component {
   render() {
     let node_data = this.props.node.getData();
     return (
-      <div className={"table-node " + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editNode')}}>
+      <div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editNode');}}>
         <div className="table-toolbar">
           <DetailsToggleButton className='btn-xs' showDetails={this.state.show_details} onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}} />
           {this.props.node.getNote() &&
             <IconButton icon="far fa-sticky-note" className="btn-xs btn-warning ml-auto" onClick={()=>{
-              this.props.node.fireEvent({}, 'showNote')
+              this.props.node.fireEvent({}, 'showNote');
             }} title="Check note" />}
         </div>
-        <div className="table-schema">
-          <span className="wcTabIcon icon-schema"></span>
-          {node_data.schema}
+        <div className="d-flex table-schema-data">
+          <div className="table-icon-schema"></div>
+          <div className="table-schema">{node_data.schema}</div>
         </div>
-        <div className="table-name">
-          <span className="wcTabIcon icon-table"></span>
-          {node_data.name}
+        <div className="d-flex table-name-data">
+          <div><span className="wcTabIcon icon-table"></span></div>
+          <div className="table-name">{node_data.name}</div>
         </div>
         <div className="table-cols">
           {_.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..e36aaf2f1 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,10 @@ 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 * as htmlToImage from 'html-to-image';
+
 
 import ERDCore from '../ERDCore';
 import ToolBar, {IconButton, DetailsToggleButton, ButtonGroup} from './ToolBar';
@@ -22,22 +26,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<shortcut_handlers.length; i++){
-        let [key, handler] = shortcut_handlers[i];
+    for(let i=0; i<shortcut_handlers.length; i++){
+      let [key, handler] = shortcut_handlers[i];
+      if(key) {
         this.shortcuts[this.shortcutKey(key.alt, key.control, key.shift, false, key.key.key_code)] = handler;
       }
+    }
   }
 
   shortcutKey(altKey, ctrlKey, shiftKey, metaKey, keyCode) {
@@ -69,9 +76,12 @@ export default class BodyWidget extends React.Component {
       current_file: null,
       dirty: false,
       show_details: true,
+      is_new_tab: false,
       preferences: {},
-    }
+    };
     this.diagram = new ERDCore();
+    /* Flag for checking if user has opted for save before close */
+    this.closeOnSave = React.createRef();
     this.fileInputRef = React.createRef();
     this.diagramContainerRef = React.createRef();
     this.canvasEle = null;
@@ -83,6 +93,7 @@ export default class BodyWidget extends React.Component {
     this.onSaveDiagram = this.onSaveDiagram.bind(this);
     this.onSaveAsDiagram = this.onSaveAsDiagram.bind(this);
     this.onSQLClick = this.onSQLClick.bind(this);
+    this.onImageClick = this.onImageClick.bind(this);
     this.onAddNewNode = this.onAddNewNode.bind(this);
     this.onEditNode = this.onEditNode.bind(this);
     this.onCloneNode = this.onCloneNode.bind(this);
@@ -133,7 +144,7 @@ export default class BodyWidget extends React.Component {
       },
       'editNode': (event) => {
         this.addEditNode(event.node);
-      }
+      },
     };
     Object.keys(diagramEvents).forEach(eventName => {
       this.diagram.registerModelEvent(eventName, diagramEvents[eventName]);
@@ -157,9 +168,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 +194,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 +214,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 +244,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 +375,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)
         + '<br />' + 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 +398,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 +421,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 +431,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 +461,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 +472,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 +492,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 +507,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 +521,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 +601,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 +612,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 +620,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 +633,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 +656,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 +684,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 +707,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 +732,7 @@ export default class BodyWidget extends React.Component {
         <ButtonGroup>
           <IconButton id="open-file" icon="fa fa-folder-open" onClick={this.onLoadDiagram} title={gettext('Load from file')}
             shortcut={this.state.preferences.open_project}/>
-          <IconButton id="save-erd" icon="fa fa-save" onClick={()=>{this.onSaveDiagram()}} title={gettext('Save project')}
+          <IconButton id="save-erd" icon="fa fa-save" onClick={()=>{this.onSaveDiagram();}} title={gettext('Save project')}
             shortcut={this.state.preferences.save_project} disabled={!this.state.dirty}/>
           <IconButton id="save-as-erd" icon="fa fa-share-square" onClick={this.onSaveAsDiagram} title={gettext('Save as')}
             shortcut={this.state.preferences.save_project_as}/>
@@ -612,6 +740,8 @@ export default class BodyWidget extends React.Component {
         <ButtonGroup>
           <IconButton id="save-sql" icon="fa fa-file-code" onClick={this.onSQLClick} title={gettext('Generate SQL')}
             shortcut={this.state.preferences.generate_sql}/>
+          <IconButton id="save-image" icon="fa fa-file-image" onClick={this.onImageClick} title={gettext('Generate SQL')}
+            shortcut={this.state.preferences.generate_sql}/>
         </ButtonGroup>
         <ButtonGroup>
           <IconButton id="add-node" icon="fa fa-plus-square" onClick={this.onAddNewNode} title={gettext('Add table')}
@@ -634,7 +764,8 @@ export default class BodyWidget extends React.Component {
             shortcut={this.state.preferences.add_edit_note} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
           <IconButton id="auto-align" icon="fa fa-magic" onClick={this.onAutoDistribute} title={gettext('Auto align')}
             shortcut={this.state.preferences.auto_align} />
-          <DetailsToggleButton id="more-details" onClick={this.onDetailsToggle} showDetails={this.state.show_details} />
+          <DetailsToggleButton id="more-details" onClick={this.onDetailsToggle} showDetails={this.state.show_details}
+            shortcut={this.state.preferences.show_details} />
         </ButtonGroup>
         <ButtonGroup>
           <IconButton id="zoom-to-fit" icon="fa fa-compress" onClick={this.diagram.zoomToFit} title={gettext('Zoom to fit')}
@@ -654,7 +785,7 @@ export default class BodyWidget extends React.Component {
         reference={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
       <div className="diagram-container" ref={this.diagramContainerRef}>
         <Loader message={this.state.loading_msg} autoEllipsis={true}/>
-        <CanvasWidget className="diagram-canvas flex-grow-1" ref={(ele)=>{this.canvasEle = ele?.ref?.current}} engine={this.diagram.getEngine()} />
+        <CanvasWidget className="diagram-canvas flex-grow-1" ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
       </div>
       </>
     );
@@ -672,10 +803,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">
         </span>
       </div>
       <div className="connection-info btn-group" role="group" aria-label="">
         <div className="editor-title"
           style={{backgroundColor: bgcolor, color: fgcolor}}>
-            {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}
         </div>
       </div>
     </div>
-  )
+  );
 }
 
 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,
           </div>
         </div>
       </div>
-      )}
-      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 (
     <div style={{justifyContent: 'center', marginTop: '0.125rem'}} className="d-flex">
       {keys.map((key, i)=>{
-        return <div key={i} className="shortcut-key">{key}</div>
+        return <div key={i} className="shortcut-key">{key}</div>;
       })}
     </div>
-  )
+  );
 }
 
 const shortcutPropType = PropTypes.shape({
@@ -85,7 +85,7 @@ export const IconButton = forwardRef((props, ref) => {
       </Tippy>
     );
   } else {
-    return <BaseIconButton ref={ref} className='btn btn-sm btn-primary-icon' {...otherProps}/>
+    return <BaseIconButton ref={ref} className='btn btn-sm btn-primary-icon' {...otherProps}/>;
   }
 });
 
@@ -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 (
     <IconButton
       icon={showDetails ? 'far fa-eye' : 'fas fa-low-vision'}
-      title={showDetails ? gettext('Show fewer details') : gettext("Show more details") }
+      title={showDetails ? gettext('Show fewer details') : gettext('Show more details') }
       {...props} />
   );
 }
 
 DetailsToggleButton.propTypes = {
   showDetails: PropTypes.bool,
-}
+};
 
 /* Button group container */
 export function ButtonGroup({className, children}) {
@@ -115,12 +115,12 @@ export function ButtonGroup({className, children}) {
     <div className={'btn-group mr-1 ' + (className ? className : '')} role="group" aria-label="save group">
       {children}
     </div>
-  )
+  );
 }
 
 ButtonGroup.propTypes = {
   className: PropTypes.string,
-}
+};
 
 /* Toolbar container */
 export default function ToolBar({id, children}) {
@@ -128,9 +128,9 @@ export default function ToolBar({id, children}) {
     <div id={id} className="editor-toolbar d-flex" role="toolbar" aria-label="">
       {children}
     </div>
-  )
+  );
 }
 
 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(<BodyWidget params={params} pgAdmin={pgAdmin} getDialog={getDialog} transformToSupported={()=>{}} alertify={alertify}/>);
+    body = mount(<BodyWidget params={params} pgAdmin={pgAdmin} pgWindow={pgWindow} getDialog={getDialog} transformToSupported={()=>{}} 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==
 
[email protected]:
+  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"
+
 [email protected]:
   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 @@ [email protected]:
   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"
 
[email protected], 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 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143"
   integrity sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=
 
[email protected]:
+  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"
+
 [email protected]:
   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:
[email protected], 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"
 
[email protected]:
+  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"
+
 [email protected]:
   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"
+
[email protected], 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==
 
[email protected]:
+  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"
+
 [email protected], 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==
 
[email protected]:
+  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"
+
 [email protected]:
   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==
 
[email protected]:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/rework-visit/-/rework-visit-1.0.0.tgz#9945b2803f219e2f7aca00adb8bc9f640f842c9a"
+  integrity sha1-mUWygD8hni96ygCtuLyfZA+ELJo=
+
[email protected]:
+  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 @@ [email protected]:
   resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.0.tgz#0fe96503ac86a5adb5de63f4e412ae4872cdbe86"
   integrity sha1-D+llA6yGpa213mP05BKuSHLNvoY=
 
[email protected], 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"


view thread (21+ messages)  latest in thread

reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected]
  Subject: Re: [pgAdmin][RM1802] ERD Tool (Beta)
  In-Reply-To: <CAM9w-_kYsHOi+AEDxVKAEG5z9m=OhisSYfPKTDQjG8ogu03DUA@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox