Files
sousa-gecko/devtools/client/framework/components/ToolboxTabs.js
T
Mantaroh Yoshinaga ad7f3b8912 Bug 1456056 - Part 2. Display chevron button only if the devtool width is narrow.r=jdescottes
This patch will allow that all buttons which accessing the tool panel is
overflowed. i.e. toolbar will display chevron button only.

MozReview-Commit-ID: GbKbAhtpYt7
2018-05-31 10:23:36 +09:00

286 lines
9.0 KiB
JavaScript

/* 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
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/ToolboxTab"));
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,
visibleToolboxButtonCount: PropTypes.number.isRequired,
L10N: PropTypes.object,
onTabsOrderUpdated: PropTypes.func.isRequired,
};
}
constructor(props) {
super(props);
this.state = {
// Array of overflowed tool id.
overflowedTabIds: [],
};
// 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(props.onTabsOrderUpdated);
}
componentDidMount() {
window.addEventListener("resize", this.resizeHandler);
this.updateCachedToolTabsWidthMap();
this.updateOverflowedTabs();
}
componentWillUpdate(nextProps, nextState) {
if (this.shouldUpdateToolboxTabs(this.props, nextProps)) {
// Force recalculate and render in this cycle if panel definition has
// changed or selected tool has changed.
nextState.overflowedTabIds = [];
}
}
componentDidUpdate(prevProps, prevState) {
if (this.shouldUpdateToolboxTabs(prevProps, this.props)) {
this.updateCachedToolTabsWidthMap();
this.updateOverflowedTabs();
}
}
componentWillUnmount() {
this._tabsOrderManager.destroy();
}
/**
* Check if two array of ids are the same or not.
*/
equalToolIdArray(prevPanels, nextPanels) {
if (prevPanels.length !== nextPanels.length) {
return false;
}
// Check panel definitions even if both of array size is same.
// For example, the case of changing the tab's order.
return prevPanels.join("-") === nextPanels.join("-");
}
/**
* Return true if we should update the overflowed tabs.
*/
shouldUpdateToolboxTabs(prevProps, nextProps) {
if (prevProps.currentToolId !== nextProps.currentToolId ||
prevProps.visibleToolboxButtonCount !== nextProps.visibleToolboxButtonCount) {
return true;
}
let prevPanels = prevProps.panelDefinitions.map(def => def.id);
let nextPanels = nextProps.panelDefinitions.map(def => def.id);
return !this.equalToolIdArray(prevPanels, nextPanels);
}
/**
* Update the Map of tool id and tool tab width.
*/
updateCachedToolTabsWidthMap() {
let thisNode = findDOMNode(this);
let utils = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
// Force a reflow before calling getBoundingWithoutFlushing on each tab.
thisNode.clientWidth;
for (let tab of thisNode.querySelectorAll(".devtools-tab")) {
let tabId = tab.id.replace("toolbox-tab-", "");
if (!this._cachedToolTabsWidthMap.has(tabId)) {
let rect = utils.getBoundsWithoutFlushing(tab);
this._cachedToolTabsWidthMap.set(tabId, rect.width);
}
}
}
/**
* Update the overflowed tab array from currently displayed tool tab.
* If calculated result is the same as the current overflowed tab array, this
* function will not update state.
*/
updateOverflowedTabs() {
let node = findDOMNode(this);
const toolboxWidth = parseInt(getComputedStyle(node).width, 10);
let { currentToolId } = this.props;
let enabledTabs = this.props.panelDefinitions.map(def => def.id);
let sumWidth = 0;
let visibleTabs = [];
for (const id of enabledTabs) {
let width = this._cachedToolTabsWidthMap.get(id);
sumWidth += width;
if (sumWidth <= toolboxWidth) {
visibleTabs.push(id);
} else {
sumWidth = sumWidth - width + CHEVRON_BUTTON_WIDTH;
// If toolbox can't display the Chevron, remove the last tool tab.
if (sumWidth > toolboxWidth) {
let removeTabId = visibleTabs.pop();
sumWidth -= this._cachedToolTabsWidthMap.get(removeTabId);
}
break;
}
}
// If the selected tab is in overflowed tabs, insert it into a visible
// toolbox.
if (!visibleTabs.includes(currentToolId) &&
enabledTabs.includes(currentToolId)) {
let selectedToolWidth = this._cachedToolTabsWidthMap.get(currentToolId);
while ((sumWidth + selectedToolWidth) > toolboxWidth &&
visibleTabs.length > 0) {
let removingToolId = visibleTabs.pop();
let removingToolWidth = this._cachedToolTabsWidthMap.get(removingToolId);
sumWidth -= removingToolWidth;
}
// If toolbox width is narrow, toolbox display only chevron menu.
// i.e. All tool tabs will overflow.
if ((sumWidth + selectedToolWidth) <= toolboxWidth) {
visibleTabs.push(currentToolId);
}
}
let willOverflowTabs = enabledTabs.filter(id => !visibleTabs.includes(id));
if (!this.equalToolIdArray(this.state.overflowedTabIds, willOverflowTabs)) {
this.setState({ overflowedTabIds: willOverflowTabs });
}
}
resizeHandler(evt) {
window.cancelIdleCallback(this._resizeTimerId);
this._resizeTimerId = window.requestIdleCallback(() => {
this.updateOverflowedTabs();
}, { timeout: 100 });
}
/**
* Render a button to access overflowed tools, displayed only when the toolbar
* presents an overflow.
*/
renderToolsChevronButton() {
let {
panelDefinitions,
selectTool,
toolbox,
L10N,
} = this.props;
return button({
className: "devtools-button tools-chevron-menu",
tabIndex: -1,
title: L10N.getStr("toolbox.allToolsButton.tooltip"),
id: "tools-chevron-menu-button",
onClick: ({ target }) => {
let menu = new Menu({
id: "tools-chevron-menupopup"
});
panelDefinitions.forEach(({id, label}) => {
if (this.state.overflowedTabIds.includes(id)) {
menu.append(new MenuItem({
click: () => {
selectTool(id, "tab_switch");
},
id: "tools-chevron-menupopup-" + id,
label,
type: "checkbox",
}));
}
});
let rect = target.getBoundingClientRect();
let screenX = target.ownerDocument.defaultView.mozInnerScreenX;
let screenY = target.ownerDocument.defaultView.mozInnerScreenY;
// Display the popup below the button.
menu.popupWithZoom(rect.left + screenX, rect.bottom + screenY, toolbox);
return menu;
},
});
}
/**
* Render all of the tabs, based on the panel definitions and builds out
* a toolbox tab for each of them. Will render the chevron button if the
* container has an overflow.
*/
render() {
let {
currentToolId,
focusButton,
focusedButton,
highlightedTools,
panelDefinitions,
selectTool,
} = this.props;
this._tabsOrderManager.setCurrentPanelDefinitions(panelDefinitions);
let tabs = panelDefinitions.map(panelDefinition => {
// Don't display overflowed tab.
if (!this.state.overflowedTabIds.includes(panelDefinition.id)) {
return ToolboxTab({
key: panelDefinition.id,
currentToolId,
focusButton,
focusedButton,
highlightedTools,
panelDefinition,
selectTool,
});
}
return null;
});
return div(
{
className: "toolbox-tabs-wrapper"
},
div(
{
className: "toolbox-tabs",
onMouseDown: (e) => this._tabsOrderManager.onMouseDown(e),
},
tabs,
(this.state.overflowedTabIds.length > 0)
? this.renderToolsChevronButton() : null
)
);
}
}
module.exports = ToolboxTabs;