Bug 1309193 - Implement sidebar toggle button in Net Panel Toolbar;r=honza,jsnajdr draft
authorFred Lin <gasolin@mozilla.com>
Thu, 27 Oct 2016 11:53:18 +0800
changeset 432035 565f3f862301f5906dfb1957dc5b188e80c4237f
parent 432034 e36f864a05312ee102f1409121af8acaaedecba3
child 535524 43552c98e14535a5c70a98d7a52040610c92224e
push id34173
push userbmo:gasolin@mozilla.com
push dateTue, 01 Nov 2016 04:10:56 +0000
reviewershonza, jsnajdr
bugs1309193
milestone52.0a1
Bug 1309193 - Implement sidebar toggle button in Net Panel Toolbar;r=honza,jsnajdr MozReview-Commit-ID: 2mHiawLmrPy
devtools/client/locales/en-US/netmonitor.properties
devtools/client/netmonitor/actions/index.js
devtools/client/netmonitor/actions/moz.build
devtools/client/netmonitor/actions/sidebar.js
devtools/client/netmonitor/components/moz.build
devtools/client/netmonitor/components/toggle-button.js
devtools/client/netmonitor/constants.js
devtools/client/netmonitor/netmonitor-view.js
devtools/client/netmonitor/netmonitor.xul
devtools/client/netmonitor/reducers/index.js
devtools/client/netmonitor/reducers/moz.build
devtools/client/netmonitor/reducers/sidebar.js
devtools/client/netmonitor/requests-menu-view.js
devtools/client/netmonitor/toolbar-view.js
devtools/client/themes/netmonitor.css
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -487,20 +487,16 @@ netmonitor.toolbar.filterFreetext.key=Cm
 # LOCALIZATION NOTE (netmonitor.toolbar.clear): This is the label displayed
 # in the network toolbar for the "Clear" button.
 netmonitor.toolbar.clear=Clear
 
 # LOCALIZATION NOTE (netmonitor.toolbar.perf): This is the label displayed
 # in the network toolbar for the performance analysis button.
 netmonitor.toolbar.perf=Toggle performance analysis…
 
-# LOCALIZATION NOTE (netmonitor.panesButton.tooltip): This is the tooltip for
-# the button that toggles the panes visible or hidden in the netmonitor UI.
-netmonitor.panesButton.tooltip=Toggle network info
-
 # LOCALIZATION NOTE (netmonitor.summary.url): This is the label displayed
 # in the network details headers tab identifying the URL.
 netmonitor.summary.url=Request URL:
 
 # LOCALIZATION NOTE (netmonitor.summary.method): This is the label displayed
 # in the network details headers tab identifying the method.
 netmonitor.summary.method=Request method:
 
--- a/devtools/client/netmonitor/actions/index.js
+++ b/devtools/client/netmonitor/actions/index.js
@@ -1,8 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const filters = require("./filters");
+const sidebar = require("./sidebar");
 
-module.exports = Object.assign({}, filters);
+module.exports = Object.assign({}, filters, sidebar);
--- a/devtools/client/netmonitor/actions/moz.build
+++ b/devtools/client/netmonitor/actions/moz.build
@@ -1,9 +1,10 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'filters.js',
-    'index.js'
+    'index.js',
+    'sidebar.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/actions/sidebar.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+  DISABLE_TOGGLE_BUTTON,
+  SHOW_SIDEBAR,
+  TOGGLE_SIDEBAR,
+} = require("../constants");
+
+/**
+ * Change ToggleButton disabled state.
+ *
+ * @param {boolean} disabled - expected button disabled state
+ */
+function disableToggleButton(disabled) {
+  return {
+    type: DISABLE_TOGGLE_BUTTON,
+    disabled: disabled,
+  };
+}
+
+/**
+ * Change sidebar visible state.
+ *
+ * @param {boolean} visible - expected sidebar visible state
+ */
+function showSidebar(visible) {
+  return {
+    type: SHOW_SIDEBAR,
+    visible: visible,
+  };
+}
+
+/**
+ * Toggle to show/hide sidebar.
+ */
+function toggleSidebar() {
+  return {
+    type: TOGGLE_SIDEBAR,
+  };
+}
+
+module.exports = {
+  disableToggleButton,
+  showSidebar,
+  toggleSidebar,
+};
--- a/devtools/client/netmonitor/components/moz.build
+++ b/devtools/client/netmonitor/components/moz.build
@@ -1,8 +1,9 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'filter-buttons.js',
+    'toggle-button.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/components/toggle-button.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* globals NetMonitorView */
+"use strict";
+
+const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../l10n");
+const Actions = require("../actions/index");
+
+// Shortcuts
+const { button } = DOM;
+
+/**
+ * Button used to toggle sidebar
+ */
+function ToggleButton({
+  disabled,
+  onToggle,
+  visible,
+}) {
+  let className = ["devtools-button"];
+  if (!visible) {
+    className.push("pane-collapsed");
+  }
+  let titleMsg = visible ? L10N.getStr("collapseDetailsPane") :
+                           L10N.getStr("expandDetailsPane");
+
+  return button({
+    id: "details-pane-toggle",
+    className: className.join(" "),
+    title: titleMsg,
+    disabled: disabled,
+    tabIndex: "0",
+    onMouseDown: onToggle,
+  });
+}
+
+ToggleButton.propTypes = {
+  disabled: PropTypes.bool.isRequired,
+  onToggle: PropTypes.func.isRequired,
+  visible: PropTypes.bool.isRequired,
+};
+
+module.exports = connect(
+  (state) => ({
+    disabled: state.sidebar.toggleButtonDisabled,
+    visible: state.sidebar.visible,
+  }),
+  (dispatch) => ({
+    onToggle: () => {
+      dispatch(Actions.toggleSidebar());
+
+      let requestsMenu = NetMonitorView.RequestsMenu;
+      let selectedIndex = requestsMenu.selectedIndex;
+
+      // Make sure there's a selection if the button is pressed, to avoid
+      // showing an empty network details pane.
+      if (selectedIndex == -1 && requestsMenu.itemCount) {
+        requestsMenu.selectedIndex = 0;
+      } else {
+        requestsMenu.selectedIndex = -1;
+      }
+    },
+  })
+)(ToggleButton);
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -1,11 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const actionTypes = {
   TOGGLE_FILTER: "TOGGLE_FILTER",
   ENABLE_FILTER_ONLY: "ENABLE_FILTER_ONLY",
+  TOGGLE_SIDEBAR: "TOGGLE_SIDEBAR",
+  SHOW_SIDEBAR: "SHOW_SIDEBAR",
+  DISABLE_TOGGLE_BUTTON: "DISABLE_TOGGLE_BUTTON",
 };
 
 module.exports = actionTypes;
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -111,20 +111,16 @@ var NetMonitorView = {
   /**
    * Initializes the UI for all the displayed panes.
    */
   _initializePanes: function () {
     dumpn("Initializing the NetMonitorView panes");
 
     this._body = $("#body");
     this._detailsPane = $("#details-pane");
-    this._detailsPaneToggleButton = $("#details-pane-toggle");
-
-    this._collapsePaneString = L10N.getStr("collapseDetailsPane");
-    this._expandPaneString = L10N.getStr("expandDetailsPane");
 
     this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
     this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
     this.toggleDetailsPane({ visible: false });
 
     // Disable the performance statistics mode.
     if (!Prefs.statistics) {
       $("#request-menu-context-perf").hidden = true;
@@ -138,17 +134,16 @@ var NetMonitorView = {
    */
   _destroyPanes: Task.async(function* () {
     dumpn("Destroying the NetMonitorView panes");
 
     Prefs.networkDetailsWidth = this._detailsPane.getAttribute("width");
     Prefs.networkDetailsHeight = this._detailsPane.getAttribute("height");
 
     this._detailsPane = null;
-    this._detailsPaneToggleButton = null;
 
     for (let p of this._editorPromises.values()) {
       let editor = yield p;
       editor.destroy();
     }
   }),
 
   /**
@@ -167,29 +162,24 @@ var NetMonitorView = {
    *        - visible: true if the pane should be shown, false to hide
    *        - animated: true to display an animation on toggle
    *        - delayed: true to wait a few cycles before toggle
    *        - callback: a function to invoke when the toggle finishes
    * @param number tabIndex [optional]
    *        The index of the intended selected tab in the details pane.
    */
   toggleDetailsPane: function (flags, tabIndex) {
-    let pane = this._detailsPane;
-    let button = this._detailsPaneToggleButton;
-
-    ViewHelpers.togglePane(flags, pane);
+    ViewHelpers.togglePane(flags, this._detailsPane);
 
     if (flags.visible) {
       this._body.classList.remove("pane-collapsed");
-      button.classList.remove("pane-collapsed");
-      button.setAttribute("tooltiptext", this._collapsePaneString);
+      gStore.dispatch(Actions.showSidebar(true));
     } else {
       this._body.classList.add("pane-collapsed");
-      button.classList.add("pane-collapsed");
-      button.setAttribute("tooltiptext", this._expandPaneString);
+      gStore.dispatch(Actions.showSidebar(false));
     }
 
     if (tabIndex !== undefined) {
       $("#event-details-pane").selectedIndex = tabIndex;
     }
   },
 
   /**
@@ -284,19 +274,16 @@ var NetMonitorView = {
     let editor = new Editor(DEFAULT_EDITOR_CONFIG);
     editor.appendTo($(id)).then(() => deferred.resolve(editor));
 
     return deferred.promise;
   },
 
   _body: null,
   _detailsPane: null,
-  _detailsPaneToggleButton: null,
-  _collapsePaneString: "",
-  _expandPaneString: "",
   _editorPromises: new Map()
 };
 
 /**
  * Functions handling the sidebar details view.
  */
 function SidebarView() {
   dumpn("SidebarView was instantiated");
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -31,21 +31,18 @@
         <toolbarbutton id="requests-menu-network-summary-button"
                        class="devtools-toolbarbutton icon-and-text"
                        data-localization="tooltiptext=netmonitor.toolbar.perf"/>
         <textbox id="requests-menu-filter-freetext-text"
                  class="devtools-filterinput"
                  type="search"
                  required="true"
                  data-localization="placeholder=netmonitor.toolbar.filterFreetext.label"/>
-        <toolbarbutton id="details-pane-toggle"
-                       class="devtools-toolbarbutton"
-                       data-localization="tooltiptext=netmonitor.panesButton.tooltip"
-                       disabled="true"
-                       tabindex="0"/>
+        <html:div xmlns="http://www.w3.org/1999/xhtml"
+                  id="react-details-pane-toggle-hook"/>
       </hbox>
       <hbox id="network-table-and-sidebar"
             class="devtools-responsive-container"
             flex="1">
         <vbox id="network-table" flex="1" class="devtools-main-content">
           <toolbar id="requests-menu-toolbar"
                    class="devtools-toolbar"
                    align="center">
--- a/devtools/client/netmonitor/reducers/index.js
+++ b/devtools/client/netmonitor/reducers/index.js
@@ -1,11 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { combineReducers } = require("devtools/client/shared/vendor/redux");
 const filters = require("./filters");
+const sidebar = require("./sidebar");
 
 module.exports = combineReducers({
   filters,
+  sidebar,
 });
--- a/devtools/client/netmonitor/reducers/moz.build
+++ b/devtools/client/netmonitor/reducers/moz.build
@@ -1,9 +1,10 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
     'filters.js',
-    'index.js'
+    'index.js',
+    'sidebar.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/reducers/sidebar.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const I = require("devtools/client/shared/vendor/immutable");
+const {
+  DISABLE_TOGGLE_BUTTON,
+  SHOW_SIDEBAR,
+  TOGGLE_SIDEBAR,
+} = require("../constants");
+
+const SidebarState = I.Record({
+  toggleButtonDisabled: true,
+  visible: false,
+});
+
+function disableToggleButton(state, action) {
+  return state.set("toggleButtonDisabled", action.disabled);
+}
+
+function showSidebar(state, action) {
+  return state.set("visible", action.visible);
+}
+
+function toggleSidebar(state, action) {
+  return state.set("visible", !state.visible);
+}
+
+function sidebar(state = new SidebarState(), action) {
+  switch (action.type) {
+    case DISABLE_TOGGLE_BUTTON:
+      return disableToggleButton(state, action);
+    case SHOW_SIDEBAR:
+      return showSidebar(state, action);
+    case TOGGLE_SIDEBAR:
+      return toggleSidebar(state, action);
+    default:
+      return state;
+  }
+}
+
+module.exports = sidebar;
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -122,16 +122,18 @@ function RequestsMenuView() {
 
 RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function (store) {
     dumpn("Initializing the RequestsMenuView");
 
+    this.store = store;
+
     let widgetParentEl = $("#requests-menu-contents");
     this.widget = new SideMenuWidget(widgetParentEl);
     this._splitter = $("#network-inspector-view-splitter");
     this._summary = $("#requests-menu-network-summary-button");
     this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
     this.userInputTimer = Cc["@mozilla.org/timer;1"]
       .createInstance(Ci.nsITimer);
 
@@ -760,17 +762,17 @@ RequestsMenuView.prototype = Heritage.ex
 
   /**
    * Removes all network requests and closes the sidebar if open.
    */
   clear: function () {
     NetMonitorController.NetworkEventsHandler.clearMarkers();
     NetMonitorView.Sidebar.toggle(false);
 
-    $("#details-pane-toggle").disabled = true;
+    this.store.dispatch(Actions.disableToggleButton(true));
     $("#requests-menu-empty-notice").hidden = false;
 
     this.empty();
     this.refreshSummary();
   },
 
   /**
    * Refreshes the status displayed in this container's footer, providing
@@ -1073,17 +1075,17 @@ RequestsMenuView.prototype = Heritage.ex
         NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
       }
     }
 
     // We're done flushing all the requests, clear the update queue.
     this._updateQueue = [];
     this._addQueue = [];
 
-    $("#details-pane-toggle").disabled = !this.itemCount;
+    this.store.dispatch(Actions.disableToggleButton(!this.itemCount));
     $("#requests-menu-empty-notice").hidden = !!this.itemCount;
 
     // Make sure all the requests are sorted and filtered.
     // Freshly added requests may not yet contain all the information required
     // for sorting and filtering predicates, so this is done each time the
     // network requests table is flushed (don't worry, events are drained first
     // so this doesn't happen once per network event update).
     this.sortContents();
--- a/devtools/client/netmonitor/toolbar-view.js
+++ b/devtools/client/netmonitor/toolbar-view.js
@@ -1,38 +1,38 @@
 /* globals dumpn, $, NetMonitorView */
 "use strict";
 
 const { createFactory, DOM } = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
 const FilterButtons = createFactory(require("./components/filter-buttons"));
+const ToggleButton = createFactory(require("./components/toggle-button"));
 const { L10N } = require("./l10n");
 
 // Shortcuts
 const { button } = DOM;
 
 /**
  * Functions handling the toolbar view: expand/collapse button etc.
  */
 function ToolbarView() {
   dumpn("ToolbarView was instantiated");
-
-  this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
 }
 
 ToolbarView.prototype = {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function (store) {
     dumpn("Initializing the ToolbarView");
 
     this._clearContainerNode = $("#react-clear-button-hook");
     this._filterContainerNode = $("#react-filter-buttons-hook");
+    this._toggleContainerNode = $("#react-details-pane-toggle-hook");
 
     // clear button
     ReactDOM.render(button({
       id: "requests-menu-clear-button",
       className: "devtools-button devtools-clear-icon",
       title: L10N.getStr("netmonitor.toolbar.clear"),
       onClick: () => {
         NetMonitorView.RequestsMenu.clear();
@@ -40,46 +40,27 @@ ToolbarView.prototype = {
     }), this._clearContainerNode);
 
     // filter button
     ReactDOM.render(Provider(
       { store },
       FilterButtons()
     ), this._filterContainerNode);
 
-    this._detailsPaneToggleButton = $("#details-pane-toggle");
-    this._detailsPaneToggleButton.addEventListener("mousedown",
-      this._onTogglePanesPressed, false);
+    ReactDOM.render(Provider(
+      { store },
+      ToggleButton()
+    ), this._toggleContainerNode);
   },
 
   /**
    * Destruction function, called when the debugger is closed.
    */
   destroy: function () {
     dumpn("Destroying the ToolbarView");
 
     ReactDOM.unmountComponentAtNode(this._clearContainerNode);
     ReactDOM.unmountComponentAtNode(this._filterContainerNode);
-
-    this._detailsPaneToggleButton.removeEventListener("mousedown",
-      this._onTogglePanesPressed, false);
-  },
-
-  /**
-   * Listener handling the toggle button click event.
-   */
-  _onTogglePanesPressed: function () {
-    let requestsMenu = NetMonitorView.RequestsMenu;
-    let selectedIndex = requestsMenu.selectedIndex;
-
-    // Make sure there's a selection if the button is pressed, to avoid
-    // showing an empty network details pane.
-    if (selectedIndex == -1 && requestsMenu.itemCount) {
-      requestsMenu.selectedIndex = 0;
-    } else {
-      requestsMenu.selectedIndex = -1;
-    }
-  },
-
-  _detailsPaneToggleButton: null
+    ReactDOM.unmountComponentAtNode(this._toggleContainerNode);
+  }
 };
 
 exports.ToolbarView = ToolbarView;
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -2,17 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #toolbar-labels {
   overflow: hidden;
 }
 
-#react-clear-button-hook {
+#react-clear-button-hook,
+#react-details-pane-toggle-hook {
   display: flex;
 }
 
 /**
  * Collapsed details pane needs to be truly hidden to prevent both accessibility
  * tools and keyboard from accessing its contents.
  */
 #details-pane.pane-collapsed {
@@ -563,24 +564,24 @@
 /* Size Column */
 .theme-firebug .requests-menu-subitem.requests-menu-size {
   text-align: end;
   padding-inline-end: 4px;
 }
 
 /* Network request details */
 
-#details-pane-toggle:-moz-locale-dir(ltr),
-#details-pane-toggle.pane-collapsed:-moz-locale-dir(rtl) {
-  list-style-image: var(--theme-pane-collapse-image);
+#details-pane-toggle:-moz-locale-dir(ltr)::before,
+#details-pane-toggle.pane-collapsed:-moz-locale-dir(rtl)::before {
+  background-image: var(--theme-pane-collapse-image);
 }
 
-#details-pane-toggle.pane-collapsed:-moz-locale-dir(ltr),
-#details-pane-toggle:-moz-locale-dir(rtl) {
-  list-style-image: var(--theme-pane-expand-image);
+#details-pane-toggle.pane-collapsed:-moz-locale-dir(ltr)::before,
+#details-pane-toggle:-moz-locale-dir(rtl)::before {
+  background-image: var(--theme-pane-expand-image);
 }
 
 /* Network request details tabpanels */
 
 .tabpanel-content {
   background-color: var(--theme-sidebar-background);
 }