Source code
Revision control
Copy as Markdown
Other Tools
/* 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,
"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;