Bug 1426041 - rearrange and add 2 columns toolbar layout to support bottom and side dock mode;r=honza draft
authorFred Lin <gasolin@gmail.com>
Thu, 04 Jan 2018 11:40:27 +0800
changeset 717184 7a3cefaa4977f9306ea35b0ff682df6fdb61cd5a
parent 717183 ca379fcca95b1f4a3744242ea8647004b99b3507
child 717185 5a09257dfa380edff510675983f144739535d19e
push id94587
push userbmo:gasolin@mozilla.com
push dateMon, 08 Jan 2018 10:31:14 +0000
reviewershonza
bugs1426041
milestone59.0a1
Bug 1426041 - rearrange and add 2 columns toolbar layout to support bottom and side dock mode;r=honza new toolbar layout base on https://mozilla.invisionapp.com/share/2XEEY0RYA#/screens/263398480_Console-Network_-_Input_Field_Focus_Ring * The media query for render single or 2 columns is 920px * Add separators * apply same filter bar style as webconsole MozReview-Commit-ID: AD6sSmaCq3
devtools/client/netmonitor/src/assets/styles/Toolbar.css
devtools/client/netmonitor/src/components/MonitorPanel.js
devtools/client/netmonitor/src/components/Toolbar.js
devtools/client/shared/components/SearchBox.js
--- a/devtools/client/netmonitor/src/assets/styles/Toolbar.css
+++ b/devtools/client/netmonitor/src/assets/styles/Toolbar.css
@@ -16,19 +16,34 @@
 
 .devtools-toolbar-group {
   display: flex;
   flex: 0 0 auto;
   flex-wrap: nowrap;
   align-items: center;
 }
 
+.devtools-toolbar-group .devtools-separator {
+  height: 24px;
+}
+
+.devtools-toolbar-two-rows-1,
+.devtools-toolbar-two-rows-2,
+.devtools-toolbar-single-row {
+  flex-grow: 1;
+}
+
+.devtools-toolbar-two-rows-2 {
+  justify-content: space-between;
+}
+
 .requests-list-filter-buttons {
   display: flex;
   flex-wrap: wrap;
+  margin: 0 2px;
 }
 
 .devtools-button.devtools-pause-icon::before {
   background-image: var(--pause-icon-url);
 }
 
 .devtools-button.devtools-play-icon::before {
   background-image: var(--play-icon-url);
@@ -63,9 +78,17 @@
 @media (max-width: 700px) {
   .network-details-panel-toggle:dir(ltr)::before {
     transform: rotate(90deg);
   }
 
   .network-details-panel-toggle:dir(rtl)::before {
     transform: rotate(-90deg);
   }
+
+  .devtools-checkbox-label {
+    margin-top: 1px;
+  }
+
+  .devtools-toolbar {
+    line-height: 23px;
+  }
 }
--- a/devtools/client/netmonitor/src/components/MonitorPanel.js
+++ b/devtools/client/netmonitor/src/components/MonitorPanel.js
@@ -22,17 +22,18 @@ const {
 const SplitBox = createFactory(require("devtools/client/shared/components/splitter/SplitBox"));
 const RequestList = createFactory(require("./RequestList"));
 const Toolbar = createFactory(require("./Toolbar"));
 
 loader.lazyGetter(this, "NetworkDetailsPanel", function () {
   return createFactory(require("./NetworkDetailsPanel"));
 });
 
-const MediaQueryList = window.matchMedia("(min-width: 700px)");
+const MediaQueryVert = window.matchMedia("(min-width: 700px)");
+const MediaQuerySingleRow = window.matchMedia("(min-width: 920px)");
 
 /**
  * Monitor panel component
  * The main panel for displaying various network request information
  */
 class MonitorPanel extends Component {
   static get propTypes() {
     return {
@@ -47,55 +48,59 @@ class MonitorPanel extends Component {
       updateRequest: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
 
     this.state = {
-      isVerticalSpliter: MediaQueryList.matches,
+      isSingleRow: MediaQuerySingleRow.matches,
+      isVerticalSpliter: MediaQueryVert.matches,
     };
 
     this.onLayoutChange = this.onLayoutChange.bind(this);
   }
 
   componentDidMount() {
-    MediaQueryList.addListener(this.onLayoutChange);
+    MediaQuerySingleRow.addListener(this.onLayoutChange);
+    MediaQueryVert.addListener(this.onLayoutChange);
   }
 
   componentWillReceiveProps(nextProps) {
     updateFormDataSections(nextProps);
   }
 
   componentDidUpdate() {
     let { selectedRequestVisible, openNetworkDetails } = this.props;
     if (!selectedRequestVisible) {
       openNetworkDetails(false);
     }
   }
 
   componentWillUnmount() {
-    MediaQueryList.removeListener(this.onLayoutChange);
+    MediaQuerySingleRow.removeListener(this.onLayoutChange);
+    MediaQueryVert.removeListener(this.onLayoutChange);
 
     let { clientWidth, clientHeight } = findDOMNode(this.refs.endPanel) || {};
 
     if (this.state.isVerticalSpliter && clientWidth) {
       Services.prefs.setIntPref(
         "devtools.netmonitor.panes-network-details-width", clientWidth);
     }
     if (!this.state.isVerticalSpliter && clientHeight) {
       Services.prefs.setIntPref(
         "devtools.netmonitor.panes-network-details-height", clientHeight);
     }
   }
 
   onLayoutChange() {
     this.setState({
-      isVerticalSpliter: MediaQueryList.matches,
+      isSingleRow: MediaQuerySingleRow.matches,
+      isVerticalSpliter: MediaQueryVert.matches,
     });
   }
 
   render() {
     let {
       connector,
       isEmpty,
       networkDetailsOpen,
@@ -105,17 +110,20 @@ class MonitorPanel extends Component {
 
     let initialWidth = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-width");
     let initialHeight = Services.prefs.getIntPref(
         "devtools.netmonitor.panes-network-details-height");
 
     return (
       div({ className: "monitor-panel" },
-        Toolbar({ connector }),
+        Toolbar({
+          connector,
+          singleRow: this.state.isSingleRow,
+        }),
         SplitBox({
           className: "devtools-responsive-container",
           initialWidth: `${initialWidth}px`,
           initialHeight: `${initialHeight}px`,
           minSize: "50px",
           maxSize: "80%",
           splitterSize: 1,
           startPanel: RequestList({ isEmpty, connector }),
--- a/devtools/client/netmonitor/src/components/Toolbar.js
+++ b/devtools/client/netmonitor/src/components/Toolbar.js
@@ -68,16 +68,17 @@ class Toolbar extends Component {
       enablePersistentLogs: PropTypes.func.isRequired,
       togglePersistentLogs: PropTypes.func.isRequired,
       persistentLogsEnabled: PropTypes.bool.isRequired,
       disableBrowserCache: PropTypes.func.isRequired,
       toggleBrowserCache: PropTypes.func.isRequired,
       browserCacheDisabled: PropTypes.bool.isRequired,
       toggleRequestFilterType: PropTypes.func.isRequired,
       filteredRequests: PropTypes.array.isRequired,
+      singleRow: PropTypes.bool.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.autocompleteProvider = this.autocompleteProvider.bind(this);
     this.onSearchBoxFocus = this.onSearchBoxFocus.bind(this);
     this.toggleRequestFilterType = this.toggleRequestFilterType.bind(this);
@@ -93,16 +94,17 @@ class Toolbar extends Component {
   }
 
   shouldComponentUpdate(nextProps) {
     return this.props.networkDetailsOpen !== nextProps.networkDetailsOpen
     || this.props.networkDetailsToggleDisabled !== nextProps.networkDetailsToggleDisabled
     || this.props.persistentLogsEnabled !== nextProps.persistentLogsEnabled
     || this.props.browserCacheDisabled !== nextProps.browserCacheDisabled
     || this.props.recording !== nextProps.recording
+    || this.props.singleRow !== nextProps.singleRow
     || !I.is(this.props.requestFilterTypes, nextProps.requestFilterTypes)
 
     // Filtered requests are useful only when searchbox is focused
     || !!(this.refs.searchbox && this.refs.searchbox.focused);
   }
 
   componentWillUnmount() {
     Services.prefs.removeObserver(DEVTOOLS_ENABLE_PERSISTENT_LOG_PREF,
@@ -139,32 +141,90 @@ class Toolbar extends Component {
     filteredRequests.forEach((request) => {
       fetchNetworkUpdatePacket(connector.requestData, request, [
         "responseCookies",
         "responseHeaders",
       ]);
     });
   }
 
-  render() {
-    let {
-      toggleRecording,
-      clearRequests,
-      requestFilterTypes,
-      setRequestFilterText,
-      networkDetailsToggleDisabled,
-      networkDetailsOpen,
-      toggleNetworkDetails,
-      togglePersistentLogs,
-      persistentLogsEnabled,
-      toggleBrowserCache,
-      browserCacheDisabled,
-      recording,
-    } = this.props;
+  /**
+   * Render a separator.
+   */
+  renderSeparator() {
+    return span({ className: "devtools-separator" });
+  }
+
+  /**
+   * Render a clear button.
+   */
+  renderClearButton(clearRequests) {
+    return (
+      button({
+        className: "devtools-button devtools-clear-icon requests-list-clear-button",
+        title: TOOLBAR_CLEAR,
+        onClick: clearRequests,
+      })
+    );
+  }
+
+  /**
+   * Render a Detail button.
+   */
+  renderDetailButton(networkDetailsOpen, networkDetailsToggleDisabled,
+    toggleNetworkDetails) {
+    // Detail toggle button
+    let toggleDetailButtonClassList = [
+      "network-details-panel-toggle",
+      "devtools-button",
+    ];
 
+    if (!networkDetailsOpen) {
+      toggleDetailButtonClassList.push("pane-collapsed");
+    }
+    let toggleDetailButtonClass = toggleDetailButtonClassList.join(" ");
+    let toggleDetailButtonTitle = networkDetailsOpen ? COLLAPSE_DETAILS_PANE :
+      EXPAND_DETAILS_PANE;
+
+    return (
+      button({
+        className: toggleDetailButtonClass,
+        title: toggleDetailButtonTitle,
+        disabled: networkDetailsToggleDisabled,
+        tabIndex: "0",
+        onClick: toggleNetworkDetails,
+      })
+    );
+  }
+
+  /**
+   * Render a ToggleRecording button.
+   */
+  renderToggleRecordingButton(recording, toggleRecording) {
+    // Calcula[te class-list for toggle recording button.
+    // The button has two states: pause/play.
+    let toggleRecordingButtonClass = [
+      "devtools-button",
+      "requests-list-pause-button",
+      recording ? "devtools-pause-icon" : "devtools-play-icon",
+    ].join(" ");
+
+    return (
+      button({
+        className: toggleRecordingButtonClass,
+        title: TOOLBAR_TOGGLE_RECORDING,
+        onClick: toggleRecording,
+      })
+    );
+  }
+
+  /**
+   * Render filter buttons.
+   */
+  renderFilterButtons(requestFilterTypes) {
     // Render list of filter-buttons.
     let buttons = Object.entries(requestFilterTypes).map(([type, checked]) => {
       let classList = ["devtools-button", `requests-list-filter-${type}-button`];
       checked && classList.push("checked");
 
       return (
         button({
           className: classList.join(" "),
@@ -174,99 +234,140 @@ class Toolbar extends Component {
           "aria-pressed": checked,
           "data-key": type,
         },
           TOOLBAR_FILTER_LABELS[type]
         )
       );
     });
 
-    // Calculate class-list for toggle recording button. The button
-    // has two states: pause/play.
-    let toggleRecordingButtonClass = [
-      "devtools-button",
-      "requests-list-pause-button",
-      recording ? "devtools-pause-icon" : "devtools-play-icon",
-    ].join(" ");
+    return div({ className: "requests-list-filter-buttons" }, buttons);
+  }
+
+  /**
+   * Render a Persistlog checkbox.
+   */
+  renderPersistlogCheckbox(persistentLogsEnabled, togglePersistentLogs) {
+    return (
+      label(
+        {
+          className: "devtools-checkbox-label",
+          title: ENABLE_PERSISTENT_LOGS_TOOLTIP,
+        },
+        input({
+          id: "devtools-persistlog-checkbox",
+          className: "devtools-checkbox",
+          type: "checkbox",
+          checked: persistentLogsEnabled,
+          onChange: togglePersistentLogs,
+        }),
+        ENABLE_PERSISTENT_LOGS_LABEL,
+      )
+    );
+  }
 
-    // Detail toggle button
-    let toggleDetailButtonClassList = [
-      "network-details-panel-toggle",
-      "devtools-button",
-    ];
+  /**
+   * Render a Cache checkbox.
+   */
+  renderCacheCheckbox(browserCacheDisabled, toggleBrowserCache) {
+    return (
+      label(
+        {
+          className: "devtools-checkbox-label",
+          title: DISABLE_CACHE_TOOLTIP,
+        },
+        input({
+          id: "devtools-cache-checkbox",
+          className: "devtools-checkbox",
+          type: "checkbox",
+          checked: browserCacheDisabled,
+          onChange: toggleBrowserCache,
+        }),
+        DISABLE_CACHE_LABEL,
+      )
+    );
+  }
 
-    if (!networkDetailsOpen) {
-      toggleDetailButtonClassList.push("pane-collapsed");
-    }
-    let toggleDetailButtonClass = toggleDetailButtonClassList.join(" ");
-    let toggleDetailButtonTitle = networkDetailsOpen ? COLLAPSE_DETAILS_PANE :
-      EXPAND_DETAILS_PANE;
+  /**
+   * Render filter Searchbox.
+   */
+  renderFilterBox(setRequestFilterText) {
+    return (
+      SearchBox({
+        delay: FILTER_SEARCH_DELAY,
+        keyShortcut: SEARCH_KEY_SHORTCUT,
+        placeholder: SEARCH_PLACE_HOLDER,
+        plainStyle: true,
+        type: "filter",
+        ref: "searchbox",
+        onChange: setRequestFilterText,
+        onFocus: this.onSearchBoxFocus,
+        autocompleteProvider: this.autocompleteProvider,
+      })
+    );
+  }
+
+  render() {
+    let {
+      toggleRecording,
+      clearRequests,
+      requestFilterTypes,
+      setRequestFilterText,
+      networkDetailsToggleDisabled,
+      networkDetailsOpen,
+      toggleNetworkDetails,
+      togglePersistentLogs,
+      persistentLogsEnabled,
+      toggleBrowserCache,
+      browserCacheDisabled,
+      recording,
+      singleRow,
+    } = this.props;
 
     // Render the entire toolbar.
-    return (
+    // dock at bottom or dock at side has different layout
+    return singleRow ? (
       span({ className: "devtools-toolbar devtools-toolbar-container" },
-        span({ className: "devtools-toolbar-group" },
-          button({
-            className: toggleRecordingButtonClass,
-            title: TOOLBAR_TOGGLE_RECORDING,
-            onClick: toggleRecording,
-          }),
-          button({
-            className: "devtools-button devtools-clear-icon requests-list-clear-button",
-            title: TOOLBAR_CLEAR,
-            onClick: clearRequests,
-          }),
-          div({ className: "requests-list-filter-buttons" }, buttons),
-          label(
-            {
-              className: "devtools-checkbox-label",
-              title: ENABLE_PERSISTENT_LOGS_TOOLTIP,
-            },
-            input({
-              id: "devtools-persistlog-checkbox",
-              className: "devtools-checkbox",
-              type: "checkbox",
-              checked: persistentLogsEnabled,
-              onChange: togglePersistentLogs,
-            }),
-            ENABLE_PERSISTENT_LOGS_LABEL
-          ),
-          label(
-            {
-              className: "devtools-checkbox-label",
-              title: DISABLE_CACHE_TOOLTIP,
-            },
-            input({
-              id: "devtools-cache-checkbox",
-              className: "devtools-checkbox",
-              type: "checkbox",
-              checked: browserCacheDisabled,
-              onChange: toggleBrowserCache,
-            }),
-            DISABLE_CACHE_LABEL,
-          ),
+        span({ className: "devtools-toolbar-group devtools-toolbar-single-row" },
+          this.renderClearButton(clearRequests),
+          this.renderSeparator(),
+          this.renderFilterBox(setRequestFilterText),
+          this.renderSeparator(),
+          this.renderToggleRecordingButton(recording, toggleRecording),
+          this.renderSeparator(),
+          this.renderFilterButtons(requestFilterTypes),
+          this.renderSeparator(),
+          this.renderPersistlogCheckbox(persistentLogsEnabled, togglePersistentLogs),
+          this.renderCacheCheckbox(browserCacheDisabled, toggleBrowserCache),
         ),
         span({ className: "devtools-toolbar-group" },
-          SearchBox({
-            delay: FILTER_SEARCH_DELAY,
-            keyShortcut: SEARCH_KEY_SHORTCUT,
-            placeholder: SEARCH_PLACE_HOLDER,
-            type: "filter",
-            ref: "searchbox",
-            onChange: setRequestFilterText,
-            onFocus: this.onSearchBoxFocus,
-            autocompleteProvider: this.autocompleteProvider,
-          }),
-          button({
-            className: toggleDetailButtonClass,
-            title: toggleDetailButtonTitle,
-            disabled: networkDetailsToggleDisabled,
-            tabIndex: "0",
-            onClick: toggleNetworkDetails,
-          }),
+          this.renderSeparator(),
+          this.renderDetailButton(networkDetailsOpen,
+            networkDetailsToggleDisabled, toggleNetworkDetails),
+        )
+      )
+    ) : (
+      span({ className: "devtools-toolbar devtools-toolbar-container" },
+        span({ className: "devtools-toolbar-group devtools-toolbar-two-rows-1" },
+          this.renderClearButton(clearRequests),
+          this.renderSeparator(),
+          this.renderFilterBox(setRequestFilterText),
+          this.renderSeparator(),
+          this.renderToggleRecordingButton(recording, toggleRecording),
+          this.renderSeparator(),
+          this.renderPersistlogCheckbox(persistentLogsEnabled, togglePersistentLogs),
+          this.renderCacheCheckbox(browserCacheDisabled, toggleBrowserCache),
+        ),
+        span({ className: "devtools-toolbar-group devtools-toolbar-two-rows-2" },
+          this.renderFilterButtons(requestFilterTypes),
+          span({},
+            this.renderSeparator(),
+            this.renderDetailButton(networkDetailsOpen,
+              networkDetailsToggleDisabled, toggleNetworkDetails),
+          ),
         )
       )
     );
   }
 }
 
 module.exports = connect(
   (state) => ({
--- a/devtools/client/shared/components/SearchBox.js
+++ b/devtools/client/shared/components/SearchBox.js
@@ -17,16 +17,17 @@ class SearchBox extends Component {
     return {
       delay: PropTypes.number,
       keyShortcut: PropTypes.string,
       onChange: PropTypes.func,
       onFocus: PropTypes.func,
       onBlur: PropTypes.func,
       onKeyDown: PropTypes.func,
       placeholder: PropTypes.string,
+      plainStyle: PropTypes.bool,
       type: PropTypes.string,
       autocompleteProvider: PropTypes.func,
     };
   }
 
   constructor(props) {
     super(props);
 
@@ -155,20 +156,24 @@ class SearchBox extends Component {
     }
   }
 
   render() {
     let {
       type = "search",
       placeholder,
       autocompleteProvider,
+      plainStyle,
     } = this.props;
     let { value } = this.state;
     let divClassList = ["devtools-searchbox", "has-clear-btn"];
     let inputClassList = [`devtools-${type}input`];
+    if (plainStyle) {
+      inputClassList.push("devtools-plaininput");
+    }
     let showAutocomplete = autocompleteProvider && this.state.focused && value !== "";
 
     if (value !== "") {
       inputClassList.push("filled");
     }
     return dom.div(
       { className: divClassList.join(" ") },
       dom.input({