Bug 1373134 - Add a context menu to scroll into view of a grid cell in the grid outline. r?gl draft
authorMicah Tigley <tigleym@gmail.com>
Sun, 18 Jun 2017 21:50:36 -0600
changeset 597049 798c33a4f4e51ad41d85508b9a2ad041e5b1b98f
parent 596296 5021015129a39300cb189bf0e351b551d934cc72
child 634119 bc08627e28a827b26b271bc17ee91b8e5bf14a0b
push id64810
push userbmo:tigleym@gmail.com
push dateTue, 20 Jun 2017 02:28:55 +0000
reviewersgl
bugs1373134
milestone56.0a1
Bug 1373134 - Add a context menu to scroll into view of a grid cell in the grid outline. r?gl MozReview-Commit-ID: L66v119nqfc
devtools/client/inspector/grids/components/Grid.js
devtools/client/inspector/grids/components/GridOutline.js
devtools/client/inspector/grids/grid-inspector.js
devtools/client/inspector/layout/layout.js
devtools/client/themes/layout.css
devtools/server/actors/highlighters/css-grid.js
--- a/devtools/client/inspector/grids/components/Grid.js
+++ b/devtools/client/inspector/grids/components/Grid.js
@@ -20,16 +20,17 @@ module.exports = createClass({
 
   propTypes: {
     getSwatchColorPickerTooltip: PropTypes.func.isRequired,
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
     highlighterSettings: PropTypes.shape(Types.highlighterSettings).isRequired,
     setSelectedNode: PropTypes.func.isRequired,
     showGridOutline: PropTypes.bool.isRequired,
     onHideBoxModelHighlighter: PropTypes.func.isRequired,
+    onScrollToGridCellHighlight: PropTypes.func.isRequired,
     onSetGridOverlayColor: PropTypes.func.isRequired,
     onShowBoxModelHighlighterForNode: PropTypes.func.isRequired,
     onShowGridAreaHighlight: PropTypes.func.isRequired,
     onShowGridCellHighlight: PropTypes.func.isRequired,
     onShowGridLineNamesHighlight: PropTypes.func.isRequired,
     onToggleGridHighlighter: PropTypes.func.isRequired,
     onToggleShowGridAreas: PropTypes.func.isRequired,
     onToggleShowGridLineNumbers: PropTypes.func.isRequired,
@@ -41,16 +42,17 @@ module.exports = createClass({
   render() {
     let {
       getSwatchColorPickerTooltip,
       grids,
       highlighterSettings,
       setSelectedNode,
       showGridOutline,
       onHideBoxModelHighlighter,
+      onScrollToGridCellHighlight,
       onSetGridOverlayColor,
       onShowBoxModelHighlighterForNode,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onToggleShowGridAreas,
       onToggleGridHighlighter,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
@@ -79,16 +81,17 @@ module.exports = createClass({
             onToggleShowGridAreas,
             onToggleShowGridLineNumbers,
             onToggleShowInfiniteLines,
           })
         ),
         showGridOutline ?
           GridOutline({
             grids,
+            onScrollToGridCellHighlight,
             onShowGridAreaHighlight,
             onShowGridCellHighlight,
           })
           :
           null
       )
       :
       dom.div(
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -28,28 +28,30 @@ const VIEWPORT_MIN_HEIGHT = 100;
 const VIEWPORT_MAX_HEIGHT = 150;
 
 module.exports = createClass({
 
   displayName: "GridOutline",
 
   propTypes: {
     grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired,
+    onScrollToGridCellHighlight: PropTypes.func.isRequired,
     onShowGridAreaHighlight: PropTypes.func.isRequired,
     onShowGridCellHighlight: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {
       height: 0,
       selectedGrid: null,
       showOutline: true,
       width: 0,
+      showContextMenu: false,
     };
   },
 
   componentWillReceiveProps({ grids }) {
     let selectedGrid = grids.find(grid => grid.highlighted);
 
     // Store the height of the grid container in the component state to prevent overflow
     // issues. We want to store the width of the grid container as well so that the
@@ -124,16 +126,40 @@ module.exports = createClass({
       return VIEWPORT_MAX_HEIGHT;
     } else if (height <= VIEWPORT_MIN_HEIGHT) {
       return VIEWPORT_MIN_HEIGHT;
     }
 
     return height;
   },
 
+  onContextMenu(e, gridFragmentIndex, rowNumber, columnNumber) {
+    e.preventDefault();
+    const { clientX, clientY } = e;
+    const contextMenu = this.refs.contextmenu;
+    contextMenu.style.left = clientX + "px";
+    contextMenu.style.top = clientY + "px";
+
+    this.setState({
+      showContextMenu: true,
+      gridFragmentIndex,
+      rowNumber,
+      columnNumber
+    });
+  },
+
+  onGridOutlineClick(e) {
+    this.setState({
+      showContextMenu: false,
+      gridFragmentIndex: null,
+      rowNumber: null,
+      columnNumber: null,
+    });
+  },
+
   onHighlightCell({ target, type }) {
     // Debounce the highlighting of cells.
     // This way we don't end up sending many requests to the server for highlighting when
     // cells get hovered in a rapid succession We only send a request if the user settles
     // on a cell for some time.
     if (this.highlightTimeout) {
       clearTimeout(this.highlightTimeout);
     }
@@ -142,42 +168,61 @@ module.exports = createClass({
       this.doHighlightCell(target, type === "mouseleave");
       this.highlightTimeout = null;
     }, GRID_HIGHLIGHTING_DEBOUNCE);
   },
 
   doHighlightCell(target, hide) {
     const {
       grids,
+      onScrollToGridCellHighlight,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
     } = this.props;
     const name = target.dataset.gridAreaName;
     const id = target.dataset.gridId;
     const fragmentIndex = target.dataset.gridFragmentIndex;
     const color = target.closest(".grid-cell-group").dataset.gridLineColor;
     const rowNumber = target.dataset.gridRow;
     const columnNumber = target.dataset.gridColumn;
 
     if (hide) {
       onShowGridAreaHighlight(grids[id].nodeFront, null, color);
       onShowGridCellHighlight(grids[id].nodeFront, color);
+      onScrollToGridCellHighlight(grids[id].nodeFront, color);
       return;
     }
 
     if (name) {
       onShowGridAreaHighlight(grids[id].nodeFront, name, color);
     }
 
     if (fragmentIndex && rowNumber && columnNumber) {
       onShowGridCellHighlight(grids[id].nodeFront, color, fragmentIndex,
         rowNumber, columnNumber);
+      onScrollToGridCellHighlight(grids[id].nodeFront, color);
     }
   },
 
+  onScrollToGridCell({ target }) {
+    const { grids, onScrollToGridCellHighlight } = this.props;
+    const { gridFragmentIndex, columnNumber, rowNumber, selectedGrid } = this.state;
+    const { id, color } = selectedGrid;
+
+    onScrollToGridCellHighlight(grids[id].nodeFront, color,
+                                gridFragmentIndex, rowNumber, columnNumber, true);
+
+    this.setState({
+      showContextMenu: false,
+      gridFragmentIndex: null,
+      rowNumber: null,
+      columnNumber: null,
+    });
+  },
+
   /**
    * Displays a message text "Cannot show outline for this grid".
    */
   renderCannotShowOutlineText() {
     return dom.div(
       {
         className: "grid-outline-text"
       },
@@ -281,22 +326,44 @@ module.exports = createClass({
         "data-grid-id": id,
         "data-grid-row": rowNumber,
         "data-grid-column": columnNumber,
         x,
         y,
         width,
         height,
         fill: "none",
+        onContextMenu: (e) => this.onContextMenu(e,
+          gridFragmentIndex, rowNumber, columnNumber),
         onMouseEnter: this.onHighlightCell,
         onMouseLeave: this.onHighlightCell,
       }
     );
   },
 
+  renderGridCellContextMenu() {
+    const { showContextMenu } = this.state;
+
+    return dom.div(
+      {
+        className: "grid-outline-context-menu",
+        ref: "contextmenu"
+      },
+      showContextMenu ?
+        dom.div({
+          onClick: this.onScrollToGridCell,
+          className: "grid-outline-context-menu-item",
+        },
+        "Scroll to grid cell"
+      )
+      :
+      null
+    );
+  },
+
   renderGridOutline(grid) {
     let { color } = grid;
 
     return dom.g(
       {
         "className": "grid-cell-group",
         "data-grid-line-color": color,
         "style": { color }
@@ -341,16 +408,18 @@ module.exports = createClass({
 
   render() {
     const { selectedGrid } = this.state;
 
     return selectedGrid ?
       dom.div(
         {
           className: "grid-outline",
+          onClick: this.onGridOutlineClick,
         },
-        this.renderOutline()
+        this.renderOutline(),
+        this.renderGridCellContextMenu()
       )
       :
       null;
   },
 
 });
--- a/devtools/client/inspector/grids/grid-inspector.js
+++ b/devtools/client/inspector/grids/grid-inspector.js
@@ -47,16 +47,17 @@ function GridInspector(inspector, window
 
   this.getSwatchColorPickerTooltip = this.getSwatchColorPickerTooltip.bind(this);
   this.updateGridPanel = this.updateGridPanel.bind(this);
 
   this.onGridLayoutChange = this.onGridLayoutChange.bind(this);
   this.onHighlighterChange = this.onHighlighterChange.bind(this);
   this.onMarkupMutation = this.onMarkupMutation.bind(this);
   this.onReflow = this.onReflow.bind(this);
+  this.onScrollToGridCellHighlight = this.onScrollToGridCellHighlight.bind(this);
   this.onSetGridOverlayColor = this.onSetGridOverlayColor.bind(this);
   this.onShowGridAreaHighlight = this.onShowGridAreaHighlight.bind(this);
   this.onShowGridCellHighlight = this.onShowGridCellHighlight.bind(this);
   this.onShowGridLineNamesHighlight = this.onShowGridLineNamesHighlight.bind(this);
   this.onSidebarSelect = this.onSidebarSelect.bind(this);
   this.onToggleGridHighlighter = this.onToggleGridHighlighter.bind(this);
   this.onToggleShowGridAreas = this.onToggleShowGridAreas.bind(this);
   this.onToggleShowGridLineNumbers = this.onToggleShowGridLineNumbers.bind(this);
@@ -120,16 +121,17 @@ GridInspector.prototype = {
     this.swatchColorPickerTooltip = null;
     this.walker = null;
   },
 
   getComponentProps() {
     return {
       getSwatchColorPickerTooltip: this.getSwatchColorPickerTooltip,
       onSetGridOverlayColor: this.onSetGridOverlayColor,
+      onScrollToGridCellHighlight: this.onScrollToGridCellHighlight,
       onShowGridAreaHighlight: this.onShowGridAreaHighlight,
       onShowGridCellHighlight: this.onShowGridCellHighlight,
       onShowGridLineNamesHighlight: this.onShowGridLineNamesHighlight,
       onToggleGridHighlighter: this.onToggleGridHighlighter,
       onToggleShowGridAreas: this.onToggleShowGridAreas,
       onToggleShowGridLineNumbers: this.onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines: this.onToggleShowInfiniteLines,
     };
@@ -378,16 +380,54 @@ GridInspector.prototype = {
       if (grid.nodeFront === node && grid.highlighted) {
         let highlighterSettings = this.getGridHighlighterSettings(node);
         this.showGridHighlighter(node, highlighterSettings);
       }
     }
   },
 
   /**
+   * Scrolls to the grid cell in the CSS Grid Highlighter for the given grid.
+   *
+   * @param  {NodeFront} node
+   *         The NodeFront of the grid container element for which the grid
+   *         highlighter is highlighted for.
+   * @param  {String} color
+   *         The color of the grid cell for which the grid highlighter
+   *         is highlighted for.
+   * @param  {Number|null} gridFragmentIndex
+   *         The index of the grid fragment for which the grid highlighter
+   *         is highlighted for.
+   * @param  {Number|null} rowNumber
+   *         The row number of the grid cell for which the grid highlighter
+   *         is highlighted for.
+   * @param  {Number|null} columnNumber
+   *         The column number of the grid cell for which the grid highlighter
+   *         is highlighted for.
+   * @param  {Boolean|null} scroll
+   *         Whether or not to scroll to the grid cell for which the grid highlighter
+   *         is highlighted for.
+   */
+  onScrollToGridCellHighlight(node,
+    color, gridFragmentIndex, rowNumber, columnNumber, scroll) {
+    let { highlighterSettings } = this.store.getState();
+
+    highlighterSettings.scrollToGridCell = scroll;
+
+    if (scroll) {
+      highlighterSettings.showGridCell = { gridFragmentIndex, rowNumber, columnNumber };
+      highlighterSettings.color = color;
+    }
+
+    this.showGridHighlighter(node, highlighterSettings);
+
+    this.store.dispatch(updateGridHighlighted(node, true));
+  },
+
+  /**
    * Highlights the grid area in the CSS Grid Highlighter for the given grid.
    *
    * @param  {NodeFront} node
    *         The NodeFront of the grid container element for which the grid
    *         highlighter is highlighted for.
    * @param  {String} gridAreaName
    *         The name of the grid area for which the grid highlighter
    *         is highlighted for.
--- a/devtools/client/inspector/layout/layout.js
+++ b/devtools/client/inspector/layout/layout.js
@@ -48,16 +48,17 @@ LayoutView.prototype = {
       onHideBoxModelHighlighter,
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onToggleGeometryEditor,
     } = this.inspector.getPanel("boxmodel").getComponentProps();
 
     let {
       getSwatchColorPickerTooltip,
+      onScrollToGridCellHighlight,
       onSetGridOverlayColor,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGridHighlighter,
       onToggleShowGridAreas,
       onToggleShowGridLineNumbers,
       onToggleShowInfiniteLines,
@@ -79,16 +80,17 @@ LayoutView.prototype = {
       /**
        * Shows the grid outline if user preferences are set to true, otherwise, hidden by
        * default.
        */
       showGridOutline: Services.prefs.getBoolPref(SHOW_GRID_OUTLINE_PREF),
 
       onHideBoxModelHighlighter,
       onPromoteLearnMoreClick,
+      onScrollToGridCellHighlight,
       onSetGridOverlayColor,
       onShowBoxModelEditor,
       onShowBoxModelHighlighter,
       onShowBoxModelHighlighterForNode,
       onShowGridAreaHighlight,
       onShowGridCellHighlight,
       onShowGridLineNamesHighlight,
       onToggleGeometryEditor,
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -149,16 +149,36 @@
   vector-effect: non-scaling-stroke;
 }
 
 .grid-outline-cell:hover {
   opacity: 0.45;
   fill: currentColor;
 }
 
+.grid-outline-context-menu {
+  position: fixed;
+  background: white;
+  border-radius: 3px 3px 3px 3px;
+  box-shadow: 0px 2px 10px #999999;
+}
+
+.grid-outline-context-menu-item {
+  border-radius: 3px 3px 3px 3px;
+  padding: 6px 50px 5px 10px;
+  min-width: 160px;
+  cursor: pointer;
+  font-size: 12px;
+}
+
+.grid-outline-context-menu-item:hover {
+  background: linear-gradient(to top, #555, #333);
+  color: white;
+}
+
 .grid-outline-line {
   opacity: 0;
   stroke-width: 10;
 }
 
 .grid-outline-text {
   display: flex;
   align-items: center;
--- a/devtools/server/actors/highlighters/css-grid.js
+++ b/devtools/server/actors/highlighters/css-grid.js
@@ -717,16 +717,61 @@ CssGridHighlighter.prototype = extend(Au
     return this._update();
   },
 
   _clearCache() {
     gCachedGridPattern.clear();
   },
 
   /**
+   * Scrolls to the grid cell highlight for the given grid cell options.
+   *
+   * @param  {Number} options.gridFragmentIndex
+   *         Index of the grid fragment to scroll to grid cell highlight.
+   * @param  {Number} options.rowNumber
+   *         Row number of the grid cell to scroll to.
+   * @param  {Number} options.columnNumber
+   *         Column number of the grid cell to scroll to.
+   */
+  scrollToGridCell({ gridFragmentIndex, rowNumber, columnNumber }) {
+    let fragment = this.gridData[gridFragmentIndex];
+    let displayPixelRatio = getDisplayPixelRatio(this.win);
+
+    if (!fragment) {
+      return;
+    }
+
+    let row = fragment.rows.tracks[rowNumber - 1];
+    let column = fragment.cols.tracks[columnNumber - 1];
+
+    if (!row || !column) {
+      return;
+    }
+
+    let currentZoom = getCurrentZoom(this.win);
+    let x1 = column.start;
+    let y1 = row.start;
+    let x2 = column.start + column.breadth;
+    let y2 = row.start + row.breadth;
+
+    let points = getPointsFromDiagonal(x1, y1, x2, y2, this.currentMatrix);
+
+    let bounds = getBoundsFromPoints(points.map(point => ({
+      x: Math.round(point.x / displayPixelRatio),
+      y: Math.round(point.y / displayPixelRatio)
+    })));
+
+    const { x, y } = bounds;
+    const xPos = x / currentZoom;
+    const yPos = y / currentZoom;
+
+    this.win.scrollTo(xPos * displayPixelRatio, (yPos - 100) * displayPixelRatio);
+  },
+
+  /**
    * Shows the grid area highlight for the given area name.
    *
    * @param  {String} areaName
    *         Grid area name.
    */
   showGridArea(areaName) {
     this.renderGridArea(areaName);
   },
@@ -863,16 +908,20 @@ CssGridHighlighter.prototype = extend(Au
       this.showAllGridAreas();
     } else if (this.options.showGridArea) {
       this.showGridArea(this.options.showGridArea);
     }
 
     // Display the grid cell highlights if needed.
     if (this.options.showGridCell) {
       this.showGridCell(this.options.showGridCell);
+
+      if (this.options.scrollToGridCell) {
+        this.scrollToGridCell(this.options.showGridCell);
+      }
     }
 
     // Display the grid line names if needed.
     if (this.options.showGridLineNames) {
       this.showGridLineNames(this.options.showGridLineNames);
     }
 
     this._showGrid();