/* 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/. */
/* eslint-env browser */
"use strict" ;
const {
Component,
createFactory,
createRef,
} = require("resource://devtools/client/shared/vendor/react.js");
const PropTypes = require("resource://devtools/client/shared/vendor/react-prop-types.js");
const dom = require("resource://devtools/client/shared/vendor/react-dom-factories.js");
const Sidebar = createFactory(
require("resource://devtools/client/shared/components/Sidebar.js")
);
loader.lazyRequireGetter(
this ,
"Menu" ,
"resource://devtools/client/framework/menu.js"
);
loader.lazyRequireGetter(
this ,
"MenuItem" ,
"resource://devtools/client/framework/menu-item.js"
);
// Shortcuts
const { div } = dom;
/**
* Renders Tabbar component.
*/
class Tabbar extends Component {
static get propTypes() {
return {
children: PropTypes.array,
menuDocument: PropTypes.object,
onSelect: PropTypes.func,
showAllTabsMenu: PropTypes.bool,
allTabsMenuButtonTooltip: PropTypes.string,
activeTabId: PropTypes.string,
renderOnlySelected: PropTypes.bool,
sidebarToggleButton: PropTypes.shape({
// Set to true if collapsed.
collapsed: PropTypes.bool.isRequired,
// Tooltip text used when the button indicates expanded state.
collapsePaneTitle: PropTypes.string.isRequired,
// Tooltip text used when the button indicates collapsed state.
expandPaneTitle: PropTypes.string.isRequired,
// Click callback
onClick: PropTypes.func.isRequired,
// align toggle button to right
alignRight: PropTypes.bool,
// if set to true toggle-button rotate 90
canVerticalSplit: PropTypes.bool,
}),
};
}
static get defaultProps() {
return {
menuDocument: window.parent.document,
showAllTabsMenu: false ,
};
}
constructor(props, context) {
super (props, context);
const { activeTabId, children = [] } = props;
const tabs = this .createTabs(children);
const activeTab = tabs.findIndex(tab => tab.id === activeTabId);
this .state = {
activeTab: activeTab === -1 ? 0 : activeTab,
tabs,
};
// Array of queued tabs to add to the Tabbar.
this .queuedTabs = [];
this .createTabs = this .createTabs.bind(this );
this .addTab = this .addTab.bind(this );
this .addAllQueuedTabs = this .addAllQueuedTabs.bind(this );
this .queueTab = this .queueTab.bind(this );
this .toggleTab = this .toggleTab.bind(this );
this .removeTab = this .removeTab.bind(this );
this .select = this .select.bind(this );
this .getTabIndex = this .getTabIndex.bind(this );
this .getTabId = this .getTabId.bind(this );
this .getCurrentTabId = this .getCurrentTabId.bind(this );
this .onTabChanged = this .onTabChanged.bind(this );
this .onAllTabsMenuClick = this .onAllTabsMenuClick.bind(this );
this .renderTab = this .renderTab.bind(this );
this .tabbarRef = createRef();
}
// FIXME: https://bugzilla.mozilla.org/show_bug.cgi?id=1774507
UNSAFE_componentWillReceiveProps(nextProps) {
const { activeTabId, children = [] } = nextProps;
const tabs = this .createTabs(children);
const activeTab = tabs.findIndex(tab => tab.id === activeTabId);
if (
activeTab !== this .state.activeTab ||
children !== this .props.children
) {
this .setState({
activeTab: activeTab === -1 ? 0 : activeTab,
tabs,
});
}
}
createTabs(children) {
return children
.filter(panel => panel)
.map((panel, index) =>
Object.assign({}, children[index], {
id: panel.props.id || index,
panel,
title: panel.props.title,
})
);
}
// Public API
addTab(id, title, selected = false , panel, url, index = -1) {
const tabs = this .state.tabs.slice();
if (index >= 0) {
tabs.splice(index, 0, { id, title, panel, url });
} else {
tabs.push({ id, title, panel, url });
}
const newState = Object.assign({}, this .state, {
tabs,
});
if (selected) {
newState.activeTab = index >= 0 ? index : tabs.length - 1;
}
this .setState(newState, () => {
if (this .props.onSelect && selected) {
this .props.onSelect(id);
}
});
}
addAllQueuedTabs() {
if (!this .queuedTabs.length) {
return ;
}
const tabs = this .state.tabs.slice();
// Preselect the first sidebar tab if none was explicitly selected.
let activeTab = 0;
let activeId = this .queuedTabs[0].id;
for (const { id, index, panel, selected, title, url } of this .queuedTabs) {
if (index >= 0) {
tabs.splice(index, 0, { id, title, panel, url });
} else {
tabs.push({ id, title, panel, url });
}
if (selected) {
activeId = id;
activeTab = index >= 0 ? index : tabs.length - 1;
}
}
const newState = Object.assign({}, this .state, {
activeTab,
tabs,
});
this .setState(newState, () => {
if (this .props.onSelect) {
this .props.onSelect(activeId);
}
});
this .queuedTabs = [];
}
/**
* Queues a tab to be added. This is more performant than calling addTab for every
* single tab to be added since we will limit the number of renders happening when
* a new state is set. Once all the tabs to be added have been queued, call
* addAllQueuedTabs() to populate the TabBar with all the queued tabs.
*/
queueTab(id, title, selected = false , panel, url, index = -1) {
this .queuedTabs.push({
id,
index,
panel,
selected,
title,
url,
});
}
toggleTab(tabId, isVisible) {
const index = this .getTabIndex(tabId);
if (index < 0) {
return ;
}
const tabs = this .state.tabs.slice();
tabs[index] = Object.assign({}, tabs[index], {
isVisible,
});
this .setState(
Object.assign({}, this .state, {
tabs,
})
);
}
removeTab(tabId) {
const index = this .getTabIndex(tabId);
if (index < 0) {
return ;
}
const tabs = this .state.tabs.slice();
tabs.splice(index, 1);
let activeTab = this .state.activeTab - 1;
activeTab = activeTab === -1 ? 0 : activeTab;
this .setState(
Object.assign({}, this .state, {
activeTab,
tabs,
}),
() => {
// Select the next active tab and force the select event handler to initialize
// the panel if needed.
if (tabs.length && this .props.onSelect) {
this .props.onSelect(this .getTabId(activeTab));
}
}
);
}
select(tabId) {
const docRef = this .tabbarRef.current.ownerDocument;
const index = this .getTabIndex(tabId);
if (index < 0) {
return ;
}
const newState = Object.assign({}, this .state, {
activeTab: index,
});
const tabDomElement = docRef.querySelector(`[data-tab-index="${index}" ]`);
if (tabDomElement) {
tabDomElement.scrollIntoView();
}
this .setState(newState, () => {
if (this .props.onSelect) {
this .props.onSelect(tabId);
}
});
}
// Helpers
getTabIndex(tabId) {
let tabIndex = -1;
this .state.tabs.forEach((tab, index) => {
if (tab.id === tabId) {
tabIndex = index;
}
});
return tabIndex;
}
getTabId(index) {
return this .state.tabs[index].id;
}
getCurrentTabId() {
return this .state.tabs[this .state.activeTab].id;
}
// Event Handlers
onTabChanged(index) {
this .setState(
{
activeTab: index,
},
() => {
if (this .props.onSelect) {
this .props.onSelect(this .state.tabs[index].id);
}
}
);
}
onAllTabsMenuClick(event) {
const menu = new Menu();
const target = event.target;
// Generate list of menu items from the list of tabs.
this .state.tabs.forEach(tab => {
menu.append(
new MenuItem({
label: tab.title,
type: "checkbox" ,
checked: this .getCurrentTabId() === tab.id,
click: () => this .select(tab.id),
})
);
});
// Show a drop down menu with frames.
menu.popupAtTarget(target);
return menu;
}
// Rendering
renderTab(tab) {
if (typeof tab.panel === "function" ) {
return tab.panel({
key: tab.id,
title: tab.title,
id: tab.id,
url: tab.url,
});
}
return tab.panel;
}
render() {
const tabs = this .state.tabs.map(tab => this .renderTab(tab));
return div(
{
className: "devtools-sidebar-tabs" ,
ref: this .tabbarRef,
},
Sidebar(
{
onAllTabsMenuClick: this .onAllTabsMenuClick,
renderOnlySelected: this .props.renderOnlySelected,
showAllTabsMenu: this .props.showAllTabsMenu,
allTabsMenuButtonTooltip: this .props.allTabsMenuButtonTooltip,
sidebarToggleButton: this .props.sidebarToggleButton,
activeTab: this .state.activeTab,
onAfterChange: this .onTabChanged,
},
tabs
)
);
}
}
module.exports = Tabbar;
quality 100%
¤ Dauer der Verarbeitung: 0.5 Sekunden
¤
*© Formatika GbR, Deutschland