Bug 1338300 - part3: add colorpicker to update grid overlay color;r=gl draft
authorJulian Descottes <jdescottes@mozilla.com>
Wed, 22 Feb 2017 12:39:04 +0100
changeset 487907 88ffb25a7737d4ea44911a7d6101454298387824
parent 487880 a116abf52a1c0b77b6ad5a06bde9aafe53d2b10c
child 487910 243ddbe9ec5dcbc9c26ee035a77a8a7fd7246b00
child 487915 4711ca83e84cd3364ac782016e7f06f85c655ab2
push id46401
push userjdescottes@mozilla.com
push dateWed, 22 Feb 2017 11:40:40 +0000
reviewersgl
bugs1338300
milestone54.0a1
Bug 1338300 - part3: add colorpicker to update grid overlay color;r=gl MozReview-Commit-ID: 5wgZCgx8J3u
devtools/client/inspector/layout/actions/grids.js
devtools/client/inspector/layout/actions/index.js
devtools/client/inspector/layout/components/App.js
devtools/client/inspector/layout/components/Grid.js
devtools/client/inspector/layout/components/GridItem.js
devtools/client/inspector/layout/components/GridList.js
devtools/client/inspector/layout/layout.js
devtools/client/inspector/layout/reducers/grids.js
devtools/client/inspector/layout/types.js
devtools/client/themes/layout.css
--- a/devtools/client/inspector/layout/actions/grids.js
+++ b/devtools/client/inspector/layout/actions/grids.js
@@ -1,34 +1,51 @@
 /* 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 {
+  UPDATE_GRID_COLOR,
   UPDATE_GRID_HIGHLIGHTED,
   UPDATE_GRIDS,
 } = require("./index");
 
 module.exports = {
 
   /**
+   * Update the color used for the grid's highlighter.
+   *
+   * @param  {NodeFront} nodeFront
+   *         The NodeFront of the DOM node to toggle the grid highlighter.
+   * @param  {String} color
+   *         The color to use for thie nodeFront's grid highlighter.
+   */
+  updateGridColor(nodeFront, color) {
+    return {
+      type: UPDATE_GRID_COLOR,
+      color,
+      nodeFront,
+    };
+  },
+
+  /**
    * Update the grid highlighted state.
    *
    * @param  {NodeFront} nodeFront
    *         The NodeFront of the DOM node to toggle the grid highlighter.
    * @param  {Boolean} highlighted
    *         Whether or not the grid highlighter is highlighting the grid.
    */
   updateGridHighlighted(nodeFront, highlighted) {
     return {
       type: UPDATE_GRID_HIGHLIGHTED,
+      highlighted,
       nodeFront,
-      highlighted,
     };
   },
 
   /**
    * Update the grid state with the new list of grids.
    */
   updateGrids(grids) {
     return {
--- a/devtools/client/inspector/layout/actions/index.js
+++ b/devtools/client/inspector/layout/actions/index.js
@@ -3,16 +3,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createEnum } = require("devtools/client/shared/enum");
 
 createEnum([
 
+  // Update the color used for the overlay of a grid.
+  "UPDATE_GRID_COLOR",
+
   // Update the grid highlighted state.
   "UPDATE_GRID_HIGHLIGHTED",
 
   // Update the entire grids state with the new list of grids.
   "UPDATE_GRIDS",
 
   // Update the layout state with the latest layout properties.
   "UPDATE_LAYOUT",
--- a/devtools/client/inspector/layout/components/App.js
+++ b/devtools/client/inspector/layout/components/App.js
@@ -21,21 +21,23 @@ const BOXMODEL_STRINGS_URI = "devtools/c
 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI);
 
 const App = createClass({
 
   displayName: "App",
 
   propTypes: {
     boxModel: PropTypes.shape(Types.boxModel).isRequired,
+    getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
     showBoxModelProperties: PropTypes.bool.isRequired,
+    onHideBoxModelHighlighter: PropTypes.func.isRequired,
+    onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelEditor: PropTypes.func.isRequired,
-    onHideBoxModelHighlighter: PropTypes.func.isRequired,
     onShowBoxModelHighlighter: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
--- a/devtools/client/inspector/layout/components/Grid.js
+++ b/devtools/client/inspector/layout/components/Grid.js
@@ -13,41 +13,47 @@ const GridList = createFactory(require("
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 module.exports = createClass({
 
   displayName: "Grid",
 
   propTypes: {
+    getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
+    onSetGridOverlayColor: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
     onToggleShowInfiniteLines: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
+      getSwatchColorPickerTooltip,
       grids,
       highlighterSettings,
+      onSetGridOverlayColor,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
     } = this.props;
 
     return grids.length ?
       dom.div(
         {
           id: "layout-grid-container",
         },
         GridList({
+          getSwatchColorPickerTooltip,
           grids,
+          onSetGridOverlayColor,
           onToggleGridHighlighter,
         }),
         GridDisplaySettings({
           highlighterSettings,
           onToggleShowGridLineNumbers,
           onToggleShowInfiniteLines,
         })
       )
--- a/devtools/client/inspector/layout/components/GridItem.js
+++ b/devtools/client/inspector/layout/components/GridItem.js
@@ -1,31 +1,60 @@
 /* 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 { addons, createClass, DOM: dom, PropTypes } =
-  require("devtools/client/shared/vendor/react");
+const { addons, createClass, DOM: dom, PropTypes } = require("devtools/client/shared/vendor/react");
+const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 
 const Types = require("../types");
 
 module.exports = createClass({
 
   displayName: "GridItem",
 
   propTypes: {
+    getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grid: PropTypes.shape(Types.grid).isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
+  componentDidMount() {
+    let tooltip = this.props.getSwatchColorPickerTooltip();
+    let swatchEl = findDOMNode(this).querySelector(".grid-color-swatch");
+
+    let previousColor;
+    tooltip.addSwatch(swatchEl, {
+      onCommit: this.setGridColor,
+      onPreview: this.setGridColor,
+      onRevert: () => {
+        this.props.onSetGridOverlayColor(this.props.grid.nodeFront, previousColor);
+      },
+      onShow: () => {
+        previousColor = this.props.grid.color;
+      },
+    });
+  },
+
+  componentWillUnmount() {
+    let tooltip = this.props.getSwatchColorPickerTooltip();
+    let swatchEl = findDOMNode(this).querySelector(".grid-color-swatch");
+    tooltip.removeSwatch(swatchEl);
+  },
+
+  setGridColor() {
+    let color = findDOMNode(this).querySelector(".grid-color-value").textContent;
+    this.props.onSetGridOverlayColor(this.props.grid.nodeFront, color);
+  },
+
   onGridCheckboxClick() {
     let {
       grid,
       onToggleGridHighlighter,
     } = this.props;
 
     onToggleGridHighlighter(grid.nodeFront);
   },
@@ -45,25 +74,45 @@ module.exports = createClass({
     let classIndex = attributes.findIndex(({name}) => name === "class");
     if (classIndex > -1 && attributes[classIndex].value) {
       gridName += "." + attributes[classIndex].value.split(" ").join(".");
     }
 
     return dom.li(
       {
         key: grid.id,
+        className: "grid-item",
       },
       dom.label(
         {},
         dom.input(
           {
             type: "checkbox",
             value: grid.id,
             checked: grid.highlighted,
             onChange: this.onGridCheckboxClick,
           }
         ),
         gridName
+      ),
+      dom.div(
+        {
+          className: "grid-color-swatch",
+          style: {
+            backgroundColor: grid.color,
+          },
+          title: grid.color,
+        }
+      ),
+      // The SwatchColorPicker relies on the nextSibling of the swatch element to apply
+      // the selected color. This is why we use a span in display: none for now.
+      // Ideally we should modify the SwatchColorPickerTooltip to bypass this requirement.
+      // See https://bugzilla.mozilla.org/show_bug.cgi?id=1341578
+      dom.span(
+        {
+          className: "grid-color-value"
+        },
+        grid.color
       )
     );
   },
 
 });
--- a/devtools/client/inspector/layout/components/GridList.js
+++ b/devtools/client/inspector/layout/components/GridList.js
@@ -12,39 +12,45 @@ const GridItem = createFactory(require("
 const Types = require("../types");
 const { getStr } = require("../utils/l10n");
 
 module.exports = createClass({
 
   displayName: "GridList",
 
   propTypes: {
+    getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+    onSetGridOverlayColor: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
+      getSwatchColorPickerTooltip,
       grids,
+      onSetGridOverlayColor,
       onToggleGridHighlighter,
     } = this.props;
 
     return dom.div(
       {
         className: "grid-container",
       },
       dom.span(
         {},
         getStr("layout.overlayGrid")
       ),
       dom.ul(
         {},
         grids.map(grid => GridItem({
+          getSwatchColorPickerTooltip,
           grid,
+          onSetGridOverlayColor,
           onToggleGridHighlighter,
         }))
       )
     );
   },
 
 });
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -8,20 +8,23 @@ const Services = require("Services");
 const { Task } = require("devtools/shared/task");
 const { getCssProperties } = require("devtools/shared/fronts/css-properties");
 const { ReflowFront } = require("devtools/shared/fronts/reflow");
 
 const { InplaceEditor } = require("devtools/client/shared/inplace-editor");
 const { createFactory, createElement } = require("devtools/client/shared/vendor/react");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
+const SwatchColorPickerTooltip = require("devtools/client/shared/widgets/tooltip/SwatchColorPickerTooltip");
+
 const {
   updateLayout,
 } = require("./actions/box-model");
 const {
+  updateGridColor,
   updateGridHighlighted,
   updateGrids,
 } = require("./actions/grids");
 const {
   updateShowGridLineNumbers,
   updateShowInfiniteLines,
 } = require("./actions/highlighter-settings");
 
@@ -32,16 +35,28 @@ const EditingSession = require("./utils/
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 
 const NUMERIC = /^-?[\d\.]+$/;
 const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers";
 const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines";
 
+// Default grid colors.
+const GRID_COLORS = [
+  "#05E4EE",
+  "#BB9DFF",
+  "#FFB53B",
+  "#71F362",
+  "#FF90FF",
+  "#FF90FF",
+  "#1B80FF",
+  "#FF2647"
+];
+
 function LayoutView(inspector, window) {
   this.document = window.document;
   this.highlighters = inspector.highlighters;
   this.inspector = inspector;
   this.store = inspector.store;
   this.walker = this.inspector.walker;
 
   this.updateBoxModel = this.updateBoxModel.bind(this);
@@ -69,32 +84,71 @@ LayoutView.prototype = {
     if (!this.inspector) {
       return;
     }
 
     this.layoutInspector = yield this.inspector.walker.getLayoutInspector();
 
     this.loadHighlighterSettings();
 
+    // Create a shared SwatchColorPicker instance to be reused by all GridItem components.
+    this.swatchColorPickerTooltip = new SwatchColorPickerTooltip(
+      this.inspector.toolbox.doc,
+      this.inspector,
+      {
+        supportsCssColor4ColorFunction: () => false
+      }
+    );
+
     let app = App({
       /**
+       * Retrieve the shared SwatchColorPicker instance.
+       */
+      getSwatchColorPickerTooltip: () => {
+        return this.swatchColorPickerTooltip;
+      },
+
+      /**
        * Shows the box model properties under the box model if true, otherwise, hidden by
        * default.
        */
       showBoxModelProperties: true,
 
       /**
        * Hides the box-model highlighter on the currently selected element.
        */
       onHideBoxModelHighlighter: () => {
         let toolbox = this.inspector.toolbox;
         toolbox.highlighterUtils.unhighlight();
       },
 
       /**
+       * Handler for a change in the grid overlay color picker for a grid container.
+       *
+       * @param  {NodeFront} node
+       *         The NodeFront of the grid container element for which the grid color is
+       *         being updated.
+       * @param  {String} color
+       *         A hex string representing the color to use.
+       */
+      onSetGridOverlayColor: (node, color) => {
+        this.store.dispatch(updateGridColor(node, color));
+        let { grids } = this.store.getState();
+
+        // If the grid for which the color was updated currently has a highlighter, update
+        // the color.
+        for (let grid of grids) {
+          if (grid.nodeFront === node && grid.highlighted) {
+            let highlighterSettings = this.getGridHighlighterSettings(node);
+            this.highlighters.showGridHighlighter(node, highlighterSettings);
+          }
+        }
+      },
+
+      /**
        * Shows the inplace editor when a box model editable value is clicked on the
        * box model panel.
        *
        * @param  {DOMNode} element
        *         The element that was clicked.
        * @param  {Event} event
        *         The event object.
        * @param  {String} property
@@ -175,37 +229,38 @@ LayoutView.prototype = {
        * Handler for a change in the input checkboxes in the GridList component.
        * Toggles on/off the grid highlighter for the provided grid container element.
        *
        * @param  {NodeFront} node
        *         The NodeFront of the grid container element for which the grid
        *         highlighter is toggled on/off for.
        */
       onToggleGridHighlighter: node => {
-        let { highlighterSettings } = this.store.getState();
+        let highlighterSettings = this.getGridHighlighterSettings(node);
         this.highlighters.toggleGridHighlighter(node, highlighterSettings);
       },
 
       /**
        * Handler for a change in the show grid line numbers checkbox in the
-       * GridDisplaySettings component. TOggles on/off the option to show the grid line
+       * GridDisplaySettings component. Toggles on/off the option to show the grid line
        * numbers in the grid highlighter. Refreshes the shown grid highlighter for the
        * grids currently highlighted.
        *
        * @param  {Boolean} enabled
        *         Whether or not the grid highlighter should show the grid line numbers.
        */
       onToggleShowGridLineNumbers: enabled => {
         this.store.dispatch(updateShowGridLineNumbers(enabled));
         Services.prefs.setBoolPref(SHOW_GRID_LINE_NUMBERS, enabled);
 
-        let { grids, highlighterSettings } = this.store.getState();
+        let { grids } = this.store.getState();
 
         for (let grid of grids) {
           if (grid.highlighted) {
+            let highlighterSettings = this.getGridHighlighterSettings(grid.nodeFront);
             this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings);
           }
         }
       },
 
       /**
        * Handler for a change in the extend grid lines infinitely checkbox in the
        * GridDisplaySettings component. Toggles on/off the option to extend the grid
@@ -214,24 +269,25 @@ LayoutView.prototype = {
        *
        * @param  {Boolean} enabled
        *         Whether or not the grid highlighter should extend grid lines infinitely.
        */
       onToggleShowInfiniteLines: enabled => {
         this.store.dispatch(updateShowInfiniteLines(enabled));
         Services.prefs.setBoolPref(SHOW_INFINITE_LINES_PREF, enabled);
 
-        let { grids, highlighterSettings } = this.store.getState();
+        let { grids } = this.store.getState();
 
         for (let grid of grids) {
           if (grid.highlighted) {
+            let highlighterSettings = this.getGridHighlighterSettings(grid.nodeFront);
             this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings);
           }
         }
-      },
+      }
     });
 
     let provider = createElement(Provider, {
       store: this.store,
       id: "layoutview",
       title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"),
       key: "layoutview",
     }, app);
@@ -266,16 +322,53 @@ LayoutView.prototype = {
     this.document = null;
     this.inspector = null;
     this.layoutInspector = null;
     this.store = null;
     this.walker = null;
   },
 
   /**
+   * Returns the color set for the grid highlighter associated with the provided
+   * nodeFront.
+   *
+   * @param  {NodeFront} nodeFront
+   *         The NodeFront for which we need the color.
+   */
+  getGridColorForNodeFront(nodeFront) {
+    let { grids } = this.store.getState();
+
+    for (let grid of grids) {
+      if (grid.nodeFront === nodeFront) {
+        return grid.color;
+      }
+    }
+
+    return null;
+  },
+
+  /**
+   * Create a highlighter settings object for the provided nodeFront.
+   *
+   * @param  {NodeFront} nodeFront
+   *         The NodeFront for which we need highlighter settings.
+   */
+  getGridHighlighterSettings(nodeFront) {
+    let { highlighterSettings } = this.store.getState();
+
+    // Get the grid color for the provided nodeFront.
+    let color = this.getGridColorForNodeFront(nodeFront);
+
+    // Merge the grid color to the generic highlighter settings.
+    return Object.assign({}, highlighterSettings, {
+      color
+    });
+  },
+
+  /**
    * Returns true if the layout panel is visible, and false otherwise.
    */
   isPanelVisible() {
     return this.inspector.toolbox.currentToolId === "inspector" &&
            this.inspector.sidebar &&
            this.inspector.sidebar.getCurrentTabID() === "layoutview";
   },
 
@@ -386,18 +479,22 @@ LayoutView.prototype = {
       gridFronts = yield this.layoutInspector.getAllGrids(this.walker.rootNode);
     }
 
     let grids = [];
     for (let i = 0; i < gridFronts.length; i++) {
       let grid = gridFronts[i];
       let nodeFront = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]);
 
+      let fallbackColor = GRID_COLORS[i % GRID_COLORS.length];
+      let color = this.getGridColorForNodeFront(nodeFront) || fallbackColor;
+
       grids.push({
         id: i,
+        color,
         gridFragments: grid.gridFragments,
         highlighted: nodeFront == this.highlighters.gridHighlighterShown,
         nodeFront,
       });
     }
 
     this.store.dispatch(updateGrids(grids));
   }),
--- a/devtools/client/inspector/layout/reducers/grids.js
+++ b/devtools/client/inspector/layout/reducers/grids.js
@@ -1,23 +1,36 @@
 /* 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 {
+  UPDATE_GRID_COLOR,
   UPDATE_GRID_HIGHLIGHTED,
   UPDATE_GRIDS,
 } = require("../actions/index");
 
 const INITIAL_GRIDS = [];
 
 let reducers = {
 
+  [UPDATE_GRID_COLOR](grids, { nodeFront, color }) {
+    let newGrids = grids.map(g => {
+      if (g.nodeFront == nodeFront) {
+        g.color = color;
+      }
+
+      return g;
+    });
+
+    return newGrids;
+  },
+
   [UPDATE_GRID_HIGHLIGHTED](grids, { nodeFront, highlighted }) {
     return grids.map(g => {
       return Object.assign({}, g, {
         highlighted: g.nodeFront === nodeFront ? highlighted : false
       });
     });
   },
 
--- a/devtools/client/inspector/layout/types.js
+++ b/devtools/client/inspector/layout/types.js
@@ -19,16 +19,19 @@ exports.boxModel = {
 /**
  * A single grid container in the document.
  */
 exports.grid = {
 
   // The id of the grid
   id: PropTypes.number,
 
+  // The color for the grid overlay highlighter
+  color: PropTypes.string,
+
   // The grid fragment object of the grid container
   gridFragments: PropTypes.array,
 
   // Whether or not the grid highlighter is highlighting the grid
   highlighted: PropTypes.bool,
 
   // The node front of the grid container
   nodeFront: PropTypes.object,
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -47,8 +47,34 @@
  * Container when no grids are present
  */
 
 .layout-no-grids {
   font-style: italic;
   text-align: center;
   padding: 0.5em;
 }
+
+/**
+ * Grid Item
+ */
+
+.grid-item {
+  display: flex;
+  align-items: center;
+}
+
+.grid-item input {
+  margin: 0 5px;
+}
+
+.grid-color-swatch {
+  width: 12px;
+  height: 12px;
+  margin-left: 5px;
+  border: 1px solid var(--theme-highlight-gray);
+  border-radius: 50%;
+  cursor: pointer;
+}
+
+.grid-color-value {
+  display: none;
+}