Bug 1467572 - Part 5: Implement the new style for the global toolbar. r=rcaliman draft
authorGabriel Luong <gabriel.luong@gmail.com>
Tue, 14 Aug 2018 17:54:53 -0400 (2018-08-14)
changeset 829210 a3eed587c4e5e13585219f6ce90dfdfdc848618e
parent 829209 04ab126f2756de1e3b799dc89ff1574bca3baf5b
child 829211 3618a25db727c4752737e7761e0a3819fd5abb85
push id118750
push userbmo:gl@mozilla.com
push dateTue, 14 Aug 2018 21:55:43 +0000 (2018-08-14)
reviewersrcaliman
bugs1467572
milestone63.0a1
Bug 1467572 - Part 5: Implement the new style for the global toolbar. r=rcaliman MozReview-Commit-ID: ArByqDwIFVE
devtools/client/responsive.html/components/App.js
devtools/client/responsive.html/components/DevicePixelRatioSelector.js
devtools/client/responsive.html/components/DeviceSelector.js
devtools/client/responsive.html/components/GlobalToolbar.js
devtools/client/responsive.html/components/ReloadConditions.js
devtools/client/responsive.html/components/Toolbar.js
devtools/client/responsive.html/components/Viewports.js
devtools/client/responsive.html/components/moz.build
devtools/client/responsive.html/index.css
devtools/client/responsive.html/index.js
devtools/client/responsive.html/test/browser/browser_network_throttling.js
devtools/client/responsive.html/test/browser/head.js
devtools/client/shared/components/throttling/NetworkThrottlingSelector.js
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -26,17 +26,17 @@ const {
   changeDevice,
   changePixelRatio,
   removeDeviceAssociation,
   resizeViewport,
   rotateViewport,
 } = require("../actions/viewports");
 
 const DeviceModal = createFactory(require("./DeviceModal"));
-const GlobalToolbar = createFactory(require("./GlobalToolbar"));
+const Toolbar = createFactory(require("./Toolbar"));
 const Viewports = createFactory(require("./Viewports"));
 
 const Types = require("../types");
 
 class App extends Component {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
@@ -211,17 +211,17 @@ class App extends Component {
     if (devices.modalOpenedFromViewport !== null) {
       deviceAdderViewportTemplate = viewports[devices.modalOpenedFromViewport];
     }
 
     return dom.div(
       {
         id: "app",
       },
-      GlobalToolbar({
+      Toolbar({
         devices,
         displayPixelRatio,
         networkThrottling,
         reloadConditions,
         screenshot,
         selectedDevice,
         selectedPixelRatio,
         touchSimulation,
--- a/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
+++ b/devtools/client/responsive.html/components/DevicePixelRatioSelector.js
@@ -114,17 +114,17 @@ class DevicePixelRatioSelector extends P
     let listContent = PIXEL_RATIO_PRESET.map(createVisibleOption);
 
     if (state == Types.loadableState.LOADED) {
       listContent = listContent.concat(hiddenOptions.map(createHiddenOption));
     }
 
     return dom.select(
       {
-        id: "global-device-pixel-ratio-selector",
+        id: "device-pixel-ratio-selector",
         value: selectedPixelRatio.value || displayPixelRatio,
         disabled: isDisabled,
         onChange: this.onSelectChange,
         onFocus: this.onFocusChange,
         onBlur: this.onFocusChange,
         className: selectorClass,
         title: title
       },
--- a/devtools/client/responsive.html/components/DeviceSelector.js
+++ b/devtools/client/responsive.html/components/DeviceSelector.js
@@ -67,17 +67,17 @@ class DeviceSelector extends PureCompone
         }
       }
     }
 
     options.sort(function(a, b) {
       return a.name.localeCompare(b.name);
     });
 
-    let selectClass = "viewport-device-selector toolbar-dropdown";
+    let selectClass = "toolbar-dropdown";
     if (selectedDevice) {
       selectClass += " selected";
     }
 
     const state = devices.listState;
     let listContent;
 
     if (state == Types.loadableState.LOADED) {
@@ -112,16 +112,17 @@ class DeviceSelector extends PureCompone
         value: "",
         title: "",
         disabled: true,
       }, getStr("responsive.deviceListError"))];
     }
 
     return dom.select(
       {
+        id: "device-selector",
         className: selectClass,
         value: selectedDevice,
         title: selectedDevice,
         onChange: this.onSelectChange,
         disabled: (state !== Types.loadableState.LOADED),
       },
       ...listContent
     );
--- a/devtools/client/responsive.html/components/ReloadConditions.js
+++ b/devtools/client/responsive.html/components/ReloadConditions.js
@@ -21,17 +21,17 @@ class ReloadConditions extends PureCompo
 
   render() {
     const {
       reloadConditions,
       onChangeReloadCondition,
     } = this.props;
 
     return ToggleMenu({
-      id: "global-reload-conditions-menu",
+      id: "reload-conditions-menu",
       items: [
         {
           id: "touchSimulation",
           label: getStr("responsive.reloadConditions.touchSimulation"),
           checked: reloadConditions.touchSimulation,
         },
         {
           id: "userAgent",
rename from devtools/client/responsive.html/components/GlobalToolbar.js
rename to devtools/client/responsive.html/components/Toolbar.js
--- a/devtools/client/responsive.html/components/GlobalToolbar.js
+++ b/devtools/client/responsive.html/components/Toolbar.js
@@ -10,17 +10,17 @@ const dom = require("devtools/client/sha
 
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 const DevicePixelRatioSelector = createFactory(require("./DevicePixelRatioSelector"));
 const DeviceSelector = createFactory(require("./DeviceSelector"));
 const NetworkThrottlingSelector = createFactory(require("devtools/client/shared/components/throttling/NetworkThrottlingSelector"));
 const ReloadConditions = createFactory(require("./ReloadConditions"));
 
-class GlobalToolbar extends PureComponent {
+class Toolbar extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
       displayPixelRatio: Types.pixelRatio.value.isRequired,
       networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
       reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       selectedDevice: PropTypes.string.isRequired,
@@ -60,60 +60,63 @@ class GlobalToolbar extends PureComponen
     } = this.props;
 
     let touchButtonClass = "toolbar-button devtools-button";
     if (touchSimulation.enabled) {
       touchButtonClass += " checked";
     }
 
     return dom.header(
-      {
-        id: "global-toolbar",
-        className: "container",
-      },
+      { id: "toolbar" },
       DeviceSelector({
         devices,
         selectedDevice,
         viewportId: 0,
         onChangeDevice,
         onResizeViewport,
         onUpdateDeviceModal,
       }),
-      NetworkThrottlingSelector({
-        networkThrottling,
-        onChangeNetworkThrottling,
-      }),
-      DevicePixelRatioSelector({
-        devices,
-        displayPixelRatio,
-        selectedDevice,
-        selectedPixelRatio,
-        onChangePixelRatio,
-      }),
-      ReloadConditions({
-        reloadConditions,
-        onChangeReloadCondition,
-      }),
-      dom.button({
-        id: "global-touch-simulation-button",
-        className: touchButtonClass,
-        title: (touchSimulation.enabled ?
-          getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
-        onClick: () => onChangeTouchSimulation(!touchSimulation.enabled),
-      }),
-      dom.button({
-        id: "global-screenshot-button",
-        className: "toolbar-button devtools-button",
-        title: getStr("responsive.screenshot"),
-        onClick: onScreenshot,
-        disabled: screenshot.isCapturing,
-      }),
-      dom.button({
-        id: "global-exit-button",
-        className: "toolbar-button devtools-button",
-        title: getStr("responsive.exit"),
-        onClick: onExit,
-      })
+      dom.div(
+        { id: "toolbar-center-controls" },
+        DevicePixelRatioSelector({
+          devices,
+          displayPixelRatio,
+          selectedDevice,
+          selectedPixelRatio,
+          onChangePixelRatio,
+        }),
+        NetworkThrottlingSelector({
+          networkThrottling,
+          onChangeNetworkThrottling,
+        }),
+        dom.button({
+          id: "touch-simulation-button",
+          className: touchButtonClass,
+          title: (touchSimulation.enabled ?
+            getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
+          onClick: () => onChangeTouchSimulation(!touchSimulation.enabled),
+        })
+      ),
+      dom.div(
+        { id: "toolbar-end-controls" },
+        dom.button({
+          id: "screenshot-button",
+          className: "toolbar-button devtools-button",
+          title: getStr("responsive.screenshot"),
+          onClick: onScreenshot,
+          disabled: screenshot.isCapturing,
+        }),
+        ReloadConditions({
+          reloadConditions,
+          onChangeReloadCondition,
+        }),
+        dom.button({
+          id: "exit-button",
+          className: "toolbar-button devtools-button",
+          title: getStr("responsive.exit"),
+          onClick: onExit,
+        })
+      )
     );
   }
 }
 
-module.exports = GlobalToolbar;
+module.exports = Toolbar;
--- a/devtools/client/responsive.html/components/Viewports.js
+++ b/devtools/client/responsive.html/components/Viewports.js
@@ -28,29 +28,49 @@ class Viewports extends Component {
       screenshot,
       viewports,
       onBrowserMounted,
       onContentResize,
       onRemoveDeviceAssociation,
       onResizeViewport,
     } = this.props;
 
-    return dom.div(
-      {
-        id: "viewports",
-      },
-      viewports.map((viewport, i) => {
-        return ResizableViewport({
-          key: viewport.id,
-          screenshot,
-          swapAfterMount: i == 0,
-          viewport,
-          onBrowserMounted,
-          onContentResize,
-          onRemoveDeviceAssociation,
-          onResizeViewport,
-        });
-      })
+    const viewportSize = window.getViewportSize();
+    // The viewport may not have been created yet. Default to justify-content: center
+    // for the container.
+    let justifyContent = "center";
+
+    // If the RDM viewport is bigger than the window's inner width, set the container's
+    // justify-content to start so that the left-most viewport is visible when there's
+    // horizontal overflow. That is when the horizontal space become smaller than the
+    // viewports and a scrollbar appears, then the first viewport will still be visible.
+    if (viewportSize && viewportSize.width > window.innerWidth) {
+      justifyContent = "start";
+    }
+
+    return (
+      dom.div(
+        {
+          id: "viewports-container",
+          style: {
+            justifyContent,
+          },
+        },
+        dom.div({ id: "viewports" },
+          viewports.map((viewport, i) => {
+            return ResizableViewport({
+              key: viewport.id,
+              screenshot,
+              swapAfterMount: i == 0,
+              viewport,
+              onBrowserMounted,
+              onContentResize,
+              onRemoveDeviceAssociation,
+              onResizeViewport,
+            });
+          })
+        )
+      )
     );
   }
 }
 
 module.exports = Viewports;
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -6,15 +6,15 @@
 
 DevToolsModules(
     'App.js',
     'Browser.js',
     'DeviceAdder.js',
     'DeviceModal.js',
     'DevicePixelRatioSelector.js',
     'DeviceSelector.js',
-    'GlobalToolbar.js',
     'ReloadConditions.js',
     'ResizableViewport.js',
     'ToggleMenu.js',
+    'Toolbar.js',
     'ViewportDimension.js',
     'Viewports.js',
 )
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -28,34 +28,30 @@
 * {
   box-sizing: border-box;
 }
 
 :root,
 input,
 select,
 button {
-  font-size: 11px;
+  font-size: 12px;
 }
 
 html,
 body,
 #root {
   height: 100%;
-  margin: 0;
+  overflow: hidden;
 }
 
 #app {
-  /* Center the viewports container */
   display: flex;
-  align-items: center;
   flex-direction: column;
-  padding-top: 15px;
-  padding-bottom: 1%;
-  position: relative;
+  width: 100%;
   height: 100%;
 }
 
 /**
  * Common styles for shared components
  */
 
 .container {
@@ -141,17 +137,17 @@ select > option.divider {
   position: relative;
 }
 
 .devtools-toggle-menu .devtools-menu {
   display: none;
   flex-direction: column;
   align-items: start;
   position: absolute;
-  left: 0;
+  right: 0;
   top: 100%;
   z-index: 1;
   padding: 5px;
   border-radius: 2px;
   background-color: var(--theme-toolbar-background);
   box-shadow: var(--rdm-box-shadow);
 }
 
@@ -162,128 +158,145 @@ select > option.divider {
 .devtools-toggle-menu .devtools-menu-item {
   display: flex;
   align-items: center;
 }
 
 /**
  * Common background for dropdowns like select and toggle menu
  */
+
 .toolbar-dropdown,
 .toolbar-dropdown.devtools-button,
 .toolbar-dropdown.devtools-button:hover:not(:empty):not(:disabled):not(.checked) {
   background-color: var(--theme-toolbar-background);
   background-image: var(--viewport-selection-arrow);
   background-position: 100% 50%;
   background-repeat: no-repeat;
   background-size: 7px;
   -moz-context-properties: fill;
   fill: currentColor;
 }
 
+.toolbar-dropdown {
+  background-position-x: right 5px;
+  border-right: 1px solid var(--theme-splitter-color);
+  padding-right: 15px;
+}
+
 /**
- * Global Toolbar
+ * Toolbar
  */
 
-#global-toolbar {
-  color: var(--theme-body-color-alt);
-  border-radius: 2px;
-  box-shadow: var(--rdm-box-shadow);
-  margin: 0 0 15px 0;
-  padding: 4px 5px;
-  display: inline-flex;
-  align-items: center;
+#toolbar {
+  background-color: var(--theme-toolbar-background);
+  border-bottom: 1px solid var(--theme-splitter-color);
+  display: grid;
+  grid-template-columns: min-content auto min-content;
+  width: 100%;
+  min-height: 29px;
   -moz-user-select: none;
 }
 
-#global-toolbar > .toolbar-button:first-of-type {
+#toolbar > .toolbar-button:first-of-type {
   margin-inline-start: 8px;
 }
 
-#global-toolbar > .toolbar-button::before {
+#toolbar > .toolbar-button::before {
   width: 12px;
   height: 12px;
   background-size: cover;
 }
 
-#global-toolbar .toolbar-dropdown {
-  background-position-x: right 5px;
-  border-right: 1px solid var(--theme-splitter-color);
-  padding-right: 15px;
-  /* padding-left: 0; */
+#device-selector {
+  border-right: none;
 }
 
-#global-touch-simulation-button::before {
+#toolbar-center-controls,
+#toolbar-end-controls {
+  display: flex;
+  align-items: center;
+}
+
+#toolbar-center-controls {
+  justify-self: center;
+}
+
+#touch-simulation-button::before {
   background-image: url("./images/touch-events.svg");
 }
 
-#global-screenshot-button::before {
+#screenshot-button::before {
   background-image: url("./images/screenshot.svg");
 }
 
-#global-exit-button::before {
+#exit-button::before {
   background-image: url("chrome://devtools/skin/images/close.svg");
 }
 
-#global-screenshot-button:disabled {
+#screenshot-button:disabled {
   filter: var(--theme-icon-checked-filter);
   opacity: 1 !important;
 }
 
-#global-network-throttling-selector {
+#network-throttling-selector {
   height: 15px;
   padding-left: 0;
   width: 103px;
 }
 
-#global-device-pixel-ratio-selector {
+#device-pixel-ratio-selector {
   -moz-user-select: none;
   color: var(--viewport-color);
   height: 15px;
   /* `max-width` is here to keep the UI compact if the device pixel ratio changes to a
      repeating decimal value.  This can happen if you zoom the UI (Cmd + Plus / Minus on
      macOS for example). */
   max-width: 8em;
 }
 
-#global-device-pixel-ratio-selector.focused,
-#global-device-pixel-ratio-selector:not(.disabled):hover {
+#device-pixel-ratio-selector.focused,
+#device-pixel-ratio-selector:not(.disabled):hover {
   color: var(--viewport-hover-color);
 }
 
-#global-device-pixel-ratio-selector:focus {
+#device-pixel-ratio-selector:focus {
   color: var(--viewport-active-color);
 }
 
-#global-device-pixel-ratio-selector.selected {
+#device-pixel-ratio-selector.selected {
   color: var(--viewport-active-color);
 }
 
-#global-device-pixel-ratio-selector > option {
+#device-pixel-ratio-selector > option {
   padding: 5px;
 }
 
 .viewport-rotate-button {
   position: absolute;
   right: 0;
 }
 
 .viewport-rotate-button::before {
   background-image: url("./images/rotate-viewport.svg");
 }
 
+#viewports-container {
+  display: flex;
+  justify-content: center;
+  overflow: auto;
+  height: 100%;
+  width: 100%;
+}
+
 #viewports {
-  /* Make sure left-most viewport is visible when there's horizontal overflow.
-     That is, when the horizontal space become smaller than the viewports and a
-     scrollbar appears, then the first viewport will still be visible */
-  position: sticky;
-  left: 0;
   /* Individual viewports are inline elements, make sure they stay on a single
      line */
   white-space: nowrap;
+  padding-top: 30px;
 }
 
 /**
  * Viewport Container
  */
 
 .viewport {
   display: inline-block;
--- a/devtools/client/responsive.html/index.js
+++ b/devtools/client/responsive.html/index.js
@@ -132,17 +132,22 @@ window.addInitialViewport = ({ uri, user
     console.error(e);
   }
 };
 
 /**
  * Called by manager.js when tests want to check the viewport size.
  */
 window.getViewportSize = () => {
-  const { width, height } = bootstrap.store.getState().viewports[0];
+  const { viewports } = bootstrap.store.getState();
+  if (!viewports.length) {
+    return null;
+  }
+
+  const { width, height } = viewports[0];
   return { width, height };
 };
 
 /**
  * Called by manager.js to set viewport size from tests, GCLI, etc.
  */
 window.setViewportSize = ({ width, height }) => {
   try {
--- a/devtools/client/responsive.html/test/browser/browser_network_throttling.js
+++ b/devtools/client/responsive.html/test/browser/browser_network_throttling.js
@@ -26,17 +26,17 @@ addRDMTask(TEST_URL, async function({ ui
 
   // Test switching back to no throttling
   await selectNetworkThrottling(ui, "No throttling");
   testNetworkThrottlingSelectorLabel(ui, "No throttling");
   await testNetworkThrottlingState(ui, null);
 });
 
 function testNetworkThrottlingSelectorLabel(ui, expected) {
-  const selector = "#global-network-throttling-selector";
+  const selector = "#network-throttling-selector";
   const select = ui.toolWindow.document.querySelector(selector);
   is(select.selectedOptions[0].textContent, expected,
     `Select label should be changed to ${expected}`);
 }
 
 var testNetworkThrottlingState = async function(ui, expected) {
   const state = await ui.emulationFront.getNetworkThrottling();
   Assert.deepEqual(state, expected, "Network throttling state should be " +
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -268,17 +268,17 @@ const selectDevice = (ui, value) => Prom
   changeSelectValue(ui, ".viewport-device-selector", value)
 ]);
 
 const selectDevicePixelRatio = (ui, value) =>
   changeSelectValue(ui, "#global-device-pixel-ratio-selector", value);
 
 const selectNetworkThrottling = (ui, value) => Promise.all([
   once(ui, "network-throttling-changed"),
-  changeSelectValue(ui, "#global-network-throttling-selector", value)
+  changeSelectValue(ui, "#network-throttling-selector", value)
 ]);
 
 function getSessionHistory(browser) {
   return ContentTask.spawn(browser, {}, async function() {
     /* eslint-disable no-undef */
     const { SessionHistory } =
       ChromeUtils.import("resource://gre/modules/sessionstore/SessionHistory.jsm", {});
     return SessionHistory.collect(docShell);
--- a/devtools/client/shared/components/throttling/NetworkThrottlingSelector.js
+++ b/devtools/client/shared/components/throttling/NetworkThrottlingSelector.js
@@ -94,17 +94,17 @@ class NetworkThrottlingSelector extends 
           },
           profile.id
         );
       }),
     ];
 
     return dom.select(
       {
-        id: "global-network-throttling-selector",
+        id: "network-throttling-selector",
         className: selectClass,
         value: selectedProfile,
         onChange: this.onSelectChange,
       },
       ...listContent
     );
   }
 }