/* 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 { Component, createFactory, } = require("resource://devtools/client/shared/vendor/react.js"); const ToolboxToolbar = createFactory( require("resource://devtools/client/framework/components/ToolboxToolbar.js") ); const ELEMENT_PICKER_ID = "command-button-pick"; /** * This component serves as a state controller for the toolbox React component. It's a * thin layer for translating events and state of the outside world into the React update * cycle. This solution was used to keep the amount of code changes to a minimimum while * adapting the existing codebase to start using React. */ class ToolboxController extends Component { constructor(props, context) { super(props, context); // See the ToolboxToolbar propTypes for documentation on each of these items in // state, and for the definitions of the props that are expected to be passed in. this.state = { focusedButton: ELEMENT_PICKER_ID, toolboxButtons: [], visibleToolboxButtonCount: 0, currentToolId: null, highlightedTools: new Set(), panelDefinitions: [], hostTypes: [], currentHostType: undefined, areDockOptionsEnabled: true, canCloseToolbox: true, isSplitConsoleActive: false, disableAutohide: undefined, alwaysOnTop: undefined, pseudoLocale: undefined, canRender: false, buttonIds: [], checkedButtonsUpdated: () => { this.forceUpdate(); }, }; this.setFocusedButton = this.setFocusedButton.bind(this); this.setToolboxButtons = this.setToolboxButtons.bind(this); this.setCurrentToolId = this.setCurrentToolId.bind(this); this.highlightTool = this.highlightTool.bind(this); this.unhighlightTool = this.unhighlightTool.bind(this); this.setHostTypes = this.setHostTypes.bind(this); this.setCurrentHostType = this.setCurrentHostType.bind(this); this.setDockOptionsEnabled = this.setDockOptionsEnabled.bind(this); this.setCanCloseToolbox = this.setCanCloseToolbox.bind(this); this.setIsSplitConsoleActive = this.setIsSplitConsoleActive.bind(this); this.setDisableAutohide = this.setDisableAutohide.bind(this); this.setCanRender = this.setCanRender.bind(this); this.setPanelDefinitions = this.setPanelDefinitions.bind(this); this.updateButtonIds = this.updateButtonIds.bind(this); this.updateFocusedButton = this.updateFocusedButton.bind(this); this.setDebugTargetData = this.setDebugTargetData.bind(this); } shouldComponentUpdate() { return this.state.canRender; } componentWillUnmount() { this.state.toolboxButtons.forEach(button => { button.off("updatechecked", this.state.checkedButtonsUpdated); }); } /** * The button and tab ids must be known in order to be able to focus left and right * using the arrow keys. */ updateButtonIds() { const { toolboxButtons, panelDefinitions, canCloseToolbox } = this.state; // This is a little gnarly, but go through all of the state and extract the IDs. this.setState({ buttonIds: [ ...toolboxButtons .filter(btn => btn.isInStartContainer) .map(({ id }) => id), ...panelDefinitions.map(({ id }) => id), ...toolboxButtons .filter(btn => !btn.isInStartContainer) .map(({ id }) => id), canCloseToolbox ? "toolbox-close" : null, ].filter(id => id), }); this.updateFocusedButton(); } updateFocusedButton() { this.setFocusedButton(this.state.focusedButton); } setFocusedButton(focusedButton) { const { buttonIds } = this.state; focusedButton = focusedButton && buttonIds.includes(focusedButton) ? focusedButton : buttonIds[0]; if (this.state.focusedButton !== focusedButton) { this.setState({ focusedButton, }); } } setCurrentToolId(currentToolId) { this.setState({ currentToolId }, () => { // Also set the currently focused button to this tool. this.setFocusedButton(currentToolId); }); } setCanRender() { this.setState({ canRender: true }, this.updateButtonIds); } highlightTool(highlightedTool) { const { highlightedTools } = this.state; highlightedTools.add(highlightedTool); this.setState({ highlightedTools }); } unhighlightTool(id) { const { highlightedTools } = this.state; if (highlightedTools.has(id)) { highlightedTools.delete(id); this.setState({ highlightedTools }); } } setDockOptionsEnabled(areDockOptionsEnabled) { this.setState({ areDockOptionsEnabled }); } setHostTypes(hostTypes) { this.setState({ hostTypes }); } setCurrentHostType(currentHostType) { this.setState({ currentHostType }); } setCanCloseToolbox(canCloseToolbox) { this.setState({ canCloseToolbox }, this.updateButtonIds); } setIsSplitConsoleActive(isSplitConsoleActive) { this.setState({ isSplitConsoleActive }); } /** * @param {bool | undefined} disableAutohide */ setDisableAutohide(disableAutohide) { this.setState({ disableAutohide }); } /** * @param {bool | undefined} alwaysOnTop */ setAlwaysOnTop(alwaysOnTop) { this.setState({ alwaysOnTop }); } /** * @param {bool} focusedState */ setFocusedState(focusedState) { // We only care about the focused state when the toolbox is always on top if (this.state.alwaysOnTop) { this.setState({ focusedState }); } } /** * @param {"bidi" | "accented" | "none" | undefined} pseudoLocale */ setPseudoLocale(pseudoLocale) { this.setState({ pseudoLocale }); } setPanelDefinitions(panelDefinitions) { this.setState({ panelDefinitions }, this.updateButtonIds); } get panelDefinitions() { return this.state.panelDefinitions; } setToolboxButtons(toolboxButtons) { // Listen for updates of the checked attribute. this.state.toolboxButtons.forEach(button => { button.off("updatechecked", this.state.checkedButtonsUpdated); }); toolboxButtons.forEach(button => { button.on("updatechecked", this.state.checkedButtonsUpdated); }); const visibleToolboxButtonCount = toolboxButtons.filter( button => button.isVisible ).length; this.setState( { toolboxButtons, visibleToolboxButtonCount }, this.updateButtonIds ); } setDebugTargetData(data) { this.setState({ debugTargetData: data }); } render() { return ToolboxToolbar(Object.assign({}, this.props, this.state)); } } module.exports = ToolboxController;