diff --git a/web/package.json b/web/package.json
index d30f0238c..97a31badc 100644
--- a/web/package.json
+++ b/web/package.json
@@ -13,8 +13,10 @@
     "@babel/plugin-proposal-object-rest-spread": "^7.9.6",
     "@babel/preset-env": "^7.10.2",
     "@emotion/core": "^10.0.14",
+    "@emotion/memoize": "^0.7.5",
     "@emotion/react": "^11.1.5",
     "@emotion/styled": "^10.0.14",
+    "@emotion/utils": "^1.0.0",
     "@wojtekmaj/enzyme-adapter-react-17": "^0.4.1",
     "autoprefixer": "^10.2.4",
     "axios-mock-adapter": "^1.17.0",
@@ -48,7 +50,7 @@
     "karma-sourcemap-loader": "^0.3.7",
     "karma-webpack": "^5.0.0",
     "mini-css-extract-plugin": "^1.3.5",
-    "popper.js": "^1.14.7",
+    "popper.js": "^1.16.1",
     "postcss-loader": "^5.0.0",
     "process": "^0.11.10",
     "prop-types": "^15.7.2",
@@ -68,7 +70,12 @@
   "dependencies": {
     "@babel/plugin-proposal-class-properties": "^7.10.4",
     "@babel/preset-react": "^7.12.13",
+    "@emotion/sheet": "^1.0.1",
     "@fortawesome/fontawesome-free": "^5.14.0",
+    "@material-ui/core": "^4.11.4",
+    "@material-ui/icons": "^4.11.2",
+    "@material-ui/lab": "^4.0.0-alpha.58",
+    "@material-ui/pickers": "^3.2.10",
     "@projectstorm/react-diagrams": "^6.4.2",
     "@simonwep/pickr": "^1.5.1",
     "@tippyjs/react": "^4.2.0",
@@ -90,6 +97,7 @@
     "css-loader": "^5.0.1",
     "cssnano": "^5.0.2",
     "dagre": "^0.8.4",
+    "diff-arrays-of-objects": "^1.1.8",
     "dropzone": "^5.7.4",
     "html2canvas": "^1.0.0-rc.7",
     "immutability-helper": "^3.0.0",
@@ -113,12 +121,15 @@
     "raf": "^3.4.1",
     "react": "^17.0.1",
     "react-dom": "^17.0.1",
+    "react-select": "^4.2.1",
+    "react-table": "^7.6.3",
     "select2": "^4.0.13",
     "shim-loader": "^1.0.1",
     "slickgrid": "git+https://github.com/6pac/SlickGrid.git#2.3.16",
     "snapsvg-cjs": "^0.0.6",
     "socket.io-client": "^4.0.0",
     "split.js": "^1.5.10",
+    "styled-components": "^5.2.1",
     "tablesorter": "^2.31.2",
     "tempusdominus-bootstrap-4": "^5.1.2",
     "tempusdominus-core": "^5.0.3",
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 eb20cac5f..7b4ee0a98 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
@@ -7,6 +7,11 @@
 //
 //////////////////////////////////////////////////////////////
 
+import { getNodeAjaxOptions, getNodeListByName } from '../../../../../static/js/node_ajax';
+import { getNodePrivilegeRoleSchema } from '../../../static/js/privilege.ui';
+import { getNodeVariableSchema } from '../../../static/js/variable.ui';
+import DatabaseSchema from './database.ui';
+
 define('pgadmin.node.database', [
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
   'sources/utils', 'sources/pgadmin', 'pgadmin.browser.utils',
@@ -306,30 +311,56 @@ define('pgadmin.node.database', [
           pgBrowser.Node.callbacks.refresh.apply(this, arguments);
         },
       },
+      getSchema: function(treeNodeInfo, itemNodeData) {
+        return new DatabaseSchema(
+          ()=>getNodeVariableSchema(this, treeNodeInfo, itemNodeData, false, true),
+          (privileges)=>getNodePrivilegeRoleSchema(this, treeNodeInfo, itemNodeData, privileges),
+          {
+            role: ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+            encoding:
+              ()=>getNodeAjaxOptions('get_encodings', this, treeNodeInfo, itemNodeData, {
+                cacheLevel: 'server',
+              }),
+            template:
+              ()=>getNodeAjaxOptions('get_databases', this, treeNodeInfo, itemNodeData, {
+                cacheLevel: 'server',
+              }, (data)=>{
+                let res = [];
+                if (data && _.isArray(data)) {
+                  _.each(data, function(d) {
+                    res.push({label: d, value: d,
+                      image: 'pg-icon-database'});
+                  });
+                }
+                return res;
+              }),
+            spcname:
+              ()=>getNodeListByName('tablespace', treeNodeInfo, itemNodeData, (m)=>{
+                return (m.label != 'pg_global');
+              }),
+            datcollate:
+              ()=>getNodeAjaxOptions('get_ctypes', this, treeNodeInfo, itemNodeData, {
+                cacheLevel: 'server',
+              }),
+            datctype:
+              ()=>getNodeAjaxOptions('get_ctypes', this, treeNodeInfo, itemNodeData, {
+                cacheLevel: 'server',
+              }),
+          },
+          {
+            datowner: pgBrowser.serverInfo[treeNodeInfo.server._id].user.name,
+          }
+        );
+      },
+      /* Few fields are kept since the properties tab for collection is not
+      yet migrated to new react schema. Once the properties for collection
+      is removed, remove this model */
       model: pgBrowser.Node.Model.extend({
         idAttribute: 'did',
         defaults: {
           name: undefined,
           owner: undefined,
-          is_sys_obj: undefined,
           comment: undefined,
-          encoding: 'UTF8',
-          template: undefined,
-          tablespace: undefined,
-          collation: undefined,
-          char_type: undefined,
-          datconnlimit: -1,
-          datallowconn: undefined,
-          variables: [],
-          privileges: [],
-          securities: [],
-          datacl: [],
-          deftblacl: [],
-          deffuncacl: [],
-          defseqacl: [],
-          is_template: false,
-          deftypeacl: [],
-          schema_res:'',
         },
 
         // Default values!
@@ -354,187 +385,11 @@ define('pgadmin.node.database', [
             id: 'datowner', label: gettext('Owner'),
             editable: false, type: 'text', node: 'role',
             control: Backform.NodeListByNameControl, select2: { allowClear: false },
-          },{
-            id: 'acl', label: gettext('Privileges'), type: 'text',
-            group: gettext('Security'), mode: ['properties'],
-          },{
-            id: 'tblacl', label: gettext('Default TABLE privileges'), type: 'text',
-            group: gettext('Security'), mode: ['properties'],
-          },{
-            id: 'seqacl', label: gettext('Default SEQUENCE privileges'), type: 'text',
-            group: gettext('Security'), mode: ['properties'],
-          },{
-            id: 'funcacl', label: gettext('Default FUNCTION privileges'), type: 'text',
-            group: gettext('Security'), mode: ['properties'],
-          },{
-            id: 'typeacl', label: gettext('Default TYPE privileges'), type: 'text',
-            group: gettext('Security'), mode: ['properties'], min_version: 90200,
-          },{
-            id: 'is_sys_obj', label: gettext('System database?'),
-            cell:'boolean', type: 'switch', mode: ['properties'],
           },{
             id: 'comments', label: gettext('Comment'),
             editable: false, type: 'multiline',
-          },{
-            id: 'encoding', label: gettext('Encoding'),
-            editable: false, type: 'text', group: gettext('Definition'),
-            readonly: function(m) { return !m.isNew(); }, url: 'get_encodings',
-            control: 'node-ajax-options', cache_level: 'server',
-          },{
-            id: 'template', label: gettext('Template'),
-            editable: false, type: 'text', group: gettext('Definition'),
-            readonly: function(m) { return !m.isNew(); },
-            control: 'node-list-by-name', url: 'get_databases', cache_level: 'server',
-            select2: { allowClear: false }, mode: ['create'],
-            transform: function(data, cell) {
-              var res = [],
-                control = cell || this,
-                label = control.model.get('name');
-
-              if (!control.model.isNew()) {
-                res.push({label: label, value: label});
-              }
-              else {
-                if (data && _.isArray(data)) {
-                  _.each(data, function(d) {
-                    res.push({label: d, value: d,
-                      image: 'pg-icon-database'});
-                  });
-                }
-              }
-              return res;
-            },
-          },{
-            id: 'spcname', label: gettext('Tablespace'),
-            editable: false, type: 'text', group: gettext('Definition'),
-            control: 'node-list-by-name', node: 'tablespace',
-            select2: { allowClear: false },
-            filter: function(m) {
-              return (m.label != 'pg_global');
-            },
-          },{
-            id: 'datcollate', label: gettext('Collation'),
-            editable: false, type: 'text', group: gettext('Definition'),
-            readonly: function(m) { return !m.isNew(); }, url: 'get_ctypes',
-            control: 'node-ajax-options', cache_level: 'server',
-          },{
-            id: 'datctype', label: gettext('Character type'),
-            editable: false, type: 'text', group: gettext('Definition'),
-            readonly: function(m) { return !m.isNew(); }, url: 'get_ctypes',
-            control: 'node-ajax-options', cache_level: 'server',
-          },{
-            id: 'datconnlimit', label: gettext('Connection limit'),
-            editable: false, type: 'int', group: gettext('Definition'), min: -1,
-          },{
-            id: 'is_template', label: gettext('Template?'),
-            editable: false, type: 'switch', group: gettext('Definition'),
-            readonly: true,  mode: ['properties', 'edit'],
-          },{
-            id: 'datallowconn', label: gettext('Allow connections?'),
-            editable: false, type: 'switch', group: gettext('Definition'),
-            mode: ['properties'],
-          },{
-            id: 'datacl', label: gettext('Privileges'), type: 'collection',
-            model: pgBrowser.Node.PrivilegeRoleModel.extend({
-              privileges: ['C', 'T', 'c'],
-            }), uniqueCol : ['grantee', 'grantor'], editable: false,
-            group: gettext('Security'), mode: ['edit', 'create'],
-            canAdd: true, canDelete: true, control: 'unique-col-collection',
-          },{
-            id: 'variables', label: '', type: 'collection',
-            model: pgBrowser.Node.VariableModel.extend({keys:['name', 'role']}), editable: false,
-            group: gettext('Parameters'), mode: ['edit', 'create'],
-            canAdd: true, canEdit: false, canDelete: true, hasRole: true,
-            control: Backform.VariableCollectionControl, node: 'role',
-          },{
-            id: 'seclabels', label: gettext('Security labels'),
-            model: pgBrowser.SecLabelModel,
-            editable: false, type: 'collection', canEdit: false,
-            group: gettext('Security'), canDelete: true,
-            mode: ['edit', 'create'], canAdd: true,
-            control: 'unique-col-collection', uniqueCol : ['provider'],
-            min_version: 90200,
-          },{
-            type: 'nested', control: 'tab', group: gettext('Default Privileges'),
-            mode: ['edit'],
-            schema:[{
-              id: 'deftblacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
-                {privileges: ['a', 'r', 'w', 'd', 'D', 'x', 't']}), label: '',
-              editable: false, type: 'collection', group: gettext('Tables'),
-              mode: ['edit', 'create'], control: 'unique-col-collection',
-              canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
-            },{
-              id: 'defseqacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
-                {privileges: ['r', 'w', 'U']}), label: '',
-              editable: false, type: 'collection', group: gettext('Sequences'),
-              mode: ['edit', 'create'], control: 'unique-col-collection',
-              canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
-            },{
-              id: 'deffuncacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
-                {privileges: ['X']}), label: '',
-              editable: false, type: 'collection', group: gettext('Functions'),
-              mode: ['edit', 'create'], control: 'unique-col-collection',
-              canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
-            },{
-              id: 'deftypeacl', model: pgBrowser.Node.PrivilegeRoleModel.extend(
-                {privileges: ['U']}),  label: '',
-              editable: false, type: 'collection', group: 'deftypesacl_group',
-              mode: ['edit', 'create'], control: 'unique-col-collection',
-              canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
-              min_version: 90200,
-            },{
-              id: 'deftypesacl_group', type: 'group', label: gettext('Types'),
-              mode: ['edit', 'create'], min_version: 90200,
-            },
-            ],
-          },{
-            type: 'collection', group: gettext('Advanced'),
-          },
-          {
-            id: 'schema_res', label: gettext('Schema restriction'),
-            type: 'select2', group: gettext('Advanced'),
-            mode: ['properties', 'edit', 'create'],
-            helpMessage: gettext('Note: Changes to the schema restriction will require the Schemas node in the browser to be refreshed before they will be shown.'),
-            select2: {
-              multiple: true, allowClear: false, tags: true,
-              tokenSeparators: [','], first_empty: false,
-              selectOnClose: true, emptyOptions: true,
-            },
-            control: Backform.Select2Control.extend({
-              onChange: function() {
-                Backform.Select2Control.prototype.onChange.apply(this, arguments);
-                if (!this.model || !(
-                  this.model.changed &&
-                this.model.get('oid') !== undefined
-                )) {
-                  this.model.inform_text = undefined;
-                  return;
-                }
-
-                if(this.model.origSessAttrs.schema_res != this.model.changed.schema_res)
-                {
-                  this.model.inform_text = gettext(
-                    'Please refresh the Schemas node to make changes to the schema restriction take effect.'
-                  );
-                } else {
-                  this.model.inform_text = undefined;
-                }
-              },
-            }),
           },
         ],
-        validate: function() {
-          var name = this.get('name');
-          if (_.isUndefined(name) || _.isNull(name) ||
-            String(name).replace(/^\s+|\s+$/g, '') == '') {
-            var msg = gettext('Name cannot be empty.');
-            this.errorModel.set('name', msg);
-            return msg;
-          } else {
-            this.errorModel.unset('name');
-          }
-          return null;
-        },
       }),
     });
 
diff --git a/web/pgadmin/browser/server_groups/servers/databases/static/js/database.ui.js b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.ui.js
new file mode 100644
index 000000000..ae97a8f68
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/databases/static/js/database.ui.js
@@ -0,0 +1,207 @@
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import SecLabelSchema from '../../../static/js/sec_label.ui';
+
+export class DefaultPrivSchema extends BaseUISchema {
+  constructor(getPrivilegeRoleSchema) {
+    super();
+    this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
+  }
+
+  get fields() {
+    return [
+      {
+        id: 'deftblacl', type: 'collection', group: gettext('Tables'),
+        schema: this.getPrivilegeRoleSchema(['a', 'r', 'w', 'd', 'D', 'x', 't']),
+        mode: ['edit', 'create'],
+        canAdd: true, canDelete: true,
+        uniqueCol : ['grantee', 'grantor'],
+      },{
+        id: 'defseqacl', type: 'collection', group: gettext('Sequences'),
+        schema: this.getPrivilegeRoleSchema(['r', 'w', 'U']),
+        mode: ['edit', 'create'],
+        canAdd: true, canDelete: true,
+        uniqueCol : ['grantee', 'grantor'],
+      },{
+        id: 'deffuncacl', type: 'collection', group: gettext('Functions'),
+        schema: this.getPrivilegeRoleSchema(['X']),
+        mode: ['edit', 'create'],
+        canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
+      },{
+        id: 'deftypeacl', type: 'collection', group: gettext('Types'),
+        schema: this.getPrivilegeRoleSchema(['U']), min_version: 90200,
+        mode: ['edit', 'create'],
+        canAdd: true, canDelete: true, uniqueCol : ['grantee', 'grantor'],
+      },
+    ];
+  }
+}
+
+export default class DatabaseSchema extends BaseUISchema {
+  constructor(getVariableSchema, getPrivilegeRoleSchema, fieldOptions={}, initValues) {
+    super({
+      name: undefined,
+      owner: undefined,
+      is_sys_obj: undefined,
+      comment: undefined,
+      encoding: 'UTF8',
+      template: undefined,
+      tablespace: undefined,
+      collation: undefined,
+      char_type: undefined,
+      datconnlimit: -1,
+      datallowconn: undefined,
+      variables: [],
+      privileges: [],
+      securities: [],
+      datacl: [],
+      deftblacl: [],
+      deffuncacl: [],
+      defseqacl: [],
+      is_template: false,
+      deftypeacl: [],
+      schema_res:'',
+      ...initValues,
+    });
+    this.getVariableSchema = getVariableSchema;
+    this.getPrivilegeRoleSchema = getPrivilegeRoleSchema;
+    this.fieldOptions = {
+      role: [],
+      encoding: [],
+      template: [],
+      spcname: [],
+      datcollate: [],
+      datctype: [],
+      ...fieldOptions,
+    };
+  }
+
+  get idAttribute() {
+    return 'did';
+  }
+
+  get fields() {
+    let obj = this;
+    return [
+      {
+        id: 'name', label: gettext('Database'), cell: 'text',
+        editable: false, type: 'text', noEmpty: true,
+      },{
+        id: 'did', label: gettext('OID'), cell: 'text', mode: ['properties'],
+        editable: false, type: 'text',
+      },{
+        id: 'datowner', label: gettext('Owner'),
+        editable: false, type: 'select', options: this.fieldOptions.role,
+        controlProps: { allowClear: false },
+      },{
+        id: 'is_sys_obj', label: gettext('System database?'),
+        cell: 'switch', type: 'switch', mode: ['properties'],
+      },{
+        id: 'comments', label: gettext('Comment'),
+        editable: false, type: 'multiline',
+      },{
+        id: 'encoding', label: gettext('Encoding'),
+        editable: false, type: 'select', group: gettext('Definition'),
+        readonly: function(state) {return !obj.isNew(state); },
+        options: this.fieldOptions.encoding,
+      },{
+        id: 'template', label: gettext('Template'),
+        editable: false, type: 'select', group: gettext('Definition'),
+        readonly: function(state) {return !obj.isNew(state); },
+        options: this.fieldOptions.template,
+        controlProps: { allowClear: false }, mode: ['create'],
+      },{
+        id: 'spcname', label: gettext('Tablespace'),
+        editable: false, type: 'select', group: gettext('Definition'),
+        options: this.fieldOptions.spcname,
+        controlProps: { allowClear: false },
+      },{
+        id: 'datcollate', label: gettext('Collation'),
+        editable: false, type: 'select', group: gettext('Definition'),
+        readonly: function(state) {return !obj.isNew(state); },
+        options: this.fieldOptions.datcollate,
+      },{
+        id: 'datctype', label: gettext('Character type'),
+        editable: false, type: 'select', group: gettext('Definition'),
+        readonly: function(state) {return !obj.isNew(state); },
+        options: this.fieldOptions.datctype,
+      },{
+        id: 'datconnlimit', label: gettext('Connection limit'),
+        editable: false, type: 'int', group: gettext('Definition'),
+        min: -1,
+      },{
+        id: 'is_template', label: gettext('Template?'),
+        editable: false, type: 'switch', group: gettext('Definition'),
+        readonly: true,  mode: ['properties', 'edit'],
+      },{
+        id: 'datallowconn', label: gettext('Allow connections?'),
+        editable: false, type: 'switch', group: gettext('Definition'),
+        mode: ['properties'],
+      },{
+        id: 'acl', label: gettext('Privileges'), type: 'text',
+        group: gettext('Security'), mode: ['properties'],
+      },{
+        id: 'tblacl', label: gettext('Default TABLE privileges'), type: 'text',
+        group: gettext('Security'), mode: ['properties'],
+      },{
+        id: 'seqacl', label: gettext('Default SEQUENCE privileges'), type: 'text',
+        group: gettext('Security'), mode: ['properties'],
+      },{
+        id: 'funcacl', label: gettext('Default FUNCTION privileges'), type: 'text',
+        group: gettext('Security'), mode: ['properties'],
+      },{
+        id: 'typeacl', label: gettext('Default TYPE privileges'), type: 'text',
+        group: gettext('Security'), mode: ['properties'], min_version: 90200,
+      },
+      {
+        id: 'datacl', label: gettext('Privileges'), type: 'collection',
+        schema: this.getPrivilegeRoleSchema(['C', 'T', 'c']),
+        uniqueCol : ['grantee', 'grantor'],
+        editable: false,
+        group: gettext('Security'), mode: ['edit', 'create'],
+        canAdd: true, canDelete: true,
+      },
+      {
+        id: 'variables', label: '', type: 'collection',
+        schema: this.getVariableSchema(),
+        editable: false,
+        group: gettext('Parameters'), mode: ['edit', 'create'],
+        canAdd: true, canEdit: false, canDelete: true, hasRole: true,
+        node: 'role',
+      },{
+        id: 'seclabels', label: gettext('Security labels'), type: 'collection',
+        schema: new SecLabelSchema(),
+        editable: false, group: gettext('Security'),
+        mode: ['edit', 'create'],
+        canAdd: true, canEdit: false, canDelete: true,
+        uniqueCol : ['provider'],
+        min_version: 90200,
+      },{
+        type: 'nested-tab', group: gettext('Default Privileges'),
+        mode: ['edit'],
+        schema: new DefaultPrivSchema(this.getPrivilegeRoleSchema),
+      },
+      {
+        id: 'schema_res', label: gettext('Schema restriction'),
+        type: 'select', group: gettext('Advanced'),
+        mode: ['properties', 'edit', 'create'],
+        helpMessage: gettext('Note: Changes to the schema restriction will require the Schemas node in the browser to be refreshed before they will be shown.'),
+        controlProps: {
+          multiple: true, allowClear: false, creatable: true,
+        }, depChange: (state)=>{
+          if(!_.isUndefined(state.oid)) {
+            obj.informText = undefined;
+          }
+
+          if(obj.origData.schema_res != state.schema_res) {
+            obj.informText = gettext(
+              'Please refresh the Schemas node to make changes to the schema restriction take effect.'
+            );
+          } else {
+            obj.informText = undefined;
+          }
+        },
+      },
+    ];
+  }
+}
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/privilege.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/privilege.ui.js
new file mode 100644
index 000000000..0c0bd814e
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/js/privilege.ui.js
@@ -0,0 +1,73 @@
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import { getNodeListByName } from '../../../../static/js/node_ajax';
+
+export function getNodePrivilegeRoleSchema(nodeObj, treeNodeInfo, itemNodeData, privileges) {
+  let keys = ['grantee', 'privileges', 'grantor'];
+  return new PrivilegeRoleSchema(
+    ()=>getNodeListByName('role', treeNodeInfo, itemNodeData, ()=>true, (res)=>{
+      res.unshift({label: 'PUBLIC', value: 'PUBLIC'});
+      return res;
+    }),
+    ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+    keys,
+    treeNodeInfo,
+    privileges
+  );
+}
+
+export default class PrivilegeRoleSchema extends BaseUISchema {
+  constructor(granteeOptions, grantorOptions, keys, nodeInfo, supportedPrivs) {
+    super({
+      grantee: undefined,
+      grantor: nodeInfo?.server?.user?.name,
+      privileges: undefined,
+    });
+    this.granteeOptions = granteeOptions;
+    this.grantorOptions = grantorOptions;
+    this.nodeInfo = nodeInfo;
+    this.supportedPrivs = supportedPrivs || [];
+    this.keys = keys;
+  }
+
+  get baseFields() {
+    let obj = this;
+
+    return [{
+      id: 'grantee', label: gettext('Grantee'), type:'text',
+      editable: true,
+      cell: ()=>({
+        cell: 'select', options: this.granteeOptions,
+        controlProps: {
+          allowClear: false,
+        }
+      }),
+      noEmpty: true,
+    },
+    {
+      id: 'privileges', label: gettext('Privileges'),
+      type: 'text', group: null,
+      cell: ()=>({cell: 'privilege', controlProps: {
+        supportedPrivs: this.supportedPrivs,
+      }}), minWidth: 280,
+      disabled : function(state) {
+        return !(
+          obj.nodeInfo &&
+            obj.nodeInfo.server.user.name == state['grantor']
+        );
+      },
+    },
+    {
+      id: 'grantor', label: gettext('Grantor'), type: 'text', readonly: true,
+      cell: ()=>({cell: 'select', options: obj.grantorOptions}),
+    }];
+  }
+
+  validate(state, setError) {
+    if((state.privileges || []).length <= 0) {
+      setError('privileges', gettext('At least one privilege should be selected.'));
+      return true;
+    }
+    return false;
+  }
+}
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/sec_label.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/sec_label.ui.js
new file mode 100644
index 000000000..4a29ab32a
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/js/sec_label.ui.js
@@ -0,0 +1,23 @@
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+
+export default class SecLabelSchema extends BaseUISchema {
+  constructor() {
+    super({
+      provider: undefined,
+      label: undefined,
+    });
+    this.keys = ['provider', 'label'];
+  }
+
+  get baseFields() {
+    return [{
+      id: 'provider', label: gettext('Provider'),
+      type: 'text', editable: true, cell: 'text',
+    },
+    {
+      id: 'label', label: gettext('Security label'),
+      type: 'text', editable: true, cell: 'text', noEmpty: true,
+    }];
+  }
+}
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.js b/web/pgadmin/browser/server_groups/servers/static/js/server.js
index ffd715f8e..8086dd408 100644
--- a/web/pgadmin/browser/server_groups/servers/static/js/server.js
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.js
@@ -7,23 +7,22 @@
 //
 //////////////////////////////////////////////////////////////
 
+import { getNodeListById } from '../../../../static/js/node_ajax';
+import ServerSchema from './server.ui';
+
 define('pgadmin.node.server', [
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore', 'backbone',
   'sources/pgadmin', 'pgadmin.browser',
-  'pgadmin.server.supported_servers', 'pgadmin.user_management.current_user',
+  'pgadmin.user_management.current_user',
   'pgadmin.alertifyjs', 'pgadmin.backform',
-  'sources/browser/server_groups/servers/model_validation',
   'pgadmin.authenticate.kerberos',
   'pgadmin.browser.server.privilege',
 ], function(
   gettext, url_for, $, _, Backbone, pgAdmin, pgBrowser,
-  supported_servers, current_user, Alertify, Backform,
-  modelValidation, Kerberos,
+  current_user, Alertify, Backform, Kerberos,
 ) {
 
   if (!pgBrowser.Nodes['server']) {
-    var SSL_MODES = ['prefer', 'require', 'verify-ca', 'verify-full'];
-
     pgBrowser.SecLabelModel = pgBrowser.Node.Model.extend({
       defaults: {
         provider: undefined,
@@ -738,469 +737,15 @@ define('pgadmin.node.server', [
           pgBrowser.psql.psql_tool(d, i, true);
         }
       },
-      model: pgAdmin.Browser.Node.Model.extend({
-        defaults: {
-          gid: undefined,
-          id: undefined,
-          name: '',
-          sslmode: 'prefer',
-          host: '',
-          hostaddr: '',
-          port: 5432,
-          db: 'postgres',
-          username: current_user.name,
-          role: null,
-          connect_now: true,
-          password: undefined,
-          save_password: false,
-          db_res: '',
-          passfile: undefined,
-          sslcompression: false,
-          sslcert: undefined,
-          sslkey: undefined,
-          sslrootcert: undefined,
-          sslcrl: undefined,
-          service: undefined,
-          use_ssh_tunnel: 0,
-          tunnel_host: undefined,
-          tunnel_port: 22,
-          tunnel_username: undefined,
-          tunnel_identity_file: undefined,
-          tunnel_password: undefined,
-          tunnel_authentication: 0,
-          save_tunnel_password: false,
-          connect_timeout: 10,
-        },
-        // Default values!
-        initialize: function(attrs, args) {
-          var isNew = (_.size(attrs) === 0);
-
-          if (isNew) {
-            this.set({'gid': args.node_info['server_group']._id});
-          }
-          pgAdmin.Browser.Node.Model.prototype.initialize.apply(this, arguments);
-        },
-        schema: [{
-          id: 'id', label: gettext('ID'), type: 'int', mode: ['properties'],
-          visible: function(model){
-            if (model.attributes.user_id != current_user.id && pgAdmin.server_mode == 'True')
-              return false;
-            return true;
-          },
-        },{
-          id: 'name', label: gettext('Name'), type: 'text',
-          mode: ['properties', 'edit', 'create'], disabled: 'isShared',
-        },
-        {
-          id: 'gid', label: gettext('Server group'), type: 'int',
-          control: 'node-list-by-id', node: 'server_group',
-          mode: ['create', 'edit'], select2: {allowClear: false}, disabled: 'isShared',
-        },
-        {
-          id: 'server_owner', label: gettext('Shared Server Owner'), type: 'text', mode: ['properties'],
-          visible:function(model){
-            var serverOwner = model.attributes.user_id;
-            if (model.attributes.shared && serverOwner != current_user.id && pgAdmin.server_mode == 'True'){
-              return true;
-            }
-            return false;
-
-          },
-        },
-        {
-          id: 'server_type', label: gettext('Server type'), type: 'options',
-          mode: ['properties'], visible: 'isConnected',
-          'options': supported_servers,
-        },{
-          id: 'connected', label: gettext('Connected?'), type: 'switch',
-          mode: ['properties'], group: gettext('Connection'), 'options': {
-            'onText':  gettext('True'), 'offText':  gettext('False'), 'size': 'mini',
-          },
-        },{
-          id: 'version', label: gettext('Version'), type: 'text', group: null,
-          mode: ['properties'], visible: 'isConnected',
-        },{
-          id: 'bgcolor', label: gettext('Background'), type: 'color',
-          group: null, mode: ['edit', 'create'], disabled: 'isfgColorSet',
-          deps: ['fgcolor'],
-        },{
-          id: 'fgcolor', label: gettext('Foreground'), type: 'color',
-          group: null, mode: ['edit', 'create'], disabled: 'isConnected',
-        },{
-          id: 'connect_now', controlLabel: gettext('Connect now?'), type: 'checkbox',
-          group: null, mode: ['create'],
-        },{
-          id: 'shared', label: gettext('Shared?'), type: 'switch',
-          mode: ['properties', 'create', 'edit'], 'options': {'size': 'mini'},
-          readonly: function(model){
-            var serverOwner = model.attributes.user_id;
-            if (!model.isNew() && serverOwner != current_user.id){
-              return true;
-            }
-            return false;
-          },visible: function(){
-            if (current_user.is_admin && pgAdmin.server_mode == 'True')
-              return true;
-
-            return false;
-          },
-        },
-        {
-          id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
-          mode: ['properties', 'edit', 'create'],
-        },{
-          id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
-          mode: ['properties', 'edit', 'create'],disabled: 'isShared',
-          control: Backform.InputControl.extend({
-            onChange: function() {
-              Backform.InputControl.prototype.onChange.apply(this, arguments);
-              if (!this.model || !this.model.changed) {
-                this.model.inform_text = undefined;
-                return;
-              }
-
-              if(this.model.origSessAttrs.host != this.model.changed.host && !this.model.isNew() && this.model.get('connected'))
-              {
-                this.model.inform_text = gettext(
-                  'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
-                );
-              } else {
-                this.model.inform_text = undefined;
-              }
-            },
-          }),
-        },{
-          id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
-          mode: ['properties', 'edit', 'create'], min: 1, max: 65535, disabled: 'isShared',
-          control: Backform.InputControl.extend({
-            onChange: function() {
-              Backform.InputControl.prototype.onChange.apply(this, arguments);
-              if (!this.model || !this.model.changed) {
-                this.model.inform_text = undefined;
-                return;
-              }
-
-              if(this.model.origSessAttrs.port != this.model.changed.port && !this.model.isNew() && this.model.get('connected'))
-              {
-                this.model.inform_text = gettext(
-                  'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
-                );
-              } else {
-                this.model.inform_text = undefined;
-              }
-            },
-          }),
-        },{
-          id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
-          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',disabled: 'isShared',
-        },{
-          id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
-          mode: ['properties', 'edit', 'create'],
-          control: Backform.InputControl.extend({
-            onChange: function() {
-              Backform.InputControl.prototype.onChange.apply(this, arguments);
-              if (!this.model || !this.model.changed) {
-                this.model.inform_text = undefined;
-                return;
-              }
-
-              if(this.model.origSessAttrs.username != this.model.changed.username && !this.model.isNew() && this.model.get('connected'))
-              {
-                this.model.inform_text = gettext(
-                  'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
-                );
-              } else {
-                this.model.inform_text = undefined;
-              }
-            },
-          }),
-        },{
-          id: 'kerberos_conn', label: gettext('Kerberos authentication?'), type: 'switch',
-          group: gettext('Connection'), 'options': {
-            'onText':  gettext('True'), 'offText':  gettext('False'), 'size': 'mini',
-          }
-        },{
-          id: 'gss_authenticated', label: gettext('GSS authenticated?'), type: 'switch',
-          group: gettext('Connection'), 'options': {
-            'onText':  gettext('True'), 'offText':  gettext('False'), 'size': 'mini',
-          }, mode: ['properties'], visible: 'isConnected'
-        },{
-          id: 'gss_encrypted', label: gettext('GSS encrypted?'), type: 'switch',
-          group: gettext('Connection'), 'options': {
-            'onText':  gettext('True'), 'offText':  gettext('False'), 'size': 'mini',
-          }, mode: ['properties'], visible: 'isConnected',
-        },{
-          id: 'password', label: gettext('Password'), type: 'password', maxlength: null,
-          group: gettext('Connection'), control: 'input', mode: ['create'],
-          deps: ['connect_now', 'kerberos_conn'],
-          visible: function(model) {
-            return model.get('connect_now') && model.isNew();
-          },
-          disabled: function(model) {
-            if (model.get('kerberos_conn'))
-              return true;
-
-            return false;
-          },
-        },{
-          id: 'save_password', controlLabel: gettext('Save password?'),
-          type: 'checkbox', group: gettext('Connection'), mode: ['create'],
-          deps: ['connect_now', 'kerberos_conn'], visible: function(model) {
-            return model.get('connect_now') && model.isNew();
-          },
-          disabled: function(model) {
-            if (!current_user.allow_save_password || model.get('kerberos_conn'))
-              return true;
-
-            return false;
-          },
-        },{
-          id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
-          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
-        },{
-          id: 'service', label: gettext('Service'), type: 'text',
-          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
-          group: gettext('Connection'),
-        },{
-          id: 'sslmode', label: gettext('SSL mode'), control: 'select2', group: gettext('SSL'),
-          select2: {
-            allowClear: false,
-            minimumResultsForSearch: Infinity,
-          },
-          mode: ['properties', 'edit', 'create'], disabled: 'isConnected',
-          'options': [
-            {label: gettext('Allow'), value: 'allow'},
-            {label: gettext('Prefer'), value: 'prefer'},
-            {label: gettext('Require'), value: 'require'},
-            {label: gettext('Disable'), value: 'disable'},
-            {label: gettext('Verify-CA'), value: 'verify-ca'},
-            {label: gettext('Verify-Full'), value: 'verify-full'},
-          ],
-        },{
-          id: 'sslcert', label: gettext('Client certificate'), type: 'text',
-          group: gettext('SSL'), mode: ['edit', 'create'],
-          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
-          dialog_type: 'select_file', supp_types: ['*'],
-          deps: ['sslmode'],
-        },{
-          id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
-          group: gettext('SSL'), mode: ['edit', 'create'],
-          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
-          dialog_type: 'select_file', supp_types: ['*'],
-          deps: ['sslmode'],
-        },{
-          id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
-          group: gettext('SSL'), mode: ['edit', 'create'],
-          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
-          dialog_type: 'select_file', supp_types: ['*'],
-          deps: ['sslmode'],
-        },{
-          id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
-          group: gettext('SSL'), mode: ['edit', 'create'],
-          disabled: 'isSSL', readonly: 'isConnected', control: Backform.FileControl,
-          dialog_type: 'select_file', supp_types: ['*'],
-          deps: ['sslmode'],
-        },{
-          id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
-          mode: ['edit', 'create'], group: gettext('SSL'),
-          'options': {'size': 'mini'},
-          deps: ['sslmode'], disabled: 'isSSL', readonly: 'isConnected',
-        },{
-          id: 'sslcert', label: gettext('Client certificate'), type: 'text',
-          group: gettext('SSL'), mode: ['properties'],
-          deps: ['sslmode'],
-          visible: function(model) {
-            var sslcert = model.get('sslcert');
-            return !_.isUndefined(sslcert) && !_.isNull(sslcert);
-          },
-        },{
-          id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
-          group: gettext('SSL'), mode: ['properties'],
-          deps: ['sslmode'],
-          visible: function(model) {
-            var sslkey = model.get('sslkey');
-            return !_.isUndefined(sslkey) && !_.isNull(sslkey);
-          },
-        },{
-          id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
-          group: gettext('SSL'), mode: ['properties'],
-          deps: ['sslmode'],
-          visible: function(model) {
-            var sslrootcert = model.get('sslrootcert');
-            return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
-          },
-        },{
-          id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
-          group: gettext('SSL'), mode: ['properties'],
-          deps: ['sslmode'],
-          visible: function(model) {
-            var sslcrl = model.get('sslcrl');
-            return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
-          },
-        },{
-          id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
-          mode: ['properties'], group: gettext('SSL'),
-          'options': {'size': 'mini'},
-          deps: ['sslmode'], visible: function(model) {
-            var sslmode = model.get('sslmode');
-            return _.indexOf(SSL_MODES, sslmode) != -1;
-          },
-        },{
-          id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
-          mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
-          'options': {'size': 'mini'},
-          disabled: function(model) {
-            if (!pgAdmin.Browser.utils.support_ssh_tunnel) {
-              setTimeout(function() {
-                model.set('use_ssh_tunnel', 0);
-              }, 10);
-
-              return true;
-            }
-
-            return false;
-          },
-          readonly: 'isConnected',
-        },{
-          id: 'tunnel_host', label: gettext('Tunnel host'), type: 'text', group: gettext('SSH Tunnel'),
-          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
-          disabled: function(model) {
-            return !model.get('use_ssh_tunnel');
-          },
-          readonly: 'isConnected',
-        },{
-          id: 'tunnel_port', label: gettext('Tunnel port'), type: 'int', group: gettext('SSH Tunnel'),
-          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'], max: 65535,
-          disabled: function(model) {
-            return !model.get('use_ssh_tunnel');
-          },
-          readonly: 'isConnected',
-        },{
-          id: 'tunnel_username', label: gettext('Username'), type: 'text', group: gettext('SSH Tunnel'),
-          mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
-          disabled: function(model) {
-            return !model.get('use_ssh_tunnel');
-          },
-          readonly: 'isConnected',
-        },{
-          id: 'tunnel_authentication', label: gettext('Authentication'), type: 'switch',
-          mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
-          'options': {'onText':  gettext('Identity file'),
-            'offText':  gettext('Password'), 'size': 'mini', width: '90'},
-          deps: ['use_ssh_tunnel'],
-          disabled: function(model) {
-            return !model.get('use_ssh_tunnel');
-          },
-          readonly: 'isConnected',
-        }, {
-          id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'text',
-          group: gettext('SSH Tunnel'), mode: ['properties', 'edit', 'create'],
-          control: Backform.FileControl, dialog_type: 'select_file', supp_types: ['*'],
-          deps: ['tunnel_authentication', 'use_ssh_tunnel'],
-          disabled: function(model) {
-            let file = model.get('tunnel_identity_file');
-            if (!model.get('tunnel_authentication') && file) {
-              setTimeout(function() {
-                model.set('tunnel_identity_file', null);
-              }, 10);
-            }
-            return !model.get('tunnel_authentication') || !model.get('use_ssh_tunnel');
-          },
-        },{
-          id: 'tunnel_password', label: gettext('Password'), type: 'password',
-          group: gettext('SSH Tunnel'), control: 'input', mode: ['create'],
-          deps: ['use_ssh_tunnel'],
-          disabled: function(model) {
-            return !model.get('use_ssh_tunnel');
-          },
-          readonly: 'isConnected',
-        }, {
-          id: 'save_tunnel_password', controlLabel: gettext('Save password?'),
-          type: 'checkbox', group: gettext('SSH Tunnel'), mode: ['create'],
-          deps: ['connect_now', 'use_ssh_tunnel'], visible: function(model) {
-            return model.get('connect_now') && model.isNew();
-          },
-          disabled: function(model) {
-            if (!current_user.allow_save_tunnel_password ||
-              !model.get('use_ssh_tunnel'))
-              return true;
-
-            return false;
-          },
-        }, {
-          id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
-          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
-        },{
-          id: 'db_res', label: gettext('DB restriction'), type: 'select2', group: gettext('Advanced'),
-          mode: ['properties', 'edit', 'create'], readonly: 'isConnected', select2: {multiple: true, allowClear: false,
-            tags: true, tokenSeparators: [','], first_empty: false, selectOnClose: true, emptyOptions: true},
-        },{
-          id: 'passfile', label: gettext('Password file'), type: 'text',
-          group: gettext('Advanced'), mode: ['edit', 'create'],
-          disabled: 'isValidLib', readonly: 'isConnected', control: Backform.FileControl,
-          dialog_type: 'select_file', supp_types: ['*'],
-        },{
-          id: 'passfile', label: gettext('Password file'), type: 'text',
-          group: gettext('Advanced'), mode: ['properties'],
-          visible: function(model) {
-            var passfile = model.get('passfile');
-            return !_.isUndefined(passfile) && !_.isNull(passfile);
-          },
-        },{
-          id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
-          type: 'int', group: gettext('Advanced'),
-          mode: ['properties', 'edit', 'create'], readonly: 'isConnected',
-          min: 0,
-        }],
-        isVisible: function(model){
-          var serverOwner = model.attributes.user_id;
-          if (!model.isNew() && serverOwner != current_user.id){
-            return false;
+      getSchema: (treeNodeInfo, itemNodeData)=>{
+        let schema = new ServerSchema(
+          getNodeListById(pgBrowser.Nodes['server_group'], treeNodeInfo, itemNodeData),
+          {
+            gid: treeNodeInfo['server_group']._id,
           }
-          return true;
-
-        },
-        isShared: function(model){
-          var serverOwner = model.attributes.user_id;
-          if (!model.isNew() && serverOwner != current_user.id && model.attributes.shared){
-            return true;
-          }
-          return false;
-        },
-        validate: function() {
-          const validateModel = new modelValidation.ModelValidation(this);
-          return validateModel.validate();
-        },
-        isConnected: function(model) {
-          return model.get('connected');
-        },
-        isfgColorSet: function(model) {
-          var bgcolor = model.get('bgcolor'),
-            fgcolor = model.get('fgcolor');
-
-          if(model.get('connected')) {
-            return true;
-          }
-          // If fgcolor is set and bgcolor is not set then force bgcolor
-          // to set as white
-          if(_.isUndefined(bgcolor) || _.isNull(bgcolor) || !bgcolor) {
-            if(fgcolor) {
-              model.set('bgcolor', '#ffffff');
-            }
-          }
-
-          return false;
-        },
-        isSSL: function(model) {
-          var ssl_mode = model.get('sslmode');
-          return _.indexOf(SSL_MODES, ssl_mode) == -1;
-        },
-        isValidLib: function() {
-          // older version of libpq do not support 'passfile' parameter in
-          // connect method, valid libpq must have version >= 100000
-          return pgBrowser.utils.pg_libpq_version < 100000;
-        },
-      }),
+        );
+        return schema;
+      },
       connection_lost: function(i, resp) {
         if (pgBrowser.tree) {
           var t = pgBrowser.tree,
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js
new file mode 100644
index 000000000..afff409a9
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/js/server.ui.js
@@ -0,0 +1,529 @@
+import gettext from 'sources/gettext';
+import _ from 'lodash';
+import {Address4, Address6} from 'ip-address';
+
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import pgAdmin from 'sources/pgadmin';
+import {default as supportedServers} from 'pgadmin.server.supported_servers';
+
+import current_user from 'pgadmin.user_management.current_user';
+import { isEmptyString } from 'sources/validators';
+
+export default class ServerSchema extends BaseUISchema {
+  constructor(serverGroupOptions=[], initValues) {
+    super({
+      gid: undefined,
+      id: undefined,
+      name: '',
+      bgcolor: '',
+      fgcolor: '',
+      sslmode: 'prefer',
+      host: '',
+      hostaddr: '',
+      port: 5432,
+      db: 'postgres',
+      username: current_user.name,
+      role: null,
+      connect_now: true,
+      password: undefined,
+      save_password: false,
+      db_res: '',
+      passfile: undefined,
+      sslcompression: false,
+      sslcert: undefined,
+      sslkey: undefined,
+      sslrootcert: undefined,
+      sslcrl: undefined,
+      service: undefined,
+      use_ssh_tunnel: 0,
+      tunnel_host: undefined,
+      tunnel_port: 22,
+      tunnel_username: undefined,
+      tunnel_identity_file: undefined,
+      tunnel_password: undefined,
+      tunnel_authentication: false,
+      save_tunnel_password: false,
+      connect_timeout: 10,
+      ...initValues,
+    });
+
+    this.serverGroupOptions = serverGroupOptions;
+    _.bindAll(this, 'isShared', 'isSSL');
+  }
+
+  get SSL_MODES() { return ['prefer', 'require', 'verify-ca', 'verify-full']; }
+
+  isShared(state) {
+    if(!this.isNew(state) && state.user_id != current_user.id && state.shared) {
+      return true;
+    }
+    return false;
+  }
+
+  isConnected(state) {
+    return Boolean(state.connected);
+  }
+
+  isSSL(state) {
+    return this.SSL_MODES.indexOf(state.sslmode) == -1;
+  }
+
+  isValidLib() {
+    // older version of libpq do not support 'passfile' parameter in
+    // connect method, valid libpq must have version >= 100000
+    return pgAdmin.Browser.utils.pg_libpq_version < 100000;
+  }
+
+  get baseFields() {
+    let obj = this;
+    return [
+      {
+        id: 'id', label: gettext('ID'), type: 'int', group: null,
+        mode: ['properties'],
+      },{
+        id: 'name', label: gettext('Name'), type: 'text', group: null,
+        mode: ['properties', 'edit', 'create'], noEmpty: true,
+        disabled: obj.isShared,
+      },{
+        id: 'gid', label: gettext('Server group'), type: 'select',
+        options: obj.serverGroupOptions,
+        mode: ['create', 'edit'],
+        controlProps: { allowClear: false },
+        disabled: obj.isShared,
+      },
+      {
+        id: 'server_owner', label: gettext('Shared Server Owner'), type: 'text', mode: ['properties'],
+        visible: function(state) {
+          var serverOwner = state.user_id;
+          if (state.shared && serverOwner != current_user.id && pgAdmin.server_mode == 'True'){
+            return true;
+          }
+          return false;
+        },
+      },
+      {
+        id: 'server_type', label: gettext('Server type'), type: 'select',
+        mode: ['properties'], visible: obj.isConnected,
+        options: supportedServers,
+      }, {
+        id: 'connected', label: gettext('Connected?'), type: 'switch',
+        mode: ['properties'], group: gettext('Connection'),
+      }, {
+        id: 'version', label: gettext('Version'), type: 'text', group: null,
+        mode: ['properties'], visible: obj.isConnected,
+      },
+      {
+        id: 'bgcolor', label: gettext('Background'), type: 'color',
+        group: null, mode: ['edit', 'create'],
+        disabled: obj.isConnected, deps: ['fgcolor'], depChange: (state)=>{
+          if(!state.bgcolor && state.fgcolor) {
+            return {'bgcolor': '#ffffff'};
+          }
+        }
+      },{
+        id: 'fgcolor', label: gettext('Foreground'), type: 'color',
+        group: null, mode: ['edit', 'create'], disabled: obj.isConnected,
+      },
+      {
+        id: 'connect_now', label: gettext('Connect now?'), type: 'switch',
+        group: null, mode: ['create'],
+      },
+      {
+        id: 'shared', label: gettext('Shared?'), type: 'switch',
+        mode: ['properties', 'create', 'edit'],
+        readonly: function(state){
+          var serverOwner = state.user_id;
+          if (obj.isNew(state) && serverOwner != current_user.id) {
+            return true;
+          }
+          return false;
+        }, visible: function(){
+          if (current_user.is_admin && pgAdmin.server_mode == 'True')
+            return true;
+
+          return false;
+        },
+      },
+      {
+        id: 'comment', label: gettext('Comments'), type: 'multiline', group: null,
+        mode: ['properties', 'edit', 'create'],
+      },
+      {
+        id: 'host', label: gettext('Host name/address'), type: 'text', group: gettext('Connection'),
+        mode: ['properties', 'edit', 'create'], disabled: obj.isShared,
+        depChange: (state)=>{
+          if(obj.origData.host != state.host && !obj.isNew(state) && state.connected){
+            obj.informText = gettext(
+              'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
+            );
+          } else {
+            obj.informText = undefined;
+          }
+        }
+      },
+      {
+        id: 'port', label: gettext('Port'), type: 'int', group: gettext('Connection'),
+        mode: ['properties', 'edit', 'create'], min: 1, max: 65535, disabled: obj.isShared,
+        depChange: (state)=>{
+          if(obj.origData.port != state.port && !obj.isNew(state) && state.connected){
+            obj.informText = gettext(
+              'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
+            );
+          } else {
+            obj.informText = undefined;
+          }
+        }
+      },{
+        id: 'db', label: gettext('Maintenance database'), type: 'text', group: gettext('Connection'),
+        mode: ['properties', 'edit', 'create'], readonly: obj.isConnected, disabled: obj.isShared,
+        noEmpty: true,
+      },{
+        id: 'username', label: gettext('Username'), type: 'text', group: gettext('Connection'),
+        mode: ['properties', 'edit', 'create'],
+        depChange: (state)=>{
+          if(obj.origData.username != state.username && !obj.isNew(state) && state.connected){
+            obj.informText = gettext(
+              'To apply changes to the connection configuration, please disconnect from the server and then reconnect.'
+            );
+          } else {
+            obj.informText = undefined;
+          }
+        }
+      },{
+        id: 'kerberos_conn', label: gettext('Kerberos authentication?'), type: 'switch',
+        group: gettext('Connection'),
+      },{
+        id: 'gss_authenticated', label: gettext('GSS authenticated?'), type: 'switch',
+        group: gettext('Connection'), mode: ['properties'], visible: obj.isConnected,
+      },{
+        id: 'gss_encrypted', label: gettext('GSS encrypted?'), type: 'switch',
+        group: gettext('Connection'), mode: ['properties'], visible: obj.isConnected,
+      },{
+        id: 'password', label: gettext('Password'), type: 'password', maxlength: null,
+        group: gettext('Connection'),
+        mode: ['create'],
+        deps: ['connect_now', 'kerberos_conn'],
+        visible: function(state) {
+          return state.connect_now && obj.isNew(state);
+        },
+        disabled: function(state) {return state.kerberos_conn;},
+      },{
+        id: 'save_password', label: gettext('Save password?'),
+        type: 'switch', group: gettext('Connection'), mode: ['create'],
+        deps: ['connect_now', 'kerberos_conn'],
+        visible: function(state) {
+          return state.connect_now && obj.isNew(state);
+        },
+        disabled: function(state) {
+          if (!current_user.allow_save_password || state.kerberos_conn)
+            return true;
+          return false;
+        },
+      },{
+        id: 'role', label: gettext('Role'), type: 'text', group: gettext('Connection'),
+        mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
+      },{
+        id: 'service', label: gettext('Service'), type: 'text',
+        mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
+        group: gettext('Connection'),
+      },
+      {
+        id: 'sslmode', label: gettext('SSL mode'), type: 'select', group: gettext('SSL'),
+        controlProps: {
+          allowClear: false,
+        },
+        mode: ['properties', 'edit', 'create'], disabled: obj.isConnected,
+        options: [
+          {label: gettext('Allow'), value: 'allow'},
+          {label: gettext('Prefer'), value: 'prefer'},
+          {label: gettext('Require'), value: 'require'},
+          {label: gettext('Disable'), value: 'disable'},
+          {label: gettext('Verify-CA'), value: 'verify-ca'},
+          {label: gettext('Verify-Full'), value: 'verify-full'},
+        ],
+      },
+      {
+        id: 'sslcert', label: gettext('Client certificate'), type: 'file',
+        group: gettext('SSL'), mode: ['edit', 'create'],
+        disabled: obj.isSSL, readonly: obj.isConnected,
+        controlProps: {
+          dialogType: 'select_file', supportedTypes: ['*'],
+        },
+        deps: ['sslmode'],
+      },
+      {
+        id: 'sslkey', label: gettext('Client certificate key'), type: 'file',
+        group: gettext('SSL'), mode: ['edit', 'create'],
+        disabled: obj.isSSL, readonly: obj.isConnected,
+        controlProps: {
+          dialogType: 'select_file', supportedTypes: ['*'],
+        },
+        deps: ['sslmode'],
+      },{
+        id: 'sslrootcert', label: gettext('Root certificate'), type: 'file',
+        group: gettext('SSL'), mode: ['edit', 'create'],
+        disabled: obj.isSSL, readonly: obj.isConnected,
+        controlProps: {
+          dialogType: 'select_file', supportedTypes: ['*'],
+        },
+        deps: ['sslmode'],
+      },{
+        id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'file',
+        group: gettext('SSL'), mode: ['edit', 'create'],
+        disabled: obj.isSSL, readonly: obj.isConnected,
+        controlProps: {
+          dialogType: 'select_file', supportedTypes: ['*'],
+        },
+        deps: ['sslmode'],
+      },
+      {
+        id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
+        mode: ['edit', 'create'], group: gettext('SSL'),
+        disabled: obj.isSSL, readonly: obj.isConnected,
+        deps: ['sslmode'],
+      },
+      {
+        id: 'sslcert', label: gettext('Client certificate'), type: 'text',
+        group: gettext('SSL'), mode: ['properties'],
+        deps: ['sslmode'],
+        visible: function(state) {
+          var sslcert = state.sslcert;
+          return !_.isUndefined(sslcert) && !_.isNull(sslcert);
+        },
+      },{
+        id: 'sslkey', label: gettext('Client certificate key'), type: 'text',
+        group: gettext('SSL'), mode: ['properties'],
+        deps: ['sslmode'],
+        visible: function(state) {
+          var sslkey = state.sslkey;
+          return !_.isUndefined(sslkey) && !_.isNull(sslkey);
+        },
+      },{
+        id: 'sslrootcert', label: gettext('Root certificate'), type: 'text',
+        group: gettext('SSL'), mode: ['properties'],
+        deps: ['sslmode'],
+        visible: function(state) {
+          var sslrootcert = state.sslrootcert;
+          return !_.isUndefined(sslrootcert) && !_.isNull(sslrootcert);
+        },
+      },{
+        id: 'sslcrl', label: gettext('Certificate revocation list'), type: 'text',
+        group: gettext('SSL'), mode: ['properties'],
+        deps: ['sslmode'],
+        visible: function(state) {
+          var sslcrl = state.sslcrl;
+          return !_.isUndefined(sslcrl) && !_.isNull(sslcrl);
+        },
+      },{
+        id: 'sslcompression', label: gettext('SSL compression?'), type: 'switch',
+        mode: ['properties'], group: gettext('SSL'),
+        deps: ['sslmode'],
+        visible: function(state) {
+          return _.indexOf(obj.SSL_MODES, state.sslmode) != -1;
+        },
+      },
+      {
+        id: 'use_ssh_tunnel', label: gettext('Use SSH tunneling'), type: 'switch',
+        mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
+        disabled: function() {
+          return !pgAdmin.Browser.utils.support_ssh_tunnel;
+        },
+        readonly: obj.isConnected,
+      },{
+        id: 'tunnel_host', label: gettext('Tunnel host'), type: 'text', group: gettext('SSH Tunnel'),
+        mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
+        disabled: function(state) {
+          return !state.use_ssh_tunnel;
+        },
+        readonly: obj.isConnected,
+      },{
+        id: 'tunnel_port', label: gettext('Tunnel port'), type: 'int', group: gettext('SSH Tunnel'),
+        mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'], max: 65535,
+        disabled: function(state) {
+          return !state.use_ssh_tunnel;
+        },
+        readonly: obj.isConnected,
+      },{
+        id: 'tunnel_username', label: gettext('Username'), type: 'text', group: gettext('SSH Tunnel'),
+        mode: ['properties', 'edit', 'create'], deps: ['use_ssh_tunnel'],
+        disabled: function(state) {
+          return !state.use_ssh_tunnel;
+        },
+        readonly: obj.isConnected,
+      },{
+        id: 'tunnel_authentication', label: gettext('Authentication'), type: 'toggle',
+        mode: ['properties', 'edit', 'create'], group: gettext('SSH Tunnel'),
+        options: [
+          {'label': gettext('Password'), value: false},
+          {'label': gettext('Identity file'), value: true},
+        ],
+        disabled: function(state) {
+          return !state.use_ssh_tunnel;
+        },
+        readonly: obj.isConnected,
+      },
+      {
+        id: 'tunnel_identity_file', label: gettext('Identity file'), type: 'file',
+        group: gettext('SSH Tunnel'), mode: ['properties', 'edit', 'create'],
+        controlProps: {
+          dialogType: 'select_file', supportedTypes: ['*'],
+        },
+        deps: ['tunnel_authentication', 'use_ssh_tunnel'],
+        depChange: (state)=>{
+          if (!state.tunnel_authentication && state.tunnel_identity_file) {
+            return {tunnel_identity_file: null};
+          }
+        },
+        disabled: function(state) {
+          return !state.tunnel_authentication || !state.use_ssh_tunnel;
+        },
+      },
+      {
+        id: 'tunnel_password', label: gettext('Password'), type: 'password',
+        group: gettext('SSH Tunnel'), mode: ['create'],
+        deps: ['use_ssh_tunnel'],
+        disabled: function(state) {
+          return !state.use_ssh_tunnel;
+        },
+        readonly: obj.isConnected,
+      }, {
+        id: 'save_tunnel_password', label: gettext('Save password?'),
+        type: 'switch', group: gettext('SSH Tunnel'), mode: ['create'],
+        deps: ['connect_now', 'use_ssh_tunnel'],
+        visible: function(state) {
+          return state.connect_now && obj.isNew(state);
+        },
+        disabled: function(state) {
+          return (!current_user.allow_save_tunnel_password || !state.use_ssh_tunnel);
+        },
+      }, {
+        id: 'hostaddr', label: gettext('Host address'), type: 'text', group: gettext('Advanced'),
+        mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
+      },
+      {
+        id: 'db_res', label: gettext('DB restriction'), type: 'select', group: gettext('Advanced'),
+        options: [],
+        mode: ['properties', 'edit', 'create'], readonly: obj.isConnected, controlProps: {
+          multiple: true, allowClear: false, creatable: true},
+      },
+      {
+        id: 'passfile', label: gettext('Password file'), type: 'file',
+        group: gettext('Advanced'), mode: ['edit', 'create'],
+        disabled: obj.isValidLib, readonly: obj.isConnected,
+        controlProps: {
+          dialogType: 'select_file', supportedTypes: ['*'],
+        },
+      },
+      {
+        id: 'passfile', label: gettext('Password file'), type: 'text',
+        group: gettext('Advanced'), mode: ['properties'],
+        visible: function(state) {
+          var passfile = state.passfile;
+          return !_.isUndefined(passfile) && !_.isNull(passfile);
+        },
+      },{
+        id: 'connect_timeout', label: gettext('Connection timeout (seconds)'),
+        type: 'int', group: gettext('Advanced'),
+        mode: ['properties', 'edit', 'create'], readonly: obj.isConnected,
+        min: 0,
+      }
+    ];
+  }
+
+  validate(state, setError) {
+    let errmsg = null;
+
+    if (isEmptyString(state.service)) {
+      errmsg = gettext('Either Host name, Address or Service must be specified.');
+      if(isEmptyString(state.host) && isEmptyString(state.hostaddr)) {
+        setError('host', errmsg);
+        return true;
+      } else {
+        errmsg = null;
+        setError('host', errmsg);
+        setError('hostaddr', errmsg);
+      }
+
+      /* IP address validate */
+      if (state.hostaddr) {
+        try {
+          new Address4(state.hostaddr);
+        } catch(e) {
+          try {
+            new Address6(state.hostaddr);
+          } catch(ex) {
+            errmsg = gettext('Host address must be valid IPv4 or IPv6 address.');
+            setError('hostaddr', errmsg);
+            return true;
+          }
+        }
+      } else {
+        setError('hostaddr', null);
+      }
+
+      if(isEmptyString(state.username)) {
+        errmsg = gettext('Username must be specified.');
+        setError('username', errmsg);
+        return true;
+      } else {
+        errmsg = null;
+        setError('username', errmsg);
+      }
+
+      if(isEmptyString(state.port)) {
+        errmsg = gettext('Port must be specified.');
+        setError('port', errmsg);
+        return true;
+      } else {
+        errmsg = null;
+        setError('port', errmsg);
+      }
+    } else {
+      errmsg = null;
+      _.each(['host', 'hostaddr', 'db', 'username', 'port'], (item) => {
+        setError(item, errmsg);
+      });
+    }
+
+    if (state.use_ssh_tunnel) {
+      if(isEmptyString(state.tunnel_host)) {
+        errmsg = gettext('SSH Tunnel host must be specified.');
+        setError('tunnel_host', errmsg);
+        return true;
+      } else {
+        errmsg = null;
+        setError('tunnel_host', errmsg);
+      }
+
+      if(isEmptyString(state.tunnel_port)) {
+        errmsg = gettext('SSH Tunnel port must be specified.');
+        setError('tunnel_port', errmsg);
+        return true;
+      } else {
+        errmsg = null;
+        setError('tunnel_port', errmsg);
+      }
+
+      if(isEmptyString(state.tunnel_username)) {
+        errmsg = gettext('SSH Tunnel username must be specified.');
+        setError('tunnel_username', errmsg);
+        return true;
+      } else {
+        errmsg = null;
+        setError('tunnel_username', errmsg);
+      }
+
+      if (state.tunnel_authentication) {
+        if(isEmptyString(state.tunnel_identity_file)) {
+          errmsg = gettext('SSH Tunnel identity file must be specified.');
+          setError('tunnel_identity_file', errmsg);
+          return true;
+        } else {
+          errmsg = null;
+          setError('tunnel_identity_file', errmsg);
+        }
+      }
+    }
+    return false;
+  }
+}
diff --git a/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js b/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js
new file mode 100644
index 000000000..25d30729d
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/servers/static/js/variable.ui.js
@@ -0,0 +1,130 @@
+import gettext from 'sources/gettext';
+import _ from 'lodash';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import { getNodeAjaxOptions, getNodeListByName } from '../../../../static/js/node_ajax';
+
+export function getNodeVariableSchema(nodeObj, treeNodeInfo, itemNodeData, hasDatabase, hasRole) {
+  let keys = ['name', 'value'];
+  if(hasDatabase) {
+    keys.push('database');
+  }
+  if(hasRole) {
+    keys.push('role');
+  }
+  return new VariableSchema(
+    ()=>getNodeAjaxOptions('vopts', nodeObj, treeNodeInfo, itemNodeData, null, (vars)=>{
+      var res = [];
+      _.each(vars, function(v) {
+        res.push({
+          'value': v.name,
+          'image': undefined,
+          'label': v.name,
+          'vartype': v.vartype,
+          'enumvals': v.enumvals,
+          'max_val': v.max_val,
+          'min_val': v.min_val,
+        });
+      });
+
+      return res;
+    }),
+    ()=>getNodeListByName('database', treeNodeInfo, itemNodeData),
+    ()=>getNodeListByName('role', treeNodeInfo, itemNodeData),
+    keys
+  );
+}
+
+export default class VariableSchema extends BaseUISchema {
+  constructor(vnameOptions, databaseOptions, roleOptions, keys) {
+    super({
+      name: undefined,
+      value: undefined,
+      role: null,
+      database: null,
+    });
+    this.vnameOptions = vnameOptions;
+    this.databaseOptions = databaseOptions;
+    this.roleOptions = roleOptions;
+    this.varTypes = {};
+    this.keys = keys;
+  }
+
+  setVarTypes(options) {
+    options.forEach((option)=>{
+      this.varTypes[option.value] = {
+        ...option,
+      };
+    });
+  }
+
+  getValueFieldProps(variable) {
+    switch(variable?.vartype) {
+    case 'bool':
+      return 'switch';
+    case 'enum':
+      return {
+        cell: 'select',
+        options: (variable.enumvals || []).map((val)=>({
+          label: val,
+          value: val
+        }))
+      };
+    case 'integer':
+      return 'int';
+    case 'real':
+      return 'number';
+    case 'string':
+      return 'text';
+    default:
+      return '';
+    }
+  }
+
+  get baseFields() {
+    let obj = this;
+    return [
+      {
+        id: 'id', label: gettext('ID'), type: 'int', group: null,
+        mode: ['properties'],
+      },
+      {
+        id: 'name', label: gettext('Name'), type:'text',
+        readonly: function(state) {
+          return !obj.isNew(state);
+        },
+        cell: ()=>({
+          cell: 'select',
+          options: this.vnameOptions,
+          optionsLoaded: (options)=>{obj.setVarTypes(options);},
+          controlProps: { allowClear: false },
+        }),
+        noEmpty: true,
+      },
+      {
+        id: 'value', label: gettext('Value'), type: 'text',
+        noEmpty: true, deps: ['name'],
+        depChange: (state, changeSource)=>{
+          if(changeSource == 'name') {
+            return {...state
+              , value: null
+            };
+          }
+        },
+        cell: (row)=>{
+          let variable = this.varTypes[row.name];
+          return this.getValueFieldProps(variable);
+        }
+      },
+      {id: 'database', label: gettext('Database'), type: 'text',
+        cell: ()=>({cell: 'select', options: this.databaseOptions }),
+      },
+      {id: 'role', label: gettext('Role'), type: 'text',
+        cell: ()=>({cell: 'select', options: this.roleOptions,
+          controlProps: {
+            allowClear: false,
+          }
+        }),
+      },
+    ];
+  }
+}
diff --git a/web/pgadmin/browser/server_groups/static/js/server_group.js b/web/pgadmin/browser/server_groups/static/js/server_group.js
index 6942b8308..5c6c41dfa 100644
--- a/web/pgadmin/browser/server_groups/static/js/server_group.js
+++ b/web/pgadmin/browser/server_groups/static/js/server_group.js
@@ -6,6 +6,7 @@
 // This software is released under the PostgreSQL Licence
 //
 //////////////////////////////////////////////////////////////
+import ServerGroupSchema from './server_group.ui';
 
 define('pgadmin.node.server_group', [
   'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
@@ -35,52 +36,7 @@ define('pgadmin.node.server_group', [
           data: {'action': 'create'}, icon: 'wcTabIcon icon-server_group',
         }]);
       },
-      model: pgAdmin.Browser.Node.Model.extend({
-        defaults: {
-          id: undefined,
-          name: null,
-          user_id: undefined,
-        },
-        schema: [
-          {
-            id: 'id', label: gettext('ID'), type: 'int', group: null,
-            mode: ['properties'],
-            visible: function(model){
-              if (model.attributes.user_id != current_user.id && !current_user.is_admin)
-                return false;
-              return true;
-            },
-          },{
-            id: 'name', label: gettext('Name'), type: 'text', group: null,
-            mode: ['properties', 'edit', 'create'],
-            disabled: function(model){
-              if (model.attributes.user_id != current_user.id && !_.isUndefined(model.attributes.user_id))
-                return true;
-              return false;
-            },
-          },
-        ],
-        validate: function() {
-          var errmsg = null;
-
-          this.errorModel.clear();
-
-          if (!this.isNew() && 'id' in this.changed) {
-            errmsg = gettext('The ID cannot be changed.');
-            this.errorModel.set('id', errmsg);
-            return errmsg;
-          }
-          if (_.isUndefined(this.get('name')) ||
-            _.isNull(this.get('name')) ||
-              String(this.get('name')).replace(/^\s+|\s+$/g, '') == '') {
-            errmsg = gettext('Name cannot be empty.');
-            this.errorModel.set('name', errmsg);
-            return errmsg;
-          }
-          return null;
-        },
-      }),
-
+      getSchema: ()=>new ServerGroupSchema(),
       canDrop: function(itemData) {
         var serverOwner = itemData.user_id;
         if (serverOwner != current_user.id && !_.isUndefined(serverOwner))
diff --git a/web/pgadmin/browser/server_groups/static/js/server_group.ui.js b/web/pgadmin/browser/server_groups/static/js/server_group.ui.js
new file mode 100644
index 000000000..f633640ae
--- /dev/null
+++ b/web/pgadmin/browser/server_groups/static/js/server_group.ui.js
@@ -0,0 +1,25 @@
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+
+export default class ServerGroupSchema extends BaseUISchema {
+  constructor() {
+    super({
+      id: undefined,
+      name: null,
+      user_id: undefined,
+    });
+  }
+
+  get baseFields() {
+    return [
+      {
+        id: 'id', label: gettext('ID'), type: 'int', group: null,
+        mode: ['properties'], visible: true,
+      },{
+        id: 'name', label: gettext('Name'), type: 'text', group: null,
+        mode: ['properties', 'edit', 'create'], noEmpty: true,
+        disabled: false,
+      }
+    ];
+  }
+}
diff --git a/web/pgadmin/browser/static/js/collection.js b/web/pgadmin/browser/static/js/collection.js
index 47c877156..aadc34b6c 100644
--- a/web/pgadmin/browser/static/js/collection.js
+++ b/web/pgadmin/browser/static/js/collection.js
@@ -7,6 +7,8 @@
 //
 //////////////////////////////////////////////////////////////
 
+import {removeNodeView} from './node_view';
+
 define([
   'sources/gettext', 'jquery', 'underscore', 'sources/pgadmin',
   'backbone', 'alertify', 'backform', 'backgrid', 'sources/browser/generate_url',
@@ -238,6 +240,8 @@ define([
           j.data('obj-view', null);
         }
 
+        /* Remove any dom rendered by getNodeView */
+        removeNodeView(j[0]);
         // Make sure the HTML element is empty.
         j.empty();
         j.data('obj-view', gridView);
diff --git a/web/pgadmin/browser/static/js/node.js b/web/pgadmin/browser/static/js/node.js
index d58a2e037..1b00117d1 100644
--- a/web/pgadmin/browser/static/js/node.js
+++ b/web/pgadmin/browser/static/js/node.js
@@ -7,6 +7,8 @@
 //
 //////////////////////////////////////////////////////////////
 
+import {getNodeView, removeNodeView} from './node_view';
+
 define('pgadmin.browser.node', [
   'sources/tree/pgadmin_tree_node', 'sources/url_for',
   'sources/gettext', 'jquery', 'underscore', 'sources/pgadmin',
@@ -1216,6 +1218,22 @@ define('pgadmin.browser.node', [
           // Cache the current IDs for next time
           $(this).data('node-prop', treeHierarchy);
 
+          /* Remove any dom rendered by getNodeView */
+          removeNodeView(j[0]);
+          /* getSchema is a schema for React. Get the react node view */
+          if(that.getSchema) {
+            let treeNodeInfo = that.getTreeNodeHierarchy.apply(this, [item]);
+            getNodeView(
+              that.type, treeNodeInfo, 'properties', data, 'tab', j[0], this, onCancelFunc, onEdit,
+              (nodeData)=>{
+                if(nodeData.node) {
+                  onSaveFunc(nodeData.node, treeNodeInfo);
+                }
+              }
+            );
+            return;
+          }
+
           if (!content.hasClass('has-pg-prop-btn-group'))
             content.addClass('has-pg-prop-btn-group');
 
@@ -1460,6 +1478,28 @@ define('pgadmin.browser.node', [
             action = 'edit';
           }
           self.$container.attr('action-mode', action);
+
+          self.icon(
+            _.isFunction(that['node_image']) ?
+              (that['node_image']).apply(that, [data]) :
+              (that['node_image'] || ('icon-' + that.type))
+          );
+          /* Remove any dom rendered by getNodeView */
+          removeNodeView(j[0]);
+          /* getSchema is a schema for React. Get the react node view */
+          if(that.getSchema) {
+            let treeNodeInfo = that.getTreeNodeHierarchy.apply(this, [item]);
+            getNodeView(
+              that.type, treeNodeInfo, action, data, 'dialog', j[0], this, onCancelFunc, onEdit,
+              (nodeData)=>{
+                if(nodeData.node) {
+                  onSaveFunc(nodeData.node, treeNodeInfo);
+                }
+              }
+            );
+            return;
+          }
+
           // We need to release any existing view, before
           // creating the new view.
           if (view) {
@@ -1654,10 +1694,10 @@ define('pgadmin.browser.node', [
           // Closing this panel
           this.close();
         }.bind(panel),
-        updateTreeItem = function(obj) {
+        updateTreeItem = function(obj, tnode, node_info) {
           var _old = data,
-            _new = _.clone(view.model.tnode),
-            info = _.clone(view.model.node_info);
+            _new = tnode || _.clone(view.model.tnode),
+            info = node_info || _.clone(view.model.node_info);
 
           // Clear the cache for this node now.
           setTimeout(function() {
@@ -1681,7 +1721,7 @@ define('pgadmin.browser.node', [
           );
           closePanel(false);
         },
-        saveNewNode = function(obj) {
+        saveNewNode = function(obj, tnode, node_info) {
           var $props = this.$container.find('.obj_properties').first(),
             objview = $props.data('obj-view');
 
@@ -1691,8 +1731,8 @@ define('pgadmin.browser.node', [
           }, 0);
           try {
             pgBrowser.Events.trigger(
-              'pgadmin:browser:tree:add', _.clone(objview.model.tnode),
-              _.clone(objview.model.node_info)
+              'pgadmin:browser:tree:add', _.clone(tnode || objview.model.tnode),
+              _.clone(node_info || objview.model.node_info)
             );
           } catch (e) {
             console.warn(e.stack || e);
@@ -1724,10 +1764,10 @@ define('pgadmin.browser.node', [
         }
       } else {
         /* Show properties */
-        properties();
         onEdit = editInNewPanel.bind(panel);
+        properties();
       }
-      if (panel.closeable()) {
+      if (panel.closeable() && !that.getSchema) {
         panel.on(wcDocker.EVENT.CLOSING, warnBeforeChangesLost.bind(
           panel,
           gettext('Changes will be lost. Are you sure you want to close the dialog?'),
diff --git a/web/pgadmin/browser/static/js/node_ajax.js b/web/pgadmin/browser/static/js/node_ajax.js
new file mode 100644
index 000000000..494cbb838
--- /dev/null
+++ b/web/pgadmin/browser/static/js/node_ajax.js
@@ -0,0 +1,164 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import _ from 'lodash';
+import getApiInstance from '../../../static/js/api_instance';
+import {generate_url} from 'sources/browser/generate_url';
+import pgAdmin from 'sources/pgadmin';
+
+/* It generates the URL based on tree node selected */
+export function generateNodeUrl(treeNodeInfo, actionType, itemNodeData, withId, jumpAfterNode) {
+  let opURL = {
+      'create': 'obj',
+      'drop': 'obj',
+      'edit': 'obj',
+      'properties': 'obj',
+      'statistics': 'stats',
+    },
+    priority = -Infinity;
+  let nodeObj = this;
+  let itemID = withId && itemNodeData._type == nodeObj.type ? encodeURIComponent(itemNodeData._id) : '';
+  actionType = actionType in opURL ? opURL[actionType] : actionType;
+
+  if (nodeObj.parent_type) {
+    if (_.isString(nodeObj.parent_type)) {
+      let p = treeNodeInfo[nodeObj.parent_type];
+      if (p) {
+        priority = p.priority;
+      }
+    } else {
+      _.each(nodeObj.parent_type, function(o) {
+        let p = treeNodeInfo[o];
+        if (p) {
+          if (priority < p.priority) {
+            priority = p.priority;
+          }
+        }
+      });
+    }
+  }
+
+  let jump_after_priority = priority;
+  if(jumpAfterNode && treeNodeInfo[jumpAfterNode]) {
+    jump_after_priority = treeNodeInfo[jumpAfterNode].priority;
+  }
+
+  var nodePickFunction = function(treeInfoValue) {
+    return (treeInfoValue.priority <= jump_after_priority || treeInfoValue.priority == priority);
+  };
+
+  return generate_url(pgAdmin.Browser.URL, treeNodeInfo, actionType, nodeObj.type, nodePickFunction, itemID);
+}
+
+
+/* Get the nodes list as options required by select controls
+ * The options are cached for performance reasons.
+ */
+export function getNodeAjaxOptions(url, nodeObj, treeNodeInfo, itemNodeData, params={}, transform=(data)=>data) {
+  let otherParams = {
+    urlWithId: false,
+    jumpAfterNode: null,
+    ...params
+  };
+  return new Promise((resolve, reject)=>{
+    const api = getApiInstance();
+    let fullUrl = '';
+    if(url) {
+      fullUrl = generateNodeUrl.call(
+        nodeObj, treeNodeInfo, url, itemNodeData, otherParams.urlWithId, nodeObj.parent_type, otherParams.jumpAfterNode
+      );
+    }
+
+    if (url) {
+      let cacheNode = pgAdmin.Browser.Nodes[otherParams.cacheNode] || nodeObj;
+      let cacheLevel = otherParams.cacheLevel || cacheNode.cache_level(treeNodeInfo, otherParams.urlWithId);
+      /*
+      * We needs to check, if we have already cached data for this url.
+      * If yes - use that, and do not bother about fetching it again,
+      * and use it.
+      */
+      var data = cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel);
+
+      if (_.isUndefined(data) || _.isNull(data)) {
+        api.get(fullUrl)
+          .then((res)=>{
+            data = res.data.data;
+            cacheNode.cache(nodeObj.type + '#' + url, treeNodeInfo, cacheLevel, data);
+            resolve(transform(data));
+          })
+          .catch((err)=>{
+            reject(err);
+          });
+      } else {
+        // To fetch only options from cache, we do not need time from 'at'
+        // attribute but only options.
+        resolve(transform(data.data || []));
+      }
+    }
+  });
+}
+
+/* Get the nodes list based on current selected node id */
+export function getNodeListById(nodeObj, treeNodeInfo, itemNodeData, filter=()=>true) {
+  /* Transform the result to add image details */
+  const transform = (rows) => {
+    var res = [];
+
+    _.each(rows, function(r) {
+      if (filter(r)) {
+        var l = (_.isFunction(nodeObj['node_label']) ?
+            (nodeObj['node_label']).apply(nodeObj, [r]) :
+            r.label),
+          image = (_.isFunction(nodeObj['node_image']) ?
+            (nodeObj['node_image']).apply(nodeObj, [r]) :
+            (nodeObj['node_image'] || ('icon-' + nodeObj.type)));
+
+        res.push({
+          'value': r._id,
+          'image': image,
+          'label': l,
+        });
+      }
+    });
+
+    return res;
+  };
+
+  return getNodeAjaxOptions('nodes', nodeObj, treeNodeInfo, itemNodeData, null, transform);
+}
+
+/* Get the nodes list based on node name passed */
+export function getNodeListByName(node, treeNodeInfo, itemNodeData, filter=()=>true, postTransform=(res)=>res) {
+  let nodeObj = pgAdmin.Browser.Nodes[node];
+  /* Transform the result to add image details */
+  const transform = (rows) => {
+    var res = [];
+
+    _.each(rows, function(r) {
+      if (filter(r)) {
+        var l = (_.isFunction(nodeObj['node_label']) ?
+            (nodeObj['node_label']).apply(nodeObj, [r]) :
+            r.label),
+          image = (_.isFunction(nodeObj['node_image']) ?
+            (nodeObj['node_image']).apply(nodeObj, [r]) :
+            (nodeObj['node_image'] || ('icon-' + nodeObj.type)));
+
+        res.push({
+          'value': r.label,
+          'image': image,
+          'label': l,
+        });
+      }
+    });
+
+    return postTransform(res);
+  };
+
+  return getNodeAjaxOptions('nodes', nodeObj, treeNodeInfo, itemNodeData, null, transform);
+}
diff --git a/web/pgadmin/browser/static/js/node_view.jsx b/web/pgadmin/browser/static/js/node_view.jsx
new file mode 100644
index 000000000..837364769
--- /dev/null
+++ b/web/pgadmin/browser/static/js/node_view.jsx
@@ -0,0 +1,198 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+
+import pgAdmin from 'sources/pgadmin';
+import getApiInstance from 'sources/api_instance';
+import {getHelpUrl} from 'pgadmin.help';
+import SchemaView from 'sources/SchemaView';
+import { generateNodeUrl } from './node_ajax';
+import Alertify from 'pgadmin.alertifyjs';
+import gettext from 'sources/gettext';
+import 'wcdocker';
+
+/* The entry point for rendering React based view in properties, called in node.js */
+export function getNodeView(nodeType, treeNodeInfo, actionType, itemNodeData, formType, container, containerPanel, onCancel, onEdit, onSave) {
+  let nodeObj = pgAdmin.Browser.Nodes[nodeType];
+  let serverInfo = treeNodeInfo && ('server' in treeNodeInfo) &&
+      pgAdmin.Browser.serverInfo && pgAdmin.Browser[treeNodeInfo.server._id];
+  let inCatalog = treeNodeInfo && ('catalog' in treeNodeInfo);
+  let urlBase = generateNodeUrl.call(nodeObj, treeNodeInfo, actionType, itemNodeData, false, null);
+  const api = getApiInstance();
+  const url = (isNew)=>{
+    return urlBase + (isNew ? '' : itemNodeData._id);
+  };
+  let isDirty = false; // usefull for warnings
+  let warnOnCloseFlag = true;
+  const confirmOnCloseReset = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
+
+  /* Called when dialog is opened in edit mode, promise required */
+  let initData = ()=>new Promise((resolve, reject)=>{
+    api.get(url(false))
+      .then((res)=>{
+        resolve(res.data);
+      })
+      .catch((err)=>{
+        if(err.response){
+          console.error('error resp', err.response);
+        } else if(err.request){
+          console.error('error req', err.request);
+        } else if(err.message){
+          console.error('error msg', err.message);
+        }
+        reject(err);
+      });
+  });
+
+  /* on save button callback, promise required */
+  const onSaveClick = (isNew, data)=>new Promise((resolve, reject)=>{
+    return api({
+      url: url(isNew),
+      method: isNew ? 'POST' : 'PUT',
+      data: data,
+    }).then((res)=>{
+      /* Don't warn the user before closing dialog */
+      warnOnCloseFlag = false;
+      resolve(res.data);
+      onSave(res.data);
+    }).catch((err)=>{
+      reject(err);
+    });
+  });
+
+  /* Called when switched to SQL tab, promise required */
+  const getSQLValue = (isNew, changedData)=>{
+    const msqlUrl = generateNodeUrl.call(nodeObj, treeNodeInfo, 'msql', itemNodeData, !isNew, null);
+    return new Promise((resolve, reject)=>{
+      api({
+        url: msqlUrl,
+        method: 'GET',
+        params: changedData,
+      }).then((res)=>{
+        resolve(res.data.data);
+      }).catch((err)=>{
+        if(err.response){
+          console.error('error resp', err.response);
+        } else if(err.request){
+          console.error('error req', err.request);
+        } else if(err.message){
+          console.error('error msg', err.message);
+        }
+        reject(err);
+      });
+    });
+  };
+
+  /* Callback for help button */
+  const onHelp = (isSqlHelp=false, isNew=false)=>{
+    if(isSqlHelp) {
+      let server = treeNodeInfo.server;
+      let url = pgAdmin.Browser.utils.pg_help_path;
+      if (server.server_type == 'ppas') {
+        url = pgAdmin.Browser.utils.edbas_help_path;
+      }
+
+      let fullUrl = '';
+      if (nodeObj.sqlCreateHelp == '' && nodeObj.sqlAlterHelp != '') {
+        fullUrl = getHelpUrl(url, nodeObj.sqlAlterHelp, server.version, server.server_type);
+      } else if (nodeObj.sqlCreateHelp != '' && nodeObj.sqlAlterHelp == '') {
+        fullUrl = getHelpUrl(url, nodeObj.sqlCreateHelp, server.version, server.server_type);
+      } else {
+        if (isNew) {
+          fullUrl = getHelpUrl(url, nodeObj.sqlCreateHelp, server.version, server.server_type);
+        } else {
+          fullUrl = getHelpUrl(url, nodeObj.sqlAlterHelp, server.version, server.server_type);
+        }
+      }
+
+      window.open(fullUrl, 'postgres_help');
+    } else {
+      window.open(nodeObj.dialogHelp, 'pgadmin_help');
+    }
+  };
+
+  /* A warning before closing the dialog with unsaved changes, based on preference */
+  let warnBeforeChangesLost = (yesCallback)=>{
+    let confirmOnClose = pgAdmin.Browser.get_preferences_for_module('browser').confirm_on_properties_close;
+    if (warnOnCloseFlag && confirmOnClose) {
+      if(isDirty){
+        Alertify.confirm(
+          gettext('Warning'),
+          gettext('Changes will be lost. Are you sure you want to close the dialog?'),
+          function() {
+            yesCallback();
+            return true;
+          },
+          function() {
+            return true;
+          }
+        ).set('labels', {
+          ok: gettext('Yes'),
+          cancel: gettext('No'),
+        }).show();
+      } else {
+        return true;
+      }
+      return false;
+    } else {
+      yesCallback();
+      return true;
+    }
+  };
+
+  /* Bind the wcDocker dialog close event and check if user should be warned */
+  if (containerPanel.closeable()) {
+    containerPanel.on(window.wcDocker.EVENT.CLOSING, warnBeforeChangesLost.bind(
+      containerPanel,
+      function() {
+        containerPanel.off(window.wcDocker.EVENT.CLOSING);
+        /* Always clean up the react mounted dom before closing */
+        removeNodeView(container);
+        containerPanel.close();
+      }
+    ));
+  }
+
+  /* All other useful details can go with this object */
+  const viewHelperProps = {
+    mode: actionType,
+    serverInfo: serverInfo ? {
+      type: serverInfo.type,
+      version: serverInfo.version,
+    }: undefined,
+    inCatalog: inCatalog,
+  };
+
+  /* Fire at will, mount the DOM */
+  ReactDOM.render(
+    <SchemaView
+      formType={formType}
+      getInitData={initData}
+      schema={nodeObj.getSchema.call(nodeObj, treeNodeInfo, itemNodeData)}
+      viewHelperProps={viewHelperProps}
+      onSave={onSaveClick}
+      onClose={()=>containerPanel.close()}
+      onHelp={onHelp}
+      onEdit={onEdit}
+      onDataChange={(dataChanged)=>{
+        isDirty = dataChanged;
+      }}
+      confirmOnCloseReset={confirmOnCloseReset}
+      hasSQL={nodeObj.hasSQL && (actionType === 'create' || actionType === 'edit')}
+      getSQLValue={getSQLValue}
+      disableSqlHelp={nodeObj.sqlAlterHelp == '' && nodeObj.sqlCreateHelp == ''}
+    />, container);
+}
+
+/* When switching from normal node to collection node, clean up the React mounted DOM */
+export function removeNodeView(container) {
+  ReactDOM.unmountComponentAtNode(container);
+}
diff --git a/web/pgadmin/browser/templates/browser/js/messages.js b/web/pgadmin/browser/templates/browser/js/messages.js
index a101033d1..dba5d6687 100644
--- a/web/pgadmin/browser/templates/browser/js/messages.js
+++ b/web/pgadmin/browser/templates/browser/js/messages.js
@@ -28,6 +28,7 @@ define(
     'MUST_BE_NUM' : gettext("'%s' must be a numeric."),
     'MUST_GR_EQ' : gettext("'%s' must be greater than or equal to %s."),
     'MUST_LESS_EQ' : gettext("'%s' must be less than or equal to %s."),
+    'CANNOT_BE_EMPTY': gettext("'%s' cannot be empty."),
     'STATISTICS_LABEL': gettext("Statistics"),
     'STATISTICS_VALUE_LABEL': gettext("Value"),
     'NODE_HAS_NO_SQL': gettext("No SQL could be generated for the selected object."),
diff --git a/web/pgadmin/misc/themes/__init__.py b/web/pgadmin/misc/themes/__init__.py
index 19dcd965d..8ebe86c39 100644
--- a/web/pgadmin/misc/themes/__init__.py
+++ b/web/pgadmin/misc/themes/__init__.py
@@ -42,7 +42,7 @@ def themes(app):
                 # Let the default theme go if exception occurs
                 pass
 
-            return theme_css
+            return theme_css, theme
 
         return {
             'get_theme_css': get_theme_css,
diff --git a/web/pgadmin/static/js/SchemaView/DataGridView.jsx b/web/pgadmin/static/js/SchemaView/DataGridView.jsx
new file mode 100644
index 000000000..3cf16783b
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/DataGridView.jsx
@@ -0,0 +1,399 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* The DataGridView component is based on react-table component */
+
+import React, { useCallback, useMemo, useState } from 'react';
+import { Box } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import { PgIconButton } from '../components/Buttons';
+import AddIcon from '@material-ui/icons/AddOutlined';
+import { MappedCellControl } from './MappedControl';
+import EditRoundedIcon from '@material-ui/icons/EditRounded';
+import DeleteRoundedIcon from '@material-ui/icons/DeleteRounded';
+import { useTable, useBlockLayout, useResizeColumns, useSortBy, useExpanded } from 'react-table';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+import _ from 'lodash';
+
+import gettext from 'sources/gettext';
+import { SCHEMA_STATE_ACTIONS } from '.';
+import FormView from './FormView';
+import { confirmDeleteRow } from '../helpers/legacyConnector';
+import CustomPropTypes from 'sources/custom_prop_types';
+import { evalFunc } from 'sources/utils';
+
+const useStyles = makeStyles((theme)=>({
+  grid: {
+    ...theme.mixins.panelBorder,
+    backgroundColor: theme.palette.background.default,
+  },
+  gridHeader: {
+    display: 'flex',
+    ...theme.mixins.panelBorder.bottom,
+    backgroundColor: theme.otherVars.headerBg,
+  },
+  gridHeaderText: {
+    padding: theme.spacing(0.5, 1),
+    fontWeight: theme.typography.fontWeightBold,
+  },
+  gridControls: {
+    marginLeft: 'auto',
+  },
+  gridControlsButton: {
+    border: 0,
+    borderRadius: 0,
+    ...theme.mixins.panelBorder.left,
+  },
+  gridRowButton: {
+    border: 0,
+    borderRadius: 0,
+    padding: 0,
+    minWidth: 0,
+    backgroundColor: 'inherit',
+  },
+  gridTableContainer: {
+    overflow: 'auto',
+    width: '100%',
+  },
+  table: {
+    borderSpacing: 0,
+    width: '100%',
+    overflow: 'auto',
+    backgroundColor: theme.otherVars.tableBg,
+  },
+  tableCell: {
+    margin: 0,
+    padding: theme.spacing(0.5),
+    ...theme.mixins.panelBorder.bottom,
+    ...theme.mixins.panelBorder.right,
+    position: 'relative',
+    textAlign: 'center'
+  },
+  tableCellHeader: {
+    fontWeight: theme.typography.fontWeightBold,
+    padding: theme.spacing(1, 0.5),
+    textAlign: 'left',
+  },
+  resizer: {
+    display: 'inline-block',
+    width: '5px',
+    height: '100%',
+    position: 'absolute',
+    right: 0,
+    top: 0,
+    transform: 'translateX(50%)',
+    zIndex: 1,
+    touchAction: 'none',
+  },
+}));
+
+function DataTableHeader({headerGroups}) {
+  const classes = useStyles();
+  return (
+    <div>
+      {headerGroups.map((headerGroup, hi) => (
+        <div key={hi} {...headerGroup.getHeaderGroupProps()}>
+          {headerGroup.headers.map((column, ci) => (
+            <div key={ci} {...column.getHeaderProps()}>
+              <div {...(column.sortable ? column.getSortByToggleProps() : {})} className={clsx(classes.tableCell, classes.tableCellHeader)}>
+                {column.render('Header')}
+                <span>
+                  {column.isSorted
+                    ? column.isSortedDesc
+                      ? ' 🔽'
+                      : ' 🔼'
+                    : ''}
+                </span>
+              </div>
+              {column.resizable &&
+                <div
+                  {...column.getResizerProps()}
+                  className={classes.resizer}
+                />}
+            </div>
+          ))}
+        </div>
+      ))}
+    </div>
+  );
+}
+
+DataTableHeader.propTypes = {
+  headerGroups: PropTypes.array.isRequired,
+};
+
+function DataTableRow({row, totalRows, canExpand, isResizing, viewHelperProps, formErr, schema, dataDispatch, accessPath}) {
+  const classes = useStyles();
+  const [key, setKey] = useState(false);
+  // let key = useRef(true);
+  /* Memoize the row to avoid unnecessary re-render.
+   * If table data changes, then react-table re-renders the complete tables
+   * We can avoid re-render by if row data is not changed
+   */
+  let depsMap = _.values(row.original, Object.keys(row.original).filter((k)=>!k.startsWith('btn')));
+  depsMap = depsMap.concat([totalRows, row.isExpanded, key, isResizing]);
+  return (
+    useMemo(()=>
+      <>
+        <div {...row.getRowProps()} className="tr">
+          {row.cells.map((cell, ci) => {
+            return (
+              <div key={ci} {...cell.getCellProps()} className={classes.tableCell}>
+                {cell.render('Cell', {
+                  reRenderRow: ()=>{setKey((currKey)=>!currKey);}
+                })}
+              </div>
+            );
+          })}
+        </div>
+        {
+          canExpand && row.isExpanded &&
+          <FormView key={row.index} value={row.original} viewHelperProps={viewHelperProps} formErr={formErr} dataDispatch={dataDispatch}
+            schema={schema} accessPath={accessPath} isNested={true}/>
+        }
+      </>
+    , depsMap)
+  );
+}
+
+export default function DataGridView({
+  value, viewHelperProps, formErr, schema, accessPath, dataDispatch, containerClassName, ...props}) {
+  const classes = useStyles();
+  /* Calculate the fields which depends on the current field
+  deps has info on fields which the current field depends on. */
+  const dependsOnField = useMemo(()=>{
+    let res = {};
+    schema.fields.forEach((field)=>{
+      (field.deps || []).forEach((dep)=>{
+        res[dep] = res[dep] || [];
+        res[dep].push(field.id);
+      });
+    });
+    return res;
+  }, []);
+  let columns = useMemo(
+    ()=>{
+      let cols = [];
+      if(props.canEdit) {
+        let colInfo = {
+          Header: <>&nbsp;</>,
+          id: 'btn-edit',
+          accessor: ()=>{},
+          resizable: false,
+          sortable: false,
+          dataType: 'edit',
+          width: 30,
+          minWidth: '0',
+          Cell: ({row})=><PgIconButton data-test="expand-row" title={gettext('Edit row')} icon={<EditRoundedIcon />} className={classes.gridRowButton}
+            onClick={()=>{
+              row.toggleRowExpanded(!row.isExpanded);
+            }}
+          />
+        };
+        colInfo.Cell.displayName = 'Cell',
+        colInfo.Cell.propTypes = {
+          row: PropTypes.object.isRequired,
+        };
+        cols.push(colInfo);
+      }
+      if(props.canDelete) {
+        let colInfo = {
+          Header: <>&nbsp;</>,
+          id: 'btn-delete',
+          accessor: ()=>{},
+          resizable: false,
+          sortable: false,
+          dataType: 'delete',
+          width: 30,
+          minWidth: '0',
+          Cell: ({row}) => {
+            return (
+              <PgIconButton data-test="delete-row" title={gettext('Delete row')} icon={<DeleteRoundedIcon />}
+                onClick={()=>{
+                  confirmDeleteRow(()=>{
+                    dataDispatch({
+                      type: SCHEMA_STATE_ACTIONS.DELETE_ROW,
+                      path: accessPath,
+                      value: row.index,
+                    });
+                  }, ()=>{}, props.customDeleteTitle, props.customDeleteMsg);
+                }} className={classes.gridRowButton} />
+            );
+          }
+        };
+        colInfo.Cell.displayName = 'Cell',
+        colInfo.Cell.propTypes = {
+          row: PropTypes.object.isRequired,
+        };
+        cols.push(colInfo);
+      }
+
+      cols = cols.concat(
+        schema.fields
+          .map((field)=>{
+            let colInfo = {
+              Header: field.label,
+              accessor: field.id,
+              field: field,
+              resizable: true,
+              sortable: true,
+              ...(field.minWidth ?  {minWidth: field.minWidth} : {}),
+              Cell: ({value, row, ...other}) => {
+                let {visible, disabled, readonly, ..._field} = field;
+
+                let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
+                  ((_.isUndefined(field.server_type) ? true :
+                    (viewHelperProps.serverInfo.type in field.server_type)) &&
+                  (_.isUndefined(field.min_version) ? true :
+                    (viewHelperProps.serverInfo.version >= field.min_version)) &&
+                  (_.isUndefined(field.max_version) ? true :
+                    (viewHelperProps.serverInfo.version <= field.max_version))));
+                let _readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');
+                if(!_readonly) {
+                  _readonly = evalFunc(readonly, row.original || {});
+                }
+
+                let _visible = true;
+                if(visible) {
+                  _visible = evalFunc(visible, row.original || {});
+                }
+                _visible = _visible && verInLimit;
+
+                disabled = evalFunc(disabled, row.original || {});
+
+                return <MappedCellControl rowIndex={row.index} value={value}
+                  row={row.original} {..._field}
+                  readonly={_readonly}
+                  disabled={disabled}
+                  visible={_visible}
+                  onCellChange={(value)=>{
+                    /* Get the changes on dependent fields as well.
+                     * The return value of depChange function is merged and passed to state.
+                     */
+                    const depChange = (state)=>{
+                      let rowdata = _.get(state, accessPath.concat(row.index));
+                      _field.depChange && _.merge(rowdata, _field.depChange(rowdata, _field.id) || {});
+                      (dependsOnField[_field.id] || []).forEach((d)=>{
+                        d = _.find(schema.fields, (f)=>f.id==d);
+                        if(d.depChange) {
+                          _.merge(rowdata, d.depChange(rowdata, _field.id) || {});
+                        }
+                      });
+                      return state;
+                    };
+                    dataDispatch({
+                      type: SCHEMA_STATE_ACTIONS.SET_VALUE,
+                      path: accessPath.concat([row.index, _field.id]),
+                      value: value,
+                      depChange: depChange,
+                    });
+                  }}
+                  reRenderRow={other.reRenderRow}
+                />;
+              },
+            };
+            colInfo.Cell.displayName = 'Cell',
+            colInfo.Cell.propTypes = {
+              row: PropTypes.object.isRequired,
+              value: PropTypes.any,
+              onCellChange: PropTypes.func,
+            };
+            return colInfo;
+          })
+      );
+      return cols;
+    },[]);
+
+  const onAddClick = useCallback(()=>{
+    let newRow = {};
+    columns.forEach((column)=>{
+      if(column.field) {
+        newRow[column.field.id] = schema.defaults[column.field.id];
+      }
+    });
+    dataDispatch({
+      type: SCHEMA_STATE_ACTIONS.ADD_ROW,
+      path: accessPath,
+      value: newRow,
+    });
+  });
+
+  const defaultColumn = useMemo(()=>({
+    minWidth: 175,
+  }));
+
+  let tablePlugins = [
+    useBlockLayout,
+    useResizeColumns,
+    useSortBy,
+  ];
+  if(props.canEdit) {
+    tablePlugins.push(useExpanded);
+  }
+  const {
+    getTableProps,
+    getTableBodyProps,
+    headerGroups,
+    rows,
+    prepareRow,
+  } = useTable(
+    {
+      columns,
+      data: value || [],
+      defaultColumn,
+      manualSortBy: true,
+      autoResetSortBy: false,
+      autoResetExpanded: false,
+    },
+    ...tablePlugins,
+  );
+
+  const isResizing = _.flatMap(headerGroups, headerGroup => headerGroup.headers.map(col=>col.isResizing)).includes(true);
+
+  return (
+    <Box className={containerClassName}>
+      <Box className={classes.grid}>
+        <Box className={classes.gridHeader}>
+          <Box className={classes.gridHeaderText}>{props.label}</Box>
+          <Box className={classes.gridControls}>
+            {props.canAdd && <PgIconButton data-test="add-row" title={gettext('Add row')} onClick={onAddClick} icon={<AddIcon />} className={classes.gridControlsButton} />}
+          </Box>
+        </Box>
+        <div {...getTableProps()} className={classes.table}>
+          <DataTableHeader headerGroups={headerGroups} />
+          <div {...getTableBodyProps()}>
+            {rows.map((row, i) => {
+              prepareRow(row);
+              return <DataTableRow key={i} row={row} totalRows={rows.length} canExpand={props.canEdit}
+                value={value} viewHelperProps={viewHelperProps} formErr={formErr} isResizing={isResizing}
+                schema={schema} accessPath={accessPath.concat([row.index])} dataDispatch={dataDispatch} />;
+            })}
+          </div>
+        </div>
+      </Box>
+    </Box>
+  );
+}
+
+DataGridView.propTypes = {
+  label: PropTypes.string,
+  value: PropTypes.array,
+  viewHelperProps: PropTypes.object,
+  formErr: PropTypes.object,
+  schema: CustomPropTypes.schemaUI,
+  accessPath: PropTypes.array.isRequired,
+  dataDispatch: PropTypes.func.isRequired,
+  containerClassName: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
+  canEdit: PropTypes.bool,
+  canAdd: PropTypes.bool,
+  canDelete: PropTypes.bool,
+  customDeleteTitle: PropTypes.string,
+  customDeleteMsg: PropTypes.string,
+};
diff --git a/web/pgadmin/static/js/SchemaView/FormView.jsx b/web/pgadmin/static/js/SchemaView/FormView.jsx
new file mode 100644
index 000000000..485d983dd
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/FormView.jsx
@@ -0,0 +1,257 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { Box, makeStyles, Tab, Tabs } from '@material-ui/core';
+import _ from 'lodash';
+import PropTypes from 'prop-types';
+
+import { MappedFormControl } from './MappedControl';
+import TabPanel from '../components/TabPanel';
+import DataGridView from './DataGridView';
+import { SCHEMA_STATE_ACTIONS } from '.';
+import { InputSQL } from '../components/FormComponents';
+import gettext from 'sources/gettext';
+import { evalFunc } from 'sources/utils';
+import CustomPropTypes from '../custom_prop_types';
+
+const useStyles = makeStyles((theme)=>({
+  fullSpace: {
+    padding: 0,
+    height: '100%'
+  },
+  controlRow: {
+    paddingBottom: theme.spacing(1),
+  },
+}));
+
+/* Optional SQL tab */
+function SQLTab({active, getSQLValue}) {
+  const [sql, setSql] = useState('Loading...');
+  useEffect(()=>{
+    let unmounted = false;
+    if(active) {
+      setSql('Loading...');
+      getSQLValue().then((value)=>{
+        if(!unmounted) {
+          setSql(value);
+        }
+      });
+    }
+    return ()=>{unmounted=true;};
+  }, [active]);
+
+  return <InputSQL
+    value={sql}
+    options={{
+      readOnly: true,
+    }}
+  />;
+}
+
+SQLTab.propTypes = {
+  active: PropTypes.bool,
+  getSQLValue: PropTypes.func.isRequired,
+};
+
+/* The first component of schema view form */
+export default function FormView({
+  value, formErr, schema={}, viewHelperProps, isNested=false, accessPath, dataDispatch, hasSQLTab, getSQLValue, onTabChange, firstEleRef}) {
+  let defaultTab = 'General';
+  let tabs = {};
+  let tabsClassname = {};
+  const [tabValue, setTabValue] = useState(0);
+  const classes = useStyles();
+  const firstElement = useRef();
+
+  schema = schema || {fields: []};
+
+  /* Calculate the fields which depends on the current field
+  deps has info on fields which the current field depends on. */
+  const dependsOnField = useMemo(()=>{
+    let res = {};
+    schema.fields.forEach((field)=>{
+      (field.deps || []).forEach((dep)=>{
+        res[dep] = res[dep] || [];
+        res[dep].push(field.id);
+      });
+    });
+    return res;
+  }, []);
+
+  /* Prepare the array of components based on the types */
+  schema.fields.forEach((f)=>{
+    let modeSuppoted = true;
+    if(f.mode) {
+      modeSuppoted = (f.mode.indexOf(viewHelperProps.mode) > -1);
+    }
+    if(modeSuppoted) {
+      let {visible, disabled, group, readonly, ...field} = f;
+      group = group || defaultTab;
+
+      let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
+        ((_.isUndefined(field.server_type) ? true :
+          (viewHelperProps.serverInfo.type in field.server_type)) &&
+          (_.isUndefined(field.min_version) ? true :
+            (viewHelperProps.serverInfo.version >= field.min_version)) &&
+          (_.isUndefined(field.max_version) ? true :
+            (viewHelperProps.serverInfo.version <= field.max_version))));
+
+      let _readonly = viewHelperProps.inCatalog || (viewHelperProps.mode == 'properties');
+      if(!_readonly) {
+        _readonly = evalFunc(readonly, value);
+      }
+
+      let _visible = true;
+
+      if(visible) {
+        _visible = evalFunc(visible, value);
+      }
+      _visible = _visible && verInLimit;
+
+      disabled = evalFunc(disabled, value);
+
+
+      if(!tabs[group]) tabs[group] = [];
+
+      /* Lets choose the path based on type */
+      if(field.type === 'nested-tab') {
+        /* Pass on the top schema */
+        field.schema.top = schema.top;
+        tabs[group].push(
+          <FormView key={`nested${tabs[group].length}`} value={value} viewHelperProps={viewHelperProps} formErr={formErr}
+            schema={field.schema} accessPath={accessPath} dataDispatch={dataDispatch} isNested={true} />
+        );
+      } else if(field.type === 'collection') {
+        /* Pass on the top schema */
+        field.schema.top = schema.top;
+        /* If its a collection, let data grid view handle it */
+        tabs[group].push(
+          useMemo(()=><DataGridView key={field.id} value={value[field.id]} viewHelperProps={viewHelperProps} formErr={formErr}
+            schema={field.schema} accessPath={accessPath.concat(field.id)} dataDispatch={dataDispatch} containerClassName={classes.controlRow}
+            {...field}/>, [value[field.id]])
+        );
+      } else {
+        /* Its a form control */
+        const hasError = field.id == formErr.name;
+        /* When there is a change, the dependent values can change
+         * lets pass the new changes to dependent and get the new values
+         * from there as well.
+         */
+        tabs[group].push(
+          useMemo(()=><MappedFormControl
+            inputRef={(ele)=>{
+              if(firstEleRef && !firstEleRef.current) {
+                firstEleRef.current = ele;
+              }
+            }}
+            key={field.id}
+            viewHelperProps={viewHelperProps}
+            name={field.id}
+            value={value[field.id]}
+            readonly={_readonly}
+            disabled={disabled}
+            visible={_visible}
+            {...field}
+            onChange={(value)=>{
+              /* Get the changes on dependent fields as well */
+              const depChange = (state)=>{
+                field.depChange && _.merge(state, field.depChange(state) || {});
+                (dependsOnField[field.id] || []).forEach((d)=>{
+                  d = _.find(schema.fields, (f)=>f.id==d);
+                  if(d.depChange) {
+                    _.merge(state, d.depChange(state) || {});
+                  }
+                });
+                return state;
+              };
+              dataDispatch({
+                type: SCHEMA_STATE_ACTIONS.SET_VALUE,
+                path: accessPath.concat(field.id),
+                value: value,
+                depChange: depChange,
+              });
+            }}
+            hasError={hasError}
+            className={classes.controlRow}
+          />, [
+            value[field.id],
+            _readonly,
+            disabled,
+            _visible,
+            hasError,
+            classes.controlRow,
+            ...(field.deps || []).map((dep)=>value[dep])
+          ])
+        );
+      }
+    }
+  });
+
+  /* Add the SQL tab if required */
+  let sqlTabActive = false;
+  if(hasSQLTab) {
+    let sqlTabName = gettext('SQL');
+    sqlTabActive = (Object.keys(tabs).length === tabValue);
+    /* Re-render and fetch the SQL tab when it is active */
+    tabs[sqlTabName] = [
+      useMemo(()=><SQLTab key="sqltab" active={sqlTabActive} getSQLValue={getSQLValue} />, [sqlTabActive]),
+    ];
+    tabsClassname[sqlTabName] = classes.fullSpace;
+  }
+
+  useEffect(()=>{
+    firstElement.current && firstElement.current.focus();
+  }, []);
+
+  useEffect(()=>{
+    onTabChange && onTabChange(tabValue, Object.keys(tabs)[tabValue], sqlTabActive);
+  }, [tabValue]);
+
+  return (
+    <>
+      <Box>
+        <Tabs
+          value={tabValue}
+          onChange={(event, selTabValue) => {
+            setTabValue(selTabValue);
+          }}
+          // indicatorColor="primary"
+          variant="scrollable"
+          scrollButtons="auto"
+          action={(ref)=>ref && ref.updateIndicator()}
+        >
+          {Object.keys(tabs).map((tabName)=>{
+            return <Tab key={tabName} label={tabName} />;
+          })}
+        </Tabs>
+      </Box>
+      {Object.keys(tabs).map((tabName, i)=>{
+        return (
+          <TabPanel key={tabName} value={tabValue} index={i} classNameRoot={isNested ? classes.fullSpace : tabsClassname[tabName]}>
+            {tabs[tabName]}
+          </TabPanel>
+        );
+      })}
+    </>);
+}
+
+FormView.propTypes = {
+  value: PropTypes.any,
+  formErr: PropTypes.object,
+  schema: CustomPropTypes.schemaUI.isRequired,
+  viewHelperProps: PropTypes.object,
+  isNested: PropTypes.bool,
+  accessPath: PropTypes.array.isRequired,
+  dataDispatch: PropTypes.func,
+  hasSQLTab: PropTypes.bool,
+  getSQLValue: PropTypes.func,
+  onTabChange: PropTypes.func,
+  firstEleRef: CustomPropTypes.ref,
+};
diff --git a/web/pgadmin/static/js/SchemaView/MappedControl.jsx b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
new file mode 100644
index 000000000..46a815245
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/MappedControl.jsx
@@ -0,0 +1,199 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useCallback } from 'react';
+import _ from 'lodash';
+
+import { FormInputText, FormInputSelect, FormInputSwitch, FormInputCheckbox, InputSQL, FormInputColor, FormInputFileSelect, FormInputToggle, InputSwitch } from '../components/FormComponents';
+import { InputSelect, InputText } from '../components/FormComponents';
+import Privilege from '../components/Privilege';
+import { evalFunc } from 'sources/utils';
+import PropTypes from 'prop-types';
+import CustomPropTypes from '../custom_prop_types';
+
+/* Control mapping for form view */
+function MappedFormControlBase({type, value, id, onChange, className, visible, inputRef, ...props}) {
+  const name = id;
+  const onTextChange = useCallback((e) => {
+    let value = e;
+    if(e && e.target) {
+      value = e.target.value;
+    }
+    onChange && onChange(value);
+  });
+
+  const onIntChange = useCallback((e) => {
+    let value = e;
+    if(e && e.target) {
+      value = e.target.value;
+    }
+    if(!isNaN(parseInt(value))) {
+      value = parseInt(value);
+    }
+    onChange && onChange(value);
+  });
+
+  if(!visible) {
+    return <></>;
+  }
+
+  /* The mapping uses Form* components as it comes with labels */
+  switch (type) {
+  case 'int':
+    return <FormInputText name={name} value={value} onChange={onIntChange} className={className} inputRef={inputRef} {...props}/>;
+  case 'text':
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} inputRef={inputRef} {...props}/>;
+  case 'multiline':
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className}
+      inputRef={inputRef} controlProps={{multiline: true}} {...props}/>;
+  case 'password':
+    return <FormInputText name={name} value={value} onChange={onTextChange} className={className} type='password' inputRef={inputRef} {...props}/>;
+  case 'select':
+    return <FormInputSelect name={name} value={value} onChange={onTextChange} className={className} {...props} />;
+  case 'switch':
+    return <FormInputSwitch name={name} value={value}
+      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} className={className}
+      {...props} />;
+  case 'checkbox':
+    return <FormInputCheckbox name={name} value={value}
+      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} className={className}
+      {...props} />;
+  case 'toggle':
+    return <FormInputToggle name={name} value={value}
+      onChange={onTextChange} className={className}
+      {...props} />;
+  case 'color':
+    return <FormInputColor name={name} value={value} onChange={onTextChange} className={className} {...props} />;
+  case 'file':
+    return <FormInputFileSelect name={name} value={value} onChange={onTextChange} className={className} {...props} />;
+  case 'sql':
+    return <InputSQL name={name} value={value} onChange={onTextChange} className={className} {...props}/>;
+  default:
+    return <></>;
+  }
+}
+
+MappedFormControlBase.propTypes = {
+  type: PropTypes.oneOfType([
+    PropTypes.string, PropTypes.func,
+  ]).isRequired,
+  value: PropTypes.any,
+  id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+  onChange: PropTypes.func,
+  className: PropTypes.oneOfType([
+    PropTypes.string, PropTypes.object,
+  ]),
+  visible: PropTypes.bool,
+  inputRef: CustomPropTypes.ref,
+};
+
+/* Control mapping for grid cell view */
+function MappedCellControlBase({cell, value, id, optionsLoaded, onCellChange, visible, reRenderRow,...props}) {
+  const name = id;
+  const onTextChange = useCallback((e) => {
+    let value = e;
+    if(e && e.target) {
+      value = e.target.value;
+    }
+
+    onCellChange(value);
+  });
+
+  /* Some grid cells are based on options selected in other cells.
+   * lets trigger a re-render for the row if optionsLoaded
+   */
+  const optionsLoadedRerender = useCallback((res)=>{
+    /* optionsLoaded is called when select options are fetched */
+    optionsLoaded && optionsLoaded(res);
+    reRenderRow && reRenderRow();
+  });
+
+  if(!visible) {
+    return <></>;
+  }
+
+  /* The mapping does not need Form* components as labels are not needed for grid cells */
+  switch(cell) {
+  case 'int':
+  case 'number':
+  case 'text':
+    return <InputText name={name} value={value} onChange={onTextChange} {...props}/>;
+  case 'password':
+    return <InputText name={name} value={value} onChange={onTextChange} {...props}/>;
+  case 'select':
+    return <InputSelect name={name} value={value} onChange={onTextChange} optionsLoaded={optionsLoadedRerender} {...props}/>;
+  case 'switch':
+    return <InputSwitch name={name} value={value}
+      onChange={(e)=>onTextChange(e.target.checked, e.target.name)} {...props} />;
+  case 'privilege':
+    return <Privilege name={name} value={value} onChange={onTextChange} {...props}/>;
+  default:
+    return <></>;
+  }
+}
+
+MappedCellControlBase.propTypes = {
+  cell: PropTypes.oneOfType([
+    PropTypes.string, PropTypes.func,
+  ]).isRequired,
+  value: PropTypes.any,
+  id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
+  onChange: PropTypes.func,
+  reRenderRow: PropTypes.func,
+  optionsLoaded: PropTypes.func,
+  onCellChange: PropTypes.func,
+  visible: PropTypes.bool
+};
+
+const ALLOWED_PROPS_FIELD_COMMON = [
+  'mode', 'value', 'readonly', 'disabled', 'hasError', 'id',
+  'label', 'options', 'optionsLoaded', 'controlProps', 'schema', 'inputRef',
+  'visible', 'autoFocus', 'helpMessage', 'className'
+];
+
+const ALLOWED_PROPS_FIELD_FORM = [
+  'type', 'onChange'
+];
+
+const ALLOWED_PROPS_FIELD_CELL = [
+  'cell', 'onCellChange', 'row', 'reRenderRow',
+];
+
+
+export const MappedFormControl = (props)=>{
+  let newProps = {...props};
+  let typeProps = evalFunc(newProps.type, newProps.value);
+  if(typeof(typeProps) === 'object') {
+    newProps = {
+      ...newProps,
+      ...typeProps,
+    };
+  } else {
+    newProps.type = typeProps;
+  }
+
+  /* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
+  return <MappedFormControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM))}/>;
+};
+
+export const MappedCellControl = (props)=>{
+  let newProps = {...props};
+  let cellProps = evalFunc(newProps.cell, newProps.row);
+  if(typeof(cellProps) === 'object') {
+    newProps = {
+      ...newProps,
+      ...cellProps,
+    };
+  } else {
+    newProps.cell = cellProps;
+  }
+
+  /* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
+  return <MappedCellControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_CELL))}/>;
+};
diff --git a/web/pgadmin/static/js/SchemaView/base_schema.ui.js b/web/pgadmin/static/js/SchemaView/base_schema.ui.js
new file mode 100644
index 000000000..fb01a24d2
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/base_schema.ui.js
@@ -0,0 +1,91 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* This is the base schema class for SchemaView.
+ * A UI schema must inherit this to use SchemaView for UI.
+ */
+export default class BaseUISchema {
+  constructor(defaults) {
+    /* Pass the initial data to constructor so that
+    they will set to defaults */
+    this._defaults = defaults;
+
+    this.keys = null; // If set, other fields except keys will be filtered
+    this.informText = null; // Inform text to show after save, this only saves it
+    this._top = null;
+  }
+
+  /* Top schema is helpful if this is used as child */
+  set top(val) {
+    this._top = val;
+  }
+
+  get top() {
+    /* If no top, I'm the top */
+    return this._top || this;
+  }
+
+  /* The original data before any changes */
+  set origData(val) {
+    this._origData = val;
+  }
+
+  get origData() {
+    return this._origData || {};
+  }
+
+  /* Property allows to restrict setting this later */
+  get defaults() {
+    return this._defaults || {};
+  }
+
+  /* ID key for the view state */
+  get idAttribute() {
+    return 'id';
+  }
+
+  /* Schema fields, to be defined by inherited UI schema. Override this */
+  get baseFields() {
+    throw new Error('Property method \'baseFields()\' must be implemented.');
+  }
+
+  /* Used by schema view component. Do not override this, it will
+  concat base fields with extraFields.
+  */
+  get fields() {
+    /* Select only keys if specified */
+    return this.baseFields
+      .filter((field)=>this.keys ? this.keys.indexOf(field.id) > -1 : true);
+  }
+
+  /* Check if current data is new or existing */
+  isNew(state) {
+    if(_.has(state, this.idAttribute)) {
+      return _.isUndefined(state[this.idAttribute])
+        || _.isNull(state[this.idAttribute]);
+    }
+    /* Nested collection rows may or may not have idAttribute.
+    So to decide whether row is new or not set, the cid starts with
+    nn (not new) for existing rows. Newly added will start with 'c' (created)
+    */
+    if(_.has(state, 'cid')) {
+      return !state.cid.startsWith('nn');
+    }
+    return true;
+  }
+
+  /* Called by SchemaView to validate data, return true indicates invalid.
+  validate will receive two params state and setError func
+  Eg - setError('fieldname', 'Some error').
+  And return true if invalid, otherwise false.
+  */
+  validate() {
+    return false;
+  }
+}
diff --git a/web/pgadmin/static/js/SchemaView/index.jsx b/web/pgadmin/static/js/SchemaView/index.jsx
new file mode 100644
index 000000000..c3106b36c
--- /dev/null
+++ b/web/pgadmin/static/js/SchemaView/index.jsx
@@ -0,0 +1,663 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react';
+import { Box, makeStyles } from '@material-ui/core';
+import {Accordion, AccordionSummary, AccordionDetails} from '@material-ui/core';
+import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
+import SaveIcon from '@material-ui/icons/Save';
+import SettingsBackupRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
+import CloseIcon from '@material-ui/icons/Close';
+import InfoIcon from '@material-ui/icons/InfoRounded';
+import HelpIcon from '@material-ui/icons/HelpRounded';
+import EditIcon from '@material-ui/icons/Edit';
+import diffArray from 'diff-arrays-of-objects';
+import _ from 'lodash';
+
+import {FormFooterMessage, MESSAGE_TYPE } from 'sources/components/FormComponents';
+import Theme from 'sources/Theme';
+import { PrimaryButton, DefaultButton, PgIconButton } from 'sources/components/Buttons';
+import Loader from 'sources/components/Loader';
+import { minMaxValidator, numberValidator, integerValidator, emptyValidator, checkUniqueCol } from '../validators';
+import { MappedFormControl } from './MappedControl';
+import gettext from 'sources/gettext';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import FormView from './FormView';
+import { pgAlertify } from '../helpers/legacyConnector';
+import { evalFunc } from 'sources/utils';
+import PropTypes from 'prop-types';
+import CustomPropTypes from '../custom_prop_types';
+import { parseApiError } from '../api_instance';
+
+const useDialogStyles = makeStyles((theme)=>({
+  root: {
+    display: 'flex',
+    flexDirection: 'column',
+    height: '100%',
+  },
+  form: {
+    flexGrow: 1,
+    position: 'relative',
+    minHeight: 0,
+    display: 'flex',
+    flexDirection: 'column',
+  },
+  footer: {
+    padding: theme.spacing(1),
+    background: theme.otherVars.headerBg,
+    display: 'flex',
+    zIndex: 1010,
+    ...theme.mixins.panelBorder.top,
+  },
+  mappedControl: {
+    paddingBottom: theme.spacing(1),
+  },
+  buttonMargin: {
+    marginRight: '0.5rem',
+  },
+}));
+
+/* Compare the sessData with schema.origData
+schema.origData is set to incoming or default data
+*/
+function getChangedData(topSchema, mode, sessData, stringify=false) {
+  let changedData = {};
+  let isEdit = mode === 'edit';
+
+  /* The comparator and setter */
+  const attrChanged = (currPath, change, force=false)=>{
+    let origVal = _.get(topSchema.origData, currPath);
+    let sessVal = _.get(sessData, currPath);
+    let attrDefined = !_.isUndefined(origVal) && !_.isUndefined(sessVal) && !_.isNull(origVal) && !_.isNull(sessVal);
+
+    /* If the orig value was null and new one is empty string, then its a "no change" */
+    /* If the orig value and new value are of different datatype but of same value(numeric) "no change" */
+    if ((_.isEqual(origVal, sessVal)
+      || ((origVal === null || _.isUndefined(origVal)) && sessVal === '')
+      || (attrDefined ? _.isEqual(origVal.toString(), sessVal.toString()) : false))
+       && !force) {
+      return;
+    } else {
+      change = change || _.get(sessData, currPath);
+      _.set(changedData, currPath, stringify ? JSON.stringify(change) : change);
+    }
+  };
+
+  /* Will be called recursively as data can be nested */
+  const parseChanges = (schema, accessPath, changedData)=>{
+    schema.fields.forEach((field)=>{
+      if(field.type === 'nested-tab') {
+        /* its nested */
+        parseChanges(field.schema, accessPath, changedData);
+      } else {
+        let currPath = accessPath.concat(field.id);
+        /* Check for changes only if its in edit mode, otherwise everything is changed */
+        if(isEdit && !_.isEqual(_.get(topSchema.origData, currPath), _.get(sessData, currPath))) {
+          let change = null;
+          if(field.type === 'collection') {
+            /* Use diffArray package to get the array diff and extract the info
+            cid is used to identify the rows uniquely */
+            const changeDiff = diffArray(
+              _.get(topSchema.origData, currPath),
+              _.get(sessData, currPath),
+              'cid'
+            );
+            change = {};
+            if(changeDiff.added.length > 0) {
+              change['added'] = cleanCid(changeDiff.added);
+            }
+            if(changeDiff.removed.length > 0) {
+              change['deleted'] = cleanCid(changeDiff.removed.map((row)=>{
+                /* Deleted records should be original, not the changed */
+                return _.find(_.get(topSchema.origData, currPath), ['cid', row.cid]);
+              }));
+            }
+            if(changeDiff.updated.length > 0) {
+              change['changed'] = cleanCid(changeDiff.updated);
+            }
+            if(Object.keys(change).length > 0) {
+              attrChanged(currPath, change, true);
+            }
+          } else {
+            attrChanged(currPath);
+          }
+        } else if(!isEdit) {
+          if(field.type === 'collection') {
+            let change = cleanCid(_.get(sessData, currPath));
+            attrChanged(currPath, change);
+          } else {
+            attrChanged(currPath);
+          }
+        }
+      }
+    });
+  };
+
+  parseChanges(topSchema, [], changedData);
+  return changedData;
+}
+
+function validateSchema(schema, sessData, setError) {
+  sessData = sessData || {};
+  for(let field of schema.fields) {
+    /* Skip id validation */
+    if(schema.idAttribute == field.id) {
+      continue;
+    }
+    /* If the field is has nested schema then validate the schema */
+    if(field.schema && (field.schema instanceof BaseUISchema)) {
+      /* A collection is an array */
+      if(field.type === 'collection') {
+        let rows = sessData[field.id] || [];
+
+        /* Validate duplicate rows */
+        let dupInd = checkUniqueCol(rows, field.uniqueCol);
+        if(dupInd > 0) {
+          let uniqueColNames = _.filter(field.schema.fields, (uf)=>field.uniqueCol.indexOf(uf.id) > -1)
+            .map((uf)=>uf.label).join(', ');
+          setError(field.uniqueCol[0], gettext('%s in %s must be unique.', uniqueColNames, field.label));
+          return true;
+        }
+        /* Loop through data */
+        for(const row of rows) {
+          if(validateSchema(field.schema, row, setError)) {
+            return true;
+          }
+        }
+      } else {
+        /* A nested schema ? Recurse */
+        if(validateSchema(field.schema, sessData, setError)) {
+          return true;
+        }
+      }
+    } else {
+      /* Normal field, default validations */
+      let value = sessData[field.id];
+      let message = null;
+      if(field.noEmpty) {
+        message = emptyValidator(field.label, value);
+      }
+      if(!message && (field.type == 'int' || field.type == 'numeric')) {
+        message = minMaxValidator(field.label, value, field.min, field.max);
+      }
+      if(!message && field.type == 'int') {
+        message = integerValidator(field.label, value);
+      } else if(!message && field.type == 'numeric') {
+        message = numberValidator(field.label, value);
+      }
+      if(message) {
+        setError(field.id, message);
+        return true;
+      }
+    }
+  }
+  return schema.validate(sessData, setError);
+}
+
+export const SCHEMA_STATE_ACTIONS = {
+  INIT: 'init',
+  SET_VALUE: 'set_value',
+  ADD_ROW: 'add_row',
+  DELETE_ROW: 'delete_row',
+  RERENDER: 'rerender',
+};
+
+/* The main function which manipulates the session state based on actions */
+/*
+The state is managed based on path array of a particular key
+For Eg. if the state is
+{
+  key1: {
+    ckey1: [
+      {a: 0, b: 0},
+      {a: 1, b: 1}
+    ]
+  }
+}
+The path for b in first row will be [key1, ckey1, 0, b]
+The path for second row of ckey1 will be [key1, ckey1, 1]
+The path for key1 is [key1]
+The state starts with path []
+*/
+const sessDataReducer = (state, action)=>{
+  let data = _.cloneDeep(state);
+  let rows, cid;
+  switch(action.type) {
+  case SCHEMA_STATE_ACTIONS.INIT:
+    data = action.payload;
+    break;
+  case SCHEMA_STATE_ACTIONS.SET_VALUE:
+    _.set(data, action.path, action.value);
+    /* If there is any dep listeners get the changes */
+    if(action.depChange) {
+      _.set(data, action.depChange(data));
+    }
+    break;
+  case SCHEMA_STATE_ACTIONS.ADD_ROW:
+    /* Create id to identify a row uniquely, usefull when getting diff */
+    cid = _.uniqueId('c');
+    action.value['cid'] = cid;
+    rows = (_.get(data, action.path)||[]).concat(action.value);
+    _.set(data, action.path, rows);
+    break;
+  case SCHEMA_STATE_ACTIONS.DELETE_ROW:
+    rows = _.get(data, action.path)||[];
+    rows.splice(action.value, 1);
+    _.set(data, action.path, rows);
+    break;
+  }
+  return data;
+};
+
+/* Remove cid key added by prepareData */
+function cleanCid(coll) {
+  if(!coll) {
+    return coll;
+  }
+  return coll.map((o)=>_.pickBy(o, (v, k)=>k!='cid'));
+}
+
+function prepareData(origData) {
+  _.forIn(origData, function (val) {
+    if (_.isArray(val)) {
+      val.forEach(function(el) {
+        if (_.isObject(el)) {
+          /* The each row in collection need to have an id to identify them uniquely
+          This helps in easily getting what has changed */
+          /* Nested collection rows may or may not have idAttribute.
+          So to decide whether row is new or not set, the cid starts with
+          nn (not new) for existing rows. Newly added will start with 'c' (created)
+          */
+          el['cid'] = _.uniqueId('nn');
+          prepareData(el);
+        }
+      });
+    }
+    if (_.isObject(val)) {
+      prepareData(val);
+    }
+  });
+  return origData;
+}
+
+/* If its the dialog */
+function SchemaDialogView({
+  getInitData, viewHelperProps, schema={}, ...props}) {
+  const classes = useDialogStyles();
+  /* Some useful states */
+  const [dirty, setDirty] = useState(false);
+  /* formErr has 2 keys - name and message.
+  Footer message will be displayed if message is set.
+  */
+  const [formErr, setFormErr] = useState({});
+  const [loaderText, setLoaderText] = useState('');
+  const [saving, setSaving] = useState(false);
+  const [sqlTabActive, setSqlTabActive] = useState(false);
+  const [formReady, setFormReady] = useState(false);
+  const firstEleRef = useRef();
+  const isNew = schema.isNew(schema.origData);
+  /* The session data */
+  const [sessData, sessDispatch] = useReducer(sessDataReducer, {});
+
+  useEffect(()=>{
+    /* if sessData changes, validate the schema */
+    if(!formReady) return;
+    let isNotValid = validateSchema(schema, sessData, (name, message)=>{
+      if(message) {
+        setFormErr({
+          name: name,
+          message: message,
+        });
+      }
+    });
+    if(!isNotValid) setFormErr({});
+
+    /* check if anything changed */
+    let dataChanged = Object.keys(getChangedData(schema, viewHelperProps.mode, sessData)).length > 0;
+    setDirty(dataChanged);
+
+    /* tell the callbacks the data has changed */
+    props.onDataChange && props.onDataChange(dataChanged);
+  }, [sessData]);
+
+  useEffect(()=>{
+    /* Docker on load focusses itself, so our focus should execute later */
+    let focusTimeout = setTimeout(()=>{
+      firstEleRef.current && firstEleRef.current.focus();
+    }, 250);
+
+    /* Re-triggering focus on already focussed loses the focus */
+    if(viewHelperProps.mode === 'edit') {
+      setLoaderText('Loading...');
+      /* If its an edit mode, get the initial data using getInitData
+      getInitData should be a promise */
+      if(!getInitData) {
+        throw new Error('getInitData must be passed for edit');
+      }
+      getInitData && getInitData().then((data)=>{firstEleRef.current;
+        data = data || {};
+        /* Set the origData to incoming data, useful for comparing and reset */
+        schema.origData = prepareData(data || {});
+        sessDispatch({
+          type: SCHEMA_STATE_ACTIONS.INIT,
+          payload: schema.origData,
+        });
+        setFormReady(true);
+        setLoaderText('');
+
+      });
+    } else {
+      /* Use the defaults as the initital data */
+      schema.origData = prepareData(schema.defaults);
+      sessDispatch({
+        type: SCHEMA_STATE_ACTIONS.INIT,
+        payload: schema.origData,
+      });
+      setFormReady(true);
+      setLoaderText('');
+    }
+
+    /* Clear the focus timeout it unmounted */
+    return ()=>clearTimeout(focusTimeout);
+  }, []);
+
+  const onResetClick = ()=>{
+    const resetIt = ()=>{
+      sessDispatch({
+        type: SCHEMA_STATE_ACTIONS.INIT,
+        payload: schema.origData,
+      });
+      return true;
+    };
+    /* Confirm before reset */
+    if(props.confirmOnCloseReset) {
+      pgAlertify().confirm(
+        gettext('Warning'),
+        gettext('Changes will be lost. Are you sure you want to reset?'),
+        resetIt,
+        function() {
+          return true;
+        }
+      ).set('labels', {
+        ok: gettext('Yes'),
+        cancel: gettext('No'),
+      }).show();
+    } else {
+      resetIt();
+    }
+  };
+
+  const onSaveClick = ()=>{
+    setSaving(true);
+    setLoaderText('Saving...');
+    /* Get the changed data */
+    let data = getChangedData(schema, viewHelperProps.mode, sessData);
+
+    /* Add the id when in edit mode */
+    if(viewHelperProps.mode === 'edit') {
+      data[schema.idAttribute] = schema.origData[schema.idAttribute];
+    } else {
+      /* If new then merge the changed data with origData */
+      data = _.merge(schema.origData, data);
+    }
+    props.onSave(isNew, data)
+      .then(()=>{
+        if(schema.informText) {
+          pgAlertify().alert(
+            gettext('Warning'),
+            schema.informText,
+          );
+        }
+      }).catch((err)=>{
+        setFormErr({
+          name: 'apierror',
+          message: parseApiError(err),
+        });
+      }).finally(()=>{
+        setSaving(false);
+        setLoaderText('');
+      });
+  };
+
+  const onErrClose = useCallback(()=>{
+    /* Unset the error message, but not the name */
+    setFormErr((prev)=>({
+      ...prev,
+      message: '',
+    }));
+  });
+
+  const getSQLValue = ()=>{
+    /* Called when SQL tab is active */
+    if(dirty) {
+      if(!formErr.name) {
+        let changeData = getChangedData(schema, viewHelperProps.mode, sessData, true);
+        /* Call the passed incoming getSQLValue func to get the SQL
+        return of getSQLValue should be a promise.
+        */
+        return props.getSQLValue(isNew, changeData);
+      } else {
+        return Promise.resolve('-- ' + gettext('Definition incomplete.'));
+      }
+    } else {
+      return Promise.resolve('-- ' + gettext('No updates.'));
+    }
+  };
+
+  /* I am Groot */
+  return (
+    <Box className={classes.root}>
+      <Box className={classes.form}>
+        <Loader message={loaderText}/>
+        <FormView value={sessData} viewHelperProps={viewHelperProps} formErr={formErr}
+          schema={schema} accessPath={[]} dataDispatch={sessDispatch}
+          hasSQLTab={props.hasSQL} getSQLValue={getSQLValue} onTabChange={(i, tabName, sqlActive)=>setSqlTabActive(sqlActive)}
+          firstEleRef={firstEleRef} />
+        <FormFooterMessage type={MESSAGE_TYPE.ERROR} message={sqlTabActive ? '' : formErr.message}
+          onClose={onErrClose} />
+      </Box>
+      <Box className={classes.footer}>
+        {useMemo(()=><Box>
+          <PgIconButton data-test="sql-help" onClick={()=>props.onHelp(true, isNew)} icon={<InfoIcon />}
+            disabled={props.disableSqlHelp} className={classes.buttonMargin} title="SQL help for this object type."/>
+          <PgIconButton data-test="dialog-help" onClick={()=>props.onHelp(false, isNew)} icon={<HelpIcon />} title="Help for this dialog."/>
+        </Box>, [])}
+        <Box marginLeft="auto">
+          <DefaultButton data-test="Close" onClick={props.onClose} startIcon={<CloseIcon />} className={classes.buttonMargin}>
+            {gettext('Close')}
+          </DefaultButton>
+          <DefaultButton data-test="Reset" onClick={onResetClick} startIcon={<SettingsBackupRestoreIcon />} disabled={!dirty || saving} className={classes.buttonMargin}>
+            {gettext('Reset')}
+          </DefaultButton>
+          <PrimaryButton data-test="Save" onClick={onSaveClick} startIcon={<SaveIcon />} disabled={!dirty || saving || Boolean(formErr.name) || !formReady}>
+            {gettext('Save')}
+          </PrimaryButton>
+        </Box>
+      </Box>
+    </Box>
+  );
+}
+
+SchemaDialogView.propTypes = {
+  getInitData: PropTypes.func,
+  viewHelperProps: PropTypes.shape({
+    mode: PropTypes.string.isRequired,
+    serverInfo: PropTypes.shape({
+      type: PropTypes.string,
+      version: PropTypes.number,
+    }),
+    inCatalog: PropTypes.bool,
+  }).isRequired,
+  schema: CustomPropTypes.schemaUI,
+  onSave: PropTypes.func,
+  onClose: PropTypes.func,
+  onHelp: PropTypes.func,
+  onDataChange: PropTypes.func,
+  confirmOnCloseReset: PropTypes.bool,
+  hasSQL: PropTypes.bool,
+  getSQLValue: PropTypes.func,
+  disableSqlHelp: PropTypes.bool,
+};
+
+const usePropsStyles = makeStyles((theme)=>({
+  root: {
+    height: '100%',
+    minHeight: 0,
+    display: 'flex',
+    flexDirection: 'column'
+  },
+  controlRow: {
+    paddingBottom: theme.spacing(1),
+  },
+  form: {
+    padding: theme.spacing(1),
+    overflow: 'auto',
+    flexGrow: 1,
+  },
+  toolbar: {
+    padding: theme.spacing(0.5),
+    background: theme.palette.background.default,
+    ...theme.mixins.panelBorder.bottom,
+  },
+  buttonMargin: {
+    marginRight: '0.5rem',
+  },
+}));
+
+/* If its the properties tab */
+function SchemaPropertiesView({
+  getInitData, viewHelperProps, schema={}, ...props}) {
+  const classes = usePropsStyles();
+  let defaultTab = 'General';
+  let tabs = {};
+  const [origData, setOrigData] = useState({});
+  const [loaderText, setLoaderText] = useState('');
+
+  useEffect(()=>{
+    setLoaderText('Loading...');
+    getInitData().then((data)=>{
+      data = data || {};
+      setOrigData(data || {});
+      setLoaderText('');
+    });
+  }, [getInitData]);
+
+  /* A simple loop to get all the controls for the fields */
+  schema.fields.forEach((f)=>{
+    let {visible, disabled, group, readonly, ...field} = f;
+    group = group || defaultTab;
+
+    let verInLimit = (_.isUndefined(viewHelperProps.serverInfo) ? true :
+      ((_.isUndefined(field.server_type) ? true :
+        (viewHelperProps.serverInfo.type in field.server_type)) &&
+        (_.isUndefined(field.min_version) ? true :
+          (viewHelperProps.serverInfo.version >= field.min_version)) &&
+        (_.isUndefined(field.max_version) ? true :
+          (viewHelperProps.serverInfo.version <= field.max_version))));
+
+    let _visible = true;
+    if(field.mode) {
+      _visible = (field.mode.indexOf(viewHelperProps.mode) > -1);
+    }
+    if(_visible && visible) {
+      _visible = evalFunc(visible, origData);
+    }
+
+    disabled = evalFunc(disabled, origData);
+    readonly = true;
+    if(_visible && verInLimit) {
+      if(!tabs[group]) tabs[group] = [];
+      tabs[group].push(
+        <MappedFormControl
+          key={field.id}
+          viewHelperProps={viewHelperProps}
+          name={field.id}
+          value={origData[field.id]}
+          readonly={readonly}
+          disabled={disabled}
+          visible={_visible}
+          {...field}
+          className={classes.controlRow}
+        />
+      );
+    }
+  });
+
+  return (
+    <Box className={classes.root}>
+      <Loader message={loaderText}/>
+      <Box className={classes.toolbar}>
+        <PgIconButton
+          data-test="help" onClick={()=>props.onHelp(true, false)} icon={<InfoIcon />} disabled={props.disableSqlHelp}
+          title="SQL help for this object type." className={classes.buttonMargin} />
+        <PgIconButton data-test="edit"
+          onClick={props.onEdit} icon={<EditIcon />} title="Edit the object" />
+      </Box>
+      <Box className={classes.form}>
+        <Box>
+          {Object.keys(tabs).map((tabName)=>{
+            let id = tabName.replace(' ', '');
+            return (
+              <Accordion key={id}>
+                <AccordionSummary
+                  expandIcon={<ExpandMoreIcon />}
+                  aria-controls={`${id}-content`}
+                  id={`${id}-header`}
+                >
+                  {tabName}
+                </AccordionSummary>
+                <AccordionDetails>
+                  <Box style={{width: '100%'}}>
+                    {tabs[tabName]}
+                  </Box>
+                </AccordionDetails>
+              </Accordion>
+            );
+          })}
+        </Box>
+      </Box>
+    </Box>
+  );
+}
+
+SchemaPropertiesView.propTypes = {
+  getInitData: PropTypes.func.isRequired,
+  viewHelperProps: PropTypes.shape({
+    mode: PropTypes.string.isRequired,
+    serverInfo: PropTypes.shape({
+      type: PropTypes.string,
+      version: PropTypes.number,
+    }),
+    inCatalog: PropTypes.bool,
+  }).isRequired,
+  schema: CustomPropTypes.schemaUI,
+  onHelp: PropTypes.func,
+  disableSqlHelp: PropTypes.bool,
+  onEdit: PropTypes.func,
+};
+
+export default function SchemaView({formType, ...props}) {
+  /* Switch the view based on formType */
+  if(formType === 'tab') {
+    return (
+      <Theme>
+        <SchemaPropertiesView {...props}/>
+      </Theme>
+    );
+  }
+  return (
+    <Theme>
+      <SchemaDialogView {...props}/>
+    </Theme>
+  );
+}
+
+SchemaView.propTypes = {
+  formType: PropTypes.oneOf(['tab', 'dialog']),
+};
diff --git a/web/pgadmin/static/js/Theme/dark.js b/web/pgadmin/static/js/Theme/dark.js
new file mode 100644
index 000000000..ab8414647
--- /dev/null
+++ b/web/pgadmin/static/js/Theme/dark.js
@@ -0,0 +1,89 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* The dark theme */
+import { createMuiTheme } from '@material-ui/core/styles';
+import { darken } from '@material-ui/core/styles/colorManipulator';
+
+export default function(basicSettings) {
+  return createMuiTheme(basicSettings, {
+    palette: {
+      default: {
+        main: '#6b6b6b',
+        contrastText: '#fff',
+        borderColor: '#2e2e2e',
+        disabledBorderColor: '#2e2e2e',
+        disabledContrastText: '#fff',
+        hoverMain: '#303030',
+        hoverContrastText: '#fff',
+        hoverBorderColor: '#2e2e2e',
+      },
+      primary: {
+        main: '#234d6e',
+        light: '#d6effc',
+        contrastText: '#fff',
+        hoverMain: darken('#234d6e', 0.25),
+        hoverBorderColor: darken('#234d6e', 0.25),
+        disabledMain: '#234d6e',
+      },
+      success:  {
+        main: '#26852B',
+        light: '#2B472C',
+        contrastText: '#000',
+      },
+      error: {
+        main: '#da6758',
+        light: '#212121',
+        contrastText: '#fff',
+        lighter: '#212121',
+      },
+      warning: {
+        main: '#eea236',
+        light: '#b18d5a',
+        contrastText: '#fff',
+      },
+      info: {
+        main: '#fde74c',
+      },
+      grey: {
+        '200': '#424242',
+        '400': '#303030',
+        '600': '#2e2e2e',
+        '800': '#212121',
+      },
+      text: {
+        primary: '#d4d4d4',
+      },
+      background: {
+        paper: '#212121',
+        default: '#212121',
+      }
+    },
+    custom: {
+      icon: {
+        main: '#6b6b6b',
+        contrastText: '#fff',
+        borderColor: '#2e2e2e',
+        disabledMain: '#6b6b6b',
+        disabledContrastText: '#fff',
+        disabledBorderColor: '#2e2e2e',
+        hoverMain: '#303030',
+        hoverContrastText: '#fff',
+      }
+    },
+    otherVars: {
+      borderColor: '#4a4a4a',
+      inputBorderColor: '#6b6b6b',
+      inputDisabledBg: 'inherit',
+      headerBg: '#424242',
+      activeColor: '#d4d4d4',
+      tableBg: '#424242',
+    }
+  });
+}
diff --git a/web/pgadmin/static/js/Theme/high_contrast.js b/web/pgadmin/static/js/Theme/high_contrast.js
new file mode 100644
index 000000000..898589d5b
--- /dev/null
+++ b/web/pgadmin/static/js/Theme/high_contrast.js
@@ -0,0 +1,87 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* The dark theme */
+import { createMuiTheme } from '@material-ui/core/styles';
+
+export default function(basicSettings) {
+  return createMuiTheme(basicSettings, {
+    palette: {
+      default: {
+        main: 'transparent',
+        contrastText: '#84d6ff',
+        borderColor: '#84d6ff',
+        disabledBorderColor: '#8B9CAD',
+        disabledContrastText: '#8B9CAD',
+        hoverMain: 'transparent',
+        hoverContrastText: '#fff',
+        hoverBorderColor: '#fff',
+      },
+      primary: {
+        main: '#84D6FF',
+        light: '#84D6FF',
+        contrastText: '#010B15',
+        hoverMain: '#fff',
+        hoverBorderColor: '#fff',
+        disabledMain: '#8B9CAD',
+      },
+      success:  {
+        main: '#45D48A',
+        light: '#010B15',
+        contrastText: '#000',
+      },
+      error: {
+        main: '#EE7A55',
+        light: '#EE7A55',
+        contrastText: '#010B15',
+      },
+      warning: {
+        main: '#F4D35E',
+        light: '#F4D35E',
+        contrastText: '#010B15',
+      },
+      info: {
+        main: '#fde74c',
+      },
+      grey: {
+        '200': '#8B9CAD',
+        '400': '#2D3A48',
+        '600': '#1F2932',
+        '800': '#010B15',
+      },
+      text: {
+        primary: '#fff',
+      },
+      background: {
+        paper: '#010B15',
+        default: '#010B15',
+      },
+    },
+    custom: {
+      icon: {
+        main: '#010B15',
+        contrastText: '#fff',
+        borderColor: '#fff',
+        disabledMain: '#1F2932',
+        disabledContrastText: '#8B9CAD',
+        disabledBorderColor: '#8B9CAD',
+        hoverMain: '#fff',
+        hoverContrastText: '#010B15',
+      }
+    },
+    otherVars: {
+      borderColor: '#4a4a4a',
+      inputBorderColor: '#6b6b6b',
+      inputDisabledBg: '#1F2932',
+      headerBg: '#010B15',
+      activeColor: '#d4d4d4',
+      tableBg: '#010B15',
+    }
+  });
+}
diff --git a/web/pgadmin/static/js/Theme/index.jsx b/web/pgadmin/static/js/Theme/index.jsx
new file mode 100644
index 000000000..57bf72964
--- /dev/null
+++ b/web/pgadmin/static/js/Theme/index.jsx
@@ -0,0 +1,353 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* The complete styling file for Material-UI components used
+ * This will become the main theme file for pgAdmin. All the
+ * custom themes info will come here.
+ */
+
+import React, { useMemo } from 'react';
+import { createMuiTheme, ThemeProvider } from '@material-ui/core/styles';
+import CustomPropTypes from '../custom_prop_types';
+
+import getStandardTheme from './standard';
+import getDarkTheme from './dark';
+import getHightContrastTheme from './high_contrast';
+
+/* Common settings across all themes */
+let basicSettings = createMuiTheme();
+basicSettings = createMuiTheme(basicSettings, {
+  typography: {
+    fontSize: 14,
+    htmlFontSize: 14,
+  },
+  shape: {
+    borderRadius: 4,
+  },
+  overrides: {
+    MuiTabs: {
+      root: {
+        minHeight: 0,
+      }
+    },
+    PrivateTabIndicator: {
+      root: {
+        height: '3px',
+        transition: basicSettings.transitions.create(['all'], {duration: '150ms'}),
+      }
+    },
+    MuiTab: {
+      root: {
+        textTransform: 'none',
+        minHeight: 0,
+        padding: '5px 10px',
+        [basicSettings.breakpoints.up('xs')]: {
+          minWidth: 0,
+        },
+        [basicSettings.breakpoints.up('sm')]: {
+          minWidth: 0,
+        },
+        [basicSettings.breakpoints.up('md')]: {
+          minWidth: 0,
+        },
+        [basicSettings.breakpoints.up('lg')]: {
+          minWidth: 0,
+        },
+      },
+      textColorInherit: {
+        textTransform: 'none',
+        opacity: 1,
+      }
+    },
+    MuiButton: {
+      root: {
+        textTransform: 'none,',
+        padding: basicSettings.spacing(0.5, 1.5),
+        '&.Mui-disabled': {
+          opacity: 0.65,
+        }
+      },
+      contained: {
+        boxShadow: 'none',
+        '&:hover': {
+          boxShadow: 'none',
+        }
+      },
+      outlined: {
+        padding: basicSettings.spacing(0.375, 1),
+      },
+      startIcon: {
+        marginRight: basicSettings.spacing(0.5),
+      }
+    },
+    MuiOutlinedInput: {
+      multiline: {
+        padding: '0px',
+      },
+      input: {
+        padding: basicSettings.spacing(0.75, 1.5),
+        borderRadius: 'inherit',
+      },
+      inputMultiline: {
+        padding: basicSettings.spacing(0.75, 1.5),
+      },
+      adornedEnd: {
+        paddingRight: basicSettings.spacing(1.5),
+      }
+    },
+    MuiToggleButton: {
+      root: {
+        textTransform: 'none,',
+        padding: basicSettings.spacing(0.5, 2.5, 0.5, 0.5),
+        color: 'abc',
+        '&:hover':{
+          backgroundColor: 'abc',
+        },
+        '&$selected': {
+          color: 'abc',
+          backgroundColor: 'abc',
+          '&:hover':{
+            backgroundColor: 'abc',
+          }
+        }
+      }
+    },
+    MuiAccordion: {
+      root: {
+        boxShadow: 'none',
+      }
+    },
+    MuiAccordionSummary: {
+      root: {
+        minHeight: 0,
+        '&.Mui-expanded': {
+          minHeight: 0,
+        },
+        padding: basicSettings.spacing(0, 1),
+        fontWeight: basicSettings.typography.fontWeightBold,
+      },
+      content: {
+        margin: basicSettings.spacing(1),
+        '&.Mui-expanded': {
+          margin: basicSettings.spacing(1),
+        }
+      },
+      expandIcon: {
+        order: -1,
+      }
+    },
+    MuiAccordionDetails: {
+      root: {
+        padding: basicSettings.spacing(1),
+      }
+    },
+    MuiFormControlLabel: {
+      root: {
+        marginBottom: 0,
+        marginLeft: 0,
+        marginRight: 0,
+      }
+    },
+    MuiFormHelperText: {
+      root: {
+        fontSize: '1em',
+      }
+    },
+    MuiTypography: {
+      body1: {
+        fontSize: '1em',
+      }
+    }
+  },
+  transitions: {
+    duration: {
+      shortest: 50,
+      shorter: 100,
+      short: 150,
+      standard: 200,
+      complex: 175,
+      enteringScreen: 125,
+      leavingScreen: 95,
+    }
+  },
+  props: {
+    MuiTextField: {
+      variant: 'outlined',
+    },
+    MuiButton: {
+      disableTouchRipple: true,
+    },
+    MuiIconButton: {
+      size: 'small',
+      disableTouchRipple: true,
+    },
+    MuiAccordion: {
+      defaultExpanded: true,
+    },
+    MuiTab: {
+      textColor: 'inherit',
+    }
+  },
+});
+
+/* Get the final theme after merging base theme with selected theme */
+function getFinalTheme(baseTheme) {
+  let mixins = {
+    panelBorder: {
+      border: '1px solid '+baseTheme.otherVars.borderColor,
+      top: {
+        borderTop: '1px solid '+baseTheme.otherVars.borderColor,
+      },
+      bottom: {
+        borderBottom: '1px solid '+baseTheme.otherVars.borderColor,
+      },
+      right: {
+        borderRight: '1px solid '+baseTheme.otherVars.borderColor,
+      }
+    },
+    nodeIcon: {
+      backgroundPosition: 'center',
+      padding: baseTheme.spacing(0, 1.5),
+    }
+  };
+
+  return createMuiTheme({
+    mixins: mixins,
+    overrides: {
+      MuiOutlinedInput:  {
+        notchedOutline: {
+          borderColor: baseTheme.otherVars.inputBorderColor,
+        }
+      },
+      MuiTabs: {
+        root: {
+          backgroundColor: baseTheme.otherVars.headerBg,
+          ...mixins.panelBorder.bottom
+        },
+        indicator: {
+          backgroundColor: baseTheme.otherVars.activeColor,
+        }
+      },
+      MuiFormLabel: {
+        root: {
+          color: baseTheme.palette.text.primary,
+          fontSize: baseTheme.typography.fontSize,
+        },
+        asterisk: {
+          color: baseTheme.palette.error.main,
+        }
+      },
+      MuiInputBase: {
+        root: {
+          backgroundColor: baseTheme.palette.background.default,
+        },
+        inputMultiline: {
+          fontSize: baseTheme.typography.fontSize,
+          height: 'unset',
+          backgroundColor: baseTheme.palette.background.default,
+          '&[readonly]': {
+            backgroundColor: baseTheme.palette.inputDisabledBg,
+          }
+        },
+        input: {
+          fontSize: baseTheme.typography.fontSize,
+          height: 'unset',
+          backgroundColor: baseTheme.palette.background.default,
+          '&[readonly]': {
+            backgroundColor: baseTheme.otherVars.inputDisabledBg,
+          }
+        }
+      },
+      MuiIconButton: {
+        root: {
+          color: baseTheme.palette.text.primary,
+        }
+      },
+      MuiAccordion: {
+        root: {
+          ...mixins.panelBorder,
+        }
+      },
+      MuiAccordionSummary: {
+        root: {
+          ...mixins.panelBorder.bottom,
+          backgroundColor: baseTheme.otherVars.headerBg,
+        }
+      },
+      MuiSwitch: {
+        root: {
+          width: 54,
+          height: 28,
+          padding: '7px 12px',
+        },
+        switchBase: {
+          padding: baseTheme.spacing(0.5),
+          '&.Mui-checked': {
+            color: baseTheme.palette.success.main,
+            transform: 'translateX(24px)',
+          },
+          '&.Mui-checked + .MuiSwitch-track': {
+            backgroundColor: baseTheme.palette.success.light,
+          },
+        },
+      },
+      MuiCheckbox: {
+        root: {
+          padding: '0px',
+          color: baseTheme.otherVars.inputBorderColor,
+        }
+      },
+      MuiToggleButton: {
+        root: {
+          paddingRight: baseTheme.spacing(2.5),
+          paddingLeft: baseTheme.spacing(0.5),
+          color: 'abc',
+          '&:hover':{
+            backgroundColor: 'abc',
+          },
+          '&$selected': {
+            color: 'abc',
+            backgroundColor: 'abc',
+            '&:hover':{
+              backgroundColor: 'abc',
+            }
+          }
+        }
+      },
+    }
+  }, baseTheme);
+}
+
+/* Theme wrapper used by DOM containers to apply theme */
+/* In future, this will be moved to App container */
+export default function Theme(props) {
+  const theme = useMemo(()=>{
+    /* We'll remove this in future, we can get the value from preferences directly */
+    let themeName = document.querySelector('link[data-theme]')?.getAttribute('data-theme');
+    let baseTheme = getStandardTheme(basicSettings);
+    switch(themeName) {
+    case 'dark':
+      baseTheme = getDarkTheme(baseTheme);
+      break;
+    case 'high_contrast':
+      baseTheme = getHightContrastTheme(baseTheme);
+      break;
+    }
+    return getFinalTheme(baseTheme);
+  }, []);
+  return (
+    <ThemeProvider theme={theme}>
+      {props.children}
+    </ThemeProvider>
+  );
+}
+
+Theme.propTypes = {
+  children: CustomPropTypes.children,
+};
diff --git a/web/pgadmin/static/js/Theme/standard.js b/web/pgadmin/static/js/Theme/standard.js
new file mode 100644
index 000000000..46586f72e
--- /dev/null
+++ b/web/pgadmin/static/js/Theme/standard.js
@@ -0,0 +1,95 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* The standard theme */
+import { createMuiTheme } from '@material-ui/core/styles';
+import { fade, darken } from '@material-ui/core/styles/colorManipulator';
+
+export default function(basicSettings) {
+  return createMuiTheme(basicSettings, {
+    palette: {
+      default: {
+        main: '#fff',
+        contrastText: '#222',
+        borderColor: '#bac1cd',
+        disabledBorderColor: '#bac1cd',
+        disabledContrastText: '#222',
+        hoverMain: '#ebeef3',
+        hoverContrastText: '#222',
+        hoverBorderColor: '#bac1cd',
+      },
+      primary: {
+        main: '#326690',
+        light: '#d6effc',
+        contrastText: '#fff',
+        hoverMain: darken('#326690', 0.25),
+        hoverBorderColor: darken('#326690', 0.25),
+        disabledMain: '#326690',
+      },
+      success:  {
+        main: '#26852B',
+        light: '#D9ECDA',
+        contrastText: '#000',
+      },
+      error: {
+        main: '#CC0000',
+        light: '#FAECEC',
+        contrastText: '#fff',
+      },
+      warning: {
+        main: '#eea236',
+        light: '#fce5c5',
+        contrastText: '#000',
+      },
+      info: {
+        main: '#fde74c',
+      },
+      grey: {
+        '200': '#f3f5f9',
+        '400': '#ebeef3',
+        '600': '#bac1cd',
+        '800': '#848ea0',
+      },
+      text: {
+        primary: '#222',
+      },
+      background: {
+        paper: '#fff',
+        default: '#fff',
+      },
+    },
+    custom: {
+      icon: {
+        main: '#fff',
+        contrastText: '#222',
+        borderColor: '#bac1cd',
+        disabledMain: '#fff',
+        disabledContrastText: '#222',
+        disabledBorderColor: '#bac1cd',
+        hoverMain: '#ebeef3',
+        hoverContrastText: '#222',
+      }
+    },
+    otherVars: {
+      reactSelect: {
+        padding: '5px 8px',
+      },
+      borderColor: '#dde0e6',
+      loader: {
+        backgroundColor: fade('#000', 0.65),
+        color: '#fff',
+      },
+      inputBorderColor: '#dde0e6',
+      inputDisabledBg: '#f3f5f9',
+      headerBg: '#fff',
+      activeColor: '#326690',
+      tableBg: '#fff',
+    }
+  });
+}
diff --git a/web/pgadmin/static/js/api_instance.js b/web/pgadmin/static/js/api_instance.js
new file mode 100644
index 000000000..30e2f707a
--- /dev/null
+++ b/web/pgadmin/static/js/api_instance.js
@@ -0,0 +1,45 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import pgAdmin from 'sources/pgadmin';
+import gettext from 'sources/gettext';
+import axios from 'axios';
+
+/* Get the axios instance to call back end APIs.
+Do not import axios directly, instead use this */
+export default function getApiInstance(headers={}) {
+  const api = axios.create({
+    headers: {
+      'Content-type': 'application/json',
+      [pgAdmin.csrf_token_header]: pgAdmin.csrf_token,
+      ...headers,
+    }
+  });
+  return api;
+}
+
+export function parseApiError(error) {
+  if (error.response) {
+    // The request was made and the server responded with a status code
+    // that falls out of the range of 2xx
+    if(error.response.headers['content-type'] == 'application/json') {
+      return error.response.data.errormsg;
+    } else {
+      return error.response.statusText;
+    }
+  } else if (error.request) {
+    // The request was made but no response was received
+    // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
+    // http.ClientRequest in node.js
+    return gettext('Connection to pgAdmin server has been lost');
+  } else {
+    // Something happened in setting up the request that triggered an Error
+    return error.message;
+  }
+}
diff --git a/web/pgadmin/static/js/components/Buttons.jsx b/web/pgadmin/static/js/components/Buttons.jsx
new file mode 100644
index 000000000..981f90246
--- /dev/null
+++ b/web/pgadmin/static/js/components/Buttons.jsx
@@ -0,0 +1,106 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { Button, makeStyles, Tooltip } from '@material-ui/core';
+import React, { forwardRef } from 'react';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+import CustomPropTypes from '../custom_prop_types';
+
+const useStyles = makeStyles((theme)=>({
+  primaryButton: {
+    '&.Mui-disabled': {
+      color: theme.palette.primary.contrastText,
+      backgroundColor: theme.palette.primary.disabledMain,
+    },
+    '&:hover': {
+      backgroundColor: theme.palette.primary.hoverMain,
+      borderColor: theme.palette.primary.hoverBorderColor,
+    },
+  },
+  defaultButton: {
+    backgroundColor: theme.palette.default.main,
+    color: theme.palette.default.contrastText,
+    border: '1px solid '+theme.palette.default.borderColor,
+    '&.Mui-disabled': {
+      color: theme.palette.default.disabledContrastText,
+      borderColor: theme.palette.default.disabledBorderColor
+    },
+    '&:hover': {
+      backgroundColor: theme.palette.default.hoverMain,
+      color: theme.palette.default.hoverContrastText,
+      borderColor: theme.palette.default.hoverBorderColor,
+    }
+  },
+  iconButton: {
+    padding: '3px 6px',
+    borderColor: theme.custom.icon.borderColor,
+    color: theme.custom.icon.contrastText,
+    backgroundColor: theme.custom.icon.main,
+    '&.Mui-disabled': {
+      borderColor: theme.custom.icon.disabledBorderColor,
+      backgroundColor: theme.custom.icon.disabledMain,
+      color: theme.custom.icon.disabledContrastText,
+    },
+    '&:hover': {
+      backgroundColor: theme.custom.icon.hoverMain,
+      color: theme.custom.icon.hoverContrastText,
+    }
+  }
+}));
+
+/* pgAdmin primary button */
+export const PrimaryButton = forwardRef((props, ref)=>{
+  let {children, className, ...otherProps} = props;
+  const classes = useStyles();
+
+  return (
+    <Button ref={ref} variant="contained" color="primary" className={clsx(classes.primaryButton, className)} {...otherProps}>{children}</Button>
+  );
+});
+PrimaryButton.displayName = 'PrimaryButton';
+PrimaryButton.propTypes = {
+  children: CustomPropTypes.children,
+  className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+};
+
+/* pgAdmin default button */
+export const DefaultButton = forwardRef((props, ref)=>{
+  let {children, className, ...otherProps} = props;
+  const classes = useStyles();
+
+  return (
+    <Button ref={ref} variant="outlined" color="default" className={clsx(classes.defaultButton, className)} {...otherProps}>{children}</Button>
+  );
+});
+DefaultButton.displayName = 'DefaultButton';
+DefaultButton.propTypes = {
+  children: CustomPropTypes.children,
+  className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+};
+
+/* pgAdmin Icon button, takes Icon component as input */
+export const PgIconButton = forwardRef(({icon, title, className, ...props}, ref)=>{
+  const classes = useStyles();
+
+  /* Tooltip does not work for disabled items */
+  return (
+    <Tooltip title={title || ''} aria-label={title || ''}>
+      <DefaultButton ref={ref} style={{minWidth: 0}} className={clsx(classes.iconButton, className)} {...props}>
+        {icon}
+      </DefaultButton>
+    </Tooltip>
+  );
+});
+PgIconButton.displayName = 'PgIconButton';
+PgIconButton.propTypes = {
+  icon: CustomPropTypes.children,
+  title: PropTypes.string.isRequired,
+  className: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
+};
diff --git a/web/pgadmin/static/js/components/CodeMirror.jsx b/web/pgadmin/static/js/components/CodeMirror.jsx
new file mode 100644
index 000000000..6fac7e2cd
--- /dev/null
+++ b/web/pgadmin/static/js/components/CodeMirror.jsx
@@ -0,0 +1,40 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React, { useEffect, useRef } from 'react';
+import {default as OrigCodeMirror} from 'bundled_codemirror';
+import PropTypes from 'prop-types';
+
+/* React wrapper for CodeMirror */
+export default function CodeMirror({name, value, options}) {
+  const taRef = useRef();
+  const cmObj = useRef();
+
+  useEffect(()=>{
+    /* Create the object only once on mount */
+    cmObj.current = new OrigCodeMirror.fromTextArea(
+      taRef.current, options);
+  }, []);
+
+  useEffect(()=>{
+    /* Refresh when value changes */
+    if(cmObj.current) {
+      cmObj.current.setValue(value);
+      cmObj.current.refresh();
+    }
+  }, [value]);
+
+  return <textarea ref={taRef} name={name} />;
+}
+
+CodeMirror.propTypes = {
+  name: PropTypes.string,
+  value: PropTypes.string,
+  options: PropTypes.object
+};
diff --git a/web/pgadmin/static/js/components/FormComponents.jsx b/web/pgadmin/static/js/components/FormComponents.jsx
new file mode 100644
index 000000000..660366851
--- /dev/null
+++ b/web/pgadmin/static/js/components/FormComponents.jsx
@@ -0,0 +1,842 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+/* Common form components used in pgAdmin */
+
+import React, { forwardRef, useCallback, useEffect, useRef, useState } from 'react';
+import { makeStyles } from '@material-ui/core/styles';
+import { Box, FormControl, OutlinedInput, FormHelperText,
+  Grid, IconButton, FormControlLabel, Switch, Checkbox, useTheme, InputLabel } from '@material-ui/core';
+import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab';
+import ReportProblemIcon from '@material-ui/icons/ReportProblemRounded';
+import InfoIcon from '@material-ui/icons/InfoRounded';
+import CloseIcon from '@material-ui/icons/CloseRounded';
+import CheckIcon from '@material-ui/icons/CheckCircleOutlineRounded';
+import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
+import FolderOpenRoundedIcon from '@material-ui/icons/FolderOpenRounded';
+import Select, {components as RSComponents} from 'react-select';
+import CreatableSelect from 'react-select/creatable';
+import Pickr from '@simonwep/pickr';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+
+import CodeMirror from './CodeMirror';
+import gettext from 'sources/gettext';
+import { showFileDialog } from '../helpers/legacyConnector';
+import _ from 'lodash';
+import { DefaultButton, PrimaryButton, PgIconButton } from './Buttons';
+import CustomPropTypes from '../custom_prop_types';
+
+
+const useStyles = makeStyles((theme) => ({
+  formRoot: {
+    padding: '1rem'
+  },
+  img: {
+    maxWidth: '100%',
+    height: 'auto'
+  },
+  info: {
+    color: theme.palette.info.main,
+    marginLeft: '0.25rem',
+    fontSize: '1rem',
+  },
+  formLabel: {
+    margin: theme.spacing(0.75, 0.75, 0.75, 0.75),
+    display: 'flex',
+  },
+  formLabelError:  {
+    color: theme.palette.error.main,
+  },
+  sql: {
+    height: '100%',
+  },
+  optionIcon: {
+    ...theme.mixins.nodeIcon,
+  },
+  colorBtn: {
+    height: theme.spacing(3.5),
+    minHeight: theme.spacing(3.5),
+    width: theme.spacing(3.5),
+    minWidth: theme.spacing(3.5),
+  }
+}));
+
+
+export const MESSAGE_TYPE = {
+  SUCCESS: 'Success',
+  ERROR: 'Error',
+  INFO: 'Info',
+  CLOSE: 'Close',
+};
+
+/* Icon based on MESSAGE_TYPE */
+function FormIcon({type, close=false, ...props}) {
+  let TheIcon = null;
+  if(close) {
+    TheIcon = CloseIcon;
+  } else if(type === MESSAGE_TYPE.SUCCESS) {
+    TheIcon = CheckIcon;
+  } else if(type === MESSAGE_TYPE.ERROR) {
+    TheIcon = ReportProblemIcon;
+  } else if(type === MESSAGE_TYPE.INFO) {
+    TheIcon = InfoIcon;
+  }
+
+  return <TheIcon fontSize="small" {...props} />;
+}
+FormIcon.propTypes = {
+  type: PropTypes.oneOf(Object.values(MESSAGE_TYPE)),
+  close: PropTypes.bool,
+};
+
+/* Wrapper on any form component to add label, error indicator and help message */
+export function FormInput({children, error, className, label, helpMessage, required, testcid}) {
+  const classes = useStyles();
+  const cid = testcid || _.uniqueId('c');
+  const helpid = `h${cid}`;
+  return (
+    <Grid container spacing={0} className={className}>
+      <Grid item lg={3} md={3} sm={3} xs={12}>
+        <InputLabel htmlFor={cid} className={clsx(classes.formLabel, error?classes.formLabelError : null)} required={required}>
+          {label}
+          {error && <FormIcon type={MESSAGE_TYPE.ERROR} style={{marginLeft: 'auto'}}/>}
+        </InputLabel>
+      </Grid>
+      <Grid item lg={9} md={9} sm={9} xs={12}>
+        <FormControl error={Boolean(error)} fullWidth>
+          {React.cloneElement(children, {cid, helpid})}
+        </FormControl>
+        <FormHelperText id={helpid} variant="outlined">{helpMessage}</FormHelperText>
+      </Grid>
+    </Grid>
+  );
+}
+FormInput.propTypes = {
+  children: CustomPropTypes.children,
+  error: PropTypes.bool,
+  className: CustomPropTypes.className,
+  label: PropTypes.string,
+  helpMessage: PropTypes.string,
+  required: PropTypes.bool,
+  testcid: PropTypes.any,
+};
+
+export function InputSQL({value, options}) {
+  const classes = useStyles();
+  return (
+    <CodeMirror
+      value={value||''}
+      options={{
+        lineNumbers: true,
+        mode: 'text/x-pgsql',
+        ...options,
+      }}
+      className={classes.sql}
+    />
+  );
+}
+InputSQL.propTypes = {
+  value: PropTypes.string,
+  options: PropTypes.object,
+};
+
+export function FormInputSQL({hasError, required, label, className, helpMessage, testcid, value, controlProps}) {
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputSQL value={value} options={controlProps}/>
+    </FormInput>
+  );
+}
+FormInputSQL.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+  value: PropTypes.string,
+  controlProps: PropTypes.object,
+};
+
+
+/* Use forwardRef to pass ref prop to OutlinedInput */
+export const InputText = forwardRef(({
+  cid, helpid, readonly, disabled, maxlength=255, value, onChange, controlProps, ...props}, ref)=>{
+
+  const classes = useStyles();
+
+  return(
+    <OutlinedInput
+      ref={ref}
+      color="primary"
+      fullWidth
+      className={classes.formInput}
+      inputProps={{
+        id: cid,
+        maxLength: maxlength,
+        'aria-describedby': helpid,
+      }}
+      readOnly={Boolean(readonly)}
+      disabled={Boolean(disabled)}
+      rows={4}
+      notched={false}
+      value={value || ''}
+      onChange={onChange}
+      {...controlProps}
+      {...props}
+    />
+  );
+});
+InputText.displayName = 'InputText';
+InputText.propTypes = {
+  cid: PropTypes.string,
+  helpid: PropTypes.string,
+  label: PropTypes.string,
+  readonly: PropTypes.bool,
+  disabled: PropTypes.bool,
+  maxlength: PropTypes.number,
+  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+  onChange: PropTypes.func,
+  controlProps: PropTypes.object,
+};
+
+export function FormInputText({hasError, required, label, className, helpMessage, testcid, ...props}) {
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputText label={label} {...props}/>
+    </FormInput>
+  );
+}
+FormInputText.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+/* Using the existing file dialog functions using showFileDialog */
+export function InputFileSelect({controlProps, onChange, disabled, readonly, ...props}) {
+  const inpRef = useRef();
+  const onFileSelect = (value)=>{
+    onChange && onChange(decodeURI(value));
+    inpRef.current.focus();
+  };
+  return (
+    <InputText ref={inpRef} disabled={disabled} readonly={readonly} {...props} endAdornment={
+      <IconButton onClick={()=>showFileDialog(controlProps, onFileSelect)}
+        disabled={disabled||readonly} aria-label={gettext('Select a file')}><FolderOpenRoundedIcon /></IconButton>
+    } />
+  );
+}
+InputFileSelect.propTypes = {
+  controlProps: PropTypes.object,
+  onChange: PropTypes.func,
+  disabled: PropTypes.bool,
+  readonly: PropTypes.bool,
+};
+
+export function FormInputFileSelect({
+  hasError, required, label, className, helpMessage, testcid, ...props}) {
+
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputFileSelect required={required} label={label} {...props}/>
+    </FormInput>
+  );
+}
+FormInputFileSelect.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+export function InputSwitch({cid, helpid, value, onChange, readonly, controlProps, ...props}) {
+  return (
+    <Switch color="primary"
+      checked={Boolean(value)}
+      onChange={
+        readonly ? ()=>{} : onChange
+      }
+      id={cid}
+      inputProps={{
+        'aria-describedby': helpid,
+      }}
+      {...controlProps}
+      {...props}
+    />
+  );
+}
+InputSwitch.propTypes = {
+  cid: PropTypes.string,
+  helpid: PropTypes.string,
+  value: PropTypes.any,
+  onChange: PropTypes.func,
+  readonly: PropTypes.bool,
+  controlProps: PropTypes.object,
+};
+
+export function FormInputSwitch({hasError, required, label, className, helpMessage, testcid, ...props}) {
+
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputSwitch {...props}/>
+    </FormInput>
+  );
+}
+FormInputSwitch.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+export function InputCheckbox({cid, helpid, value, onChange, controlProps, readonly, ...props}) {
+  controlProps = controlProps || {};
+  return (
+    <FormControlLabel
+      control={
+        <Checkbox
+          id={cid}
+          checked={Boolean(value)}
+          onChange={readonly ? ()=>{} : onChange}
+          color="primary"
+          inputProps={{
+            'aria-describedby': helpid,
+          }}
+          {...props}
+        />
+      }
+      label={controlProps.label}
+    />
+
+  );
+}
+InputCheckbox.propTypes = {
+  cid: PropTypes.string,
+  helpid: PropTypes.string,
+  value: PropTypes.bool,
+  controlProps: PropTypes.object,
+  onChange: PropTypes.func,
+  readonly: PropTypes.bool,
+};
+
+export function FormInputCheckbox({hasError, required, label,
+  className, helpMessage, testcid, ...props}) {
+
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputCheckbox {...props}/>
+    </FormInput>
+  );
+}
+FormInputCheckbox.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+
+export function InputToggle({cid, value, onChange, options, disabled, readonly, ...props}) {
+  return (
+    <ToggleButtonGroup
+      id={cid}
+      value={value}
+      exclusive
+      onChange={(e, val)=>{val!==null && onChange(val);}}
+      {...props}
+    >
+      {
+        (options||[]).map((option)=>{
+          const isSelected = option.value === value;
+          const isDisabled = disabled || (readonly && isSelected);
+          return (
+            <ToggleButton key={option.label} value={option.value} component={isSelected ? PrimaryButton : DefaultButton}
+              disabled={isDisabled} aria-label={option.label}>
+              <CheckRoundedIcon style={{visibility: isSelected ? 'visible': 'hidden'}}/>&nbsp;{option.label}
+            </ToggleButton>
+          );
+        })
+      }
+    </ToggleButtonGroup>
+  );
+}
+InputToggle.propTypes = {
+  cid: PropTypes.string,
+  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
+  options: PropTypes.array,
+  controlProps: PropTypes.object,
+  onChange: PropTypes.func,
+  disabled: PropTypes.bool,
+  readonly: PropTypes.bool,
+};
+
+export function FormInputToggle({hasError, required, label,
+  className, helpMessage, testcid, ...props}) {
+
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputToggle {...props}/>
+    </FormInput>
+  );
+}
+FormInputToggle.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+/* react-select package is used for select input
+ * Customizing the select styles to fit existing theme
+ */
+const customReactSelectStyles = (theme, readonly)=>({
+  input: (provided) => {
+    return {...provided, padding: 0, margin: 0, color: 'inherit'};
+  },
+  singleValue: (provided) => {
+    return {
+      ...provided,
+      color: 'inherit',
+    };
+  },
+  control: (provided, state) => ({
+    ...provided,
+    minHeight: '0',
+    backgroundColor: readonly ? theme.otherVars.inputDisabledBg : theme.palette.background.default,
+    borderColor: theme.otherVars.inputBorderColor,
+    ...(state.isFocused ? {
+      borderColor: theme.palette.primary.main,
+      boxShadow: 'inset 0 0 0 1px '+theme.palette.primary.main,
+      '&:hover': {
+        borderColor: theme.palette.primary.main,
+      }
+    } : {}),
+  }),
+  dropdownIndicator: (provided)=>({
+    ...provided,
+    padding: '0rem 0.25rem',
+  }),
+  indicatorsContainer: (provided)=>({
+    ...provided,
+    ...(readonly ? {display: 'none'} : {})
+  }),
+  clearIndicator: (provided)=>({
+    ...provided,
+    padding: '0rem 0.25rem',
+  }),
+  valueContainer: (provided)=>({
+    ...provided,
+    padding: theme.otherVars.reactSelect.padding,
+  }),
+  menu: (provided)=>({
+    ...provided,
+    backgroundColor: theme.palette.background.default,
+    color: theme.palette.text.primary,
+    boxShadow: 'none',
+    border: '1px solid ' + theme.otherVars.inputBorderColor,
+  }),
+  menuPortal: (provided)=>({
+    ...provided, zIndex: 9999,
+    backgroundColor: 'inherit',
+    color: 'inherit',
+  }),
+  option: (provided, state)=>({
+    ...provided,
+    padding: '0.5rem',
+    backgroundColor: state.isFocused ? theme.palette.grey[200] :
+      (state.isSelected ? theme.palette.primary.main : 'inherit'),
+  }),
+  multiValue: (provided)=>({
+    ...provided,
+    backgroundColor: theme.palette.grey[400],
+  }),
+  multiValueLabel: (provided)=>({
+    ...provided,
+    fontSize: '1em',
+    zIndex: 9999,
+    color: theme.palette.text.primary
+  }),
+  multiValueRemove: (provided)=>({
+    ...provided,
+    '&:hover': {
+      backgroundColor: 'unset',
+      color: theme.palette.error.main,
+    },
+    ...(readonly ? {display: 'none'} : {})
+  }),
+});
+
+function OptionView({image, label}) {
+  const classes = useStyles();
+  return (
+    <>
+      {image && <span className={clsx(classes.optionIcon, image)}></span>}
+      <span>{label}</span>
+    </>
+  );
+}
+OptionView.propTypes = {
+  image: PropTypes.string,
+  label: PropTypes.string,
+};
+
+function CustomSelectOption(props) {
+  return (
+    <RSComponents.Option {...props}>
+      <OptionView image={props.data.image} label={props.data.label} />
+    </RSComponents.Option>
+  );
+}
+CustomSelectOption.propTypes = {
+  data: PropTypes.object,
+};
+
+function CustomSelectSingleValue(props) {
+  return (
+    <RSComponents.SingleValue {...props}>
+      <OptionView image={props.data.image} label={props.data.label} />
+    </RSComponents.SingleValue>
+  );
+}
+CustomSelectSingleValue.propTypes = {
+  data: PropTypes.object,
+};
+
+function getRealValue(options, value, creatable) {
+  let realValue = null;
+  if(_.isArray(value)) {
+    if(creatable) {
+      realValue = value.map((val)=>({label:val, value: val}));
+    } else {
+      realValue = value.map((val)=>(_.find(options, (option)=>option.value==val)));
+    }
+  } else {
+    realValue = _.find(options, (option)=>option.value==value) || null;
+  }
+  return realValue;
+}
+
+export function InputSelect({
+  cid, onChange, options, readonly=false, value, controlProps={}, optionsLoaded, disabled, ...props}) {
+  const [[finalOptions, isLoading], setFinalOptions] = useState([[], true]);
+  const theme = useTheme();
+
+  useEffect(()=>{
+    let optPromise = options, umounted=false;
+    if(typeof options === 'function') {
+      optPromise = options();
+    }
+    Promise.resolve(optPromise)
+      .then((res)=>{
+        /* If component unmounted, dont update state */
+        if(!umounted) {
+          optionsLoaded && optionsLoaded(res, value);
+          setFinalOptions([res || [], false]);
+        }
+      });
+    return ()=>umounted=true;
+  }, []);
+
+  const onChangeOption = useCallback((selectVal, action)=>{
+    if(_.isArray(selectVal)) {
+      onChange && onChange(selectVal.map((option)=>option.value, action.name));
+    } else {
+      onChange && onChange(selectVal ? selectVal.value : null, action.name);
+    }
+  }, [onChange]);
+
+  const realValue = getRealValue(finalOptions, value, controlProps.creatable);
+  const otherProps = {
+    isSearchable: !readonly,
+    isClearable: !readonly && (!_.isUndefined(controlProps.allowClear) ? controlProps.allowClear : true),
+    isDisabled: Boolean(disabled),
+  };
+
+  const styles = customReactSelectStyles(theme, readonly || disabled);
+
+  const commonProps = {
+    components: {
+      Option: CustomSelectOption,
+      SingleValue: CustomSelectSingleValue,
+    },
+    isMulti: Boolean(controlProps.multiple),
+    openMenuOnClick: !readonly,
+    onChange: onChangeOption,
+    isLoading: isLoading,
+    options: finalOptions,
+    value: realValue,
+    menuPortalTarget: document.body,
+    styles: styles,
+    inputId: cid,
+    ...otherProps,
+    ...props
+  };
+  if(!controlProps.creatable) {
+    return (
+      <Select {...commonProps}/>
+    );
+  } else {
+    return (
+      <CreatableSelect {...commonProps}/>
+    );
+  }
+}
+InputSelect.propTypes = {
+  cid: PropTypes.string,
+  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]),
+  options: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(Promise), PropTypes.func]),
+  controlProps: PropTypes.object,
+  optionsLoaded: PropTypes.func,
+  onChange: PropTypes.func,
+  disabled: PropTypes.bool,
+  readonly: PropTypes.bool,
+};
+
+
+export function FormInputSelect({
+  hasError, required, className, label, helpMessage, testcid, ...props}) {
+
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputSelect {...props}/>
+    </FormInput>
+  );
+}
+FormInputSelect.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  label: PropTypes.string,
+  className: CustomPropTypes.className,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+/* React wrapper on color pickr */
+export function InputColor({value, controlProps, disabled, onChange, currObj}) {
+  const pickrOptions = {
+    showPalette: true,
+    allowEmpty: true,
+    colorFormat: 'HEX',
+    defaultColor: null,
+    position: 'right-middle',
+    clearText: gettext('No color'),
+    ...controlProps,
+    disabled: disabled,
+  };
+  const eleRef = useRef();
+  const pickrObj = useRef();
+  const classes = useStyles();
+
+  const setColor = (value)=>{
+    pickrObj.current &&
+      pickrObj.current.setColor((_.isUndefined(value) || value == '') ? pickrOptions.defaultColor : value);
+  };
+
+  const destroyPickr = ()=>{
+    if(pickrObj.current) {
+      pickrObj.current.destroy();
+      pickrObj.current = null;
+    }
+  };
+
+  const initPickr = ()=>{
+    /* pickr does not have way to update options, need to
+    destroy and recreate pickr to reflect options */
+    destroyPickr();
+
+    pickrObj.current = new Pickr({
+      el: eleRef.current,
+      useAsButton: true,
+      theme: 'monolith',
+      swatches: [
+        '#000', '#666', '#ccc', '#fff', '#f90', '#ff0', '#0f0',
+        '#f0f', '#f4cccc', '#fce5cd', '#d0e0e3', '#cfe2f3', '#ead1dc', '#ea9999',
+        '#b6d7a8', '#a2c4c9', '#d5a6bd', '#e06666','#93c47d', '#76a5af', '#c27ba0',
+        '#f1c232', '#6aa84f', '#45818e', '#a64d79', '#bf9000', '#0c343d', '#4c1130',
+      ],
+      position: pickrOptions.position,
+      strings: {
+        clear: pickrOptions.clearText,
+      },
+      components: {
+        palette: pickrOptions.showPalette,
+        preview: true,
+        hue: pickrOptions.showPalette,
+        interaction: {
+          clear: pickrOptions.allowEmpty,
+          defaultRepresentation: pickrOptions.colorFormat,
+          disabled: pickrOptions.disabled,
+        },
+      },
+    }).on('init', instance => {
+      setColor(value);
+      disabled && instance.disable();
+
+      const {lastColor} = instance.getRoot().preview;
+      const {clear} = instance.getRoot().interaction;
+
+      /* Cycle the keyboard navigation within the color picker */
+      clear.addEventListener('keydown', (e)=>{
+        if(e.keyCode === 9) {
+          e.preventDefault();
+          e.stopPropagation();
+          lastColor.focus();
+        }
+      });
+
+      lastColor.addEventListener('keydown', (e)=>{
+        if(e.keyCode === 9 && e.shiftKey) {
+          e.preventDefault();
+          e.stopPropagation();
+          clear.focus();
+        }
+      });
+    }).on('clear', () => {
+      onChange && onChange('');
+    }).on('change', (color) => {
+      onChange && onChange(color.toHEXA().toString());
+    }).on('show', (color, instance) => {
+      const {palette} = instance.getRoot().palette;
+      palette.focus();
+    }).on('hide', (instance) => {
+      const button = instance.getRoot().button;
+      button.focus();
+    });
+
+    if(currObj) {
+      currObj(pickrObj.current);
+    }
+  };
+
+  useEffect(()=>{
+    initPickr();
+    return ()=>{
+      destroyPickr();
+    };
+  }, [...Object.values(pickrOptions)]);
+
+  useEffect(()=>{
+    if(pickrObj.current) {
+      setColor(value);
+    }
+  }, [value]);
+
+  let btnStyles = {backgroundColor: value};
+  return (
+    // <Button variant="contained" ref={eleRef} className={classes.colorBtn} style={btnStyles} disabled={pickrOptions.disabled}>
+    //   {(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon />}
+    // </Button>
+    <PgIconButton ref={eleRef} title={gettext('Select the color')} className={classes.colorBtn} style={btnStyles} disabled={pickrOptions.disabled}
+      icon={(_.isUndefined(value) || _.isNull(value) || value === '') && <CloseIcon />}
+    />
+  );
+}
+InputColor.propTypes = {
+  value: PropTypes.string,
+  controlProps: PropTypes.object,
+  onChange: PropTypes.func,
+  disabled: PropTypes.bool,
+  currObj: PropTypes.func,
+};
+
+export function FormInputColor({
+  hasError, required, className, label, helpMessage, testcid, ...props}) {
+
+  return (
+    <FormInput required={required} label={label} error={hasError} className={className} helpMessage={helpMessage} testcid={testcid}>
+      <InputColor {...props}/>
+    </FormInput>
+  );
+}
+FormInputColor.propTypes = {
+  hasError: PropTypes.bool,
+  required: PropTypes.bool,
+  className: CustomPropTypes.className,
+  label: PropTypes.string,
+  helpMessage: PropTypes.string,
+  testcid: PropTypes.string,
+};
+
+
+const useStylesFormFooter = makeStyles((theme)=>({
+  root: {
+    padding: theme.spacing(0.5),
+    position: 'absolute',
+    bottom: 0,
+    left: 0,
+    right: 0,
+  },
+  container: {
+    borderWidth: '1px',
+    borderStyle: 'solid',
+    borderRadius: theme.shape.borderRadius,
+    padding: theme.spacing(0.5),
+    display: 'flex',
+    alignItems: 'center',
+  },
+  containerSuccess: {
+    borderColor: theme.palette.success.main,
+    backgroundColor: theme.palette.success.light,
+  },
+  iconSuccess: {
+    color: theme.palette.success.main,
+  },
+  containerError: {
+    borderColor: theme.palette.error.main,
+    backgroundColor: theme.palette.error.light,
+  },
+  iconError: {
+    color: theme.palette.error.main,
+  },
+  message: {
+    marginLeft: theme.spacing(0.5),
+  },
+  closeButton: {
+    marginLeft: 'auto',
+  },
+}));
+
+/* The form footer used mostly for showing error */
+export function FormFooterMessage({type=MESSAGE_TYPE.SUCCESS, message, closable=true, onClose=()=>{}}) {
+  const classes = useStylesFormFooter();
+
+  if(!message) {
+    return <></>;
+  }
+  return (
+    <Box className={classes.root}>
+      <Box className={clsx(classes.container, classes[`container${type}`])}>
+        <FormIcon type={type} className={classes[`icon${type}`]}/>
+        <Box className={classes.message}>{message}</Box>
+        {closable && <IconButton className={clsx(classes.closeButton, classes[`icon${type}`])} onClick={onClose}>
+          <FormIcon close={true}/>
+        </IconButton>}
+      </Box>
+    </Box>
+  );
+}
+
+FormFooterMessage.propTypes = {
+  type: PropTypes.oneOf(Object.values(MESSAGE_TYPE)).isRequired,
+  message: PropTypes.string,
+  closable: PropTypes.bool,
+  onClose: PropTypes.func,
+};
diff --git a/web/pgadmin/static/js/components/Loader.jsx b/web/pgadmin/static/js/components/Loader.jsx
new file mode 100644
index 000000000..191b93d87
--- /dev/null
+++ b/web/pgadmin/static/js/components/Loader.jsx
@@ -0,0 +1,57 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { CircularProgress, Box, Typography, makeStyles } from '@material-ui/core';
+import React from 'react';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles((theme)=>({
+  root: {
+    position: 'absolute',
+    top: 0,
+    bottom: 0,
+    left: 0,
+    right: 0,
+    backgroundColor: theme.otherVars.loader.backgroundColor,
+    color: theme.otherVars.loader.color,
+    zIndex: 1000,
+    display: 'flex',
+  },
+  loaderRoot: {
+    color: theme.otherVars.loader.color,
+    display: 'flex',
+    alignItems: 'center',
+    margin: 'auto',
+    '.MuiTypography-root': {
+      marginLeft: theme.spacing(1),
+    }
+  },
+  loader: {
+    color: theme.otherVars.loader.color,
+  }
+}));
+
+export default function Loader({message}) {
+  const classes = useStyles();
+  if(!message) {
+    return <></>;
+  }
+  return (
+    <Box className={classes.root}>
+      <Box className={classes.loaderRoot}>
+        <CircularProgress className={classes.loader} />
+        <Typography>{message}</Typography>
+      </Box>
+    </Box>
+  );
+}
+
+Loader.propTypes = {
+  message: PropTypes.string,
+};
diff --git a/web/pgadmin/static/js/components/Privilege.jsx b/web/pgadmin/static/js/components/Privilege.jsx
new file mode 100644
index 000000000..db2bacbfb
--- /dev/null
+++ b/web/pgadmin/static/js/components/Privilege.jsx
@@ -0,0 +1,176 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import { makeStyles } from '@material-ui/core';
+import _ from 'lodash';
+import React from 'react';
+import { InputCheckbox, InputText } from './FormComponents';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+
+const useStyles = makeStyles(()=>({
+  /* Display the privs table only when focussed */
+  root: {
+    '&:not(:focus-within) .priv-table': {
+      display: 'none',
+    }
+  },
+  table: {
+    borderSpacing: 0,
+    width: '100%',
+    fontSize: '0.8em',
+  },
+  tableCell: {
+    textAlign: 'left',
+  }
+}));
+
+export default function Privilege({value, onChange, controlProps}) {
+  // All available privileges in the PostgreSQL database server for
+  // generating the label for the specific Control
+  const LABELS = {
+    'C': 'CREATE',
+    'T': 'TEMPORARY',
+    'c': 'CONNECT',
+    'a': 'INSERT',
+    'r': 'SELECT',
+    'w': 'UPDATE',
+    'd': 'DELETE',
+    'D': 'TRUNCATE',
+    'x': 'REFERENCES',
+    't': 'TRIGGER',
+    'U': 'USAGE',
+    'X': 'EXECUTE',
+  };
+  let all = false;
+  let allWithGrant = false;
+  const classes = useStyles();
+  let textValue = '';
+  for(const v of value||[]) {
+    if(v.privilege) {
+      textValue += v.privilege_type;
+      if(v.with_grant) {
+        textValue += '*';
+      }
+    }
+  }
+
+  const checkboxId = _.uniqueId();
+  /* Calculate the real display value by merging all supported privs with incoming value */
+  const realVal = (controlProps?.supportedPrivs || []).map((priv)=>{
+    let inValue = _.find(value, (v)=>v.privilege_type===priv) || {
+      privilege: false, with_grant: false,
+    };
+    return {
+      privilege_type: priv,
+      privilege: Boolean(inValue.privilege),
+      with_grant: Boolean(inValue.with_grant),
+    };
+  });
+
+  const onCheckAll = (e, forWithGrant)=>{
+    let newValue = [];
+    /* Push all the privs or ignore if unchecked and return empty */
+    realVal.forEach((v)=>{
+      if(forWithGrant) {
+        newValue.push({
+          ...v,
+          privilege: true,
+          with_grant: e.target.checked,
+        });
+      } else if(e.target.checked) {
+        newValue.push({
+          ...v,
+          privilege: e.target.checked,
+        });
+      }
+    });
+    onChange(newValue);
+  };
+
+  const onCheck = (e, forWithGrant)=>{
+    let exists = false;
+    let newValue = [];
+
+    /* Calculate the newValue by pushing all selected and ignore if unchecked */
+    (value||[]).forEach((v)=>{
+      if(v.privilege_type === e.target.name) {
+        exists = true;
+        if(forWithGrant) {
+          newValue.push({
+            ...v,
+            with_grant: e.target.checked,
+          });
+        }
+      } else {
+        newValue.push(v);
+      }
+    });
+
+    if(!exists && e.target.checked) {
+      newValue.push({
+        privilege_type: e.target.name,
+        privilege: e.target.checked,
+        with_grant: false,
+      });
+    }
+
+    onChange(newValue);
+  };
+
+  /* If all supported privs and incoming value length matches, clearly all are selected */
+  all = (realVal.length === (value || []).length);
+  allWithGrant = (realVal.length === (value || []).length) && (value || []).every((d)=>d.with_grant);
+
+  return (
+    <div className={classes.root}>
+      <InputText value={textValue} readOnly/>
+      <table className={clsx(classes.table, 'priv-table')}>
+        {(realVal.length > 1) && <thead>
+          <tr>
+            <td className={classes.tableCell}>
+              <InputCheckbox name="all" controlProps={{label: 'ALL'}} id={checkboxId} size="small"
+                onChange={(e)=>onCheckAll(e, false)} value={all}/>
+            </td>
+            <td className={classes.tableCell}>
+              <InputCheckbox name="all" controlProps={{label: 'WITH GRANT OPTION'}} id={checkboxId} size="small"
+                disabled={!all} onChange={(e)=>onCheckAll(e, true)} value={allWithGrant}/>
+            </td>
+          </tr>
+        </thead>}
+        <tbody>
+          {
+            realVal.map((d)=>{
+              return (
+                <tr key={d.privilege_type}>
+                  <td className={classes.tableCell}>
+                    <InputCheckbox name={d.privilege_type} controlProps={{label: LABELS[d.privilege_type]}}
+                      id={checkboxId} value={Boolean(d.privilege)} size="small"
+                      onChange={(e)=>onCheck(e, false)}/>
+                  </td>
+                  <td className={classes.tableCell}>
+                    <InputCheckbox name={d.privilege_type} controlProps={{label: 'WITH GRANT OPTION'}}
+                      id={checkboxId} value={Boolean(d.with_grant)} size="small" disabled={!d.privilege}
+                      onChange={(e)=>onCheck(e, true)}/>
+                  </td>
+                </tr>
+              );
+            })
+          }
+        </tbody>
+      </table>
+    </div>
+  );
+}
+
+Privilege.propTypes = {
+  value: PropTypes.array,
+  onChange: PropTypes.func,
+  controlProps: PropTypes.object,
+};
diff --git a/web/pgadmin/static/js/components/TabPanel.jsx b/web/pgadmin/static/js/components/TabPanel.jsx
new file mode 100644
index 000000000..ce46ba707
--- /dev/null
+++ b/web/pgadmin/static/js/components/TabPanel.jsx
@@ -0,0 +1,42 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import React from 'react';
+import {Box, makeStyles} from '@material-ui/core';
+import clsx from 'clsx';
+import PropTypes from 'prop-types';
+import CustomPropTypes from '../custom_prop_types';
+
+const useStyles = makeStyles((theme)=>({
+  root: {
+    height: '100%',
+    padding: theme.spacing(1),
+    overflow: 'auto',
+    backgroundColor: theme.palette.grey[400]
+  }
+}));
+
+/* Material UI does not have any tabpanel component, we create one for us */
+export default function TabPanel({children, classNameRoot, className, value, index}) {
+  const classes = useStyles();
+  const active = value === index;
+  return (
+    <Box className={clsx(classes.root, classNameRoot)} component="div" hidden={!active}>
+      <Box style={{height: '100%'}} className={className}>{children}</Box>
+    </Box>
+  );
+}
+
+TabPanel.propTypes = {
+  children: CustomPropTypes.children,
+  classNameRoot: CustomPropTypes.className,
+  className: CustomPropTypes.className,
+  value: PropTypes.any.isRequired,
+  index: PropTypes.any.isRequired,
+};
diff --git a/web/pgadmin/static/js/custom_prop_types.js b/web/pgadmin/static/js/custom_prop_types.js
index 4e4f45b9b..4513b6d3a 100644
--- a/web/pgadmin/static/js/custom_prop_types.js
+++ b/web/pgadmin/static/js/custom_prop_types.js
@@ -1,10 +1,32 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
 import PropTypes from 'prop-types';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
 
+/* Common Prop types */
 const CustomPropTypes = {
   ref: PropTypes.oneOfType([
     PropTypes.func,
     PropTypes.shape({ current: PropTypes.instanceOf(Element) }),
   ]),
+
+  schemaUI: PropTypes.instanceOf(BaseUISchema),
+
+  children: PropTypes.oneOfType([
+    PropTypes.arrayOf(PropTypes.node),
+    PropTypes.node
+  ]),
+
+  className: PropTypes.oneOfType([
+    PropTypes.string,
+    PropTypes.object,
+  ])
 };
 
 export default CustomPropTypes;
diff --git a/web/pgadmin/static/js/helpers/legacyConnector.js b/web/pgadmin/static/js/helpers/legacyConnector.js
new file mode 100644
index 000000000..9257a8219
--- /dev/null
+++ b/web/pgadmin/static/js/helpers/legacyConnector.js
@@ -0,0 +1,66 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+/* This file will have wrappers and connectors used by React components to
+ * re-use any existing non-react components.
+ * These functions may not be needed once all are migrated
+ */
+
+import Alertify from 'pgadmin.alertifyjs';
+import gettext from 'sources/gettext';
+import pgAdmin from 'sources/pgadmin';
+
+export function confirmDeleteRow(onOK, onCancel, title, message) {
+  Alertify.confirm(
+    title || gettext('Delete Row'),
+    message || gettext('Are you sure you wish to delete this row?'),
+    function() {
+      onOK();
+      return true;
+    },
+    function() {
+      onCancel();
+      return true;
+    }
+  );
+}
+
+/* Don't import alertfiy directly in react files. Not good for testability */
+export function pgAlertify() {
+  return Alertify;
+}
+
+/* Used by file select component to re-use existing logic */
+export function showFileDialog(dialogParams, onFileSelect) {
+  let params = {
+    supported_types: dialogParams.supportedTypes || [],
+    dialog_type: dialogParams.dialogType || 'select_file',
+    dialog_title: dialogParams.dialogTitle || '',
+    btn_primary: dialogParams.btnPrimary || '',
+  };
+  pgAdmin.FileManager.init();
+  pgAdmin.FileManager.show_dialog(params);
+
+  const onDialogClose = ()=>removeListeners();
+  pgAdmin.Browser.Events.on('pgadmin-storage:finish_btn:' + params.dialog_type, onFileSelect);
+  pgAdmin.Browser.Events.on('pgadmin-storage:cancel_btn:' + params.dialog_type, onDialogClose);
+
+  const removeListeners = ()=>{
+    pgAdmin.Browser.Events.off('pgadmin-storage:finish_btn:' + params.dialog_type, onFileSelect);
+    pgAdmin.Browser.Events.off('pgadmin-storage:cancel_btn:' + params.dialog_type, onDialogClose);
+  };
+}
+
+export function onPgadminEvent(eventName, handler) {
+  pgAdmin.Browser.Events.on(eventName, handler);
+}
+
+export function offPgadminEvent(eventName, handler) {
+  pgAdmin.Browser.Events.off(eventName, handler);
+}
diff --git a/web/pgadmin/static/js/utils.js b/web/pgadmin/static/js/utils.js
index 79b0bcefe..81a3b0fe8 100644
--- a/web/pgadmin/static/js/utils.js
+++ b/web/pgadmin/static/js/utils.js
@@ -408,3 +408,11 @@ function checkBinaryPathExists(binaryPathArray, selectedServerVersion) {
 
   return (serverSpecificPathExist | foundDefaultPath);
 }
+
+/* If a function, then evaluate */
+export function evalFunc(func, param) {
+  if(_.isFunction(func)) {
+    return func.apply(null, [param]);
+  }
+  return func;
+}
diff --git a/web/pgadmin/static/js/validators.js b/web/pgadmin/static/js/validators.js
new file mode 100644
index 000000000..a3343480a
--- /dev/null
+++ b/web/pgadmin/static/js/validators.js
@@ -0,0 +1,70 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+
+import {sprintf} from 'sources/utils';
+import _ from 'lodash';
+import pgAdmin from 'sources/pgadmin';
+
+/* Validate value for min max range */
+export function minMaxValidator(label, value, minValue, maxValue) {
+  if((_.isUndefined(value) || _.isNull(value) || String(value) === ''))
+    return null;
+  if (!_.isUndefined(minValue) && value < minValue) {
+    return sprintf(pgAdmin.Browser.messages.MUST_GR_EQ, label, minValue);
+  } else if (!_.isUndefined(maxValue) && value > maxValue) {
+    return sprintf(pgAdmin.Browser.messages.MUST_LESS_EQ, label, maxValue);
+  }
+  return null;
+}
+
+/* Validate value to check if it is a number */
+export function numberValidator(label, value) {
+  if((_.isUndefined(value) || _.isNull(value) || String(value) === ''))
+    return null;
+  var pattern = new RegExp('^-?[0-9]+(\.?[0-9]*)?$');
+  if (!pattern.test(value)) {
+    return sprintf(pgAdmin.Browser.messages.MUST_BE_NUM, label);
+  }
+  return null;
+}
+
+/* Validate value to check if it is an integer */
+export function integerValidator(label, value) {
+  if((_.isUndefined(value) || _.isNull(value) || String(value) === ''))
+    return null;
+  var pattern = new RegExp('^-?[0-9]*$');
+  if (!pattern.test(value)) {
+    return sprintf(pgAdmin.Browser.messages.MUST_BE_INT, label);
+  }
+  return null;
+}
+
+/* Validate value to check if it is empty */
+export function emptyValidator(label, value) {
+  if(isEmptyString(value) || String(value).replace(/^\s+|\s+$/g, '') == '') {
+    return sprintf(pgAdmin.Browser.messages.CANNOT_BE_EMPTY, label);
+  }
+  return null;
+}
+
+export function isEmptyString(string) {
+  return _.isUndefined(string) || _.isNull(string) || String(string).trim() === '';
+}
+
+/* Validate rows to check for any duplicate rows based on uniqueCols-columns array */
+export function checkUniqueCol(rows, uniqueCols) {
+  if(uniqueCols) {
+    for(const [i, row] of rows.entries()) {
+      for(const checkRow of rows.slice(0, i)) {
+        if(_.isEqual(_.pick(checkRow, uniqueCols), _.pick(row, uniqueCols))) return i;
+      }
+    }
+  }
+  return -1;
+}
diff --git a/web/pgadmin/static/scss/_bootstrap.overrides.scss b/web/pgadmin/static/scss/_bootstrap.overrides.scss
index 55a59025b..4caa372fd 100644
--- a/web/pgadmin/static/scss/_bootstrap.overrides.scss
+++ b/web/pgadmin/static/scss/_bootstrap.overrides.scss
@@ -107,7 +107,7 @@ legend {
 	  color: $btn-secondary-hover-fg !important;
 	} &.disabled, &:disabled {
 	  color: $btn-secondary-disabled-fg !important;
-	  border-color: $btn-secondary-disabled-bg !important;
+	  border-color: $btn-secondary-disabled-border-color !important;
 	}
 	color: $btn-secondary-fg !important;
 
diff --git a/web/pgadmin/static/scss/_pgadmin.style.scss b/web/pgadmin/static/scss/_pgadmin.style.scss
index 6a185471b..0213db301 100644
--- a/web/pgadmin/static/scss/_pgadmin.style.scss
+++ b/web/pgadmin/static/scss/_pgadmin.style.scss
@@ -264,6 +264,7 @@
 
 .obj_properties {
     padding: 0px;
+    height: 100%;
 }
 
 .obj_properties .pgadmin-control .uneditable-input {
diff --git a/web/pgadmin/static/scss/_webcabin.pgadmin.scss b/web/pgadmin/static/scss/_webcabin.pgadmin.scss
index ba15edc15..7d9d4289e 100644
--- a/web/pgadmin/static/scss/_webcabin.pgadmin.scss
+++ b/web/pgadmin/static/scss/_webcabin.pgadmin.scss
@@ -189,7 +189,7 @@
 .wcFrameEdge {
   background-color: transparent;
   border: none;
-  z-index: 5;
+  z-index: 5000;
 }
 
 .wcFrameEdgeN, .wcFrameEdgeS {
diff --git a/web/pgadmin/static/scss/resources/_default.variables.scss b/web/pgadmin/static/scss/resources/_default.variables.scss
index d9fb6653a..7b187a45c 100644
--- a/web/pgadmin/static/scss/resources/_default.variables.scss
+++ b/web/pgadmin/static/scss/resources/_default.variables.scss
@@ -286,7 +286,7 @@ $btn-secondary-fg: $color-fg !default;
 $btn-secondary-hover-fg: $color-fg !default;
 $btn-secondary-border: $color-gray !default;
 $btn-secondary-hover-bg: $color-gray-light !default;
-$btn-secondary-disabled-bg: $color-gray !default;
+$btn-secondary-disabled-border-color: $color-gray !default;
 $btn-secondary-disabled-fg: $color-fg !default;
 
 $btn-frame-close-bg:$btn-secondary-hover-bg !default;
diff --git a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss
index b4783bce0..cd6462df4 100644
--- a/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss
+++ b/web/pgadmin/static/scss/resources/high_contrast/_theme.variables.scss
@@ -151,7 +151,7 @@ $btn-secondary-bg: transparent;
 $btn-secondary-fg: $color-primary;
 $btn-secondary-hover-fg: $color-fg;
 $btn-secondary-border: $color-primary;
-$btn-secondary-disabled-bg: $color-gray-lighter;
+$btn-secondary-disabled-border-color: $color-gray-lighter;
 $btn-secondary-disabled-fg: $color-gray-lighter;
 
 $btn-frame-close-bg: $color-gray-light;
diff --git a/web/pgadmin/templates/base.html b/web/pgadmin/templates/base.html
index ce375ec44..991b745b3 100644
--- a/web/pgadmin/templates/base.html
+++ b/web/pgadmin/templates/base.html
@@ -21,7 +21,8 @@
     <!-- Base template stylesheets -->
     <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/generated/style.css')}}"/>
     <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename='js/generated/pgadmin.style.css')}}"/>
-    <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename=('js/generated/'+get_theme_css())) }}"/>
+    {% set theme = get_theme_css() %}
+    <link type="text/css" rel="stylesheet" href="{{ url_for('static', filename=('js/generated/'+theme[0])) }}" data-theme="{{theme[1]}}"/>
 
      <!--View specified stylesheets-->
     {% block css_link %}{% endblock %}
diff --git a/web/regression/javascript/SchemaView/SchemaView.spec.js b/web/regression/javascript/SchemaView/SchemaView.spec.js
new file mode 100644
index 000000000..16bb28f8a
--- /dev/null
+++ b/web/regression/javascript/SchemaView/SchemaView.spec.js
@@ -0,0 +1,492 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import {TestSchema, TestSchemaAllTypes} from './TestSchema.ui';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import * as legacyConnector from 'sources/helpers/legacyConnector';
+
+const initData = {
+  id: 1,
+  field1: 'field1val',
+  field2: 1,
+  fieldcoll: [
+    {field3: 1, field4: 'field4val1', field5: 'field5val1'},
+    {field3: 2, field4: 'field4val2', field5: 'field5val2'},
+  ],
+  field3: 3,
+  field4: 'field4val',
+};
+
+function getInitData() {
+  return Promise.resolve(initData);
+}
+
+function getSchema() {
+  return new TestSchema();
+}
+
+function getSchemaAllTypes() {
+  return new TestSchemaAllTypes();
+}
+
+describe('SchemaView', ()=>{
+  let mount;
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+  });
+
+  describe('SchemaDialogView', ()=>{
+    let ctrl,
+      onSave=jasmine.createSpy('onSave').and.returnValue(Promise.resolve()),
+      onClose=jasmine.createSpy('onClose'),
+      onHelp=jasmine.createSpy('onHelp'),
+      onEdit=jasmine.createSpy('onEdit'),
+      onDataChange=jasmine.createSpy('onDataChange'),
+      getSQLValue=jasmine.createSpy('onEdit').and.returnValue(Promise.resolve('select 1;')),
+      ctrlMount = (props)=>{
+        ctrl?.unmount();
+        ctrl = mount(<SchemaView
+          formType='dialog'
+          schema={getSchema()}
+          viewHelperProps={{
+            mode: 'create',
+          }}
+          onSave={onSave}
+          onClose={onClose}
+          onHelp={onHelp}
+          onEdit={onEdit}
+          onDataChange={onDataChange}
+          confirmOnCloseReset={true}
+          hasSQL={true}
+          getSQLValue={getSQLValue}
+          disableSqlHelp={false}
+          {...props}
+        />);
+      },
+      simulateValidData = ()=>{
+        ctrl.find('MappedFormControl[id="field1"]').find('input').simulate('change', {target: {value: 'val1'}});
+        ctrl.find('MappedFormControl[id="field2"]').find('input').simulate('change', {target: {value: '2'}});
+        ctrl.find('MappedFormControl[id="field5"]').find('textarea').simulate('change', {target: {value: 'val5'}});
+        /* Add a row */
+        ctrl.find('DataGridView').find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+        ctrl.find('DataGridView').find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+        ctrl.find('MappedCellControl[id="field5"]').at(0).find('input').simulate('change', {target: {value: 'rval51'}});
+        ctrl.find('MappedCellControl[id="field5"]').at(1).find('input').simulate('change', {target: {value: 'rval52'}});
+      };
+    beforeEach(()=>{
+      ctrlMount();
+    });
+
+    it('init', (done)=>{
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find('MappedFormControl').length).toBe(6);
+        expect(ctrl.find('FormView').length).toBe(2);
+        expect(ctrl.find('DataGridView').length).toBe(1);
+        expect(ctrl.find('DefaultButton[data-test="Reset"]').prop('disabled')).toBeTrue();
+        expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeTrue();
+
+        done();
+      }, 0);
+    });
+
+    it('change text', (done)=>{
+      ctrl.find('MappedFormControl[id="field2"]').find('input').simulate('change', {target: {value: '2'}});
+      setTimeout(()=>{
+        ctrl.update();
+        /* Error should come for field1 as it is empty and noEmpty true */
+        expect(ctrl.find('FormFooterMessage').prop('message')).toBe('\'Field1\' cannot be empty.');
+        expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeTrue();
+        done();
+      }, 0);
+    });
+
+    it('close error on click', (done)=>{
+      ctrl.find('FormFooterMessage').find('button').simulate('click');
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find('FormFooterMessage').prop('message')).toBe('');
+        done();
+      }, 0);
+    });
+
+    it('valid form data', (done)=>{
+      simulateValidData();
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find('FormFooterMessage').prop('message')).toBeFalsy();
+        done();
+      }, 0);
+    });
+
+    describe('DataGridView', ()=>{
+      it('add row', (done)=>{
+        ctrl.find('DataGridView').find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('DataGridView').find('DataTableRow').length).toBe(1);
+          done();
+        }, 0);
+      });
+
+      it('remove row', (done)=>{
+        simulateValidData();
+
+        /* Press OK */
+        let confirmSpy = spyOn(legacyConnector, 'confirmDeleteRow').and.callFake((yesFn)=>{
+          yesFn();
+        });
+        ctrl.find('DataGridView').find('PgIconButton[data-test="delete-row"]').at(0).find('button').simulate('click');
+        expect(confirmSpy.calls.argsFor(0)[2]).toBe('Custom delete title');
+        expect(confirmSpy.calls.argsFor(0)[3]).toBe('Custom delete message');
+
+        /* Press Cancel */
+        spyOn(legacyConnector, 'confirmDeleteRow').and.callFake((yesFn, cancelFn)=>{
+          cancelFn();
+        });
+        ctrl.find('DataGridView').find('PgIconButton[data-test="delete-row"]').at(0).find('button').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('DataGridView').find('DataTableRow').length).toBe(1);
+          done();
+        }, 0);
+      });
+
+      it('expand row', (done)=>{
+        simulateValidData();
+        ctrl.find('DataGridView').find('PgIconButton[data-test="expand-row"]').at(0).find('button').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('DataGridView').find('FormView').length).toBe(1);
+          done();
+        }, 0);
+      });
+
+      it('unique col test', (done)=>{
+        simulateValidData();
+        ctrl.find('MappedCellControl[id="field5"]').at(1).find('input').simulate('change', {target: {value: 'rval51'}});
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('FormFooterMessage').prop('message')).toBe('Field5 in FieldColl must be unique.');
+          done();
+        }, 0);
+      });
+    });
+
+    describe('SQL tab', ()=>{
+      it('no changes', (done)=>{
+        ctrl.find('ForwardRef(Tab)[label="SQL"]').find('button').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          /* Dont show error message */
+          expect(ctrl.find('FormFooterMessage').prop('message')).toBe('');
+          expect(ctrl.find('CodeMirror').prop('value')).toBe('-- No updates.');
+          done();
+        }, 0);
+      });
+
+      it('data invalid', (done)=>{
+        ctrl.find('MappedFormControl[id="field2"]').find('input').simulate('change', {target: {value: '2'}});
+        ctrl.find('ForwardRef(Tab)[label="SQL"]').find('button').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('CodeMirror').prop('value')).toBe('-- Definition incomplete.');
+          done();
+        }, 0);
+      });
+
+      it('valid data', (done)=>{
+        simulateValidData();
+        ctrl.find('ForwardRef(Tab)[label="SQL"]').find('button').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('CodeMirror').prop('value')).toBe('select 1;');
+          done();
+        }, 0);
+      });
+    });
+
+    it('onSave click', (done)=>{
+      simulateValidData();
+      let alertSpy = spyOn(legacyConnector.pgAlertify(), 'alert');
+      onSave.calls.reset();
+      ctrl.find('PrimaryButton[data-test="Save"]').simulate('click');
+      setTimeout(()=>{
+        expect(onSave.calls.argsFor(0)[0]).toBe(true);
+        expect(onSave.calls.argsFor(0)[1]).toEqual({
+          id: undefined,
+          field1: 'val1',
+          field2: 2,
+          field5: 'val5',
+          fieldcoll: [
+            {field3: null, field4: null, field5:  'rval51'},
+            {field3: null, field4: null, field5:  'rval52'},
+          ]
+        });
+        expect(alertSpy).toHaveBeenCalledWith('Warning', 'some inform text');
+        done();
+      }, 0);
+    });
+
+    describe('onReset', ()=>{
+      it('with confirm check and yes click', (done)=>{
+        simulateValidData();
+        onDataChange.calls.reset();
+        let confirmSpy = spyOn(legacyConnector.pgAlertify(), 'confirm').and.callThrough();
+        ctrl.find('DefaultButton[data-test="Reset"]').simulate('click');
+        /* Press OK */
+        confirmSpy.calls.argsFor(0)[2]();
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('DefaultButton[data-test="Reset"]').prop('disabled')).toBeTrue();
+          expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeTrue();
+          expect(onDataChange).toHaveBeenCalledWith(false);
+          done();
+        }, 0);
+      });
+
+      it('with confirm check and cancel click', (done)=>{
+        simulateValidData();
+        let confirmSpy = spyOn(legacyConnector.pgAlertify(), 'confirm').and.callThrough();
+        ctrl.find('DefaultButton[data-test="Reset"]').simulate('click');
+        /* Press cancel */
+        confirmSpy.calls.argsFor(0)[3]();
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('DefaultButton[data-test="Reset"]').prop('disabled')).toBeFalse();
+          expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeFalse();
+          done();
+        }, 0);
+      });
+
+
+      it('no confirm check', (done)=>{
+        ctrlMount({
+          confirmOnCloseReset: false,
+        });
+        ctrl.update();
+        simulateValidData();
+        onDataChange.calls.reset();
+        let confirmSpy = spyOn(legacyConnector.pgAlertify(), 'confirm').and.callThrough();
+        ctrl.find('DefaultButton[data-test="Reset"]').simulate('click');
+        setTimeout(()=>{
+          ctrl.update();
+          expect(confirmSpy).not.toHaveBeenCalled();
+          expect(ctrl.find('DefaultButton[data-test="Reset"]').prop('disabled')).toBeTrue();
+          expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeTrue();
+          expect(onDataChange).toHaveBeenCalledWith(false);
+          done();
+        }, 0);
+      });
+    });
+
+    it('onHelp SQL', (done)=>{
+      ctrl.find('PgIconButton[data-test="sql-help"]').find('button').simulate('click');
+      setTimeout(()=>{
+        ctrl.update();
+        expect(onHelp).toHaveBeenCalledWith(true, true);
+        done();
+      }, 0);
+    });
+
+    it('onHelp Dialog', (done)=>{
+      ctrl.find('PgIconButton[data-test="dialog-help"]').find('button').simulate('click');
+      setTimeout(()=>{
+        ctrl.update();
+        expect(onHelp).toHaveBeenCalledWith(false, true);
+        done();
+      }, 0);
+    });
+
+    describe('edit mode', ()=>{
+      let simulateChanges = ()=>{
+        ctrl.find('MappedFormControl[id="field1"]').find('input').simulate('change', {target: {value: 'val1'}});
+        ctrl.find('MappedFormControl[id="field5"]').find('textarea').simulate('change', {target: {value: 'val5'}});
+
+        /* Add a row */
+        ctrl.find('DataGridView').find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+        ctrl.find('MappedCellControl[id="field5"]').at(2).find('input').simulate('change', {target: {value: 'rval53'}});
+
+        /* Remove the 1st row */
+        ctrl.find('DataTableRow').find('PgIconButton[data-test="delete-row"]').at(0).find('button').simulate('click');
+
+        /* Edit the 2nd row which is first now*/
+        ctrl.find('MappedCellControl[id="field5"]').at(0).find('input').simulate('change', {target: {value: 'rvalnew'}});
+
+      };
+      beforeEach(()=>{
+        ctrlMount({
+          getInitData: getInitData,
+          viewHelperProps: {
+            mode: 'edit',
+          }
+        });
+
+        /* Press OK */
+        spyOn(legacyConnector, 'confirmDeleteRow').and.callFake((yesFn)=>{
+          yesFn();
+        });
+      });
+      it('init', (done)=>{
+        setTimeout(()=>{
+          ctrl.update();
+          expect(ctrl.find('MappedFormControl').length).toBe(4);
+          expect(ctrl.find('FormView').length).toBe(2);
+          expect(ctrl.find('DataGridView').length).toBe(1);
+          expect(ctrl.find('DataTableRow').length).toBe(2);
+          expect(ctrl.find('DefaultButton[data-test="Reset"]').prop('disabled')).toBeTrue();
+          expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeTrue();
+
+          done();
+        }, 0);
+      });
+
+      it('onSave after change', (done)=>{
+        setTimeout(()=>{
+          ctrl.update();
+          simulateChanges();
+
+          let alertSpy = spyOn(legacyConnector.pgAlertify(), 'alert');
+          onSave.calls.reset();
+          ctrl.find('PrimaryButton[data-test="Save"]').simulate('click');
+          setTimeout(()=>{
+            ctrl.update();
+            expect(onSave.calls.argsFor(0)[0]).toBe(false);
+            expect(onSave.calls.argsFor(0)[1]).toEqual({
+              id: 1,
+              field1: 'val1',
+              field5: 'val5',
+              fieldcoll: {
+                added: [
+                  { field3: null, field4: null, field5: 'rval53'}
+                ],
+                changed: [
+                  { field3: 2, field4: 'field4val2', field5: 'rvalnew'}
+                ],
+                deleted: [
+                  { field3: 1, field4: 'field4val1', field5: 'field5val1'}
+                ]
+              }
+            });
+            expect(alertSpy).toHaveBeenCalledWith('Warning', 'some inform text');
+            done();
+          }, 0);
+        }, 0);
+      });
+
+      it('onReset after change', (done)=>{
+        setTimeout(()=>{
+          ctrl.update();
+          simulateChanges();
+          onDataChange.calls.reset();
+          let confirmSpy = spyOn(legacyConnector.pgAlertify(), 'confirm').and.callThrough();
+          ctrl.find('DefaultButton[data-test="Reset"]').simulate('click');
+          /* Press OK */
+          confirmSpy.calls.argsFor(0)[2]();
+          setTimeout(()=>{
+            ctrl.update();
+            expect(ctrl.find('DefaultButton[data-test="Reset"]').prop('disabled')).toBeTrue();
+            expect(ctrl.find('PrimaryButton[data-test="Save"]').prop('disabled')).toBeTrue();
+            expect(onDataChange).toHaveBeenCalledWith(false);
+            done();
+          }, 0);
+        }, 0);
+      });
+    });
+  });
+
+  describe('all types', ()=>{
+    let ctrl;
+    beforeEach(()=>{
+      ctrl?.unmount();
+      ctrl = mount(<SchemaView
+        formType='dialog'
+        schema={getSchemaAllTypes()}
+        viewHelperProps={{
+          mode: 'create',
+        }}
+        onSave={()=>{}}
+        onClose={()=>{}}
+        onHelp={()=>{}}
+        onEdit={()=>{}}
+        onDataChange={()=>{}}
+        confirmOnCloseReset={false}
+        hasSQL={true}
+        getSQLValue={()=>'select 1;'}
+        disableSqlHelp={false}
+      />);
+    });
+
+    it('init', ()=>{
+      /* Add a row */
+      ctrl.find('DataGridView').find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+      setTimeout(()=>{
+        ctrl.update();
+      }, 0);
+    });
+  });
+
+  describe('SchemaPropertiesView', ()=>{
+    let onEdit = jasmine.createSpy('onEdit'),
+      onHelp = jasmine.createSpy('onHelp'),
+      ctrl = null;
+
+    beforeEach(()=>{
+      ctrl = mount(<SchemaView
+        formType='tab'
+        schema={getSchema()}
+        getInitData={getInitData}
+        viewHelperProps={{
+          mode: 'properties',
+        }}
+        onHelp={onHelp}
+        disableSqlHelp={false}
+        onEdit={onEdit}
+      />);
+    });
+
+    it('init', (done)=>{
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find('MappedFormControl').length).toBe(6);
+        expect(ctrl.find('ForwardRef(Accordion)').length).toBe(2);
+        done();
+      }, 0);
+    });
+
+    it('onHelp', (done)=>{
+      ctrl.find('PgIconButton[data-test="help"]').find('button').simulate('click');
+      setTimeout(()=>{
+        ctrl.update();
+        expect(onHelp).toHaveBeenCalled();
+        done();
+      }, 0);
+    });
+
+    it('onEdit', (done)=>{
+      ctrl.find('PgIconButton[data-test="edit"]').find('button').simulate('click');
+      setTimeout(()=>{
+        ctrl.update();
+        expect(onEdit).toHaveBeenCalled();
+        done();
+      }, 0);
+    });
+  });
+});
diff --git a/web/regression/javascript/SchemaView/TestSchema.ui.js b/web/regression/javascript/SchemaView/TestSchema.ui.js
new file mode 100644
index 000000000..bfa4db6ab
--- /dev/null
+++ b/web/regression/javascript/SchemaView/TestSchema.ui.js
@@ -0,0 +1,181 @@
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+
+class TestSubSchema extends BaseUISchema {
+  constructor() {
+    super({
+      field3: null,
+      field4: null,
+    });
+
+    this.keys = ['field3', 'field4', 'field5'];
+  }
+
+  get baseFields() {
+    return [
+      {
+        id: 'field3', label: 'Field3', type: 'int', group: null,
+        mode: ['properties'], visible: true, cell: 'int'
+      },
+      {
+        id: 'field4', label: 'Field4', type: 'select', group: null,
+        cell: ()=>({cell: 'select', options: []}),
+        mode: ['properties', 'edit', 'create'],
+        disabled: (state)=>this.isNew(state), deps: ['field5'],
+        depChange: ()=>{}, optionsLoaded: ()=>{},
+      },
+      {
+        id: 'field5', label: 'Field5', type: 'multiline', group: null,
+        cell: 'text', mode: ['properties', 'edit', 'create'], disabled: false,
+        noEmpty: true, minWidth: '50%',
+      },
+      {
+        id: 'fieldskip', label: 'FieldSkip', type: 'text', group: null,
+        cell: 'text', mode: ['properties', 'edit', 'create'], disabled: false,
+        noEmpty: true,
+      }
+    ];
+  }
+}
+
+export class TestSchema extends BaseUISchema {
+  constructor() {
+    super({
+      id: undefined,
+      field1: null,
+      field2: null,
+      fieldcoll: null,
+    });
+
+    this.informText = 'some inform text';
+  }
+
+  get baseFields() {
+    return [
+      {
+        id: 'id', label: 'ID', type: 'int', group: null,
+        mode: ['properties'],
+      },{
+        id: 'field1', label: 'Field1', type: 'text', group: null,
+        mode: ['properties', 'edit', 'create'], disabled: false,
+        noEmpty: true, visible: true,
+      },{
+        id: 'field2', label: 'Field2', type: ()=>({
+          type: 'int', min: 0, max: 255,
+        }), group: null, mode: ['properties', 'create'],
+        disabled: ()=>false, visible: ()=>true, deps: ['field1'],
+        depChange: ()=>({})
+      },{
+        id: 'fieldcoll', label: 'FieldColl', type: 'collection', group: null,
+        mode: ['edit', 'create'], schema: new TestSubSchema(),
+        canAdd: true, canEdit: true, canDelete: true, uniqueCol: ['field5'],
+        customDeleteTitle: 'Custom delete title',
+        customDeleteMsg: 'Custom delete message',
+      },{
+        type: 'nested-tab', group: null,
+        mode: ['edit', 'create'], schema: new TestSubSchema(),
+      },{
+        id: 'field6', label: 'Field6', type: 'numeric', group: null,
+        mode: ['properties', 'create'],
+        disabled: false,
+      },{
+        id: 'field7', label: 'Field7', type: 'text', group: 'Advanced',
+        mode: ['properties'],
+      },{
+        id: 'fieldinvis', label: 'fieldinvis', type: 'text', group: 'Advanced',
+        mode: ['properties', 'edit', 'create'], visible: false,
+      },
+    ];
+  }
+
+  validate() {
+    return false;
+  }
+}
+
+
+class TestSubSchemaAllTypes extends BaseUISchema {
+  constructor() {
+    super();
+  }
+
+  get baseFields() {
+    return [
+      {
+        id: 'int', label: 'int', type: 'int', group: null,
+        mode: ['create'], cell: 'int'
+      },{
+        id: 'text', label: 'text', type: 'text', group: null,
+        mode: ['create'], cell: 'text'
+      },{
+        id: 'password', label: 'password', type: 'password', group: null,
+        mode: ['create'], cell: 'password',
+      },{
+        id: 'select', label: 'select', type: 'select', group: null,
+        mode: ['create'], options: [], cell: 'select'
+      },{
+        id: 'switch', label: 'switch', type: 'switch', group: null,
+        mode: ['create'], options: [], cell: 'switch'
+      },{
+        id: 'privilege', label: 'privilege', type: 'privilege', group: null,
+        mode: ['create'], options: [], cell: 'privilege',
+      }
+    ];
+  }
+}
+
+
+export class TestSchemaAllTypes extends BaseUISchema {
+  constructor() {
+    super();
+  }
+
+  get baseFields() {
+    return [
+      {
+        id: 'int', label: 'int', type: 'int', group: null,
+        mode: ['create'],
+      },{
+        id: 'text', label: 'text', type: 'text', group: null,
+        mode: ['create'],
+      },{
+        id: 'multiline', label: 'multiline', type: 'multiline', group: null,
+        mode: ['create'],
+      },{
+        id: 'password', label: 'password', type: 'password', group: null,
+        mode: ['create'],
+      },{
+        id: 'select', label: 'select', type: 'select', group: null,
+        mode: ['create'], options: [],
+      },{
+        id: 'switch', label: 'switch', type: 'switch', group: null,
+        mode: ['create'], options: [],
+      },{
+        id: 'checkbox', label: 'checkbox', type: 'checkbox', group: null,
+        mode: ['create'],
+      },{
+        id: 'toggle', label: 'toggle', type: 'toggle', group: null,
+        mode: ['create'],
+      },{
+        id: 'color', label: 'color', type: 'color', group: null,
+        mode: ['create'],
+      },{
+        id: 'file', label: 'file', type: 'file', group: null,
+        mode: ['create'],
+      },{
+        id: 'sql', label: 'sql', type: 'sql', group: null,
+        mode: ['create'],
+      },{
+        id: 'function', label: 'function', type: ()=>'text',
+        group: null,mode: ['create'],
+      },{
+        id: 'collection', label: 'collection', type: 'collection', group: null,
+        mode: ['edit', 'create'], schema: new TestSubSchemaAllTypes(),
+        canAdd: true, canEdit: true, canDelete: true,
+      }
+    ];
+  }
+
+  validate() {
+    return false;
+  }
+}
diff --git a/web/regression/javascript/components/Buttons.spec.js b/web/regression/javascript/components/Buttons.spec.js
new file mode 100644
index 000000000..155a0df33
--- /dev/null
+++ b/web/regression/javascript/components/Buttons.spec.js
@@ -0,0 +1,47 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { withTheme } from '../fake_theme';
+import { createMount } from '@material-ui/core/test-utils';
+import InfoIcon from '@material-ui/icons/InfoRounded';
+
+import {PrimaryButton, DefaultButton, PgIconButton} from 'sources/components/Buttons';
+
+/* MUI Components need to be wrapped in Theme for theme vars */
+describe('components Buttons', ()=>{
+  let mount;
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+  });
+
+  it('PrimaryButton', ()=>{
+    let ThemedBtn = withTheme(PrimaryButton);
+    let btn = mount(<ThemedBtn>Test</ThemedBtn>);
+    expect(btn.getDOMNode().classList.contains('MuiButton-containedPrimary')).toBe(true);
+  });
+
+  it('DefaultButton', ()=>{
+    let ThemedBtn = withTheme(DefaultButton);
+    let btn = mount(<ThemedBtn className="testClass">Test</ThemedBtn>);
+    expect(btn.getDOMNode().classList.contains('MuiButton-outlined')).toBe(true);
+    expect(btn.getDOMNode().classList.contains('testClass')).toBe(true);
+  });
+
+  it('PgIconButton', ()=>{
+    let Icon = <InfoIcon />;
+    let ThemedBtn = withTheme(PgIconButton);
+    let btn = mount(<ThemedBtn title="The icon button" icon={Icon} className="testClass"></ThemedBtn>);
+    expect(btn.find(InfoIcon)).not.toBe(null);
+  });
+});
diff --git a/web/regression/javascript/components/CodeMirror.spec.js b/web/regression/javascript/components/CodeMirror.spec.js
new file mode 100644
index 000000000..b503cd254
--- /dev/null
+++ b/web/regression/javascript/components/CodeMirror.spec.js
@@ -0,0 +1,37 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import {default as OrigCodeMirror} from 'bundled_codemirror';
+
+import CodeMirror from 'sources/components/CodeMirror';
+import { mount } from 'enzyme';
+
+describe('CodeMirror', ()=>{
+  let cmInstance, options={
+      lineNumbers: true,
+      mode: 'text/x-pgsql',
+    }, cmObj = jasmine.createSpyObj('cmObj', {'setValue': ()=>{}, 'refresh': ()=>{}});
+  beforeEach(()=>{
+    jasmineEnzyme();
+    spyOn(OrigCodeMirror, 'fromTextArea').and.returnValue(cmObj);
+    cmInstance = mount(
+      <CodeMirror
+        value={'Init text'}
+        options={options}
+        className="testClass"
+      />);
+  });
+
+  it('init', ()=>{
+    /* textarea ref passed to fromTextArea */
+    expect(OrigCodeMirror.fromTextArea).toHaveBeenCalledWith(cmInstance.getDOMNode(), options);
+    expect(cmObj.setValue).toHaveBeenCalledWith('Init text');
+    expect(cmObj.refresh).toHaveBeenCalled();
+  });
+
+  it('change value', ()=>{
+    cmInstance.setProps({value: 'the new text'});
+    expect(cmObj.setValue).toHaveBeenCalledWith('the new text');
+    expect(cmObj.refresh).toHaveBeenCalled();
+  });
+});
diff --git a/web/regression/javascript/components/FormComponents.spec.js b/web/regression/javascript/components/FormComponents.spec.js
new file mode 100644
index 000000000..e2359ff45
--- /dev/null
+++ b/web/regression/javascript/components/FormComponents.spec.js
@@ -0,0 +1,597 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { withTheme } from '../fake_theme';
+import { createMount } from '@material-ui/core/test-utils';
+import { OutlinedInput, FormHelperText, IconButton, FormControlLabel,
+  Switch, Checkbox, Button, InputLabel } from '@material-ui/core';
+import Select from 'react-select';
+import CreatableSelect from 'react-select/creatable';
+import CheckRoundedIcon from '@material-ui/icons/CheckRounded';
+import ReportProblemIcon from '@material-ui/icons/ReportProblemRounded';
+import InfoIcon from '@material-ui/icons/InfoRounded';
+import CloseIcon from '@material-ui/icons/CloseRounded';
+import CheckIcon from '@material-ui/icons/CheckCircleOutlineRounded';
+
+
+import {FormInputText, FormInputFileSelect, FormInputSQL,
+  FormInputSwitch, FormInputCheckbox, FormInputToggle, FormInputSelect,
+  FormInputColor,
+  FormFooterMessage,
+  MESSAGE_TYPE} from '../../../pgadmin/static/js/components/FormComponents';
+import * as legacyConnector from '../../../pgadmin/static/js/helpers/legacyConnector';
+import CodeMirror from '../../../pgadmin/static/js/components/CodeMirror';
+import { ToggleButton } from '@material-ui/lab';
+import { DefaultButton, PrimaryButton } from '../../../pgadmin/static/js/components/Buttons';
+
+/* MUI Components need to be wrapped in Theme for theme vars */
+describe('FormComponents', ()=>{
+  let mount;
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+  });
+
+  describe('FormInputText', ()=>{
+    let ThemedFormInputText = withTheme(FormInputText), ctrl;
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormInputText
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputText */
+          readonly={false}
+          disabled={false}
+          maxlength={50}
+          value={'thevalue'}
+          controlProps={{
+            extraprop: 'test'
+          }}
+        />);
+    });
+
+    it('init', ()=>{
+      expect(ctrl.find(InputLabel).text()).toBe('First');
+      expect(ctrl.find(OutlinedInput).prop('extraprop')).toEqual('test');
+      expect( ctrl.find(OutlinedInput).prop('inputProps')).toEqual(jasmine.objectContaining({
+        maxLength: 50,
+      }));
+      expect(ctrl.find(OutlinedInput).prop('readOnly')).toBe(false);
+      expect(ctrl.find(OutlinedInput).prop('disabled')).toBe(false);
+      expect(ctrl.find(OutlinedInput).prop('value')).toBe('thevalue');
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+    });
+
+    it('props change', ()=>{
+      let onChange = ()=>{};
+      ctrl.setProps({
+        readonly: true,
+        disabled: true,
+        value: 'new value',
+        onChange: onChange,
+      });
+
+      expect(ctrl.find(OutlinedInput).prop('readOnly')).toBe(true);
+      expect(ctrl.find(OutlinedInput).prop('disabled')).toBe(true);
+      expect(ctrl.find(OutlinedInput).prop('value')).toBe('new value');
+      expect(ctrl.find(OutlinedInput).prop('onChange')).toBe(onChange);
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+      let inputProps = ctrl.find(OutlinedInput).prop('inputProps');
+      expect(inputProps).toEqual(jasmine.objectContaining({
+        id: 'inpCid',
+        'aria-describedby': 'hinpCid',
+      }));
+    });
+  });
+
+  describe('FormInputFileSelect', ()=>{
+    let ThemedFormInputFileSelect = withTheme(FormInputFileSelect), ctrl;
+
+    beforeEach(()=>{
+      spyOn(legacyConnector, 'showFileDialog').and.callFake((controlProps, onFileSelect)=>{
+        onFileSelect('selected/file');
+      });
+      ctrl = mount(
+        <ThemedFormInputFileSelect
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputText */
+          readonly={false}
+          disabled={false}
+          value={'thevalue'}
+          controlProps={{
+            dialogType: 'select_file', supportedTypes: ['*'],
+          }}
+        />);
+    });
+
+    it('init', ()=>{
+      expect(ctrl.find(InputLabel).text()).toBe('First');
+      expect(ctrl.find(OutlinedInput).prop('readOnly')).toBe(false);
+      expect(ctrl.find(OutlinedInput).prop('disabled')).toBe(false);
+      expect(ctrl.find(OutlinedInput).prop('value')).toBe('thevalue');
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+    });
+
+    it('props change', ()=>{
+      ctrl.setProps({
+        readonly: true,
+        disabled: true,
+        value: 'new value',
+      });
+
+      expect(ctrl.find(OutlinedInput).prop('readOnly')).toBe(true);
+      expect(ctrl.find(OutlinedInput).prop('disabled')).toBe(true);
+      expect(ctrl.find(OutlinedInput).prop('value')).toBe('new value');
+      expect(ctrl.find(IconButton).prop('disabled')).toBe(true);
+    });
+
+
+    it('file select', (done)=>{
+      let onChange = jasmine.createSpy();
+      ctrl.setProps({
+        onChange: onChange,
+      });
+      ctrl.find(IconButton).simulate('click');
+      setTimeout(()=>{
+        expect(onChange).toHaveBeenCalledWith('selected/file');
+        done();
+      }, 0);
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+      let inputProps = ctrl.find(OutlinedInput).prop('inputProps');
+      expect(inputProps).toEqual(jasmine.objectContaining({
+        id: 'inpCid',
+        'aria-describedby': 'hinpCid',
+      }));
+    });
+  });
+
+  describe('FormInputSQL', ()=>{
+    let ThemedFormInputSQL = withTheme(FormInputSQL), ctrl;
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormInputSQL
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputSQL */
+          value={'thevalue'}
+          controlProps={{
+            op1: 'test'
+          }}
+        />);
+    });
+
+    it('init', ()=>{
+      expect(ctrl.find(InputLabel).text()).toBe('First');
+      expect(ctrl.find(CodeMirror).prop('value')).toEqual('thevalue');
+      expect(ctrl.find(CodeMirror).prop('options')).toEqual(jasmine.objectContaining({
+        op1: 'test'
+      }));
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+    });
+  });
+
+  describe('FormInputSwitch', ()=>{
+    let ThemedFormInputSwitch = withTheme(FormInputSwitch), ctrl, onChange=()=>{return 1;};
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormInputSwitch
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputSwitch */
+          readonly={false}
+          value={false}
+          onChange={onChange}
+        />);
+    });
+
+    it('init', ()=>{
+      expect(ctrl.find(InputLabel).text()).toBe('First');
+      expect(ctrl.find(Switch).prop('checked')).toBe(false);
+      expect(ctrl.find(Switch).prop('onChange')).toBe(onChange);
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+    });
+
+    it('props change', ()=>{
+      ctrl.setProps({
+        readonly: true,
+        value: true,
+      });
+
+      expect(ctrl.find(Switch).prop('checked')).toBe(true);
+      expect(ctrl.find(Switch).prop('onChange')).not.toBe(onChange);
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+      expect(ctrl.find(Switch).prop('id')).toBe('inpCid');
+      let inputProps = ctrl.find(Switch).prop('inputProps');
+      expect(inputProps).toEqual(jasmine.objectContaining({
+        'aria-describedby': 'hinpCid',
+      }));
+    });
+  });
+
+  describe('FormInputCheckbox', ()=>{
+    let ThemedFormInputCheckbox = withTheme(FormInputCheckbox), ctrl, onChange=()=>{return 1;};
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormInputCheckbox
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputCheckbox */
+          disabled={false}
+          value={false}
+          onChange={onChange}
+          controlProps={{
+            label: 'Second'
+          }}
+        />);
+    });
+
+    it('init', ()=>{
+      expect(ctrl.find(InputLabel).text()).toBe('First');
+      expect(ctrl.find(FormControlLabel).prop('label')).toBe('Second');
+      expect(ctrl.find(Checkbox).prop('checked')).toBe(false);
+      expect(ctrl.find(Checkbox).prop('onChange')).toBe(onChange);
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+    });
+
+    it('props change', ()=>{
+      ctrl.setProps({
+        readonly: true,
+        value: true,
+      });
+
+      expect(ctrl.find(Checkbox).prop('checked')).toBe(true);
+      expect(ctrl.find(Checkbox).prop('onChange')).not.toBe(onChange);
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+      expect(ctrl.find(Checkbox).prop('id')).toBe('inpCid');
+      let inputProps = ctrl.find(Checkbox).prop('inputProps');
+      expect(inputProps).toEqual(jasmine.objectContaining({
+        'aria-describedby': 'hinpCid',
+      }));
+    });
+  });
+
+  describe('FormInputToggle', ()=>{
+    let ThemedFormInputToggle = withTheme(FormInputToggle), ctrl, onChange=()=>{return 1;};
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormInputToggle
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputToggle */
+          disabled={false}
+          options={[
+            {label: 'Op1', value: 1},
+            {label: 'Op2', value: 2},
+            {label: 'Op3', value: 3},
+          ]}
+          value={2}
+          onChange={onChange}
+        />);
+    });
+
+    it('init', ()=>{
+      expect(ctrl.find(InputLabel).text()).toBe('First');
+      expect(ctrl.find(ToggleButton).length).toBe(3);
+      expect(ctrl.find(PrimaryButton).length).toBe(1);
+      expect(ctrl.find(DefaultButton).length).toBe(2);
+      expect(ctrl.find(ToggleButton).at(1).prop('component')).toBe(PrimaryButton);
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+    });
+
+    it('props change', ()=>{
+      ctrl.setProps({
+        value: 1,
+      });
+      expect(ctrl.find(ToggleButton).at(0).prop('component')).toBe(PrimaryButton);
+      expect(ctrl.find(ToggleButton).at(0)
+        .find(CheckRoundedIcon)
+        .prop('style')).toEqual(jasmine.objectContaining({
+        visibility: 'visible'
+      }));
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+    });
+  });
+
+  describe('FormInputSelect', ()=>{
+    let ThemedFormInputSelect = withTheme(FormInputSelect), ctrl, onChange=jasmine.createSpy('onChange'),
+      ctrlMount = (props)=>{
+        ctrl?.unmount();
+        ctrl = mount(
+          <ThemedFormInputSelect
+            label="First"
+            className="someClass"
+            testcid="inpCid"
+            helpMessage="some help message"
+            /* InputSelect */
+            readonly={false}
+            disabled={false}
+            options={[
+              {label: 'Op1', value: 1},
+              {label: 'Op2', value: 2},
+              {label: 'Op3', value: 3},
+            ]}
+            value={1}
+            onChange={onChange}
+            {...props}
+          />);
+      };
+
+    beforeEach(()=>{
+      ctrlMount();
+    });
+
+    it('init', (done)=>{
+      expect(ctrl.find(Select).exists()).toBe(true);
+      expect(ctrl.find(CreatableSelect).exists()).toBe(false);
+      expect(ctrl.find(FormHelperText).text()).toBe('some help message');
+
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find(Select).props()).toEqual(jasmine.objectContaining({
+          isMulti: false,
+          value: {label: 'Op1', value: 1},
+          inputId: 'inpCid',
+          isSearchable: true,
+          isClearable: true,
+          isDisabled: false,
+        }));
+        done();
+      }, 0);
+    });
+
+    it('readonly disabled', (done)=>{
+      ctrl.setProps({
+        readonly: true,
+        disabled: true,
+      });
+
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find(Select).props()).toEqual(jasmine.objectContaining({
+          isSearchable: false,
+          isClearable: false,
+          isDisabled: true,
+          openMenuOnClick: false,
+        }));
+        done();
+      }, 0);
+    });
+
+    it('no-clear with multi', (done)=>{
+      ctrl.setProps({
+        controlProps: {
+          allowClear: false,
+          multiple: true,
+        },
+        value: [2, 3],
+      });
+
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find(Select).props()).toEqual(jasmine.objectContaining({
+          isMulti: true,
+          isClearable: false,
+          value: [{label: 'Op2', value: 2}, {label: 'Op3', value: 3}]
+        }));
+        done();
+      }, 0);
+    });
+
+    it('creatable with multi', (done)=>{
+      ctrl.setProps({
+        controlProps: {
+          creatable: true,
+          multiple: true,
+        },
+        value: ['val1', 'val2'],
+      });
+
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find(Select).exists()).toBe(false);
+        expect(ctrl.find(CreatableSelect).exists()).toBe(true);
+
+        expect(ctrl.find(CreatableSelect).props()).toEqual(jasmine.objectContaining({
+          isMulti: true,
+          value: [{label: 'val1', value: 'val1'}, {label: 'val2', value: 'val2'}]
+        }));
+        done();
+      }, 0);
+    });
+
+    it('promise options', (done)=>{
+      let optionsLoaded = jasmine.createSpy();
+      let res = [
+        {label: 'PrOp1', value: 1},
+        {label: 'PrOp2', value: 2},
+        {label: 'PrOp3', value: 3},
+      ];
+      /* For options change, remount needed */
+      ctrlMount({
+        options: ()=>Promise.resolve(res),
+        value: 3,
+        optionsLoaded: optionsLoaded,
+      });
+
+      setTimeout(()=>{
+        ctrl.update();
+        expect(optionsLoaded).toHaveBeenCalledWith(res, 3);
+        expect(ctrl.find(Select).props()).toEqual(jasmine.objectContaining({
+          value: {label: 'PrOp3', value: 3},
+        }));
+        done();
+      }, 0);
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+    });
+  });
+
+  describe('FormInputColor', ()=>{
+    let pickrObj = React.createRef();
+    let ThemedFormInputColor = withTheme(FormInputColor), ctrl, onChange=jasmine.createSpy('onChange');
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormInputColor
+          label="First"
+          className="someClass"
+          testcid="inpCid"
+          helpMessage="some help message"
+          /* InputColor */
+          disabled={false}
+          value="#f0f"
+          onChange={onChange}
+          currObj={(obj)=>pickrObj.current=obj}
+        />);
+    });
+
+    afterEach(()=>{
+      ctrl.unmount();
+    });
+
+    it('init', (done)=>{
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find(Button).prop('style')).toEqual(jasmine.objectContaining({
+          backgroundColor: '#f0f',
+        }));
+        done();
+      }, 0);
+    });
+
+    it('no color', (done)=>{
+      ctrl.setProps({
+        value: null,
+      });
+      setTimeout(()=>{
+        ctrl.update();
+        expect(ctrl.find(Button).prop('style')).toEqual(jasmine.objectContaining({
+          backgroundColor: null,
+        }));
+        expect(ctrl.find(Button).find(CloseIcon).exists()).toBe(true);
+        done();
+      }, 0);
+    });
+
+    it('other events', (done)=>{
+      pickrObj.current.applyColor(false);
+      setTimeout(()=>{
+        expect(onChange).toHaveBeenCalled();
+        done();
+      }, 0);
+    });
+
+    it('accessibility', ()=>{
+      expect(ctrl.find(InputLabel)).toHaveProp('htmlFor', 'inpCid');
+      expect(ctrl.find(FormHelperText)).toHaveProp('id', 'hinpCid');
+    });
+  });
+
+  describe('FormFooterMessage', ()=>{
+    let ThemedFormFooterMessage = withTheme(FormFooterMessage), ctrl, onClose=jasmine.createSpy('onClose');
+
+    beforeEach(()=>{
+      ctrl = mount(
+        <ThemedFormFooterMessage
+          type={MESSAGE_TYPE.SUCCESS}
+          message="Some message"
+          closable={false}
+          onClose={onClose}
+        />);
+    });
+
+    // if(close) {
+    //   TheIcon = CloseIcon;
+    // } else if(type === MESSAGE_TYPE.SUCCESS) {
+    //   TheIcon = CheckIcon;
+    // } else if(type === MESSAGE_TYPE.ERROR) {
+    //   TheIcon = ReportProblemIcon;
+    // } else if(type === MESSAGE_TYPE.INFO) {
+    //   TheIcon = InfoIcon;
+    // }
+
+    it('init', ()=>{
+      expect(ctrl.find(CheckIcon).exists()).toBeTrue();
+      expect(ctrl.text()).toBe('Some message');
+    });
+
+    it('change types', ()=>{
+      ctrl.setProps({
+        type: MESSAGE_TYPE.ERROR,
+      });
+      expect(ctrl.find(ReportProblemIcon).exists()).toBeTrue();
+
+      ctrl.setProps({
+        type: MESSAGE_TYPE.INFO,
+      });
+      expect(ctrl.find(InfoIcon).exists()).toBeTrue();
+    });
+
+    it('closable', ()=>{
+      ctrl.setProps({
+        closable: true,
+      });
+      expect(ctrl.find(CloseIcon).exists()).toBeTrue();
+      ctrl.find(IconButton).simulate('click');
+      expect(onClose).toHaveBeenCalled();
+    });
+
+    it('no message', ()=>{
+      ctrl.setProps({
+        message: '',
+      });
+      expect(ctrl.isEmptyRender()).toBeTrue();
+    });
+  });
+});
diff --git a/web/regression/javascript/components/Loader.spec.js b/web/regression/javascript/components/Loader.spec.js
new file mode 100644
index 000000000..53e4e14be
--- /dev/null
+++ b/web/regression/javascript/components/Loader.spec.js
@@ -0,0 +1,43 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import { withTheme } from '../fake_theme';
+import Loader from 'sources/components/Loader';
+
+
+/* MUI Components need to be wrapped in Theme for theme vars */
+describe('Loader', ()=>{
+  let mount, loaderInst, ThemedLoader;
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+    /* Loader need Mui Theme context as well */
+    ThemedLoader = withTheme(Loader);
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    loaderInst = mount(<ThemedLoader message={'loading'} />);
+  });
+
+  it('init', ()=>{
+    expect(loaderInst.find('.MuiTypography-root').text()).toBe('loading');
+  });
+
+  it('no message', ()=>{
+    loaderInst.setProps({message: ''});
+    expect(loaderInst.isEmptyRender()).toBeTruthy();
+  });
+
+  it('change message', ()=>{
+    loaderInst.setProps({message: 'test message'});
+    expect(loaderInst.find('.MuiTypography-root').text()).toBe('test message');
+  });
+});
diff --git a/web/regression/javascript/components/Privilege.spec.js b/web/regression/javascript/components/Privilege.spec.js
new file mode 100644
index 000000000..09de5ea0c
--- /dev/null
+++ b/web/regression/javascript/components/Privilege.spec.js
@@ -0,0 +1,195 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import Privilege from 'sources/components/Privilege';
+import { mount } from 'enzyme';
+
+describe('Privilege', ()=>{
+  let ctrl, onChange = jasmine.createSpy('onChange');
+  beforeEach(()=>{
+    jasmineEnzyme();
+    ctrl = mount(
+      <Privilege
+        value={[{
+          privilege_type: 'C',
+          privilege: true,
+          with_grant: false,
+        },{
+          privilege_type: 'a',
+          privilege: true,
+          with_grant: true,
+        }]}
+        controlProps={{
+          supportedPrivs: ['C', 'a', 'r']
+        }}
+        onChange={onChange}
+      />);
+  });
+
+  it('init', ()=>{
+    expect(ctrl.find('InputText').prop('value')).toBe('Ca*');
+    expect(ctrl.find('InputCheckbox[name="C"]').at(0).prop('value')).toBeTrue();
+    expect(ctrl.find('InputCheckbox[name="C"]').at(1).prop('value')).toBeFalse();
+
+    expect(ctrl.find('InputCheckbox[name="a"]').at(0).prop('value')).toBeTrue();
+    expect(ctrl.find('InputCheckbox[name="a"]').at(1).prop('value')).toBeTrue();
+
+    expect(ctrl.find('InputCheckbox[name="r"]').at(0).prop('value')).toBeFalse();
+    expect(ctrl.find('InputCheckbox[name="r"]').at(1).prop('value')).toBeFalse();
+  });
+
+  it('change prop value', ()=>{
+    ctrl.setProps({value: [{
+      privilege_type: 'C',
+      privilege: true,
+      with_grant: true,
+    },{
+      privilege_type: 'r',
+      privilege: true,
+      with_grant: false,
+    }]});
+
+    expect(ctrl.find('InputText').prop('value')).toBe('C*r');
+    expect(ctrl.find('InputCheckbox[name="C"]').at(0).prop('value')).toBeTrue();
+    expect(ctrl.find('InputCheckbox[name="C"]').at(1).prop('value')).toBeTrue();
+
+    expect(ctrl.find('InputCheckbox[name="a"]').at(0).prop('value')).toBeFalse();
+    expect(ctrl.find('InputCheckbox[name="a"]').at(1).prop('value')).toBeFalse();
+
+    expect(ctrl.find('InputCheckbox[name="r"]').at(0).prop('value')).toBeTrue();
+    expect(ctrl.find('InputCheckbox[name="r"]').at(1).prop('value')).toBeFalse();
+  });
+
+  it('no prop value', ()=>{
+    ctrl.setProps({value: null});
+
+    expect(ctrl.find('InputText').prop('value')).toBe('');
+    expect(ctrl.find('InputCheckbox[name="C"]').at(0).prop('value')).toBeFalse();
+    expect(ctrl.find('InputCheckbox[name="C"]').at(1).prop('value')).toBeFalse();
+
+    expect(ctrl.find('InputCheckbox[name="a"]').at(0).prop('value')).toBeFalse();
+    expect(ctrl.find('InputCheckbox[name="a"]').at(1).prop('value')).toBeFalse();
+
+    expect(ctrl.find('InputCheckbox[name="r"]').at(0).prop('value')).toBeFalse();
+    expect(ctrl.find('InputCheckbox[name="r"]').at(1).prop('value')).toBeFalse();
+  });
+
+  it('with grant disabled', ()=>{
+    expect(ctrl.find('InputCheckbox[name="all"]').at(1).prop('disabled')).toBeTrue();
+    expect(ctrl.find('InputCheckbox[name="r"]').at(1).prop('disabled')).toBeTrue();
+  });
+
+  it('on check click', (done)=>{
+    onChange.calls.reset();
+    ctrl.find('InputCheckbox[name="C"]').at(0).find('input').
+      simulate('change', {target: {checked: false, name: 'C'}});
+
+    setTimeout(()=>{
+      expect(onChange).toHaveBeenCalledWith([{
+        privilege_type: 'a',
+        privilege: true,
+        with_grant: true,
+      }]);
+      done();
+    }, 500);
+  });
+
+  it('on new check click', (done)=>{
+    onChange.calls.reset();
+    ctrl.find('InputCheckbox[name="r"]').at(0).find('input').
+      simulate('change', {target: {checked: true, name: 'r'}});
+
+    setTimeout(()=>{
+      expect(onChange).toHaveBeenCalledWith([{
+        privilege_type: 'C',
+        privilege: true,
+        with_grant: false,
+      },{
+        privilege_type: 'a',
+        privilege: true,
+        with_grant: true,
+      },{
+        privilege_type: 'r',
+        privilege: true,
+        with_grant: false,
+      }]);
+      done();
+    }, 500);
+  });
+
+  it('on check grant click', (done)=>{
+    onChange.calls.reset();
+    ctrl.find('InputCheckbox[name="C"]').at(1).find('input').
+      simulate('change', {target: {checked: true, name: 'C'}});
+
+    setTimeout(()=>{
+      expect(onChange).toHaveBeenCalledWith([{
+        privilege_type: 'C',
+        privilege: true,
+        with_grant: true,
+      },{
+        privilege_type: 'a',
+        privilege: true,
+        with_grant: true,
+      }]);
+      done();
+    }, 500);
+  });
+
+  it('on all click', (done)=>{
+    ctrl.find('InputCheckbox[name="all"]').at(0).find('input').simulate('change', {target: {checked: true}});
+
+    setTimeout(()=>{
+      expect(onChange).toHaveBeenCalledWith([{
+        privilege_type: 'C',
+        privilege: true,
+        with_grant: false,
+      },{
+        privilege_type: 'a',
+        privilege: true,
+        with_grant: true,
+      },{
+        privilege_type: 'r',
+        privilege: true,
+        with_grant: false,
+      }]);
+      done();
+    }, 500);
+  });
+
+  it('on all with grant click', (done)=>{
+    ctrl.setProps({
+      value: [{
+        privilege_type: 'C',
+        privilege: true,
+        with_grant: false,
+      },{
+        privilege_type: 'a',
+        privilege: true,
+        with_grant: true,
+      },{
+        privilege_type: 'r',
+        privilege: true,
+        with_grant: false,
+      }]
+    });
+    ctrl.find('InputCheckbox[name="all"]').at(1).find('input').simulate('change', {target: {checked: true}});
+
+    setTimeout(()=>{
+      expect(onChange).toHaveBeenCalledWith([{
+        privilege_type: 'C',
+        privilege: true,
+        with_grant: true,
+      },{
+        privilege_type: 'a',
+        privilege: true,
+        with_grant: true,
+      },{
+        privilege_type: 'r',
+        privilege: true,
+        with_grant: true,
+      }]);
+      done();
+    }, 500);
+  });
+});
diff --git a/web/regression/javascript/components/TabPanel.spec.js b/web/regression/javascript/components/TabPanel.spec.js
new file mode 100644
index 000000000..0433e9080
--- /dev/null
+++ b/web/regression/javascript/components/TabPanel.spec.js
@@ -0,0 +1,39 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import { withTheme } from '../fake_theme';
+import TabPanel from 'sources/components/TabPanel';
+
+
+/* MUI Components need to be wrapped in Theme for theme vars */
+describe('TabPanel', ()=>{
+  let mount, panelInst, ThemedPanel;
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+    /* Need Mui Theme context as well */
+    ThemedPanel = withTheme(TabPanel);
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    panelInst = mount(<ThemedPanel value={1} index={0}><h1>test</h1></ThemedPanel>);
+  });
+
+  it('init', ()=>{
+    expect(panelInst.getDOMNode().hidden).toBeTrue();
+    expect(panelInst.find('h1')).not.toBe(null);
+  });
+
+  it('tab select', ()=>{
+    panelInst.setProps({value: 0});
+    expect(panelInst.getDOMNode().hidden).toBeFalse();
+  });
+});
diff --git a/web/regression/javascript/fake_current_user.js b/web/regression/javascript/fake_current_user.js
index 6ad0b45b8..b32da5b91 100644
--- a/web/regression/javascript/fake_current_user.js
+++ b/web/regression/javascript/fake_current_user.js
@@ -9,6 +9,7 @@
 
 define(function () {
   return {
+    'id': 'pgadmin4@pgadmin.org',
     'current_auth_source': 'internal'
   };
 });
diff --git a/web/regression/javascript/fake_messages.js b/web/regression/javascript/fake_messages.js
new file mode 100644
index 000000000..f8888a838
--- /dev/null
+++ b/web/regression/javascript/fake_messages.js
@@ -0,0 +1,20 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+define('pgadmin.browser.messages',['sources/pgadmin'], function(pgAdmin) {
+  var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
+
+  if (pgBrowser.messages)
+    return pgBrowser.messages;
+
+  pgBrowser.messages = {
+    'CANNOT_BE_EMPTY': '\'%s\' cannot be empty.',
+  };
+  return pgBrowser;
+});
diff --git a/web/regression/javascript/fake_supported_servers.js b/web/regression/javascript/fake_supported_servers.js
new file mode 100644
index 000000000..d90f76f3a
--- /dev/null
+++ b/web/regression/javascript/fake_supported_servers.js
@@ -0,0 +1,17 @@
+//////////////////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2021, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////////////////
+
+define(function () {
+  return [
+    {label: 'Greenplum Database', value: 'gpdb'},
+    {label: 'EDB Advanced Server', value: 'ppas'},
+    {label: 'PostgreSQL', value: 'pg'},
+    {label: 'Unknown', value: ''},
+  ];
+});
diff --git a/web/regression/javascript/fake_theme.js b/web/regression/javascript/fake_theme.js
new file mode 100644
index 000000000..875e8fa3b
--- /dev/null
+++ b/web/regression/javascript/fake_theme.js
@@ -0,0 +1,9 @@
+import React from 'react';
+import Theme from 'sources/Theme';
+
+export function withTheme(WrappedComp) {
+  let NewComp = (props)=>{
+    return <Theme><WrappedComp {...props}/></Theme>;
+  };
+  return NewComp;
+}
diff --git a/web/regression/javascript/schema_ui_files/database.ui.spec.js b/web/regression/javascript/schema_ui_files/database.ui.spec.js
new file mode 100644
index 000000000..8a5f2157e
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/database.ui.spec.js
@@ -0,0 +1,111 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import _ from 'lodash';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import DatabaseSchema from '../../../pgadmin/browser/server_groups/servers/databases/static/js/database.ui';
+
+class MockSchema extends BaseUISchema {
+  get baseFields() {
+    return [];
+  }
+}
+
+describe('DatabaseSchema', ()=>{
+  let mount;
+  let schemaObj = new DatabaseSchema(
+    ()=>new MockSchema(),
+    ()=>new MockSchema(),
+    {
+      role: ()=>[],
+      encoding: ()=>[],
+      template: ()=>[],
+      spcname: ()=>[],
+      datcollate: ()=>[],
+      datctype: ()=>[],
+    },
+    {
+      datowner: 'postgres',
+    }
+  );
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+    pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+    pgAdmin.Browser.utils.support_ssh_tunnel = true;
+  });
+
+  it('create', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('edit', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('properties', ()=>{
+    mount(<SchemaView
+      formType='tab'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'properties',
+      }}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+    />);
+  });
+
+  it('schema_res depChange', ()=>{
+    let depChange = _.find(schemaObj.fields, (f)=>f.id=='schema_res').depChange;
+    depChange({schema_res: 'abc'});
+    expect(schemaObj.informText).toBe('Please refresh the Schemas node to make changes to the schema restriction take effect.');
+  });
+});
diff --git a/web/regression/javascript/schema_ui_files/privilege.ui.spec.js b/web/regression/javascript/schema_ui_files/privilege.ui.spec.js
new file mode 100644
index 000000000..fa2af635f
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/privilege.ui.spec.js
@@ -0,0 +1,121 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import PrivilegeRoleSchema, {getNodePrivilegeRoleSchema} from '../../../pgadmin/browser/server_groups/servers/static/js/privilege.ui';
+import {DefaultPrivSchema} from '../../../pgadmin/browser/server_groups/servers/databases/static/js/database.ui';
+import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
+
+describe('PrivilegeSchema', ()=>{
+  let mount;
+  let schemaObj = new PrivilegeRoleSchema(
+    ()=>[],
+    ()=>[],
+    null,
+    {server: {user: {name: 'postgres'}}},
+    ['X']
+  );
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+    pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+    pgAdmin.Browser.utils.support_ssh_tunnel = true;
+  });
+
+  it('create', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('edit', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('properties', ()=>{
+    mount(<SchemaView
+      formType='tab'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'properties',
+      }}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+    />);
+  });
+
+  it('validate', ()=>{
+    let state = {};
+    let setError = jasmine.createSpy('setError');
+
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('privileges', 'At least one privilege should be selected.');
+  });
+
+  it('DefaultPrivSchema', ()=>{
+    spyOn(nodeAjax, 'getNodeListByName').and.returnValue([]);
+    let defPrivObj = new DefaultPrivSchema((privileges)=>getNodePrivilegeRoleSchema({}, {server: {user: {name: 'postgres'}}}, {}, privileges));
+    let ctrl = mount(<SchemaView
+      formType='dialog'
+      schema={defPrivObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+    /* Make sure you hit every corner */
+    ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+  });
+});
diff --git a/web/regression/javascript/schema_ui_files/server.ui.spec.js b/web/regression/javascript/schema_ui_files/server.ui.spec.js
new file mode 100644
index 000000000..5b8319ec5
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/server.ui.spec.js
@@ -0,0 +1,130 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import ServerSchema from '../../../pgadmin/browser/server_groups/servers/static/js/server.ui';
+
+describe('ServerSchema', ()=>{
+  let mount;
+  let schemaObj = new ServerSchema([{
+    label: 'Servers', value: 1,
+  }], {
+    user_id: 'jasmine',
+  });
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+    pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+    pgAdmin.Browser.utils.support_ssh_tunnel = true;
+  });
+
+  it('create', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('edit', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('properties', ()=>{
+    mount(<SchemaView
+      formType='tab'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'properties',
+      }}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+    />);
+  });
+
+  it('validate', ()=>{
+    let state = {};
+    let setError = jasmine.createSpy('setError');
+
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('host', 'Either Host name, Address or Service must be specified.');
+
+    state.hostaddr = 'incorrectip';
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('hostaddr', 'Host address must be valid IPv4 or IPv6 address.');
+
+    state.host = '127.0.0.1';
+    state.hostaddr = null;
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('username', 'Username must be specified.');
+
+    state.username = 'postgres';
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('port', 'Port must be specified.');
+
+    state.port = 5432;
+    state.use_ssh_tunnel = true;
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('tunnel_host', 'SSH Tunnel host must be specified.');
+
+    state.service = 'pgservice';
+    state.tunnel_host = 'localhost';
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('tunnel_port', 'SSH Tunnel port must be specified.');
+
+    state.tunnel_port = 8080;
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('tunnel_username', 'SSH Tunnel username must be specified.');
+
+    state.tunnel_username = 'jasmine';
+    state.tunnel_authentication = true;
+    schemaObj.validate(state, setError);
+    expect(setError).toHaveBeenCalledWith('tunnel_identity_file', 'SSH Tunnel identity file must be specified.');
+
+    state.tunnel_identity_file = '/file/path/xyz.pem';
+    expect(schemaObj.validate(state, setError)).toBeFalse();
+  });
+});
diff --git a/web/regression/javascript/schema_ui_files/server_group.ui.spec.js b/web/regression/javascript/schema_ui_files/server_group.ui.spec.js
new file mode 100644
index 000000000..3c1e977eb
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/server_group.ui.spec.js
@@ -0,0 +1,81 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import ServerGroupSchema from '../../../pgadmin/browser/server_groups/static/js/server_group.ui';
+
+describe('ServerGroupSchema', ()=>{
+  let mount;
+  let schemaObj = new ServerGroupSchema();
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+  });
+
+  it('create', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('edit', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('properties', ()=>{
+    mount(<SchemaView
+      formType='tab'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'properties',
+      }}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+    />);
+  });
+});
diff --git a/web/regression/javascript/schema_ui_files/variable.ui.spec.js b/web/regression/javascript/schema_ui_files/variable.ui.spec.js
new file mode 100644
index 000000000..71dc910bb
--- /dev/null
+++ b/web/regression/javascript/schema_ui_files/variable.ui.spec.js
@@ -0,0 +1,146 @@
+import jasmineEnzyme from 'jasmine-enzyme';
+import React from 'react';
+import '../helper/enzyme.helper';
+import { createMount } from '@material-ui/core/test-utils';
+import pgAdmin from 'sources/pgadmin';
+import {messages} from '../fake_messages';
+import SchemaView from '../../../pgadmin/static/js/SchemaView';
+import BaseUISchema from 'sources/SchemaView/base_schema.ui';
+import VariableSchema, {getNodeVariableSchema} from '../../../pgadmin/browser/server_groups/servers/static/js/variable.ui';
+import * as nodeAjax from '../../../pgadmin/browser/static/js/node_ajax';
+
+
+/* Used to check collection mode */
+class MockSchema extends BaseUISchema {
+  constructor(getVariableSchema) {
+    super();
+    this.getVariableSchema = getVariableSchema;
+  }
+
+  get baseFields() {
+    return [{
+      id: 'variables', label: '', type: 'collection',
+      schema: this.getVariableSchema(),
+      editable: false,
+      group: 'Parameters', mode: ['edit', 'create'],
+      canAdd: true, canEdit: false, canDelete: true, hasRole: true,
+      node: 'role',
+    }];
+  }
+}
+
+describe('PrivilegeSchema', ()=>{
+  let mount;
+  let schemaObj = new VariableSchema(
+    ()=>[],
+    ()=>[],
+    ()=>[],
+    null
+  );
+  let getInitData = ()=>Promise.resolve({});
+
+  /* Use createMount so that material ui components gets the required context */
+  /* https://material-ui.com/guides/testing/#api */
+  beforeAll(()=>{
+    mount = createMount();
+  });
+
+  afterAll(() => {
+    mount.cleanUp();
+  });
+
+  beforeEach(()=>{
+    jasmineEnzyme();
+    /* messages used by validators */
+    pgAdmin.Browser = pgAdmin.Browser || {};
+    pgAdmin.Browser.messages = pgAdmin.Browser.messages || messages;
+    pgAdmin.Browser.utils = pgAdmin.Browser.utils || {};
+    pgAdmin.Browser.utils.support_ssh_tunnel = true;
+  });
+
+  it('create', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('edit', ()=>{
+    mount(<SchemaView
+      formType='dialog'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+  });
+
+  it('properties', ()=>{
+    mount(<SchemaView
+      formType='tab'
+      schema={schemaObj}
+      getInitData={getInitData}
+      viewHelperProps={{
+        mode: 'properties',
+      }}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+    />);
+  });
+
+  it('getValueFieldProps', ()=>{
+    expect(schemaObj.getValueFieldProps({vartype: 'bool'})).toBe('switch');
+    expect(schemaObj.getValueFieldProps({vartype: 'enum', enumvals: []})).toEqual(jasmine.objectContaining({
+      cell: 'select',
+    }));
+    expect(schemaObj.getValueFieldProps({vartype: 'integer'})).toBe('int');
+    expect(schemaObj.getValueFieldProps({vartype: 'real'})).toBe('number');
+    expect(schemaObj.getValueFieldProps({vartype: 'string'})).toBe('text');
+    expect(schemaObj.getValueFieldProps({})).toBe('');
+  });
+
+  it('variable collection', ()=>{
+    spyOn(nodeAjax, 'getNodeAjaxOptions').and.returnValue([]);
+    spyOn(nodeAjax, 'getNodeListByName').and.returnValue([]);
+    let varCollObj = new MockSchema(()=>getNodeVariableSchema({}, {server: {user: {name: 'postgres'}}}, {}, true, true));
+    let ctrl = mount(<SchemaView
+      formType='dialog'
+      schema={varCollObj}
+      viewHelperProps={{
+        mode: 'create',
+      }}
+      onSave={()=>{}}
+      onClose={()=>{}}
+      onHelp={()=>{}}
+      onEdit={()=>{}}
+      onDataChange={()=>{}}
+      confirmOnCloseReset={false}
+      hasSQL={false}
+      disableSqlHelp={false}
+    />);
+    /* Make sure you hit every corner */
+    ctrl.find('DataGridView').at(0).find('PgIconButton[data-test="add-row"]').find('button').simulate('click');
+  });
+
+
+});
diff --git a/web/webpack.shim.js b/web/webpack.shim.js
index e0b28091c..73c17ecb8 100644
--- a/web/webpack.shim.js
+++ b/web/webpack.shim.js
@@ -151,7 +151,9 @@ var webpackShimConfig = {
     'dagre':  path.join(__dirname, 'node_modules/dagre'),
     'graphlib': path.join(__dirname, 'node_modules/graphlib'),
     'react': path.join(__dirname, 'node_modules/react'),
+    'react-dom': path.join(__dirname, 'node_modules/react-dom'),
     'stylis': path.join(__dirname, 'node_modules/stylis'),
+    'popper.js': path.join(__dirname, 'node_modules/popper.js'),
 
     // AciTree
     'jquery.acitree': path.join(__dirname, './node_modules/acitree/js/jquery.aciTree.min'),
diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js
index 3cc079f92..c05e9dacb 100644
--- a/web/webpack.test.config.js
+++ b/web/webpack.test.config.js
@@ -160,6 +160,8 @@ module.exports = {
       'backgrid.filter': path.join(__dirname, './node_modules/backgrid-filter/backgrid-filter'),
       'sources': sourcesDir + '/js',
       'translations': regressionDir + '/javascript/fake_translations',
+      'pgadmin.browser.messages': regressionDir + '/javascript/fake_messages',
+      'pgadmin.server.supported_servers': regressionDir + '/javascript/fake_supported_servers',
       'pgadmin.browser.endpoints': regressionDir + '/javascript/fake_endpoints',
       'slickgrid': nodeModulesDir + '/slickgrid/',
       'slickgrid.plugins': nodeModulesDir + '/slickgrid/plugins/',
diff --git a/web/yarn.lock b/web/yarn.lock
index cd614283b..414213ae5 100644
--- a/web/yarn.lock
+++ b/web/yarn.lock
@@ -16,58 +16,59 @@
   dependencies:
     "@babel/highlight" "^7.12.13"
 
-"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.13.15", "@babel/compat-data@^7.14.0":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.14.0.tgz#a901128bce2ad02565df95e6ecbf195cf9465919"
-  integrity sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==
+"@babel/compat-data@^7.13.0", "@babel/compat-data@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.8.tgz#5b783b9808f15cef71547f1b691f34f8ff6003a6"
+  integrity sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog==
 
 "@babel/core@^7.10.2", "@babel/core@^7.7.5":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.14.3.tgz#5395e30405f0776067fbd9cf0884f15bfb770a38"
-  integrity sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.8.tgz#c191d9c5871788a591d69ea1dc03e5843a3680fb"
+  integrity sha512-oYapIySGw1zGhEFRd6lzWNLWFX2s5dA/jm+Pw/+59ZdXtjyIuwlXbrId22Md0rgZVop+aVoqow2riXhBLNyuQg==
   dependencies:
     "@babel/code-frame" "^7.12.13"
-    "@babel/generator" "^7.14.3"
-    "@babel/helper-compilation-targets" "^7.13.16"
-    "@babel/helper-module-transforms" "^7.14.2"
-    "@babel/helpers" "^7.14.0"
-    "@babel/parser" "^7.14.3"
+    "@babel/generator" "^7.13.0"
+    "@babel/helper-compilation-targets" "^7.13.8"
+    "@babel/helper-module-transforms" "^7.13.0"
+    "@babel/helpers" "^7.13.0"
+    "@babel/parser" "^7.13.4"
     "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.14.2"
-    "@babel/types" "^7.14.2"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
     convert-source-map "^1.7.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.2"
     json5 "^2.1.2"
+    lodash "^4.17.19"
     semver "^6.3.0"
     source-map "^0.5.0"
 
 "@babel/eslint-parser@^7.12.13":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.14.3.tgz#8f292caf83dd2d7b364f938fe7074806af6d70ea"
-  integrity sha512-IfJXKEVRV/Gisvgmih/+05gkBzzg4Dy0gcxkZ84iFiLK8+O+fI1HLnGJv3UrUMPpsMmmThNa69v+UnF80XP+kA==
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.13.8.tgz#6f2bde6b0690fcc0598b4869fc7c8e8b55b17687"
+  integrity sha512-XewKkiyukrGzMeqToXJQk6hjg2veI9SNQElGzAoAjKxYCLbgcVX4KA2WhoyqMon9N4RMdCZhNTJNOBcp9spsiw==
   dependencies:
-    eslint-scope "^5.1.0"
-    eslint-visitor-keys "^2.1.0"
+    eslint-scope "5.1.0"
+    eslint-visitor-keys "^1.3.0"
     semver "^6.3.0"
 
 "@babel/eslint-plugin@^7.12.13":
-  version "7.13.16"
-  resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.13.16.tgz#d6937636c567b41d6ca46d291c7b2d101e6ccaf3"
-  integrity sha512-RNL0dLHBvjXW857JwzToTHxtbOZfGcvdSegcgXC1c1PqfE+o/QN8MugkgKj2vXFxCcMByfxJrmpCbLzVBVlgIA==
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/eslint-plugin/-/eslint-plugin-7.13.0.tgz#e6d99efcd6b8551adf479e382a47218726179b1b"
+  integrity sha512-YGwCLc/u/uc3bU+q/fvgRQ62+TkxuyVvdmybK6ElzE49vODp+RnRe16eJzMM7EwvcRPQfQvcOSuGmzfcbZE2+w==
   dependencies:
     eslint-rule-composer "^0.3.0"
 
-"@babel/generator@^7.14.2", "@babel/generator@^7.14.3":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.14.3.tgz#0c2652d91f7bddab7cccc6ba8157e4f40dcedb91"
-  integrity sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==
+"@babel/generator@^7.13.0":
+  version "7.13.9"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39"
+  integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==
   dependencies:
-    "@babel/types" "^7.14.2"
+    "@babel/types" "^7.13.0"
     jsesc "^2.5.1"
     source-map "^0.5.0"
 
-"@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13":
+"@babel/helper-annotate-as-pure@^7.0.0", "@babel/helper-annotate-as-pure@^7.10.4", "@babel/helper-annotate-as-pure@^7.12.13":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz#0f58e86dfc4bb3b1fcd7db806570e177d439b6ab"
   integrity sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==
@@ -82,26 +83,25 @@
     "@babel/helper-explode-assignable-expression" "^7.12.13"
     "@babel/types" "^7.12.13"
 
-"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.16":
-  version "7.13.16"
-  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz#6e91dccf15e3f43e5556dffe32d860109887563c"
-  integrity sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==
+"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.8.tgz#02bdb22783439afb11b2f009814bdd88384bd468"
+  integrity sha512-pBljUGC1y3xKLn1nrx2eAhurLMA8OqBtBP/JwG4U8skN7kf8/aqwwxpV1N6T0e7r6+7uNitIa/fUxPFagSXp3A==
   dependencies:
-    "@babel/compat-data" "^7.13.15"
+    "@babel/compat-data" "^7.13.8"
     "@babel/helper-validator-option" "^7.12.17"
     browserslist "^4.14.5"
     semver "^6.3.0"
 
-"@babel/helper-create-class-features-plugin@^7.13.0", "@babel/helper-create-class-features-plugin@^7.14.0", "@babel/helper-create-class-features-plugin@^7.14.3":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.3.tgz#832111bcf4f57ca57a4c5b1a000fc125abc6554a"
-  integrity sha512-BnEfi5+6J2Lte9LeiL6TxLWdIlEv9Woacc1qXzXBgbikcOzMRM2Oya5XGg/f/ngotv1ej2A/b+3iJH8wbS1+lQ==
+"@babel/helper-create-class-features-plugin@^7.13.0":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.8.tgz#0367bd0a7505156ce018ca464f7ac91ba58c1a04"
+  integrity sha512-qioaRrKHQbn4hkRKDHbnuQ6kAxmmOF+kzKGnIfxPK4j2rckSJCpKzr/SSTlohSCiE3uAQpNDJ9FIh4baeE8W+w==
   dependencies:
-    "@babel/helper-annotate-as-pure" "^7.12.13"
-    "@babel/helper-function-name" "^7.14.2"
-    "@babel/helper-member-expression-to-functions" "^7.13.12"
+    "@babel/helper-function-name" "^7.12.13"
+    "@babel/helper-member-expression-to-functions" "^7.13.0"
     "@babel/helper-optimise-call-expression" "^7.12.13"
-    "@babel/helper-replace-supers" "^7.14.3"
+    "@babel/helper-replace-supers" "^7.13.0"
     "@babel/helper-split-export-declaration" "^7.12.13"
 
 "@babel/helper-create-regexp-features-plugin@^7.12.13":
@@ -112,10 +112,10 @@
     "@babel/helper-annotate-as-pure" "^7.12.13"
     regexpu-core "^4.7.1"
 
-"@babel/helper-define-polyfill-provider@^0.2.2":
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz#0525edec5094653a282688d34d846e4c75e9c0b6"
-  integrity sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==
+"@babel/helper-define-polyfill-provider@^0.1.5":
+  version "0.1.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.5.tgz#3c2f91b7971b9fc11fe779c945c014065dea340e"
+  integrity sha512-nXuzCSwlJ/WKr8qxzW816gwyT6VZgiJG17zR40fou70yfAcqjoNyTLl/DQ+FExw5Hx5KNqshmN8Ldl/r2N7cTg==
   dependencies:
     "@babel/helper-compilation-targets" "^7.13.0"
     "@babel/helper-module-imports" "^7.12.13"
@@ -133,7 +133,7 @@
   dependencies:
     "@babel/types" "^7.13.0"
 
-"@babel/helper-function-name@^7.12.13", "@babel/helper-function-name@^7.14.2":
+"@babel/helper-function-name@^7.12.13":
   version "7.14.2"
   resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz#397688b590760b6ef7725b5f0860c82427ebaac2"
   integrity sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==
@@ -150,19 +150,19 @@
     "@babel/types" "^7.12.13"
 
 "@babel/helper-hoist-variables@^7.13.0":
-  version "7.13.16"
-  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz#1b1651249e94b51f8f0d33439843e33e39775b30"
-  integrity sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz#5d5882e855b5c5eda91e0cadc26c6e7a2c8593d8"
+  integrity sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==
   dependencies:
-    "@babel/traverse" "^7.13.15"
-    "@babel/types" "^7.13.16"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
-"@babel/helper-member-expression-to-functions@^7.13.12":
-  version "7.13.12"
-  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz#dfe368f26d426a07299d8d6513821768216e6d72"
-  integrity sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==
+"@babel/helper-member-expression-to-functions@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz#6aa4bb678e0f8c22f58cdb79451d30494461b091"
+  integrity sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==
   dependencies:
-    "@babel/types" "^7.13.12"
+    "@babel/types" "^7.13.0"
 
 "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.13.12":
   version "7.13.12"
@@ -171,19 +171,20 @@
   dependencies:
     "@babel/types" "^7.13.12"
 
-"@babel/helper-module-transforms@^7.13.0", "@babel/helper-module-transforms@^7.14.0", "@babel/helper-module-transforms@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz#ac1cc30ee47b945e3e0c4db12fa0c5389509dfe5"
-  integrity sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==
+"@babel/helper-module-transforms@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz#42eb4bd8eea68bab46751212c357bfed8b40f6f1"
+  integrity sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw==
   dependencies:
-    "@babel/helper-module-imports" "^7.13.12"
-    "@babel/helper-replace-supers" "^7.13.12"
-    "@babel/helper-simple-access" "^7.13.12"
+    "@babel/helper-module-imports" "^7.12.13"
+    "@babel/helper-replace-supers" "^7.13.0"
+    "@babel/helper-simple-access" "^7.12.13"
     "@babel/helper-split-export-declaration" "^7.12.13"
-    "@babel/helper-validator-identifier" "^7.14.0"
+    "@babel/helper-validator-identifier" "^7.12.11"
     "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.14.2"
-    "@babel/types" "^7.14.2"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
+    lodash "^4.17.19"
 
 "@babel/helper-optimise-call-expression@^7.12.13":
   version "7.12.13"
@@ -206,17 +207,17 @@
     "@babel/helper-wrap-function" "^7.13.0"
     "@babel/types" "^7.13.0"
 
-"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.12", "@babel/helper-replace-supers@^7.14.3":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz#ca17b318b859d107f0e9b722d58cf12d94436600"
-  integrity sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA==
+"@babel/helper-replace-supers@^7.12.13", "@babel/helper-replace-supers@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz#6034b7b51943094cb41627848cb219cb02be1d24"
+  integrity sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==
   dependencies:
-    "@babel/helper-member-expression-to-functions" "^7.13.12"
+    "@babel/helper-member-expression-to-functions" "^7.13.0"
     "@babel/helper-optimise-call-expression" "^7.12.13"
-    "@babel/traverse" "^7.14.2"
-    "@babel/types" "^7.14.2"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
-"@babel/helper-simple-access@^7.13.12":
+"@babel/helper-simple-access@^7.12.13":
   version "7.13.12"
   resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz#dd6c538afb61819d205a012c31792a39c7a5eaf6"
   integrity sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==
@@ -257,42 +258,33 @@
     "@babel/traverse" "^7.13.0"
     "@babel/types" "^7.13.0"
 
-"@babel/helpers@^7.14.0":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.14.0.tgz#ea9b6be9478a13d6f961dbb5f36bf75e2f3b8f62"
-  integrity sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==
+"@babel/helpers@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.0.tgz#7647ae57377b4f0408bf4f8a7af01c42e41badc0"
+  integrity sha512-aan1MeFPxFacZeSz6Ld7YZo5aPuqnKlD7+HZY75xQsueczFccP9A7V05+oe0XpLwHK3oLorPe9eaAUljL7WEaQ==
   dependencies:
     "@babel/template" "^7.12.13"
-    "@babel/traverse" "^7.14.0"
-    "@babel/types" "^7.14.0"
+    "@babel/traverse" "^7.13.0"
+    "@babel/types" "^7.13.0"
 
 "@babel/highlight@^7.10.4", "@babel/highlight@^7.12.13":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.14.0.tgz#3197e375711ef6bf834e67d0daec88e4f46113cf"
-  integrity sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.8.tgz#10b2dac78526424dfc1f47650d0e415dfd9dc481"
+  integrity sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==
   dependencies:
-    "@babel/helper-validator-identifier" "^7.14.0"
+    "@babel/helper-validator-identifier" "^7.12.11"
     chalk "^2.0.0"
     js-tokens "^4.0.0"
 
-"@babel/parser@^7.12.13", "@babel/parser@^7.14.2", "@babel/parser@^7.14.3":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.14.3.tgz#9b530eecb071fd0c93519df25c5ff9f14759f298"
-  integrity sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==
+"@babel/parser@^7.12.13", "@babel/parser@^7.13.0", "@babel/parser@^7.13.4":
+  version "7.13.9"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.9.tgz#ca34cb95e1c2dd126863a84465ae8ef66114be99"
+  integrity sha512-nEUfRiARCcaVo3ny3ZQjURjHQZUo/JkEw7rLlSZy/psWGnvwXFtPcr6jb7Yb41DVW5LTe6KRq9LGleRNsg1Frw==
 
-"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.13.12":
-  version "7.13.12"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz#a3484d84d0b549f3fc916b99ee4783f26fabad2a"
-  integrity sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
-    "@babel/plugin-proposal-optional-chaining" "^7.13.12"
-
-"@babel/plugin-proposal-async-generator-functions@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.2.tgz#3a2085abbf5d5f962d480dbc81347385ed62eb1e"
-  integrity sha512-b1AM4F6fwck4N8ItZ/AtC4FP/cqZqmKRQ4FaTDutwSYyjuhtvsGEMLK4N/ztV/ImP40BjIDyMgBQAeAMsQYVFQ==
+"@babel/plugin-proposal-async-generator-functions@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.8.tgz#87aacb574b3bc4b5603f6fe41458d72a5a2ec4b1"
+  integrity sha512-rPBnhj+WgoSmgq+4gQUtXx/vOcU+UYtjy1AA/aeD61Hwj410fwYyqfUcRP3lR8ucgliVJL/G7sXcNUecC75IXA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/helper-remap-async-to-generator" "^7.13.0"
@@ -306,24 +298,15 @@
     "@babel/helper-create-class-features-plugin" "^7.13.0"
     "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-proposal-class-static-block@^7.13.11":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.3.tgz#5a527e2cae4a4753119c3a3e7f64ecae8ccf1360"
-  integrity sha512-HEjzp5q+lWSjAgJtSluFDrGGosmwTgKwCXdDQZvhKsRlwv3YdkUEqxNrrjesJd+B9E9zvr1PVPVBvhYZ9msjvQ==
-  dependencies:
-    "@babel/helper-create-class-features-plugin" "^7.14.3"
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/plugin-syntax-class-static-block" "^7.12.13"
-
-"@babel/plugin-proposal-dynamic-import@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz#01ebabd7c381cff231fa43e302939a9de5be9d9f"
-  integrity sha512-oxVQZIWFh91vuNEMKltqNsKLFWkOIyJc95k2Gv9lWVyDfPUQGSSlbDEgWuJUU1afGE9WwlzpucMZ3yDRHIItkA==
+"@babel/plugin-proposal-dynamic-import@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.13.8.tgz#876a1f6966e1dec332e8c9451afda3bebcdf2e1d"
+  integrity sha512-ONWKj0H6+wIRCkZi9zSbZtE/r73uOhMVHh256ys0UzfM7I3d4n+spZNWjOnJv2gzopumP2Wxi186vI8N0Y2JyQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-dynamic-import" "^7.8.3"
 
-"@babel/plugin-proposal-export-namespace-from@^7.14.2":
+"@babel/plugin-proposal-export-namespace-from@^7.12.13":
   version "7.14.2"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz#62542f94aa9ce8f6dba79eec698af22112253791"
   integrity sha512-sRxW3z3Zp3pFfLAgVEvzTFutTXax837oOatUIvSG9o5gRj9mKwm3br1Se5f4QalTQs9x4AzlA/HrCWbQIHASUQ==
@@ -331,31 +314,31 @@
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
 
-"@babel/plugin-proposal-json-strings@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz#830b4e2426a782e8b2878fbfe2cba85b70cbf98c"
-  integrity sha512-w2DtsfXBBJddJacXMBhElGEYqCZQqN99Se1qeYn8DVLB33owlrlLftIbMzn5nz1OITfDVknXF433tBrLEAOEjA==
+"@babel/plugin-proposal-json-strings@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.13.8.tgz#bf1fb362547075afda3634ed31571c5901afef7b"
+  integrity sha512-w4zOPKUFPX1mgvTmL/fcEqy34hrQ1CRcGxdphBc6snDnnqJ47EZDIyop6IwXzAC8G916hsIuXB2ZMBCExC5k7Q==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-json-strings" "^7.8.3"
 
-"@babel/plugin-proposal-logical-assignment-operators@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.2.tgz#222348c080a1678e0e74ea63fe76f275882d1fd7"
-  integrity sha512-1JAZtUrqYyGsS7IDmFeaem+/LJqujfLZ2weLR9ugB0ufUPjzf8cguyVT1g5im7f7RXxuLq1xUxEzvm68uYRtGg==
+"@babel/plugin-proposal-logical-assignment-operators@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.13.8.tgz#93fa78d63857c40ce3c8c3315220fd00bfbb4e1a"
+  integrity sha512-aul6znYB4N4HGweImqKn59Su9RS8lbUIqxtXTOcAGtNIDczoEFv+l1EhmX8rUBp3G1jMjKJm8m0jXVp63ZpS4A==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
 
-"@babel/plugin-proposal-nullish-coalescing-operator@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz#425b11dc62fc26939a2ab42cbba680bdf5734546"
-  integrity sha512-ebR0zU9OvI2N4qiAC38KIAK75KItpIPTpAtd2r4OZmMFeKbKJpUFLYP2EuDut82+BmYi8sz42B+TfTptJ9iG5Q==
+"@babel/plugin-proposal-nullish-coalescing-operator@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.8.tgz#3730a31dafd3c10d8ccd10648ed80a2ac5472ef3"
+  integrity sha512-iePlDPBn//UhxExyS9KyeYU7RM9WScAG+D3Hhno0PLJebAEpDZMocbDe64eqynhNAnwz/vZoL/q/QB2T1OH39A==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
 
-"@babel/plugin-proposal-numeric-separator@^7.14.2":
+"@babel/plugin-proposal-numeric-separator@^7.12.13":
   version "7.14.2"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.2.tgz#82b4cc06571143faf50626104b335dd71baa4f9e"
   integrity sha512-DcTQY9syxu9BpU3Uo94fjCB3LN9/hgPS8oUL7KrSW3bA2ePrKZZPJcc5y0hoJAM9dft3pGfErtEUvxXQcfLxUg==
@@ -363,29 +346,29 @@
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-numeric-separator" "^7.10.4"
 
-"@babel/plugin-proposal-object-rest-spread@^7.14.2", "@babel/plugin-proposal-object-rest-spread@^7.9.6":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.2.tgz#e17d418f81cc103fedd4ce037e181c8056225abc"
-  integrity sha512-hBIQFxwZi8GIp934+nj5uV31mqclC1aYDhctDu5khTi9PCCUOczyy0b34W0oE9U/eJXiqQaKyVsmjeagOaSlbw==
+"@babel/plugin-proposal-object-rest-spread@^7.13.8", "@babel/plugin-proposal-object-rest-spread@^7.9.6":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.8.tgz#5d210a4d727d6ce3b18f9de82cc99a3964eed60a"
+  integrity sha512-DhB2EuB1Ih7S3/IRX5AFVgZ16k3EzfRbq97CxAVI1KSYcW+lexV8VZb7G7L8zuPVSdQMRn0kiBpf/Yzu9ZKH0g==
   dependencies:
-    "@babel/compat-data" "^7.14.0"
-    "@babel/helper-compilation-targets" "^7.13.16"
+    "@babel/compat-data" "^7.13.8"
+    "@babel/helper-compilation-targets" "^7.13.8"
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
-    "@babel/plugin-transform-parameters" "^7.14.2"
+    "@babel/plugin-transform-parameters" "^7.13.0"
 
-"@babel/plugin-proposal-optional-catch-binding@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.2.tgz#150d4e58e525b16a9a1431bd5326c4eed870d717"
-  integrity sha512-XtkJsmJtBaUbOxZsNk0Fvrv8eiqgneug0A6aqLFZ4TSkar2L5dSXWcnUKHgmjJt49pyB/6ZHvkr3dPgl9MOWRQ==
+"@babel/plugin-proposal-optional-catch-binding@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.13.8.tgz#3ad6bd5901506ea996fc31bdcf3ccfa2bed71107"
+  integrity sha512-0wS/4DUF1CuTmGo+NiaHfHcVSeSLj5S3e6RivPTg/2k3wOv3jO35tZ6/ZWsQhQMvdgI7CwphjQa/ccarLymHVA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
 
-"@babel/plugin-proposal-optional-chaining@^7.13.12", "@babel/plugin-proposal-optional-chaining@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz#df8171a8b9c43ebf4c1dabe6311b432d83e1b34e"
-  integrity sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA==
+"@babel/plugin-proposal-optional-chaining@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.8.tgz#e39df93efe7e7e621841babc197982e140e90756"
+  integrity sha512-hpbBwbTgd7Cz1QryvwJZRo1U0k1q8uyBmeXOSQUjdg/A2TASkhR/rz7AyqZ/kS8kbpsNA80rOYbxySBJAqmhhQ==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/helper-skip-transparent-expression-wrappers" "^7.12.1"
@@ -399,16 +382,6 @@
     "@babel/helper-create-class-features-plugin" "^7.13.0"
     "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-proposal-private-property-in-object@^7.14.0":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz#b1a1f2030586b9d3489cc26179d2eb5883277636"
-  integrity sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==
-  dependencies:
-    "@babel/helper-annotate-as-pure" "^7.12.13"
-    "@babel/helper-create-class-features-plugin" "^7.14.0"
-    "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/plugin-syntax-private-property-in-object" "^7.14.0"
-
 "@babel/plugin-proposal-unicode-property-regex@^7.12.13", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz#bebde51339be829c17aaaaced18641deb62b39ba"
@@ -431,13 +404,6 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-syntax-class-static-block@^7.12.13":
-  version "7.12.13"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz#8e3d674b0613e67975ceac2776c97b60cafc5c9c"
-  integrity sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.12.13"
-
 "@babel/plugin-syntax-dynamic-import@^7.8.3":
   version "7.8.3"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3"
@@ -508,13 +474,6 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.8.0"
 
-"@babel/plugin-syntax-private-property-in-object@^7.14.0":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz#762a4babec61176fec6c88480dec40372b140c0b"
-  integrity sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==
-  dependencies:
-    "@babel/helper-plugin-utils" "^7.13.0"
-
 "@babel/plugin-syntax-top-level-await@^7.12.13":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz#c5f0fa6e249f5b739727f923540cf7a806130178"
@@ -545,23 +504,23 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-block-scoping@^7.14.2":
+"@babel/plugin-transform-block-scoping@^7.12.13":
   version "7.14.2"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.2.tgz#761cb12ab5a88d640ad4af4aa81f820e6b5fdf5c"
   integrity sha512-neZZcP19NugZZqNwMTH+KoBjx5WyvESPSIOQb4JHpfd+zPfqcH65RMu5xJju5+6q/Y2VzYrleQTr+b6METyyxg==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-classes@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.2.tgz#3f1196c5709f064c252ad056207d87b7aeb2d03d"
-  integrity sha512-7oafAVcucHquA/VZCsXv/gmuiHeYd64UJyyTYU+MPfNu0KeNlxw06IeENBO8bJjXVbolu+j1MM5aKQtH1OMCNg==
+"@babel/plugin-transform-classes@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz#0265155075c42918bf4d3a4053134176ad9b533b"
+  integrity sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.12.13"
-    "@babel/helper-function-name" "^7.14.2"
+    "@babel/helper-function-name" "^7.12.13"
     "@babel/helper-optimise-call-expression" "^7.12.13"
     "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-replace-supers" "^7.13.12"
+    "@babel/helper-replace-supers" "^7.13.0"
     "@babel/helper-split-export-declaration" "^7.12.13"
     globals "^11.1.0"
 
@@ -572,10 +531,10 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
 
-"@babel/plugin-transform-destructuring@^7.13.17":
-  version "7.13.17"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.17.tgz#678d96576638c19d5b36b332504d3fd6e06dea27"
-  integrity sha512-UAUqiLv+uRLO+xuBKKMEpC+t7YRNVRqBsWWq1yKXbBZBje/t3IXCiSinZhjn/DC3qzBfICeYd2EFGEbHsh5RLA==
+"@babel/plugin-transform-destructuring@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz#c5dce270014d4e1ebb1d806116694c12b7028963"
+  integrity sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
 
@@ -631,23 +590,23 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.12.13"
 
-"@babel/plugin-transform-modules-amd@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.2.tgz#6622806fe1a7c07a1388444222ef9535f2ca17b0"
-  integrity sha512-hPC6XBswt8P3G2D1tSV2HzdKvkqOpmbyoy+g73JG0qlF/qx2y3KaMmXb1fLrpmWGLZYA0ojCvaHdzFWjlmV+Pw==
+"@babel/plugin-transform-modules-amd@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz#19f511d60e3d8753cc5a6d4e775d3a5184866cc3"
+  integrity sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==
   dependencies:
-    "@babel/helper-module-transforms" "^7.14.2"
+    "@babel/helper-module-transforms" "^7.13.0"
     "@babel/helper-plugin-utils" "^7.13.0"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-commonjs@^7.14.0":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz#52bc199cb581e0992edba0f0f80356467587f161"
-  integrity sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==
+"@babel/plugin-transform-modules-commonjs@^7.13.8":
+  version "7.13.8"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.8.tgz#7b01ad7c2dcf2275b06fa1781e00d13d420b3e1b"
+  integrity sha512-9QiOx4MEGglfYZ4XOnU79OHr6vIWUakIj9b4mioN8eQIoEh+pf5p/zEB36JpDFWA12nNMiRf7bfoRvl9Rn79Bw==
   dependencies:
-    "@babel/helper-module-transforms" "^7.14.0"
+    "@babel/helper-module-transforms" "^7.13.0"
     "@babel/helper-plugin-utils" "^7.13.0"
-    "@babel/helper-simple-access" "^7.13.12"
+    "@babel/helper-simple-access" "^7.12.13"
     babel-plugin-dynamic-import-node "^2.3.3"
 
 "@babel/plugin-transform-modules-systemjs@^7.13.8":
@@ -661,12 +620,12 @@
     "@babel/helper-validator-identifier" "^7.12.11"
     babel-plugin-dynamic-import-node "^2.3.3"
 
-"@babel/plugin-transform-modules-umd@^7.14.0":
-  version "7.14.0"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz#2f8179d1bbc9263665ce4a65f305526b2ea8ac34"
-  integrity sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==
+"@babel/plugin-transform-modules-umd@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz#8a3d96a97d199705b9fd021580082af81c06e70b"
+  integrity sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==
   dependencies:
-    "@babel/helper-module-transforms" "^7.14.0"
+    "@babel/helper-module-transforms" "^7.13.0"
     "@babel/helper-plugin-utils" "^7.13.0"
 
 "@babel/plugin-transform-named-capturing-groups-regex@^7.12.13":
@@ -691,10 +650,10 @@
     "@babel/helper-plugin-utils" "^7.12.13"
     "@babel/helper-replace-supers" "^7.12.13"
 
-"@babel/plugin-transform-parameters@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.2.tgz#e4290f72e0e9e831000d066427c4667098decc31"
-  integrity sha512-NxoVmA3APNCC1JdMXkdYXuQS+EMdqy0vIwyDHeKHiJKRxmp1qGSdb0JLEIoPRhkx6H/8Qi3RJ3uqOCYw8giy9A==
+"@babel/plugin-transform-parameters@^7.13.0":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz#8fa7603e3097f9c0b7ca1a4821bc2fb52e9e5007"
+  integrity sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==
   dependencies:
     "@babel/helper-plugin-utils" "^7.13.0"
 
@@ -719,16 +678,27 @@
   dependencies:
     "@babel/plugin-transform-react-jsx" "^7.12.17"
 
-"@babel/plugin-transform-react-jsx@^7.12.17", "@babel/plugin-transform-react-jsx@^7.13.12":
-  version "7.14.3"
-  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.14.3.tgz#0e26597805cf0862da735f264550933c38babb66"
-  integrity sha512-uuxuoUNVhdgYzERiHHFkE4dWoJx+UFVyuAl0aqN8P2/AKFHwqgUC5w2+4/PjpKXJsFgBlYAFXlUmDQ3k3DUkXw==
+"@babel/plugin-transform-react-jsx@^7.12.17":
+  version "7.12.17"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.12.17.tgz#dd2c1299f5e26de584939892de3cfc1807a38f24"
+  integrity sha512-mwaVNcXV+l6qJOuRhpdTEj8sT/Z0owAVWf9QujTZ0d2ye9X/K+MTOTSizcgKOj18PGnTc/7g1I4+cIUjsKhBcw==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.12.13"
+    "@babel/helper-module-imports" "^7.12.13"
+    "@babel/helper-plugin-utils" "^7.12.13"
+    "@babel/plugin-syntax-jsx" "^7.12.13"
+    "@babel/types" "^7.12.17"
+
+"@babel/plugin-transform-react-jsx@^7.13.12":
+  version "7.13.12"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.13.12.tgz#1df5dfaf0f4b784b43e96da6f28d630e775f68b3"
+  integrity sha512-jcEI2UqIcpCqB5U5DRxIl0tQEProI2gcu+g8VTIqxLO5Iidojb4d77q+fwGseCvd8af/lJ9masp4QWzBXFE2xA==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.12.13"
     "@babel/helper-module-imports" "^7.13.12"
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/plugin-syntax-jsx" "^7.12.13"
-    "@babel/types" "^7.14.2"
+    "@babel/types" "^7.13.12"
 
 "@babel/plugin-transform-react-pure-annotations@^7.12.1":
   version "7.12.1"
@@ -738,7 +708,7 @@
     "@babel/helper-annotate-as-pure" "^7.10.4"
     "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-transform-regenerator@^7.13.15":
+"@babel/plugin-transform-regenerator@^7.12.13":
   version "7.13.15"
   resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz#e5eb28945bf8b6563e7f818945f966a8d2997f39"
   integrity sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==
@@ -804,33 +774,29 @@
     "@babel/helper-plugin-utils" "^7.12.13"
 
 "@babel/preset-env@^7.10.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.14.2.tgz#e80612965da73579c84ad2f963c2359c71524ed5"
-  integrity sha512-7dD7lVT8GMrE73v4lvDEb85cgcQhdES91BSD7jS/xjC6QY8PnRhux35ac+GCpbiRhp8crexBvZZqnaL6VrY8TQ==
+  version "7.13.9"
+  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.9.tgz#3ee5f233316b10d066d7f379c6d1e13a96853654"
+  integrity sha512-mcsHUlh2rIhViqMG823JpscLMesRt3QbMsv1+jhopXEb3W2wXvQ9QoiOlZI9ZbR3XqPtaFpZwEZKYqGJnGMZTQ==
   dependencies:
-    "@babel/compat-data" "^7.14.0"
-    "@babel/helper-compilation-targets" "^7.13.16"
+    "@babel/compat-data" "^7.13.8"
+    "@babel/helper-compilation-targets" "^7.13.8"
     "@babel/helper-plugin-utils" "^7.13.0"
     "@babel/helper-validator-option" "^7.12.17"
-    "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.13.12"
-    "@babel/plugin-proposal-async-generator-functions" "^7.14.2"
+    "@babel/plugin-proposal-async-generator-functions" "^7.13.8"
     "@babel/plugin-proposal-class-properties" "^7.13.0"
-    "@babel/plugin-proposal-class-static-block" "^7.13.11"
-    "@babel/plugin-proposal-dynamic-import" "^7.14.2"
-    "@babel/plugin-proposal-export-namespace-from" "^7.14.2"
-    "@babel/plugin-proposal-json-strings" "^7.14.2"
-    "@babel/plugin-proposal-logical-assignment-operators" "^7.14.2"
-    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.14.2"
-    "@babel/plugin-proposal-numeric-separator" "^7.14.2"
-    "@babel/plugin-proposal-object-rest-spread" "^7.14.2"
-    "@babel/plugin-proposal-optional-catch-binding" "^7.14.2"
-    "@babel/plugin-proposal-optional-chaining" "^7.14.2"
+    "@babel/plugin-proposal-dynamic-import" "^7.13.8"
+    "@babel/plugin-proposal-export-namespace-from" "^7.12.13"
+    "@babel/plugin-proposal-json-strings" "^7.13.8"
+    "@babel/plugin-proposal-logical-assignment-operators" "^7.13.8"
+    "@babel/plugin-proposal-nullish-coalescing-operator" "^7.13.8"
+    "@babel/plugin-proposal-numeric-separator" "^7.12.13"
+    "@babel/plugin-proposal-object-rest-spread" "^7.13.8"
+    "@babel/plugin-proposal-optional-catch-binding" "^7.13.8"
+    "@babel/plugin-proposal-optional-chaining" "^7.13.8"
     "@babel/plugin-proposal-private-methods" "^7.13.0"
-    "@babel/plugin-proposal-private-property-in-object" "^7.14.0"
     "@babel/plugin-proposal-unicode-property-regex" "^7.12.13"
     "@babel/plugin-syntax-async-generators" "^7.8.4"
     "@babel/plugin-syntax-class-properties" "^7.12.13"
-    "@babel/plugin-syntax-class-static-block" "^7.12.13"
     "@babel/plugin-syntax-dynamic-import" "^7.8.3"
     "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
     "@babel/plugin-syntax-json-strings" "^7.8.3"
@@ -840,15 +806,14 @@
     "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
     "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
     "@babel/plugin-syntax-optional-chaining" "^7.8.3"
-    "@babel/plugin-syntax-private-property-in-object" "^7.14.0"
     "@babel/plugin-syntax-top-level-await" "^7.12.13"
     "@babel/plugin-transform-arrow-functions" "^7.13.0"
     "@babel/plugin-transform-async-to-generator" "^7.13.0"
     "@babel/plugin-transform-block-scoped-functions" "^7.12.13"
-    "@babel/plugin-transform-block-scoping" "^7.14.2"
-    "@babel/plugin-transform-classes" "^7.14.2"
+    "@babel/plugin-transform-block-scoping" "^7.12.13"
+    "@babel/plugin-transform-classes" "^7.13.0"
     "@babel/plugin-transform-computed-properties" "^7.13.0"
-    "@babel/plugin-transform-destructuring" "^7.13.17"
+    "@babel/plugin-transform-destructuring" "^7.13.0"
     "@babel/plugin-transform-dotall-regex" "^7.12.13"
     "@babel/plugin-transform-duplicate-keys" "^7.12.13"
     "@babel/plugin-transform-exponentiation-operator" "^7.12.13"
@@ -856,16 +821,16 @@
     "@babel/plugin-transform-function-name" "^7.12.13"
     "@babel/plugin-transform-literals" "^7.12.13"
     "@babel/plugin-transform-member-expression-literals" "^7.12.13"
-    "@babel/plugin-transform-modules-amd" "^7.14.2"
-    "@babel/plugin-transform-modules-commonjs" "^7.14.0"
+    "@babel/plugin-transform-modules-amd" "^7.13.0"
+    "@babel/plugin-transform-modules-commonjs" "^7.13.8"
     "@babel/plugin-transform-modules-systemjs" "^7.13.8"
-    "@babel/plugin-transform-modules-umd" "^7.14.0"
+    "@babel/plugin-transform-modules-umd" "^7.13.0"
     "@babel/plugin-transform-named-capturing-groups-regex" "^7.12.13"
     "@babel/plugin-transform-new-target" "^7.12.13"
     "@babel/plugin-transform-object-super" "^7.12.13"
-    "@babel/plugin-transform-parameters" "^7.14.2"
+    "@babel/plugin-transform-parameters" "^7.13.0"
     "@babel/plugin-transform-property-literals" "^7.12.13"
-    "@babel/plugin-transform-regenerator" "^7.13.15"
+    "@babel/plugin-transform-regenerator" "^7.12.13"
     "@babel/plugin-transform-reserved-words" "^7.12.13"
     "@babel/plugin-transform-shorthand-properties" "^7.12.13"
     "@babel/plugin-transform-spread" "^7.13.0"
@@ -875,10 +840,10 @@
     "@babel/plugin-transform-unicode-escapes" "^7.12.13"
     "@babel/plugin-transform-unicode-regex" "^7.12.13"
     "@babel/preset-modules" "^0.1.4"
-    "@babel/types" "^7.14.2"
-    babel-plugin-polyfill-corejs2 "^0.2.0"
-    babel-plugin-polyfill-corejs3 "^0.2.0"
-    babel-plugin-polyfill-regenerator "^0.2.0"
+    "@babel/types" "^7.13.0"
+    babel-plugin-polyfill-corejs2 "^0.1.4"
+    babel-plugin-polyfill-corejs3 "^0.1.3"
+    babel-plugin-polyfill-regenerator "^0.1.2"
     core-js-compat "^3.9.0"
     semver "^6.3.0"
 
@@ -905,13 +870,20 @@
     "@babel/plugin-transform-react-jsx-development" "^7.12.17"
     "@babel/plugin-transform-react-pure-annotations" "^7.12.1"
 
-"@babel/runtime@^7.13.10", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4":
+"@babel/runtime@^7.12.0", "@babel/runtime@^7.13.10":
   version "7.14.0"
   resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.0.tgz#46794bc20b612c5f75e62dd071e24dfd95f1cbe6"
   integrity sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==
   dependencies:
     regenerator-runtime "^0.13.4"
 
+"@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
+  version "7.13.9"
+  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee"
+  integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==
+  dependencies:
+    regenerator-runtime "^0.13.4"
+
 "@babel/template@^7.12.13":
   version "7.12.13"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.12.13.tgz#530265be8a2589dbb37523844c5bcb55947fb327"
@@ -921,21 +893,31 @@
     "@babel/parser" "^7.12.13"
     "@babel/types" "^7.12.13"
 
-"@babel/traverse@^7.13.0", "@babel/traverse@^7.13.15", "@babel/traverse@^7.14.0", "@babel/traverse@^7.14.2":
-  version "7.14.2"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.14.2.tgz#9201a8d912723a831c2679c7ebbf2fe1416d765b"
-  integrity sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==
+"@babel/traverse@^7.13.0", "@babel/traverse@^7.4.5":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.13.0.tgz#6d95752475f86ee7ded06536de309a65fc8966cc"
+  integrity sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==
   dependencies:
     "@babel/code-frame" "^7.12.13"
-    "@babel/generator" "^7.14.2"
-    "@babel/helper-function-name" "^7.14.2"
+    "@babel/generator" "^7.13.0"
+    "@babel/helper-function-name" "^7.12.13"
     "@babel/helper-split-export-declaration" "^7.12.13"
-    "@babel/parser" "^7.14.2"
-    "@babel/types" "^7.14.2"
+    "@babel/parser" "^7.13.0"
+    "@babel/types" "^7.13.0"
     debug "^4.1.0"
     globals "^11.1.0"
+    lodash "^4.17.19"
 
-"@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.13.0", "@babel/types@^7.13.12", "@babel/types@^7.13.16", "@babel/types@^7.14.0", "@babel/types@^7.14.2", "@babel/types@^7.4.4":
+"@babel/types@^7.12.1", "@babel/types@^7.12.13", "@babel/types@^7.12.17", "@babel/types@^7.13.0", "@babel/types@^7.4.4":
+  version "7.13.0"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.13.0.tgz#74424d2816f0171b4100f0ab34e9a374efdf7f80"
+  integrity sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.12.11"
+    lodash "^4.17.19"
+    to-fast-properties "^2.0.0"
+
+"@babel/types@^7.13.12", "@babel/types@^7.14.2":
   version "7.14.2"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.14.2.tgz#4208ae003107ef8a057ea8333e56eb64d2f6a2c3"
   integrity sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==
@@ -943,6 +925,11 @@
     "@babel/helper-validator-identifier" "^7.14.0"
     to-fast-properties "^2.0.0"
 
+"@date-io/core@1.x":
+  version "1.3.13"
+  resolved "https://registry.yarnpkg.com/@date-io/core/-/core-1.3.13.tgz#90c71da493f20204b7a972929cc5c482d078b3fa"
+  integrity sha512-AlEKV7TxjeK+jxWVKcCFrfYAk8spX9aCyiToFIiLPtfQbsjmRGLIhb5VZgptQcJdHtLXo7+m0DuurwFgUToQuA==
+
 "@discoveryjs/json-ext@^0.5.0":
   version "0.5.3"
   resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz#90420f9f9c6d3987f176a19a7d8e764271a2f55d"
@@ -995,7 +982,7 @@
   resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
   integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
 
-"@emotion/is-prop-valid@0.8.8":
+"@emotion/is-prop-valid@0.8.8", "@emotion/is-prop-valid@^0.8.8":
   version "0.8.8"
   resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a"
   integrity sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==
@@ -1007,12 +994,12 @@
   resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb"
   integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==
 
-"@emotion/memoize@^0.7.4":
+"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5":
   version "0.7.5"
   resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
   integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
 
-"@emotion/react@^11.1.5":
+"@emotion/react@^11.1.1", "@emotion/react@^11.1.5":
   version "11.4.0"
   resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.0.tgz#2465ad7b073a691409b88dfd96dc17097ddad9b7"
   integrity sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg==
@@ -1075,12 +1062,12 @@
     "@emotion/styled-base" "^10.0.27"
     babel-plugin-emotion "^10.0.27"
 
-"@emotion/stylis@0.8.5":
+"@emotion/stylis@0.8.5", "@emotion/stylis@^0.8.4":
   version "0.8.5"
   resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04"
   integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==
 
-"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.5":
+"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.4", "@emotion/unitless@^0.7.5":
   version "0.7.5"
   resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
   integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==
@@ -1100,10 +1087,10 @@
   resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
   integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
 
-"@eslint/eslintrc@^0.4.1":
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.1.tgz#442763b88cecbe3ee0ec7ca6d6dd6168550cbf14"
-  integrity sha512-5v7TDE9plVhvxQeWLXDTvFvJBdH6pEsdnl2g/dAptmuFEPedQ4Erq5rsDsX+mvAM610IhNaO2W5V1dOOnDKxkQ==
+"@eslint/eslintrc@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-0.4.0.tgz#99cc0a0584d72f1df38b900fb062ba995f395547"
+  integrity sha512-2ZPCc+uNbjV5ERJr+aKSPRwZgKd2z11x0EgLvb1PURmUrn9QNRXFqje0Ldq454PfAVyaJYyrDvvIKSFP4NnBog==
   dependencies:
     ajv "^6.12.4"
     debug "^4.1.1"
@@ -1125,6 +1112,100 @@
   resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
   integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
 
+"@material-ui/core@^4.11.4":
+  version "4.11.4"
+  resolved "https://registry.yarnpkg.com/@material-ui/core/-/core-4.11.4.tgz#4fb9fe5dec5dcf780b687e3a40cff78b2b9640a4"
+  integrity sha512-oqb+lJ2Dl9HXI9orc6/aN8ZIAMkeThufA5iZELf2LQeBn2NtjVilF5D2w7e9RpntAzDb4jK5DsVhkfOvFY/8fg==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    "@material-ui/styles" "^4.11.4"
+    "@material-ui/system" "^4.11.3"
+    "@material-ui/types" "5.1.0"
+    "@material-ui/utils" "^4.11.2"
+    "@types/react-transition-group" "^4.2.0"
+    clsx "^1.0.4"
+    hoist-non-react-statics "^3.3.2"
+    popper.js "1.16.1-lts"
+    prop-types "^15.7.2"
+    react-is "^16.8.0 || ^17.0.0"
+    react-transition-group "^4.4.0"
+
+"@material-ui/icons@^4.11.2":
+  version "4.11.2"
+  resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.11.2.tgz#b3a7353266519cd743b6461ae9fdfcb1b25eb4c5"
+  integrity sha512-fQNsKX2TxBmqIGJCSi3tGTO/gZ+eJgWmMJkgDiOfyNaunNaxcklJQFaFogYcFl0qFuaEz1qaXYXboa/bUXVSOQ==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+
+"@material-ui/lab@^4.0.0-alpha.58":
+  version "4.0.0-alpha.58"
+  resolved "https://registry.yarnpkg.com/@material-ui/lab/-/lab-4.0.0-alpha.58.tgz#c7ebb66f49863c5acbb20817163737caa299fafc"
+  integrity sha512-GKHlJqLxUeHH3L3dGQ48ZavYrqGOTXkFkiEiuYMAnAvXAZP4rhMIqeHOPXSUQan4Bd8QnafDcpovOSLnadDmKw==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    "@material-ui/utils" "^4.11.2"
+    clsx "^1.0.4"
+    prop-types "^15.7.2"
+    react-is "^16.8.0 || ^17.0.0"
+
+"@material-ui/pickers@^3.2.10":
+  version "3.2.10"
+  resolved "https://registry.yarnpkg.com/@material-ui/pickers/-/pickers-3.2.10.tgz#19df024895876eb0ec7cd239bbaea595f703f0ae"
+  integrity sha512-B8G6Obn5S3RCl7hwahkQj9sKUapwXWFjiaz/Bsw1fhYFdNMnDUolRiWQSoKPb1/oKe37Dtfszoywi1Ynbo3y8w==
+  dependencies:
+    "@babel/runtime" "^7.6.0"
+    "@date-io/core" "1.x"
+    "@types/styled-jsx" "^2.2.8"
+    clsx "^1.0.2"
+    react-transition-group "^4.0.0"
+    rifm "^0.7.0"
+
+"@material-ui/styles@^4.11.4":
+  version "4.11.4"
+  resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.11.4.tgz#eb9dfccfcc2d208243d986457dff025497afa00d"
+  integrity sha512-KNTIZcnj/zprG5LW0Sao7zw+yG3O35pviHzejMdcSGCdWbiO8qzRgOYL8JAxAsWBKOKYwVZxXtHWaB5T2Kvxew==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    "@emotion/hash" "^0.8.0"
+    "@material-ui/types" "5.1.0"
+    "@material-ui/utils" "^4.11.2"
+    clsx "^1.0.4"
+    csstype "^2.5.2"
+    hoist-non-react-statics "^3.3.2"
+    jss "^10.5.1"
+    jss-plugin-camel-case "^10.5.1"
+    jss-plugin-default-unit "^10.5.1"
+    jss-plugin-global "^10.5.1"
+    jss-plugin-nested "^10.5.1"
+    jss-plugin-props-sort "^10.5.1"
+    jss-plugin-rule-value-function "^10.5.1"
+    jss-plugin-vendor-prefixer "^10.5.1"
+    prop-types "^15.7.2"
+
+"@material-ui/system@^4.11.3":
+  version "4.11.3"
+  resolved "https://registry.yarnpkg.com/@material-ui/system/-/system-4.11.3.tgz#466bc14c9986798fd325665927c963eb47cc4143"
+  integrity sha512-SY7otguNGol41Mu2Sg6KbBP1ZRFIbFLHGK81y4KYbsV2yIcaEPOmsCK6zwWlp+2yTV3J/VwT6oSBARtGIVdXPw==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    "@material-ui/utils" "^4.11.2"
+    csstype "^2.5.2"
+    prop-types "^15.7.2"
+
+"@material-ui/types@5.1.0":
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/@material-ui/types/-/types-5.1.0.tgz#efa1c7a0b0eaa4c7c87ac0390445f0f88b0d88f2"
+  integrity sha512-7cqRjrY50b8QzRSYyhSpx4WRw2YuO0KKIGQEVk5J8uoz2BanawykgZGoWEqKm7pVIbzFDN0SpPcVV4IhOFkl8A==
+
+"@material-ui/utils@^4.11.2":
+  version "4.11.2"
+  resolved "https://registry.yarnpkg.com/@material-ui/utils/-/utils-4.11.2.tgz#f1aefa7e7dff2ebcb97d31de51aecab1bb57540a"
+  integrity sha512-Uul8w38u+PICe2Fg2pDKCaIG7kOyhowZ9vjiC1FsVwPABTW8vPPKfF6OvxRq3IiBaI1faOJmgdvMG7rMJARBhA==
+  dependencies:
+    "@babel/runtime" "^7.4.4"
+    prop-types "^15.7.2"
+    react-is "^16.8.0 || ^17.0.0"
+
 "@nodelib/fs.scandir@2.1.4":
   version "2.1.4"
   resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69"
@@ -1152,9 +1233,9 @@
   integrity sha512-15spi3V28QdevleWBNXE4pIls3nFZmBbUGrW9IVPwiQczuSb9n76TCB4bsk8TSel+I1OkHEdPhu5QKMfY6rQHA==
 
 "@popperjs/core@^2.8.3":
-  version "2.9.2"
-  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.2.tgz#adea7b6953cbb34651766b0548468e743c6a2353"
-  integrity sha512-VZMYa7+fXHdwIq1TDhSXoVmSPEGM/aa+6Aiq3nVVJ9bXr24zScr+NlKFKC3iPljA7ho/GAZr+d2jOf5GIRC30Q==
+  version "2.9.0"
+  resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.9.0.tgz#32e63212293dd3efbb521cd35a5020ab66eaa546"
+  integrity sha512-wjtKehFAIARq2OxK8j3JrggNlEslJfNuSm2ArteIbKyRMts2g0a7KzTxfRVNUM+O0gnBJ2hNV8nWPOYBgI1sew==
 
 "@projectstorm/geometry@^6.5.2":
   version "6.5.2"
@@ -1215,9 +1296,9 @@
   integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
 
 "@tippyjs/react@^4.2.0":
-  version "4.2.5"
-  resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.5.tgz#9b5837db93a1cac953962404df906aef1a18e80d"
-  integrity sha512-YBLgy+1zznBNbx4JOoOdFXWMLXjBh9hLPwRtq3s8RRdrez2l3tPBRt2m2909wZd9S1KUeKjOOYYsnitccI9I3A==
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/@tippyjs/react/-/react-4.2.3.tgz#c2dbe9eb71bd9ffa3974f9ba8e3a4a6fc1538aed"
+  integrity sha512-44vBapqROQI7Q5nDtX1MMAgcAV+3DsIi+m/45CxQ72C5LDNmNDq9h3f04x3NHMrUhWcfgfgjYA2EmeLSH/4eRg==
   dependencies:
     tippy.js "^6.3.1"
 
@@ -1250,18 +1331,23 @@
     "@types/estree" "*"
 
 "@types/eslint@*":
-  version "7.2.12"
-  resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.12.tgz#fefaa48a4db2415b621fe315e4baeedde525927e"
-  integrity sha512-HjikV/jX6e0Pg4DcB+rtOBKSrG6w5IaxWpmi3efL/eLxMz5lZTK+W1DKERrX5a+mNzL78axfsDNXu7JHFP4uLg==
+  version "7.2.7"
+  resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.2.7.tgz#f7ef1cf0dceab0ae6f9a976a0a9af14ab1baca26"
+  integrity sha512-EHXbc1z2GoQRqHaAT7+grxlTJ3WE2YNeD6jlpPoRc83cCoThRY+NUWjCUZaYmk51OICkPXn2hhphcWcWXgNW0Q==
   dependencies:
     "@types/estree" "*"
     "@types/json-schema" "*"
 
-"@types/estree@*", "@types/estree@^0.0.47":
+"@types/estree@*":
   version "0.0.47"
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.47.tgz#d7a51db20f0650efec24cd04994f523d93172ed4"
   integrity sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==
 
+"@types/estree@^0.0.46":
+  version "0.0.46"
+  resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.46.tgz#0fb6bfbbeabd7a30880504993369c4bf1deab1fe"
+  integrity sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==
+
 "@types/glob@^7.1.1":
   version "7.1.3"
   resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.3.tgz#e6ba80f36b7daad2c685acd9266382e68985c183"
@@ -1281,24 +1367,57 @@
   integrity sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==
 
 "@types/node@*", "@types/node@>=10.0.0":
-  version "15.6.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-15.6.1.tgz#32d43390d5c62c5b6ec486a9bc9c59544de39a08"
-  integrity sha512-7EIraBEyRHEe7CH+Fm1XvgqU6uwZN8Q7jppJGcqjROMT29qhAuuOxYB1uEY5UMYQKEmA5D+5tBnhdaPXSsLONA==
+  version "14.14.32"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.32.tgz#90c5c4a8d72bbbfe53033f122341343249183448"
+  integrity sha512-/Ctrftx/zp4m8JOujM5ZhwzlWLx22nbQJiVqz8/zE15gOeEW+uly3FSX4fGFpcfEvFzXcMCJwq9lGVWgyARXhg==
 
 "@types/parse-json@^4.0.0":
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
   integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
 
+"@types/prop-types@*":
+  version "15.7.3"
+  resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
+  integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
+
 "@types/q@^1.5.1":
   version "1.5.4"
   resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.4.tgz#15925414e0ad2cd765bfef58842f7e26a7accb24"
   integrity sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==
 
+"@types/react-transition-group@^4.2.0":
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.1.tgz#e1a3cb278df7f47f17b5082b1b3da17170bd44b1"
+  integrity sha512-vIo69qKKcYoJ8wKCJjwSgCTM+z3chw3g18dkrDfVX665tMH7tmbDxEAnPdey4gTlwZz5QuHGzd+hul0OVZDqqQ==
+  dependencies:
+    "@types/react" "*"
+
+"@types/react@*":
+  version "17.0.3"
+  resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
+  integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
+  dependencies:
+    "@types/prop-types" "*"
+    "@types/scheduler" "*"
+    csstype "^3.0.2"
+
+"@types/scheduler@*":
+  version "0.16.1"
+  resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
+  integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
+
+"@types/styled-jsx@^2.2.8":
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/@types/styled-jsx/-/styled-jsx-2.2.8.tgz#b50d13d8a3c34036282d65194554cf186bab7234"
+  integrity sha512-Yjye9VwMdYeXfS71ihueWRSxrruuXTwKCbzue4+5b2rjnQ//AtyM7myZ1BEhNhBQ/nL/RE7bdToUoLln2miKvg==
+  dependencies:
+    "@types/react" "*"
+
 "@vusion/webfonts-generator@^0.7.0":
-  version "0.7.2"
-  resolved "https://registry.yarnpkg.com/@vusion/webfonts-generator/-/webfonts-generator-0.7.2.tgz#c85ef497eb545b86cb3a1ca9f4513f57608198e6"
-  integrity sha512-ZQwLUpoPV2s6AnRX1wNOAWsUafjvOtQIEneI9IldW83juRW/QMHDfBUFLi5o0qRQ1fAZi9MMLiW+iC8S/SWO9g==
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/@vusion/webfonts-generator/-/webfonts-generator-0.7.0.tgz#47e7230565e8e9903aa4791189a35f07121bcc00"
+  integrity sha512-RoIpfZks/uFG/zvtTo7wW6H5v2UMFnscdjJSvhSh3/1Q6PmPv+EYbPcVimjRlP10d+bqoNzi7UL8CvyrJK2Pdw==
   dependencies:
     handlebars "^4.0.11"
     mkdirp "^1.0.4"
@@ -1533,7 +1652,7 @@ acorn@^7.0.0, acorn@^7.4.0:
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
   integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
 
-acorn@^8.0.4, acorn@^8.2.1:
+acorn@^8.0.4:
   version "8.2.4"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.2.4.tgz#caba24b08185c3b56e3168e97d15ed17f4d31fd0"
   integrity sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==
@@ -1676,11 +1795,6 @@ argparse@^1.0.6, argparse@^1.0.7:
   dependencies:
     sprintf-js "~1.0.2"
 
-argparse@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
-  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
-
 array-find-index@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1"
@@ -1904,29 +2018,39 @@ babel-plugin-macros@^2.0.0:
     cosmiconfig "^6.0.0"
     resolve "^1.12.0"
 
-babel-plugin-polyfill-corejs2@^0.2.0:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz#e9124785e6fd94f94b618a7954e5693053bf5327"
-  integrity sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==
+babel-plugin-polyfill-corejs2@^0.1.4:
+  version "0.1.10"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.10.tgz#a2c5c245f56c0cac3dbddbf0726a46b24f0f81d1"
+  integrity sha512-DO95wD4g0A8KRaHKi0D51NdGXzvpqVLnLu5BTvDlpqUEpTmeEtypgC1xqesORaWmiUOQI14UHKlzNd9iZ2G3ZA==
   dependencies:
-    "@babel/compat-data" "^7.13.11"
-    "@babel/helper-define-polyfill-provider" "^0.2.2"
+    "@babel/compat-data" "^7.13.0"
+    "@babel/helper-define-polyfill-provider" "^0.1.5"
     semver "^6.1.1"
 
-babel-plugin-polyfill-corejs3@^0.2.0:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.2.tgz#7424a1682ee44baec817327710b1b094e5f8f7f5"
-  integrity sha512-l1Cf8PKk12eEk5QP/NQ6TH8A1pee6wWDJ96WjxrMXFLHLOBFzYM4moG80HFgduVhTqAFez4alnZKEhP/bYHg0A==
+babel-plugin-polyfill-corejs3@^0.1.3:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.7.tgz#80449d9d6f2274912e05d9e182b54816904befd0"
+  integrity sha512-u+gbS9bbPhZWEeyy1oR/YaaSpod/KDT07arZHb80aTpl8H5ZBq+uN1nN9/xtX7jQyfLdPfoqI4Rue/MQSWJquw==
   dependencies:
-    "@babel/helper-define-polyfill-provider" "^0.2.2"
-    core-js-compat "^3.9.1"
+    "@babel/helper-define-polyfill-provider" "^0.1.5"
+    core-js-compat "^3.8.1"
 
-babel-plugin-polyfill-regenerator@^0.2.0:
-  version "0.2.2"
-  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz#b310c8d642acada348c1fa3b3e6ce0e851bee077"
-  integrity sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==
+babel-plugin-polyfill-regenerator@^0.1.2:
+  version "0.1.6"
+  resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.6.tgz#0fe06a026fe0faa628ccc8ba3302da0a6ce02f3f"
+  integrity sha512-OUrYG9iKPKz8NxswXbRAdSwF0GhRdIEMTloQATJi4bDuFqrXaXcCUT/VGNrr8pBcjMh1RxZ7Xt9cytVJTJfvMg==
   dependencies:
-    "@babel/helper-define-polyfill-provider" "^0.2.2"
+    "@babel/helper-define-polyfill-provider" "^0.1.5"
+
+"babel-plugin-styled-components@>= 1":
+  version "1.12.0"
+  resolved "https://registry.yarnpkg.com/babel-plugin-styled-components/-/babel-plugin-styled-components-1.12.0.tgz#1dec1676512177de6b827211e9eda5a30db4f9b9"
+  integrity sha512-FEiD7l5ZABdJPpLssKXjBUJMYqzbcNzBowfXDCdJhOpbhWiewapUaY+LZGT8R4Jg2TwOjGjG4RKeyrO5p9sBkA==
+  dependencies:
+    "@babel/helper-annotate-as-pure" "^7.0.0"
+    "@babel/helper-module-imports" "^7.0.0"
+    babel-plugin-syntax-jsx "^6.18.0"
+    lodash "^4.17.11"
 
 babel-plugin-syntax-jsx@^6.18.0:
   version "6.18.0"
@@ -2358,7 +2482,18 @@ browserify@^17.0.0:
     vm-browserify "^1.0.0"
     xtend "^4.0.0"
 
-browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6:
+browserslist@^4.0.0, browserslist@^4.14.5, browserslist@^4.16.3:
+  version "4.16.3"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.3.tgz#340aa46940d7db878748567c5dea24a48ddf3717"
+  integrity sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==
+  dependencies:
+    caniuse-lite "^1.0.30001181"
+    colorette "^1.2.1"
+    electron-to-chromium "^1.3.649"
+    escalade "^3.1.1"
+    node-releases "^1.1.70"
+
+browserslist@^4.16.0, browserslist@^4.16.6:
   version "4.16.6"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2"
   integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==
@@ -2487,6 +2622,16 @@ camelcase@^2.0.0:
   resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f"
   integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=
 
+camelcase@^6.2.0:
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
+  integrity sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==
+
+camelize@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b"
+  integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=
+
 caniuse-api@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0"
@@ -2497,7 +2642,17 @@ caniuse-api@^3.0.0:
     lodash.memoize "^4.1.2"
     lodash.uniq "^4.5.0"
 
-caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001230:
+caniuse-lite@^1.0.0:
+  version "1.0.30001197"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001197.tgz#47ad15b977d2f32b3ec2fe2b087e0c50443771db"
+  integrity sha512-8aE+sqBqtXz4G8g35Eg/XEaFr2N7rd/VQ6eABGBmNtcB8cN6qNJhMi6oSFy4UWWZgqgL3filHT8Nha4meu3tsw==
+
+caniuse-lite@^1.0.30001181, caniuse-lite@^1.0.30001219:
+  version "1.0.30001228"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001228.tgz#bfdc5942cd3326fa51ee0b42fbef4da9d492a7fa"
+  integrity sha512-QQmLOGJ3DEgokHbMSA8cj2a+geXqmnpyOFT0lhQV6P3/YOJvGDEwoedcwxEQ30gJIwIIunHIicunJ2rzK5gB2A==
+
+caniuse-lite@^1.0.30001230:
   version "1.0.30001230"
   resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71"
   integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==
@@ -2592,7 +2747,7 @@ cheerio@^1.0.0-rc.3:
     parse5-htmlparser2-tree-adapter "^6.0.1"
     tslib "^2.2.0"
 
-"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.2:
+"chokidar@>=2.0.0 <4.0.0", chokidar@^3.4.2:
   version "3.5.1"
   resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a"
   integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==
@@ -2662,6 +2817,11 @@ closest@^0.0.1:
   dependencies:
     matches-selector "0.0.1"
 
+clsx@^1.0.2, clsx@^1.0.4:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
+  integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
+
 co@^4.6.0:
   version "4.6.0"
   resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
@@ -2682,9 +2842,9 @@ code-point-at@^1.0.0:
   integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
 
 codemirror@^5.59.2:
-  version "5.61.1"
-  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.61.1.tgz#ccfc8a43b8fcfb8b12e8e75b5ffde48d541406e0"
-  integrity sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==
+  version "5.59.4"
+  resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.59.4.tgz#bfc11c8ce32b04818e8d661bbd790a94f4b3a6f3"
+  integrity sha512-achw5JBgx8QPcACDDn+EUUXmCYzx/zxEtOGXyjvLEvYY8GleUrnfm5D+Zb+UjShHggXKDT9AXrbkBZX6a0YSQg==
 
 color-convert@^1.9.0, color-convert@^1.9.3:
   version "1.9.3"
@@ -2757,7 +2917,12 @@ commander@^6.2.0:
   resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
   integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
 
-commander@^7.0.0, commander@^7.1.0:
+commander@^7.0.0:
+  version "7.1.0"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-7.1.0.tgz#f2eaecf131f10e36e07d894698226e36ae0eb5ff"
+  integrity sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==
+
+commander@^7.1.0:
   version "7.2.0"
   resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7"
   integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==
@@ -2868,12 +3033,12 @@ copy-webpack-plugin@^7.0.0:
     schema-utils "^3.0.0"
     serialize-javascript "^5.0.1"
 
-core-js-compat@^3.9.0, core-js-compat@^3.9.1:
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.13.0.tgz#a88f5fa81d8e9b15d7f98abc4447a4dfca2a358f"
-  integrity sha512-jhbI2zpVskgfDC9mGRaDo1gagd0E0i/kYW0+WvibL/rafEHKAHO653hEXIxJHqRlRLITluXtRH3AGTL5qJmifQ==
+core-js-compat@^3.8.1, core-js-compat@^3.9.0:
+  version "3.9.1"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.9.1.tgz#4e572acfe90aff69d76d8c37759d21a5c59bb455"
+  integrity sha512-jXAirMQxrkbiiLsCx9bQPJFA6llDadKMpYrBJQJ3/c4/vsPP/fAf29h24tviRlvwUL6AmY5CHLu2GvjuYviQqA==
   dependencies:
-    browserslist "^4.16.6"
+    browserslist "^4.16.3"
     semver "7.0.0"
 
 core-js@^2.4.0:
@@ -2881,10 +3046,15 @@ core-js@^2.4.0:
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.12.tgz#d9333dfa7b065e347cc5682219d6f690859cc2ec"
   integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==
 
-core-js@^3.12.1, core-js@^3.2.1:
-  version "3.13.0"
-  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.13.0.tgz#58ca436bf01d6903aee3d364089868d0d89fe58d"
-  integrity sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog==
+core-js@^3.12.1:
+  version "3.12.1"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.12.1.tgz#6b5af4ff55616c08a44d386f1f510917ff204112"
+  integrity sha512-Ne9DKPHTObRuB09Dru5AjwKjY4cJHVGu+y5f7coGn1E9Grkc3p2iBwE9AI/nJzsE29mQF7oq+mhYYRqOMFN1Bw==
+
+core-js@^3.2.1:
+  version "3.9.1"
+  resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.9.1.tgz#cec8de593db8eb2a85ffb0dbdeb312cb6e5460ae"
+  integrity sha512-gSjRvzkxQc1zjM/5paAmL4idJBFzuJoo+jDjF1tStYFMV2ERfD02HhahhCGXUyHxQRG4yFKVSdO6g62eoRMcDg==
 
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
@@ -3005,6 +3175,11 @@ crypto-browserify@^3.0.0:
     randombytes "^2.0.0"
     randomfill "^1.0.3"
 
+css-color-keywords@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
+  integrity sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=
+
 css-color-names@^0.0.4:
   version "0.0.4"
   resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
@@ -3030,20 +3205,22 @@ css-line-break@1.1.1:
     base64-arraybuffer "^0.2.0"
 
 css-loader@^5.0.1:
-  version "5.2.6"
-  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.6.tgz#c3c82ab77fea1f360e587d871a6811f4450cc8d1"
-  integrity sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.1.tgz#9362d444a0f7c08c148a109596715c904e252879"
+  integrity sha512-5FfhpjwtuRgxqmusDidowqmLlcb+1HgnEDMsi2JhiUrZUcoc+cqw+mUtMIF/+OfeMYaaFCLYp1TaIt9H6I/fKA==
   dependencies:
+    camelcase "^6.2.0"
+    cssesc "^3.0.0"
     icss-utils "^5.1.0"
     loader-utils "^2.0.0"
-    postcss "^8.2.15"
+    postcss "^8.2.6"
     postcss-modules-extract-imports "^3.0.0"
     postcss-modules-local-by-default "^4.0.0"
     postcss-modules-scope "^3.0.0"
     postcss-modules-values "^4.0.0"
     postcss-value-parser "^4.1.0"
     schema-utils "^3.0.0"
-    semver "^7.3.5"
+    semver "^7.3.4"
 
 css-minimizer-webpack-plugin@^3.0.0:
   version "3.0.0"
@@ -3095,6 +3272,15 @@ css-select@^4.1.2:
     domutils "^2.6.0"
     nth-check "^2.0.0"
 
+css-to-react-native@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.0.0.tgz#62dbe678072a824a689bcfee011fc96e02a7d756"
+  integrity sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==
+  dependencies:
+    camelize "^1.0.0"
+    css-color-keywords "^1.0.0"
+    postcss-value-parser "^4.0.2"
+
 css-tree@1.0.0-alpha.37:
   version "1.0.0-alpha.37"
   resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22"
@@ -3111,6 +3297,14 @@ css-tree@^1.1.2:
     mdn-data "2.0.14"
     source-map "^0.6.1"
 
+css-vendor@^2.0.8:
+  version "2.0.8"
+  resolved "https://registry.yarnpkg.com/css-vendor/-/css-vendor-2.0.8.tgz#e47f91d3bd3117d49180a3c935e62e3d9f7f449d"
+  integrity sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==
+  dependencies:
+    "@babel/runtime" "^7.8.3"
+    is-in-browser "^1.0.2"
+
 css-what@^3.2.1:
   version "3.4.2"
   resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4"
@@ -3187,10 +3381,10 @@ csso@^4.0.2, csso@^4.2.0:
   dependencies:
     css-tree "^1.1.2"
 
-csstype@^2.5.7:
-  version "2.6.17"
-  resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e"
-  integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==
+csstype@^2.5.2, csstype@^2.5.7:
+  version "2.6.16"
+  resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.16.tgz#544d69f547013b85a40d15bff75db38f34fe9c39"
+  integrity sha512-61FBWoDHp/gRtsoDkq/B1nWrCUG/ok1E3tUrcNbZjsE9Cxd9yzUirjS3+nAATB8U4cTtaQmAHbNndoFz5L6C9Q==
 
 csstype@^3.0.2:
   version "3.0.8"
@@ -3328,6 +3522,11 @@ decompress@^4.0.0, decompress@^4.2.0:
     pify "^2.3.0"
     strip-dirs "^2.0.0"
 
+deep-diff@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/deep-diff/-/deep-diff-1.0.2.tgz#afd3d1f749115be965e89c63edc7abb1506b9c26"
+  integrity sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==
+
 deep-equal-ident@^1.1.1:
   version "1.1.1"
   resolved "https://registry.yarnpkg.com/deep-equal-ident/-/deep-equal-ident-1.1.1.tgz#06f4b89e53710cd6cea4a7781c7a956642de8dc9"
@@ -3406,6 +3605,14 @@ di@^0.0.1:
   resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
   integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
 
+diff-arrays-of-objects@^1.1.8:
+  version "1.1.8"
+  resolved "https://registry.yarnpkg.com/diff-arrays-of-objects/-/diff-arrays-of-objects-1.1.8.tgz#a5b008bb409af7c7f997f0064e312df0d5a1bb24"
+  integrity sha512-OAaiDlQRiv5+EASUpwNSDa/sWyKHKvODQfah1CAx0dosR8OWXedFXgxAQHIdeWDobZ86D6g93BfK2X9ECIRuqw==
+  dependencies:
+    deep-diff "^1.0.2"
+    lodash "^4.17.19"
+
 diffie-hellman@^5.0.0:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875"
@@ -3441,6 +3648,14 @@ doctrine@^3.0.0:
   dependencies:
     esutils "^2.0.2"
 
+dom-helpers@^5.0.1:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.0.tgz#57fd054c5f8f34c52a3eeffdb7e7e93cd357d95b"
+  integrity sha512-Ru5o9+V8CpunKnz5LGgWXkmrH/20cGKwcHwS4m73zIvs54CN9epEmT/HLqFJW3kXpakAFkEdzgy1hzlJe3E4OQ==
+  dependencies:
+    "@babel/runtime" "^7.8.7"
+    csstype "^3.0.2"
+
 dom-serialize@^2.2.1:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/dom-serialize/-/dom-serialize-2.2.1.tgz#562ae8999f44be5ea3076f5419dcd59eb43ac95b"
@@ -3543,9 +3758,9 @@ download@^7.1.0:
     pify "^3.0.0"
 
 dropzone@^5.7.4:
-  version "5.9.2"
-  resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.9.2.tgz#0a90ce3f6b011c3932484f368f4bf06223729450"
-  integrity sha512-5t2z51DzIsWDbTpwcJIvUlwxBbvcwdCApz0yb9ecKJwG155Xm92KMEZmHW1B0MzoXOKvFwdd0nPu5cpeVcvPHQ==
+  version "5.8.0"
+  resolved "https://registry.yarnpkg.com/dropzone/-/dropzone-5.8.0.tgz#8a3c27f4ac6d29e4e37bbe2522b5fc17821075f8"
+  integrity sha512-aJQ0VpHrhkm7TcAaeUgCgnyGP/7gNXjNBM6xxx+7HPlLiHfFUa2PkfRMU3wHgBNPyH+UcF/zpJWY3QCt4l61xQ==
 
 duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2:
   version "0.1.4"
@@ -3584,6 +3799,11 @@ ejs@~3.1.6:
   dependencies:
     jake "^10.6.1"
 
+electron-to-chromium@^1.3.649:
+  version "1.3.730"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.730.tgz#6e1fad8f250827f5524672e572f823b34a6417e1"
+  integrity sha512-1Tr3h09wXhmqXnvDyrRe6MFgTeU0ZXy3+rMJWTrOHh/HNesWwBBrKnMxRJWZ86dzs8qQdw2c7ZE1/qeGHygImA==
+
 electron-to-chromium@^1.3.723:
   version "1.3.740"
   resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.740.tgz#e38b7d2b848f632191b643e6dabca51be2162922"
@@ -3659,7 +3879,7 @@ engine.io@~4.1.0:
     engine.io-parser "~4.0.0"
     ws "~7.4.2"
 
-enhanced-resolve@^5.8.0:
+enhanced-resolve@^5.7.0:
   version "5.8.2"
   resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz#15ddc779345cbb73e97c611cd00c01c1e7bf4d8b"
   integrity sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==
@@ -3685,9 +3905,9 @@ entities@^2.0.0:
   integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
 
 env-paths@^2.2.0:
-  version "2.2.1"
-  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2"
-  integrity sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.0.tgz#cdca557dc009152917d6166e2febe1f039685e43"
+  integrity sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==
 
 envinfo@^7.7.3:
   version "7.8.1"
@@ -3842,7 +4062,15 @@ eslint-rule-composer@^0.3.0:
   resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9"
   integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==
 
-eslint-scope@^5.1.0, eslint-scope@^5.1.1:
+eslint-scope@5.1.0:
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.0.tgz#d0f971dfe59c69e0cada684b23d49dbf82600ce5"
+  integrity sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==
+  dependencies:
+    esrecurse "^4.1.0"
+    estraverse "^4.1.1"
+
+eslint-scope@^5.1.1:
   version "5.1.1"
   resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
   integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
@@ -3862,36 +4090,34 @@ eslint-visitor-keys@^1.1.0, eslint-visitor-keys@^1.3.0:
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz#30ebd1ef7c2fdff01c3a4f151044af25fab0523e"
   integrity sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==
 
-eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0:
+eslint-visitor-keys@^2.0.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303"
   integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==
 
 eslint@^7.19.0:
-  version "7.27.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.27.0.tgz#665a1506d8f95655c9274d84bd78f7166b07e9c7"
-  integrity sha512-JZuR6La2ZF0UD384lcbnd0Cgg6QJjiCwhMD6eU4h/VGPcVGwawNNzKU41tgokGXnfjOOyI6QIffthhJTPzzuRA==
+  version "7.21.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.21.0.tgz#4ecd5b8c5b44f5dedc9b8a110b01bbfeb15d1c83"
+  integrity sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==
   dependencies:
     "@babel/code-frame" "7.12.11"
-    "@eslint/eslintrc" "^0.4.1"
+    "@eslint/eslintrc" "^0.4.0"
     ajv "^6.10.0"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
     debug "^4.0.1"
     doctrine "^3.0.0"
     enquirer "^2.3.5"
-    escape-string-regexp "^4.0.0"
     eslint-scope "^5.1.1"
     eslint-utils "^2.1.0"
     eslint-visitor-keys "^2.0.0"
     espree "^7.3.1"
     esquery "^1.4.0"
     esutils "^2.0.2"
-    fast-deep-equal "^3.1.3"
     file-entry-cache "^6.0.1"
     functional-red-black-tree "^1.0.1"
     glob-parent "^5.0.0"
-    globals "^13.6.0"
+    globals "^12.1.0"
     ignore "^4.0.6"
     import-fresh "^3.0.0"
     imurmurhash "^0.1.4"
@@ -3899,7 +4125,7 @@ eslint@^7.19.0:
     js-yaml "^3.13.1"
     json-stable-stringify-without-jsonify "^1.0.1"
     levn "^0.4.1"
-    lodash.merge "^4.6.2"
+    lodash "^4.17.20"
     minimatch "^3.0.4"
     natural-compare "^1.4.0"
     optionator "^0.9.1"
@@ -3908,7 +4134,7 @@ eslint@^7.19.0:
     semver "^7.2.1"
     strip-ansi "^6.0.0"
     strip-json-comments "^3.1.0"
-    table "^6.0.9"
+    table "^6.0.4"
     text-table "^0.2.0"
     v8-compile-cache "^2.0.3"
 
@@ -3933,7 +4159,7 @@ esquery@^1.4.0:
   dependencies:
     estraverse "^5.1.0"
 
-esrecurse@^4.3.0:
+esrecurse@^4.1.0, esrecurse@^4.3.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
   integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
@@ -4297,9 +4523,9 @@ flatted@^3.1.0:
   integrity sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==
 
 follow-redirects@^1.0.0, follow-redirects@^1.10.0:
-  version "1.14.1"
-  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
-  integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
+  version "1.13.3"
+  resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.3.tgz#e5598ad50174c1bc4e872301e82ac2cd97f90267"
+  integrity sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==
 
 foreach@^2.0.5:
   version "2.0.5"
@@ -4496,9 +4722,9 @@ glob-to-regexp@^0.4.1:
   integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
 
 glob@^7.1.0, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
-  version "7.1.7"
-  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
-  integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
+  version "7.1.6"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+  integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
   dependencies:
     fs.realpath "^1.0.0"
     inflight "^1.0.4"
@@ -4519,13 +4745,6 @@ globals@^12.1.0:
   dependencies:
     type-fest "^0.8.1"
 
-globals@^13.6.0:
-  version "13.9.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-13.9.0.tgz#4bf2bf635b334a173fb1daf7c5e6b218ecdc06cb"
-  integrity sha512-74/FduwI/JaIrr1H8e71UbDE+5x7pIPs1C2rrwC52SszOo043CsWOZEMW7o2Y58xwm9b+0RBKDxY5n2sUpEFxA==
-  dependencies:
-    type-fest "^0.20.2"
-
 globals@^9.18.0:
   version "9.18.0"
   resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
@@ -4651,7 +4870,7 @@ has-ansi@^2.0.0:
   dependencies:
     ansi-regex "^2.0.0"
 
-has-bigints@^1.0.1:
+has-bigints@^1.0.0:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113"
   integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==
@@ -4676,7 +4895,7 @@ has-symbol-support-x@^1.4.1:
   resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
   integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
 
-has-symbols@^1.0.1, has-symbols@^1.0.2:
+has-symbols@^1.0.0, has-symbols@^1.0.1, has-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
   integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
@@ -4741,7 +4960,7 @@ hmac-drbg@^1.0.1:
     minimalistic-assert "^1.0.0"
     minimalistic-crypto-utils "^1.0.1"
 
-hoist-non-react-statics@^3.3.1:
+hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45"
   integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==
@@ -4847,6 +5066,11 @@ human-signals@^2.1.0:
   resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
   integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
 
+hyphenate-style-name@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d"
+  integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==
+
 iconv-lite@0.4.24:
   version "0.4.24"
   resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
@@ -4975,6 +5199,13 @@ imurmurhash@^0.1.4:
   resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
   integrity sha1-khi5srkoojixPcT7a21XbyMUU+o=
 
+indefinite-observable@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/indefinite-observable/-/indefinite-observable-2.0.1.tgz#574af29bfbc17eb5947793797bddc94c9d859400"
+  integrity sha512-G8vgmork+6H9S8lUAg1gtXEj2JxIQTo0g2PbFiYOdjkziSI0F7UYBiVwhZRuixhBCNGczAls34+5HJPyZysvxQ==
+  dependencies:
+    symbol-observable "1.2.0"
+
 indent-string@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80"
@@ -5093,9 +5324,9 @@ is-arrayish@^0.2.1:
   integrity sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=
 
 is-bigint@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a"
-  integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2"
+  integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg==
 
 is-binary-path@~2.1.0:
   version "2.1.0"
@@ -5105,11 +5336,11 @@ is-binary-path@~2.1.0:
     binary-extensions "^2.0.0"
 
 is-boolean-object@^1.0.1, is-boolean-object@^1.1.0:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8"
-  integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0"
+  integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA==
   dependencies:
-    call-bind "^1.0.2"
+    call-bind "^1.0.0"
 
 is-buffer@^1.1.0:
   version "1.1.6"
@@ -5139,9 +5370,9 @@ is-color-stop@^1.1.0:
     rgba-regex "^1.0.0"
 
 is-core-module@^2.2.0:
-  version "2.4.0"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
-  integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a"
+  integrity sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==
   dependencies:
     has "^1.0.3"
 
@@ -5194,6 +5425,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1:
   dependencies:
     is-extglob "^2.1.1"
 
+is-in-browser@^1.0.2, is-in-browser@^1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/is-in-browser/-/is-in-browser-1.1.3.tgz#56ff4db683a078c6082eb95dad7dc62e1d04f835"
+  integrity sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=
+
 is-jpg@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/is-jpg/-/is-jpg-2.0.0.tgz#2e1997fa6e9166eaac0242daae443403e4ef1d97"
@@ -5241,7 +5477,15 @@ is-png@^2.0.0:
   resolved "https://registry.yarnpkg.com/is-png/-/is-png-2.0.0.tgz#ee8cbc9e9b050425cedeeb4a6fb74a649b0a4a8d"
   integrity sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==
 
-is-regex@^1.0.5, is-regex@^1.1.0, is-regex@^1.1.3:
+is-regex@^1.0.5, is-regex@^1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251"
+  integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==
+  dependencies:
+    call-bind "^1.0.2"
+    has-symbols "^1.0.1"
+
+is-regex@^1.1.3:
   version "1.1.3"
   resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f"
   integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==
@@ -5287,11 +5531,11 @@ is-svg@^4.2.1:
     fast-xml-parser "^3.19.0"
 
 is-symbol@^1.0.2, is-symbol@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
-  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937"
+  integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==
   dependencies:
-    has-symbols "^1.0.2"
+    has-symbols "^1.0.1"
 
 is-typed-array@^1.1.3:
   version "1.1.5"
@@ -5605,6 +5849,77 @@ jsprim@^1.2.2:
     json-schema "0.2.3"
     verror "1.10.0"
 
+jss-plugin-camel-case@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-camel-case/-/jss-plugin-camel-case-10.5.1.tgz#427b24a9951b4c2eaa7e3d5267acd2e00b0934f9"
+  integrity sha512-9+oymA7wPtswm+zxVti1qiowC5q7bRdCJNORtns2JUj/QHp2QPXYwSNRD8+D2Cy3/CEMtdJzlNnt5aXmpS6NAg==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    hyphenate-style-name "^1.0.3"
+    jss "10.5.1"
+
+jss-plugin-default-unit@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-default-unit/-/jss-plugin-default-unit-10.5.1.tgz#2be385d71d50aee2ee81c2a9ac70e00592ed861b"
+  integrity sha512-D48hJBc9Tj3PusvlillHW8Fz0y/QqA7MNmTYDQaSB/7mTrCZjt7AVRROExoOHEtd2qIYKOYJW3Jc2agnvsXRlQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    jss "10.5.1"
+
+jss-plugin-global@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-global/-/jss-plugin-global-10.5.1.tgz#0e1793dea86c298360a7e2004721351653c7e764"
+  integrity sha512-jX4XpNgoaB8yPWw/gA1aPXJEoX0LNpvsROPvxlnYe+SE0JOhuvF7mA6dCkgpXBxfTWKJsno7cDSCgzHTocRjCQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    jss "10.5.1"
+
+jss-plugin-nested@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-nested/-/jss-plugin-nested-10.5.1.tgz#8753a80ad31190fb6ac6fdd39f57352dcf1295bb"
+  integrity sha512-xXkWKOCljuwHNjSYcXrCxBnjd8eJp90KVFW1rlhvKKRXnEKVD6vdKXYezk2a89uKAHckSvBvBoDGsfZrldWqqQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    jss "10.5.1"
+    tiny-warning "^1.0.2"
+
+jss-plugin-props-sort@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-props-sort/-/jss-plugin-props-sort-10.5.1.tgz#ab1c167fd2d4506fb6a1c1d66c5f3ef545ff1cd8"
+  integrity sha512-t+2vcevNmMg4U/jAuxlfjKt46D/jHzCPEjsjLRj/J56CvP7Iy03scsUP58Iw8mVnaV36xAUZH2CmAmAdo8994g==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    jss "10.5.1"
+
+jss-plugin-rule-value-function@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.5.1.tgz#37f4030523fb3032c8801fab48c36c373004de7e"
+  integrity sha512-3gjrSxsy4ka/lGQsTDY8oYYtkt2esBvQiceGBB4PykXxHoGRz14tbCK31Zc6DHEnIeqsjMUGbq+wEly5UViStQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    jss "10.5.1"
+    tiny-warning "^1.0.2"
+
+jss-plugin-vendor-prefixer@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.5.1.tgz#45a183a3a0eb097bdfab0986b858d99920c0bbd8"
+  integrity sha512-cLkH6RaPZWHa1TqSfd2vszNNgxT1W0omlSjAd6hCFHp3KIocSrW21gaHjlMU26JpTHwkc+tJTCQOmE/O1A4FKQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    css-vendor "^2.0.8"
+    jss "10.5.1"
+
+jss@10.5.1, jss@^10.5.1:
+  version "10.5.1"
+  resolved "https://registry.yarnpkg.com/jss/-/jss-10.5.1.tgz#93e6b2428c840408372d8b548c3f3c60fa601c40"
+  integrity sha512-hbbO3+FOTqVdd7ZUoTiwpHzKXIo5vGpMNbuXH1a0wubRSWLWSBvwvaq4CiHH/U42CmjOnp6lVNNs/l+Z7ZdDmg==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+    csstype "^3.0.2"
+    indefinite-observable "^2.0.1"
+    is-in-browser "^1.1.3"
+    tiny-warning "^1.0.2"
+
 "jsx-ast-utils@^2.4.1 || ^3.0.0":
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
@@ -5893,11 +6208,6 @@ lodash.memoize@~3.0.3:
   resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-3.0.4.tgz#2dcbd2c287cbc0a55cc42328bd0c736150d53e3f"
   integrity sha1-LcvSwofLwKVcxCMovQxzYVDVPj8=
 
-lodash.merge@^4.6.2:
-  version "4.6.2"
-  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
-  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-
 lodash.truncate@^4.4.2:
   version "4.4.2"
   resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@@ -5908,7 +6218,7 @@ lodash.uniq@^4.5.0:
   resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
   integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
 
-lodash@4.*, lodash@^4.14.1, lodash@^4.17.10, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4:
+lodash@4.*, lodash@^4.14.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.4:
   version "4.17.21"
   resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
   integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -6012,9 +6322,9 @@ map-obj@^1.0.0, map-obj@^1.0.1:
   integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=
 
 marked@^2.0.0:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.5.tgz#2d15c759b9497b0e7b5b57f4c2edabe1002ef9e7"
-  integrity sha512-yfCEUXmKhBPLOzEC7c+tc4XZdIeTdGoRCZakFMkCxodr7wDXqoapIME4wjcpBPJLNyUnKJ3e8rb8wlAgnLnaDw==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.1.tgz#5e7ed7009bfa5c95182e4eb696f85e948cefcee3"
+  integrity sha512-5+/fKgMv2hARmMW7DOpykr2iLhl0NgjyELk5yn92iE7z8Se1IS9n3UsFm86hFXIkvMBmVxki8+ckcpjBeyo/hw==
 
 matches-selector@0.0.1:
   version "0.0.1"
@@ -6045,6 +6355,11 @@ media-typer@0.3.0:
   resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
   integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
 
+memoize-one@^5.0.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
+  integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
+
 meow@^3.3.0:
   version "3.7.0"
   resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb"
@@ -6092,17 +6407,17 @@ miller-rabin@^4.0.0:
     bn.js "^4.0.0"
     brorand "^1.0.1"
 
-mime-db@1.47.0, mime-db@^1.28.0:
-  version "1.47.0"
-  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c"
-  integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==
+mime-db@1.46.0, mime-db@^1.28.0:
+  version "1.46.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.46.0.tgz#6267748a7f799594de3cbc8cde91def349661cee"
+  integrity sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==
 
 mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.19, mime-types@~2.1.24:
-  version "2.1.30"
-  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d"
-  integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==
+  version "2.1.29"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.29.tgz#1d4ab77da64b91f5f72489df29236563754bb1b2"
+  integrity sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==
   dependencies:
-    mime-db "1.47.0"
+    mime-db "1.46.0"
 
 mime@^2.3.1, mime@^2.4.5:
   version "2.5.2"
@@ -6120,9 +6435,9 @@ mimic-response@^1.0.0:
   integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
 
 mini-css-extract-plugin@^1.3.5:
-  version "1.6.0"
-  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.6.0.tgz#b4db2525af2624899ed64a23b0016e0036411893"
-  integrity sha512-nPFKI7NSy6uONUo9yn2hIfb9vyYvkFu95qki0e21DQ9uaqNKDP15DGpK0KnV6wDroWxPHtExrdEwx/yDQ8nVRw==
+  version "1.3.9"
+  resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-1.3.9.tgz#47a32132b0fd97a119acd530e8421e8f6ab16d5e"
+  integrity sha512-Ac4s+xhVbqlyhXS5J/Vh/QXUz3ycXlCqoCPpg0vdfhsIBH9eg/It/9L1r1XhSCH737M1lqcWnMuWL13zcygn5A==
   dependencies:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
@@ -6284,7 +6599,7 @@ nan@^2.14.2:
   resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19"
   integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==
 
-nanoid@^3.1.23:
+nanoid@^3.1.20, nanoid@^3.1.23:
   version "3.1.23"
   resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.23.tgz#f744086ce7c2bc47ee0a8472574d5c78e4183a81"
   integrity sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==
@@ -6347,7 +6662,7 @@ node-gyp@^7.1.2:
     tar "^6.0.2"
     which "^2.0.2"
 
-node-releases@^1.1.71:
+node-releases@^1.1.70, node-releases@^1.1.71:
   version "1.1.72"
   resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe"
   integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==
@@ -6454,11 +6769,16 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
   integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
 
-object-inspect@^1.10.3, object-inspect@^1.7.0, object-inspect@^1.9.0:
+object-inspect@^1.10.3:
   version "1.10.3"
   resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369"
   integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==
 
+object-inspect@^1.7.0, object-inspect@^1.9.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a"
+  integrity sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==
+
 object-is@^1.0.2, object-is@^1.1.2:
   version "1.1.5"
   resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.1.5.tgz#b9deeaa5fc7f1846a0faecdceec138e5778f53ac"
@@ -6893,7 +7213,12 @@ pngquant-bin@^6.0.0:
     execa "^4.0.0"
     logalot "^2.0.0"
 
-popper.js@^1.14.7, popper.js@^1.16.1:
+popper.js@1.16.1-lts:
+  version "1.16.1-lts"
+  resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1-lts.tgz#cf6847b807da3799d80ee3d6d2f90df8a3f50b05"
+  integrity sha512-Kjw8nKRl1m+VrSFCoVGPph93W/qrSO7ZkqPpTf7F4bk/sqcfWK019dWBUpE/fBOsOQY1dks/Bmcbfn1heM/IsA==
+
+popper.js@^1.16.1:
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/popper.js/-/popper.js-1.16.1.tgz#2a223cb3dc7b6213d740e40372be40de43e65b1b"
   integrity sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==
@@ -6943,9 +7268,9 @@ postcss-discard-overridden@^5.0.1:
   integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==
 
 postcss-loader@^5.0.0:
-  version "5.3.0"
-  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-5.3.0.tgz#1657f869e48d4fdb018a40771c235e499ee26244"
-  integrity sha512-/+Z1RAmssdiSLgIZwnJHwBMnlABPgF7giYzTN2NOfr9D21IJZ4mQC1R2miwp80zno9M4zMD/umGI8cR+2EL5zw==
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/postcss-loader/-/postcss-loader-5.1.0.tgz#8a36f18b8989bee94172626b25f2b9cc6160fb43"
+  integrity sha512-tGgKZF6Ntn16zIWXt7yKV19L0rISaozHPCfdPt+aHOnTZrreeqVR6hCkFhZYfJ6KgpyIFRkKuW8ygHtUid4GlA==
   dependencies:
     cosmiconfig "^7.0.0"
     klona "^2.0.4"
@@ -7156,13 +7481,22 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
   integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
 
 postcss@^8.2.15, postcss@^8.2.9:
-  version "8.3.0"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f"
-  integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==
+  version "8.2.15"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.15.tgz#9e66ccf07292817d226fc315cbbf9bc148fbca65"
+  integrity sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==
   dependencies:
     colorette "^1.2.2"
     nanoid "^3.1.23"
-    source-map-js "^0.6.2"
+    source-map "^0.6.1"
+
+postcss@^8.2.6:
+  version "8.2.7"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.7.tgz#48ed8d88b4de10afa0dfd1c3f840aa57b55c4d47"
+  integrity sha512-DsVLH3xJzut+VT+rYr0mtvOtpTjSyqDwPf5EZWXcb0uAKfitGpTY9Ec+afi2+TgdN8rWS9Cs88UDYehKo/RvOw==
+  dependencies:
+    colorette "^1.2.2"
+    nanoid "^3.1.20"
+    source-map "^0.6.1"
 
 precond@^0.2.3:
   version "0.2.3"
@@ -7208,7 +7542,7 @@ prop-types-exact@^1.2.0:
     object.assign "^4.1.0"
     reflect.ownkeys "^0.2.0"
 
-prop-types@^15.7.2:
+prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
   version "15.7.2"
   resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
   integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@@ -7370,16 +7704,41 @@ react-dom@^17.0.1:
     object-assign "^4.1.1"
     scheduler "^0.20.2"
 
-"react-is@^16.12.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.2:
-  version "17.0.2"
-  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
-  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+react-input-autosize@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
+  integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==
+  dependencies:
+    prop-types "^15.5.8"
+
+"react-is@^16.12.0 || ^17.0.0", "react-is@^16.8.0 || ^17.0.0", react-is@^17.0.0:
+  version "17.0.1"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.1.tgz#5b3531bd76a645a4c9fb6e693ed36419e3301339"
+  integrity sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==
 
 react-is@^16.13.1, react-is@^16.7.0, react-is@^16.8.1:
   version "16.13.1"
   resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
   integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
 
+react-is@^17.0.2:
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
+react-select@^4.2.1:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81"
+  integrity sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==
+  dependencies:
+    "@babel/runtime" "^7.12.0"
+    "@emotion/cache" "^11.4.0"
+    "@emotion/react" "^11.1.1"
+    memoize-one "^5.0.0"
+    prop-types "^15.6.0"
+    react-input-autosize "^3.0.0"
+    react-transition-group "^4.3.0"
+
 react-shallow-renderer@^16.13.1:
   version "16.14.1"
   resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
@@ -7388,6 +7747,11 @@ react-shallow-renderer@^16.13.1:
     object-assign "^4.1.1"
     react-is "^16.12.0 || ^17.0.0"
 
+react-table@^7.6.3:
+  version "7.7.0"
+  resolved "https://registry.yarnpkg.com/react-table/-/react-table-7.7.0.tgz#e2ce14d7fe3a559f7444e9ecfe8231ea8373f912"
+  integrity sha512-jBlj70iBwOTvvImsU9t01LjFjy4sXEtclBovl3mTiqjz23Reu0DKnRza4zlLtOPACx6j2/7MrQIthIK1Wi+LIA==
+
 react-test-renderer@^17.0.0:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-17.0.2.tgz#4cd4ae5ef1ad5670fc0ef776e8cc7e1231d9866c"
@@ -7398,6 +7762,16 @@ react-test-renderer@^17.0.0:
     react-shallow-renderer "^16.13.1"
     scheduler "^0.20.2"
 
+react-transition-group@^4.0.0, react-transition-group@^4.3.0, react-transition-group@^4.4.0:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9"
+  integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw==
+  dependencies:
+    "@babel/runtime" "^7.5.5"
+    dom-helpers "^5.0.1"
+    loose-envify "^1.4.0"
+    prop-types "^15.6.2"
+
 react@^17.0.1:
   version "17.0.2"
   resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
@@ -7678,6 +8052,13 @@ rgba-regex@^1.0.0:
   resolved "https://registry.yarnpkg.com/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
   integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
 
+rifm@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
+  integrity sha512-DSOJTWHD67860I5ojetXdEQRIBvF6YcpNe53j0vn1vp9EUb9N80EiZTxgP+FkDKorWC8PZw052kTF4C1GOivCQ==
+  dependencies:
+    "@babel/runtime" "^7.3.1"
+
 rimraf@^2.5.4:
   version "2.7.1"
   resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
@@ -7749,11 +8130,11 @@ sass-resources-loader@^2.2.1:
     loader-utils "^2.0.0"
 
 sass@^1.24.4:
-  version "1.34.0"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.0.tgz#e46d5932d8b0ecc4feb846d861f26a578f7f7172"
-  integrity sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw==
+  version "1.32.8"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.8.tgz#f16a9abd8dc530add8834e506878a2808c037bdc"
+  integrity sha512-Sl6mIeGpzjIUZqvKnKETfMf0iDAswD9TNlv13A7aAF3XZlRPMq4VvJWBC2N2DXbp94MQVdNSFG6LfF/iOXrPHQ==
   dependencies:
-    chokidar ">=3.0.0 <4.0.0"
+    chokidar ">=2.0.0 <4.0.0"
 
 sax@^1.2.4, sax@~1.2.4:
   version "1.2.4"
@@ -7832,10 +8213,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
   integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
 
-semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
-  version "7.3.5"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
-  integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
+semver@^7.2.1, semver@^7.3.2, semver@^7.3.4:
+  version "7.3.4"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
+  integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
   dependencies:
     lru-cache "^6.0.0"
 
@@ -7871,6 +8252,11 @@ shallow-clone@^3.0.0:
   dependencies:
     kind-of "^6.0.2"
 
+shallowequal@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/shallowequal/-/shallowequal-1.1.0.tgz#188d521de95b9087404fd4dcb68b13df0ae4e7f8"
+  integrity sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==
+
 shasum-object@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/shasum-object/-/shasum-object-1.0.0.tgz#0b7b74ff5b66ecf9035475522fa05090ac47e29e"
@@ -8375,6 +8761,22 @@ style-loader@^2.0.0:
     loader-utils "^2.0.0"
     schema-utils "^3.0.0"
 
+styled-components@^5.2.1:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.2.1.tgz#6ed7fad2dc233825f64c719ffbdedd84ad79101a"
+  integrity sha512-sBdgLWrCFTKtmZm/9x7jkIabjFNVzCUeKfoQsM6R3saImkUnjx0QYdLwJHBjY9ifEcmjDamJDVfknWm1yxZPxQ==
+  dependencies:
+    "@babel/helper-module-imports" "^7.0.0"
+    "@babel/traverse" "^7.4.5"
+    "@emotion/is-prop-valid" "^0.8.8"
+    "@emotion/stylis" "^0.8.4"
+    "@emotion/unitless" "^0.7.4"
+    babel-plugin-styled-components ">= 1"
+    css-to-react-native "^3.0.0"
+    hoist-non-react-statics "^3.0.0"
+    shallowequal "^1.1.0"
+    supports-color "^5.5.0"
+
 stylehacks@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb"
@@ -8400,7 +8802,7 @@ supports-color@^2.0.0:
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
   integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=
 
-supports-color@^5.3.0:
+supports-color@^5.3.0, supports-color@^5.5.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
   integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
@@ -8420,16 +8822,16 @@ svg-pathdata@^5.0.2:
   integrity sha512-TAAvLNSE3fEhyl/Da19JWfMAdhSXTYeviXsLSoDT1UM76ADj5ndwAPX1FKQEgB/gFMPavOy6tOqfalXKUiXrow==
 
 svg2ttf@^5.0.0:
-  version "5.2.0"
-  resolved "https://registry.yarnpkg.com/svg2ttf/-/svg2ttf-5.2.0.tgz#75c9b8d3c799a91c893ef7bdbfa72d8e00a48a5c"
-  integrity sha512-CzxPnSm2/CrMnJuKlXVllOx+q9wuarbIMi4Vf14eJoeESRqAOxVZiH0Ias71mhyXYGgz88A4T/E8fN/Y8eXoYA==
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/svg2ttf/-/svg2ttf-5.1.0.tgz#5513bef392414b51dfc8622e7d11259008375d1d"
+  integrity sha512-+YRNac48DDW0Q8+/4nc3gn6gRbh8MMLEa9ao06plW7mQyhJbPGEE5EobVYTFiDVInOpRExSCo19sjhL9zYsbsA==
   dependencies:
-    argparse "^2.0.1"
+    argparse "^1.0.6"
     cubic2quad "^1.0.0"
     lodash "^4.17.10"
     microbuffer "^1.0.0"
     svgpath "^2.1.5"
-    xmldom "~0.5.0"
+    xmldom "~0.1.22"
 
 svgicons2svgfont@^9.0.3:
   version "9.2.0"
@@ -8484,6 +8886,11 @@ svgpath@^2.1.5:
   resolved "https://registry.yarnpkg.com/svgpath/-/svgpath-2.3.1.tgz#b102334bebd2244b4818460ba2ebad52716a0d43"
   integrity sha512-wNz6lCoj+99GMoyU7SozTfPqiLHz6WcJYZ30Z+F4lF/gPtxWHBCpZ4DhoDI0+oZ0dObKyYsJdSPGbL2mJq/qCg==
 
+symbol-observable@1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
+  integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
+
 syntax-error@^1.1.1:
   version "1.4.0"
   resolved "https://registry.yarnpkg.com/syntax-error/-/syntax-error-1.4.0.tgz#2d9d4ff5c064acb711594a3e3b95054ad51d907c"
@@ -8629,6 +9036,11 @@ timsort@^0.3.0:
   resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
   integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=
 
+tiny-warning@^1.0.2:
+  version "1.0.3"
+  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.1"
   resolved "https://registry.yarnpkg.com/tippy.js/-/tippy.js-6.3.1.tgz#3788a007be7015eee0fd589a66b98fb3f8f10181"
@@ -8714,9 +9126,9 @@ ttf2eot@^2.0.0:
     microbuffer "^1.0.0"
 
 ttf2woff2@^4.0.1:
-  version "4.0.2"
-  resolved "https://registry.yarnpkg.com/ttf2woff2/-/ttf2woff2-4.0.2.tgz#9f26109f1ed6644b1ca28a27588be05840e077ad"
-  integrity sha512-Qqogwe1ypkSbPOhCSo6z9DqbVeJj7wP8YHJE6nES78KBXZN7DcdBiWzNV9SNQHM+N2GRMDnBMQbyyXHuDfbimg==
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/ttf2woff2/-/ttf2woff2-4.0.1.tgz#00a79587da5f150ad2ecfbfdf0dbb4b5eead4674"
+  integrity sha512-Hd0bQb7nf6t1aSk7lz8XFGcbcc6PVN3UGWy/evDmF/2/B7HoTlEt8OlMHZvKDizLmdNOWyy4S9qt160GpJ8/BA==
   dependencies:
     bindings "^1.5.0"
     bufferstreams "^3.0.0"
@@ -8761,11 +9173,6 @@ type-fest@^0.11.0:
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
   integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
 
-type-fest@^0.20.2:
-  version "0.20.2"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
-  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
-
 type-fest@^0.8.1:
   version "0.8.1"
   resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
@@ -8785,14 +9192,14 @@ typedarray@^0.0.6:
   integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
 
 ua-parser-js@^0.7.23:
-  version "0.7.28"
-  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31"
-  integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g==
+  version "0.7.24"
+  resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.24.tgz#8d3ecea46ed4f1f1d63ec25f17d8568105dc027c"
+  integrity sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw==
 
 uglify-js@^3.1.4:
-  version "3.13.8"
-  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.8.tgz#7c2f9f2553f611f3ff592bdc19c6fb208dc60afb"
-  integrity sha512-PvFLMFIQHfIjFFlvAch69U2IvIxK9TNzNWt1SxZGp9JZ/v70yvqIQuiJeVPPtUMOzoNt+aNRDk4wgxb34wvEqA==
+  version "3.13.0"
+  resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.13.0.tgz#66ed69f7241f33f13531d3d51d5bcebf00df7f69"
+  integrity sha512-TWYSWa9T2pPN4DIJYbU9oAjQx+5qdV5RUDxwARg8fmJZrD/V27Zj0JngW5xg1DFz42G0uDYl2XhzF6alSzD62w==
 
 umd@^3.0.0:
   version "3.0.3"
@@ -9108,19 +9515,19 @@ webpack-sources@^2.1.1:
     source-map "^0.6.1"
 
 webpack@^5.21.2:
-  version "5.37.1"
-  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.37.1.tgz#2deb5acd350583c1ab9338471f323381b0b0c14b"
-  integrity sha512-btZjGy/hSjCAAVHw+cKG+L0M+rstlyxbO2C+BOTaQ5/XAnxkDrP5sVbqWhXgo4pL3X2dcOib6rqCP20Zr9PLow==
+  version "5.24.3"
+  resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.24.3.tgz#6ec0f5059f8d7c7961075fa553cfce7b7928acb3"
+  integrity sha512-x7lrWZ7wlWAdyKdML6YPvfVZkhD1ICuIZGODE5SzKJjqI9A4SpqGTjGJTc6CwaHqn19gGaoOR3ONJ46nYsn9rw==
   dependencies:
     "@types/eslint-scope" "^3.7.0"
-    "@types/estree" "^0.0.47"
+    "@types/estree" "^0.0.46"
     "@webassemblyjs/ast" "1.11.0"
     "@webassemblyjs/wasm-edit" "1.11.0"
     "@webassemblyjs/wasm-parser" "1.11.0"
-    acorn "^8.2.1"
+    acorn "^8.0.4"
     browserslist "^4.14.5"
     chrome-trace-event "^1.0.2"
-    enhanced-resolve "^5.8.0"
+    enhanced-resolve "^5.7.0"
     es-module-lexer "^0.4.0"
     eslint-scope "^5.1.1"
     events "^3.2.0"
@@ -9136,7 +9543,7 @@ webpack@^5.21.2:
     watchpack "^2.0.0"
     webpack-sources "^2.1.1"
 
-which-boxed-primitive@^1.0.2:
+which-boxed-primitive@^1.0.1:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
   integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
@@ -9218,14 +9625,14 @@ wrappy@1:
   integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
 
 ws@^7.3.1, ws@~7.4.2:
-  version "7.4.6"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
-  integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
+  version "7.4.4"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.4.tgz#383bc9742cb202292c9077ceab6f6047b17f2d59"
+  integrity sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==
 
-xmldom@~0.5.0:
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.5.0.tgz#193cb96b84aa3486127ea6272c4596354cb4962e"
-  integrity sha512-Foaj5FXVzgn7xFzsKeNIde9g6aFBxTPi37iwsno8QvApmtg7KYrr+OPyRHcJF7dud2a5nGRBXK3n0dL62Gf7PA==
+xmldom@~0.1.22:
+  version "0.1.31"
+  resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff"
+  integrity sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==
 
 xtend@^4.0.0, xtend@^4.0.1, xtend@^4.0.2, xtend@~4.0.1:
   version "4.0.2"
@@ -9273,9 +9680,9 @@ yaml@^1.10.0, yaml@^1.7.2:
   integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
 
 yargs-parser@^20.2.2:
-  version "20.2.7"
-  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
-  integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
+  version "20.2.6"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20"
+  integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==
 
 yargs@^16.1.1:
   version "16.2.0"
