/* 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 Services = require("Services"); const { Task } = require("devtools/shared/task"); const { createFactory, createElement } = require("devtools/client/shared/vendor/react"); const { Provider } = require("devtools/client/shared/vendor/react-redux"); const { updateGridHighlighted, updateGrids, } = require("./actions/grids"); const { updateShowGridLineNumbers, updateShowInfiniteLines, } = require("./actions/highlighter-settings"); const App = createFactory(require("./components/App")); const Store = require("./store"); const { LocalizationHelper } = require("devtools/shared/l10n"); const INSPECTOR_L10N = new LocalizationHelper("devtools/client/locales/inspector.properties"); const SHOW_GRID_LINE_NUMBERS = "devtools.gridinspector.showGridLineNumbers"; const SHOW_INFINITE_LINES_PREF = "devtools.gridinspector.showInfiniteLines"; function LayoutView(inspector, window) { this.document = window.document; this.highlighters = inspector.highlighters; this.inspector = inspector; this.store = null; this.walker = this.inspector.walker; this.onGridLayoutChange = this.onGridLayoutChange.bind(this); this.onHighlighterChange = this.onHighlighterChange.bind(this); this.onSidebarSelect = this.onSidebarSelect.bind(this); this.highlighters.on("grid-highlighter-hidden", this.onHighlighterChange); this.highlighters.on("grid-highlighter-shown", this.onHighlighterChange); this.inspector.sidebar.on("select", this.onSidebarSelect); this.init(); } LayoutView.prototype = { /** * Initializes the layout view by fetching the LayoutFront from the walker, creating * the redux store and adding the view into the inspector sidebar. */ init: Task.async(function* () { if (!this.inspector) { return; } this.layoutInspector = yield this.inspector.walker.getLayoutInspector(); let store = this.store = Store(); this.loadHighlighterSettings(); let app = App({ /** * Handler for a change in the input checkboxes in the GridList component. * Toggles on/off the grid highlighter for the provided grid container element. * * @param {NodeFront} node * The NodeFront of the grid container element for which the grid * highlighter is toggled on/off for. */ onToggleGridHighlighter: node => { let { highlighterSettings } = this.store.getState(); this.highlighters.toggleGridHighlighter(node, highlighterSettings); }, /** * Handler for a change in the show grid line numbers checkbox in the * GridDisplaySettings component. TOggles on/off the option to show the grid line * numbers in the grid highlighter. Refreshes the shown grid highlighter for the * grids currently highlighted. * * @param {Boolean} enabled * Whether or not the grid highlighter should show the grid line numbers. */ onToggleShowGridLineNumbers: enabled => { this.store.dispatch(updateShowGridLineNumbers(enabled)); Services.prefs.setBoolPref(SHOW_GRID_LINE_NUMBERS, enabled); let { grids, highlighterSettings } = this.store.getState(); for (let grid of grids) { if (grid.highlighted) { this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings); } } }, /** * Handler for a change in the extend grid lines infinitely checkbox in the * GridDisplaySettings component. Toggles on/off the option to extend the grid * lines infinitely in the grid highlighter. Refreshes the shown grid highlighter * for grids currently highlighted. * * @param {Boolean} enabled * Whether or not the grid highlighter should extend grid lines infinitely. */ onToggleShowInfiniteLines: enabled => { this.store.dispatch(updateShowInfiniteLines(enabled)); Services.prefs.setBoolPref(SHOW_INFINITE_LINES_PREF, enabled); let { grids, highlighterSettings } = this.store.getState(); for (let grid of grids) { if (grid.highlighted) { this.highlighters.showGridHighlighter(grid.nodeFront, highlighterSettings); } } }, }); let provider = createElement(Provider, { store, id: "layoutview", title: INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"), key: "layoutview", }, app); let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar"); this.inspector.addSidebarTab( "layoutview", INSPECTOR_L10N.getStr("inspector.sidebar.layoutViewTitle2"), provider, defaultTab == "layoutview" ); }), /** * Destruction function called when the inspector is destroyed. Removes event listeners * and cleans up references. */ destroy() { this.highlighters.off("grid-highlighter-hidden", this.onHighlighterChange); this.highlighters.off("grid-highlighter-shown", this.onHighlighterChange); this.inspector.sidebar.off("select", this.onSidebarSelect); this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange); this.document = null; this.inspector = null; this.layoutInspector = null; this.store = null; this.walker = null; }, /** * Returns true if the layout panel is visible, and false otherwise. */ isPanelVisible() { return this.inspector.toolbox.currentToolId === "inspector" && this.inspector.sidebar && this.inspector.sidebar.getCurrentTabID() === "layoutview"; }, /** * Load the grid highligher display settings into the store from the stored preferences. */ loadHighlighterSettings() { let { dispatch } = this.store; let showGridLineNumbers = Services.prefs.getBoolPref(SHOW_GRID_LINE_NUMBERS); let showInfinteLines = Services.prefs.getBoolPref(SHOW_INFINITE_LINES_PREF); dispatch(updateShowGridLineNumbers(showGridLineNumbers)); dispatch(updateShowInfiniteLines(showInfinteLines)); }, /** * Refreshes the layout view by dispatching the new grid data. This is called when the * layout view becomes visible or the view needs to be updated with new grid data. * * @param {Array|null} gridFronts * Optional array of all GridFront in the current page. */ refresh: Task.async(function* (gridFronts) { // Stop refreshing if the inspector or store is already destroyed. if (!this.inspector || !this.store) { return; } // Get all the GridFront from the server if no gridFronts were provided. if (!gridFronts) { gridFronts = yield this.layoutInspector.getAllGrids(this.walker.rootNode); } let grids = []; for (let i = 0; i < gridFronts.length; i++) { let grid = gridFronts[i]; let nodeFront = yield this.walker.getNodeFromActor(grid.actorID, ["containerEl"]); grids.push({ id: i, gridFragments: grid.gridFragments, highlighted: nodeFront == this.highlighters.gridHighlighterShown, nodeFront, }); } this.store.dispatch(updateGrids(grids)); }), /** * Handler for "grid-layout-changed" events emitted from the LayoutActor. * * @param {Array} grids * Array of all GridFront in the current page. */ onGridLayoutChange(grids) { if (this.isPanelVisible()) { this.refresh(grids); } }, /** * Handler for "grid-highlighter-shown" and "grid-highlighter-hidden" events emitted * from the HighlightersOverlay. Updates the NodeFront's grid highlighted state. * * @param {Event} event * Event that was triggered. * @param {NodeFront} nodeFront * The NodeFront of the grid container element for which the grid highlighter * is shown for. */ onHighlighterChange(event, nodeFront) { let highlighted = event === "grid-highlighter-shown"; this.store.dispatch(updateGridHighlighted(nodeFront, highlighted)); }, /** * Handler for the inspector sidebar select event. Starts listening for * "grid-layout-changed" if the layout panel is visible. Otherwise, stop * listening for grid layout changes. Finally, refresh the layout view if * it is visible. */ onSidebarSelect() { if (!this.isPanelVisible()) { this.layoutInspector.off("grid-layout-changed", this.onGridLayoutChange); return; } this.layoutInspector.on("grid-layout-changed", this.onGridLayoutChange); this.refresh(); }, }; module.exports = LayoutView;