diff --git a/chrome/content/zotero/components/tabBar.jsx b/chrome/content/zotero/components/tabBar.jsx
index 69a23eb97a..9767fad760 100644
--- a/chrome/content/zotero/components/tabBar.jsx
+++ b/chrome/content/zotero/components/tabBar.jsx
@@ -25,12 +25,69 @@
'use strict';
-import React, { forwardRef, useState, useRef, useImperativeHandle, useEffect, useLayoutEffect } from 'react';
+import React, { forwardRef, useState, useRef, useImperativeHandle, useEffect, useLayoutEffect, memo, useCallback } from 'react';
+import PropTypes from 'prop-types';
import cx from 'classnames';
-const { CSSIcon } = require('./icons');
+const { CSSIcon, CSSItemTypeIcon } = require('./icons');
const SCROLL_ARROW_SCROLL_BY = 222;
+const Tab = memo((props) => {
+ const { icon, id, index, isBeingDragged, isItemType, onContextMenu, onDragEnd, onDragStart, onTabClick, onTabClose, onTabMouseDown, selected, title } = props;
+
+ const handleTabMouseDown = useCallback(event => onTabMouseDown(event, id), [onTabMouseDown, id]);
+ const handleContextMenu = useCallback(event => onContextMenu(event, id), [onContextMenu, id]);
+ const handleTabClick = useCallback(event => onTabClick(event, id), [onTabClick, id]);
+ const handleDragStart = useCallback(event => onDragStart(event, id, index), [onDragStart, id, index]);
+ const handleTabClose = useCallback(event => onTabClose(event, id), [onTabClose, id]);
+
+ return (
+
+ { isItemType
+ ?
+ :
+ }
+
{title}
+
+
+
+
+ );
+});
+
+Tab.displayName = 'Tab';
+Tab.propTypes = {
+ icon: PropTypes.string,
+ id: PropTypes.string.isRequired,
+ index: PropTypes.number.isRequired,
+ isBeingDragged: PropTypes.bool.isRequired,
+ isItemType: PropTypes.bool,
+ onContextMenu: PropTypes.func.isRequired,
+ onDragEnd: PropTypes.func.isRequired,
+ onDragStart: PropTypes.func.isRequired,
+ onTabClick: PropTypes.func.isRequired,
+ onTabClose: PropTypes.func.isRequired,
+ onTabMouseDown: PropTypes.func.isRequired,
+ selected: PropTypes.bool.isRequired,
+ title: PropTypes.string.isRequired
+};
+
+
const TabBar = forwardRef(function (props, ref) {
const [tabs, setTabs] = useState([]);
const [dragging, setDragging] = useState(false);
@@ -109,7 +166,7 @@ const TabBar = forwardRef(function (props, ref) {
}
}
- function handleTabMouseDown(event, id) {
+ const handleTabMouseDown = useCallback((event, id) => {
// Don't select tab if it'll be closed with middle button click on mouse up
// or on right-click
if ([1, 2].includes(event.button)) {
@@ -121,23 +178,23 @@ const TabBar = forwardRef(function (props, ref) {
}
props.onTabSelect(id);
event.stopPropagation();
- }
+ }, [props.onTabSelect]);
- function handleContextMenu(event, id) {
+ const handleContextMenu = useCallback((event, id) => {
let { screenX, screenY } = event;
// Popup gets immediately closed without this
setTimeout(() => {
props.onContextMenu(screenX, screenY, id);
});
- }
+ }, [props.onContextMenu]);
- function handleTabClick(event, id) {
+ const handleTabClick = useCallback((event, id) => {
if (event.button === 1) {
props.onTabClose(id);
}
- }
+ }, [props.onTabClose]);
- function handleDragStart(event, id, index) {
+ const handleDragStart = useCallback((event, id, index) => {
// Library tab is not draggable
if (index === 0) {
event.preventDefault();
@@ -157,14 +214,14 @@ const TabBar = forwardRef(function (props, ref) {
setDragging(true);
// Store the current tab id
dragIDRef.current = id;
- }
+ }, []);
- function handleDragEnd() {
+ const handleDragEnd = useCallback(() => {
setDragging(false);
props.refocusReader();
- }
+ }, [props.refocusReader]);
- function handleTabBarDragOver(event) {
+ const handleTabBarDragOver = useCallback((event) => {
event.preventDefault();
event.dataTransfer.dropEffect = 'move';
// Throttle
@@ -220,15 +277,15 @@ const TabBar = forwardRef(function (props, ref) {
props.onTabMove(dragIDRef.current, index);
}
mouseMoveWaitUntil.current = Date.now() + 20;
- }
+ }, [props.onTabMove]);
- function handleTabClose(event, id) {
+ const handleTabClose = useCallback((event, id) => {
props.onTabClose(id);
event.stopPropagation();
- }
+ }, [props.onTabClose]);
- function handleWheel(event) {
+ const handleWheel = useCallback((event) => {
// Normalize wheel speed
let x = event.deltaX || event.deltaY;
if (x && event.deltaMode) {
@@ -242,54 +299,27 @@ const TabBar = forwardRef(function (props, ref) {
window.requestAnimationFrame(() => {
tabsRef.current.scrollLeft += x;
});
- }
+ }, []);
- function handleClickScrollStart() {
+ const handleClickScrollStart = useCallback(() => {
tabsRef.current.scrollTo({
left: tabsRef.current.scrollLeft - (SCROLL_ARROW_SCROLL_BY * (Zotero.rtl ? -1 : 1)),
behavior: 'smooth'
});
- }
+ }, []);
- function handleClickScrollEnd() {
+ const handleClickScrollEnd = useCallback(() => {
tabsRef.current.scrollTo({
left: tabsRef.current.scrollLeft + (SCROLL_ARROW_SCROLL_BY * (Zotero.rtl ? -1 : 1)),
behavior: 'smooth'
});
- }
+ }, []);
// Prevent maximizing/minimizing window
- function handleScrollArrowDoubleClick(event) {
+ const handleScrollArrowDoubleClick = useCallback((event) => {
event.preventDefault();
- }
+ }, []);
- function renderTab({ id, title, selected, icon }, index) {
- return (
- handleTabMouseDown(event, id)}
- onContextMenu={(event) => handleContextMenu(event, id)}
- onClick={(event) => handleTabClick(event, id)}
- onAuxClick={(event) => handleTabClick(event, id)}
- onDragStart={(event) => handleDragStart(event, id, index)}
- onDragEnd={handleDragEnd}
- tabIndex="-1"
- >
- {icon}
-
{title}
-
handleTabClose(event, id)}
- >
-
-
-
- );
- }
-
return (
- {tabs.length ? renderTab(tabs[0], 0) : null}
+ {tabs.length
+ ?
+ : null}
- {tabs.map((tab, index) => renderTab(tab, index))}
+ {tabs.map((tab, index) => )}
;
+ icon = { isItemType: false, icon: iconName };
}
}
else if (tab.data?.itemID) {
try {
let item = Zotero.Items.get(tab.data.itemID);
- icon = ;
+ icon = { isItemType: true, icon: item.getItemTypeIconName(true) };
}
catch (e) {
// item might not yet be loaded, we will get the right icon on the next update
// but until then use a default placeholder
- icon = ;
+ icon = { isItemType: true, icon: null };
}
}
@@ -119,7 +119,7 @@ var Zotero_Tabs = new function () {
type: tab.type,
title: tab.title,
selected: tab.id == this._selectedID,
- icon,
+ ...icon,
};
}));
// Disable File > Close menuitem if multiple tabs are open