--- 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;
+}