diff --git a/docs/en_US/images/erd_1m_dialog.png b/docs/en_US/images/erd_1m_dialog.png
index df6629a7bede455122f7fe455706e301c7323ae3..87ae94acd60be2a699b8fabc6c38907d4251e753 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_mm_dialog.png b/docs/en_US/images/erd_mm_dialog.png
index 25621bde7ab654258195970972decc3d0f99e895..abb414b24b800ed89abd31514b8ce39f0d539260 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_table_dialog.png b/docs/en_US/images/erd_table_dialog.png
index 8eec7f07c4e4d5fdb2682cf689c8c195b2ab80d0..01577c8bf1ac0a76fe80636d31e6396b3782f808 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_table_link.png b/docs/en_US/images/erd_table_link.png
index 3b334bd3e6d55c7bd46caa20cdcfa8c9d25193ab..10698e47d5b6ff46ceb900f93bf2766f87fd94a7 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_table_node.png b/docs/en_US/images/erd_table_node.png
index fafca52c620c1ca8cb5236716c93bdfc9a6e1cc8..78e4ddfacc647d793cf10d25a22a4397d0e0f4dc 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_table_note.png b/docs/en_US/images/erd_table_note.png
index c9c2d914ad38b524f6ff532eb15b3a016be740a7..c29fdf56e639b0ecffec58cc45eae23b24b4b4aa 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_tool.png b/docs/en_US/images/erd_tool.png
index 64591dc7f3f1ad7b4ef533a7faa97eedc120d6a9..c4e51391b46bcbd1aa650c70df28c8c4de82a5d3 100644
GIT binary patch
diff --git a/docs/en_US/images/erd_tool_toolbar.png b/docs/en_US/images/erd_tool_toolbar.png
index 1491dc78becd70db65caf950ba8c4e5870a06016..62ad7aca381a3d56b73be6b535fb3e7806bc9680 100644
GIT binary patch
diff --git a/web/package.json b/web/package.json
index b0c24b834..fe8baf944 100644
--- a/web/package.json
+++ b/web/package.json
@@ -88,7 +88,6 @@
     "@projectstorm/react-diagrams": "^6.6.1",
     "@simonwep/pickr": "^1.5.1",
     "@szhsin/react-menu": "^2.2.0",
-    "@tippyjs/react": "^4.2.0",
     "@types/classnames": "^2.2.6",
     "@types/react": "^16.7.18",
     "@types/react-dom": "^16.0.11",
diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
index 0cb80fe18..4fccec388 100644
--- a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
+++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.js
@@ -279,7 +279,7 @@ define('pgadmin.node.database', [
             t = pgBrowser.tree,
             i = input.item || t.selected(),
             d = i ? t.itemData(i) : undefined;
-          pgBrowser.erd.showErdTool(d, i, true);
+          pgAdmin.Tools.ERD.showErdTool(d, i, true);
         },
 
         /* Connect the database (if not connected), before opening this node */
diff --git a/web/pgadmin/static/img/magic.svg b/web/pgadmin/static/img/magic.svg
new file mode 100644
index 000000000..58753b7e8
--- /dev/null
+++ b/web/pgadmin/static/img/magic.svg
@@ -0,0 +1 @@
+<svg width="18" height="18" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"><path d="M224 96l16-32 32-16-32-16-16-32-16 32-32 16 32 16 16 32zM80 160l26.66-53.33L160 80l-53.34-26.67L80 0 53.34 53.33 0 80l53.34 26.67L80 160zm352 128l-26.66 53.33L352 368l53.34 26.67L432 448l26.66-53.33L512 368l-53.34-26.67L432 288zm70.62-193.77L417.77 9.38C411.53 3.12 403.34 0 395.15 0c-8.19 0-16.38 3.12-22.63 9.38L9.38 372.52c-12.5 12.5-12.5 32.76 0 45.25l84.85 84.85c6.25 6.25 14.44 9.37 22.62 9.37 8.19 0 16.38-3.12 22.63-9.37l363.14-363.15c12.5-12.48 12.5-32.75 0-45.24zM359.45 203.46l-50.91-50.91 86.6-86.6 50.91 50.91-86.6 86.6z"></path></svg>
\ No newline at end of file
diff --git a/web/pgadmin/static/img/sql_file.svg b/web/pgadmin/static/img/sql_file.svg
new file mode 100644
index 000000000..3d6df27c8
--- /dev/null
+++ b/web/pgadmin/static/img/sql_file.svg
@@ -0,0 +1,54 @@
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="18" height="18" viewBox="0 0 548.29 548.291" style="enable-background:new 0 0 548.29 548.291;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M276.043,244.216c-24.575,0-38.741,24.087-38.741,53.862c-0.241,30.228,14.407,53.382,38.5,53.382
+			c24.323,0,38.512-22.92,38.512-54.091C314.313,268.303,300.604,244.216,276.043,244.216z"></path>
+		<path d="M486.2,196.116h-13.164V132.59c0-0.399-0.064-0.795-0.116-1.2c-0.021-2.52-0.824-4.997-2.551-6.96L364.656,3.677
+			c-0.031-0.031-0.064-0.044-0.085-0.075c-0.629-0.704-1.364-1.29-2.141-1.796c-0.231-0.154-0.462-0.283-0.704-0.419
+			c-0.672-0.365-1.386-0.672-2.121-0.893c-0.199-0.052-0.377-0.134-0.576-0.186C358.229,0.118,357.4,0,356.562,0H96.757
+			C84.893,0,75.256,9.649,75.256,21.502v174.613H62.093c-16.967,0-30.733,13.756-30.733,30.733v159.812
+			c0,16.961,13.766,30.731,30.733,30.731h13.163V526.79c0,11.854,9.637,21.501,21.501,21.501h354.777
+			c11.853,0,21.502-9.647,21.502-21.501V417.392H486.2c16.977,0,30.729-13.771,30.729-30.731V226.849
+			C516.93,209.872,503.177,196.116,486.2,196.116z M96.757,21.502h249.053v110.006c0,5.943,4.818,10.751,10.751,10.751h94.973
+			v53.861H96.757V21.502z M353.033,376.96l-10.394,27.884c-22.666-6.619-41.565-13.479-62.828-22.445
+			c-3.527-1.418-7.317-2.132-11.094-2.362c-35.909-2.352-69.449-28.819-69.449-80.778c0-47.711,30.236-83.623,77.71-83.623
+			c48.675,0,75.351,36.854,75.351,80.317c0,36.142-16.766,61.638-37.785,71.091v0.945
+			C326.828,371.528,340.519,374.367,353.033,376.96z M72.912,370.116l7.328-29.764c9.69,4.96,24.554,9.915,39.917,9.915
+			c16.525,0,25.271-6.84,25.271-17.228c0-9.928-7.56-15.597-26.691-22.442c-26.457-9.217-43.696-23.858-43.696-47.014
+			c0-27.163,22.68-47.948,60.231-47.948c17.954,0,31.184,3.791,40.623,8.03l-8.021,29.061c-6.375-3.076-17.711-7.564-33.3-7.564
+			c-15.599,0-23.163,7.079-23.163,15.357c0,10.15,8.977,14.646,29.533,22.447c28.108,10.394,41.332,25.023,41.332,47.464
+			c0,26.699-20.557,49.365-64.253,49.365C99.844,379.785,81.899,375.06,72.912,370.116z M451.534,520.962H96.757v-103.57h354.777
+			V520.962z M475.387,377.428h-99.455V218.231h36.158v128.97h63.297V377.428z"></path>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
\ No newline at end of file
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/ConfirmSaveContent.jsx b/web/pgadmin/static/js/Dialogs/ConfirmSaveContent.jsx
similarity index 89%
rename from web/pgadmin/tools/sqleditor/static/js/components/dialogs/ConfirmSaveContent.jsx
rename to web/pgadmin/static/js/Dialogs/ConfirmSaveContent.jsx
index ef48b8c30..2838c6bec 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/dialogs/ConfirmSaveContent.jsx
+++ b/web/pgadmin/static/js/Dialogs/ConfirmSaveContent.jsx
@@ -1,8 +1,8 @@
 import React from 'react';
-import { useModalStyles } from '../../../../../../static/js/helpers/ModalProvider';
+import { useModalStyles } from '../helpers/ModalProvider';
 import gettext from 'sources/gettext';
 import { Box } from '@material-ui/core';
-import { DefaultButton, PrimaryButton } from '../../../../../../static/js/components/Buttons';
+import { DefaultButton, PrimaryButton } from '../components/Buttons';
 import CloseIcon from '@material-ui/icons/CloseRounded';
 import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
 import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded';
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx
index 8d13115fe..b377544c9 100644
--- a/web/pgadmin/static/js/SchemaView/DataGridView.jsx
+++ b/web/pgadmin/static/js/SchemaView/DataGridView.jsx
@@ -508,7 +508,7 @@ export default function DataGridView({
             setGlobalFilter(value || undefined);
           }}
         />}
-        <div {...getTableProps()} className={classes.table}>
+        <div {...getTableProps(()=>({style: {minWidth: 'unset'}}))} className={classes.table}>
           <DataTableHeader headerGroups={headerGroups} />
           <div {...getTableBodyProps()} className={classes.tableContentWidth}>
             {rows.map((row, i) => {
diff --git a/web/pgadmin/static/js/Theme/dark.js b/web/pgadmin/static/js/Theme/dark.js
index 506434ad6..e36072a6e 100644
--- a/web/pgadmin/static/js/Theme/dark.js
+++ b/web/pgadmin/static/js/Theme/dark.js
@@ -100,7 +100,9 @@ export default function(basicSettings) {
       cardHeaderBg: '#424242',
       colorFg: '#FFFFFF',
       emptySpaceBg: '#212121',
-      textMuted: '#8A8A8A'
+      textMuted: '#8A8A8A',
+      erdCanvasBg: '#303030',
+      erdGridColor: '#444952',
     }
   });
 }
diff --git a/web/pgadmin/static/js/Theme/high_contrast.js b/web/pgadmin/static/js/Theme/high_contrast.js
index 4fbd6c0ca..9c8757370 100644
--- a/web/pgadmin/static/js/Theme/high_contrast.js
+++ b/web/pgadmin/static/js/Theme/high_contrast.js
@@ -98,7 +98,9 @@ export default function(basicSettings) {
       cardHeaderBg: '#062F57',
       colorFg: '#FFFFFF',
       emptySpaceBg: '#010B15',
-      textMuted: '#8b9cad'
+      textMuted: '#8b9cad',
+      erdCanvasBg: '#010B15',
+      erdGridColor: '#1F2932',
     }
   });
 }
diff --git a/web/pgadmin/static/js/Theme/standard.js b/web/pgadmin/static/js/Theme/standard.js
index ddceb8b80..66e24ee3f 100644
--- a/web/pgadmin/static/js/Theme/standard.js
+++ b/web/pgadmin/static/js/Theme/standard.js
@@ -106,6 +106,8 @@ export default function(basicSettings) {
       cardHeaderBg: '#fff',
       emptySpaceBg: '#ebeef3',
       textMuted: '#646B82',
+      erdCanvasBg: '#fff',
+      erdGridColor: '#bac1cd',
       explain: {
         sev2: {
           color: '#222222',
diff --git a/web/pgadmin/static/js/components/ExternalIcon.jsx b/web/pgadmin/static/js/components/ExternalIcon.jsx
index b5c0fee90..94d0b4603 100644
--- a/web/pgadmin/static/js/components/ExternalIcon.jsx
+++ b/web/pgadmin/static/js/components/ExternalIcon.jsx
@@ -17,6 +17,8 @@ import Collapse from '../../img/fonticon/close_fullscreen.svg?svgr';
 import AWS from '../../img/aws.svg?svgr';
 import BigAnimal from '../../img/biganimal.svg?svgr';
 import Azure from '../../img/azure.svg?svgr';
+import SQLFileSvg from '../../img/sql_file.svg?svgr';
+import MagicSvg from '../../img/magic.svg?svgr';
 
 export default function ExternalIcon({Icon, ...props}) {
   return <Icon className={'MuiSvgIcon-root'} {...props} />;
@@ -75,4 +77,10 @@ export const BigAnimalIcon = ({style})=><ExternalIcon Icon={BigAnimal} style={{h
 BigAnimalIcon.propTypes = {style: PropTypes.object};
 
 export const AzureIcon = ({style})=><ExternalIcon Icon={Azure} style={{height: '1.4rem', ...style}} data-label="AzureIcon" />;
-AzureIcon.propTypes = {style: PropTypes.object};
\ No newline at end of file
+AzureIcon.propTypes = {style: PropTypes.object};
+
+export const SQLFileIcon = ({style})=><ExternalIcon Icon={SQLFileSvg} style={{height: '1rem', ...style}} data-label="SQLFileIcon" />;
+SQLFileIcon.propTypes = {style: PropTypes.object};
+
+export const MagicIcon = ({style})=><ExternalIcon Icon={MagicSvg} style={{height: '1rem', ...style}} data-label="MagicIcon" />;
+MagicIcon.propTypes = {style: PropTypes.object};
diff --git a/web/pgadmin/static/js/components/Loader.jsx b/web/pgadmin/static/js/components/Loader.jsx
index 0232090be..48dd7f958 100644
--- a/web/pgadmin/static/js/components/Loader.jsx
+++ b/web/pgadmin/static/js/components/Loader.jsx
@@ -40,7 +40,7 @@ const useStyles = makeStyles((theme)=>({
   }
 }));
 
-export default function Loader({message, style, ...props}) {
+export default function Loader({message, style, autoEllipsis=false, ...props}) {
   const classes = useStyles();
   if(!message) {
     return <></>;
@@ -49,7 +49,7 @@ export default function Loader({message, style, ...props}) {
     <Box className={classes.root} style={style} data-label="loader" {...props}>
       <Box className={classes.loaderRoot}>
         <CircularProgress className={classes.loader} />
-        <Typography className={classes.message}>{message}</Typography>
+        <Typography className={classes.message}>{message}{autoEllipsis ? '...':''}</Typography>
       </Box>
     </Box>
   );
@@ -58,4 +58,5 @@ export default function Loader({message, style, ...props}) {
 Loader.propTypes = {
   message: PropTypes.string,
   style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
+  autoEllipsis: PropTypes.bool,
 };
diff --git a/web/pgadmin/static/js/helpers/ModalProvider.jsx b/web/pgadmin/static/js/helpers/ModalProvider.jsx
index b7b9ee867..be18ded0d 100644
--- a/web/pgadmin/static/js/helpers/ModalProvider.jsx
+++ b/web/pgadmin/static/js/helpers/ModalProvider.jsx
@@ -98,7 +98,7 @@ export default function ModalProvider({ children }) {
   const [modals, setModals] = React.useState([]);
 
   const showModal = (title, content, modalOptions) => {
-    let id = getEpoch().toString() + crypto.getRandomValues(new Uint8Array(1));
+    let id = getEpoch().toString() + crypto.getRandomValues(new Uint8Array(4));
     setModals((prev) => [...prev, {
       id: id,
       title: title,
diff --git a/web/pgadmin/static/scss/_tippy.overrides.scss b/web/pgadmin/static/scss/_tippy.overrides.scss
deleted file mode 100644
index 27e959dbb..000000000
--- a/web/pgadmin/static/scss/_tippy.overrides.scss
+++ /dev/null
@@ -1,9 +0,0 @@
-@import "node_modules/tippy.js/dist/tippy.css";
-
-.tippy-box {
-  background-color: $popover-bg;
-  color: $popover-body-color;
-  .tippy-arrow {
-    color: $popover-bg;
-  }
-}
diff --git a/web/pgadmin/static/scss/pgadmin.scss b/web/pgadmin/static/scss/pgadmin.scss
index 78e3eecd0..892b370a5 100644
--- a/web/pgadmin/static/scss/pgadmin.scss
+++ b/web/pgadmin/static/scss/pgadmin.scss
@@ -31,7 +31,6 @@ $theme-colors: (
 @import 'pgadmin.style';
 @import 'bootstrap4-toggle.overrides';
 @import 'pickr.overrides';
-@import 'tippy.overrides';
 @import 'jsoneditor.overrides';
 @import 'pgadmin4-tree.overrides';
 @import 'pgadmin4-tree/src/css/styles';
diff --git a/web/pgadmin/tools/erd/__init__.py b/web/pgadmin/tools/erd/__init__.py
index b1be8233e..5052fb544 100644
--- a/web/pgadmin/tools/erd/__init__.py
+++ b/web/pgadmin/tools/erd/__init__.py
@@ -399,8 +399,6 @@ def panel(trans_id):
     if 'gen' in params:
         params['gen'] = True if params['gen'] == 'true' else False
 
-    close_url = request.form['close_url']
-
     # We need client OS information to render correct Keyboard shortcuts
     user_agent = UserAgent(request.headers.get('User-Agent'))
 
@@ -439,7 +437,6 @@ def panel(trans_id):
     return render_template(
         "erd/index.html",
         title=underscore_unescape(params['title']),
-        close_url=close_url,
         requirejs=True,
         basejs=True,
         params=json.dumps(params),
diff --git a/web/pgadmin/tools/erd/static/js/ERDModule.js b/web/pgadmin/tools/erd/static/js/ERDModule.js
new file mode 100644
index 000000000..59549feff
--- /dev/null
+++ b/web/pgadmin/tools/erd/static/js/ERDModule.js
@@ -0,0 +1,255 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import pgWindow from 'sources/window';
+import {getPanelTitle} from 'tools/sqleditor/static/js/sqleditor_title';
+import {getRandomInt, registerDetachEvent} from 'sources/utils';
+import Notify from '../../../../static/js/helpers/Notifier';
+import url_for from 'sources/url_for';
+import gettext from 'sources/gettext';
+import React from 'react';
+import ReactDOM from 'react-dom';
+import ERDTool from './erd_tool/components/ERDTool';
+import ModalProvider from '../../../../static/js/helpers/ModalProvider';
+import Theme from '../../../../static/js/Theme';
+import $ from 'jquery';
+
+const wcDocker = window.wcDocker;
+
+export function setPanelTitle(erdToolPanel, panelTitle) {
+  erdToolPanel?.title('<span title="'+panelTitle+'">'+panelTitle+'</span>');
+}
+export default class ERDModule {
+  static instance;
+
+  static getInstance(...args) {
+    if(!ERDModule.instance) {
+      ERDModule.instance = new ERDModule(...args);
+    }
+    return ERDModule.instance;
+  }
+
+  constructor(pgAdmin, pgBrowser) {
+    this.pgAdmin = pgAdmin;
+    this.pgBrowser = pgBrowser;
+  }
+
+
+  init() {
+    if (this.initialized)
+      return;
+
+    this.initialized = true;
+
+    // Define the nodes on which the menus to be appear
+    this.pgBrowser.add_menus([{
+      name: 'erd',
+      module: this,
+      applies: ['tools'],
+      callback: 'showErdTool',
+      priority: 1,
+      label: gettext('ERD Tool'),
+      enable: this.erdToolEnabled,
+      data: {
+        data_disabled: gettext('The selected tree node does not support this option.'),
+      },
+    }]);
+
+    // let self = this;
+    // /* Cache may take time to load for the first time
+    //  * Keep trying till available
+    //  */
+    // let cacheIntervalId = setInterval(function() {
+    //   if(this.pgBrowser.preference_version() > 0) {
+    //     self.preferences = this.pgBrowser.get_preferences_for_module('erd');
+    //     clearInterval(cacheIntervalId);
+    //   }
+    // },0);
+
+    // this.pgBrowser.onPreferencesChange('erd', function() {
+    //   self.preferences = this.pgBrowser.get_preferences_for_module('erd');
+    // });
+
+    // Creating a new this.pgBrowser frame to show the data.
+    const erdFrameType = new this.pgBrowser.Frame({
+      name: 'frm_erdtool',
+      showTitle: true,
+      isCloseable: true,
+      isPrivate: true,
+      url: 'about:blank',
+    });
+
+    // Load the newly created frame
+    erdFrameType.load(this.pgBrowser.docker);
+    return this;
+  }
+
+  erdToolEnabled(obj) {
+    /* Same as query tool */
+    return (() => {
+      if (!_.isUndefined(obj) && !_.isNull(obj)) {
+        if (_.indexOf(this.pgAdmin.unsupported_nodes, obj._type) == -1) {
+          if (obj._type == 'database' && obj.allowConn) {
+            return true;
+          } else if (obj._type != 'database') {
+            return true;
+          } else {
+            return false;
+          }
+        } else {
+          return false;
+        }
+      } else {
+        return false;
+      }
+    })();
+  }
+
+  // Callback to draw ERD Tool for objects
+  showErdTool(_data, treeIdentifier, gen=false) {
+    if (treeIdentifier === undefined) {
+      Notify.alert(
+        gettext('ERD Error'),
+        gettext('No object selected.')
+      );
+      return;
+    }
+
+    const parentData = this.pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
+
+    if(_.isUndefined(parentData.database)) {
+      Notify.alert(
+        gettext('ERD Error'),
+        gettext('Please select a database/database object.')
+      );
+      return;
+    }
+
+    const transId = getRandomInt(1, 9999999);
+    const panelTitle = getPanelTitle(this.pgBrowser, treeIdentifier);
+    const panelUrl = this.getPanelUrl(transId, parentData, gen);
+
+    let erdToolForm = `
+      <form id="erdToolForm" action="${panelUrl}" method="post">
+        <input id="title" name="title" hidden />
+      </form>
+      <script>
+        document.getElementById("title").value = "${_.escape(panelTitle)}";
+        document.getElementById("erdToolForm").submit();
+      </script>
+    `;
+
+    var open_new_tab = this.pgBrowser.get_preferences_for_module('browser').new_browser_tab_open;
+    if (open_new_tab && open_new_tab.includes('erd_tool')) {
+      var newWin = window.open('', '_blank');
+      newWin.document.write(erdToolForm);
+      newWin.document.title = panelTitle;
+      // Send the signal to runtime, so that proper zoom level will be set.
+      setTimeout(function() {
+        this.pgBrowser.send_signal_to_runtime('Runtime new window opened');
+      }, 500);
+    } else {
+      /* On successfully initialization find the dashboard panel,
+       * create new panel and add it to the dashboard panel.
+       */
+      var propertiesPanel = this.pgBrowser.docker.findPanels('properties');
+      var erdToolPanel = this.pgBrowser.docker.addPanel('frm_erdtool', wcDocker.DOCK.STACKED, propertiesPanel[0]);
+
+      // Set panel title and icon
+      setPanelTitle(erdToolPanel, 'Untitled');
+      erdToolPanel.icon('fa fa-sitemap');
+      erdToolPanel.focus();
+
+      // Register detach event.
+      registerDetachEvent(erdToolPanel);
+      var openErdToolURL = function(j) {
+        // add spinner element
+        let $spinner_el =
+          $(`<div class="pg-sp-container">
+                <div class="pg-sp-content">
+                    <div class="row">
+                        <div class="col-12 pg-sp-icon"></div>
+                    </div>
+                </div>
+            </div>`).appendTo($(j).data('embeddedFrame').$container);
+
+        let init_poller_id = setInterval(function() {
+          var frameInitialized = $(j).data('frameInitialized');
+          if (frameInitialized) {
+            clearInterval(init_poller_id);
+            var frame = $(j).data('embeddedFrame');
+            if (frame) {
+              frame.onLoaded(()=>{
+                $spinner_el.remove();
+              });
+              frame.openHTML(erdToolForm);
+            }
+          }
+        }, 100);
+      };
+
+      openErdToolURL(erdToolPanel);
+    }
+  }
+
+  getPanelUrl(transId, parentData, gen) {
+    let openUrl = url_for('erd.panel', {
+      trans_id: transId,
+    });
+
+    openUrl += `?sgid=${parentData.server_group._id}`
+      +`&sid=${parentData.server._id}`
+      +`&server_type=${parentData.server.server_type}`
+      +`&did=${parentData.database._id}`
+      +`&gen=${gen}`;
+
+    return openUrl;
+  }
+
+  setupPreferencesWorker() {
+    if (window.location == window.parent?.location) {
+      /* Sync the local preferences with the main window if in new tab */
+      setInterval(()=>{
+        if(pgWindow?.pgAdmin) {
+          if(this.pgAdmin.Browser.preference_version() < pgWindow.pgAdmin.Browser.preference_version()){
+            this.pgAdmin.Browser.preferences_cache = pgWindow.pgAdmin.Browser.preferences_cache;
+            this.pgAdmin.Browser.preference_version(pgWindow.pgAdmin.Browser.preference_version());
+            this.pgAdmin.Browser.triggerPreferencesChange('browser');
+            this.pgAdmin.Browser.triggerPreferencesChange('erd');
+          }
+        }
+      }, 1000);
+    }
+  }
+
+  loadComponent(container, params) {
+    let panel = null;
+
+    /* Mount the React ERD tool to the container */
+    _.each(pgWindow.pgAdmin.Browser.docker.findPanels('frm_erdtool'), function(p) {
+      if (p.isVisible()) {
+        panel = p;
+      }
+    });
+
+    this.setupPreferencesWorker();
+    ReactDOM.render(
+      <Theme>
+        <ModalProvider>
+          <ERDTool
+            params={params}
+            pgWindow={pgWindow}
+            pgAdmin={this.pgAdmin}
+            panel={panel}
+          />
+        </ModalProvider>
+      </Theme>,
+      container
+    );
+  }
+}
diff --git a/web/pgadmin/tools/erd/static/js/erd_module.js b/web/pgadmin/tools/erd/static/js/erd_module.js
deleted file mode 100644
index 900025e5f..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_module.js
+++ /dev/null
@@ -1,217 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import {getPanelTitle} from 'tools/sqleditor/static/js/sqleditor_title';
-import {getRandomInt, registerDetachEvent} from 'sources/utils';
-import Notify from '../../../../static/js/helpers/Notifier';
-
-
-export function setPanelTitle(erdToolPanel, panelTitle) {
-  erdToolPanel.title('<span title="'+panelTitle+'">'+panelTitle+'</span>');
-}
-
-export function initialize(gettext, url_for, $, _, pgAdmin, csrfToken, pgBrowser, wcDocker) {
-  /* Return back, this has been called more than once */
-  if (pgBrowser.erd)
-    return pgBrowser.erd;
-
-  pgBrowser.erd = {
-    init: function() {
-      if (this.initialized)
-        return;
-
-      this.initialized = true;
-      csrfToken.setPGCSRFToken(pgAdmin.csrf_token_header, pgAdmin.csrf_token);
-
-
-      // Define the nodes on which the menus to be appear
-      var menus = [{
-        name: 'erd',
-        module: this,
-        applies: ['tools'],
-        callback: 'showErdTool',
-        priority: 1,
-        label: gettext('ERD Tool'),
-        enable: this.erdToolEnabled,
-        data: {
-          data_disabled: gettext('The selected tree node does not support this option.'),
-        },
-      }];
-
-      pgBrowser.add_menus(menus);
-
-      // Creating a new pgBrowser frame to show the data.
-      var erdFrameType = new pgBrowser.Frame({
-        name: 'frm_erdtool',
-        showTitle: true,
-        isCloseable: true,
-        isPrivate: true,
-        url: 'about:blank',
-      });
-
-      let self = this;
-      /* Cache may take time to load for the first time
-       * Keep trying till available
-       */
-      let cacheIntervalId = setInterval(function() {
-        if(pgBrowser.preference_version() > 0) {
-          self.preferences = pgBrowser.get_preferences_for_module('erd');
-          clearInterval(cacheIntervalId);
-        }
-      },0);
-
-      pgBrowser.onPreferencesChange('erd', function() {
-        self.preferences = pgBrowser.get_preferences_for_module('erd');
-      });
-
-      // Load the newly created frame
-      erdFrameType.load(pgBrowser.docker);
-      return this;
-    },
-
-    erdToolEnabled: function(obj) {
-      /* Same as query tool */
-      return (() => {
-        if (!_.isUndefined(obj) && !_.isNull(obj)) {
-          if (_.indexOf(pgAdmin.unsupported_nodes, obj._type) == -1) {
-            if (obj._type == 'database' && obj.allowConn) {
-              return true;
-            } else if (obj._type != 'database') {
-              return true;
-            } else {
-              return false;
-            }
-          } else {
-            return false;
-          }
-        } else {
-          return false;
-        }
-      })();
-    },
-
-    // Callback to draw ERD Tool for objects
-    showErdTool: function(data, treeIdentifier, gen=false) {
-      if (treeIdentifier === undefined) {
-        Notify.alert(
-          gettext('ERD Error'),
-          gettext('No object selected.')
-        );
-        return;
-      }
-
-      const parentData = pgBrowser.tree.getTreeNodeHierarchy(treeIdentifier);
-
-      if(_.isUndefined(parentData.database)) {
-        Notify.alert(
-          gettext('ERD Error'),
-          gettext('Please select a database/database object.')
-        );
-        return;
-      }
-
-      const transId = getRandomInt(1, 9999999);
-      const panelTitle = getPanelTitle(pgBrowser, treeIdentifier);
-      const [panelUrl, panelCloseUrl] = this.getPanelUrls(transId, panelTitle, parentData, gen);
-
-      let erdToolForm = `
-        <form id="erdToolForm" action="${panelUrl}" method="post">
-          <input id="title" name="title" hidden />
-          <input name="close_url" value="${panelCloseUrl}" hidden />
-        </form>
-        <script>
-          document.getElementById("title").value = "${_.escape(panelTitle)}";
-          document.getElementById("erdToolForm").submit();
-        </script>
-      `;
-
-      var open_new_tab = pgBrowser.get_preferences_for_module('browser').new_browser_tab_open;
-      if (open_new_tab && open_new_tab.includes('erd_tool')) {
-        var newWin = window.open('', '_blank');
-        newWin.document.write(erdToolForm);
-        newWin.document.title = panelTitle;
-        // Send the signal to runtime, so that proper zoom level will be set.
-        setTimeout(function() {
-          pgBrowser.send_signal_to_runtime('Runtime new window opened');
-        }, 500);
-      } else {
-        /* On successfully initialization find the dashboard panel,
-         * create new panel and add it to the dashboard panel.
-         */
-        var propertiesPanel = pgBrowser.docker.findPanels('properties');
-        var erdToolPanel = pgBrowser.docker.addPanel('frm_erdtool', wcDocker.DOCK.STACKED, propertiesPanel[0]);
-
-        // Set panel title and icon
-        setPanelTitle(erdToolPanel, 'Untitled');
-        erdToolPanel.icon('fa fa-sitemap');
-        erdToolPanel.focus();
-
-        // Listen on the panel closed event.
-        erdToolPanel.on(wcDocker.EVENT.CLOSED, function() {
-          $.ajax({
-            url: panelCloseUrl,
-            method: 'DELETE',
-          });
-        });
-        // Register detach event.
-        registerDetachEvent(erdToolPanel);
-        var openErdToolURL = function(j) {
-          // add spinner element
-          let $spinner_el =
-            $(`<div class="pg-sp-container">
-                  <div class="pg-sp-content">
-                      <div class="row">
-                          <div class="col-12 pg-sp-icon"></div>
-                      </div>
-                  </div>
-              </div>`).appendTo($(j).data('embeddedFrame').$container);
-
-          let init_poller_id = setInterval(function() {
-            var frameInitialized = $(j).data('frameInitialized');
-            if (frameInitialized) {
-              clearInterval(init_poller_id);
-              var frame = $(j).data('embeddedFrame');
-              if (frame) {
-                frame.onLoaded(()=>{
-                  $spinner_el.remove();
-                });
-                frame.openHTML(erdToolForm);
-              }
-            }
-          }, 100);
-        };
-
-        openErdToolURL(erdToolPanel);
-      }
-    },
-
-    getPanelUrls: function(transId, panelTitle, parentData, gen) {
-      let openUrl = url_for('erd.panel', {
-        trans_id: transId,
-      });
-
-      openUrl += `?sgid=${parentData.server_group._id}`
-        +`&sid=${parentData.server._id}`
-        +`&server_type=${parentData.server.server_type}`
-        +`&did=${parentData.database._id}`
-        +`&gen=${gen}`;
-
-      let closeUrl = url_for('erd.close', {
-        trans_id: transId,
-        sgid: parentData.server_group._id,
-        sid: parentData.server._id,
-        did: parentData.database._id,
-      });
-
-      return [openUrl, closeUrl];
-    },
-  };
-
-  return pgBrowser.erd;
-}
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js b/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js
new file mode 100644
index 000000000..323e08155
--- /dev/null
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/ERDConstants.js
@@ -0,0 +1,21 @@
+export const ERD_EVENTS = {
+  LOAD_DIAGRAM: 'LOAD_DIAGRAM',
+  SAVE_DIAGRAM: 'SAVE_DIAGRAM',
+  SHOW_SQL: 'SHOW_SQL',
+  DOWNLOAD_IMAGE: 'DOWNLOAD_IMAGE',
+  ADD_NODE: 'ADD_NODE',
+  EDIT_NODE: 'EDIT_NODE',
+  CLONE_NODE: 'CLONE_NODE',
+  DELETE_NODE: 'DELETE_NODE',
+  SHOW_NOTE: 'SHOW_NOTE',
+  ONE_TO_MANY: 'ONE_TO_MANY',
+  MANY_TO_MANY: 'MANY_TO_MANY',
+  AUTO_DISTRIBUTE: 'AUTO_DISTRIBUTE',
+  TOGGLE_DETAILS: 'TOGGLE_DETAILS',
+  ZOOM_FIT: 'ZOOM_FIT',
+  ZOOM_IN: 'ZOOM_IN',
+  ZOOM_OUT: 'ZOOM_OUT',
+  SINGLE_NODE_SELECTED: 'SINGLE_NODE_SELECTED',
+  ANY_ITEM_SELECTED: 'ANY_ITEM_SELECTED',
+  DIRTY: 'DIRTY',
+};
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/ConnectionBar.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/ConnectionBar.jsx
new file mode 100644
index 000000000..7640a7e4f
--- /dev/null
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/ConnectionBar.jsx
@@ -0,0 +1,100 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import { DefaultButton, PgButtonGroup, PgIconButton } from '../../../../../../static/js/components/Buttons';
+import { Box, makeStyles, Tooltip, CircularProgress } from '@material-ui/core';
+import { ConnectedIcon, DisonnectedIcon } from '../../../../../../static/js/components/ExternalIcon';
+
+export const STATUS = {
+  CONNECTED: 1,
+  DISCONNECTED: 2,
+  CONNECTING: 3,
+  FAILED: 4,
+};
+
+function ConnectionStatusIcon({status}) {
+  if(status == STATUS.CONNECTING) {
+    return <CircularProgress style={{height: '18px', width: '18px'}} />;
+  } else if(status == STATUS.CONNECTED || status == STATUS.FAILED) {
+    return <ConnectedIcon />;
+  } else {
+    return <DisonnectedIcon />;
+  }
+}
+
+ConnectionStatusIcon.propTypes = {
+  status: PropTypes.oneOf(Object.values(STATUS)).isRequired,
+};
+
+const useStyles = makeStyles((theme)=>({
+  root: {
+    padding: '2px 4px',
+    display: 'flex',
+    alignItems: 'center',
+    gap: '4px',
+    backgroundColor: theme.otherVars.editorToolbarBg,
+    flexWrap: 'wrap',
+  },
+  connectionButton: {
+    display: 'flex',
+    width: '450px',
+    backgroundColor: theme.palette.default.main,
+    color: theme.palette.default.contrastText,
+    border: '1px solid ' + theme.palette.default.borderColor,
+    justifyContent: 'flex-start',
+  },
+}));
+
+/* The connection bar component */
+export default function ConnectionBar({status, bgcolor, fgcolor, title}) {
+  const classes = useStyles();
+  const connTitle = useMemo(()=>{
+    if(status == STATUS.CONNECTED) {
+      return gettext('Connected');
+    } else if(status == STATUS.CONNECTING) {
+      return gettext('Connecting');
+    } else if(status == STATUS.DISCONNECTED) {
+      return gettext('Disconnected');
+    } else if(status == STATUS.FAILED) {
+      return gettext('Failed');
+    }
+  }, [status]);
+  return (
+    <Box className={classes.root}>
+      <PgButtonGroup size="small">
+        <PgIconButton
+          title={connTitle}
+          icon={<ConnectionStatusIcon status={status} />}
+          data-test="btn-conn-status"
+        />
+        <DefaultButton className={classes.connectionButton} style={{backgroundColor: bgcolor, color: fgcolor}} data-test="btn-conn-title">
+          <Tooltip title={title}>
+            <Box display="flex" width="100%">
+              <Box textOverflow="ellipsis" overflow="hidden" marginRight="auto">
+                {status == STATUS.CONNECTING && (gettext('(Obtaining connection...)')+' ')}
+                {status == STATUS.FAILED && (gettext('(Connection failed)')+' ')}
+                {title}
+              </Box>
+            </Box>
+          </Tooltip>
+        </DefaultButton>
+      </PgButtonGroup>
+    </Box>
+  );
+}
+
+ConnectionBar.propTypes = {
+  status: PropTypes.oneOf(Object.values(STATUS)).isRequired,
+  bgcolor: PropTypes.string,
+  fgcolor: PropTypes.string,
+  title: PropTypes.string.isRequired,
+};
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx
similarity index 71%
rename from web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx
rename to web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx
index 6427292d3..5ec3f10f0 100644
--- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/BodyWidget.jsx
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/ERDTool.jsx
@@ -9,25 +9,29 @@
 
 import * as React from 'react';
 import { CanvasWidget, Action, InputType } from '@projectstorm/react-canvas-core';
-import axios from 'axios';
 import PropTypes from 'prop-types';
 import _ from 'lodash';
 import html2canvas from 'html2canvas';
 
 import ERDCore from '../ERDCore';
-import ToolBar, {IconButton, DetailsToggleButton, ButtonGroup} from './ToolBar';
 import ConnectionBar, { STATUS as CONNECT_STATUS } from './ConnectionBar';
-import Loader from './Loader';
 import FloatingNote from './FloatingNote';
-import {setPanelTitle} from '../../erd_module';
+import {setPanelTitle} from '../../ERDModule';
 import gettext from 'sources/gettext';
 import url_for from 'sources/url_for';
 import {showERDSqlTool} from 'tools/sqleditor/static/js/show_query_tool';
 import 'wcdocker';
-import Theme from '../../../../../../static/js/Theme';
 import TableSchema from '../../../../../../browser/server_groups/servers/databases/schemas/tables/static/js/table.ui';
 import Notify from '../../../../../../static/js/helpers/Notifier';
 import { ModalContext } from '../../../../../../static/js/helpers/ModalProvider';
+import ERDDialogs from '../dialogs';
+import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
+import Loader from '../../../../../../static/js/components/Loader';
+import { MainToolBar } from './MainToolBar';
+import { Box, withStyles } from '@material-ui/core';
+import EventBus from '../../../../../../static/js/helpers/EventBus';
+import { ERD_EVENTS } from '../ERDConstants';
+import getApiInstance, { parseApiError } from '../../../../../../static/js/api_instance';
 
 /* Custom react-diagram action for keyboard events */
 export class KeyboardShortcutAction extends Action {
@@ -60,11 +64,41 @@ export class KeyboardShortcutAction extends Action {
   }
 }
 
+const getCanvasGrid = (theme)=>{
+  let erdCanvasBg = encodeURIComponent(theme.otherVars.erdCanvasBg);
+  let erdGridColor = encodeURIComponent(theme.otherVars.erdGridColor);
+
+  return `url("data:image/svg+xml, %3Csvg width='100%25' viewBox='0 0 45 45' style='background-color:${erdCanvasBg}' height='100%25' xmlns='http://www.w3.org/2000/svg'%3E%3Cdefs%3E%3Cpattern id='smallGrid' width='15' height='15' patternUnits='userSpaceOnUse'%3E%3Cpath d='M 15 0 L 0 0 0 15' fill='none' stroke='${erdGridColor}' stroke-width='0.5'/%3E%3C/pattern%3E%3Cpattern id='grid' width='45' height='45' patternUnits='userSpaceOnUse'%3E%3Crect width='100' height='100' fill='url(%23smallGrid)'/%3E%3Cpath d='M 100 0 L 0 0 0 100' fill='none' stroke='${erdGridColor}' stroke-width='1'/%3E%3C/pattern%3E%3C/defs%3E%3Crect width='100%25' height='100%25' fill='url(%23grid)' /%3E%3C/svg%3E%0A")`;
+};
+
+const styles = ((theme)=>({
+  diagramContainer: {
+    position: 'relative',
+    width: '100%',
+    height: '100%',
+    minHeight: 0,
+  },
+  diagramCanvas: {
+    width: '100%',
+    height: '100%',
+    color: theme.palette.text.primary,
+    fontFamily: 'sans-serif',
+    backgroundColor: theme.otherVars.erdCanvasBg,
+    backgroundImage: getCanvasGrid(theme),
+    cursor: 'unset',
+    flexGrow: 1,
+  },
+  html2canvasReset: {
+    backgroundImage: 'none !important',
+    overflow: 'auto !important',
+  }
+}));
+
 /* The main body container for the ERD */
-export default class BodyWidget extends React.Component {
+class ERDTool extends React.Component {
   static contextType = ModalContext;
-  constructor() {
-    super();
+  constructor(props) {
+    super(props);
     this.state = {
       conn_status: CONNECT_STATUS.DISCONNECTED,
       server_version: null,
@@ -89,11 +123,16 @@ export default class BodyWidget extends React.Component {
     /* Flag for checking if user has opted for save before close */
     this.closeOnSave = React.createRef();
     this.fileInputRef = React.createRef();
+    this.containerRef = React.createRef();
     this.diagramContainerRef = React.createRef();
     this.canvasEle = null;
     this.noteRefEle = null;
     this.noteNode = null;
     this.keyboardActionObj = null;
+    this.erdDialogs = new ERDDialogs(this.context);
+    this.apiObj = getApiInstance();
+
+    this.eventBus = new EventBus();
 
     _.bindAll(this, ['onLoadDiagram', 'onSaveDiagram', 'onSaveAsDiagram', 'onSQLClick',
       'onImageClick', 'onAddNewNode', 'onEditTable', 'onCloneNode', 'onDeleteNode', 'onNoteClick',
@@ -125,22 +164,29 @@ export default class BodyWidget extends React.Component {
             singleNodeSelected = true;
           }
         }
+        const anyItemSelected = this.diagram.getSelectedNodes().length > 0 || this.diagram.getSelectedLinks().length > 0;
         this.setState({
           single_node_selected: singleNodeSelected,
-          any_item_selected: this.diagram.getSelectedNodes().length > 0 || this.diagram.getSelectedLinks().length > 0,
+          any_item_selected: anyItemSelected,
         });
+        this.eventBus.fireEvent(ERD_EVENTS.SINGLE_NODE_SELECTED, singleNodeSelected);
+        this.eventBus.fireEvent(ERD_EVENTS.ANY_ITEM_SELECTED, anyItemSelected);
       },
       'linksSelectionChanged': ()=>{
+        const anyItemSelected = this.diagram.getSelectedNodes().length > 0 || this.diagram.getSelectedLinks().length > 0;
         this.setState({
           single_link_selected: this.diagram.getSelectedLinks().length == 1,
           any_item_selected: this.diagram.getSelectedNodes().length > 0 || this.diagram.getSelectedLinks().length > 0,
         });
+        this.eventBus.fireEvent(ERD_EVENTS.ANY_ITEM_SELECTED, anyItemSelected);
       },
       'linksUpdated': () => {
         this.setState({dirty: true});
+        this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true);
       },
       'nodesUpdated': ()=>{
         this.setState({dirty: true});
+        this.eventBus.fireEvent(ERD_EVENTS.DIRTY, true);
       },
       'showNote': (event)=>{
         this.showNote(event.node);
@@ -154,53 +200,94 @@ export default class BodyWidget extends React.Component {
     });
   }
 
+  registerEvents() {
+    this.eventBus.registerListener(ERD_EVENTS.LOAD_DIAGRAM, this.onLoadDiagram);
+    this.eventBus.registerListener(ERD_EVENTS.SAVE_DIAGRAM, this.onSaveDiagram);
+    this.eventBus.registerListener(ERD_EVENTS.SHOW_SQL, this.onSQLClick);
+    this.eventBus.registerListener(ERD_EVENTS.DOWNLOAD_IMAGE, this.onImageClick);
+    this.eventBus.registerListener(ERD_EVENTS.ADD_NODE, this.onAddNewNode);
+    this.eventBus.registerListener(ERD_EVENTS.EDIT_NODE, this.onEditTable);
+    this.eventBus.registerListener(ERD_EVENTS.CLONE_NODE, this.onCloneNode);
+    this.eventBus.registerListener(ERD_EVENTS.DELETE_NODE, this.onDeleteNode);
+    this.eventBus.registerListener(ERD_EVENTS.SHOW_NOTE, this.onNoteClick);
+    this.eventBus.registerListener(ERD_EVENTS.ONE_TO_MANY, this.onOneToManyClick);
+    this.eventBus.registerListener(ERD_EVENTS.MANY_TO_MANY, this.onManyToManyClick);
+    this.eventBus.registerListener(ERD_EVENTS.AUTO_DISTRIBUTE, this.onAutoDistribute);
+    this.eventBus.registerListener(ERD_EVENTS.TOGGLE_DETAILS, this.onDetailsToggle);
+    this.eventBus.registerListener(ERD_EVENTS.ZOOM_FIT, this.diagram.zoomToFit);
+    this.eventBus.registerListener(ERD_EVENTS.ZOOM_IN, this.diagram.zoomIn);
+    this.eventBus.registerListener(ERD_EVENTS.ZOOM_OUT, this.diagram.zoomOut);
+  }
+
   registerKeyboardShortcuts() {
     /* First deregister to avoid double events */
     this.keyboardActionObj && this.diagram.deregisterKeyAction(this.keyboardActionObj);
 
     this.keyboardActionObj = new KeyboardShortcutAction([
-      [this.state.preferences.open_project, this.onLoadDiagram],
-      [this.state.preferences.save_project, this.onSaveDiagram],
-      [this.state.preferences.save_project_as, this.onSaveAsDiagram],
-      [this.state.preferences.generate_sql, this.onSQLClick],
-      [this.state.preferences.download_image, this.onImageClick],
-      [this.state.preferences.add_table, this.onAddNewNode],
-      [this.state.preferences.edit_table, this.onEditTable],
-      [this.state.preferences.clone_table, this.onCloneNode],
-      [this.state.preferences.drop_table, this.onDeleteNode],
-      [this.state.preferences.add_edit_note, this.onNoteClick],
-      [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.open_project, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.LOAD_DIAGRAM);
+      }],
+      [this.state.preferences.save_project, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.SAVE_DIAGRAM);
+      }],
+      [this.state.preferences.save_project_as, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.SAVE_DIAGRAM, true);
+      }],
+      [this.state.preferences.generate_sql, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.SHOW_SQL);
+      }],
+      [this.state.preferences.download_image, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.DOWNLOAD_IMAGE);
+      }],
+      [this.state.preferences.add_table, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.ADD_NODE);
+      }],
+      [this.state.preferences.edit_table, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.EDIT_NODE);
+      }],
+      [this.state.preferences.clone_table, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.CLONE_NODE);
+      }],
+      [this.state.preferences.drop_table, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.DELETE_NODE);
+      }],
+      [this.state.preferences.add_edit_note, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.SHOW_NOTE);
+      }],
+      [this.state.preferences.one_to_many, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.ONE_TO_MANY);
+      }],
+      [this.state.preferences.many_to_many, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.MANY_TO_MANY);
+      }],
+      [this.state.preferences.auto_align, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.AUTO_DISTRIBUTE);
+      }],
+      [this.state.preferences.show_details, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.TOGGLE_DETAILS);
+      }],
+      [this.state.preferences.zoom_to_fit, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.ZOOM_FIT);
+      }],
+      [this.state.preferences.zoom_in, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.ZOOM_IN);
+      }],
+      [this.state.preferences.zoom_out, ()=>{
+        this.eventBus.fireEvent(ERD_EVENTS.ZOOM_OUT);
+      }],
     ]);
 
     this.diagram.registerKeyAction(this.keyboardActionObj);
   }
 
   handleAxiosCatch(err) {
-    if (err.response) {
-      // client received an error response (5xx, 4xx)
-      Notify.alert(
-        gettext('Error'),
-        `${err.response.statusText} - ${err.response.data.errormsg}`
-      );
-      console.error('response error', err.response);
-    } else if (err.request) {
-      // client never received a response, or request never left
-      Notify.alert(gettext('Error'), gettext('Client error') + ':' + err);
-      console.error('client eror', err);
-    } else {
-      Notify.alert(gettext('Error'), err.message);
-      console.error('other error', err);
-    }
+    console.error(err);
+    Notify.alert(gettext('Error'), parseApiError(err));
   }
 
   async componentDidMount() {
     this.setLoading(gettext('Preparing...'));
+    this.registerEvents();
 
     this.setState({
       preferences: this.props.pgWindow.pgAdmin.Browser.get_preferences_for_module('erd'),
@@ -232,6 +319,15 @@ export default class BodyWidget extends React.Component {
       return true;
     });
 
+    window.addEventListener('unload', ()=>{
+      this.apiObj.delete(url_for('erd.close', {
+        trans_id: this.props.params.trans_id,
+        sgid: this.props.params.sgid,
+        sid: this.props.params.sid,
+        did: this.props.params.did
+      }));
+    });
+
     let done = await this.initConnection();
     if(!done) return;
 
@@ -257,52 +353,18 @@ 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?'));
+    this.context.showModal(gettext('Save changes?'), (closeModal)=>(
+      <ConfirmSaveContent
+        closeModal={closeModal}
+        text={gettext('The diagram has changed. Do you want to save changes?')}
+        onDontSave={()=>{
+          bodyObj.closePanel();
+        }}
+        onSave={()=>{
+          bodyObj.onSaveDiagram(false, true);
+        }}
+      />
+    ));
     return false;
   }
 
@@ -318,15 +380,18 @@ export default class BodyWidget extends React.Component {
     };
     if(dialogName === 'table_dialog') {
       return (title, attributes, isNew, callback)=>{
-        this.props.getDialog(dialogName).show(
-          title, attributes, isNew, this.diagram.getModel().getNodesDict(), this.diagram.getCache('colTypes'), this.diagram.getCache('schemas'), serverInfo, callback
-        );
+        this.erdDialogs.showTableDialog({
+          title, attributes, isNew, tableNodes: this.diagram.getModel().getNodesDict(),
+          colTypes: this.diagram.getCache('colTypes'), schemas: this.diagram.getCache('schemas'),
+          serverInfo, callback
+        });
       };
     } else if(dialogName === 'onetomany_dialog' || dialogName === 'manytomany_dialog') {
       return (title, attributes, callback)=>{
-        this.props.getDialog(dialogName).show(
-          title, attributes, this.diagram.getModel().getNodesDict(), serverInfo, callback
-        );
+        this.erdDialogs.showRelationDialog(dialogName, {
+          title, attributes, tableNodes: this.diagram.getModel().getNodesDict(),
+          serverInfo, callback
+        });
       };
     }
   }
@@ -386,7 +451,7 @@ export default class BodyWidget extends React.Component {
         Notify.error(gettext('Cannot drop table from outside of the current database.'));
       } else {
         let dataPromise = new Promise((resolve, reject)=>{
-          axios.get(nodeDropData.objUrl)
+          this.apiObj.get(nodeDropData.objUrl)
             .then((res)=>{
               resolve(this.diagram.cloneTableData(TableSchema.getErdSupportedData(res.data)));
             })
@@ -467,7 +532,7 @@ export default class BodyWidget extends React.Component {
   }
 
   onLoadDiagram() {
-    var params = {
+    const params = {
       'supported_types': ['*','pgerd'], // file types allowed
       'dialog_type': 'select_file', // open select file dialog
     };
@@ -476,13 +541,14 @@ export default class BodyWidget extends React.Component {
 
   openFile(fileName) {
     this.setLoading(gettext('Loading project...'));
-    axios.post(url_for('sqleditor.load_file'), {
+    this.apiObj.post(url_for('sqleditor.load_file'), {
       'file_name': decodeURI(fileName),
     }).then((res)=>{
       this.setState({
         current_file: fileName,
         dirty: false,
       });
+      this.eventBus.fireEvent(ERD_EVENTS.DIRTY, false);
       this.setTitle(fileName);
       this.diagram.deserialize(res.data);
       this.diagram.clearSelection();
@@ -515,7 +581,7 @@ export default class BodyWidget extends React.Component {
 
   saveFile(fileName) {
     this.setLoading(gettext('Saving...'));
-    axios.post(url_for('sqleditor.save_file'), {
+    this.apiObj.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)),
     }).then(()=>{
@@ -524,6 +590,7 @@ export default class BodyWidget extends React.Component {
         current_file: fileName,
         dirty: false,
       });
+      this.eventBus.fireEvent(ERD_EVENTS.DIRTY, false);
       this.setTitle(fileName);
       this.setLoading(null);
       if(this.closeOnSave) {
@@ -564,7 +631,7 @@ export default class BodyWidget extends React.Component {
     });
 
     this.setLoading(gettext('Preparing the SQL...'));
-    axios.post(url, this.diagram.serializeData())
+    this.apiObj.post(url, this.diagram.serializeData())
       .then((resp)=>{
         let sqlScript = resp.data.data;
         sqlScript = scriptHeader + 'BEGIN;\n' + sqlScript + '\nEND;';
@@ -579,7 +646,7 @@ export default class BodyWidget extends React.Component {
 
         let sqlId = `erd${this.props.params.trans_id}`;
         localStorage.setItem(sqlId, sqlScript);
-        showERDSqlTool(parentData, sqlId, this.props.params.title, this.props.pgWindow.pgAdmin.Tools.SQLEditor, this.props.alertify);
+        showERDSqlTool(parentData, sqlId, this.props.params.title, this.props.pgWindow.pgAdmin.Tools.SQLEditor);
       })
       .catch((error)=>{
         this.handleAxiosCatch(error);
@@ -617,7 +684,7 @@ export default class BodyWidget extends React.Component {
     });
 
     /* Change the styles for suiting html2canvas */
-    this.canvasEle.classList.add('html2canvas-reset');
+    this.canvasEle.classList.add(this.props.classes.html2canvasReset);
     this.canvasEle.style.width = this.canvasEle.scrollWidth + 'px';
     this.canvasEle.style.height = this.canvasEle.scrollHeight + 'px';
 
@@ -628,7 +695,8 @@ export default class BodyWidget extends React.Component {
         'color',
         'font-size',
         'stroke',
-        'font'
+        'font',
+        'display',
       ];
       let svgElems = Array.from(targetElem.getElementsByTagName('svg'));
       for (let svgEle of svgElems) {
@@ -638,6 +706,7 @@ export default class BodyWidget extends React.Component {
         let wrap = document.createElement('div');
         wrap.setAttribute('style', svgEle.getAttribute('style'));
         svgEle.setAttribute('style', null);
+        svgEle.style.display = 'block';
         svgEle.parentNode.insertBefore(wrap, svgEle);
         wrap.appendChild(svgEle);
         recurseElementChildren(svgEle);
@@ -679,7 +748,7 @@ export default class BodyWidget extends React.Component {
         allowTaint: true,
         backgroundColor: window.getComputedStyle(this.canvasEle).backgroundColor,
         onclone: (clonedEle)=>{
-          setSvgInlineStyles(clonedEle);
+          setSvgInlineStyles(clonedEle.body.querySelector('div[data-test="diagram-container"]'));
           return clonedEle;
         },
       }).then((canvas)=>{
@@ -697,7 +766,7 @@ export default class BodyWidget extends React.Component {
         Notify.alert(gettext('Error'), msg);
       }).then(()=>{
         /* Revert back to the original CSS styles */
-        this.canvasEle.classList.remove('html2canvas-reset');
+        this.canvasEle.classList.remove(this.props.classes.html2canvasReset);
         this.canvasEle.style.width = '';
         this.canvasEle.style.height = '';
         this.canvasEle.childNodes.forEach((ele)=>{
@@ -760,7 +829,7 @@ export default class BodyWidget extends React.Component {
     });
 
     try {
-      let response = await axios.post(initUrl);
+      let response = await this.apiObj.post(initUrl);
       this.setState({
         conn_status: CONNECT_STATUS.CONNECTED,
         server_version: response.data.data.serverVersion,
@@ -789,7 +858,7 @@ export default class BodyWidget extends React.Component {
     });
 
     try {
-      let response = await axios.get(url);
+      let response = await this.apiObj.get(url);
       let data = response.data.data;
       this.diagram.setCache('colTypes', data['col_types']);
       this.diagram.setCache('schemas', data['schemas']);
@@ -812,7 +881,7 @@ export default class BodyWidget extends React.Component {
     });
 
     try {
-      let response = await axios.get(url);
+      let response = await this.apiObj.get(url);
       this.diagram.deserializeData(response.data.data);
       return true;
     } catch (error) {
@@ -824,74 +893,27 @@ export default class BodyWidget extends React.Component {
   }
 
   render() {
+    this.erdDialogs.modal = this.context;
+
     return (
-      <Theme>
-        <ToolBar id="btn-toolbar">
-          <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')}
-              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}/>
-          </ButtonGroup>
-          <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('Download image')}
-              shortcut={this.state.preferences.download_image}/>
-          </ButtonGroup>
-          <ButtonGroup>
-            <IconButton id="add-node" icon="fa fa-plus-square" onClick={this.onAddNewNode} title={gettext('Add table')}
-              shortcut={this.state.preferences.add_table}/>
-            <IconButton id="edit-node" icon="fa fa-pencil-alt" onClick={this.onEditTable} title={gettext('Edit table')}
-              shortcut={this.state.preferences.edit_table} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
-            <IconButton id="clone-node" icon="fa fa-clone" onClick={this.onCloneNode} title={gettext('Clone table')}
-              shortcut={this.state.preferences.clone_table} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
-            <IconButton id="delete-node" icon="fa fa-trash-alt" onClick={this.onDeleteNode} title={gettext('Drop table/link')}
-              shortcut={this.state.preferences.drop_table} disabled={!this.state.any_item_selected}/>
-          </ButtonGroup>
-          <ButtonGroup>
-            <IconButton id="add-onetomany" text="1M" onClick={this.onOneToManyClick} title={gettext('One-to-Many link')}
-              shortcut={this.state.preferences.one_to_many} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
-            <IconButton id="add-manytomany" text="MM" onClick={this.onManyToManyClick} title={gettext('Many-to-Many link')}
-              shortcut={this.state.preferences.many_to_many} disabled={!this.state.single_node_selected || this.state.single_link_selected}/>
-          </ButtonGroup>
-          <ButtonGroup>
-            <IconButton id="add-note" icon="fa fa-sticky-note" onClick={this.onNoteClick} title={gettext('Add/Edit note')}
-              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}
-              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')}
-              shortcut={this.state.preferences.zoom_to_fit}/>
-            <IconButton id="zoom-in" icon="fa fa-search-plus" onClick={this.diagram.zoomIn} title={gettext('Zoom in')}
-              shortcut={this.state.preferences.zoom_in}/>
-            <IconButton id="zoom-out" icon="fa fa-search-minus" onClick={this.diagram.zoomOut} title={gettext('Zoom out')}
-              shortcut={this.state.preferences.zoom_out}/>
-          </ButtonGroup>
-          <ButtonGroup>
-            <IconButton id="help" icon="fa fa-question" onClick={this.onHelpClick} title={gettext('Help')} />
-          </ButtonGroup>
-        </ToolBar>
-        <ConnectionBar statusId="btn-conn-status" status={this.state.conn_status} bgcolor={this.props.params.bgcolor}
+      <Box ref={this.containerRef} height="100%">
+        <ConnectionBar status={this.state.conn_status} bgcolor={this.props.params.bgcolor}
           fgcolor={this.props.params.fgcolor} title={this.props.params.title}/>
+        <MainToolBar containerRef={this.containerRef} preferences={this.state.preferences} connStatus={this.state.conn_status} params={this.props.params} eventBus={this.eventBus} />
         <FloatingNote open={this.state.note_open} onClose={this.onNoteClose}
-          reference={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
-        <div className="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
+          anchorEl={this.noteRefEle} noteNode={this.state.note_node} appendTo={this.diagramContainerRef.current} rows={8}/>
+        <div className={this.props.classes.diagramContainer} data-test="diagram-container" ref={this.diagramContainerRef} onDrop={this.onDropNode} onDragOver={e => {e.preventDefault();}}>
           <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={this.props.classes.diagramCanvas} ref={(ele)=>{this.canvasEle = ele?.ref?.current;}} engine={this.diagram.getEngine()} />
         </div>
-      </Theme>
+      </Box>
     );
   }
 }
 
+export default withStyles(styles)(ERDTool);
 
-BodyWidget.propTypes = {
+ERDTool.propTypes = {
   params:PropTypes.shape({
     trans_id: PropTypes.number.isRequired,
     sgid: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
@@ -903,9 +925,8 @@ BodyWidget.propTypes = {
     fgcolor: PropTypes.string,
     gen: PropTypes.bool.isRequired,
   }),
-  getDialog: PropTypes.func.isRequired,
   pgWindow: PropTypes.object.isRequired,
   pgAdmin: PropTypes.object.isRequired,
-  alertify: PropTypes.object.isRequired,
   panel: PropTypes.object,
+  classes: PropTypes.object,
 };
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/FloatingNote.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/FloatingNote.jsx
new file mode 100644
index 000000000..f7578db8b
--- /dev/null
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/FloatingNote.jsx
@@ -0,0 +1,99 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useEffect, useState, useMemo } from 'react';
+import gettext from 'sources/gettext';
+import PropTypes from 'prop-types';
+import CustomPropTypes from 'sources/custom_prop_types';
+import { Box, makeStyles, Popper } from '@material-ui/core';
+import { DefaultButton } from '../../../../../../static/js/components/Buttons';
+import CheckIcon from '@material-ui/icons/Check';
+
+
+const useStyles = makeStyles((theme)=>({
+  root: {
+    width: '250px',
+    marginLeft: '8px',
+    ...theme.mixins.panelBorder.all,
+    borderRadius: theme.shape.borderRadius,
+    backgroundColor: theme.palette.background.default,
+    color: theme.palette.text.primary,
+  },
+  note: {
+    padding: '4px',
+    backgroundColor: theme.palette.primary.main,
+    color: theme.palette.primary.contrastText,
+    borderTopLeftRadius: 'inherit',
+    borderTopRightRadius: 'inherit',
+  },
+  header: {
+    padding: '4px',
+    whiteSpace: 'nowrap',
+    overflow: 'hidden',
+    textOverflow: 'ellipsis',
+    ...theme.mixins.panelBorder.bottom,
+  },
+  textarea: {
+    width: '100%',
+    border: 0,
+    display: 'block',
+  },
+  buttons: {
+    padding: '4px',
+    ...theme.mixins.panelBorder.top,
+    textAlign: 'right',
+  },
+}));
+
+export default function FloatingNote({open, onClose, anchorEl, rows, noteNode}) {
+  const [text, setText] = useState('');
+  const classes = useStyles();
+  useEffect(()=>{
+    if(noteNode) {
+      setText(noteNode.getNote());
+    }
+  }, [noteNode]);
+
+  const header = useMemo(()=>{
+    if(noteNode) {
+      let [schema, name] = noteNode.getSchemaTableName();
+      return `${name} (${schema})`;
+    }
+    return '';
+  }, [open]);
+
+  return (
+    <Popper
+      open={open}
+      anchorEl={anchorEl}
+      placement="right-start"
+    >
+      <Box className={classes.root}>
+        <Box className={classes.note}>{gettext('Note')}:</Box>
+        <Box className={classes.header}>{header}</Box>
+        <textarea className={classes.textarea} autoFocus value={text} rows={rows} onChange={(e)=>setText(e.target.value)}/>
+        <Box className={classes.buttons}>
+          <DefaultButton startIcon={<CheckIcon />} onClick={()=>{
+            let updated = (noteNode.getNote() != text);
+            noteNode.setNote(text);
+            if(onClose) onClose(updated);
+          }}>{gettext('OK')}</DefaultButton>
+        </Box>
+      </Box>
+    </Popper>
+  );
+}
+
+FloatingNote.propTypes = {
+  open: PropTypes.bool.isRequired,
+  onClose: PropTypes.func.isRequired,
+  anchorEl: CustomPropTypes.ref,
+  rows: PropTypes.number,
+  noteNode: PropTypes.object,
+};
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx
new file mode 100644
index 000000000..dc69895dc
--- /dev/null
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/components/MainToolBar.jsx
@@ -0,0 +1,249 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import React, {useCallback, useEffect, useState} from 'react';
+import { makeStyles } from '@material-ui/styles';
+import { Box } from '@material-ui/core';
+import { PgButtonGroup, PgIconButton } from '../../../../../../static/js/components/Buttons';
+import FolderRoundedIcon from '@material-ui/icons/FolderRounded';
+import KeyboardArrowDownIcon from '@material-ui/icons/KeyboardArrowDown';
+import SaveRoundedIcon from '@material-ui/icons/SaveRounded';
+import HelpIcon from '@material-ui/icons/HelpRounded';
+import ZoomInIcon from '@material-ui/icons/ZoomIn';
+import ZoomOutIcon from '@material-ui/icons/ZoomOut';
+import ZoomOutMapIcon from '@material-ui/icons/ZoomOutMap';
+import AddBoxIcon from '@material-ui/icons/AddBox';
+import EditRoundedIcon from '@material-ui/icons/EditRounded';
+import FileCopyRoundedIcon from '@material-ui/icons/FileCopyRounded';
+import DeleteIcon from '@material-ui/icons/Delete';
+import NoteRoundedIcon from '@material-ui/icons/NoteRounded';
+import VisibilityRoundedIcon from '@material-ui/icons/VisibilityRounded';
+import VisibilityOffRoundedIcon from '@material-ui/icons/VisibilityOffRounded';
+import ImageRoundedIcon from '@material-ui/icons/ImageRounded';
+
+import { PgMenu, PgMenuItem, usePgMenuGroup } from '../../../../../../static/js/components/Menu';
+import gettext from 'sources/gettext';
+import url_for from 'sources/url_for';
+import PropTypes from 'prop-types';
+import { ERD_EVENTS } from '../ERDConstants';
+import { MagicIcon, SQLFileIcon } from '../../../../../../static/js/components/ExternalIcon';
+import { useModal } from '../../../../../../static/js/helpers/ModalProvider';
+
+const useStyles = makeStyles((theme)=>({
+  root: {
+    padding: '2px 4px',
+    display: 'flex',
+    alignItems: 'center',
+    gap: '4px',
+    backgroundColor: theme.otherVars.editorToolbarBg,
+    flexWrap: 'wrap',
+    ...theme.mixins.panelBorder.bottom,
+  },
+  connectionButton: {
+    display: 'flex',
+    width: '450px',
+    backgroundColor: theme.palette.default.main,
+    color: theme.palette.default.contrastText,
+    border: '1px solid ' + theme.palette.default.borderColor,
+    justifyContent: 'flex-start',
+  },
+}));
+
+export function MainToolBar({preferences, eventBus}) {
+  const classes = useStyles();
+  const [buttonsDisabled, setButtonsDisabled] = useState({
+    'save': true,
+    'edit-table': true,
+    'clone-table': true,
+    'one-to-many': true,
+    'many-to-many': true,
+    'show-note': true,
+    'drop-table': true,
+  });
+  const [showDetails, setShowDetails] = useState(true);
+
+  const {openMenuName, toggleMenu, onMenuClose} = usePgMenuGroup();
+  const saveAsMenuRef = React.useRef(null);
+  const isDirtyRef = React.useRef(null);
+  const modal = useModal();
+
+  const setDisableButton = useCallback((name, disable=true)=>{
+    setButtonsDisabled((prev)=>({...prev, [name]: disable}));
+  }, []);
+
+  const onHelpClick=()=>{
+    let url = url_for('help.static', {'filename': 'erd_tool.html'});
+    window.open(url, 'pgadmin_help');
+  };
+  const confirmDiscard=(callback, checkSaved=false)=>{
+    if(checkSaved && buttonsDisabled['save']) {
+      /* No need to check  */
+      callback();
+      return;
+    }
+    modal.confirm(
+      gettext('Unsaved changes'),
+      gettext('Are you sure you wish to discard the current changes?'),
+      function() {
+        callback();
+      },
+      function() {
+        return true;
+      }
+    );
+  };
+
+  useEffect(()=>{
+    const events = [
+      [ERD_EVENTS.SINGLE_NODE_SELECTED, (selected)=>{
+        setDisableButton('edit-table', !selected);
+        setDisableButton('clone-table', !selected);
+        setDisableButton('one-to-many', !selected);
+        setDisableButton('many-to-many', !selected);
+        setDisableButton('show-note', !selected);
+      }],
+      [ERD_EVENTS.ANY_ITEM_SELECTED, (selected)=>{
+        setDisableButton('drop-table', !selected);
+      }],
+      [ERD_EVENTS.DIRTY, (isDirty)=>{
+        isDirtyRef.current = isDirty;
+        setDisableButton('save', !isDirty);
+      }],
+    ];
+    events.forEach((e)=>{
+      eventBus.registerListener(e[0], e[1]);
+    });
+    return ()=>{
+      events.forEach((e)=>{
+        eventBus.deregisterListener(e[0], e[1]);
+      });
+    };
+  }, []);
+
+  return (
+    <>
+      <Box className={classes.root}>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('Load Project')} icon={<FolderRoundedIcon />}
+            shortcut={preferences.open_project} onClick={()=>{
+              confirmDiscard(()=>{
+                eventBus.fireEvent(ERD_EVENTS.LOAD_DIAGRAM);
+              }, true);
+            }} />
+          <PgIconButton title={gettext('Save Project')} icon={<SaveRoundedIcon />}
+            shortcut={preferences.save_project} disabled={buttonsDisabled['save']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.SAVE_DIAGRAM);
+            }} />
+          <PgIconButton title={gettext('File')} icon={<KeyboardArrowDownIcon />} splitButton
+            name="menu-saveas" ref={saveAsMenuRef} onClick={toggleMenu}
+          />
+        </PgButtonGroup>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('Generate SQL')} icon={<SQLFileIcon />}
+            shortcut={preferences.generate_sql}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.SHOW_SQL);
+            }} />
+          <PgIconButton title={gettext('Download image')} icon={<ImageRoundedIcon />}
+            shortcut={preferences.download_image}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.DOWNLOAD_IMAGE);
+            }} />
+        </PgButtonGroup>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('Add Table')} icon={<AddBoxIcon />}
+            shortcut={preferences.add_table}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.ADD_NODE);
+            }} />
+          <PgIconButton title={gettext('Edit Table')} icon={<EditRoundedIcon />}
+            shortcut={preferences.edit_table} disabled={buttonsDisabled['edit-table']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.EDIT_NODE);
+            }} />
+          <PgIconButton title={gettext('Clone Table')} icon={<FileCopyRoundedIcon />}
+            shortcut={preferences.clone_table} disabled={buttonsDisabled['clone-table']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.CLONE_NODE);
+            }} />
+          <PgIconButton title={gettext('Drop Table/Relation')} icon={<DeleteIcon />}
+            shortcut={preferences.drop_table} disabled={buttonsDisabled['drop-table']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.DELETE_NODE);
+            }} />
+        </PgButtonGroup>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('One-to-Many Relation')} icon={<span style={{letterSpacing: '-1px'}}>1M</span>}
+            shortcut={preferences.one_to_many} disabled={buttonsDisabled['one-to-many']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.ONE_TO_MANY);
+            }} />
+          <PgIconButton title={gettext('Many-to-Many Relation')} icon={<span style={{letterSpacing: '-1px'}}>MM</span>}
+            shortcut={preferences.many_to_many} disabled={buttonsDisabled['many-to-many']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.MANY_TO_MANY);
+            }} />
+        </PgButtonGroup>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('Add/Edit Note')} icon={<NoteRoundedIcon />}
+            shortcut={preferences.add_edit_note} disabled={buttonsDisabled['show-note']}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.SHOW_NOTE);
+            }} />
+          <PgIconButton title={gettext('Auto Align')} icon={<MagicIcon />}
+            shortcut={preferences.auto_align}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.AUTO_DISTRIBUTE);
+            }} />
+          <PgIconButton title={gettext('Show Details')} icon={showDetails ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />}
+            shortcut={preferences.show_details}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.TOGGLE_DETAILS);
+              setShowDetails((prev)=>!prev);
+            }} />
+        </PgButtonGroup>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('Zoom In')} icon={<ZoomInIcon />}
+            shortcut={preferences.zoom_in}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.ZOOM_IN);
+            }} />
+          <PgIconButton title={gettext('Zoom to Fit')} icon={<ZoomOutMapIcon />}
+            shortcut={preferences.zoom_to_fit}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.ZOOM_FIT);
+            }} />
+          <PgIconButton title={gettext('Zoom Out')} icon={<ZoomOutIcon />}
+            shortcut={preferences.zoom_out}
+            onClick={()=>{
+              eventBus.fireEvent(ERD_EVENTS.ZOOM_OUT);
+            }}/>
+        </PgButtonGroup>
+        <PgButtonGroup size="small">
+          <PgIconButton title={gettext('Help')} icon={<HelpIcon />} onClick={onHelpClick} />
+        </PgButtonGroup>
+      </Box>
+      <PgMenu
+        anchorRef={saveAsMenuRef}
+        open={openMenuName=='menu-saveas'}
+        onClose={onMenuClose}
+        label={gettext('File Menu')}
+      >
+        <PgMenuItem onClick={()=>{
+          eventBus.fireEvent(ERD_EVENTS.SAVE_DIAGRAM, true);
+        }}>{gettext('Save as')}</PgMenuItem>
+      </PgMenu>
+    </>
+  );
+}
+
+MainToolBar.propTypes = {
+  preferences: PropTypes.object,
+  eventBus: PropTypes.object,
+};
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js
deleted file mode 100644
index b43d1415e..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/DialogWrapper.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import SchemaView from '../../../../../../static/js/SchemaView';
-import Theme from '../../../../../../static/js/Theme';
-
-export default class DialogWrapper {
-  constructor(dialogContainerSelector, dialogTitle, typeOfDialog, alertify, serverInfo) {
-    this.dialogContainerSelector = dialogContainerSelector;
-    this.dialogTitle = dialogTitle;
-    this.alertify = alertify;
-    this.typeOfDialog = typeOfDialog;
-    this.serverInfo = serverInfo;
-
-    let self = this;
-    this.hooks = {
-      onshow: ()=>{
-        self.createDialog(self.elements.content);
-      },
-      onclose: ()=>{
-        self.cleanupDialog(self.elements.content);
-      }
-    };
-  }
-
-  main(title, dialogSchema, okCallback) {
-    this.set('title', title);
-    this.dialogSchema = dialogSchema;
-    this.okCallback = okCallback;
-  }
-
-  build() {
-    this.elements.dialog.classList.add('erd-dialog');
-  }
-
-  prepare() {
-    /* If tooltip is mounted after alertify in dom and button is click,
-    alertify re-positions itself on DOM to come in focus. This makes it lose
-    the button click events. Making it modal along with following fixes things. */
-    this.elements.modal.style.maxHeight=0;
-    this.elements.modal.style.maxWidth='none';
-    this.elements.modal.style.overflow='visible';
-    this.elements.dimmer.style.display='none';
-  }
-
-  setup() {
-    return {
-      buttons: [],
-      // Set options for dialog
-      options: {
-        title: this.dialogTitle,
-        //disable both padding and overflow control.
-        padding: !1,
-        overflow: !1,
-        resizable: true,
-        maximizable: true,
-        pinnable: false,
-        closableByDimmer: false,
-        modal: true,
-        autoReset: false,
-      },
-    };
-  }
-
-  onSaveClick(_isNew, data) {
-    return new Promise((resolve, reject)=>{
-      let errorMsg = this.okCallback(data);
-      if(errorMsg) {
-        reject(errorMsg);
-      } else {
-        this.close();
-        resolve();
-      }
-    });
-  }
-
-  createDialog(container) {
-    let self = this;
-    ReactDOM.render(
-      <Theme>
-        <SchemaView
-          formType={'dialog'}
-          getInitData={()=>Promise.resolve({})}
-          schema={this.dialogSchema}
-          viewHelperProps={{
-            mode: 'create',
-            keepCid: true,
-            serverInfo: this.serverInfo,
-          }}
-          onSave={this.onSaveClick.bind(this)}
-          onClose={()=>self.close()}
-          onDataChange={()=>{/*This is intentional (SonarQube)*/}}
-          hasSQL={false}
-          disableSqlHelp={true}
-          disableDialogHelp={true}
-        />
-      </Theme>, container);
-  }
-
-  cleanupDialog(container) {
-    ReactDOM.unmountComponentAtNode(container);
-  }
-}
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js
index 4a8db3669..ca431ce91 100644
--- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/ManyToManyDialog.js
@@ -8,10 +8,7 @@
 //////////////////////////////////////////////////////////////
 
 import gettext from 'sources/gettext';
-import Alertify from 'pgadmin.alertifyjs';
 import BaseUISchema from 'sources/SchemaView/base_schema.ui';
-
-import DialogWrapper from './DialogWrapper';
 import _ from 'lodash';
 
 class ManyToManySchema extends BaseUISchema {
@@ -50,59 +47,27 @@ class ManyToManySchema extends BaseUISchema {
   }
 }
 
-export default class ManyToManyDialog {
-  constructor(pgBrowser) {
-    this.pgBrowser = pgBrowser;
-  }
-
-  dialogName() {
-    return 'manytomany_dialog';
-  }
+export function getManyToManyDialogSchema(attributes, tableNodesDict) {
+  let tablesData = [];
+  _.forEach(tableNodesDict, (node, uid)=>{
+    let [schema, name] = node.getSchemaTableName();
+    tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
+  });
 
-  getUISchema(attributes, tableNodesDict) {
-    let tablesData = [];
-    _.forEach(tableNodesDict, (node, uid)=>{
-      let [schema, name] = node.getSchemaTableName();
-      tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
-    });
-
-    return new ManyToManySchema({
-      left_table_uid: tablesData,
-      left_table_column_attnum: tableNodesDict[attributes.left_table_uid].getColumns().map((col)=>{
+  return new ManyToManySchema({
+    left_table_uid: tablesData,
+    left_table_column_attnum: tableNodesDict[attributes.left_table_uid].getColumns().map((col)=>{
+      return {
+        value: col.attnum, label: col.name, 'image': 'icon-column',
+      };
+    }),
+    right_table_uid: tablesData,
+    getRefColumns: (uid)=>{
+      return tableNodesDict[uid].getColumns().map((col)=>{
         return {
           value: col.attnum, label: col.name, 'image': 'icon-column',
         };
-      }),
-      right_table_uid: tablesData,
-      getRefColumns: (uid)=>{
-        return tableNodesDict[uid].getColumns().map((col)=>{
-          return {
-            value: col.attnum, label: col.name, 'image': 'icon-column',
-          };
-        });
-      },
-    }, attributes);
-  }
-
-  createOrGetDialog(title) {
-    const dialogName = this.dialogName();
-
-    if (!Alertify[dialogName]) {
-      Alertify.dialog(dialogName, () => {
-        return new DialogWrapper(
-          `<div class="${dialogName}"></div>`,
-          title,
-          null,
-          Alertify,
-        );
       });
-    }
-    return Alertify[dialogName];
-  }
-
-  show(title, attributes, tablesData, serverInfo, callback) {
-    let dialogTitle = title || gettext('Unknown');
-    const dialog = this.createOrGetDialog('manytomany_dialog', serverInfo);
-    dialog(dialogTitle, this.getUISchema(attributes, tablesData), callback).resizeTo(this.pgBrowser.stdW.sm, this.pgBrowser.stdH.md);
-  }
+    },
+  }, attributes);
 }
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js
index c9273c63f..4d6e48250 100644
--- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/OneToManyDialog.js
@@ -8,10 +8,7 @@
 //////////////////////////////////////////////////////////////
 
 import gettext from 'sources/gettext';
-import Alertify from 'pgadmin.alertifyjs';
 import BaseUISchema from 'sources/SchemaView/base_schema.ui';
-
-import DialogWrapper from './DialogWrapper';
 import _ from 'lodash';
 
 class OneToManySchema extends BaseUISchema {
@@ -50,60 +47,27 @@ class OneToManySchema extends BaseUISchema {
   }
 }
 
-export default class OneToManyDialog {
-  constructor(pgBrowser) {
-    this.pgBrowser = pgBrowser;
-  }
-
-  dialogName() {
-    return 'onetomany_dialog';
-  }
+export function getOneToManyDialogSchema(attributes, tableNodesDict) {
+  let tablesData = [];
+  _.forEach(tableNodesDict, (node, uid)=>{
+    let [schema, name] = node.getSchemaTableName();
+    tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
+  });
 
-  getUISchema(attributes, tableNodesDict) {
-    let tablesData = [];
-    _.forEach(tableNodesDict, (node, uid)=>{
-      let [schema, name] = node.getSchemaTableName();
-      tablesData.push({value: uid, label: `(${schema}) ${name}`, image: 'icon-table'});
-    });
-
-    return new OneToManySchema({
-      local_table_uid: tablesData,
-      local_column_attnum: tableNodesDict[attributes.local_table_uid].getColumns().map((col)=>{
+  return new OneToManySchema({
+    local_table_uid: tablesData,
+    local_column_attnum: tableNodesDict[attributes.local_table_uid].getColumns().map((col)=>{
+      return {
+        value: col.attnum, label: col.name, 'image': 'icon-column',
+      };
+    }),
+    referenced_table_uid: tablesData,
+    getRefColumns: (uid)=>{
+      return tableNodesDict[uid].getColumns().map((col)=>{
         return {
           value: col.attnum, label: col.name, 'image': 'icon-column',
         };
-      }),
-      referenced_table_uid: tablesData,
-      getRefColumns: (uid)=>{
-        return tableNodesDict[uid].getColumns().map((col)=>{
-          return {
-            value: col.attnum, label: col.name, 'image': 'icon-column',
-          };
-        });
-      },
-    }, attributes);
-  }
-
-  createOrGetDialog(title, sVersion) {
-    const dialogName = this.dialogName();
-
-    if (!Alertify[dialogName]) {
-      Alertify.dialog(dialogName, () => {
-        return new DialogWrapper(
-          `<div class="${dialogName}"></div>`,
-          title,
-          null,
-          Alertify,
-          sVersion
-        );
       });
-    }
-    return Alertify[dialogName];
-  }
-
-  show(title, attributes, tablesData, serverInfo, callback) {
-    let dialogTitle = title || gettext('Unknown');
-    const dialog = this.createOrGetDialog('onetomany_dialog', serverInfo);
-    dialog(dialogTitle, this.getUISchema(attributes, tablesData), callback).resizeTo(this.pgBrowser.stdW.sm, this.pgBrowser.stdH.md);
-  }
+    },
+  }, attributes);
 }
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 ddb02633a..8892a5be3 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
@@ -7,12 +7,9 @@
 //
 //////////////////////////////////////////////////////////////
 
-import gettext from 'sources/gettext';
-import Alertify from 'pgadmin.alertifyjs';
 import _ from 'lodash';
 import BaseUISchema from 'sources/SchemaView/base_schema.ui';
 
-import DialogWrapper from './DialogWrapper';
 import TableSchema, { ConstraintsSchema } from '../../../../../../browser/server_groups/servers/databases/schemas/tables/static/js/table.ui';
 import ColumnSchema from '../../../../../../browser/server_groups/servers/databases/schemas/tables/columns/static/js/column.ui';
 import ForeignKeySchema from '../../../../../../browser/server_groups/servers/databases/schemas/tables/constraints/foreign_key/static/js/foreign_key.ui';
@@ -26,106 +23,73 @@ class EmptySchema extends BaseUISchema {
   }
 }
 
-export default class TableDialog {
-  constructor(pgBrowser) {
-    this.pgBrowser = pgBrowser;
-  }
-
-  dialogName() {
-    return 'table_dialog';
-  }
-
-  getUISchema(attributes, isNew, tableNodesDict, colTypes, schemas) {
-    let treeNodeInfo = undefined;
-
-    let columnSchema = new ColumnSchema(
-      ()=>{/*This is intentional (SonarQube)*/},
-      treeNodeInfo,
-      ()=>colTypes,
-      ()=>[],
-      ()=>[],
-      true,
-    );
+export function getTableDialogSchema(attributes, isNew, tableNodesDict, colTypes, schemas) {
+  let treeNodeInfo = undefined;
 
-    return new TableSchema(
-      {
-        relowner: [],
-        schema: schemas.map((schema)=>{
-          return {
-            'value': schema['name'],
-            'image': 'icon-schema',
-            'label': schema['name'],
-          };
-        }),
-        spcname: [],
-        coll_inherits: [],
-        typname: [],
-        like_relation: [],
-      },
-      treeNodeInfo,
-      {
-        columns: ()=>columnSchema,
-        vacuum_settings: ()=>new EmptySchema(),
-        constraints: ()=>new ConstraintsSchema(
-          treeNodeInfo,
-          ()=>new ForeignKeySchema({
-            local_column: [],
-            references: ()=>{
-              let retOpts = [];
-              _.forEach(tableNodesDict, (node, uid)=>{
-                let [schema, name] = node.getSchemaTableName();
-                retOpts.push({value: uid, label: `(${schema}) ${name}`});
-              });
-              return retOpts;
-            }
-          },
-          treeNodeInfo,
-          (params)=>{
-            if(params.tid) {
-              return tableNodesDict[params.tid].getColumns().map((col)=>{
-                return {
-                  value: col.name, label: col.name, 'image': 'icon-column',
-                };
-              });
-            }
-          }, {autoindex: false}, true),
-          ()=>new EmptySchema(),
-          {spcname: []},
-          true
-        ),
-      },
-      ()=>new EmptySchema(),
-      ()=>[],
-      ()=>[],
-      ()=>[],
-      ()=>[],
-      isNew ? {
-        schema: schemas[0]?.name,
-      } : attributes,
-      true
-    );
-  }
-
-  createOrGetDialog(type, sVersion) {
-    const dialogName = this.dialogName();
+  let columnSchema = new ColumnSchema(
+    ()=>{/*This is intentional (SonarQube)*/},
+    treeNodeInfo,
+    ()=>colTypes,
+    ()=>[],
+    ()=>[],
+    true,
+  );
 
-    if (!Alertify[dialogName]) {
-      Alertify.dialog(dialogName, () => {
-        return new DialogWrapper(
-          `<div class="${dialogName}"></div>`,
-          null,
-          type,
-          Alertify,
-          sVersion
-        );
-      });
-    }
-    return Alertify[dialogName];
-  }
-
-  show(title, attributes, isNew, tableNodesDict, colTypes, schemas, serverInfo, callback) {
-    let dialogTitle = title || gettext('Unknown');
-    const dialog = this.createOrGetDialog('table_dialog', serverInfo);
-    dialog(dialogTitle, this.getUISchema(attributes, isNew, tableNodesDict, colTypes, schemas, serverInfo), callback).resizeTo(this.pgBrowser.stdW.lg, this.pgBrowser.stdH.md);
-  }
+  return new TableSchema(
+    {
+      relowner: [],
+      schema: schemas.map((schema)=>{
+        return {
+          'value': schema['name'],
+          'image': 'icon-schema',
+          'label': schema['name'],
+        };
+      }),
+      spcname: [],
+      coll_inherits: [],
+      typname: [],
+      like_relation: [],
+    },
+    treeNodeInfo,
+    {
+      columns: ()=>columnSchema,
+      vacuum_settings: ()=>new EmptySchema(),
+      constraints: ()=>new ConstraintsSchema(
+        treeNodeInfo,
+        ()=>new ForeignKeySchema({
+          local_column: [],
+          references: ()=>{
+            let retOpts = [];
+            _.forEach(tableNodesDict, (node, uid)=>{
+              let [schema, name] = node.getSchemaTableName();
+              retOpts.push({value: uid, label: `(${schema}) ${name}`});
+            });
+            return retOpts;
+          }
+        },
+        treeNodeInfo,
+        (params)=>{
+          if(params.tid) {
+            return tableNodesDict[params.tid].getColumns().map((col)=>{
+              return {
+                value: col.name, label: col.name, 'image': 'icon-column',
+              };
+            });
+          }
+        }, {autoindex: false}, true),
+        ()=>new EmptySchema(),
+        {spcname: []},
+        true
+      ),
+    },
+    ()=>new EmptySchema(),
+    ()=>[],
+    ()=>[],
+    ()=>[],
+    ()=>[],
+    isNew ? {
+      schema: schemas[0]?.name,
+    } : attributes,
+    true
+  );
 }
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js
deleted file mode 100644
index 84d4d51fc..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.js
+++ /dev/null
@@ -1,25 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import TableDialog from './TableDialog';
-import OneToManyDialog from './OneToManyDialog';
-import ManyToManyDialog from './ManyToManyDialog';
-import pgBrowser from 'top/browser/static/js/browser';
-import 'sources/backgrid.pgadmin';
-import 'sources/backform.pgadmin';
-
-export default function getDialog(dialogName) {
-  if(dialogName === 'table_dialog') {
-    return new TableDialog(pgBrowser);
-  } else if(dialogName === 'onetomany_dialog') {
-    return new OneToManyDialog(pgBrowser);
-  } else if(dialogName === 'manytomany_dialog') {
-    return new ManyToManyDialog(pgBrowser);
-  }
-}
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.jsx
new file mode 100644
index 000000000..f45cc1787
--- /dev/null
+++ b/web/pgadmin/tools/erd/static/js/erd_tool/dialogs/index.jsx
@@ -0,0 +1,95 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {getTableDialogSchema} from './TableDialog';
+import {getOneToManyDialogSchema} from './OneToManyDialog';
+import {getManyToManyDialogSchema} from './ManyToManyDialog';
+
+import pgAdmin from 'sources/pgadmin';
+import SchemaView from '../../../../../../static/js/SchemaView';
+import React from 'react';
+export default class ERDDialogs {
+  constructor(modalProvider) {
+    this.modal = modalProvider;
+  }
+
+  onSaveClick(_isNew, data, closeModal, okCallback) {
+    return new Promise((resolve, reject)=>{
+      let errorMsg = okCallback(data);
+      if(errorMsg) {
+        reject(errorMsg);
+      } else {
+        closeModal();
+        resolve();
+      }
+    });
+  }
+
+  showTableDialog(params) {
+    let schema = getTableDialogSchema(
+      params.attributes, params.isNew, params.tableNodes,
+      params.colTypes, params.schemas);
+    this.modal.showModal(params.title, (closeModal)=>{
+      return (
+        <SchemaView
+          formType={'dialog'}
+          getInitData={()=>Promise.resolve({})}
+          schema={schema}
+          viewHelperProps={{
+            mode: 'create',
+            keepCid: true,
+            serverInfo: params.serverInfo,
+          }}
+          onSave={(...args)=>this.onSaveClick(...args, closeModal, params.callback)}
+          onClose={closeModal}
+          onDataChange={()=>{/*This is intentional (SonarQube)*/}}
+          hasSQL={false}
+          disableSqlHelp={true}
+          disableDialogHelp={true}
+        />
+      );
+    }, {
+      isResizeable: true,
+      dialogWidth: pgAdmin.Browser?.stdW?.lg, dialogHeight: pgAdmin.Browser?.stdH?.md,
+    });
+  }
+
+  showRelationDialog(dialogName, params) {
+    let schema;
+    if(dialogName === 'onetomany_dialog') {
+      schema = getOneToManyDialogSchema(params.attributes, params.tableNodes);
+    } else if(dialogName === 'manytomany_dialog') {
+      schema = getManyToManyDialogSchema(params.attributes, params.tableNodes);
+    }
+
+    this.modal.showModal(params.title, (closeModal)=>{
+      return (
+        <SchemaView
+          formType={'dialog'}
+          getInitData={()=>Promise.resolve({})}
+          schema={schema}
+          viewHelperProps={{
+            mode: 'create',
+            keepCid: true,
+            serverInfo: params.serverInfo,
+          }}
+          onSave={(...args)=>this.onSaveClick(...args, closeModal, params.callback)}
+          onClose={closeModal}
+          onDataChange={()=>{/*This is intentional (SonarQube)*/}}
+          hasSQL={false}
+          disableSqlHelp={true}
+          disableDialogHelp={true}
+        />
+      );
+    }, {
+      isResizeable: true,
+      dialogWidth: pgAdmin.Browser?.stdW?.lg, dialogHeight: pgAdmin.Browser?.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
deleted file mode 100644
index 1c7a10d12..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/index.js
+++ /dev/null
@@ -1,57 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React from 'react';
-import ReactDOM from 'react-dom';
-import _ from 'lodash';
-
-import BodyWidget from './ui_components/BodyWidget';
-import getDialog from './dialogs';
-import Alertify from 'pgadmin.alertifyjs';
-import pgWindow from 'sources/window';
-import pgAdmin from 'sources/pgadmin';
-
-import ModalProvider from '../../../../../static/js/helpers/ModalProvider';
-import Theme from '../../../../../static/js/Theme';
-
-export default class ERDTool {
-  constructor(container, params) {
-    this.container = document.querySelector(container);
-    this.params = params;
-  }
-
-  getPreferencesForModule() {
-    /*This is intentional (SonarQube)*/
-  }
-
-  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(
-      <Theme>
-        <ModalProvider>
-          <BodyWidget
-            params={this.params}
-            getDialog={getDialog}
-            pgWindow={pgWindow}
-            pgAdmin={pgAdmin}
-            panel={panel}
-            alertify={Alertify} />
-        </ModalProvider>
-      </Theme>,
-      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 be6cf8d7e..fc6d1d1a6 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
@@ -7,7 +7,7 @@
 //
 //////////////////////////////////////////////////////////////
 
-import React from 'react';
+import React, { forwardRef } from 'react';
 import {
   RightAngleLinkModel,
   RightAngleLinkWidget,
@@ -19,6 +19,8 @@ import {
 import {Point} from '@projectstorm/geometry';
 import _ from 'lodash';
 import PropTypes from 'prop-types';
+import { makeStyles } from '@material-ui/core';
+import clsx from 'clsx';
 
 export const OneToManyModel = {
   local_table_uid: undefined,
@@ -74,20 +76,42 @@ export class OneToManyLinkModel extends RightAngleLinkModel {
   }
 }
 
+const useStyles = makeStyles((theme)=>({
+  svgLink: {
+    stroke: theme.palette.text.primary,
+  },
+  '@keyframes svgLinkSelected': {
+    'from': { strokeDashoffset: 24},
+    'to': { strokeDashoffset: 0 }
+  },
+  svgLinkSelected: {
+    strokeDasharray: '10, 2',
+    animation: '$svgLinkSelected 1s linear infinite'
+  },
+  svgLinkCircle: {
+    fill: theme.palette.text.primary,
+  },
+  svgLinkPath: {
+    pointerEvents: 'all',
+    cursor: 'move',
+  }
+}));
+
 const CustomLinkEndWidget = props => {
   const { point, rotation, tx, ty, type } = props;
+  const classes = useStyles();
 
   const svgForType = (itype) => {
     if(itype == 'many') {
       return (
         <>
-          <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} />
+          <circle className={clsx(classes.svgLink, classes.svgLinkCircle)} cx="0" cy="16" r={props.width*1.75} strokeWidth={props.width} />
+          <polyline className={classes.svgLink} points="-8,0 0,15 0,0 0,30 0,15 8,0" fill="none" strokeWidth={props.width} />
         </>
       );
     } else if (itype == '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} />
+        <polyline className={classes.svgLink} points="-8,15 0,15 0,0 0,30 0,15 8,15" fill="none" strokeWidth={props.width} />
       );
     }
   };
@@ -270,6 +294,29 @@ export class OneToManyLinkWidget extends RightAngleLinkWidget {
   }
 }
 
+const LinkSegment = forwardRef(({model, selected, path, ...props}, ref)=>{
+  const classes = useStyles();
+  return (
+    <path
+      ref={ref}
+      className={clsx(classes.svgLink, classes.svgLinkPath, (selected ? classes.svgLinkSelected : ''))}
+      stroke={model.getOptions().color}
+      strokeWidth={model.getOptions().width}
+      selected={selected}
+      d={path}
+      {...props}
+    >
+    </path>
+  );
+});
+LinkSegment.displayName = 'LinkSegment';
+LinkSegment.propTypes = {
+  model: PropTypes.object,
+  selected: PropTypes.bool,
+  path: PropTypes.any,
+};
+
+
 export class OneToManyLinkFactory extends DefaultLinkFactory {
   constructor() {
     super('onetomany');
@@ -280,19 +327,10 @@ export class OneToManyLinkFactory extends DefaultLinkFactory {
   }
 
   generateReactWidget(event) {
-    return <OneToManyLinkWidget color='#fff' width={1} smooth={true} link={event.model} diagramEngine={this.engine} factory={this} />;
+    return <OneToManyLinkWidget width={1} smooth={true} link={event.model} diagramEngine={this.engine} factory={this} />;
   }
 
   generateLinkSegment(model, selected, path) {
-    return (
-      <path
-        className={'svg-link-ele path ' + (selected ? 'selected' : '')}
-        stroke={model.getOptions().color}
-        selected={selected}
-        strokeWidth={model.getOptions().width}
-        d={path}
-      >
-      </path>
-    );
+    return <LinkSegment model={model} selected={selected} path={path} />;
   }
 }
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 be4e2c9a9..58f97f487 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
@@ -11,7 +11,6 @@ import React from 'react';
 import { DefaultNodeModel, DiagramEngine, PortWidget } from '@projectstorm/react-diagrams';
 import { AbstractReactFactory } from '@projectstorm/react-canvas-core';
 import _ from 'lodash';
-import { IconButton, DetailsToggleButton } from '../ui_components/ToolBar';
 import SchemaIcon from 'top/browser/server_groups/servers/databases/schemas/static/img/schema.svg';
 import TableIcon from 'top/browser/server_groups/servers/databases/schemas/tables/static/img/table.svg';
 import PrimaryKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/primary_key.svg';
@@ -20,6 +19,14 @@ import ColumnIcon from 'top/browser/server_groups/servers/databases/schemas/tabl
 import UniqueKeyIcon from 'top/browser/server_groups/servers/databases/schemas/tables/constraints/index_constraint/static/img/unique_constraint.svg';
 import PropTypes from 'prop-types';
 import gettext from 'sources/gettext';
+import { PgIconButton } from '../../../../../../static/js/components/Buttons';
+import NoteRoundedIcon from '@material-ui/icons/NoteRounded';
+import VisibilityRoundedIcon from '@material-ui/icons/VisibilityRounded';
+import VisibilityOffRoundedIcon from '@material-ui/icons/VisibilityOffRounded';
+import { withStyles } from '@material-ui/styles';
+import clsx from 'clsx';
+import { Box } from '@material-ui/core';
+
 
 const TYPE = 'table';
 
@@ -132,7 +139,7 @@ export class TableNodeModel extends DefaultNodeModel {
 
 function RowIcon({icon}) {
   return (
-    <div className="table-icon">
+    <div style={{padding: '0rem 0.125rem'}}>
       <img src={icon} crossOrigin="anonymous"/>
     </div>
   );
@@ -142,8 +149,48 @@ RowIcon.propTypes = {
   icon: PropTypes.any.isRequired,
 };
 
+const styles = (theme)=>({
+  tableNode: {
+    backgroundColor: theme.palette.background.default,
+    ...theme.mixins.panelBorder.all,
+    borderRadius: theme.shape.borderRadius,
+    position: 'relative',
+    width: '175px',
+    fontSize: '0.8em',
+    '& div:last-child': {
+      borderBottomLeftRadius: 'inherit',
+      borderBottomRightRadius: 'inherit',
+    }
+  },
+  tableNodeSelected: {
+    borderColor: theme.palette.primary.main,
+  },
+  tableSection: {
+    ...theme.mixins.panelBorder.bottom,
+    padding: '0.125rem 0.25rem',
+    display: 'flex',
+  },
+  tableToolbar: {
+    background: theme.otherVars.editorToolbarBg,
+    borderTopLeftRadius: 'inherit',
+    borderTopRightRadius: 'inherit',
+  },
+  tableNameText: {
+    fontWeight: 'bold',
+    wordBreak: 'break-all',
+    margin: 'auto 0',
+  },
+  error: {
+    color: theme.palette.error.main,
+  },
+  noteBtn: {
+    marginLeft: 'auto',
+    backgroundColor: theme.palette.warning.main,
+    color: theme.palette.warning.contrastText,
+  }
+});
 
-export class TableNodeWidget extends React.Component {
+class TableNodeWidgetRaw extends React.Component {
   constructor(props) {
     super(props);
 
@@ -179,17 +226,21 @@ export class TableNodeWidget extends React.Component {
     if(col.attlen) {
       cltype += '('+ col.attlen + (col.attprecision ? ',' + col.attprecision : '') +')';
     }
+
+    const {classes} = this.props;
     return (
-      <div className='d-flex col-row' key={col.attnum}>
-        <div className='d-flex col-row-data'>
+      <div className={classes.tableSection} key={col.attnum} data-test="column-row">
+        <Box display="flex" width="100%" style={{wordBreak: 'break-all'}}>
           <RowIcon icon={icon} />
-          <div className="my-auto">
-            <span className='col-name'>{col.name}</span>&nbsp;
+          <Box margin="auto 0">
+            <span data-test="column-name">{col.name}</span>&nbsp;
             {this.state.show_details &&
-            <span className='col-datatype'>{cltype}</span>}
-          </div>
-        </div>
-        <div className="ml-auto col-row-port">{this.generatePort(port)}</div>
+            <span data-test="column-type">{cltype}</span>}
+          </Box>
+        </Box>
+        <Box marginLeft="auto" padding="0" minHeight="0" display="flex" alignItems="center">
+          {this.generatePort(port)}
+        </Box>
       </div>
     );
   }
@@ -219,44 +270,50 @@ export class TableNodeWidget extends React.Component {
     (tableData.unique_constraint||[]).forEach((uk)=>{
       localUkCols.push(...uk.columns.map((c)=>c.column));
     });
+    const {classes} = this.props;
     return (
-      <div className={'table-node ' + (this.props.node.isSelected() ? 'selected': '') } onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
-        <div className="table-toolbar">
-          <DetailsToggleButton className='btn-xs' showDetails={this.state.show_details}
-            onClick={this.toggleShowDetails} onDoubleClick={(e)=>{e.stopPropagation();}}
-            disabled={tableMetaData.is_promise} />
+      <div className={clsx(classes.tableNode, (this.props.node.isSelected() ? classes.tableNodeSelected: ''))} onDoubleClick={()=>{this.props.node.fireEvent({}, 'editTable');}}>
+        <div className={clsx(classes.tableSection, classes.tableToolbar)}>
+          <PgIconButton size="xs" title={gettext('Show Details')} icon={this.state.show_details ? <VisibilityRoundedIcon /> : <VisibilityOffRoundedIcon />}
+            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');
-            }} title="Check note"/>}
+            <PgIconButton size="xs" className={classes.noteBtn}
+              title={gettext('Check Note')} icon={<NoteRoundedIcon />}
+              onClick={()=>{
+                this.props.node.fireEvent({}, 'showNote');
+              }}
+            />}
         </div>
         {tableMetaData.is_promise && <>
-          <div className="d-flex table-name-data">
-            {!tableMetaData.data_failed && <div className="table-name my-auto">{gettext('Fetching...')}</div>}
-            {tableMetaData.data_failed && <div className="table-name my-auto fetch-error">{gettext('Failed to get data. Please delete this table.')}</div>}
+          <div className={classes.tableSection}>
+            {!tableMetaData.data_failed && <div className={classes.tableNameText}>{gettext('Fetching...')}</div>}
+            {tableMetaData.data_failed && <div className={clsx(classes.tableNameText, classes.error)}>{gettext('Failed to get data. Please delete this table.')}</div>}
           </div>
         </>}
         {!tableMetaData.is_promise && <>
-          <div className="d-flex table-schema-data">
+          <div className={classes.tableSection}>
             <RowIcon icon={SchemaIcon}/>
-            <div className="table-schema my-auto">{tableData.schema}</div>
+            <div className={classes.tableNameText} data-test="schema-name">{tableData.schema}</div>
           </div>
-          <div className="d-flex table-name-data">
+          <div className={classes.tableSection}>
             <RowIcon icon={TableIcon} />
-            <div className="table-name my-auto">{tableData.name}</div>
+            <div className={classes.tableNameText} data-test="table-name">{tableData.name}</div>
           </div>
-          <div className="table-cols">
+          {tableData.columns.length > 0 && <div>
             {_.map(tableData.columns, (col)=>this.generateColumn(col, localFkCols, localUkCols))}
-          </div>
+          </div>}
         </>}
       </div>
     );
   }
 }
 
-TableNodeWidget.propTypes = {
+export const TableNodeWidget = withStyles(styles)(TableNodeWidgetRaw);
+
+TableNodeWidgetRaw.propTypes = {
   node: PropTypes.instanceOf(TableNodeModel),
   engine: PropTypes.instanceOf(DiagramEngine),
+  classes: PropTypes.object,
 };
 
 export class TableNodeFactory extends AbstractReactFactory {
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
deleted file mode 100644
index 269d4d9b6..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ConnectionBar.jsx
+++ /dev/null
@@ -1,57 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React from 'react';
-import gettext from 'sources/gettext';
-import PropTypes from 'prop-types';
-
-export const STATUS = {
-  CONNECTED: 1,
-  DISCONNECTED: 2,
-  CONNECTING: 3,
-  FAILED: 4,
-};
-
-/* The connection bar component */
-export default function ConnectionBar({statusId, status, bgcolor, fgcolor, title}) {
-  return (
-    <div className="connection_status_wrapper d-flex">
-      <div id={statusId}
-        role="status"
-        className="connection_status d-flex justify-content-center align-items-center" data-container="body"
-        data-toggle="popover" data-placement="bottom"
-        data-content=""
-        data-panel-visible="visible"
-        tabIndex="0">
-        <span className={'pg-font-icon d-flex m-auto '
-            + (status == STATUS.CONNECTED ? 'icon-connected' : '')
-            + (status == (STATUS.DISCONNECTED || STATUS.FAILED) ? 'icon-disconnected ' : '')
-            + (status == STATUS.CONNECTING ? 'obtaining-conn' : '')}
-        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}
-        </div>
-      </div>
-    </div>
-  );
-}
-
-ConnectionBar.propTypes = {
-  statusId: PropTypes.string.isRequired,
-  status: PropTypes.oneOf(Object.values(STATUS)).isRequired,
-  bgcolor: PropTypes.string,
-  fgcolor: PropTypes.string,
-  title: PropTypes.string.isRequired,
-};
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
deleted file mode 100644
index bd208d1a8..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/FloatingNote.jsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React, { useEffect, useState } from 'react';
-import Tippy from '@tippyjs/react';
-import gettext from 'sources/gettext';
-import PropTypes from 'prop-types';
-import CustomPropTypes from 'sources/custom_prop_types';
-
-/* The note component of ERD. It uses tippy to create the floating note */
-export default function FloatingNote({open, onClose, reference, rows, noteNode, ...tippyProps}) {
-  const textRef = React.useRef(null);
-  const [text, setText] = useState('');
-  const [header, setHeader] = useState('');
-  useEffect(()=>{
-    if(noteNode) {
-      setText(noteNode.getNote());
-      let [schema, name] = noteNode.getSchemaTableName();
-      setHeader(`${name} (${schema})`);
-    }
-
-    if(open) {
-      textRef?.current.focus();
-      textRef?.current.dispatchEvent(new KeyboardEvent('keypress'));
-    }
-  }, [noteNode, open]);
-
-  return (
-    <Tippy render={(attrs)=>(
-      <div className="floating-note" {...attrs}>
-        <div className="note-header">{gettext('Note')}:</div>
-        <div className="note-body">
-          <div className="p-1">{header}</div>
-          <textarea ref={textRef} className="pg-textarea" value={text} rows={rows} onChange={(e)=>setText(e.target.value)}></textarea>
-          <div className="pg_buttons">
-            <button className="btn btn-primary long_text_editor pg-alertify-button" data-label="OK"
-              onClick={()=>{
-                let updated = (noteNode.getNote() != text);
-                noteNode.setNote(text);
-                if(onClose) onClose(updated);
-              }}>
-              <span className="fa fa-check pg-alertify-button"></span>&nbsp;{gettext('OK')}
-            </button>
-          </div>
-        </div>
-      </div>
-    )}
-    visible={open}
-    interactive={true}
-    animation={false}
-    reference={reference}
-    placement='auto-end'
-    {...tippyProps}
-    />
-  );
-}
-
-FloatingNote.propTypes = {
-  open: PropTypes.bool.isRequired,
-  onClose: PropTypes.func.isRequired,
-  reference: CustomPropTypes.ref,
-  rows: PropTypes.number,
-  noteNode: PropTypes.object,
-};
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/Loader.jsx b/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/Loader.jsx
deleted file mode 100644
index 2aa2af448..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/Loader.jsx
+++ /dev/null
@@ -1,34 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React from 'react';
-import PropTypes from 'prop-types';
-
-/* The loader/spinner component */
-export default function Loader({message, autoEllipsis=false}) {
-  if(message || message == '') {
-    return (
-      <div className="pg-sp-container">
-        <div className="pg-sp-content">
-          <div className="row">
-            <div className="col-12 pg-sp-icon"></div>
-          </div>
-          <div className="row"><div className="col-12 pg-sp-text">{message}{autoEllipsis ? '...':''}</div></div>
-        </div>
-      </div>
-    );
-  } else {
-    return null;
-  }
-}
-
-Loader.propTypes = {
-  message: PropTypes.string,
-  autoEllipsis: PropTypes.bool,
-};
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
deleted file mode 100644
index 33a07e277..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool/ui_components/ToolBar.jsx
+++ /dev/null
@@ -1,146 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-import React, { forwardRef } from 'react';
-import Tippy from '@tippyjs/react';
-import {isMac} from 'sources/keyboard_shortcuts';
-import gettext from 'sources/gettext';
-import PropTypes from 'prop-types';
-import CustomPropTypes from 'sources/custom_prop_types';
-
-/* The base icon button.
-React does not pass ref prop to child component hierarchy.
-Use forwardRef for the same
-*/
-const BaseIconButton = forwardRef((props, ref)=>{
-  const {icon, text, className, ...otherProps} = props;
-
-  return(
-    <button ref={ref} className={className} {...otherProps}>
-      {icon && <span className={`${icon} sql-icon-lg`} aria-hidden="true" role="img"></span>}
-      {text && <span className="text-icon">{text}</span>}
-    </button>
-  );
-});
-BaseIconButton.displayName = 'BaseIconButton';
-
-BaseIconButton.propTypes = {
-  icon: PropTypes.string,
-  text: PropTypes.string,
-  className: PropTypes.string,
-  ref: CustomPropTypes.ref,
-};
-
-
-/* The tooltip content to show shortcut details */
-export function Shortcut({shortcut}) {
-  let keys = [];
-  shortcut.alt && keys.push((isMac() ? 'Option' : 'Alt'));
-  shortcut.control && keys.push('Ctrl');
-  shortcut.shift && keys.push('Shift');
-  keys.push(shortcut.key.char.toUpperCase());
-  return (
-    <div style={{justifyContent: 'center', marginTop: '0.125rem'}} className="d-flex">
-      {keys.map((key, i)=>{
-        return <div key={i} className="shortcut-key">{key}</div>;
-      })}
-    </div>
-  );
-}
-
-const shortcutPropType = PropTypes.shape({
-  alt: PropTypes.bool,
-  control: PropTypes.bool,
-  shift: PropTypes.bool,
-  key: PropTypes.shape({
-    char: PropTypes.string,
-  }),
-});
-
-Shortcut.propTypes = {
-  shortcut: shortcutPropType,
-};
-
-/* The icon button component which can have a tooltip based on props.
-React does not pass ref prop to child component hierarchy.
-Use forwardRef for the same
-*/
-export const IconButton = forwardRef((props, ref) => {
-  const {title, shortcut, className, ...otherProps} = props;
-
-  if (title) {
-    return (
-      <Tippy content={
-        <>
-          {<div style={{textAlign: 'center'}}>{title}</div>}
-          {shortcut && <Shortcut shortcut={shortcut} />}
-        </>
-      }>
-        <BaseIconButton ref={ref} className={'btn btn-sm btn-primary-icon ' + (className || '')} {...otherProps}/>
-      </Tippy>
-    );
-  } else {
-    return <BaseIconButton ref={ref} className='btn btn-sm btn-primary-icon' {...otherProps}/>;
-  }
-});
-IconButton.displayName = 'IconButton';
-
-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') }
-      {...props} />
-  );
-}
-
-DetailsToggleButton.propTypes = {
-  showDetails: PropTypes.bool,
-};
-
-/* Button group container */
-export function ButtonGroup({className, children}) {
-  return (
-    <div className={'btn-group mr-1 ' + (className ? className : '')} role="group" aria-label="save group">
-      {children}
-    </div>
-  );
-}
-
-ButtonGroup.propTypes = {
-  className: PropTypes.string,
-  children: PropTypes.oneOfType([
-    PropTypes.arrayOf(PropTypes.node),
-    PropTypes.node
-  ]),
-};
-
-/* Toolbar container */
-export default function ToolBar({id, children}) {
-  return (
-    <div id={id} className="editor-toolbar d-flex" role="toolbar" aria-label="">
-      {children}
-    </div>
-  );
-}
-
-ToolBar.propTypes = {
-  id: PropTypes.string,
-  children: PropTypes.oneOfType([
-    PropTypes.arrayOf(PropTypes.node),
-    PropTypes.node
-  ]),
-};
diff --git a/web/pgadmin/tools/erd/static/js/erd_tool_hook.js b/web/pgadmin/tools/erd/static/js/erd_tool_hook.js
deleted file mode 100644
index dafc69afd..000000000
--- a/web/pgadmin/tools/erd/static/js/erd_tool_hook.js
+++ /dev/null
@@ -1,35 +0,0 @@
-/////////////////////////////////////////////////////////////
-//
-// pgAdmin 4 - PostgreSQL Tools
-//
-// Copyright (C) 2013 - 2022, The pgAdmin Development Team
-// This software is released under the PostgreSQL Licence
-//
-//////////////////////////////////////////////////////////////
-
-define([
-  'sources/pgadmin', 'pgadmin.tools.erd/erd_tool', 'pgadmin.browser',
-  'pgadmin.browser.server.privilege', 'pgadmin.node.database', 'pgadmin.node.primary_key',
-  'pgadmin.node.foreign_key', 'pgadmin.browser.datamodel', 'pgadmin.tools.file_manager',
-], function(
-  pgAdmin, ERDToolModule
-) {
-  var pgTools = pgAdmin.Tools = pgAdmin.Tools || {};
-  var ERDTool = ERDToolModule.default;
-
-  /* Return back, this has been called more than once */
-  if (pgTools.ERDToolHook)
-    return pgTools.ERDToolHook;
-
-  pgTools.ERDToolHook = {
-    load: function(params) {
-      /* Create the ERD Tool object and render it */
-      let erdObj = new ERDTool('#erd-tool-container', params);
-      erdObj.render();
-    },
-  };
-
-  return pgTools.ERDToolHook;
-});
-
-
diff --git a/web/pgadmin/tools/erd/static/js/index.js b/web/pgadmin/tools/erd/static/js/index.js
index 2d8f2c1e3..71616c6c5 100644
--- a/web/pgadmin/tools/erd/static/js/index.js
+++ b/web/pgadmin/tools/erd/static/js/index.js
@@ -6,18 +6,16 @@
 // This software is released under the PostgreSQL Licence
 //
 //////////////////////////////////////////////////////////////
-import gettext from 'sources/gettext';
-import url_for from 'sources/url_for';
-import $ from 'jquery';
-import _ from 'underscore';
 import pgAdmin from 'sources/pgadmin';
 import pgBrowser from 'top/browser/static/js/browser';
-import * as csrfToken from 'sources/csrf';
-import {initialize} from './erd_module';
-var wcDocker = window.wcDocker;
+import 'pgadmin.tools.file_manager';
+import ERDModule from './ERDModule';
 
-let pgBrowserOut = initialize(gettext, url_for, $, _, pgAdmin, csrfToken, pgBrowser, wcDocker);
+if(!pgAdmin.Tools) {
+  pgAdmin.Tools = {};
+}
+pgAdmin.Tools.ERD = ERDModule.getInstance(pgAdmin, pgBrowser);
 
 module.exports = {
-  pgBrowser: pgBrowserOut,
+  ERD: pgAdmin.Tools.ERD,
 };
diff --git a/web/pgadmin/tools/erd/static/scss/_erd.scss b/web/pgadmin/tools/erd/static/scss/_erd.scss
deleted file mode 100644
index 246ceab4b..000000000
--- a/web/pgadmin/tools/erd/static/scss/_erd.scss
+++ /dev/null
@@ -1,265 +0,0 @@
-.shortcut-key {
-  padding: 0 0.25rem;
-  border: 1px solid $border-color;
-  margin-right: 0.125rem;
-  border-radius: $btn-border-radius;
-}
-
-.connection_status_wrapper {
-  width: 100%;
-  border-top: $panel-border;
-  border-bottom: $panel-border;
-}
-
-.connection_status .obtaining-conn {
-  background-image: $loader-icon-small !important;
-  background-position: center center;
-  background-repeat: no-repeat;
-  &:before {
-    content:'';
-  }
-  min-width: 50%;
-  min-height: 100%;
-}
-
-.connection_status {
-  background-color: $sql-title-bg;
-  color: $sql-title-fg;
-  border-right: $border-width solid $border-color;
-  padding: 0px 8px;
-}
-
-.editor-title {
-  padding: $sql-title-padding;
-  background: $sql-title-bg;
-  color: $sql-title-fg;
-}
-
-.connection-info {
-  background: $sql-title-bg;
-  color: $sql-title-fg;
-  width:100%;
-  display: inherit;
-}
-
-.connection-data {
-  display: inherit;
-  cursor: pointer;
-  width: auto;
-}
-
-#erd-tool-container {
-	width: 100%;
-	height: 100%;
-
-  .file-input-hidden {
-    height: 0;
-    width: 0;
-    visibility: hidden;
-  }
-
-  .text-icon {
-    font-weight: bold;
-  }
-
-  .erd-hint-bar {
-    background: $sql-gutters-bg;
-    padding: 0.25rem 0.5rem;
-  }
-
-  .diagram-container {
-    position: relative;
-    width: 100%;
-    height: 100%;
-    min-height: 0;
-  }
-
-  .floating-note {
-    width: 250px;
-    border: $panel-border;
-    border-radius: $panel-border-radius;
-    box-shadow: $dialog-box-shadow;
-    background-color: $alert-dialog-body-bg !important;
-    color: $color-fg !important;
-
-    .note-header {
-      padding: 0.25rem 0.5rem;
-      background-color: $alert-header-bg;
-      font-size: $font-size-base;
-      font-weight: bold;
-      color: $alert-header-fg;
-      overflow: hidden;
-      white-space: nowrap;
-      text-overflow: ellipsis;
-      border-radius: 0rem;
-      border-top-left-radius: $panel-border-radius;
-      border-top-right-radius: $panel-border-radius;
-      border-bottom: none;
-      margin: -$alertify-borderremove-margin; //-24px is default by alertify
-      margin-bottom: 0px;
-    }
-
-    .note-body {
-      word-break: break-all;
-      & textarea {
-        width: 100%;
-        border: none;
-        border-bottom: $border-width solid $erd-node-border-color;
-        border-top: $border-width solid $erd-node-border-color;
-      }
-
-      & .pg_buttons {
-        padding: 0.25rem;
-      }
-    }
-  }
-
-  .html2canvas-reset {
-    background-image: none !important;
-    overflow: auto !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;
-
-    .table-node {
-      background-color: $input-bg;
-      border: $border-width solid $erd-node-border-color;
-      border-radius: $input-border-radius;
-      position: relative;
-      width: 175px;
-      font-size: 0.8em;
-
-      .table-icon {
-        padding: 0rem 0.125rem;
-      }
-
-      &.selected {
-        border-color: $input-focus-border-color;
-        box-shadow: $input-btn-focus-box-shadow;
-      }
-
-      .table-toolbar {
-        background: $editor-toolbar-bg;
-        border-bottom: $border-width solid $erd-node-border-color;
-        padding: 0.125rem;
-        border-top-left-radius: inherit;
-        border-top-right-radius: inherit;
-        display: flex;
-
-        .btn {
-          &:not(:first-of-type) {
-            margin-left: 0.125rem;
-          }
-        }
-      }
-
-      .table-schema-data {
-        border-bottom: $border-width solid $erd-node-border-color;
-        padding: $erd-row-padding;
-
-        & .table-schema {
-          font-weight: bold;
-          word-break: break-all;
-        }
-      }
-
-      .table-name-data {
-        border-bottom: $border-width*2 solid $erd-node-border-color;
-        padding: $erd-row-padding;
-
-        & .table-name {
-          font-weight: bold;
-          word-break: break-all;
-        }
-
-        & .fetch-error {
-          color: $color-danger;
-        }
-      }
-
-      .table-cols {
-        .col-row {
-          border-bottom: $border-width solid $erd-node-border-color;
-          .col-row-data {
-            padding: $erd-row-padding;
-            width: 100%;
-            word-break: break-all;
-          }
-          .col-row-port {
-            padding: 0;
-            min-height: 0;
-          }
-        }
-      }
-    }
-
-    .svg-link-ele {
-      stroke: $erd-link-color;
-    }
-
-    .svg-link-ele.path {
-      pointer-events: all;
-      cursor: move;
-    }
-
-    @keyframes svg-link-ele-selected {
-      from { stroke-dashoffset: 24; } to { stroke-dashoffset: 0; }
-    }
-
-    .svg-link-ele.selected {
-      stroke: $erd-link-selected-color;
-      stroke-dasharray: 10, 2;
-      animation: svg-link-ele-selected 1s linear infinite;
-    }
-
-    .svg-link-ele.svg-otom-circle {
-      fill: $erd-link-color;
-    }
-
-    .custom-node-color{
-      position: absolute;
-      top: 50%;
-      left: 50%;
-      width: 20px;
-      height: 20px;
-      transform: translate(-50%, -50%);
-      border-radius: 10px;
-    }
-
-    .circle-port{
-      width: 12px;
-      height: 12px;
-      margin: 2px;
-      border-radius: 4px;
-      background: darkgray;
-      cursor: pointer;
-    }
-
-    .circle-port:hover{
-      background: mediumpurple;
-    }
-
-    .port {
-      display: inline-block;
-      margin: auto;
-    }
-  }
-}
-
-.alertify {
-  .erd-dialog {
-    .ajs-body .ajs-content {
-      bottom: 0!important;
-    }
-    .ajs-footer {
-      display: none;
-    }
-  }
-}
diff --git a/web/pgadmin/tools/erd/templates/erd/index.html b/web/pgadmin/tools/erd/templates/erd/index.html
index 10a0896f5..3987bc425 100644
--- a/web/pgadmin/tools/erd/templates/erd/index.html
+++ b/web/pgadmin/tools/erd/templates/erd/index.html
@@ -7,15 +7,23 @@
 {% block body %}
 <style>
     body {padding: 0px;}
-    {% if is_desktop_mode and is_linux %}
-    .alertify .ajs-dimmer,.alertify .ajs-modal{-webkit-transform: none;}
-    .alertify-notifier{-webkit-transform: none;}
-    .alertify-notifier .ajs-message{-webkit-transform: none;}
-    .alertify .ajs-dialog.ajs-shake{-webkit-animation-name: none;}
-    .sql-editor-busy-icon.fa-pulse{-webkit-animation: none;}
-    {% endif %}
+    #erd-tool-container {
+        display: flex;
+        flex-direction: column;
+        height: 100%;
+    }
+    #erd-tool-container:not(:empty) + .pg-sp-container {
+        display: none;
+    }
 </style>
 <div id="erd-tool-container" class="d-flex flex-column">
+<div class="pg-sp-container">
+    <div class="pg-sp-content">
+        <div class="row">
+            <div class="col-12 pg-sp-icon"></div>
+        </div>
+    </div>
+</div>
 </div>
 {% endblock %}
 {% block init_script %}
@@ -23,25 +31,9 @@
         require(
             ['sources/generated/browser_nodes', 'sources/generated/codemirror'],
             function() {
-                require(['sources/generated/erd_tool'], function(erdToolHook) {
-                var erdToolHook = erdToolHook || pgAdmin.Tools.ERDToolHook;
-                erdToolHook.load({{ params|safe }});
-
-                if(window.opener) {
-                    $(window).on('unload', function(ev) {
-                        $.ajax({
-                            method: 'DELETE',
-                            url: '{{close_url}}'
-                        });
-                    });
-                } else {
-                    $(window).on('beforeunload', function(ev) {
-                        $.ajax({
-                            method: 'DELETE',
-                            url: '{{close_url}}'
-                        });
-                    });
-                }
+                require(['sources/generated/erd_tool'], function(module) {
+                window.pgAdmin.Tools.ERD.loadComponent(
+                    document.getElementById('erd-tool-container'), {{ params|safe }});
             }, function() {
                 console.log(arguments);
             });
diff --git a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js
index 0edc39247..e2aa7b8ad 100644
--- a/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js
+++ b/web/pgadmin/tools/sqleditor/static/js/SQLEditorModule.js
@@ -16,7 +16,6 @@ import $ from 'jquery';
 import url_for from 'sources/url_for';
 import _ from 'lodash';
 import alertify from 'pgadmin.alertifyjs';
-var wcDocker = window.wcDocker;
 import pgWindow from 'sources/window';
 import pgAdmin from 'sources/pgadmin';
 import pgBrowser from 'pgadmin.browser';
@@ -29,6 +28,7 @@ import QueryToolComponent from './components/QueryToolComponent';
 import ModalProvider from '../../../../static/js/helpers/ModalProvider';
 import Theme from '../../../../static/js/Theme';
 
+const wcDocker = window.wcDocker;
 
 export function setPanelTitle(queryToolPanel, panelTitle) {
   queryToolPanel.title('<span title="'+panelTitle+'">'+panelTitle+'</span>');
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx
index d246209bb..366384fc7 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/Query.jsx
@@ -13,7 +13,7 @@ import CodeMirror from '../../../../../../static/js/components/CodeMirror';
 import {PANELS, QUERY_TOOL_EVENTS} from '../QueryToolConstants';
 import url_for from 'sources/url_for';
 import { LayoutEventsContext, LAYOUT_EVENTS } from '../../../../../../static/js/helpers/Layout';
-import ConfirmSaveContent from '../dialogs/ConfirmSaveContent';
+import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
 import gettext from 'sources/gettext';
 import OrigCodeMirror from 'bundled_codemirror';
 import Notifier from '../../../../../../static/js/helpers/Notifier';
diff --git a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
index 54476d41a..84b046b14 100644
--- a/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
+++ b/web/pgadmin/tools/sqleditor/static/js/components/sections/ResultSet.jsx
@@ -25,7 +25,7 @@ import { QuerySources } from './QueryHistory';
 import { getBrowser } from '../../../../../../static/js/utils';
 import CopyData from '../QueryToolDataGrid/CopyData';
 import moment from 'moment';
-import ConfirmSaveContent from '../dialogs/ConfirmSaveContent';
+import ConfirmSaveContent from '../../../../../../static/js/Dialogs/ConfirmSaveContent';
 import { makeStyles } from '@material-ui/styles';
 import EmptyPanelMessage from '../../../../../../static/js/components/EmptyPanelMessage';
 import { GraphVisualiser } from './GraphVisualiser';
diff --git a/web/pgadmin/tools/sqleditor/templates/sqleditor/index.html b/web/pgadmin/tools/sqleditor/templates/sqleditor/index.html
index cd71864ff..bda8dca48 100644
--- a/web/pgadmin/tools/sqleditor/templates/sqleditor/index.html
+++ b/web/pgadmin/tools/sqleditor/templates/sqleditor/index.html
@@ -15,13 +15,6 @@
     #sqleditor-container:not(:empty) + .pg-sp-container {
         display: none;
     }
-    {% if is_desktop_mode and is_linux %}
-    .alertify .ajs-dimmer,.alertify .ajs-modal{-webkit-transform: none;}
-    .alertify-notifier{-webkit-transform: none;}
-    .alertify-notifier .ajs-message{-webkit-transform: none;}
-    .alertify .ajs-dialog.ajs-shake{-webkit-animation-name: none;}
-    .sql-editor-busy-icon.fa-pulse{-webkit-animation: none;}
-    {% endif %}
 </style>
 <div id="sqleditor-container">
 <div class="pg-sp-container">
diff --git a/web/regression/javascript/erd/keyboard_shortcut_action_spec.js b/web/regression/javascript/erd/keyboard_shortcut_action_spec.js
index b504f37b0..993684f83 100644
--- a/web/regression/javascript/erd/keyboard_shortcut_action_spec.js
+++ b/web/regression/javascript/erd/keyboard_shortcut_action_spec.js
@@ -6,7 +6,7 @@
 // This software is released under the PostgreSQL Licence
 //
 //////////////////////////////////////////////////////////////
-import {KeyboardShortcutAction} from 'pgadmin.tools.erd/erd_tool/ui_components/BodyWidget';
+import {KeyboardShortcutAction} from 'pgadmin.tools.erd/erd_tool/components/ERDTool';
 
 describe('KeyboardShortcutAction', ()=>{
   let keyAction = null;
diff --git a/web/regression/javascript/erd/table_node_spec.js b/web/regression/javascript/erd/table_node.spec.js
similarity index 73%
rename from web/regression/javascript/erd/table_node_spec.js
rename to web/regression/javascript/erd/table_node.spec.js
index 04fa4c75c..e516d71e8 100644
--- a/web/regression/javascript/erd/table_node_spec.js
+++ b/web/regression/javascript/erd/table_node.spec.js
@@ -5,7 +5,7 @@ import '../helper/enzyme.helper';
 import { DefaultNodeModel } from '@projectstorm/react-diagrams';
 
 import {TableNodeModel, TableNodeWidget} from 'pgadmin.tools.erd/erd_tool/nodes/TableNode';
-import { IconButton, DetailsToggleButton } from 'pgadmin.tools.erd/erd_tool/ui_components/ToolBar';
+import Theme from '../../../pgadmin/static/js/Theme';
 
 
 describe('ERD TableNodeModel', ()=>{
@@ -211,63 +211,45 @@ describe('ERD TableNodeWidget', ()=>{
   });
 
   it('render', ()=>{
-    let nodeWidget = mount(<TableNodeWidget node={node}/>);
-    expect(nodeWidget.getDOMNode().className).toBe('table-node ');
-    expect(nodeWidget.find('.table-node .table-toolbar').length).toBe(1);
-    expect(nodeWidget.find('.table-node .table-schema').text()).toBe('erd');
-    expect(nodeWidget.find('.table-node .table-name').text()).toBe('table1');
-    expect(nodeWidget.find('.table-node .table-cols').length).toBe(1);
-    expect(nodeWidget.find(DetailsToggleButton).length).toBe(1);
-    expect(nodeWidget.find(IconButton).findWhere(n => n.prop('title')=='Check note').length).toBe(1);
-  });
-
-  it('node selected', ()=>{
-    spyOn(node, 'isSelected').and.returnValue(true);
-    let nodeWidget = mount(<TableNodeWidget node={node}/>);
-    expect(nodeWidget.getDOMNode().className).toBe('table-node selected');
+    let nodeWidget = mount(<Theme><TableNodeWidget node={node}/></Theme>);
+    expect(nodeWidget.find('DefaultButton[aria-label="Show Details"]').length).toBe(1);
+    expect(nodeWidget.find('DefaultButton[aria-label="Check Note"]').length).toBe(1);
+    expect(nodeWidget.find('div[data-test="schema-name"]').length).toBe(1);
+    expect(nodeWidget.find('div[data-test="table-name"]').length).toBe(1);
+    expect(nodeWidget.find('div[data-test="column-row"]').length).toBe(3);
   });
 
   it('remove note', ()=>{
     node.setNote('');
-    let nodeWidget = mount(<TableNodeWidget node={node}/>);
-    expect(nodeWidget.find(IconButton).findWhere(n => n.prop('title')=='Check note').length).toBe(0);
+    let nodeWidget = mount(<Theme><TableNodeWidget node={node}/></Theme>);
+    expect(nodeWidget.find('PgIconButton[aria-label="Check Note"]').length).toBe(0);
   });
 
   describe('generateColumn', ()=>{
     let nodeWidget = null;
 
     beforeEach(()=>{
-      nodeWidget = mount(<TableNodeWidget node={node}/>);
+      nodeWidget = mount(<Theme><TableNodeWidget node={node}/></Theme>);
     });
 
     it('count', ()=>{
-      expect(nodeWidget.find('.table-node .table-cols .col-row').length).toBe(3);
+      expect(nodeWidget.find('div[data-test="column-row"]').length).toBe(3);
     });
 
     it('column names', ()=>{
-      let cols = nodeWidget.find('.table-node .table-cols .col-row-data');
-      expect(cols.at(0).find('.col-name').text()).toBe('id');
-      expect(cols.at(1).find('.col-name').text()).toBe('amount');
-      expect(cols.at(2).find('.col-name').text()).toBe('desc');
-    });
+      let cols = nodeWidget.find('div[data-test="column-row"]');
 
-    it('data types', ()=>{
-      let cols = nodeWidget.find('.table-node .table-cols .col-row-data');
-      expect(cols.at(0).find('.col-datatype').text()).toBe('integer');
-      expect(cols.at(1).find('.col-datatype').text()).toBe('number(10,5)');
-      expect(cols.at(2).find('.col-datatype').text()).toBe('character varrying(50)');
+      expect(cols.at(0).find('span[data-test="column-name"]').text()).toBe('id');
+      expect(cols.at(1).find('span[data-test="column-name"]').text()).toBe('amount');
+      expect(cols.at(2).find('span[data-test="column-name"]').text()).toBe('desc');
     });
 
-    it('show_details', (done)=>{
-      nodeWidget.setState({show_details: false});
-      expect(nodeWidget.find('.table-node .table-cols .col-row-data .col-datatype').length).toBe(0);
+    it('data types', ()=>{
+      let cols = nodeWidget.find('div[data-test="column-row"]');
 
-      nodeWidget.instance().toggleShowDetails(jasmine.createSpyObj('event', ['preventDefault']));
-      /* Dummy set state to wait for toggleShowDetails -> setState to complete */
-      nodeWidget.setState({}, ()=>{
-        expect(nodeWidget.find('.table-node .table-cols .col-row-data .col-datatype').length).toBe(3);
-        done();
-      });
+      expect(cols.at(0).find('span[data-test="column-type"]').text()).toBe('integer');
+      expect(cols.at(1).find('span[data-test="column-type"]').text()).toBe('number(10,5)');
+      expect(cols.at(2).find('span[data-test="column-type"]').text()).toBe('character varrying(50)');
     });
   });
 });
diff --git a/web/regression/javascript/erd/ui_components/ConnectionBar.spec.js b/web/regression/javascript/erd/ui_components/ConnectionBar.spec.js
new file mode 100644
index 000000000..92985e4dd
--- /dev/null
+++ b/web/regression/javascript/erd/ui_components/ConnectionBar.spec.js
@@ -0,0 +1,32 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import {mount} from 'enzyme';
+import '../../helper/enzyme.helper';
+
+import ConnectionBar, {STATUS} from 'pgadmin.tools.erd/erd_tool/components/ConnectionBar';
+import Theme from '../../../../pgadmin/static/js/Theme';
+
+describe('ERD ConnectionBar', ()=>{
+  beforeEach(()=>{
+    jasmineEnzyme();
+  });
+
+  it('<ConnectionBar /> comp', ()=>{
+    const connBar = mount(<Theme><ConnectionBar status={STATUS.DISCONNECTED} title="test title"/></Theme>);
+
+    expect(connBar.find('DefaultButton[data-test="btn-conn-title"]').text()).toBe('test title');
+
+    connBar.setProps({
+      children: <ConnectionBar status={STATUS.CONNECTING} title="test title"/>
+    });
+    expect(connBar.find('DefaultButton[data-test="btn-conn-title"]').text()).toBe('(Obtaining connection...) test title');
+
+    connBar.setProps({
+      children: <ConnectionBar status={STATUS.CONNECTING} title="test title" bgcolor='#000' fgcolor='#fff'/>
+    });
+    const titleEle = connBar.find('DefaultButton[data-test="btn-conn-title"]');
+
+    expect(titleEle.prop('style').backgroundColor).toBe('#000');
+    expect(titleEle.prop('style').color).toBe('#fff');
+  });
+});
diff --git a/web/regression/javascript/erd/ui_components/body_widget_spec.js b/web/regression/javascript/erd/ui_components/ERDTool.spec.js
similarity index 86%
rename from web/regression/javascript/erd/ui_components/body_widget_spec.js
rename to web/regression/javascript/erd/ui_components/ERDTool.spec.js
index 04f9fa13c..c65131113 100644
--- a/web/regression/javascript/erd/ui_components/body_widget_spec.js
+++ b/web/regression/javascript/erd/ui_components/ERDTool.spec.js
@@ -1,4 +1,3 @@
-import jasmineEnzyme from 'jasmine-enzyme';
 import React from 'react';
 import {mount} from 'enzyme';
 import '../../helper/enzyme.helper';
@@ -6,12 +5,14 @@ import MockAdapter from 'axios-mock-adapter';
 import axios from 'axios/index';
 
 import ERDCore from 'pgadmin.tools.erd/erd_tool/ERDCore';
-import * as erdModule from 'pgadmin.tools.erd/erd_module';
+import * as erdModule from 'pgadmin.tools.erd/ERDModule';
 import erdPref from './erd_preferences';
-import BodyWidget from 'pgadmin.tools.erd/erd_tool/ui_components/BodyWidget';
+import ERDTool from 'pgadmin.tools.erd/erd_tool/components/ERDTool';
 import * as ERDSqlTool from 'tools/sqleditor/static/js/show_query_tool';
 import { FakeLink, FakeNode, FakePort } from '../fake_item';
 import Notify from '../../../../pgadmin/static/js/helpers/Notifier';
+import Theme from '../../../../pgadmin/static/js/Theme';
+import ModalProvider from '../../../../pgadmin/static/js/helpers/ModalProvider';
 
 
 let pgAdmin = {
@@ -50,18 +51,9 @@ let pgWindow = {
   pgAdmin: pgAdmin,
 };
 
-let alertify = jasmine.createSpyObj('alertify', {
-  'success': null,
-  'error': null,
-  'confirm': null,
-  'alert': {
-    'set': ()=>{/*This is intentional (SonarQube)*/},
-  },
-});
-
-let tableDialog = jasmine.createSpyObj('TableDialog', ['show']);
-let otmDialog = jasmine.createSpyObj('otmDialog', ['show']);
-let mtmDialog = jasmine.createSpyObj('mtmDialog', ['show']);
+let tableDialog = jasmine.createSpy('TableDialog');
+let otmDialog = jasmine.createSpy('otmDialog');
+let mtmDialog = jasmine.createSpy('mtmDialog');
 
 let getDialog = (dialogName)=>{
   switch(dialogName) {
@@ -71,7 +63,8 @@ let getDialog = (dialogName)=>{
   }
 };
 
-describe('ERD BodyWidget', ()=>{
+describe('ERDTool', ()=>{
+  let erd = null;
   let body = null;
   let bodyInstance = null;
   let networkMock = null;
@@ -129,36 +122,34 @@ describe('ERD BodyWidget', ()=>{
   });
 
   beforeEach(()=>{
-    jasmineEnzyme();
-    body = mount(<BodyWidget params={params} pgAdmin={pgAdmin} pgWindow={pgWindow} getDialog={getDialog} alertify={alertify}/>);
+    erd = mount(
+      <Theme>
+        <ModalProvider>
+          <ERDTool params={params} pgAdmin={pgAdmin} pgWindow={pgWindow} />
+        </ModalProvider>
+      </Theme>
+    );
+    body = erd.find('ERDTool');
     bodyInstance = body.instance();
+    spyOn(bodyInstance, 'getDialog').and.callFake(getDialog);
   });
 
   afterAll(() => {
     networkMock.restore();
-    if(body) {
-      body.unmount();
+    if(erd) {
+      erd.unmount();
     }
   });
 
   it('constructor', (done)=>{
-
-    expect(body.find('ToolBar').length).toBe(1);
-    expect(body.find('ConnectionBar').length).toBe(1);
-    expect(body.find('FloatingNote').length).toBe(1);
-    expect(body.find('.diagram-container Loader').length).toBe(1);
-    expect(body.find('.diagram-container CanvasWidget').length).toBe(1);
-
-    body.instance().setState({}, ()=>{
-      let instance = body.instance();
-
+    bodyInstance.setState({}, ()=>{
       setTimeout(()=>{
         expect(body.state()).toEqual(jasmine.objectContaining({
           server_version: serverVersion,
           preferences: erdPref,
         }));
-        expect(instance.diagram.getCache('colTypes')).toEqual(colTypes);
-        expect(instance.diagram.getCache('schemas')).toEqual(schemas);
+        expect(bodyInstance.diagram.getCache('colTypes')).toEqual(colTypes);
+        expect(bodyInstance.diagram.getCache('schemas')).toEqual(schemas);
         done();
       });
     });
@@ -237,17 +228,6 @@ describe('ERD BodyWidget', ()=>{
     });
   });
 
-  it('getDialog', ()=>{
-    bodyInstance.getDialog('table_dialog')();
-    expect(tableDialog.show).toHaveBeenCalled();
-
-    bodyInstance.getDialog('onetomany_dialog')();
-    expect(otmDialog.show).toHaveBeenCalled();
-
-    bodyInstance.getDialog('manytomany_dialog')();
-    expect(mtmDialog.show).toHaveBeenCalled();
-  });
-
   it('addEditTable', ()=>{
     let node1 = new FakeNode({'name': 'table1', schema: 'erd1', columns: [{name: 'col1', type: 'type1', attnum: 1}]}, 'id1');
     let node2 = new FakeNode({'name': 'table2', schema: 'erd2', columns: [{name: 'col2', type: 'type2', attnum: 2}]}, 'id2');
@@ -261,23 +241,23 @@ describe('ERD BodyWidget', ()=>{
     spyOn(bodyInstance.diagram, 'addLink');
     spyOn(bodyInstance.diagram, 'syncTableLinks');
     /* New */
-    tableDialog.show.calls.reset();
+    tableDialog.calls.reset();
     bodyInstance.addEditTable();
-    expect(tableDialog.show).toHaveBeenCalled();
+    expect(tableDialog).toHaveBeenCalled();
 
-    let saveCallback = tableDialog.show.calls.mostRecent().args[7];
+    let saveCallback = tableDialog.calls.mostRecent().args[3];
     let newData = {key: 'value'};
     saveCallback(newData);
     expect(bodyInstance.diagram.addNode).toHaveBeenCalledWith(newData);
 
     /* Existing */
-    tableDialog.show.calls.reset();
+    tableDialog.calls.reset();
     let node = new FakeNode({name: 'table1', schema: 'erd1'});
     spyOn(node, 'setData');
     bodyInstance.addEditTable(node);
-    expect(tableDialog.show).toHaveBeenCalled();
+    expect(tableDialog).toHaveBeenCalled();
 
-    saveCallback = tableDialog.show.calls.mostRecent().args[7];
+    saveCallback = tableDialog.calls.mostRecent().args[3];
     newData = {key: 'value'};
     saveCallback(newData);
     expect(node.setData).toHaveBeenCalledWith(newData);
@@ -435,11 +415,8 @@ describe('ERD BodyWidget', ()=>{
     spyOn(bodyInstance.diagram, 'addLink');
     spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
 
-    otmDialog.show.calls.reset();
     bodyInstance.onOneToManyClick();
-    expect(otmDialog.show).toHaveBeenCalled();
-
-    let saveCallback = otmDialog.show.calls.mostRecent().args[4];
+    let saveCallback = otmDialog.calls.mostRecent().args[2];
     let newData = {
       local_table_uid: 'id1',
       local_column_attnum: 1,
@@ -452,13 +429,6 @@ describe('ERD BodyWidget', ()=>{
 
   it('onManyToManyClick', ()=>{
     let node = new FakeNode({}, 'id1');
-    spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
-
-    mtmDialog.show.calls.reset();
-    bodyInstance.onManyToManyClick();
-    expect(mtmDialog.show).toHaveBeenCalled();
-
-    /* onSave */
     let node1 = new FakeNode({'name': 'table1', schema: 'erd1', columns: [{name: 'col1', type: 'type1', attnum: 1}]}, 'id1');
     let node2 = new FakeNode({'name': 'table2', schema: 'erd2', columns: [{name: 'col2', type: 'type2', attnum: 2}]}, 'id2');
     let nodesDict = {
@@ -469,8 +439,13 @@ describe('ERD BodyWidget', ()=>{
     spyOn(bodyInstance.diagram, 'getModel').and.returnValue({
       'getNodesDict': ()=>nodesDict,
     });
+    spyOn(bodyInstance.diagram, 'getSelectedNodes').and.returnValue([node]);
+
+    bodyInstance.onManyToManyClick();
+
+    /* onSave */
     spyOn(bodyInstance.diagram, 'addLink');
-    let saveCallback = mtmDialog.show.calls.mostRecent().args[4];
+    let saveCallback = mtmDialog.calls.mostRecent().args[2];
     let newData = {
       left_table_uid: 'id1',
       left_table_column_attnum: 1,
diff --git a/web/regression/javascript/erd/ui_components/floating_note_spec.js b/web/regression/javascript/erd/ui_components/FloatingNote.spec.js
similarity index 68%
rename from web/regression/javascript/erd/ui_components/floating_note_spec.js
rename to web/regression/javascript/erd/ui_components/FloatingNote.spec.js
index f786267bf..3af8f2fba 100644
--- a/web/regression/javascript/erd/ui_components/floating_note_spec.js
+++ b/web/regression/javascript/erd/ui_components/FloatingNote.spec.js
@@ -3,7 +3,8 @@ import React from 'react';
 import {mount} from 'enzyme';
 import '../../helper/enzyme.helper';
 
-import FloatingNote from 'pgadmin.tools.erd/erd_tool/ui_components/FloatingNote';
+import FloatingNote from 'pgadmin.tools.erd/erd_tool/components/FloatingNote';
+import Theme from '../../../../pgadmin/static/js/Theme';
 
 describe('ERD FloatingNote', ()=>{
 
@@ -24,15 +25,20 @@ describe('ERD FloatingNote', ()=>{
       },
     };
 
-    floatNote = mount(<FloatingNote open={false} onClose={onClose}
-      reference={null} noteNode={noteNode} appendTo={document.body} rows={8}/>);
+    floatNote = mount(
+      <Theme>
+        <FloatingNote
+          open={true} onClose={onClose} anchorEl={document.body} rows={8} noteNode={noteNode}
+        />
+      </Theme>);
 
     floatNote.find('textarea').simulate('change', {
       target: {
         value: 'the new note',
       },
     });
-    floatNote.find('button[data-label="OK"]').simulate('click');
+
+    floatNote.find('DefaultButton').simulate('click');
     expect(noteNode.setNote).toHaveBeenCalledWith('the new note');
     expect(onClose).toHaveBeenCalled();
   });
diff --git a/web/regression/javascript/erd/ui_components/connection_bar_spec.js b/web/regression/javascript/erd/ui_components/connection_bar_spec.js
deleted file mode 100644
index 17398a54e..000000000
--- a/web/regression/javascript/erd/ui_components/connection_bar_spec.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import jasmineEnzyme from 'jasmine-enzyme';
-import React from 'react';
-import {mount} from 'enzyme';
-import '../../helper/enzyme.helper';
-
-import ConnectionBar, {STATUS} from 'pgadmin.tools.erd/erd_tool/ui_components/ConnectionBar';
-
-describe('ERD ConnectionBar', ()=>{
-  beforeEach(()=>{
-    jasmineEnzyme();
-  });
-
-  it('<ConnectionBar /> comp', ()=>{
-    let connBar = mount(<ConnectionBar statusId="conn-bar" status={STATUS.DISCONNECTED} title="test title"/>);
-
-    expect(connBar.find('.editor-title').text()).toBe('test title');
-
-    connBar.setProps({status: STATUS.CONNECTING});
-    expect(connBar.find('.editor-title').text()).toBe('(Obtaining connection...) test title');
-
-    connBar.setProps({bgcolor: '#000', fgcolor: '#fff'});
-    expect(connBar.find('.editor-title').prop('style').backgroundColor).toBe('#000');
-    expect(connBar.find('.editor-title').prop('style').color).toBe('#fff');
-  });
-});
diff --git a/web/regression/javascript/erd/ui_components/loader_spec.js b/web/regression/javascript/erd/ui_components/loader_spec.js
deleted file mode 100644
index b73b69098..000000000
--- a/web/regression/javascript/erd/ui_components/loader_spec.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import jasmineEnzyme from 'jasmine-enzyme';
-import React from 'react';
-import {shallow} from 'enzyme';
-import '../../helper/enzyme.helper';
-
-import Loader from 'pgadmin.tools.erd/erd_tool/ui_components/Loader';
-
-describe('ERD Loader', ()=>{
-  beforeEach(()=>{
-    jasmineEnzyme();
-  });
-
-  it('<Loader /> comp', ()=>{
-    let loaderComp = shallow(<Loader />);
-    expect(loaderComp.isEmptyRender()).toBeTruthy();
-
-    loaderComp.setProps({message: 'test message'});
-    expect(loaderComp.find('.pg-sp-text').text()).toBe('test message');
-
-    loaderComp.setProps({autoEllipsis: true});
-    expect(loaderComp.find('.pg-sp-text').text()).toBe('test message...');
-  });
-});
diff --git a/web/regression/javascript/erd/ui_components/toolbar_spec.js b/web/regression/javascript/erd/ui_components/toolbar_spec.js
deleted file mode 100644
index 0429d718d..000000000
--- a/web/regression/javascript/erd/ui_components/toolbar_spec.js
+++ /dev/null
@@ -1,76 +0,0 @@
-import jasmineEnzyme from 'jasmine-enzyme';
-import React from 'react';
-import Tippy from '@tippyjs/react';
-import {mount, shallow} from 'enzyme';
-import '../../helper/enzyme.helper';
-
-import ToolBar, {ButtonGroup, DetailsToggleButton, IconButton, Shortcut} from 'pgadmin.tools.erd/erd_tool/ui_components/ToolBar';
-
-describe('ERD Toolbar', ()=>{
-  beforeEach(()=>{
-    jasmineEnzyme();
-  });
-
-  it('<Toolbar /> comp', ()=>{
-    let toolBar = mount(<ToolBar id="id1"><div className="test"></div></ToolBar>);
-    expect(toolBar.getDOMNode().id).toBe('id1');
-    expect(toolBar.find('.test').length).toBe(1);
-  });
-
-  it('<ButtonGroup /> comp', ()=>{
-    let btnGrp = mount(<ButtonGroup><div className="test"></div></ButtonGroup>);
-    expect(btnGrp.getDOMNode().className).toBe('btn-group mr-1 ');
-    expect(btnGrp.find('.test').length).toBe(1);
-    btnGrp.unmount();
-
-    btnGrp = mount(<ButtonGroup className="someclass"></ButtonGroup>);
-    expect(btnGrp.getDOMNode().className).toBe('btn-group mr-1 someclass');
-  });
-
-  it('<DetailsToggleButton /> comp', ()=>{
-    let toggle = shallow(<DetailsToggleButton showDetails={true} />);
-    let btn = toggle.find(IconButton);
-    expect(btn.prop('icon')).toBe('far fa-eye');
-    expect(btn.prop('title')).toBe('Show fewer details');
-
-    toggle.setProps({showDetails: false});
-    btn = toggle.find(IconButton);
-    expect(btn.prop('icon')).toBe('fas fa-low-vision');
-    expect(btn.prop('title')).toBe('Show more details');
-  });
-
-  it('<IconButton /> comp', ()=>{
-    let btn = mount(<IconButton />);
-
-    let tippy = btn.find(Tippy);
-    expect(tippy.length).toBe(0);
-
-    btn.setProps({title: 'test title'});
-    tippy = btn.find(Tippy);
-    expect(tippy.length).toBe(1);
-
-    expect(btn.find('button').getDOMNode().className).toBe('btn btn-sm btn-primary-icon ');
-
-    btn.setProps({icon: 'fa fa-icon'});
-    expect(btn.find('button .sql-icon-lg').getDOMNode().className).toBe('fa fa-icon sql-icon-lg');
-  });
-
-  it('<Shortcut /> comp', ()=>{
-    let key = {
-      alt: true,
-      control: true,
-      shift: false,
-      key: {
-        key_code: 65,
-        char: 'a',
-      },
-    };
-    let shortcutComp = mount(<Shortcut shortcut={key}/>);
-
-    expect(shortcutComp.find('.shortcut-key').length).toBe(3);
-
-    key.alt = false;
-    shortcutComp.setProps({shortcut: key});
-    expect(shortcutComp.find('.shortcut-key').length).toBe(2);
-  });
-});
diff --git a/web/regression/javascript/jasmine_capture_warnings_beforeall.js b/web/regression/javascript/jasmine_capture_warnings_beforeall.js
index 2eab2d9ac..2088b6529 100644
--- a/web/regression/javascript/jasmine_capture_warnings_beforeall.js
+++ b/web/regression/javascript/jasmine_capture_warnings_beforeall.js
@@ -13,6 +13,13 @@ beforeAll(function () {
   spyOn(console, 'warn').and.callThrough();
   spyOn(console, 'error').and.callThrough();
   jasmine.getEnv().allowRespy(true);
+
+  window.addEventListener('error', e => {
+    if(e.message === 'ResizeObserver loop completed with undelivered notifications.' ||
+        e.message === 'ResizeObserver loop limit exceeded') {
+      e.stopImmediatePropagation();
+    }
+  });
 });
 
 afterEach(function (done) {
diff --git a/web/webpack.config.js b/web/webpack.config.js
index 5c2e590a8..266a7b2c6 100644
--- a/web/webpack.config.js
+++ b/web/webpack.config.js
@@ -379,7 +379,7 @@ module.exports = [{
     slickgrid: sourceDir + '/bundle/slickgrid.js',
     sqleditor: './pgadmin/tools/sqleditor/static/js/index.js',
     schema_diff: './pgadmin/tools/schema_diff/static/js/schema_diff_hook.js',
-    erd_tool: './pgadmin/tools/erd/static/js/erd_tool_hook.js',
+    erd_tool: './pgadmin/tools/erd/static/js/index.js',
     psql_tool: './pgadmin/tools/psql/static/js/index.js',
     debugger: './pgadmin/tools/debugger/static/js/index.js',
     'pgadmin.style': pgadminCssStyles,
@@ -546,7 +546,7 @@ module.exports = [{
             'pure|pgadmin.tools.schema_diff',
             'pure|pgadmin.tools.file_manager',
             'pure|pgadmin.tools.search_objects',
-            'pure|pgadmin.tools.erd_module',
+            'pure|pgadmin.tools.erd',
             'pure|pgadmin.tools.psql_module',
             'pure|pgadmin.tools.sqleditor',
             'pure|pgadmin.misc.cloud',
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index 1e641339d..a9d334d44 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -281,7 +281,6 @@ var webpackShimConfig = {
     'pgadmin.tools.schema_diff': path.join(__dirname, './pgadmin/tools/schema_diff/static/js/schema_diff'),
     'pgadmin.tools.schema_diff_ui': path.join(__dirname, './pgadmin/tools/schema_diff/static/js/schema_diff_ui'),
     'pgadmin.tools.search_objects': path.join(__dirname, './pgadmin/tools/search_objects/static/js'),
-    'pgadmin.tools.erd_module': path.join(__dirname, './pgadmin/tools/erd/static/js/erd_module'),
     'pgadmin.tools.erd': path.join(__dirname, './pgadmin/tools/erd/static/js'),
     'pgadmin.tools.psql_module': path.join(__dirname, './pgadmin/tools/psql/static/js/psql_module'),
     'pgadmin.tools.psql': path.join(__dirname, './pgadmin/tools/psql/static/js'),
diff --git a/web/yarn.lock b/web/yarn.lock
index edd69d777..b9d666f34 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -2306,11 +2306,6 @@
   resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
   integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
 
-"@popperjs/core@^2.9.0":
-  version "2.10.2"
-  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590"
-  integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ==
-
 "@projectstorm/geometry@^6.6.1":
   version "6.6.1"
   resolved "https://registry.yarnpkg.com/@projectstorm/geometry/-/geometry-6.6.1.tgz#4a42f5c8fdfcc3d951e73f5db7fe9546514acc3d"
@@ -2501,13 +2496,6 @@
     prop-types "^15.7.2"
     react-transition-state "^1.1.3"
 
-"@tippyjs/react@^4.2.0":
-  version "4.2.6"
-  resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.6.tgz#971677a599bf663f20bb1c60a62b9555b749cc71"
-  integrity sha512-91RicDR+H7oDSyPycI13q3b7o4O60wa2oRbjlz2fyRLmHImc4vyDwuUP8NtZaN0VARJY5hybvDYrFzhY9+Lbyw==
-  dependencies:
-    tippy.js "^6.3.1"
-
 "@tootallnate/once@2":
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
@@ -11061,13 +11049,6 @@ tiny-warning@^1.0.2:
   resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754"
   integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==
 
-tippy.js@^6.3.1:
-  version "6.3.5"
-  resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.5.tgz#cbc99d34f87ccc127e6460032b86c8d47971d38f"
-  integrity sha512-B9hAQ5KNF+jDJRg6cRysV6Y3J+5fiNfD60GuXR5TP0sfrcltpgdzVc7f1wMtjQ3W0+Xsy80CDvk0Z+Vr0cM4sQ==
-  dependencies:
-    "@popperjs/core" "^2.9.0"
-
 tmp@^0.2.1:
   version "0.2.1"
   resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.1.tgz#8457fc3037dcf4719c251367a1af6500ee1ccf14"
