diff --git a/devtools/client/inspector/flexbox/components/FlexContainer.js b/devtools/client/inspector/flexbox/components/FlexContainer.js index a96f9b64c28d..b912dd6bf03a 100644 --- a/devtools/client/inspector/flexbox/components/FlexContainer.js +++ b/devtools/client/inspector/flexbox/components/FlexContainer.js @@ -4,11 +4,15 @@ "use strict"; -const { createRef, PureComponent } = require("devtools/client/shared/vendor/react"); +const { createFactory, createRef, 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 { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils"); +loader.lazyGetter(this, "FlexItemSelector", function() { + return createFactory(require("./FlexItemSelector")); +}); + // Reps const { REPS, MODE } = require("devtools/client/shared/components/reps/reps"); const { Rep } = REPS; @@ -25,6 +29,7 @@ class FlexContainer extends PureComponent { onSetFlexboxOverlayColor: PropTypes.func.isRequired, onShowBoxModelHighlighterForNode: PropTypes.func.isRequired, onToggleFlexboxHighlighter: PropTypes.func.isRequired, + onToggleFlexItemShown: PropTypes.func.isRequired, setSelectedNode: PropTypes.func.isRequired, }; } @@ -96,6 +101,33 @@ class FlexContainer extends PureComponent { nodeFront.scrollIntoView().catch(e => console.error(e)); } + renderFlexItemSelector() { + const { + flexbox, + onToggleFlexItemShown, + } = this.props; + const { + flexItems, + highlighted, + } = flexbox; + + if (!highlighted || !flexItems.length) { + return null; + } + + const selectedFlexItem = flexItems.find(item => item.shown); + + if (!selectedFlexItem) { + return null; + } + + return FlexItemSelector({ + flexItem: selectedFlexItem, + flexItems, + onToggleFlexItemShown, + }); + } + render() { const { flexbox, @@ -110,47 +142,50 @@ class FlexContainer extends PureComponent { return ( dom.div({ className: "flex-container devtools-monospace" }, - dom.label({}, - dom.input( + dom.div({}, + dom.label({}, + dom.input( + { + className: "devtools-checkbox-toggle", + checked: highlighted, + onChange: this.onFlexboxCheckboxClick, + type: "checkbox", + } + ), + Rep( + { + defaultRep: ElementNode, + mode: MODE.TINY, + object: translateNodeFrontToGrip(nodeFront), + onDOMNodeMouseOut: () => onHideBoxModelHighlighter(), + onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront), + onInspectIconClick: () => this.onFlexboxInspectIconClick(nodeFront), + } + ) + ), + dom.div( { - className: "devtools-checkbox-toggle", - checked: highlighted, - onChange: this.onFlexboxCheckboxClick, - type: "checkbox", + className: "layout-color-swatch", + ref: this.swatchEl, + style: { + backgroundColor: color, + }, + title: color, } ), - Rep( + // 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( { - defaultRep: ElementNode, - mode: MODE.TINY, - object: translateNodeFrontToGrip(nodeFront), - onDOMNodeMouseOut: () => onHideBoxModelHighlighter(), - onDOMNodeMouseOver: () => onShowBoxModelHighlighterForNode(nodeFront), - onInspectIconClick: () => this.onFlexboxInspectIconClick(nodeFront), - } + className: "layout-color-value", + ref: this.colorValueEl, + }, + color ) ), - dom.div( - { - className: "layout-color-swatch", - ref: this.swatchEl, - style: { - backgroundColor: color, - }, - title: 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: "layout-color-value", - ref: this.colorValueEl, - }, - color - ) + this.renderFlexItemSelector() ) ); } diff --git a/devtools/client/inspector/flexbox/components/FlexItemSelector.js b/devtools/client/inspector/flexbox/components/FlexItemSelector.js new file mode 100644 index 000000000000..7589bafb4b0f --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexItemSelector.js @@ -0,0 +1,119 @@ +/* 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 { 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 { translateNodeFrontToGrip } = require("devtools/client/inspector/shared/utils"); + +// Reps +const { REPS, MODE } = require("devtools/client/shared/components/reps/reps"); +const { Rep } = REPS; +const ElementNode = REPS.ElementNode; + +const Types = require("../types"); + +loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true); + +class FlexItemSelector extends PureComponent { + static get propTypes() { + return { + flexItem: PropTypes.shape(Types.flexItem).isRequired, + flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired, + onToggleFlexItemShown: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + this.onShowFlexItemMenu = this.onShowFlexItemMenu.bind(this); + } + + onShowFlexItemMenu(event) { + const { + flexItem, + flexItems, + onToggleFlexItemShown, + } = this.props; + const menuItems = []; + + for (const item of flexItems) { + const grip = translateNodeFrontToGrip(item.nodeFront); + menuItems.push({ + label: getLabel(grip), + type: "checkbox", + checked: item === flexItem, + click: () => onToggleFlexItemShown(item.nodeFront), + }); + } + + showMenu(menuItems, { + button: event.target, + }); + } + + render() { + const { flexItem } = this.props; + + return ( + dom.div({ className: "flex-item-selector-wrapper" }, + dom.button( + { + id: "flex-item-selector", + className: "devtools-button devtools-dropdown-button", + onClick: this.onShowFlexItemMenu, + }, + Rep( + { + defaultRep: ElementNode, + mode: MODE.TINY, + object: translateNodeFrontToGrip(flexItem.nodeFront) + } + ) + ) + ) + ); + } +} + +/** + * Returns a selector label of the Element Rep from the grip. This is based on the + * getElements() function in our devtools-reps component for a ElementNode. + * + * @param {Object} grip + * Grip-like object that can be used with Reps. + * @return {String} selector label of the element node. + */ +function getLabel(grip) { + const { + attributes, + nodeName, + isAfterPseudoElement, + isBeforePseudoElement + } = grip.preview; + + if (isAfterPseudoElement || isBeforePseudoElement) { + return `::${isAfterPseudoElement ? "after" : "before"}`; + } + + let label = nodeName; + + if (attributes.id) { + label += `#${attributes.id}`; + } + + if (attributes.class) { + label += attributes.class + .trim() + .split(/\s+/) + .map(cls => `.${cls}`) + .join(""); + } + + return label; +} + +module.exports = FlexItemSelector; diff --git a/devtools/client/inspector/flexbox/components/Flexbox.js b/devtools/client/inspector/flexbox/components/Flexbox.js index 0d921a337d23..b2cbc14838a6 100644 --- a/devtools/client/inspector/flexbox/components/Flexbox.js +++ b/devtools/client/inspector/flexbox/components/Flexbox.js @@ -95,6 +95,7 @@ class Flexbox extends PureComponent { onSetFlexboxOverlayColor, onShowBoxModelHighlighterForNode, onToggleFlexboxHighlighter, + onToggleFlexItemShown, setSelectedNode, } = this.props; @@ -115,6 +116,7 @@ class Flexbox extends PureComponent { onSetFlexboxOverlayColor, onShowBoxModelHighlighterForNode, onToggleFlexboxHighlighter, + onToggleFlexItemShown, setSelectedNode, }), this.renderFlexItemList(), diff --git a/devtools/client/inspector/flexbox/components/moz.build b/devtools/client/inspector/flexbox/components/moz.build index 2a76b1f11aa2..4b3568e9f68b 100644 --- a/devtools/client/inspector/flexbox/components/moz.build +++ b/devtools/client/inspector/flexbox/components/moz.build @@ -10,5 +10,6 @@ DevToolsModules( 'FlexContainerProperties.js', 'FlexItem.js', 'FlexItemList.js', + 'FlexItemSelector.js', 'FlexItemSizingProperties.js', ) diff --git a/devtools/client/themes/layout.css b/devtools/client/themes/layout.css index 6b557a157f79..d6a6ce51828c 100644 --- a/devtools/client/themes/layout.css +++ b/devtools/client/themes/layout.css @@ -2,6 +2,14 @@ * 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/. */ +:root { + --flex-item-selector-wrapper-border-color: var(--theme-content-color3); +} + +:root.theme-dark { + --flex-item-selector-wrapper-border-color: var(--theme-content-color1); +} + #layout-container { height: 100%; width: 100%; @@ -112,6 +120,46 @@ background-color: transparent; } +/** + * Flex Item Selector + */ + +.flex-item-selector-wrapper { + margin-inline-start: 25px; + position: relative; +} + +.flex-item-selector-wrapper::before { + position: absolute; + left: -12px; + top: 3px; + content: ''; + display: block; + border-left: 0.5px solid var(--flex-item-selector-wrapper-border-color); + height: 0.8em; + border-bottom: 0.5px solid var(--flex-item-selector-wrapper-border-color); + width: 10px; +} + + +#flex-item-selector { + background-position: right 4px center; + padding-left: 0; + vertical-align: middle; + width: 140px; +} + +#flex-item-selector .objectBox-node { + display: inline-block; + font-size: 12px; + overflow: hidden; + padding-top: 0.15em; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + width: 85%; +} + /** * Flex Item Sizing Properties */