LayoutApp.js (6831B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 "use strict"; 6 7 const { 8 createFactory, 9 createRef, 10 PureComponent, 11 } = require("resource://devtools/client/shared/vendor/react.mjs"); 12 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 13 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 14 const { 15 connect, 16 } = require("resource://devtools/client/shared/vendor/react-redux.js"); 17 const { 18 getSelectorFromGrip, 19 translateNodeFrontToGrip, 20 } = require("resource://devtools/client/inspector/shared/utils.js"); 21 const { LocalizationHelper } = require("resource://devtools/shared/l10n.js"); 22 23 const Accordion = createFactory( 24 require("resource://devtools/client/shared/components/Accordion.js") 25 ); 26 const BoxModel = createFactory( 27 require("resource://devtools/client/inspector/boxmodel/components/BoxModel.js") 28 ); 29 const Flexbox = createFactory( 30 require("resource://devtools/client/inspector/flexbox/components/Flexbox.js") 31 ); 32 const Grid = createFactory( 33 require("resource://devtools/client/inspector/grids/components/Grid.js") 34 ); 35 36 const BoxModelTypes = require("resource://devtools/client/inspector/boxmodel/types.js"); 37 const FlexboxTypes = require("resource://devtools/client/inspector/flexbox/types.js"); 38 const GridTypes = require("resource://devtools/client/inspector/grids/types.js"); 39 40 const BOXMODEL_STRINGS_URI = "devtools/client/locales/boxmodel.properties"; 41 const BOXMODEL_L10N = new LocalizationHelper(BOXMODEL_STRINGS_URI); 42 43 const LAYOUT_STRINGS_URI = "devtools/client/locales/layout.properties"; 44 const LAYOUT_L10N = new LocalizationHelper(LAYOUT_STRINGS_URI); 45 46 const FLEXBOX_OPENED_PREF = "devtools.layout.flexbox.opened"; 47 const FLEX_CONTAINER_OPENED_PREF = "devtools.layout.flex-container.opened"; 48 const FLEX_ITEM_OPENED_PREF = "devtools.layout.flex-item.opened"; 49 const GRID_OPENED_PREF = "devtools.layout.grid.opened"; 50 const BOXMODEL_OPENED_PREF = "devtools.layout.boxmodel.opened"; 51 52 class LayoutApp extends PureComponent { 53 static get propTypes() { 54 return { 55 boxModel: PropTypes.shape(BoxModelTypes.boxModel).isRequired, 56 dispatch: PropTypes.func.isRequired, 57 flexbox: PropTypes.shape(FlexboxTypes.flexbox).isRequired, 58 getSwatchColorPickerTooltip: PropTypes.func.isRequired, 59 grids: PropTypes.arrayOf(PropTypes.shape(GridTypes.grid)).isRequired, 60 highlighterSettings: PropTypes.shape(GridTypes.highlighterSettings) 61 .isRequired, 62 onSetFlexboxOverlayColor: PropTypes.func.isRequired, 63 onSetGridOverlayColor: PropTypes.func.isRequired, 64 onShowBoxModelEditor: PropTypes.func.isRequired, 65 onShowGridOutlineHighlight: PropTypes.func, 66 onToggleGeometryEditor: PropTypes.func.isRequired, 67 onToggleGridHighlighter: PropTypes.func.isRequired, 68 onToggleShowGridAreas: PropTypes.func.isRequired, 69 onToggleShowGridLineNumbers: PropTypes.func.isRequired, 70 onToggleShowInfiniteLines: PropTypes.func.isRequired, 71 setSelectedNode: PropTypes.func.isRequired, 72 showBoxModelProperties: PropTypes.bool.isRequired, 73 }; 74 } 75 76 constructor(props) { 77 super(props); 78 this.containerRef = createRef(); 79 80 this.scrollToTop = this.scrollToTop.bind(this); 81 } 82 83 getBoxModelSection() { 84 return { 85 component: BoxModel, 86 componentProps: this.props, 87 contentClassName: "layout-content", 88 header: BOXMODEL_L10N.getStr("boxmodel.title"), 89 id: "layout-section-boxmodel", 90 opened: Services.prefs.getBoolPref(BOXMODEL_OPENED_PREF), 91 onToggle: opened => { 92 Services.prefs.setBoolPref(BOXMODEL_OPENED_PREF, opened); 93 }, 94 }; 95 } 96 97 getFlexAccordionData(flexContainer) { 98 if (!flexContainer.actorID) { 99 // No flex container or flex item selected. 100 return { 101 pref: FLEXBOX_OPENED_PREF, 102 id: "layout-section-flex", 103 header: LAYOUT_L10N.getStr("flexbox.header"), 104 }; 105 } else if (!flexContainer.flexItemShown) { 106 // No flex item selected. 107 return { 108 pref: FLEX_CONTAINER_OPENED_PREF, 109 id: "layout-section-flex-container", 110 header: LAYOUT_L10N.getStr("flexbox.flexContainer"), 111 }; 112 } 113 114 return { 115 pref: FLEX_ITEM_OPENED_PREF, 116 id: "layout-section-flex-item", 117 header: LAYOUT_L10N.getFormatStr( 118 "flexbox.flexItemOf", 119 getSelectorFromGrip(translateNodeFrontToGrip(flexContainer.nodeFront)) 120 ), 121 }; 122 } 123 124 getFlexSection(flexContainer) { 125 const { pref, id, header } = this.getFlexAccordionData(flexContainer); 126 127 return { 128 className: "flex-accordion", 129 component: Flexbox, 130 componentProps: { 131 ...this.props, 132 flexContainer, 133 scrollToTop: this.scrollToTop, 134 }, 135 contentClassName: "layout-content", 136 header, 137 id, 138 opened: Services.prefs.getBoolPref(pref), 139 onToggle: opened => { 140 Services.prefs.setBoolPref(pref, opened); 141 }, 142 }; 143 } 144 145 getGridSection() { 146 return { 147 component: Grid, 148 componentProps: this.props, 149 contentClassName: "layout-content", 150 header: LAYOUT_L10N.getStr("layout.header"), 151 id: "layout-grid-section", 152 opened: Services.prefs.getBoolPref(GRID_OPENED_PREF), 153 onToggle: opened => { 154 Services.prefs.setBoolPref(GRID_OPENED_PREF, opened); 155 }, 156 }; 157 } 158 159 /** 160 * Scrolls to the top of the layout container. 161 */ 162 scrollToTop() { 163 this.containerRef.current.scrollTop = 0; 164 } 165 166 render() { 167 const { flexContainer, flexItemContainer } = this.props.flexbox; 168 169 const items = [ 170 this.getFlexSection(flexContainer), 171 this.getGridSection(), 172 this.getBoxModelSection(), 173 ]; 174 175 // If the current selected node is both a flex container and flex item. Render 176 // an extra accordion with another Flexbox component where the node is shown as an 177 // item of its parent flex container. 178 // If the node was selected from the markup-view, then show this accordion after the 179 // container accordion. Otherwise show it first. 180 // The reason is that if the user selects an item-container in the markup view, it 181 // is assumed that they want to primarily see that element as a container, so the 182 // container info should be at the top. 183 if (flexItemContainer?.actorID) { 184 items.splice( 185 this.props.flexbox.initiatedByMarkupViewSelection ? 1 : 0, 186 0, 187 this.getFlexSection(flexItemContainer) 188 ); 189 } 190 191 return dom.div( 192 { 193 className: "layout-container", 194 ref: this.containerRef, 195 role: "document", 196 }, 197 Accordion({ items }) 198 ); 199 } 200 } 201 202 module.exports = connect(state => state)(LayoutApp);