diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/inspector/grids/components | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
6 files changed, 923 insertions, 0 deletions
diff --git a/devtools/client/inspector/grids/components/Grid.js b/devtools/client/inspector/grids/components/Grid.js new file mode 100644 index 0000000000..0378f1e702 --- /dev/null +++ b/devtools/client/inspector/grids/components/Grid.js @@ -0,0 +1,106 @@ +/* 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"); + +// Normally, we would only lazy load GridOutline, but we also lazy load +// GridDisplaySettings and GridList because we assume the CSS grid usage is low +// and usually will not appear on the page. +loader.lazyGetter(this, "GridDisplaySettings", function () { + return createFactory( + require("resource://devtools/client/inspector/grids/components/GridDisplaySettings.js") + ); +}); +loader.lazyGetter(this, "GridList", function () { + return createFactory( + require("resource://devtools/client/inspector/grids/components/GridList.js") + ); +}); +loader.lazyGetter(this, "GridOutline", function () { + return createFactory( + require("resource://devtools/client/inspector/grids/components/GridOutline.js") + ); +}); + +const Types = require("resource://devtools/client/inspector/grids/types.js"); + +class Grid extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + 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, + onToggleShowGridAreas: PropTypes.func.isRequired, + onToggleShowGridLineNumbers: PropTypes.func.isRequired, + onToggleShowInfiniteLines: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + if (!this.props.grids.length) { + return dom.div( + { className: "devtools-sidepanel-no-result" }, + getStr("layout.noGridsOnThisPage") + ); + } + + const { + dispatch, + getSwatchColorPickerTooltip, + grids, + highlighterSettings, + onSetGridOverlayColor, + onToggleShowGridAreas, + onToggleGridHighlighter, + onToggleShowGridLineNumbers, + onToggleShowInfiniteLines, + setSelectedNode, + } = this.props; + const highlightedGrids = grids.filter(grid => grid.highlighted); + + return dom.div( + { id: "layout-grid-container" }, + dom.div( + { className: "grid-content" }, + GridList({ + dispatch, + getSwatchColorPickerTooltip, + grids, + onSetGridOverlayColor, + onToggleGridHighlighter, + setSelectedNode, + }), + GridDisplaySettings({ + highlighterSettings, + onToggleShowGridAreas, + onToggleShowGridLineNumbers, + onToggleShowInfiniteLines, + }) + ), + highlightedGrids.length === 1 + ? GridOutline({ + dispatch, + grids, + }) + : null + ); + } +} + +module.exports = Grid; diff --git a/devtools/client/inspector/grids/components/GridDisplaySettings.js b/devtools/client/inspector/grids/components/GridDisplaySettings.js new file mode 100644 index 0000000000..af525cc8c7 --- /dev/null +++ b/devtools/client/inspector/grids/components/GridDisplaySettings.js @@ -0,0 +1,116 @@ +/* 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/grids/types.js"); + +class GridDisplaySettings extends PureComponent { + static get propTypes() { + return { + highlighterSettings: PropTypes.shape(Types.highlighterSettings) + .isRequired, + onToggleShowGridAreas: PropTypes.func.isRequired, + onToggleShowGridLineNumbers: PropTypes.func.isRequired, + onToggleShowInfiniteLines: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + + this.onShowGridAreasCheckboxClick = + this.onShowGridAreasCheckboxClick.bind(this); + this.onShowGridLineNumbersCheckboxClick = + this.onShowGridLineNumbersCheckboxClick.bind(this); + this.onShowInfiniteLinesCheckboxClick = + this.onShowInfiniteLinesCheckboxClick.bind(this); + } + + onShowGridAreasCheckboxClick() { + const { highlighterSettings, onToggleShowGridAreas } = this.props; + + onToggleShowGridAreas(!highlighterSettings.showGridAreasOverlay); + } + + onShowGridLineNumbersCheckboxClick() { + const { highlighterSettings, onToggleShowGridLineNumbers } = this.props; + + onToggleShowGridLineNumbers(!highlighterSettings.showGridLineNumbers); + } + + onShowInfiniteLinesCheckboxClick() { + const { highlighterSettings, onToggleShowInfiniteLines } = this.props; + + onToggleShowInfiniteLines(!highlighterSettings.showInfiniteLines); + } + + render() { + const { highlighterSettings } = this.props; + + return dom.div( + { className: "grid-container" }, + dom.span( + { + role: "heading", + "aria-level": "3", + }, + getStr("layout.gridDisplaySettings") + ), + dom.ul( + {}, + dom.li( + { className: "grid-settings-item" }, + dom.label( + {}, + dom.input({ + id: "grid-setting-show-grid-line-numbers", + type: "checkbox", + checked: highlighterSettings.showGridLineNumbers, + onChange: this.onShowGridLineNumbersCheckboxClick, + }), + getStr("layout.displayLineNumbers") + ) + ), + dom.li( + { className: "grid-settings-item" }, + dom.label( + {}, + dom.input({ + id: "grid-setting-show-grid-areas", + type: "checkbox", + checked: highlighterSettings.showGridAreasOverlay, + onChange: this.onShowGridAreasCheckboxClick, + }), + getStr("layout.displayAreaNames") + ) + ), + dom.li( + { className: "grid-settings-item" }, + dom.label( + {}, + dom.input({ + id: "grid-setting-extend-grid-lines", + type: "checkbox", + checked: highlighterSettings.showInfiniteLines, + onChange: this.onShowInfiniteLinesCheckboxClick, + }), + getStr("layout.extendLinesInfinitely") + ) + ) + ) + ); + } +} + +module.exports = GridDisplaySettings; diff --git a/devtools/client/inspector/grids/components/GridItem.js b/devtools/client/inspector/grids/components/GridItem.js new file mode 100644 index 0000000000..984f57c6a7 --- /dev/null +++ b/devtools/client/inspector/grids/components/GridItem.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 { + 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.lazyGetter(this, "Rep", function () { + return require("resource://devtools/client/shared/components/reps/index.js") + .REPS.Rep; +}); +loader.lazyGetter(this, "MODE", function () { + return require("resource://devtools/client/shared/components/reps/index.js") + .MODE; +}); + +loader.lazyRequireGetter( + this, + "translateNodeFrontToGrip", + "resource://devtools/client/inspector/shared/utils.js", + true +); + +const Types = require("resource://devtools/client/inspector/grids/types.js"); + +const { + highlightNode, + unhighlightNode, +} = require("resource://devtools/client/inspector/boxmodel/actions/box-model-highlighter.js"); + +class GridItem extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + getSwatchColorPickerTooltip: PropTypes.func.isRequired, + grid: PropTypes.shape(Types.grid).isRequired, + grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, + onSetGridOverlayColor: PropTypes.func.isRequired, + onToggleGridHighlighter: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + constructor(props) { + super(props); + + this.swatchEl = createRef(); + + this.onGridCheckboxClick = this.onGridCheckboxClick.bind(this); + this.onGridInspectIconClick = this.onGridInspectIconClick.bind(this); + this.setGridColor = this.setGridColor.bind(this); + } + + componentDidMount() { + const tooltip = this.props.getSwatchColorPickerTooltip(); + + let previousColor; + tooltip.addSwatch(this.swatchEl.current, { + onCommit: this.setGridColor, + onPreview: this.setGridColor, + onRevert: () => { + this.props.onSetGridOverlayColor( + this.props.grid.nodeFront, + previousColor + ); + }, + onShow: () => { + previousColor = this.props.grid.color; + }, + }); + } + + componentWillUnmount() { + const tooltip = this.props.getSwatchColorPickerTooltip(); + tooltip.removeSwatch(this.swatchEl.current); + } + + setGridColor() { + const color = this.swatchEl.current.dataset.color; + this.props.onSetGridOverlayColor(this.props.grid.nodeFront, color); + } + + onGridCheckboxClick() { + const { grid, onToggleGridHighlighter } = this.props; + onToggleGridHighlighter(grid.nodeFront); + } + + onGridInspectIconClick(nodeFront) { + const { setSelectedNode } = this.props; + setSelectedNode(nodeFront, { reason: "layout-panel" }); + nodeFront.scrollIntoView().catch(e => console.error(e)); + } + + renderSubgrids() { + const { grid, grids } = this.props; + + if (!grid.subgrids.length) { + return null; + } + + const subgrids = grids.filter(g => grid.subgrids.includes(g.id)); + + return dom.ul( + {}, + subgrids.map(g => { + return createElement(GridItem, { + key: g.id, + dispatch: this.props.dispatch, + getSwatchColorPickerTooltip: this.props.getSwatchColorPickerTooltip, + grid: g, + grids, + onSetGridOverlayColor: this.props.onSetGridOverlayColor, + onToggleGridHighlighter: this.props.onToggleGridHighlighter, + setSelectedNode: this.props.setSelectedNode, + }); + }) + ); + } + + render() { + const { dispatch, grid } = this.props; + + return createElement( + Fragment, + null, + dom.li( + {}, + dom.label( + {}, + dom.input({ + checked: grid.highlighted, + disabled: grid.disabled, + type: "checkbox", + value: grid.id, + onChange: this.onGridCheckboxClick, + }), + Rep({ + defaultRep: Rep.ElementNode, + mode: MODE.TINY, + object: translateNodeFrontToGrip(grid.nodeFront), + onDOMNodeMouseOut: () => dispatch(unhighlightNode()), + onDOMNodeMouseOver: () => dispatch(highlightNode(grid.nodeFront)), + onInspectIconClick: (_, e) => { + // Stoping click propagation to avoid firing onGridCheckboxClick() + e.stopPropagation(); + this.onGridInspectIconClick(grid.nodeFront); + }, + }) + ), + dom.div({ + className: "layout-color-swatch", + "data-color": grid.color, + ref: this.swatchEl, + style: { + backgroundColor: grid.color, + }, + title: grid.color, + }) + ), + this.renderSubgrids() + ); + } +} + +module.exports = GridItem; diff --git a/devtools/client/inspector/grids/components/GridList.js b/devtools/client/inspector/grids/components/GridList.js new file mode 100644 index 0000000000..62c7ae5b13 --- /dev/null +++ b/devtools/client/inspector/grids/components/GridList.js @@ -0,0 +1,79 @@ +/* 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 GridItem = createFactory( + require("resource://devtools/client/inspector/grids/components/GridItem.js") +); + +const Types = require("resource://devtools/client/inspector/grids/types.js"); + +class GridList extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + getSwatchColorPickerTooltip: PropTypes.func.isRequired, + grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, + onSetGridOverlayColor: PropTypes.func.isRequired, + onToggleGridHighlighter: PropTypes.func.isRequired, + setSelectedNode: PropTypes.func.isRequired, + }; + } + + render() { + const { + dispatch, + getSwatchColorPickerTooltip, + grids, + onSetGridOverlayColor, + onToggleGridHighlighter, + setSelectedNode, + } = this.props; + + return dom.div( + { className: "grid-container" }, + dom.span( + { + role: "heading", + "aria-level": "3", + }, + getStr("layout.overlayGrid") + ), + dom.ul( + { + id: "grid-list", + className: "devtools-monospace", + }, + grids + // Skip subgrids since they are rendered by their parent grids in GridItem. + .filter(grid => !grid.isSubgrid) + .map(grid => + GridItem({ + dispatch, + key: grid.id, + getSwatchColorPickerTooltip, + grid, + grids, + onSetGridOverlayColor, + onToggleGridHighlighter, + setSelectedNode, + }) + ) + ) + ); + } +} + +module.exports = GridList; diff --git a/devtools/client/inspector/grids/components/GridOutline.js b/devtools/client/inspector/grids/components/GridOutline.js new file mode 100644 index 0000000000..65771f3f45 --- /dev/null +++ b/devtools/client/inspector/grids/components/GridOutline.js @@ -0,0 +1,436 @@ +/* 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 { + getWritingModeMatrix, + getCSSMatrixTransform, +} = require("resource://devtools/shared/layout/dom-matrix-2d.js"); + +const Types = require("resource://devtools/client/inspector/grids/types.js"); + +// The delay prior to executing the grid cell highlighting. +const GRID_HIGHLIGHTING_DEBOUNCE = 50; + +// Prefs for the max number of rows/cols a grid container can have for +// the outline to display. +const GRID_OUTLINE_MAX_ROWS_PREF = Services.prefs.getIntPref( + "devtools.gridinspector.gridOutlineMaxRows" +); +const GRID_OUTLINE_MAX_COLUMNS_PREF = Services.prefs.getIntPref( + "devtools.gridinspector.gridOutlineMaxColumns" +); + +// Move SVG grid to the right 100 units, so that it is not flushed against the edge of +// layout border +const TRANSLATE_X = 0; +const TRANSLATE_Y = 0; + +const GRID_CELL_SCALE_FACTOR = 50; + +const VIEWPORT_MIN_HEIGHT = 100; +const VIEWPORT_MAX_HEIGHT = 150; + +const { + showGridHighlighter, +} = require("resource://devtools/client/inspector/grids/actions/grid-highlighter.js"); + +class GridOutline extends PureComponent { + static get propTypes() { + return { + dispatch: PropTypes.func.isRequired, + grids: PropTypes.arrayOf(PropTypes.shape(Types.grid)).isRequired, + }; + } + + static getDerivedStateFromProps(props) { + const selectedGrid = props.grids.find(grid => grid.highlighted); + + // Store the height of the grid container in the component state to prevent overflow + // issues. We want to store the width of the grid container as well so that the + // viewbox is only the calculated width of the grid outline. + const { width, height } = selectedGrid?.gridFragments.length + ? getTotalWidthAndHeight(selectedGrid) + : { width: 0, height: 0 }; + let showOutline; + + if (selectedGrid?.gridFragments.length) { + const { cols, rows } = selectedGrid.gridFragments[0]; + + // Show the grid outline if both the rows/columns are less than or equal + // to their max prefs. + showOutline = + cols.lines.length <= GRID_OUTLINE_MAX_COLUMNS_PREF && + rows.lines.length <= GRID_OUTLINE_MAX_ROWS_PREF; + } + + return { height, width, selectedGrid, showOutline }; + } + + constructor(props) { + super(props); + + this.state = { + height: 0, + selectedGrid: null, + showOutline: true, + width: 0, + }; + + this.doHighlightCell = this.doHighlightCell.bind(this); + this.getGridAreaName = this.getGridAreaName.bind(this); + this.getHeight = this.getHeight.bind(this); + this.onHighlightCell = this.onHighlightCell.bind(this); + this.renderCannotShowOutlineText = + this.renderCannotShowOutlineText.bind(this); + this.renderGrid = this.renderGrid.bind(this); + this.renderGridCell = this.renderGridCell.bind(this); + this.renderGridOutline = this.renderGridOutline.bind(this); + this.renderGridOutlineBorder = this.renderGridOutlineBorder.bind(this); + this.renderOutline = this.renderOutline.bind(this); + } + + doHighlightCell(target, hide) { + const { dispatch, grids } = this.props; + const name = target.dataset.gridAreaName; + const id = target.dataset.gridId; + const gridFragmentIndex = target.dataset.gridFragmentIndex; + const rowNumber = target.dataset.gridRow; + const columnNumber = target.dataset.gridColumn; + const nodeFront = grids[id].nodeFront; + + // The options object has the following properties which corresponds to the + // required parameters for showing the grid cell or area highlights. + // See devtools/server/actors/highlighters/css-grid.js + // { + // showGridArea: String, + // showGridCell: { + // gridFragmentIndex: Number, + // rowNumber: Number, + // columnNumber: Number, + // }, + // } + const options = { + showGridArea: name, + showGridCell: { + gridFragmentIndex, + rowNumber, + columnNumber, + }, + }; + + if (hide) { + // Reset the grid highlighter to default state; no options = hide cell/area outline. + dispatch(showGridHighlighter(nodeFront)); + } else { + dispatch(showGridHighlighter(nodeFront, options)); + } + } + + /** + * Returns the grid area name if the given grid cell is part of a grid area, otherwise + * null. + * + * @param {Number} columnNumber + * The column number of the grid cell. + * @param {Number} rowNumber + * The row number of the grid cell. + * @param {Array} areas + * Array of grid areas data stored in the grid fragment. + * @return {String} If there is a grid area return area name, otherwise null. + */ + getGridAreaName(columnNumber, rowNumber, areas) { + const gridArea = areas.find( + area => + area.rowStart <= rowNumber && + area.rowEnd > rowNumber && + area.columnStart <= columnNumber && + area.columnEnd > columnNumber + ); + + if (!gridArea) { + return null; + } + + return gridArea.name; + } + + /** + * Returns the height of the grid outline ranging between a minimum and maximum height. + * + * @return {Number} The height of the grid outline. + */ + getHeight() { + const { height } = this.state; + + if (height >= VIEWPORT_MAX_HEIGHT) { + return VIEWPORT_MAX_HEIGHT; + } else if (height <= VIEWPORT_MIN_HEIGHT) { + return VIEWPORT_MIN_HEIGHT; + } + + return height; + } + + /** + * Displays a message text "Cannot show outline for this grid". + */ + renderCannotShowOutlineText() { + return dom.div( + { className: "grid-outline-text" }, + dom.span({ + className: "grid-outline-text-icon", + title: getStr("layout.cannotShowGridOutline.title"), + }), + getStr("layout.cannotShowGridOutline") + ); + } + + /** + * Renders the grid outline for the given grid container object. + * + * @param {Object} grid + * A single grid container in the document. + */ + renderGrid(grid) { + // TODO: We are drawing the first fragment since only one is currently being stored. + // In the future we will need to iterate over all fragments of a grid. + const gridFragmentIndex = 0; + const { id, color, gridFragments } = grid; + const { rows, cols, areas } = gridFragments[gridFragmentIndex]; + + const numberOfColumns = cols.lines.length - 1; + const numberOfRows = rows.lines.length - 1; + const rectangles = []; + let x = 0; + let y = 0; + let width = 0; + let height = 0; + + // Draw the cells contained within the grid outline border. + for (let rowNumber = 1; rowNumber <= numberOfRows; rowNumber++) { + height = + GRID_CELL_SCALE_FACTOR * (rows.tracks[rowNumber - 1].breadth / 100); + + for ( + let columnNumber = 1; + columnNumber <= numberOfColumns; + columnNumber++ + ) { + width = + GRID_CELL_SCALE_FACTOR * + (cols.tracks[columnNumber - 1].breadth / 100); + + const gridAreaName = this.getGridAreaName( + columnNumber, + rowNumber, + areas + ); + const gridCell = this.renderGridCell( + id, + gridFragmentIndex, + x, + y, + rowNumber, + columnNumber, + color, + gridAreaName, + width, + height + ); + + rectangles.push(gridCell); + x += width; + } + + x = 0; + y += height; + } + + // Transform the cells as needed to match the grid container's writing mode. + const cellGroupStyle = {}; + const writingModeMatrix = getWritingModeMatrix(this.state, grid); + cellGroupStyle.transform = getCSSMatrixTransform(writingModeMatrix); + const cellGroup = dom.g( + { + id: "grid-cell-group", + style: cellGroupStyle, + }, + rectangles + ); + + // Draw a rectangle that acts as the grid outline border. + const border = this.renderGridOutlineBorder( + this.state.width, + this.state.height, + color + ); + + return [border, cellGroup]; + } + + /** + * Renders the grid cell of a grid fragment. + * + * @param {Number} id + * The grid id stored on the grid fragment + * @param {Number} gridFragmentIndex + * The index of the grid fragment rendered to the document. + * @param {Number} x + * The x-position of the grid cell. + * @param {Number} y + * The y-position of the grid cell. + * @param {Number} rowNumber + * The row number of the grid cell. + * @param {Number} columnNumber + * The column number of the grid cell. + * @param {String|null} gridAreaName + * The grid area name or null if the grid cell is not part of a grid area. + * @param {Number} width + * The width of grid cell. + * @param {Number} height + * The height of the grid cell. + */ + renderGridCell( + id, + gridFragmentIndex, + x, + y, + rowNumber, + columnNumber, + color, + gridAreaName, + width, + height + ) { + return dom.rect({ + key: `${id}-${rowNumber}-${columnNumber}`, + className: "grid-outline-cell", + "data-grid-area-name": gridAreaName, + "data-grid-fragment-index": gridFragmentIndex, + "data-grid-id": id, + "data-grid-row": rowNumber, + "data-grid-column": columnNumber, + x, + y, + width, + height, + fill: "none", + onMouseEnter: this.onHighlightCell, + onMouseLeave: this.onHighlightCell, + }); + } + + renderGridOutline(grid) { + const { color } = grid; + + return dom.g( + { + id: "grid-outline-group", + className: "grid-outline-group", + style: { color }, + }, + this.renderGrid(grid) + ); + } + + renderGridOutlineBorder(borderWidth, borderHeight, color) { + return dom.rect({ + key: "border", + className: "grid-outline-border", + x: 0, + y: 0, + width: borderWidth, + height: borderHeight, + }); + } + + renderOutline() { + const { height, selectedGrid, showOutline, width } = this.state; + + return showOutline + ? dom.svg( + { + id: "grid-outline", + width: "100%", + height: this.getHeight(), + viewBox: `${TRANSLATE_X} ${TRANSLATE_Y} ${width} ${height}`, + }, + this.renderGridOutline(selectedGrid) + ) + : this.renderCannotShowOutlineText(); + } + + onHighlightCell({ target, type }) { + // Debounce the highlighting of cells. + // This way we don't end up sending many requests to the server for highlighting when + // cells get hovered in a rapid succession We only send a request if the user settles + // on a cell for some time. + if (this.highlightTimeout) { + clearTimeout(this.highlightTimeout); + } + + this.highlightTimeout = setTimeout(() => { + this.doHighlightCell(target, type === "mouseleave"); + this.highlightTimeout = null; + }, GRID_HIGHLIGHTING_DEBOUNCE); + } + + render() { + const { selectedGrid } = this.state; + + return selectedGrid?.gridFragments.length + ? dom.div( + { + id: "grid-outline-container", + className: "grid-outline-container", + }, + this.renderOutline() + ) + : null; + } +} + +/** + * Get the width and height of a given grid. + * + * @param {Object} grid + * A single grid container in the document. + * @return {Object} An object like { width, height } + */ +function getTotalWidthAndHeight(grid) { + // TODO: We are drawing the first fragment since only one is currently being stored. + // In the future we will need to iterate over all fragments of a grid. + const { gridFragments } = grid; + const { rows, cols } = gridFragments[0]; + + let height = 0; + for (let i = 0; i < rows.lines.length - 1; i++) { + height += GRID_CELL_SCALE_FACTOR * (rows.tracks[i].breadth / 100); + } + + let width = 0; + for (let i = 0; i < cols.lines.length - 1; i++) { + width += GRID_CELL_SCALE_FACTOR * (cols.tracks[i].breadth / 100); + } + + // All writing modes other than horizontal-tb (the initial value) involve a 90 deg + // rotation, so swap width and height. + if (grid.writingMode != "horizontal-tb") { + [width, height] = [height, width]; + } + + return { width, height }; +} + +module.exports = GridOutline; diff --git a/devtools/client/inspector/grids/components/moz.build b/devtools/client/inspector/grids/components/moz.build new file mode 100644 index 0000000000..e938e51ad1 --- /dev/null +++ b/devtools/client/inspector/grids/components/moz.build @@ -0,0 +1,13 @@ +# -*- 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( + "Grid.js", + "GridDisplaySettings.js", + "GridItem.js", + "GridList.js", + "GridOutline.js", +) |