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:12:45 +0530
Message-ID: <CAM9w-_nuo6d-xO1a2PASSBLeb9Q2603cXeoGrqUQioCc1=7x_g@mail.gmail.com> (raw)
In-Reply-To: <CAM9w-_nJDL0v4xb4U6RJNbpL-frSf7zVou4osB3EA9nqx8CyNA@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>

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"


Attachments:

  [application/octet-stream] RM1802.fixes_v1.patch (68.5K, 3-RM1802.fixes_v1.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 143654787..a85e008b6 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 {
         <ButtonGroup>
           <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-_nuo6d-xO1a2PASSBLeb9Q2603cXeoGrqUQioCc1=7x_g@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