Bug 1226272 - Part 2: Implement saving and loading the tabs order preference. r?jdescottes
MozReview-Commit-ID: JoVcnPwvVW7
--- a/devtools/client/framework/components/toolbox-tabs.js
+++ b/devtools/client/framework/components/toolbox-tabs.js
@@ -7,33 +7,34 @@ const { Component, createFactory } = req
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const {findDOMNode} = require("devtools/client/shared/vendor/react-dom");
const {button, div} = dom;
const Menu = require("devtools/client/framework/menu");
const MenuItem = require("devtools/client/framework/menu-item");
const ToolboxTab = createFactory(require("devtools/client/framework/components/toolbox-tab"));
-const ToolboxTabsOrderManager = require("devtools/client/framework/toolbox-tabs-order-manager");
+const { ToolboxTabsOrderManager } = require("devtools/client/framework/toolbox-tabs-order-manager");
// 26px is chevron devtools button width.(i.e. tools-chevronmenu)
const CHEVRON_BUTTON_WIDTH = 26;
class ToolboxTabs extends Component {
// See toolbox-toolbar propTypes for details on the props used here.
static get propTypes() {
return {
currentToolId: PropTypes.string,
focusButton: PropTypes.func,
focusedButton: PropTypes.string,
highlightedTools: PropTypes.object,
panelDefinitions: PropTypes.array,
selectTool: PropTypes.func,
toolbox: PropTypes.object,
L10N: PropTypes.object,
+ onTabsOrderUpdated: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
// Array of overflowed tool id.
@@ -43,17 +44,17 @@ class ToolboxTabs extends Component {
// Map with tool Id and its width size. This lifecycle is out of React's
// lifecycle. If a tool is registered, ToolboxTabs will add target tool id
// to this map. ToolboxTabs will never remove tool id from this cache.
this._cachedToolTabsWidthMap = new Map();
this._resizeTimerId = null;
this.resizeHandler = this.resizeHandler.bind(this);
- this._tabsOrderManager = new ToolboxTabsOrderManager();
+ this._tabsOrderManager = new ToolboxTabsOrderManager(props.onTabsOrderUpdated);
}
componentDidMount() {
window.addEventListener("resize", this.resizeHandler);
this.updateCachedToolTabsWidthMap();
this.updateOverflowedTabs();
}
--- a/devtools/client/framework/components/toolbox-toolbar.js
+++ b/devtools/client/framework/components/toolbox-toolbar.js
@@ -67,16 +67,18 @@ class ToolboxToolbar extends Component {
focusButton: PropTypes.func,
// Hold off displaying the toolbar until enough information is ready for
// it to render nicely.
canRender: PropTypes.bool,
// Localization interface.
L10N: PropTypes.object,
// The devtools toolbox
toolbox: PropTypes.object,
+ // Call back function to detect tabs order updated.
+ onTabsOrderUpdated: PropTypes.func.isRequired,
};
}
/**
* The render function is kept fairly short for maintainability. See the individual
* render functions for how each of the sections is rendered.
*/
render() {
--- a/devtools/client/framework/toolbox-tabs-order-manager.js
+++ b/devtools/client/framework/toolbox-tabs-order-manager.js
@@ -1,39 +1,48 @@
/* 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 Services = require("Services");
+const PREFERENCE_NAME = "devtools.toolbox.tabsOrder";
+
/**
* Manage the order of devtools tabs.
*/
class ToolboxTabsOrderManager {
- constructor() {
+ constructor(onOrderUpdated) {
+ this.onOrderUpdated = onOrderUpdated;
+
this.onMouseDown = this.onMouseDown.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseOut = this.onMouseOut.bind(this);
this.onMouseUp = this.onMouseUp.bind(this);
+
+ Services.prefs.addObserver(PREFERENCE_NAME, this.onOrderUpdated);
}
destroy() {
+ Services.prefs.removeObserver(PREFERENCE_NAME, this.onOrderUpdated);
this.onMouseUp();
}
onMouseDown(e) {
if (!e.target.classList.contains("devtools-tab")) {
return;
}
this.dragStartX = e.pageX;
this.dragTarget = e.target;
this.previousPageX = e.pageX;
this.toolboxContainerElement = this.dragTarget.closest("#toolbox-container");
this.toolboxTabsElement = this.dragTarget.closest(".toolbox-tabs");
+ this.isOrderUpdated = false;
this.dragTarget.ownerDocument.addEventListener("mousemove", this.onMouseMove);
this.dragTarget.ownerDocument.addEventListener("mouseout", this.onMouseOut);
this.dragTarget.ownerDocument.addEventListener("mouseup", this.onMouseUp);
this.toolboxContainerElement.classList.add("tabs-reordering");
}
@@ -60,16 +69,18 @@ class ToolboxTabsOrderManager {
if (isDragTargetPreviousSibling) {
tabsElement.insertBefore(this.dragTarget, tabElement.nextSibling);
} else {
tabsElement.insertBefore(this.dragTarget, tabElement);
}
const xAfter = this.dragTarget.offsetLeft;
this.dragStartX += xAfter - xBefore;
+
+ this.isOrderUpdated = true;
break;
}
}
let distance = e.pageX - this.dragStartX;
if ((!this.dragTarget.previousSibling && distance < 0) ||
(!this.dragTarget.nextSibling && distance > 0)) {
@@ -92,21 +103,53 @@ class ToolboxTabsOrderManager {
onMouseUp() {
if (!this.dragTarget) {
// The case in here has two type:
// 1. Although destroy method was called, it was not during reordering.
// 2. Although mouse event occur, destroy method was called during reordering.
return;
}
+ if (this.isOrderUpdated) {
+ const ids =
+ [...this.toolboxTabsElement.querySelectorAll(".devtools-tab")]
+ .map(tabElement => tabElement.dataset.id);
+ const pref = ids.join(",");
+ Services.prefs.setCharPref(PREFERENCE_NAME, pref);
+ }
+
this.dragTarget.ownerDocument.removeEventListener("mousemove", this.onMouseMove);
this.dragTarget.ownerDocument.removeEventListener("mouseout", this.onMouseOut);
this.dragTarget.ownerDocument.removeEventListener("mouseup", this.onMouseUp);
this.toolboxContainerElement.classList.remove("tabs-reordering");
this.dragTarget.style.left = null;
this.dragTarget = null;
this.toolboxContainerElement = null;
this.toolboxTabsElement = null;
}
}
-module.exports = ToolboxTabsOrderManager;
+function sortPanelDefinitions(definitions) {
+ const pref = Services.prefs.getCharPref(PREFERENCE_NAME, "");
+
+ if (!pref) {
+ definitions.sort(definition => {
+ return -1 * (definition.ordinal == undefined || definition.ordinal < 0
+ ? Number.MAX_VALUE
+ : definition.ordinal
+ );
+ });
+ }
+
+ const toolIds = pref.split(",");
+
+ return definitions.sort((a, b) => {
+ let orderA = toolIds.indexOf(a.id);
+ let orderB = toolIds.indexOf(b.id);
+ orderA = orderA < 0 ? Number.MAX_VALUE : orderA;
+ orderB = orderB < 0 ? Number.MAX_VALUE : orderB;
+ return orderA - orderB;
+ });
+}
+
+module.exports.ToolboxTabsOrderManager = ToolboxTabsOrderManager;
+module.exports.sortPanelDefinitions = sortPanelDefinitions;
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -71,16 +71,18 @@ loader.lazyRequireGetter(this, "viewSour
loader.lazyRequireGetter(this, "StyleSheetsFront",
"devtools/shared/fronts/stylesheets", true);
loader.lazyRequireGetter(this, "buildHarLog",
"devtools/client/netmonitor/src/har/har-builder-utils", true);
loader.lazyRequireGetter(this, "getKnownDeviceFront",
"devtools/shared/fronts/device", true);
loader.lazyRequireGetter(this, "NetMonitorAPI",
"devtools/client/netmonitor/src/api", true);
+loader.lazyRequireGetter(this, "sortPanelDefinitions",
+ "devtools/client/framework/toolbox-tabs-order-manager", true);
loader.lazyGetter(this, "domNodeConstants", () => {
return require("devtools/shared/dom-node-constants");
});
loader.lazyGetter(this, "registerHarOverlay", () => {
return require("devtools/client/netmonitor/src/har/toolbox-overlay").register;
});
@@ -142,16 +144,17 @@ function Toolbox(target, selectedTool, h
this._applyServiceWorkersTestingSettings =
this._applyServiceWorkersTestingSettings.bind(this);
this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
this._onFocus = this._onFocus.bind(this);
this._onBrowserMessage = this._onBrowserMessage.bind(this);
this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
this._updateTextBoxMenuItems = this._updateTextBoxMenuItems.bind(this);
this._onPerformanceFrontEvent = this._onPerformanceFrontEvent.bind(this);
+ this._onTabsOrderUpdated = this._onTabsOrderUpdated.bind(this);
this._onToolbarFocus = this._onToolbarFocus.bind(this);
this._onToolbarArrowKeypress = this._onToolbarArrowKeypress.bind(this);
this._onPickerClick = this._onPickerClick.bind(this);
this._onPickerKeypress = this._onPickerKeypress.bind(this);
this._onPickerStarted = this._onPickerStarted.bind(this);
this._onPickerStopped = this._onPickerStopped.bind(this);
this._onInspectObject = this._onInspectObject.bind(this);
this._onNewSelectedNodeFront = this._onNewSelectedNodeFront.bind(this);
@@ -258,23 +261,18 @@ Toolbox.prototype = {
}
},
/**
* Combines the built-in panel definitions and the additional tool definitions that
* can be set by add-ons.
*/
_combineAndSortPanelDefinitions() {
- const definitions = [...this._panelDefinitions, ...this.getVisibleAdditionalTools()];
- definitions.sort(definition => {
- return -1 * (definition.ordinal == undefined || definition.ordinal < 0
- ? MAX_ORDINAL
- : definition.ordinal
- );
- });
+ let definitions = [...this._panelDefinitions, ...this.getVisibleAdditionalTools()];
+ definitions = sortPanelDefinitions(definitions);
this.component.setPanelDefinitions(definitions);
},
lastUsedToolId: null,
/**
* Returns a *copy* of the _toolPanels collection.
*
@@ -1144,17 +1142,18 @@ Toolbox.prototype = {
const element = this.React.createElement(this.ToolboxController, {
L10N,
currentToolId: this.currentToolId,
selectTool: this.selectTool,
toggleSplitConsole: this.toggleSplitConsole,
toggleNoAutohide: this.toggleNoAutohide,
closeToolbox: this.destroy,
focusButton: this._onToolbarFocus,
- toolbox: this
+ toolbox: this,
+ onTabsOrderUpdated: this._onTabsOrderUpdated,
});
this.component = this.ReactDOM.render(element, this._componentMount);
},
/**
* Reset tabindex attributes across all focusable elements inside the toolbar.
* Only have one element with tabindex=0 at a time to make sure that tabbing
@@ -1910,16 +1909,20 @@ Toolbox.prototype = {
if (originalTarget.nodeType !== 1 ||
originalTarget.baseURI === webconsoleURL) {
return;
}
this._lastFocusedElement = originalTarget;
},
+ _onTabsOrderUpdated: function() {
+ this._combineAndSortPanelDefinitions();
+ },
+
/**
* Opens the split console.
*
* @returns {Promise} a promise that resolves once the tool has been
* loaded and focused.
*/
openSplitConsole: function() {
this._splitConsole = true;
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -24,16 +24,17 @@ pref("devtools.toolbox.footer.height", 2
pref("devtools.toolbox.sidebar.width", 500);
pref("devtools.toolbox.host", "bottom");
pref("devtools.toolbox.previousHost", "side");
pref("devtools.toolbox.selectedTool", "webconsole");
pref("devtools.toolbox.sideEnabled", true);
pref("devtools.toolbox.zoomValue", "1");
pref("devtools.toolbox.splitconsoleEnabled", false);
pref("devtools.toolbox.splitconsoleHeight", 100);
+pref("devtools.toolbox.tabsOrder", "");
// Toolbox Button preferences
pref("devtools.command-button-pick.enabled", true);
pref("devtools.command-button-frames.enabled", true);
pref("devtools.command-button-splitconsole.enabled", true);
pref("devtools.command-button-paintflashing.enabled", false);
pref("devtools.command-button-scratchpad.enabled", false);
pref("devtools.command-button-responsive.enabled", true);