Bug 1467572 - Part 17: Implement left alignment of viewports. r=rcaliman
authorGabriel Luong <gabriel.luong@gmail.com>
Wed, 15 Aug 2018 17:27:55 -0400
changeset 486962 599eb93053e0c513acdc25bc921b5b2103732151
parent 486961 3199491bd055a6ac07632efeb7b3592f417f520c
child 486963 5d6965e5700a8fb2e00b652a49153bbad09ee175
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcaliman
bugs1467572
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1467572 - Part 17: Implement left alignment of viewports. r=rcaliman
devtools/client/locales/en-US/responsive.properties
devtools/client/preferences/devtools-client.js
devtools/client/responsive.html/actions/index.js
devtools/client/responsive.html/actions/moz.build
devtools/client/responsive.html/actions/ui.js
devtools/client/responsive.html/components/App.js
devtools/client/responsive.html/components/ResizableViewport.js
devtools/client/responsive.html/components/SettingsMenu.js
devtools/client/responsive.html/components/Toolbar.js
devtools/client/responsive.html/components/Viewports.js
devtools/client/responsive.html/index.css
devtools/client/responsive.html/reducers.js
devtools/client/responsive.html/reducers/moz.build
devtools/client/responsive.html/reducers/ui.js
--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -123,8 +123,12 @@ responsive.reloadConditions.touchSimulat
 # LOCALIZATION NOTE (responsive.reloadConditions.userAgent): Label on checkbox used
 # to select whether to reload when user agent is changed.
 responsive.reloadConditions.userAgent=Reload when user agent is changed
 
 # LOCALIZATION NOTE (responsive.reloadNotification.description): Text in notification bar
 # shown on first open to clarify that some features need a reload to apply.  %1$S is the
 # label on the reload conditions menu (responsive.reloadConditions.label).
 responsive.reloadNotification.description=Device simulation changes require a reload to fully apply.  Automatic reloads are disabled by default to avoid losing any changes in DevTools.  You can enable reloading via the ā€œ%1$Sā€ menu.
+
+# LOCALIZATION NOTE (responsive.leftAlignViewport): Label on checkbox used in the settings
+# menu.
+responsive.leftAlignViewport = Left-align Viewport
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -305,16 +305,18 @@ pref("devtools.hud.loglimit", 10000);
 pref("devtools.editor.tabsize", 2);
 pref("devtools.editor.expandtab", true);
 pref("devtools.editor.keymap", "default");
 pref("devtools.editor.autoclosebrackets", true);
 pref("devtools.editor.detectindentation", true);
 pref("devtools.editor.enableCodeFolding", true);
 pref("devtools.editor.autocomplete", true);
 
+// Whether or not the viewports are left aligned.
+pref("devtools.responsive.leftAlignViewport.enabled", false);
 // Whether to reload when touch simulation is toggled
 pref("devtools.responsive.reloadConditions.touchSimulation", false);
 // Whether to reload when user agent is changed
 pref("devtools.responsive.reloadConditions.userAgent", false);
 // Whether to show the notification about reloading to apply emulation
 pref("devtools.responsive.reloadNotification.enabled", true);
 
 // Enable new about:debugging.
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -76,15 +76,18 @@ createEnum([
   "ROTATE_VIEWPORT",
 
   // Take a screenshot of the viewport.
   "TAKE_SCREENSHOT_START",
 
   // Indicates when the screenshot action ends.
   "TAKE_SCREENSHOT_END",
 
+  // Toggles the left alignment of the viewports.
+  "TOGGLE_LEFT_ALIGNMENT",
+
   // Update the device display state in the device selector.
   "UPDATE_DEVICE_DISPLAYED",
 
   // Update the device modal state.
   "UPDATE_DEVICE_MODAL",
 
 ], module.exports);
--- a/devtools/client/responsive.html/actions/moz.build
+++ b/devtools/client/responsive.html/actions/moz.build
@@ -7,10 +7,11 @@
 DevToolsModules(
     'devices.js',
     'display-pixel-ratio.js',
     'index.js',
     'location.js',
     'reload-conditions.js',
     'screenshot.js',
     'touch-simulation.js',
+    'ui.js',
     'viewports.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/ui.js
@@ -0,0 +1,20 @@
+/* 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 {
+  TOGGLE_LEFT_ALIGNMENT,
+} = require("./index");
+
+module.exports = {
+
+  toggleLeftAlignment(enabled) {
+    return {
+      type: TOGGLE_LEFT_ALIGNMENT,
+      enabled,
+    };
+  },
+
+};
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -21,16 +21,17 @@ const {
   updateDeviceDisplayed,
   updateDeviceModal,
   updatePreferredDevices,
 } = require("../actions/devices");
 const { changeNetworkThrottling } = require("devtools/client/shared/components/throttling/actions");
 const { changeReloadCondition } = require("../actions/reload-conditions");
 const { takeScreenshot } = require("../actions/screenshot");
 const { changeTouchSimulation } = require("../actions/touch-simulation");
+const { toggleLeftAlignment } = require("../actions/ui");
 const {
   changeDevice,
   changePixelRatio,
   removeDeviceAssociation,
   resizeViewport,
   rotateViewport,
 } = require("../actions/viewports");
 
@@ -63,16 +64,17 @@ class App extends Component {
     this.onContentResize = this.onContentResize.bind(this);
     this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
     this.onExit = this.onExit.bind(this);
     this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
     this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
     this.onResizeViewport = this.onResizeViewport.bind(this);
     this.onRotateViewport = this.onRotateViewport.bind(this);
     this.onScreenshot = this.onScreenshot.bind(this);
+    this.onToggleLeftAlignment = this.onToggleLeftAlignment.bind(this);
     this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
     this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
   }
 
   onAddCustomDevice(device) {
     this.props.dispatch(addCustomDevice(device));
   }
 
@@ -157,16 +159,20 @@ class App extends Component {
   onRotateViewport(id) {
     this.props.dispatch(rotateViewport(id));
   }
 
   onScreenshot() {
     this.props.dispatch(takeScreenshot());
   }
 
+  onToggleLeftAlignment() {
+    this.props.dispatch(toggleLeftAlignment());
+  }
+
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
   }
 
   onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
     this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
   }
 
@@ -192,16 +198,17 @@ class App extends Component {
       onContentResize,
       onDeviceListUpdate,
       onExit,
       onRemoveCustomDevice,
       onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
+      onToggleLeftAlignment,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModal,
     } = this;
 
     if (!viewports.length) {
       return null;
     }
 
@@ -232,16 +239,17 @@ class App extends Component {
         onChangePixelRatio,
         onChangeReloadCondition,
         onChangeTouchSimulation,
         onExit,
         onRemoveDeviceAssociation,
         onResizeViewport,
         onRotateViewport,
         onScreenshot,
+        onToggleLeftAlignment,
         onUpdateDeviceModal,
       }),
       Viewports({
         screenshot,
         viewports,
         onBrowserMounted,
         onContentResize,
         onRemoveDeviceAssociation,
--- a/devtools/client/responsive.html/components/ResizableViewport.js
+++ b/devtools/client/responsive.html/components/ResizableViewport.js
@@ -16,16 +16,17 @@ const Constants = require("../constants"
 const Types = require("../types");
 
 const VIEWPORT_MIN_WIDTH = Constants.MIN_VIEWPORT_DIMENSION;
 const VIEWPORT_MIN_HEIGHT = Constants.MIN_VIEWPORT_DIMENSION;
 
 class ResizableViewport extends Component {
   static get propTypes() {
     return {
+      leftAlignmentEnabled: PropTypes.bool.isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       swapAfterMount: PropTypes.bool.isRequired,
       viewport: PropTypes.shape(Types.viewport).isRequired,
       onBrowserMounted: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
     };
@@ -59,20 +60,24 @@ class ResizableViewport extends Componen
   }
 
   onResizeDrag({ clientX, clientY }) {
     if (!this.state.isResizing) {
       return;
     }
 
     let { lastClientX, lastClientY, ignoreX, ignoreY } = this.state;
-    // the viewport is centered horizontally, so horizontal resize resizes
-    // by twice the distance the mouse was dragged - on left and right side.
-    let deltaX = 2 * (clientX - lastClientX);
-    let deltaY = (clientY - lastClientY);
+    let deltaX = clientX - lastClientX;
+    let deltaY = clientY - lastClientY;
+
+    if (!this.props.leftAlignmentEnabled) {
+      // The viewport is centered horizontally, so horizontal resize resizes
+      // by twice the distance the mouse was dragged - on left and right side.
+      deltaX = deltaX * 2;
+    }
 
     if (ignoreX) {
       deltaX = 0;
     }
     if (ignoreY) {
       deltaY = 0;
     }
 
--- a/devtools/client/responsive.html/components/SettingsMenu.js
+++ b/devtools/client/responsive.html/components/SettingsMenu.js
@@ -1,44 +1,59 @@
 /* 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 { connect } = require("devtools/client/shared/vendor/react-redux");
 const { PureComponent } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
 
 class SettingsMenu extends PureComponent {
   static get propTypes() {
     return {
+      leftAlignmentEnabled: PropTypes.bool.isRequired,
       reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
       onChangeReloadCondition: PropTypes.func.isRequired,
+      onToggleLeftAlignment: PropTypes.func.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onToggleSettingMenu = this.onToggleSettingMenu.bind(this);
   }
 
   onToggleSettingMenu(event) {
     const {
+      leftAlignmentEnabled,
       reloadConditions,
       onChangeReloadCondition,
+      onToggleLeftAlignment,
     } = this.props;
 
     const menuItems = [
       {
+        id: "toggleLeftAlignment",
+        checked: leftAlignmentEnabled,
+        label: getStr("responsive.leftAlignViewport"),
+        type: "checkbox",
+        click: () => {
+          onToggleLeftAlignment();
+        },
+      },
+      "-",
+      {
         id: "touchSimulation",
         checked: reloadConditions.touchSimulation,
         label: getStr("responsive.reloadConditions.touchSimulation"),
         type: "checkbox",
         click: () => {
           onChangeReloadCondition("touchSimulation", !reloadConditions.touchSimulation);
         },
       },
@@ -65,9 +80,15 @@ class SettingsMenu extends PureComponent
         id: "settings-button",
         className: "devtools-button",
         onClick: this.onToggleSettingMenu,
       })
     );
   }
 }
 
-module.exports = SettingsMenu;
+const mapStateToProps = state => {
+  return {
+    leftAlignmentEnabled: state.ui.leftAlignmentEnabled,
+  };
+};
+
+module.exports = connect(mapStateToProps)(SettingsMenu);
--- a/devtools/client/responsive.html/components/Toolbar.js
+++ b/devtools/client/responsive.html/components/Toolbar.js
@@ -34,16 +34,17 @@ class Toolbar extends PureComponent {
       onChangePixelRatio: PropTypes.func.isRequired,
       onChangeReloadCondition: PropTypes.func.isRequired,
       onChangeTouchSimulation: PropTypes.func.isRequired,
       onExit: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       onRotateViewport: PropTypes.func.isRequired,
       onScreenshot: PropTypes.func.isRequired,
+      onToggleLeftAlignment: PropTypes.func.isRequired,
       onUpdateDeviceModal: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
       devices,
       displayPixelRatio,
@@ -59,16 +60,17 @@ class Toolbar extends PureComponent {
       onChangePixelRatio,
       onChangeReloadCondition,
       onChangeTouchSimulation,
       onExit,
       onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
+      onToggleLeftAlignment,
       onUpdateDeviceModal,
     } = this.props;
 
     return dom.header(
       { id: "toolbar" },
       DeviceSelector({
         devices,
         selectedDevice,
@@ -121,16 +123,17 @@ class Toolbar extends PureComponent {
           className: "devtools-button",
           title: getStr("responsive.screenshot"),
           onClick: onScreenshot,
           disabled: screenshot.isCapturing,
         }),
         SettingsMenu({
           reloadConditions,
           onChangeReloadCondition,
+          onToggleLeftAlignment,
         }),
         dom.div({ className: "devtools-separator" }),
         dom.button({
           id: "exit-button",
           className: "devtools-button",
           title: getStr("responsive.exit"),
           onClick: onExit,
         })
--- a/devtools/client/responsive.html/components/Viewports.js
+++ b/devtools/client/responsive.html/components/Viewports.js
@@ -1,36 +1,39 @@
 /* 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 { connect } = require("devtools/client/shared/vendor/react-redux");
 const { Component, createFactory } = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 
 const ResizableViewport = createFactory(require("./ResizableViewport"));
 
 const Types = require("../types");
 
 class Viewports extends Component {
   static get propTypes() {
     return {
+      leftAlignmentEnabled: PropTypes.bool.isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
       onBrowserMounted: PropTypes.func.isRequired,
       onContentResize: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
     };
   }
 
   render() {
     const {
+      leftAlignmentEnabled,
       screenshot,
       viewports,
       onBrowserMounted,
       onContentResize,
       onRemoveDeviceAssociation,
       onResizeViewport,
     } = this.props;
 
@@ -38,40 +41,52 @@ class Viewports extends Component {
     // 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) {
+    if (leftAlignmentEnabled ||
+        (viewportSize && viewportSize.width > window.innerWidth)) {
       justifyContent = "start";
     }
 
     return (
       dom.div(
         {
           id: "viewports-container",
           style: {
             justifyContent,
           },
         },
-        dom.div({ id: "viewports" },
+        dom.div(
+          {
+            id: "viewports",
+            className: leftAlignmentEnabled ? "left-aligned" : "",
+          },
           viewports.map((viewport, i) => {
             return ResizableViewport({
               key: viewport.id,
+              leftAlignmentEnabled,
               screenshot,
               swapAfterMount: i == 0,
               viewport,
               onBrowserMounted,
               onContentResize,
               onRemoveDeviceAssociation,
               onResizeViewport,
             });
           })
         )
       )
     );
   }
 }
 
-module.exports = Viewports;
+const mapStateToProps = state => {
+  return {
+    leftAlignmentEnabled: state.ui.leftAlignmentEnabled,
+  };
+};
+
+module.exports = connect(mapStateToProps)(Viewports);
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -126,31 +126,34 @@ body,
      macOS for example). */
   max-width: 8em;
   background-position: right 4px center;
   padding-left: 0;
 }
 
 #viewports-container {
   display: flex;
-  justify-content: center;
   overflow: auto;
   height: 100%;
   width: 100%;
 }
 
 .theme-light #viewports-container {
   background-color: #F5F5F6;
 }
 
 #viewports {
   /* Individual viewports are inline elements, make sure they stay on a single
      line */
   white-space: nowrap;
-  padding-top: 30px;
+  margin-top: 16px;
+}
+
+#viewports.left-aligned {
+  margin-left: 16px;
 }
 
 /**
  * Viewport Container
  */
 
 .viewport {
   display: inline-block;
--- a/devtools/client/responsive.html/reducers.js
+++ b/devtools/client/responsive.html/reducers.js
@@ -6,9 +6,10 @@
 
 exports.devices = require("./reducers/devices");
 exports.displayPixelRatio = require("./reducers/display-pixel-ratio");
 exports.location = require("./reducers/location");
 exports.networkThrottling = require("devtools/client/shared/components/throttling/reducer");
 exports.reloadConditions = require("./reducers/reload-conditions");
 exports.screenshot = require("./reducers/screenshot");
 exports.touchSimulation = require("./reducers/touch-simulation");
+exports.ui = require("./reducers/ui");
 exports.viewports = require("./reducers/viewports");
--- a/devtools/client/responsive.html/reducers/moz.build
+++ b/devtools/client/responsive.html/reducers/moz.build
@@ -6,10 +6,11 @@
 
 DevToolsModules(
     'devices.js',
     'display-pixel-ratio.js',
     'location.js',
     'reload-conditions.js',
     'screenshot.js',
     'touch-simulation.js',
+    'ui.js',
     'viewports.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/ui.js
@@ -0,0 +1,41 @@
+/* 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 Services = require("Services");
+
+const {
+  TOGGLE_LEFT_ALIGNMENT,
+} = require("../actions/index");
+
+const LEFT_ALIGNMENT_ENABLED = "devtools.responsive.leftAlignViewport.enabled";
+
+const INITIAL_UI = {
+  // Whether or not the viewports are left aligned.
+  leftAlignmentEnabled: Services.prefs.getBoolPref(LEFT_ALIGNMENT_ENABLED),
+};
+
+const reducers = {
+
+  [TOGGLE_LEFT_ALIGNMENT](ui, { enabled }) {
+    const leftAlignmentEnabled = enabled !== undefined ?
+      enabled : !ui.leftAlignmentEnabled;
+
+    Services.prefs.setBoolPref(LEFT_ALIGNMENT_ENABLED, leftAlignmentEnabled);
+
+    return Object.assign({}, ui, {
+      leftAlignmentEnabled,
+    });
+  },
+
+};
+
+module.exports = function(ui = INITIAL_UI, action) {
+  const reducer = reducers[action.type];
+  if (!reducer) {
+    return ui;
+  }
+  return reducer(ui, action);
+};