Hi Murtuza,

We tested the patch and everything looks fine.  We also refactors some
parts to include things like strict equality and using let/const instead of
var.  The updated patch is attached.
In the future, it will be more valuable to have the translation to ES6 and
the feature work in separate commits so it is easier to understand what
changed.

Sincerely,

Joao and Victoria



On Tue, Apr 24, 2018 at 4:58 AM Akshay Joshi <akshay.jo...@enterprisedb.com>
wrote:

> On Tue, Apr 24, 2018 at 1:17 PM, Dave Page <dp...@pgadmin.org> wrote:
>
>> Akshay, could you review/commit this please?
>>
>> Please also update the release_notes_3_1.rst file when you commit
>> user-visible changes, to make it easier to build the release notes.
>>
>
>    Sure
>
>>
>> Thanks.
>>
>> On Tue, Apr 24, 2018 at 8:45 AM, Murtuza Zabuawala <
>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>
>>> Hi Dave,
>>>
>>> Please find the updated patch, Now we are able to lock wcFrame and
>>> wcPanel both.
>>>
>>> --
>>> Regards,
>>> Murtuza Zabuawala
>>> EnterpriseDB: http://www.enterprisedb.com
>>> The Enterprise PostgreSQL Company
>>>
>>>
>>> On Thu, Apr 5, 2018 at 6:32 PM, Robert Eckhardt <reckha...@pivotal.io>
>>> wrote:
>>>
>>>>
>>>>
>>>> On Wed, Apr 4, 2018 at 11:31 PM, Khushboo Vashi <
>>>> khushboo.va...@enterprisedb.com> wrote:
>>>>
>>>>>
>>>>>
>>>>> On Wed, Apr 4, 2018 at 8:09 PM, Dave Page <dp...@pgadmin.org> wrote:
>>>>>
>>>>>>
>>>>>>
>>>>>> On Wed, Apr 4, 2018 at 12:54 PM, Murtuza Zabuawala <
>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>
>>>>>>> On Wed, Apr 4, 2018 at 5:00 PM, Dave Page <dp...@pgadmin.org> wrote:
>>>>>>>
>>>>>>>>
>>>>>>>>
>>>>>>>> On Wed, Apr 4, 2018 at 10:45 AM, Murtuza Zabuawala <
>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>
>>>>>>>>> On Wed, Apr 4, 2018 at 2:47 PM, Dave Page <dp...@pgadmin.org>
>>>>>>>>> wrote:
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> On Wed, Apr 4, 2018 at 7:20 AM, Murtuza Zabuawala <
>>>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>>>
>>>>>>>>>>> Hi Dave,
>>>>>>>>>>>
>>>>>>>>>>> On Tue, Apr 3, 2018 at 9:03 PM, Dave Page <dp...@pgadmin.org>
>>>>>>>>>>> wrote:
>>>>>>>>>>>
>>>>>>>>>>>> Hi
>>>>>>>>>>>>
>>>>>>>>>>>> On Tue, Apr 3, 2018 at 12:56 PM, Murtuza Zabuawala <
>>>>>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>>>>>
>>>>>>>>>>>>> Hi,
>>>>>>>>>>>>>
>>>>>>>>>>>>> Thanks Joao for reviewing.
>>>>>>>>>>>>>
>>>>>>>>>>>>> PFA updated patch.
>>>>>>>>>>>>>
>>>>>>>>>>>>> On Tue, Apr 3, 2018 at 1:11 AM, Joao De Almeida Pereira <
>>>>>>>>>>>>> jdealmeidapere...@pivotal.io> wrote:
>>>>>>>>>>>>>
>>>>>>>>>>>>>> Hello,
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> On Mon, Apr 2, 2018 at 10:07 AM Murtuza Zabuawala <
>>>>>>>>>>>>>> murtuza.zabuaw...@enterprisedb.com> wrote:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> ​Hello,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Please find updated patch,
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> Now layout will be locked after user updates its
>>>>>>>>>>>>>>> preferences, w
>>>>>>>>>>>>>>> e have used ​
>>>>>>>>>>>>>>> templated variable in the javascript file
>>>>>>>>>>>>>>> ​ because we do not have preference module or preference
>>>>>>>>>>>>>>> cache available when the page loads and panels gets rendered,
>>>>>>>>>>>>>>> ​I
>>>>>>>>>>>>>>> ​ also
>>>>>>>>>>>>>>> made changes in JS tests as per Joao's review comments.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>> Looks like everything is working when we change the lock.
>>>>>>>>>>>>>> As a personal preferences I would prefer to see this in at
>>>>>>>>>>>>>> least 2 commits, one that is related to the preference issue and 
>>>>>>>>>>>>>> another
>>>>>>>>>>>>>> one that is related to this story.
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> All the tests are working, but he linter is failing:
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> /tmp/build/4a5630c2/pivotal-rm-3155/web /tmp/build/4a5630c2
>>>>>>>>>>>>>>  
>>>>>>>>>>>>>> <https://gpdb-dev.bosh.pivotalci.info/teams/main/pipelines/pgadmin-feature-branches/jobs/pivotal-rm-3155-python-linter/builds/3#L5ab982d1:9>
>>>>>>>>>>>>>> ./pgadmin/misc/__init__.py:78: [E303] too many blank lines (2)
>>>>>>>>>>>>>>  
>>>>>>>>>>>>>> <https://gpdb-dev.bosh.pivotalci.info/teams/main/pipelines/pgadmin-feature-branches/jobs/pivotal-rm-3155-python-linter/builds/3#L5ab982d1:10>
>>>>>>>>>>>>>> 1       E303 too many blank lines (2)
>>>>>>>>>>>>>>  
>>>>>>>>>>>>>> <https://gpdb-dev.bosh.pivotalci.info/teams/main/pipelines/pgadmin-feature-branches/jobs/pivotal-rm-3155-python-linter/builds/3#L5ab982d1:11>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>> 1
>>>>>>>>>>>>>>
>>>>>>>>>>>>> ​Fixed​
>>>>>>>>>>>>>
>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>>>>> @Dave/Pivotal team,
>>>>>>>>>>>>>>> The given patch is working fine for all the Tabs/Panels (all
>>>>>>>>>>>>>>> the panels from main window as well as from Query tool and 
>>>>>>>>>>>>>>> Debugger) but
>>>>>>>>>>>>>>> I'm facing an issue while handling the Browser tree section, It 
>>>>>>>>>>>>>>> is a wcDocer
>>>>>>>>>>>>>>> frame <http://docker.api.webcabin.org/module-wcFrame.html>
>>>>>>>>>>>>>>> and not a wcDocker panel
>>>>>>>>>>>>>>> <http://docker.api.webcabin.org/module-wcPanel.html>. Like
>>>>>>>>>>>>>>> wcDocker panel, wcDocker frame do not provide any API so that a 
>>>>>>>>>>>>>>> developer
>>>>>>>>>>>>>>> can prevent drag-drop functionality on it.
>>>>>>>>>>>>>>>
>>>>>>>>>>>>>>
>>>>>>>>>>>> It's not working fine for me. For example, if I put the SQL
>>>>>>>>>>>> Panel on it's own below the properties/stats panels (so it looks 
>>>>>>>>>>>> like
>>>>>>>>>>>> pgAdmin 3 used to by default), and then lock the layout, I can 
>>>>>>>>>>>> un-dock the
>>>>>>>>>>>> SQL panel into a dialogue, but then cannot re-dock it. I can do 
>>>>>>>>>>>> weird
>>>>>>>>>>>> things with the browser tree as well, probably because it's a 
>>>>>>>>>>>> frame as you
>>>>>>>>>>>> say.
>>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>>> ​That is expected behaviour ​because once you drag the panel out
>>>>>>>>>>> of the group of Panels then it becomes individual Frame, That is 
>>>>>>>>>>> what the
>>>>>>>>>>> author of the wcDocker replied on my question,
>>>>>>>>>>> *"A panel must either be initialized as movable or non-movable
>>>>>>>>>>> from the beginning and never changed because it generates a 
>>>>>>>>>>> different
>>>>>>>>>>> arrangement of elements depending. This feature should only ever be 
>>>>>>>>>>> used
>>>>>>>>>>> within the onCreate method of the panel. I should probably have 
>>>>>>>>>>> been more
>>>>>>>>>>> clear about this limitation in the documentation."*
>>>>>>>>>>>
>>>>>>>>>>>
>>>>>>>>>> So does it become a panel again if a second panel is added to the
>>>>>>>>>> new tab group?
>>>>>>>>>>
>>>>>>>>> ​No, it stays Frame.​
>>>>>>>>>
>>>>>>>>> As far as I understand Panel needs a Frame to render itself if it
>>>>>>>>> is not attached to the main docker instance.​
>>>>>>>>>
>>>>>>>>>>
>>>>>>>>>> There must be some way we can lock a tab that's not part of a
>>>>>>>>>> group.
>>>>>>>>>>
>>>>>>>>> At a moment there is no way of ​
>>>>>>>>> locking frames out of the box :(
>>>>>>>>> ​
>>>>>>>>>
>>>>>>>>
>>>>>>>> Hmm, so the question becomes: do we include the lock feature, but
>>>>>>>> rename it to "Lock Tabs" or something similar, or leave it out 
>>>>>>>> altogether?
>>>>>>>> It clearly doesn't do everything we want right now.
>>>>>>>>
>>>>>>> ​I would say lets include the feature by adding warning note that
>>>>>>> this feature works with default layout only, And I don't think most user
>>>>>>> will try to drag drop Browser panel ​
>>>>>>> anyway, meanwhile I'll check what changes are required in main
>>>>>>> source code to make the Frame lock.
>>>>>>>
>>>>>>
>>>>>> Anyone else have any thoughts on this? Personally I don't like
>>>>>> including half-baked features.
>>>>>>
>>>>>> +1, but we need to find out the way as this feature is requested by
>>>>> many users.
>>>>>
>>>>
>>>> 100% agree. I can convince my self that this feature request has to do
>>>> with locking panels into a certain layout. I can also convince myself that
>>>> the same request is simple because users are frustrated with the fact that
>>>> the tabs and panes move around and they find that behavior annoying.
>>>>
>>>> -- Rob
>>>>
>>>>
>>>>> --
>>>>>> Dave Page
>>>>>> Blog: http://pgsnake.blogspot.com
>>>>>> Twitter: @pgsnake
>>>>>>
>>>>>> EnterpriseDB UK: http://www.enterprisedb.com
>>>>>> The Enterprise PostgreSQL Company
>>>>>>
>>>>>
>>>>>
>>>>
>>>
>>
>>
>> --
>> Dave Page
>> Blog: http://pgsnake.blogspot.com
>> Twitter: @pgsnake
>>
>> EnterpriseDB UK: http://www.enterprisedb.com
>> The Enterprise PostgreSQL Company
>>
>
>
>
> --
> *Akshay Joshi*
>
> *Sr. Software Architect *
>
>
>
> *Phone: +91 20-3058-9517 <+91%2020%203058%209517>Mobile: +91 976-788-8246
> <+91%2097678%2088246>*
>
diff --git a/docs/en_US/images/preferences_misc_layout.png b/docs/en_US/images/preferences_misc_layout.png
index e69de29b..d04d3c85 100644
Binary files a/docs/en_US/images/preferences_misc_layout.png and b/docs/en_US/images/preferences_misc_layout.png differ
diff --git a/docs/en_US/preferences.rst b/docs/en_US/preferences.rst
index e1ddc9b6..c631ce2a 100644
--- a/docs/en_US/preferences.rst
+++ b/docs/en_US/preferences.rst
@@ -97,6 +97,11 @@ Expand the *Miscellaneous* node to specify miscellaneous display preferences.
 
 * Use the *User language* drop-down listbox to select the display language for the client.
 
+.. image:: images/preferences_misc_layout.png
+    :alt: Preferences dialog user layout section
+
+* When the *Lock layout?* switch is set to *True*, user will not be able to drag and drop panels.
+
 **The Paths Node**
 
 Expand the *Paths* node to specify the locations of supporting utility and help files.
diff --git a/web/pgadmin/browser/static/css/browser.css b/web/pgadmin/browser/static/css/browser.css
index 3ba330d3..7d6e7823 100644
--- a/web/pgadmin/browser/static/css/browser.css
+++ b/web/pgadmin/browser/static/css/browser.css
@@ -66,3 +66,7 @@ samp,
 .pg-login-icon {
   font-size: 16px;
 }
+
+.no-mouse-event {
+  pointer-events: none;
+}
diff --git a/web/pgadmin/browser/static/js/browser.js b/web/pgadmin/browser/static/js/browser.js
index 897d2708..b3b3dc35 100644
--- a/web/pgadmin/browser/static/js/browser.js
+++ b/web/pgadmin/browser/static/js/browser.js
@@ -115,6 +115,7 @@ define('pgadmin.browser', [
         isPrivate: true,
         icon: 'fa fa-binoculars',
         content: '<div id="tree" class="aciTree"></div>',
+        isMoveable: true,
       }),
       // Properties of the object node
       'properties': new pgAdmin.Browser.Panel({
@@ -690,6 +691,9 @@ define('pgadmin.browser', [
           pgBrowser.keyboardNavigation.init();
           modifyAnimation.modifyAcitreeAnimation(self);
           modifyAnimation.modifyAlertifyAnimation(self);
+          pgBrowser.Events.trigger(
+            'pgadmin-browser:preferences-updated', this, arguments
+          );
         },
         error: function(xhr) {
           try {
diff --git a/web/pgadmin/browser/static/js/panel.js b/web/pgadmin/browser/static/js/panel.js
index ab0681a9..92e39c02 100644
--- a/web/pgadmin/browser/static/js/panel.js
+++ b/web/pgadmin/browser/static/js/panel.js
@@ -1,187 +1,269 @@
-define(
-  ['underscore', 'sources/pgadmin', 'jquery', 'wcdocker'],
-  function(_, pgAdmin, $) {
-
-    var pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {},
-      wcDocker = window.wcDocker;
-
-    pgAdmin.Browser.Panel = function(options) {
-      var defaults = [
-        'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
-        'isPrivate', 'content', 'icon', 'events', 'onCreate', 'elContainer',
-        'canHide', 'limit',
-      ];
-      _.extend(this, _.pick(options, defaults));
-    };
-
-    _.extend(pgAdmin.Browser.Panel.prototype, {
-      name: '',
-      title: '',
-      width: 300,
-      height: 600,
-      showTitle: true,
-      isCloseable: true,
-      isPrivate: false,
-      content: '',
-      icon: '',
-      panel: null,
-      onCreate: null,
-      elContainer: false,
-      limit: null,
-      load: function(docker, title) {
-        var that = this;
-        if (!that.panel) {
-          docker.registerPanelType(that.name, {
-            title: that.title,
-            isPrivate: that.isPrivate,
-            limit: that.limit,
-            onCreate: function(myPanel) {
-              $(myPanel).data('pgAdminName', that.name);
-              myPanel.initSize(that.width, that.height);
-
-              if (!that.showTitle)
-                myPanel.title(false);
-              else {
-                var title_elem = '<a href="#" tabindex="0" class="panel-link-heading">' + (title || that.title) + '</a>';
-                myPanel.title(title_elem);
-                if (that.icon != '')
-                  myPanel.icon(that.icon);
-              }
+import $ from 'jquery';
+import _ from 'underscore';
+import pgAdmin from 'sources/pgadmin';
+import 'wcdocker';
 
-              var $container = $('<div>', {
-                'class': 'pg-panel-content',
-              }).append($(that.content));
-
-              myPanel.closeable(!!that.isCloseable);
-              myPanel.layout().addItem($container);
-              that.panel = myPanel;
-              if (that.events && _.isObject(that.events)) {
-                _.each(that.events, function(v, k) {
-                  if (v && _.isFunction(v)) {
-                    myPanel.on(k, v);
-                  }
-                });
-              }
-              _.each([
-                wcDocker.EVENT.UPDATED, wcDocker.EVENT.VISIBILITY_CHANGED,
-                wcDocker.EVENT.BEGIN_DOCK, wcDocker.EVENT.END_DOCK,
-                wcDocker.EVENT.GAIN_FOCUS, wcDocker.EVENT.LOST_FOCUS,
-                wcDocker.EVENT.CLOSED, wcDocker.EVENT.BUTTON,
-                wcDocker.EVENT.ATTACHED, wcDocker.EVENT.DETACHED,
-                wcDocker.EVENT.MOVE_STARTED, wcDocker.EVENT.MOVE_ENDED,
-                wcDocker.EVENT.MOVED, wcDocker.EVENT.RESIZE_STARTED,
-                wcDocker.EVENT.RESIZE_ENDED, wcDocker.EVENT.RESIZED,
-                wcDocker.EVENT.SCROLLED,
-              ], function(ev) {
-                myPanel.on(ev, that.eventFunc.bind(myPanel, ev));
-              });
+let pgBrowser = pgAdmin.Browser = pgAdmin.Browser || {};
+let wcDocker = window.wcDocker;
 
-              if (that.onCreate && _.isFunction(that.onCreate)) {
-                that.onCreate.apply(that, [myPanel, $container]);
-              }
+pgAdmin.Browser.Panel = function(options) {
+  const defaults = [
+    'name', 'title', 'width', 'height', 'showTitle', 'isCloseable',
+    'isPrivate', 'content', 'icon', 'events', 'onCreate', 'elContainer',
+    'canHide', 'limit', 'isMoveable',
+  ];
+  _.extend(this, _.pick(options, defaults));
+};
 
-              if (that.elContainer) {
-                myPanel.pgElContainer = $container;
-                $container.addClass('pg-el-container');
-                _.each([
-                  wcDocker.EVENT.RESIZED, wcDocker.EVENT.ATTACHED,
-                  wcDocker.EVENT.DETACHED, wcDocker.EVENT.VISIBILITY_CHANGED,
-                ], function(ev) {
-                  myPanel.on(ev, that.resizedContainer.bind(myPanel));
-                });
-                that.resizedContainer.apply(myPanel);
-              }
+_.extend(pgAdmin.Browser.Panel.prototype, {
+  name: '',
+  title: '',
+  width: 300,
+  height: 600,
+  showTitle: true,
+  isCloseable: true,
+  isPrivate: false,
+  isMoveable: false,
+  content: '',
+  icon: '',
+  panel: null,
+  onCreate: null,
+  elContainer: false,
+  limit: null,
+  load: function(docker, title) {
+    const that = this;
+    if (!that.panel) {
+      docker.registerPanelType(that.name, {
+        title: that.title,
+        isPrivate: that.isPrivate,
+        limit: that.limit,
+        onCreate: function(myPanel) {
+          $(myPanel).data('pgAdminName', that.name);
+          myPanel.initSize(that.width, that.height);
 
-              // Bind events only if they are configurable
-              if (that.canHide) {
-                _.each([wcDocker.EVENT.CLOSED, wcDocker.EVENT.VISIBILITY_CHANGED],
-                  function(ev) {
-                    myPanel.on(ev, that.handleVisibility.bind(myPanel, ev));
-                  });
+          if (!that.showTitle)
+            myPanel.title(false);
+          else {
+            const title_elem = '<a href="#" tabindex="0" class="panel-link-heading">' + (title || that.title) + '</a>';
+            myPanel.title(title_elem);
+            if (that.icon !== '')
+              myPanel.icon(that.icon);
+          }
+
+          const $container = $('<div>', {
+            'class': 'pg-panel-content',
+          }).append($(that.content));
+
+          myPanel.closeable(!!that.isCloseable);
+          myPanel.layout().addItem($container);
+          that.panel = myPanel;
+          if (that.events && _.isObject(that.events)) {
+            _.each(that.events, function(v, k) {
+              if (v && _.isFunction(v)) {
+                myPanel.on(k, v);
               }
-            },
+            });
+          }
+          _.each([
+            wcDocker.EVENT.UPDATED, wcDocker.EVENT.VISIBILITY_CHANGED,
+            wcDocker.EVENT.BEGIN_DOCK, wcDocker.EVENT.END_DOCK,
+            wcDocker.EVENT.GAIN_FOCUS, wcDocker.EVENT.LOST_FOCUS,
+            wcDocker.EVENT.CLOSED, wcDocker.EVENT.BUTTON,
+            wcDocker.EVENT.ATTACHED, wcDocker.EVENT.DETACHED,
+            wcDocker.EVENT.MOVE_STARTED, wcDocker.EVENT.MOVE_ENDED,
+            wcDocker.EVENT.MOVED, wcDocker.EVENT.RESIZE_STARTED,
+            wcDocker.EVENT.RESIZE_ENDED, wcDocker.EVENT.RESIZED,
+            wcDocker.EVENT.SCROLLED,
+          ], function(ev) {
+            myPanel.on(ev, that.eventFunc.bind(myPanel, ev));
           });
-        }
-      },
-      eventFunc: function(eventName) {
-        var name = $(this).data('pgAdminName');
-
-        try {
-          pgBrowser.Events.trigger(
-            'pgadmin-browser:panel', eventName, this, arguments
-          );
-          pgBrowser.Events.trigger(
-            'pgadmin-browser:panel:' + eventName, this, arguments
-          );
 
-          if (name) {
-            pgBrowser.Events.trigger(
-              'pgadmin-browser:panel-' + name, eventName, this, arguments
-            );
-            pgBrowser.Events.trigger(
-              'pgadmin-browser:panel-' + name + ':' + eventName, this, arguments
-            );
+          if (that.onCreate && _.isFunction(that.onCreate)) {
+            that.onCreate.apply(that, [myPanel, $container]);
           }
-        } catch (e) {
-          console.warn(e.stack || e);
-        }
-      },
-      resizedContainer: function() {
-        var p = this;
-
-        if (p.pgElContainer && !p.pgResizeTimeout) {
-          if (!p.isVisible()) {
-            clearTimeout(p.pgResizeTimeout);
-            p.pgResizeTimeout = null;
-
-            return;
+
+          if (that.elContainer) {
+            myPanel.pgElContainer = $container;
+            $container.addClass('pg-el-container');
+            _.each([
+              wcDocker.EVENT.RESIZED, wcDocker.EVENT.ATTACHED,
+              wcDocker.EVENT.DETACHED, wcDocker.EVENT.VISIBILITY_CHANGED,
+            ], function(ev) {
+              myPanel.on(ev, that.resizedContainer.bind(myPanel));
+            });
+            that.resizedContainer.apply(myPanel);
           }
-          p.pgResizeTimeout = setTimeout(
-            function() {
-              var w = p.width();
-              p.pgResizeTimeout = null;
-
-              if (w <= 480) {
-                w = 'xs';
-              } else if (w < 600) {
-                w = 'sm';
-              } else if (w < 768) {
-                w = 'md';
-              } else {
-                w = 'lg';
-              }
 
-              p.pgElContainer.attr('el', w);
-            },
-            100
+          // Bind events only if they are configurable
+          if (that.canHide) {
+            _.each([wcDocker.EVENT.CLOSED, wcDocker.EVENT.VISIBILITY_CHANGED],
+              function(ev) {
+                myPanel.on(ev, that.handleVisibility.bind(myPanel, ev));
+              });
+          }
+
+          // Update panels as per new preference value
+          window.top.pgAdmin.Browser.Events.on(
+            'pgadmin-browser:preferences-updated',
+            that.updatePanel, myPanel
           );
-        }
-      },
-      handleVisibility: function(eventName) {
-        // Currently this function only works with dashboard panel but
-        // as per need it can be extended
-        if (this._type != 'dashboard' || _.isUndefined(pgAdmin.Dashboard))
-          return;
-
-        if (eventName == 'panelClosed') {
-          pgBrowser.save_current_layout(pgBrowser);
-          pgAdmin.Dashboard.toggleVisibility(false);
-        } else if (eventName == 'panelVisibilityChanged') {
-          if (pgBrowser.tree) {
-            pgBrowser.save_current_layout(pgBrowser);
-            var selectedNode = pgBrowser.tree.selected();
-            // Discontinue this event after first time visible
-            this.off(wcDocker.EVENT.VISIBILITY_CHANGED);
-            if (!_.isUndefined(pgAdmin.Dashboard))
-              pgAdmin.Dashboard.toggleVisibility(true);
-            // Explicitly trigger tree selected event when we add the tab.
-            pgBrowser.Events.trigger('pgadmin-browser:tree:selected', selectedNode,
-              pgBrowser.tree.itemData(selectedNode), pgBrowser.Node);
+
+          myPanel.moveable(that.isMoveable);
+          // If not frame then update it
+          if(!that.isMoveable) {
+            that.updatePanel.call(myPanel);
           }
-        }
-      },
+        },
+      });
+    }
+  },
+
+  // This method will allow us to lock the individual Frame panel
+  lockFramePanel: function() {
+    // Find each wcFrame
+    $('.wcFrameTitleBar').each(function() {
+      // If it's not locked
+      if(!$(this).hasClass('no-mouse-event')) {
+        $(this).addClass('no-mouse-event');
+      }
+    });
+  },
 
+  // This method will allow us to unlock the individual Frame panel
+  unlockFramePanel: function() {
+    $('.wcFrameTitleBar').each(function() {
+      // If it's locked
+      if($(this).hasClass('no-mouse-event')) {
+        $(this).removeClass('no-mouse-event');
+      }
     });
+  },
+
+  // We'll execute this function after preferences update
+  updatePanel: function() {
+    let panel = this;
+    let preference = pgBrowser.get_preference(
+      'miscellaneous', 'lock_panel_layout'
+    );
+
+    // If the Panel opens in iframe
+    if(_.isUndefined(preference) || _.isNull(preference)) {
+      if(window.opener && window.opener.pgAdmin.Browser){
+        preference = window.opener.pgAdmin.Browser.get_preference(
+          'miscellaneous', 'lock_panel_layout'
+        );
+      } else if(window.top && window.top.pgAdmin.Browser) {
+        preference = window.top.pgAdmin.Browser.get_preference(
+          'miscellaneous', 'lock_panel_layout'
+        );
+      }
+    }
+
+    // If still preference is missing then don't do anything
+    if(_.isUndefined(preference) || _.isNull(preference)) {
+      return;
+    }
+
+    /*
+     preference.value == true then Lock
+     preference.value == false then UnLock
+     panel.moveable() returns True if panel is moveable
+    */
+    let isMoveable = !preference.value;
+
+    // If not moveable then lock it
+    if (isMoveable) {
+      pgAdmin.Browser.Panel.prototype.unlockFramePanel();
+    } else {
+      pgAdmin.Browser.Panel.prototype.lockFramePanel();
+    }
+
+    // If no change in settings then return from here
+    if(panel.moveable() === isMoveable)
+      return;
+
+    pgBrowser.utils.isPanelMoveable = isMoveable;
+    panel.moveable(isMoveable);
+  },
+
+  eventFunc: function(eventName) {
+    const name = $(this).data('pgAdminName');
+
+    try {
+      pgBrowser.Events.trigger(
+        'pgadmin-browser:panel', eventName, this, arguments
+      );
+      pgBrowser.Events.trigger(
+        'pgadmin-browser:panel:' + eventName, this, arguments
+      );
+
+      if (name) {
+        pgBrowser.Events.trigger(
+          'pgadmin-browser:panel-' + name, eventName, this, arguments
+        );
+        pgBrowser.Events.trigger(
+          'pgadmin-browser:panel-' + name + ':' + eventName, this, arguments
+        );
+      }
+    } catch (e) {
+      console.warn(e.stack || e);
+    }
+  },
+  resizedContainer: function() {
+    const p = this;
+
+    if (p.pgElContainer && !p.pgResizeTimeout) {
+      if (!p.isVisible()) {
+        clearTimeout(p.pgResizeTimeout);
+        p.pgResizeTimeout = null;
+
+        return;
+      }
+      p.pgResizeTimeout = setTimeout(
+        function() {
+          let w = p.width();
+          p.pgResizeTimeout = null;
+
+          if (w <= 480) {
+            w = 'xs';
+          } else if (w < 600) {
+            w = 'sm';
+          } else if (w < 768) {
+            w = 'md';
+          } else {
+            w = 'lg';
+          }
+
+          p.pgElContainer.attr('el', w);
+        },
+        100
+      );
+    }
+  },
+  handleVisibility: function(eventName) {
+    // Currently this function only works with dashboard panel but
+    // as per need it can be extended
+    if (this._type !== 'dashboard' || _.isUndefined(pgAdmin.Dashboard))
+      return;
+
+    if (eventName === 'panelClosed') {
+      pgBrowser.save_current_layout(pgBrowser);
+      pgAdmin.Dashboard.toggleVisibility(false);
+    } else if (eventName === 'panelVisibilityChanged') {
+      if (pgBrowser.tree) {
+        pgBrowser.save_current_layout(pgBrowser);
+        const selectedNode = pgBrowser.tree.selected();
+        // Discontinue this event after first time visible
+        this.off(wcDocker.EVENT.VISIBILITY_CHANGED);
+        if (!_.isUndefined(pgAdmin.Dashboard))
+          pgAdmin.Dashboard.toggleVisibility(true);
+        // Explicitly trigger tree selected event when we add the tab.
+        pgBrowser.Events.trigger('pgadmin-browser:tree:selected', selectedNode,
+          pgBrowser.tree.itemData(selectedNode), pgBrowser.Node);
+      }
+    }
+  },
+
+});
 
-    return pgAdmin.Browser.Panel;
-  });
+module.exports = pgAdmin.Browser.Panel;
diff --git a/web/pgadmin/browser/templates/browser/js/utils.js b/web/pgadmin/browser/templates/browser/js/utils.js
index be3b7122..aa37925b 100644
--- a/web/pgadmin/browser/templates/browser/js/utils.js
+++ b/web/pgadmin/browser/templates/browser/js/utils.js
@@ -26,6 +26,7 @@ define('pgadmin.browser.utils',
     is_indent_with_tabs: '{{ editor_indent_with_tabs }}' == 'True',
     app_name: '{{ app_name }}',
     pg_libpq_version: {{pg_libpq_version|e}},
+    isPanelMoveable: true,
 
     counter: {total: 0, loaded: 0},
     registerScripts: function (ctx) {
diff --git a/web/pgadmin/misc/__init__.py b/web/pgadmin/misc/__init__.py
index da28413a..7b9521c2 100644
--- a/web/pgadmin/misc/__init__.py
+++ b/web/pgadmin/misc/__init__.py
@@ -68,6 +68,11 @@ class MiscModule(PgAdminModule):
             category_label=gettext('User language'),
             options=lang_options
         )
+        self.misc_preference.register(
+            'layout', 'lock_panel_layout',
+            gettext("Lock layout?"), 'boolean', False,
+            category_label=gettext('Layout')
+        )
 
     def get_exposed_url_endpoints(self):
         """
diff --git a/web/pgadmin/settings/__init__.py b/web/pgadmin/settings/__init__.py
index 269bfdf1..2b8eb2ea 100644
--- a/web/pgadmin/settings/__init__.py
+++ b/web/pgadmin/settings/__init__.py
@@ -115,7 +115,7 @@ def store(setting=None, value=None):
             store_setting(setting, value)
     except Exception as e:
         success = 0
-        errormsg = e.message
+        errormsg = str(e)
 
     try:
         info = traceback.format_exc()
diff --git a/web/pgadmin/static/css/webcabin.overrides.css b/web/pgadmin/static/css/webcabin.overrides.css
index 18d89f84..102b3451 100644
--- a/web/pgadmin/static/css/webcabin.overrides.css
+++ b/web/pgadmin/static/css/webcabin.overrides.css
@@ -399,3 +399,11 @@ i.wcTabIcon {
   background-size: 18px !important;
   height: 18px;
 }
+
+.wcTabScroller {
+  position: initial;
+}
+
+.wcPanelTab {
+  pointer-events: auto;
+}
diff --git a/web/pgadmin/tools/datagrid/static/js/datagrid.js b/web/pgadmin/tools/datagrid/static/js/datagrid.js
index b0ed60f6..27323180 100644
--- a/web/pgadmin/tools/datagrid/static/js/datagrid.js
+++ b/web/pgadmin/tools/datagrid/static/js/datagrid.js
@@ -527,10 +527,21 @@ define('pgadmin.datagrid', [
           // Set panel title and icon
           queryToolPanel.title('<span title="'+panel_title+'">'+panel_title+'</span>');
           queryToolPanel.icon('fa fa-bolt');
+          queryToolPanel.moveable(pgBrowser.utils.isPanelMoveable);
+          pgBrowser.Events.on(
+            'pgadmin-browser:preferences-updated',
+            pgAdmin.Browser.Panel.prototype.updatePanel, queryToolPanel
+          );
+
           queryToolPanel.focus();
 
           // Listen on the panel closed event.
           queryToolPanel.on(wcDocker.EVENT.CLOSED, function() {
+            pgBrowser.Events.off(
+              'pgadmin-browser:preferences-updated',
+              pgAdmin.Browser.Panel.prototype.updatePanel, queryToolPanel
+            );
+
             $.ajax({
               url: url_for('datagrid.close', {'trans_id': trans_obj.gridTransId}),
               method: 'GET',
diff --git a/web/pgadmin/tools/debugger/static/js/debugger_ui.js b/web/pgadmin/tools/debugger/static/js/debugger_ui.js
index f9049d81..3a96fd8b 100644
--- a/web/pgadmin/tools/debugger/static/js/debugger_ui.js
+++ b/web/pgadmin/tools/debugger/static/js/debugger_ui.js
@@ -725,9 +725,19 @@ define([
                         );
 
                       panel.focus();
+                      panel.moveable(pgBrowser.utils.isPanelMoveable);
+                      pgBrowser.Events.on(
+                        'pgadmin-browser:preferences-updated',
+                        pgAdmin.Browser.Panel.prototype.updatePanel, panel
+                      );
 
                       // Panel Closed event
                       panel.on(wcDocker.EVENT.CLOSED, function() {
+                        pgBrowser.Events.off(
+                          'pgadmin-browser:preferences-updated',
+                          pgAdmin.Browser.Panel.prototype.updatePanel, panel
+                        );
+
                         var closeUrl = url_for('debugger.close', {
                           'trans_id': res.data.debuggerTransId,
                         });
diff --git a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
index f95389c6..be731728 100644
--- a/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
+++ b/web/pgadmin/tools/sqleditor/static/js/sqleditor.js
@@ -155,6 +155,7 @@ define('tools.querytool', [
         height: '20%',
         isCloseable: false,
         isPrivate: true,
+        isMoveable: true,
       });
 
       sql_panel.load(main_docker);
diff --git a/web/regression/javascript/browser/panel_spec.js b/web/regression/javascript/browser/panel_spec.js
index e69de29b..3f0184c8 100644
--- a/web/regression/javascript/browser/panel_spec.js
+++ b/web/regression/javascript/browser/panel_spec.js
@@ -0,0 +1,52 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2018, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import pgAdmin from 'pgadmin';
+import Panel from 'browser/panel';
+
+describe('Panel', function () {
+  let pgBrowser = pgAdmin.Browser;
+  let Panel = pgAdmin.Browser.Panel;
+  let testPanel;
+  beforeEach(function () {
+    testPanel = new Panel({
+      name: 'test',
+      title: 'Test',
+      isMoveable: true,
+    });
+  });
+
+  describe('when we create a panel', function () {
+    describe('and it is moveable panel', function () {
+      it('it should call moveable method with true as argument', function () {
+        expect(testPanel.isMoveable).toBe(true);
+      });
+    });
+
+    describe('and it is non-moveable panel', function () {
+      beforeEach(function () {
+        testPanel.isMoveable = false;
+      });
+      it('it should call moveable method with false as argument', function () {
+        expect(testPanel.isMoveable).toBe(false);
+      });
+    });
+
+    describe('and user created panel without defining isMoveable then it should be moveable', function () {
+      beforeEach(function () {
+        testPanel = new Panel({
+          name: 'test',
+          title: 'Test',
+        });
+      });
+      it('it should call moveable method with true as argument', function () {
+        expect(testPanel.isMoveable).toBe(true);
+      });
+    });
+  });
+});
diff --git a/web/webpack.test.config.js b/web/webpack.test.config.js
index 306a030b..9932907f 100644
--- a/web/webpack.test.config.js
+++ b/web/webpack.test.config.js
@@ -80,6 +80,7 @@ module.exports = {
       'pgadmin.alertifyjs': sourcesDir + '/js/alertify.pgadmin.defaults',
       'pgadmin.backgrid': sourcesDir + '/js/backgrid.pgadmin',
       'pgadmin.backform': sourcesDir + '/js/backform.pgadmin',
+      'wcdocker': path.join(__dirname, './node_modules/webcabin-docker/Build/wcDocker'),
     },
   },
 };

Reply via email to