diff options
Diffstat (limited to 'devtools/client/framework/components/MeatballMenu.js')
-rw-r--r-- | devtools/client/framework/components/MeatballMenu.js | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/devtools/client/framework/components/MeatballMenu.js b/devtools/client/framework/components/MeatballMenu.js new file mode 100644 index 0000000000..52043740d0 --- /dev/null +++ b/devtools/client/framework/components/MeatballMenu.js @@ -0,0 +1,241 @@ +/* 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, + createFactory, +} = require("devtools/client/shared/vendor/react"); +const PropTypes = require("devtools/client/shared/vendor/react-prop-types"); +const dom = require("devtools/client/shared/vendor/react-dom-factories"); +const { hr } = dom; + +loader.lazyGetter(this, "MenuItem", function() { + return createFactory( + require("devtools/client/shared/components/menu/MenuItem") + ); +}); +loader.lazyGetter(this, "MenuList", function() { + return createFactory( + require("devtools/client/shared/components/menu/MenuList") + ); +}); + +loader.lazyRequireGetter( + this, + "openDocLink", + "devtools/client/shared/link", + true +); +loader.lazyRequireGetter(this, "assert", "devtools/shared/DevToolsUtils", true); + +const openDevToolsDocsLink = () => { + openDocLink( + "https://developer.mozilla.org/docs/Tools?utm_source=devtools&utm_medium=tabbar-menu" + ); +}; + +const openCommunityLink = () => { + openDocLink( + "https://discourse.mozilla.org/c/devtools?utm_source=devtools&utm_medium=tabbar-menu" + ); +}; + +class MeatballMenu extends PureComponent { + static get propTypes() { + return { + // The id of the currently selected tool, e.g. "inspector" + currentToolId: PropTypes.string, + + // List of possible docking options. + hostTypes: PropTypes.arrayOf( + PropTypes.shape({ + position: PropTypes.string.isRequired, + switchHost: PropTypes.func.isRequired, + }) + ), + + // Current docking type. Typically one of the position values in + // |hostTypes| but this is not always the case (e.g. for "browsertoolbox"). + currentHostType: PropTypes.string, + + // Is the split console currently visible? + isSplitConsoleActive: PropTypes.bool, + + // Are we disabling the behavior where pop-ups are automatically closed + // when clicking outside them? + // + // This is a tri-state value that may be true/false or undefined where + // undefined means that the option is not relevant in this context + // (i.e. we're not in a browser toolbox). + disableAutohide: PropTypes.bool, + + // Function to turn the options panel on / off. + toggleOptions: PropTypes.func.isRequired, + + // Function to turn the split console on / off. + toggleSplitConsole: PropTypes.func, + + // Function to turn the disable pop-up autohide behavior on / off. + toggleNoAutohide: PropTypes.func, + + // Localization interface. + L10N: PropTypes.object.isRequired, + + // Callback function that will be invoked any time the component contents + // update in such a way that its bounding box might change. + onResize: PropTypes.func, + }; + } + + componentDidUpdate(prevProps) { + if (!this.props.onResize) { + return; + } + + // We are only expecting the following kinds of dynamic changes when a popup + // is showing: + // + // - The "Disable pop-up autohide" menu item being added after the Browser + // Toolbox is connected. + // - The split console label changing between "Show Split Console" and "Hide + // Split Console". + // - The "Show/Hide Split Console" entry being added removed or removed. + // + // The latter two cases are only likely to be noticed when "Disable pop-up + // autohide" is active, but for completeness we handle them here. + const didChange = + typeof this.props.disableAutohide !== typeof prevProps.disableAutohide || + this.props.currentToolId !== prevProps.currentToolId || + this.props.isSplitConsoleActive !== prevProps.isSplitConsoleActive; + + if (didChange) { + this.props.onResize(); + } + } + + render() { + const items = []; + + // Dock options + for (const hostType of this.props.hostTypes) { + // This is more verbose than it needs to be but lets us easily search for + // l10n entities. + let l10nkey; + switch (hostType.position) { + case "window": + l10nkey = "toolbox.meatballMenu.dock.separateWindow.label"; + break; + + case "bottom": + l10nkey = "toolbox.meatballMenu.dock.bottom.label"; + break; + + case "left": + l10nkey = "toolbox.meatballMenu.dock.left.label"; + break; + + case "right": + l10nkey = "toolbox.meatballMenu.dock.right.label"; + break; + + default: + assert(false, `Unexpected hostType.position: ${hostType.position}`); + break; + } + + items.push( + MenuItem({ + id: `toolbox-meatball-menu-dock-${hostType.position}`, + key: `dock-${hostType.position}`, + label: this.props.L10N.getStr(l10nkey), + onClick: hostType.switchHost, + checked: hostType.position === this.props.currentHostType, + className: "iconic", + }) + ); + } + + if (items.length) { + items.push(hr({ key: "dock-separator" })); + } + + // Split console + if (this.props.currentToolId !== "webconsole") { + const l10nkey = this.props.isSplitConsoleActive + ? "toolbox.meatballMenu.hideconsole.label" + : "toolbox.meatballMenu.splitconsole.label"; + items.push( + MenuItem({ + id: "toolbox-meatball-menu-splitconsole", + key: "splitconsole", + label: this.props.L10N.getStr(l10nkey), + accelerator: "Esc", + onClick: this.props.toggleSplitConsole, + className: "iconic", + }) + ); + } + + // Disable pop-up autohide + // + // If |disableAutohide| is undefined, it means this feature is not available + // in this context. + if (typeof this.props.disableAutohide !== "undefined") { + items.push( + MenuItem({ + id: "toolbox-meatball-menu-noautohide", + key: "noautohide", + label: this.props.L10N.getStr( + "toolbox.meatballMenu.noautohide.label" + ), + type: "checkbox", + checked: this.props.disableAutohide, + onClick: this.props.toggleNoAutohide, + className: "iconic", + }) + ); + } + + // Settings + items.push( + MenuItem({ + id: "toolbox-meatball-menu-settings", + key: "settings", + label: this.props.L10N.getStr("toolbox.meatballMenu.settings.label"), + accelerator: this.props.L10N.getStr("toolbox.help.key"), + onClick: this.props.toggleOptions, + className: "iconic", + }) + ); + + items.push(hr({ key: "docs-separator" })); + + // Getting started + items.push( + MenuItem({ + id: "toolbox-meatball-menu-documentation", + key: "documentation", + label: this.props.L10N.getStr( + "toolbox.meatballMenu.documentation.label" + ), + onClick: openDevToolsDocsLink, + }) + ); + + // Give feedback + items.push( + MenuItem({ + id: "toolbox-meatball-menu-community", + key: "community", + label: this.props.L10N.getStr("toolbox.meatballMenu.community.label"), + onClick: openCommunityLink, + }) + ); + + return MenuList({ id: "toolbox-meatball-menu" }, items); + } +} + +module.exports = MeatballMenu; |