/* 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, createRef, 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 { connect, } = require("resource://devtools/client/shared/vendor/react-redux.js"); const { getSelectorFromGrip, translateNodeFrontToGrip, } = require("resource://devtools/client/inspector/shared/utils.js"); const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); const Accordion = createFactory( require("resource://devtools/client/shared/components/Accordion.js") ); const BoxModel = createFactory( require("resource://devtools/client/inspector/boxmodel/components/BoxModel.js") ); const Flexbox = createFactory( require("resource://devtools/client/inspector/flexbox/components/Flexbox.js") ); const Grid = createFactory( require("resource://devtools/client/inspector/grids/components/Grid.js") ); const BoxModelTypes = require("resource://devtools/client/inspector/boxmodel/types.js"); const FlexboxTypes = require("resource://devtools/client/inspector/flexbox/types.js"); const GridTypes = require("resource://devtools/client/inspector/grids/types.js"); const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties"; const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI); const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties"; const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI); const FLEXBOX_OPENED_PREF = "devtools.layout.flexbox.opened"; const FLEX_CONTAINER_OPENED_PREF = "devtools.layout.flex-container.opened"; const FLEX_ITEM_OPENED_PREF = "devtools.layout.flex-item.opened"; const GRID_OPENED_PREF = "devtools.layout.grid.opened"; const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened"; class LayoutApp extends PureComponent { static get propTypes() { return { boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired, dispatch: PropTypes.func.isRequired, flexbox: PropTypes.shape(FlexboxTypes.flexbox).isRequired, getSwatchColorPickerTooltip: PropTypes.func.isRequired, grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired, highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings) .isRequired, onSetFlexboxOverlayColor: PropTypes.func.isRequired, onSetGridOverlayColor: PropTypes.func.isRequired, onShowBoxModelEditor: PropTypes.func.isRequired, onShowGridOutlineHighlight: PropTypes.func, onToggleGeometryEditor: PropTypes.func.isRequired, onToggleGridHighlighter: PropTypes.func.isRequired, onToggleShowGridAreas: PropTypes.func.isRequired, onToggleShowGridLineNumbers: PropTypes.func.isRequired, onToggleShowInfiniteLines: PropTypes.func.isRequired, setSelectedNode: PropTypes.func.isRequired, showBoxModelProperties: PropTypes.bool.isRequired, }; } constructor(props) { super(props); this.containerRef = createRef(); this.scrollToTop = this.scrollToTop.bind(this); } getBoxModelSection() { return { component: BoxModel, componentProps: this.props, contentClassName: "layout-content", header: BOXMODEL_L10N.getStr("boxmodel.title"), id: "layout-section-boxmodel", opened: Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF), onToggle: opened => { Services.prefs.setBoolPref(BOXMODEL_OPENED_PREF, opened); }, }; } getFlexAccordionData(flexContainer) { if (!flexContainer.actorID) { // No flex container or flex item selected. return { pref: FLEXBOX_OPENED_PREF, id: "layout-section-flex", header: LAYOUT_L10N.getStr("flexbox.header"), }; } else if (!flexContainer.flexItemShown) { // No flex item selected. return { pref: FLEX_CONTAINER_OPENED_PREF, id: "layout-section-flex-container", header: LAYOUT_L10N.getStr("flexbox.flexContainer"), }; } return { pref: FLEX_ITEM_OPENED_PREF, id: "layout-section-flex-item", header: LAYOUT_L10N.getFormatStr( "flexbox.flexItemOf", getSelectorFromGrip(translateNodeFrontToGrip(flexContainer.nodeFront)) ), }; } getFlexSection(flexContainer) { const { pref, id, header } = this.getFlexAccordionData(flexContainer); return { className: "flex-accordion", component: Flexbox, componentProps: { ...this.props, flexContainer, scrollToTop: this.scrollToTop, }, contentClassName: "layout-content", header, id, opened: Services.prefs.getBoolPref(pref), onToggle: opened => { Services.prefs.setBoolPref(pref, opened); }, }; } getGridSection() { return { component: Grid, componentProps: this.props, contentClassName: "layout-content", header: LAYOUT_L10N.getStr("layout.header"), id: "layout-grid-section", opened: Services.prefs.getBoolPref(GRID_OPENED_PREF), onToggle: opened => { Services.prefs.setBoolPref(GRID_OPENED_PREF, opened); }, }; } /** * Scrolls to the top of the layout container. */ scrollToTop() { this.containerRef.current.scrollTop = 0; } render() { const { flexContainer, flexItemContainer } = this.props.flexbox; const items = [ this.getFlexSection(flexContainer), this.getGridSection(), this.getBoxModelSection(), ]; // If the current selected node is both a flex container and flex item. Render // an extra accordion with another Flexbox component where the node is shown as an // item of its parent flex container. // If the node was selected from the markup-view, then show this accordion after the // container accordion. Otherwise show it first. // The reason is that if the user selects an item-container in the markup view, it // is assumed that they want to primarily see that element as a container, so the // container info should be at the top. if (flexItemContainer?.actorID) { items.splice( this.props.flexbox.initiatedByMarkupViewSelection ? 1 : 0, 0, this.getFlexSection(flexItemContainer) ); } return dom.div( { className: "layout-container", ref: this.containerRef, role: "document", }, Accordion({ items }) ); } } module.exports = connect(state => state)(LayoutApp);