2018-04-19 09:41:56 +00:00
|
|
|
/* 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";
|
|
|
|
|
2018-04-19 09:46:55 +00:00
|
|
|
const Services = require("Services");
|
2018-05-02 04:15:02 +00:00
|
|
|
const Telemetry = require("devtools/client/shared/telemetry");
|
|
|
|
const TABS_REORDERED_SCALAR = "devtools.toolbox.tabs_reordered";
|
2018-04-19 09:46:55 +00:00
|
|
|
const PREFERENCE_NAME = "devtools.toolbox.tabsOrder";
|
|
|
|
|
2018-04-19 09:41:56 +00:00
|
|
|
/**
|
|
|
|
* Manage the order of devtools tabs.
|
|
|
|
*/
|
|
|
|
class ToolboxTabsOrderManager {
|
2018-04-19 09:46:55 +00:00
|
|
|
constructor(onOrderUpdated) {
|
|
|
|
this.onOrderUpdated = onOrderUpdated;
|
2018-04-26 21:29:04 +00:00
|
|
|
this.currentPanelDefinitions = [];
|
2018-04-19 09:46:55 +00:00
|
|
|
|
2018-04-19 09:41:56 +00:00
|
|
|
this.onMouseDown = this.onMouseDown.bind(this);
|
|
|
|
this.onMouseMove = this.onMouseMove.bind(this);
|
|
|
|
this.onMouseOut = this.onMouseOut.bind(this);
|
|
|
|
this.onMouseUp = this.onMouseUp.bind(this);
|
2018-04-19 09:46:55 +00:00
|
|
|
|
|
|
|
Services.prefs.addObserver(PREFERENCE_NAME, this.onOrderUpdated);
|
2018-05-02 04:15:02 +00:00
|
|
|
|
|
|
|
this.telemetry = new Telemetry();
|
2018-04-19 09:41:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
destroy() {
|
2018-04-19 09:46:55 +00:00
|
|
|
Services.prefs.removeObserver(PREFERENCE_NAME, this.onOrderUpdated);
|
2018-04-26 21:29:05 +00:00
|
|
|
|
|
|
|
// Save the reordering preference, because some tools might be removed.
|
|
|
|
const ids =
|
|
|
|
this.currentPanelDefinitions.map(definition => definition.extensionId || definition.id);
|
|
|
|
const pref = ids.join(",");
|
|
|
|
Services.prefs.setCharPref(PREFERENCE_NAME, pref);
|
|
|
|
|
2018-04-19 09:41:56 +00:00
|
|
|
this.onMouseUp();
|
|
|
|
}
|
|
|
|
|
2018-04-26 21:29:04 +00:00
|
|
|
setCurrentPanelDefinitions(currentPanelDefinitions) {
|
|
|
|
this.currentPanelDefinitions = currentPanelDefinitions;
|
2018-04-19 09:48:53 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 09:41:56 +00:00
|
|
|
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");
|
2018-04-19 09:46:55 +00:00
|
|
|
this.isOrderUpdated = false;
|
2018-04-19 09:41:56 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseMove(e) {
|
|
|
|
const tabsElement = this.toolboxTabsElement;
|
|
|
|
const diffPageX = e.pageX - this.previousPageX;
|
|
|
|
const dragTargetCenterX =
|
|
|
|
this.dragTarget.offsetLeft + diffPageX + this.dragTarget.clientWidth / 2;
|
|
|
|
let isDragTargetPreviousSibling = false;
|
|
|
|
|
|
|
|
for (const tabElement of tabsElement.querySelectorAll(".devtools-tab")) {
|
|
|
|
if (tabElement === this.dragTarget) {
|
|
|
|
isDragTargetPreviousSibling = true;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
const anotherElementCenterX =
|
|
|
|
tabElement.offsetLeft + tabElement.clientWidth / 2;
|
|
|
|
|
|
|
|
if (Math.abs(dragTargetCenterX - anotherElementCenterX) <
|
|
|
|
tabElement.clientWidth / 3) {
|
|
|
|
const xBefore = this.dragTarget.offsetLeft;
|
|
|
|
|
|
|
|
if (isDragTargetPreviousSibling) {
|
|
|
|
tabsElement.insertBefore(this.dragTarget, tabElement.nextSibling);
|
|
|
|
} else {
|
|
|
|
tabsElement.insertBefore(this.dragTarget, tabElement);
|
|
|
|
}
|
|
|
|
|
|
|
|
const xAfter = this.dragTarget.offsetLeft;
|
|
|
|
this.dragStartX += xAfter - xBefore;
|
2018-04-19 09:46:55 +00:00
|
|
|
|
|
|
|
this.isOrderUpdated = true;
|
2018-04-19 09:41:56 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let distance = e.pageX - this.dragStartX;
|
|
|
|
|
|
|
|
if ((!this.dragTarget.previousSibling && distance < 0) ||
|
2018-04-19 09:48:53 +00:00
|
|
|
((!this.dragTarget.nextSibling ||
|
|
|
|
this.dragTarget.nextSibling.id === "tools-chevron-menu-button") &&
|
|
|
|
distance > 0)) {
|
2018-04-19 09:41:56 +00:00
|
|
|
// If the drag target is already edge of the tabs and the mouse will make the
|
|
|
|
// element to move to same direction more, keep the position.
|
|
|
|
distance = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.dragTarget.style.left = `${ distance }px`;
|
|
|
|
this.previousPageX = e.pageX;
|
|
|
|
}
|
|
|
|
|
|
|
|
onMouseOut(e) {
|
2018-07-12 18:35:11 +00:00
|
|
|
const documentElement = this.dragTarget.ownerDocument.documentElement;
|
|
|
|
if (e.pageX <= 0 || documentElement.clientWidth <= e.pageX ||
|
|
|
|
e.pageY <= 0 || documentElement.clientHeight <= e.pageY) {
|
2018-04-19 09:41:56 +00:00
|
|
|
this.onMouseUp();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-04-19 09:46:55 +00:00
|
|
|
if (this.isOrderUpdated) {
|
2018-04-26 21:29:04 +00:00
|
|
|
const tabs = [...this.toolboxTabsElement.querySelectorAll(".devtools-tab")];
|
|
|
|
const tabIds = tabs.map(tab => tab.dataset.extensionId || tab.dataset.id);
|
|
|
|
// Concat the overflowed tabs id since they are not contained in visible tabs.
|
|
|
|
// The overflowed tabs cannot be reordered so we just append the id from current
|
|
|
|
// panel definitions on their order.
|
|
|
|
const overflowedTabIds =
|
|
|
|
this.currentPanelDefinitions
|
|
|
|
.filter(definition => !tabs.some(tab => tab.dataset.id === definition.id))
|
|
|
|
.map(definition => definition.extensionId || definition.id);
|
|
|
|
const pref = tabIds.concat(overflowedTabIds).join(",");
|
2018-04-19 09:46:55 +00:00
|
|
|
Services.prefs.setCharPref(PREFERENCE_NAME, pref);
|
2018-05-02 04:15:02 +00:00
|
|
|
|
|
|
|
// Log which tabs reordered. The question we want to answer is:
|
|
|
|
// "How frequently are the tabs re-ordered, also which tabs get re-ordered?"
|
|
|
|
const toolId = this.dragTarget.dataset.extensionId || this.dragTarget.dataset.id;
|
2018-05-09 08:53:49 +00:00
|
|
|
this.telemetry.keyedScalarAdd(TABS_REORDERED_SCALAR, toolId, 1);
|
2018-04-19 09:46:55 +00:00
|
|
|
}
|
|
|
|
|
2018-04-19 09:41:56 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-19 09:46:55 +00:00
|
|
|
function sortPanelDefinitions(definitions) {
|
|
|
|
const pref = Services.prefs.getCharPref(PREFERENCE_NAME, "");
|
|
|
|
const toolIds = pref.split(",");
|
|
|
|
|
|
|
|
return definitions.sort((a, b) => {
|
2018-04-26 21:29:04 +00:00
|
|
|
let orderA = toolIds.indexOf(a.extensionId || a.id);
|
|
|
|
let orderB = toolIds.indexOf(b.extensionId || b.id);
|
2018-04-19 09:46:55 +00:00
|
|
|
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;
|