diff --git a/devtools/client/inspector/layout/actions/grids.js b/devtools/client/inspector/layout/actions/grids.js index a1744b70046d..0f69ef62f025 100644 --- a/devtools/client/inspector/layout/actions/grids.js +++ b/devtools/client/inspector/layout/actions/grids.js @@ -5,12 +5,29 @@ "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. * @@ -22,8 +39,8 @@ module.exports = { updateGridHighlighted(nodeFront, highlighted) { return { type: UPDATE_GRID_HIGHLIGHTED, - nodeFront, highlighted, + nodeFront, }; }, diff --git a/devtools/client/inspector/layout/actions/index.js b/devtools/client/inspector/layout/actions/index.js index d2b3383877db..d694e37400a2 100644 --- a/devtools/client/inspector/layout/actions/index.js +++ b/devtools/client/inspector/layout/actions/index.js @@ -8,6 +8,9 @@ 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", diff --git a/devtools/client/inspector/layout/components/App.js b/devtools/client/inspector/layout/components/App.js index 403eccdf9101..d8dd19582d0c 100644 --- a/devtools/client/inspector/layout/components/App.js +++ b/devtools/client/inspector/layout/components/App.js @@ -26,11 +26,13 @@ const App = createClass({ 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, - onShowBoxModelEditor: PropTypes.func.isRequired, onHideBoxModelHighlighter: PropTypes.func.isRequired, + onSetGridOverlayColor: PropTypes.func.isRequired, + onShowBoxModelEditor: PropTypes.func.isRequired, onShowBoxModelHighlighter: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, diff --git a/devtools/client/inspector/layout/components/Grid.js b/devtools/client/inspector/layout/components/Grid.js index 2e1e63cc9f0f..68a88ee26646 100644 --- a/devtools/client/inspector/layout/components/Grid.js +++ b/devtools/client/inspector/layout/components/Grid.js @@ -18,8 +18,10 @@ 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, @@ -29,8 +31,10 @@ module.exports = createClass({ render() { let { + getSwatchColorPickerTooltip, grids, highlighterSettings, + onSetGridOverlayColor, onToggleGridHighlighter, onToggleShowGridLineNumbers, onToggleShowInfiniteLines, @@ -42,7 +46,9 @@ module.exports = createClass({ id: "layout-grid-container", }, GridList({ + getSwatchColorPickerTooltip, grids, + onSetGridOverlayColor, onToggleGridHighlighter, }), GridDisplaySettings({ diff --git a/devtools/client/inspector/layout/components/GridItem.js b/devtools/client/inspector/layout/components/GridItem.js index d57ed09b0693..1dbf62be8ebf 100644 --- a/devtools/client/inspector/layout/components/GridItem.js +++ b/devtools/client/inspector/layout/components/GridItem.js @@ -4,8 +4,8 @@ "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"); @@ -14,6 +14,7 @@ module.exports = createClass({ displayName: "GridItem", propTypes: { + getSwatchColorPickerTooltip: PropTypes.func.isRequired, grid: PropTypes.shape(Types.grid).isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, @@ -21,6 +22,34 @@ module.exports = createClass({ 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, @@ -50,6 +79,7 @@ module.exports = createClass({ return dom.li( { key: grid.id, + className: "grid-item", }, dom.label( {}, @@ -62,6 +92,25 @@ module.exports = createClass({ } ), 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 ) ); }, diff --git a/devtools/client/inspector/layout/components/GridList.js b/devtools/client/inspector/layout/components/GridList.js index 72101dc8ba1a..4898a8358490 100644 --- a/devtools/client/inspector/layout/components/GridList.js +++ b/devtools/client/inspector/layout/components/GridList.js @@ -17,7 +17,9 @@ 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, }, @@ -25,7 +27,9 @@ module.exports = createClass({ render() { let { + getSwatchColorPickerTooltip, grids, + onSetGridOverlayColor, onToggleGridHighlighter, } = this.props; @@ -40,7 +44,9 @@ module.exports = createClass({ dom.ul( {}, grids.map(grid => GridItem({ + getSwatchColorPickerTooltip, grid, + onSetGridOverlayColor, onToggleGridHighlighter, })) ) diff --git a/devtools/client/inspector/layout/layout.js b/devtools/client/inspector/layout/layout.js index fa2e96087c24..8ca1e09dc972 100644 --- a/devtools/client/inspector/layout/layout.js +++ b/devtools/client/inspector/layout/layout.js @@ -13,10 +13,13 @@ 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"); @@ -37,6 +40,18 @@ 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; @@ -74,7 +89,23 @@ LayoutView.prototype = { 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. @@ -89,6 +120,29 @@ LayoutView.prototype = { 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. @@ -180,13 +234,13 @@ LayoutView.prototype = { * 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. * @@ -197,10 +251,11 @@ LayoutView.prototype = { 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); } } @@ -219,14 +274,15 @@ LayoutView.prototype = { 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, { @@ -270,6 +326,43 @@ LayoutView.prototype = { 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. */ @@ -391,8 +484,12 @@ LayoutView.prototype = { 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, diff --git a/devtools/client/inspector/layout/reducers/grids.js b/devtools/client/inspector/layout/reducers/grids.js index 472b21a5fa68..f3843bf5edea 100644 --- a/devtools/client/inspector/layout/reducers/grids.js +++ b/devtools/client/inspector/layout/reducers/grids.js @@ -5,6 +5,7 @@ "use strict"; const { + UPDATE_GRID_COLOR, UPDATE_GRID_HIGHLIGHTED, UPDATE_GRIDS, } = require("../actions/index"); @@ -13,6 +14,18 @@ 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, { diff --git a/devtools/client/inspector/layout/types.js b/devtools/client/inspector/layout/types.js index 478f145b1cae..b9c286200ca0 100644 --- a/devtools/client/inspector/layout/types.js +++ b/devtools/client/inspector/layout/types.js @@ -24,6 +24,9 @@ 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, diff --git a/devtools/client/themes/layout.css b/devtools/client/themes/layout.css index 3d106e01a898..d2848ba14e0d 100644 --- a/devtools/client/themes/layout.css +++ b/devtools/client/themes/layout.css @@ -52,3 +52,29 @@ 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; +}