summaryrefslogtreecommitdiffstats
path: root/devtools/client/framework/components/MeatballMenu.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/framework/components/MeatballMenu.js')
-rw-r--r--devtools/client/framework/components/MeatballMenu.js241
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;