diff options
Diffstat (limited to 'devtools/client/inspector/flexbox/components')
9 files changed, 1110 insertions, 0 deletions
diff --git a/devtools/client/inspector/flexbox/components/FlexContainer.js b/devtools/client/inspector/flexbox/components/FlexContainer.js new file mode 100644 index 0000000000..94b3ae123d --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexContainer.js @@ -0,0 +1,122 @@ +/* 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 { + createElement, + createRef, + Fragment, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +loader.lazyRequireGetter( + this, + "getNodeRep", + "resource://devtools/client/inspector/shared/node-reps.js" +); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +const { + highlightNode, + unhighlightNode, +} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js"); + +class FlexContainer extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + color: PropTypes.string.isRequired, + flexContainer: PropTypes.shape(Types.flexContainer).isRequired, + getSwatchColorPickerTooltip: PropTypes.func.isRequired, + onSetFlexboxOverlayColor: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + + this.swatchEl = createRef(); + + this.setFlexboxColor = this.setFlexboxColor.bind(this); + } + + componentDidMount() { + const tooltip = this.props.getSwatchColorPickerTooltip(); + + let previousColor; + tooltip.addSwatch(this.swatchEl.current, { + onCommit: this.setFlexboxColor, + onPreview: this.setFlexboxColor, + onRevert: () => { + this.props.onSetFlexboxOverlayColor(previousColor); + }, + onShow: () => { + previousColor = this.props.color; + }, + }); + } + + componentWillUnmount() { + const tooltip = this.props.getSwatchColorPickerTooltip(); + tooltip.removeSwatch(this.swatchEl.current); + } + + setFlexboxColor() { + const color = this.swatchEl.current.dataset.color; + this.props.onSetFlexboxOverlayColor(color); + } + + render() { + const { color, flexContainer, dispatch } = this.props; + const { nodeFront, properties } = flexContainer; + + return createElement( + Fragment, + null, + dom.div( + { + className: "flex-header-container-label", + }, + getNodeRep(nodeFront, { + onDOMNodeMouseOut: () => dispatch(unhighlightNode()), + onDOMNodeMouseOver: () => dispatch(highlightNode(nodeFront)), + }), + dom.div({ + className: "layout-color-swatch", + "data-color": color, + ref: this.swatchEl, + style: { + backgroundColor: color, + }, + title: color, + }) + ), + dom.div( + { className: "flex-header-container-properties" }, + dom.div( + { + className: "inspector-badge", + role: "figure", + title: `flex-direction: ${properties["flex-direction"]}`, + }, + properties["flex-direction"] + ), + dom.div( + { + className: "inspector-badge", + role: "figure", + title: `flex-wrap: ${properties["flex-wrap"]}`, + }, + properties["flex-wrap"] + ) + ) + ); + } +} + +module.exports = FlexContainer; diff --git a/devtools/client/inspector/flexbox/components/FlexItem.js b/devtools/client/inspector/flexbox/components/FlexItem.js new file mode 100644 index 0000000000..b29da8243e --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexItem.js @@ -0,0 +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 { + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +loader.lazyRequireGetter( + this, + "getNodeRep", + "resource://devtools/client/inspector/shared/node-reps.js" +); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +const { + highlightNode, + unhighlightNode, +} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js"); + +class FlexItem extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + flexItem: PropTypes.shape(Types.flexItem).isRequired, + index: PropTypes.number.isRequired, + scrollToTop: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { dispatch, flexItem, index, scrollToTop, setSelectedNode } = + this.props; + const { nodeFront } = flexItem; + + return dom.button( + { + className: "devtools-button devtools-monospace", + onClick: e => { + e.stopPropagation(); + scrollToTop(); + setSelectedNode(nodeFront); + dispatch(unhighlightNode()); + }, + onMouseOut: () => dispatch(unhighlightNode()), + onMouseOver: () => dispatch(highlightNode(nodeFront)), + }, + dom.span({ className: "flex-item-index" }, index), + getNodeRep(nodeFront) + ); + } +} + +module.exports = FlexItem; diff --git a/devtools/client/inspector/flexbox/components/FlexItemList.js b/devtools/client/inspector/flexbox/components/FlexItemList.js new file mode 100644 index 0000000000..0824ee1175 --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexItemList.js @@ -0,0 +1,62 @@ +/* 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 { + createFactory, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + getStr, +} = require("resource://devtools/client/inspector/layout/utils/l10n.js"); + +const FlexItem = createFactory( + require("resource://devtools/client/inspector/flexbox/components/FlexItem.js") +); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +class FlexItemList extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired, + scrollToTop: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { dispatch, flexItems, scrollToTop, setSelectedNode } = this.props; + + return dom.div( + { className: "flex-item-list" }, + dom.div( + { + className: "flex-item-list-header", + role: "heading", + "aria-level": "3", + }, + !flexItems.length + ? getStr("flexbox.noFlexItems") + : getStr("flexbox.flexItems") + ), + flexItems.map((flexItem, index) => + FlexItem({ + key: flexItem.actorID, + dispatch, + flexItem, + index: index + 1, + scrollToTop, + setSelectedNode, + }) + ) + ); + } +} + +module.exports = FlexItemList; diff --git a/devtools/client/inspector/flexbox/components/FlexItemSelector.js b/devtools/client/inspector/flexbox/components/FlexItemSelector.js new file mode 100644 index 0000000000..d8dd2db7a3 --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexItemSelector.js @@ -0,0 +1,81 @@ +/* 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("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + getSelectorFromGrip, + translateNodeFrontToGrip, +} = require("resource://devtools/client/inspector/shared/utils.js"); + +loader.lazyRequireGetter( + this, + "getNodeRep", + "resource://devtools/client/inspector/shared/node-reps.js" +); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +loader.lazyRequireGetter( + this, + "showMenu", + "resource://devtools/client/shared/components/menu/utils.js", + true +); + +class FlexItemSelector extends PureComponent { + static get propTypes() { + return { + flexItem: PropTypes.shape(Types.flexItem).isRequired, + flexItems: PropTypes.arrayOf(PropTypes.shape(Types.flexItem)).isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + this.onShowFlexItemMenu = this.onShowFlexItemMenu.bind(this); + } + + onShowFlexItemMenu(event) { + event.stopPropagation(); + + const { flexItem, flexItems, setSelectedNode } = this.props; + const menuItems = []; + + for (const item of flexItems) { + const grip = translateNodeFrontToGrip(item.nodeFront); + menuItems.push({ + label: getSelectorFromGrip(grip), + type: "checkbox", + checked: item === flexItem, + click: () => setSelectedNode(item.nodeFront), + }); + } + + showMenu(menuItems, { + button: event.target, + }); + } + + render() { + const { flexItem } = this.props; + + return dom.button( + { + id: "flex-item-selector", + className: "devtools-button devtools-dropdown-button", + onClick: this.onShowFlexItemMenu, + }, + getNodeRep(flexItem.nodeFront) + ); + } +} + +module.exports = FlexItemSelector; diff --git a/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js new file mode 100644 index 0000000000..f0c35cd6be --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js @@ -0,0 +1,173 @@ +/* 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("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +class FlexItemSizingOutline extends PureComponent { + static get propTypes() { + return { + flexDirection: PropTypes.string.isRequired, + flexItem: PropTypes.shape(Types.flexItem).isRequired, + }; + } + + renderBasisOutline(mainBaseSize) { + return dom.div({ + className: "flex-outline-basis" + (!mainBaseSize ? " zero-basis" : ""), + }); + } + + renderDeltaOutline(mainDeltaSize) { + if (!mainDeltaSize) { + return null; + } + + return dom.div({ + className: "flex-outline-delta", + }); + } + + renderFinalOutline(isClamped) { + return dom.div({ + className: "flex-outline-final" + (isClamped ? " clamped" : ""), + }); + } + + renderPoint(className, label = className) { + return dom.div({ + key: className, + className: `flex-outline-point ${className}`, + "data-label": label, + }); + } + + render() { + const { flexItemSizing } = this.props.flexItem; + const { + mainAxisDirection, + mainBaseSize, + mainDeltaSize, + mainMaxSize, + mainMinSize, + clampState, + } = flexItemSizing; + + // Calculate the final size. This is base + delta, then clamped by min or max. + let mainFinalSize = mainBaseSize + mainDeltaSize; + mainFinalSize = Math.max(mainFinalSize, mainMinSize); + mainFinalSize = + mainMaxSize === null + ? mainFinalSize + : Math.min(mainFinalSize, mainMaxSize); + + // Just don't display anything if there isn't anything useful. + if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) { + return null; + } + + // The max size is only interesting to show if it did clamp the item. + const showMax = clampState === "clamped_to_max"; + + // The min size is only really interesting if it actually clamped the item. + // TODO: We might also want to check if the min-size property is defined. + const showMin = clampState === "clamped_to_min"; + + // Create an array of all of the sizes we want to display that we can use to create + // a grid track template. + let sizes = [ + { name: "basis-start", size: 0 }, + { name: "basis-end", size: mainBaseSize }, + { name: "final-start", size: 0 }, + { name: "final-end", size: mainFinalSize }, + ]; + + // Because mainDeltaSize is just a delta from base, make sure to make it absolute like + // the other sizes in the array, so we can later sort all sizes in the same way. + if (mainDeltaSize > 0) { + sizes.push({ name: "delta-start", size: mainBaseSize }); + sizes.push({ name: "delta-end", size: mainBaseSize + mainDeltaSize }); + } else { + sizes.push({ name: "delta-start", size: mainBaseSize + mainDeltaSize }); + sizes.push({ name: "delta-end", size: mainBaseSize }); + } + + if (showMax) { + sizes.push({ name: "max", size: mainMaxSize }); + } + if (showMin) { + sizes.push({ name: "min", size: mainMinSize }); + } + + // Sort all of the dimensions so we can create the grid track template correctly. + sizes = sizes.sort((a, b) => a.size - b.size); + + // In some cases, the delta-start may be negative (when an item wanted to shrink more + // than the item's base size). As a negative value would break the grid track template + // offset all values so they're all positive. + const offsetBy = sizes.reduce( + (acc, curr) => (curr.size < acc ? curr.size : acc), + 0 + ); + sizes = sizes.map(entry => ({ + size: entry.size - offsetBy, + name: entry.name, + })); + + let gridTemplateColumns = "["; + let accumulatedSize = 0; + for (const { name, size } of sizes) { + const breadth = Math.round(size - accumulatedSize); + if (breadth === 0) { + gridTemplateColumns += ` ${name}`; + continue; + } + + gridTemplateColumns += `] ${breadth}fr [${name}`; + accumulatedSize = size; + } + gridTemplateColumns += "]"; + + // Check the final and basis points to see if they are the same and if so, combine + // them into a single rendered point. + const renderedBaseAndFinalPoints = []; + if (mainFinalSize === mainBaseSize) { + renderedBaseAndFinalPoints.push( + this.renderPoint("basisfinal", "basis/final") + ); + } else { + renderedBaseAndFinalPoints.push(this.renderPoint("basis")); + renderedBaseAndFinalPoints.push(this.renderPoint("final")); + } + + return dom.div( + { className: "flex-outline-container" }, + dom.div( + { + className: + `flex-outline ${mainAxisDirection}` + + (mainDeltaSize > 0 ? " growing" : " shrinking"), + style: { + gridTemplateColumns, + }, + }, + renderedBaseAndFinalPoints, + showMin ? this.renderPoint("min") : null, + showMax ? this.renderPoint("max") : null, + this.renderBasisOutline(mainBaseSize), + this.renderDeltaOutline(mainDeltaSize), + this.renderFinalOutline(clampState !== "unclamped") + ) + ); + } +} + +module.exports = FlexItemSizingOutline; diff --git a/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js new file mode 100644 index 0000000000..00bea31e57 --- /dev/null +++ b/devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js @@ -0,0 +1,326 @@ +/* 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("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + getStr, +} = require("resource://devtools/client/inspector/layout/utils/l10n.js"); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +const getFlexibilityReasons = ({ + lineGrowthState, + computedFlexGrow, + computedFlexShrink, + grew, + shrank, +}) => { + const reasons = []; + + // Tell users whether the item was set to grow or shrink. + if (computedFlexGrow && lineGrowthState === "growing") { + reasons.push(getStr("flexbox.itemSizing.setToGrow")); + } + if (computedFlexShrink && lineGrowthState === "shrinking") { + reasons.push(getStr("flexbox.itemSizing.setToShrink")); + } + if (!computedFlexGrow && !grew && !shrank && lineGrowthState === "growing") { + reasons.push(getStr("flexbox.itemSizing.notSetToGrow")); + } + if ( + !computedFlexShrink && + !grew && + !shrank && + lineGrowthState === "shrinking" + ) { + reasons.push(getStr("flexbox.itemSizing.notSetToShrink")); + } + + return reasons; +}; + +class FlexItemSizingProperties extends PureComponent { + static get propTypes() { + return { + flexDirection: PropTypes.string.isRequired, + flexItem: PropTypes.shape(Types.flexItem).isRequired, + }; + } + + /** + * Rounds some size in pixels and render it. + * The rendered value will end with 'px' (unless the dimension is 0 in which case the + * unit will be omitted) + * + * @param {Number} value + * The number to be rounded + * @param {Boolean} prependPlusSign + * If set to true, the + sign will be printed before a positive value + * @return {Object} + * The React component representing this rounded size + */ + renderSize(value, prependPlusSign) { + if (value == 0) { + return dom.span({ className: "value" }, "0"); + } + + value = Math.round(value * 100) / 100; + if (prependPlusSign && value > 0) { + value = "+" + value; + } + + return dom.span( + { className: "value" }, + value, + dom.span({ className: "unit" }, "px") + ); + } + + /** + * Render an authored CSS property. + * + * @param {String} name + * The name for this CSS property + * @param {String} value + * The property value + * @param {Booleam} isDefaultValue + * Whether the value come from the browser default style + * @return {Object} + * The React component representing this CSS property + */ + renderCssProperty(name, value, isDefaultValue) { + return dom.span({ className: "css-property-link" }, `(${name}: ${value})`); + } + + /** + * Render a list of sentences to be displayed in the UI as reasons why a certain sizing + * value happened. + * + * @param {Array} sentences + * The list of sentences as Strings + * @return {Object} + * The React component representing these sentences + */ + renderReasons(sentences) { + return dom.ul( + { className: "reasons" }, + sentences.map(sentence => dom.li({}, sentence)) + ); + } + + renderBaseSizeSection({ mainBaseSize, clampState }, properties, dimension) { + const flexBasisValue = properties["flex-basis"]; + const dimensionValue = properties[dimension]; + + let title = getStr("flexbox.itemSizing.baseSizeSectionHeader"); + let property = null; + + if (flexBasisValue) { + // If flex-basis is defined, then that's what is used for the base size. + property = this.renderCssProperty("flex-basis", flexBasisValue); + } else if (dimensionValue) { + // If not and width/height is defined, then that's what defines the base size. + property = this.renderCssProperty(dimension, dimensionValue); + } else { + // Finally, if nothing is set, then the base size is the max-content size. + // In this case replace the section's title. + title = getStr("flexbox.itemSizing.itemContentSize"); + } + + const className = "section base"; + return dom.li( + { className: className + (property ? "" : " no-property") }, + dom.span({ className: "name" }, title, property), + this.renderSize(mainBaseSize) + ); + } + + renderFlexibilitySection( + flexItemSizing, + mainFinalSize, + properties, + computedStyle + ) { + const { mainDeltaSize, mainBaseSize, lineGrowthState } = flexItemSizing; + + // Don't display anything if all interesting sizes are 0. + if (!mainFinalSize && !mainBaseSize && !mainDeltaSize) { + return null; + } + + // Also don't display anything if the item did not grow or shrink. + const grew = mainDeltaSize > 0; + const shrank = mainDeltaSize < 0; + if (!grew && !shrank) { + return null; + } + + const definedFlexGrow = properties["flex-grow"]; + const computedFlexGrow = computedStyle.flexGrow; + const definedFlexShrink = properties["flex-shrink"]; + const computedFlexShrink = computedStyle.flexShrink; + + const reasons = getFlexibilityReasons({ + lineGrowthState, + computedFlexGrow, + computedFlexShrink, + grew, + shrank, + }); + + let property = null; + + if (grew && definedFlexGrow && computedFlexGrow) { + // If the item grew it's normally because it was set to grow (flex-grow is non 0). + property = this.renderCssProperty("flex-grow", definedFlexGrow); + } else if (shrank && definedFlexShrink && computedFlexShrink) { + // If the item shrank it's either because flex-shrink is non 0. + property = this.renderCssProperty("flex-shrink", definedFlexShrink); + } else if (shrank && computedFlexShrink) { + // Or also because it's default value is 1 anyway. + property = this.renderCssProperty( + "flex-shrink", + computedFlexShrink, + true + ); + } + + // Don't display the section at all if there's nothing useful to show users. + if (!property && !reasons.length) { + return null; + } + + const className = "section flexibility"; + return dom.li( + { className: className + (property ? "" : " no-property") }, + dom.span( + { className: "name" }, + getStr("flexbox.itemSizing.flexibilitySectionHeader"), + property + ), + this.renderSize(mainDeltaSize, true), + this.renderReasons(reasons) + ); + } + + renderMinimumSizeSection(flexItemSizing, properties, dimension) { + const { clampState, mainMinSize, mainDeltaSize } = flexItemSizing; + const grew = mainDeltaSize > 0; + const shrank = mainDeltaSize < 0; + const minDimensionValue = properties[`min-${dimension}`]; + + // We only display the minimum size when the item actually violates that size during + // layout & is clamped. + if (clampState !== "clamped_to_min") { + return null; + } + + const reasons = []; + if (grew || shrank) { + // The item may have wanted to grow less, but was min-clamped to a larger size. + // Or the item may have wanted to shrink more but was min-clamped to a larger size. + reasons.push(getStr("flexbox.itemSizing.clampedToMin")); + } + + return dom.li( + { className: "section min" }, + dom.span( + { className: "name" }, + getStr("flexbox.itemSizing.minSizeSectionHeader"), + minDimensionValue.length + ? this.renderCssProperty(`min-${dimension}`, minDimensionValue) + : null + ), + this.renderSize(mainMinSize), + this.renderReasons(reasons) + ); + } + + renderMaximumSizeSection(flexItemSizing, properties, dimension) { + const { clampState, mainMaxSize, mainDeltaSize } = flexItemSizing; + const grew = mainDeltaSize > 0; + const shrank = mainDeltaSize < 0; + const maxDimensionValue = properties[`max-${dimension}`]; + + if (clampState !== "clamped_to_max") { + return null; + } + + const reasons = []; + if (grew || shrank) { + // The item may have wanted to grow more than it did, because it was max-clamped. + // Or the item may have wanted shrink more, but it was clamped to its max size. + reasons.push(getStr("flexbox.itemSizing.clampedToMax")); + } + + return dom.li( + { className: "section max" }, + dom.span( + { className: "name" }, + getStr("flexbox.itemSizing.maxSizeSectionHeader"), + maxDimensionValue.length + ? this.renderCssProperty(`max-${dimension}`, maxDimensionValue) + : null + ), + this.renderSize(mainMaxSize), + this.renderReasons(reasons) + ); + } + + renderFinalSizeSection(mainFinalSize) { + return dom.li( + { className: "section final no-property" }, + dom.span( + { className: "name" }, + getStr("flexbox.itemSizing.finalSizeSectionHeader") + ), + this.renderSize(mainFinalSize) + ); + } + + render() { + const { flexItem } = this.props; + const { computedStyle, flexItemSizing, properties } = flexItem; + const { + mainAxisDirection, + mainBaseSize, + mainDeltaSize, + mainMaxSize, + mainMinSize, + } = flexItemSizing; + const dimension = mainAxisDirection.startsWith("horizontal") + ? "width" + : "height"; + + // Calculate the final size. This is base + delta, then clamped by min or max. + let mainFinalSize = mainBaseSize + mainDeltaSize; + mainFinalSize = Math.max(mainFinalSize, mainMinSize); + mainFinalSize = + mainMaxSize === null + ? mainFinalSize + : Math.min(mainFinalSize, mainMaxSize); + + return dom.ul( + { className: "flex-item-sizing" }, + this.renderBaseSizeSection(flexItemSizing, properties, dimension), + this.renderFlexibilitySection( + flexItemSizing, + mainFinalSize, + properties, + computedStyle + ), + this.renderMinimumSizeSection(flexItemSizing, properties, dimension), + this.renderMaximumSizeSection(flexItemSizing, properties, dimension), + this.renderFinalSizeSection(mainFinalSize) + ); + } +} + +module.exports = FlexItemSizingProperties; diff --git a/devtools/client/inspector/flexbox/components/Flexbox.js b/devtools/client/inspector/flexbox/components/Flexbox.js new file mode 100644 index 0000000000..a0d79d92eb --- /dev/null +++ b/devtools/client/inspector/flexbox/components/Flexbox.js @@ -0,0 +1,128 @@ +/* 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 { + createElement, + createFactory, + Fragment, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + getStr, +} = require("resource://devtools/client/inspector/layout/utils/l10n.js"); + +loader.lazyGetter(this, "FlexItemList", function () { + return createFactory( + require("resource://devtools/client/inspector/flexbox/components/FlexItemList.js") + ); +}); +loader.lazyGetter(this, "FlexItemSizingOutline", function () { + return createFactory( + require("resource://devtools/client/inspector/flexbox/components/FlexItemSizingOutline.js") + ); +}); +loader.lazyGetter(this, "FlexItemSizingProperties", function () { + return createFactory( + require("resource://devtools/client/inspector/flexbox/components/FlexItemSizingProperties.js") + ); +}); +loader.lazyGetter(this, "Header", function () { + return createFactory( + require("resource://devtools/client/inspector/flexbox/components/Header.js") + ); +}); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +class Flexbox extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + flexbox: PropTypes.shape(Types.flexbox).isRequired, + flexContainer: PropTypes.shape(Types.flexContainer).isRequired, + getSwatchColorPickerTooltip: PropTypes.func.isRequired, + onSetFlexboxOverlayColor: PropTypes.func.isRequired, + scrollToTop: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + renderFlexItemList() { + const { dispatch, scrollToTop, setSelectedNode } = this.props; + const { flexItems } = this.props.flexContainer; + + return FlexItemList({ + dispatch, + flexItems, + scrollToTop, + setSelectedNode, + }); + } + + renderFlexItemSizing() { + const { flexItems, flexItemShown, properties } = this.props.flexContainer; + + const flexItem = flexItems.find( + item => item.nodeFront.actorID === flexItemShown + ); + if (!flexItem) { + return null; + } + + return createElement( + Fragment, + null, + FlexItemSizingOutline({ + flexDirection: properties["flex-direction"], + flexItem, + }), + FlexItemSizingProperties({ + flexDirection: properties["flex-direction"], + flexItem, + }) + ); + } + + render() { + const { + dispatch, + flexbox, + flexContainer, + getSwatchColorPickerTooltip, + onSetFlexboxOverlayColor, + setSelectedNode, + } = this.props; + + if (!flexContainer.actorID) { + return dom.div( + { className: "devtools-sidepanel-no-result" }, + getStr("flexbox.noFlexboxeOnThisPage") + ); + } + + const { flexItemShown } = flexContainer; + const { color, highlighted } = flexbox; + + return dom.div( + { className: "layout-flexbox-wrapper" }, + Header({ + color, + dispatch, + flexContainer, + getSwatchColorPickerTooltip, + highlighted, + onSetFlexboxOverlayColor, + setSelectedNode, + }), + !flexItemShown ? this.renderFlexItemList() : null, + flexItemShown ? this.renderFlexItemSizing() : null + ); + } +} + +module.exports = Flexbox; diff --git a/devtools/client/inspector/flexbox/components/Header.js b/devtools/client/inspector/flexbox/components/Header.js new file mode 100644 index 0000000000..44b19703e3 --- /dev/null +++ b/devtools/client/inspector/flexbox/components/Header.js @@ -0,0 +1,142 @@ +/* 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 { + createElement, + createFactory, + Fragment, + PureComponent, +} = require("resource://devtools/client/shared/vendor/react.js"); +const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); +const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js"); +const { + getStr, +} = require("resource://devtools/client/inspector/layout/utils/l10n.js"); + +const FlexContainer = createFactory( + require("resource://devtools/client/inspector/flexbox/components/FlexContainer.js") +); +const FlexItemSelector = createFactory( + require("resource://devtools/client/inspector/flexbox/components/FlexItemSelector.js") +); + +const Types = require("resource://devtools/client/inspector/flexbox/types.js"); + +const { + toggleFlexboxHighlighter, +} = require("resource://devtools/client/inspector/flexbox/actions/flexbox-highlighter.js"); + +class Header extends PureComponent { + static get propTypes() { + return { + color: PropTypes.string.isRequired, + dispatch: PropTypes.func.isRequired, + flexContainer: PropTypes.shape(Types.flexContainer).isRequired, + getSwatchColorPickerTooltip: PropTypes.func.isRequired, + highlighted: PropTypes.bool.isRequired, + onSetFlexboxOverlayColor: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + renderFlexboxHighlighterToggle() { + const { dispatch, flexContainer, highlighted } = this.props; + // Don't show the flexbox highlighter toggle for the parent flex container of the + // selected element. + if (flexContainer.isFlexItemContainer) { + return null; + } + + return createElement( + Fragment, + null, + dom.div({ className: "devtools-separator" }), + dom.input({ + id: "flexbox-checkbox-toggle", + className: "devtools-checkbox-toggle", + checked: highlighted, + onChange: () => + dispatch(toggleFlexboxHighlighter(flexContainer.nodeFront, "layout")), + title: getStr("flexbox.togglesFlexboxHighlighter2"), + type: "checkbox", + }) + ); + } + + renderFlexContainer() { + if (this.props.flexContainer.flexItemShown) { + return null; + } + + const { + color, + dispatch, + flexContainer, + getSwatchColorPickerTooltip, + onSetFlexboxOverlayColor, + } = this.props; + + return FlexContainer({ + color, + dispatch, + flexContainer, + getSwatchColorPickerTooltip, + onSetFlexboxOverlayColor, + }); + } + + renderFlexItemSelector() { + if (!this.props.flexContainer.flexItemShown) { + return null; + } + + const { flexContainer, setSelectedNode } = this.props; + const { flexItems, flexItemShown } = flexContainer; + const flexItem = flexItems.find( + item => item.nodeFront.actorID === flexItemShown + ); + + if (!flexItem) { + return null; + } + + return FlexItemSelector({ + flexItem, + flexItems, + setSelectedNode, + }); + } + + render() { + const { flexContainer, setSelectedNode } = this.props; + const { flexItemShown, nodeFront } = flexContainer; + + return dom.div( + { className: "flex-header devtools-monospace" }, + flexItemShown + ? dom.button({ + className: "flex-header-button-prev devtools-button", + "aria-label": getStr("flexbox.backButtonLabel"), + onClick: e => { + e.stopPropagation(); + setSelectedNode(nodeFront); + }, + }) + : null, + dom.div( + { + className: + "flex-header-content" + (flexItemShown ? " flex-item-shown" : ""), + }, + this.renderFlexContainer(), + this.renderFlexItemSelector() + ), + this.renderFlexboxHighlighterToggle() + ); + } +} + +module.exports = Header; diff --git a/devtools/client/inspector/flexbox/components/moz.build b/devtools/client/inspector/flexbox/components/moz.build new file mode 100644 index 0000000000..3e077a217f --- /dev/null +++ b/devtools/client/inspector/flexbox/components/moz.build @@ -0,0 +1,16 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DevToolsModules( + "Flexbox.js", + "FlexContainer.js", + "FlexItem.js", + "FlexItemList.js", + "FlexItemSelector.js", + "FlexItemSizingOutline.js", + "FlexItemSizingProperties.js", + "Header.js", +) |