ToolboxController.js (6792B)
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 file, 3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 "use strict"; 5 6 const { 7 Component, 8 createFactory, 9 } = require("resource://devtools/client/shared/vendor/react.mjs"); 10 const ToolboxToolbar = createFactory( 11 require("resource://devtools/client/framework/components/ToolboxToolbar.js") 12 ); 13 const ELEMENT_PICKER_ID = "command-button-pick"; 14 15 /** 16 * This component serves as a state controller for the toolbox React component. It's a 17 * thin layer for translating events and state of the outside world into the React update 18 * cycle. This solution was used to keep the amount of code changes to a minimimum while 19 * adapting the existing codebase to start using React. 20 */ 21 class ToolboxController extends Component { 22 constructor(props, context) { 23 super(props, context); 24 25 // See the ToolboxToolbar propTypes for documentation on each of these items in 26 // state, and for the definitions of the props that are expected to be passed in. 27 this.state = { 28 focusedButton: ELEMENT_PICKER_ID, 29 toolboxButtons: [], 30 visibleToolboxButtonCount: 0, 31 currentToolId: null, 32 highlightedTools: new Set(), 33 panelDefinitions: [], 34 hostTypes: [], 35 currentHostType: undefined, 36 areDockOptionsEnabled: true, 37 canCloseToolbox: true, 38 isSplitConsoleActive: false, 39 disableAutohide: undefined, 40 alwaysOnTop: undefined, 41 pseudoLocale: undefined, 42 canRender: false, 43 buttonIds: [], 44 checkedButtonsUpdated: () => { 45 this.forceUpdate(); 46 }, 47 }; 48 49 this.setFocusedButton = this.setFocusedButton.bind(this); 50 this.setToolboxButtons = this.setToolboxButtons.bind(this); 51 this.setCurrentToolId = this.setCurrentToolId.bind(this); 52 this.highlightTool = this.highlightTool.bind(this); 53 this.unhighlightTool = this.unhighlightTool.bind(this); 54 this.setHostTypes = this.setHostTypes.bind(this); 55 this.setCurrentHostType = this.setCurrentHostType.bind(this); 56 this.setDockOptionsEnabled = this.setDockOptionsEnabled.bind(this); 57 this.setCanCloseToolbox = this.setCanCloseToolbox.bind(this); 58 this.setIsSplitConsoleActive = this.setIsSplitConsoleActive.bind(this); 59 this.setDisableAutohide = this.setDisableAutohide.bind(this); 60 this.setCanRender = this.setCanRender.bind(this); 61 this.setPanelDefinitions = this.setPanelDefinitions.bind(this); 62 this.updateButtonIds = this.updateButtonIds.bind(this); 63 this.updateFocusedButton = this.updateFocusedButton.bind(this); 64 this.setDebugTargetData = this.setDebugTargetData.bind(this); 65 } 66 67 shouldComponentUpdate() { 68 return this.state.canRender; 69 } 70 71 componentWillUnmount() { 72 this.state.toolboxButtons.forEach(button => { 73 button.off("updatechecked", this.state.checkedButtonsUpdated); 74 }); 75 } 76 77 /** 78 * The button and tab ids must be known in order to be able to focus left and right 79 * using the arrow keys. 80 */ 81 updateButtonIds() { 82 const { toolboxButtons, panelDefinitions, canCloseToolbox } = this.state; 83 84 // This is a little gnarly, but go through all of the state and extract the IDs. 85 this.setState({ 86 buttonIds: [ 87 ...toolboxButtons 88 .filter(btn => btn.isInStartContainer) 89 .map(({ id }) => id), 90 ...panelDefinitions.map(({ id }) => id), 91 ...toolboxButtons 92 .filter(btn => !btn.isInStartContainer) 93 .map(({ id }) => id), 94 canCloseToolbox ? "toolbox-close" : null, 95 ].filter(id => id), 96 }); 97 98 this.updateFocusedButton(); 99 } 100 101 updateFocusedButton() { 102 this.setFocusedButton(this.state.focusedButton); 103 } 104 105 setFocusedButton(focusedButton) { 106 const { buttonIds } = this.state; 107 108 focusedButton = 109 focusedButton && buttonIds.includes(focusedButton) 110 ? focusedButton 111 : buttonIds[0]; 112 if (this.state.focusedButton !== focusedButton) { 113 this.setState({ 114 focusedButton, 115 }); 116 } 117 } 118 119 setCurrentToolId(currentToolId) { 120 this.setState({ currentToolId }, () => { 121 // Also set the currently focused button to this tool. 122 this.setFocusedButton(currentToolId); 123 }); 124 } 125 126 setCanRender() { 127 this.setState({ canRender: true }, this.updateButtonIds); 128 } 129 130 highlightTool(highlightedTool) { 131 const { highlightedTools } = this.state; 132 highlightedTools.add(highlightedTool); 133 this.setState({ highlightedTools }); 134 } 135 136 unhighlightTool(id) { 137 const { highlightedTools } = this.state; 138 if (highlightedTools.has(id)) { 139 highlightedTools.delete(id); 140 this.setState({ highlightedTools }); 141 } 142 } 143 144 setDockOptionsEnabled(areDockOptionsEnabled) { 145 this.setState({ areDockOptionsEnabled }); 146 } 147 148 setHostTypes(hostTypes) { 149 this.setState({ hostTypes }); 150 } 151 152 setCurrentHostType(currentHostType) { 153 this.setState({ currentHostType }); 154 } 155 156 setCanCloseToolbox(canCloseToolbox) { 157 this.setState({ canCloseToolbox }, this.updateButtonIds); 158 } 159 160 setIsSplitConsoleActive(isSplitConsoleActive) { 161 this.setState({ isSplitConsoleActive }); 162 } 163 164 /** 165 * @param {bool | undefined} disableAutohide 166 */ 167 setDisableAutohide(disableAutohide) { 168 this.setState({ disableAutohide }); 169 } 170 171 /** 172 * @param {bool | undefined} alwaysOnTop 173 */ 174 setAlwaysOnTop(alwaysOnTop) { 175 this.setState({ alwaysOnTop }); 176 } 177 178 /** 179 * @param {bool} focusedState 180 */ 181 setFocusedState(focusedState) { 182 // We only care about the focused state when the toolbox is always on top 183 if (this.state.alwaysOnTop) { 184 this.setState({ focusedState }); 185 } 186 } 187 188 /** 189 * @param {"bidi" | "accented" | "none" | undefined} pseudoLocale 190 */ 191 setPseudoLocale(pseudoLocale) { 192 this.setState({ pseudoLocale }); 193 } 194 195 setPanelDefinitions(panelDefinitions) { 196 this.setState({ panelDefinitions }, this.updateButtonIds); 197 } 198 199 get panelDefinitions() { 200 return this.state.panelDefinitions; 201 } 202 203 setToolboxButtons(toolboxButtons) { 204 // Listen for updates of the checked attribute. 205 this.state.toolboxButtons.forEach(button => { 206 button.off("updatechecked", this.state.checkedButtonsUpdated); 207 }); 208 toolboxButtons.forEach(button => { 209 button.on("updatechecked", this.state.checkedButtonsUpdated); 210 }); 211 212 const visibleToolboxButtonCount = toolboxButtons.filter( 213 button => button.isVisible 214 ).length; 215 216 this.setState( 217 { toolboxButtons, visibleToolboxButtonCount }, 218 this.updateButtonIds 219 ); 220 } 221 222 setDebugTargetData(data) { 223 this.setState({ debugTargetData: data }); 224 } 225 226 render() { 227 return ToolboxToolbar(Object.assign({}, this.props, this.state)); 228 } 229 } 230 231 module.exports = ToolboxController;