MeatballMenu.js (9729B)
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 SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsole.enabled"; 7 8 const { 9 PureComponent, 10 createFactory, 11 } = require("resource://devtools/client/shared/vendor/react.mjs"); 12 const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.mjs"); 13 const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js"); 14 const { hr } = dom; 15 16 loader.lazyGetter(this, "MenuItem", function () { 17 return createFactory( 18 require("resource://devtools/client/shared/components/menu/MenuItem.js") 19 ); 20 }); 21 loader.lazyGetter(this, "MenuList", function () { 22 return createFactory( 23 require("resource://devtools/client/shared/components/menu/MenuList.js") 24 ); 25 }); 26 27 loader.lazyRequireGetter( 28 this, 29 "openDocLink", 30 "resource://devtools/client/shared/link.js", 31 true 32 ); 33 loader.lazyRequireGetter( 34 this, 35 "assert", 36 "resource://devtools/shared/DevToolsUtils.js", 37 true 38 ); 39 40 const openDevToolsDocsLink = () => { 41 openDocLink("https://firefox-source-docs.mozilla.org/devtools-user/"); 42 }; 43 44 const openCommunityLink = () => { 45 openDocLink( 46 "https://discourse.mozilla.org/c/devtools?utm_source=devtools&utm_medium=tabbar-menu" 47 ); 48 }; 49 50 class MeatballMenu extends PureComponent { 51 static get propTypes() { 52 return { 53 // The id of the currently selected tool, e.g. "inspector" 54 currentToolId: PropTypes.string, 55 56 // List of possible docking options. 57 hostTypes: PropTypes.arrayOf( 58 PropTypes.shape({ 59 position: PropTypes.string.isRequired, 60 switchHost: PropTypes.func.isRequired, 61 }) 62 ), 63 64 // Current docking type. Typically one of the position values in 65 // |hostTypes| but this is not always the case (e.g. for "browsertoolbox"). 66 currentHostType: PropTypes.string, 67 68 // Is the split console currently visible? 69 isSplitConsoleActive: PropTypes.bool, 70 71 // Are we disabling the behavior where pop-ups are automatically closed 72 // when clicking outside them? 73 // 74 // This is a tri-state value that may be true/false or undefined where 75 // undefined means that the option is not relevant in this context 76 // (i.e. we're not in a browser toolbox). 77 disableAutohide: PropTypes.bool, 78 79 // Apply a pseudo-locale to the Firefox UI. This is only available in the browser 80 // toolbox. This value can be undefined, "accented", "bidi", "none". 81 pseudoLocale: PropTypes.string, 82 83 // Function to turn the options panel on / off. 84 toggleOptions: PropTypes.func.isRequired, 85 86 // Function to turn the split console on / off. 87 toggleSplitConsole: PropTypes.func, 88 89 // Function to turn the disable pop-up autohide behavior on / off. 90 toggleNoAutohide: PropTypes.func, 91 92 // Manage the pseudo-localization for the Firefox UI. 93 // https://firefox-source-docs.mozilla.org/l10n/fluent/tutorial.html#manually-testing-ui-with-pseudolocalization 94 disablePseudoLocale: PropTypes.func, 95 enableAccentedPseudoLocale: PropTypes.func, 96 enableBidiPseudoLocale: PropTypes.func, 97 98 // Bug 1709191 - The help shortcut key is localized without Fluent, and still needs 99 // to be migrated. This is the only remaining use of the legacy L10N object. 100 // Everything else should prefer the Fluent API. 101 L10N: PropTypes.object.isRequired, 102 103 // Callback function that will be invoked any time the component contents 104 // update in such a way that its bounding box might change. 105 onResize: PropTypes.func, 106 }; 107 } 108 109 componentDidUpdate(prevProps) { 110 if (!this.props.onResize) { 111 return; 112 } 113 114 // We are only expecting the following kinds of dynamic changes when a popup 115 // is showing: 116 // 117 // - The "Disable pop-up autohide" menu item being added after the Browser 118 // Toolbox is connected. 119 // - The pseudo locale options being added after the Browser Toolbox is connected. 120 // - The split console label changing between "Show Split Console" and "Hide 121 // Split Console". 122 // - The "Show/Hide Split Console" entry being added removed or removed. 123 // 124 // The latter two cases are only likely to be noticed when "Disable pop-up 125 // autohide" is active, but for completeness we handle them here. 126 const didChange = 127 typeof this.props.disableAutohide !== typeof prevProps.disableAutohide || 128 this.props.pseudoLocale !== prevProps.pseudoLocale || 129 this.props.currentToolId !== prevProps.currentToolId || 130 this.props.isSplitConsoleActive !== prevProps.isSplitConsoleActive; 131 132 if (didChange) { 133 this.props.onResize(); 134 } 135 } 136 137 render() { 138 const items = []; 139 140 // Dock options 141 for (const hostType of this.props.hostTypes) { 142 // This is more verbose than it needs to be but lets us easily search for 143 // l10n entities. 144 let l10nID; 145 switch (hostType.position) { 146 case "window": 147 l10nID = "toolbox-meatball-menu-dock-separate-window-label"; 148 break; 149 150 case "bottom": 151 l10nID = "toolbox-meatball-menu-dock-bottom-label"; 152 break; 153 154 case "left": 155 l10nID = "toolbox-meatball-menu-dock-left-label"; 156 break; 157 158 case "right": 159 l10nID = "toolbox-meatball-menu-dock-right-label"; 160 break; 161 162 default: 163 assert(false, `Unexpected hostType.position: ${hostType.position}`); 164 break; 165 } 166 167 items.push( 168 MenuItem({ 169 id: `toolbox-meatball-menu-dock-${hostType.position}`, 170 key: `dock-${hostType.position}`, 171 l10nID, 172 onClick: hostType.switchHost, 173 checked: hostType.position === this.props.currentHostType, 174 className: "iconic", 175 }) 176 ); 177 } 178 179 if (items.length) { 180 items.push(hr({ key: "dock-separator" })); 181 } 182 183 // Split console 184 if (this.props.currentToolId !== "webconsole") { 185 const isSplitConsoleEnabled = Services.prefs.getBoolPref( 186 SPLITCONSOLE_ENABLED_PREF, 187 true 188 ); 189 190 if (isSplitConsoleEnabled) { 191 const l10nID = this.props.isSplitConsoleActive 192 ? "toolbox-meatball-menu-hideconsole-label" 193 : "toolbox-meatball-menu-splitconsole-label"; 194 195 items.push( 196 MenuItem({ 197 id: "toolbox-meatball-menu-splitconsole", 198 key: "splitconsole", 199 l10nID, 200 accelerator: "Esc", 201 onClick: this.props.toggleSplitConsole, 202 className: "iconic", 203 }) 204 ); 205 } 206 } 207 208 // Settings 209 items.push( 210 MenuItem({ 211 id: "toolbox-meatball-menu-settings", 212 key: "settings", 213 l10nID: "toolbox-meatball-menu-settings-label", 214 // Bug 1709191 - The help key is localized without Fluent, and still needs to 215 // be migrated. 216 accelerator: this.props.L10N.getStr("toolbox.help.key"), 217 onClick: this.props.toggleOptions, 218 className: "iconic", 219 }) 220 ); 221 222 if ( 223 typeof this.props.disableAutohide !== "undefined" || 224 typeof this.props.pseudoLocale !== "undefined" 225 ) { 226 items.push(hr({ key: "docs-separator-1" })); 227 } 228 229 // Disable pop-up autohide 230 // 231 // If |disableAutohide| is undefined, it means this feature is not available 232 // in this context. 233 if (typeof this.props.disableAutohide !== "undefined") { 234 items.push( 235 MenuItem({ 236 id: "toolbox-meatball-menu-noautohide", 237 key: "noautohide", 238 l10nID: "toolbox-meatball-menu-noautohide-label", 239 type: "checkbox", 240 checked: this.props.disableAutohide, 241 onClick: this.props.toggleNoAutohide, 242 className: "iconic", 243 }) 244 ); 245 } 246 247 // Pseudo-locales. 248 if (typeof this.props.pseudoLocale !== "undefined") { 249 const { 250 pseudoLocale, 251 enableAccentedPseudoLocale, 252 enableBidiPseudoLocale, 253 disablePseudoLocale, 254 } = this.props; 255 items.push( 256 MenuItem({ 257 id: "toolbox-meatball-menu-pseudo-locale-accented", 258 key: "pseudo-locale-accented", 259 l10nID: "toolbox-meatball-menu-pseudo-locale-accented", 260 type: "checkbox", 261 checked: pseudoLocale === "accented", 262 onClick: 263 pseudoLocale === "accented" 264 ? disablePseudoLocale 265 : enableAccentedPseudoLocale, 266 className: "iconic", 267 }), 268 MenuItem({ 269 id: "toolbox-meatball-menu-pseudo-locale-bidi", 270 key: "pseudo-locale-bidi", 271 l10nID: "toolbox-meatball-menu-pseudo-locale-bidi", 272 type: "checkbox", 273 checked: pseudoLocale === "bidi", 274 onClick: 275 pseudoLocale === "bidi" 276 ? disablePseudoLocale 277 : enableBidiPseudoLocale, 278 className: "iconic", 279 }) 280 ); 281 } 282 283 items.push(hr({ key: "docs-separator-2" })); 284 285 // Getting started 286 items.push( 287 MenuItem({ 288 id: "toolbox-meatball-menu-documentation", 289 key: "documentation", 290 l10nID: "toolbox-meatball-menu-documentation-label", 291 onClick: openDevToolsDocsLink, 292 }) 293 ); 294 295 // Give feedback 296 items.push( 297 MenuItem({ 298 id: "toolbox-meatball-menu-community", 299 key: "community", 300 l10nID: "toolbox-meatball-menu-community-label", 301 onClick: openCommunityLink, 302 }) 303 ); 304 305 return MenuList({ id: "toolbox-meatball-menu" }, items); 306 } 307 } 308 309 module.exports = MeatballMenu;