Clarify and comment tab moving code
This commit is contained in:
parent
d8af5b7923
commit
d77b00bf9a
1 changed files with 38 additions and 16 deletions
|
@ -32,23 +32,29 @@ const { IconXmark } = require('./icons');
|
||||||
const TabBar = forwardRef(function (props, ref) {
|
const TabBar = forwardRef(function (props, ref) {
|
||||||
const [tabs, setTabs] = useState([]);
|
const [tabs, setTabs] = useState([]);
|
||||||
const [dragging, setDragging] = useState(false);
|
const [dragging, setDragging] = useState(false);
|
||||||
const [draggingX, setDraggingX] = useState(0);
|
const [dragMouseX, setDragMouseX] = useState(0);
|
||||||
const draggingIDRef = useRef(null);
|
const dragIDRef = useRef(null);
|
||||||
const draggingDeltaXRef = useRef();
|
const dragGrabbedDeltaXRef = useRef();
|
||||||
const tabsRef = useRef();
|
const tabsRef = useRef();
|
||||||
|
// Used to throttle mouse movement
|
||||||
const mouseMoveWaitUntil = useRef(0);
|
const mouseMoveWaitUntil = useRef(0);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => ({ setTabs }));
|
useImperativeHandle(ref, () => ({ setTabs }));
|
||||||
|
|
||||||
|
// Use offsetLeft and offsetWidth to calculate and translate tab X position
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!draggingIDRef.current) return;
|
if (!dragIDRef.current) return;
|
||||||
let tab = Array.from(tabsRef.current.children).find(x => x.dataset.id === draggingIDRef.current);
|
let tab = Array.from(tabsRef.current.children).find(x => x.dataset.id === dragIDRef.current);
|
||||||
if (tab) {
|
if (tab) {
|
||||||
let x = draggingX - tab.offsetLeft - draggingDeltaXRef.current;
|
// While the actual tab node retains its space between other tabs,
|
||||||
|
// we use CSS translation to move it to the left/right side to
|
||||||
|
// position it under the mouse
|
||||||
|
let x = dragMouseX - tab.offsetLeft - dragGrabbedDeltaXRef.current;
|
||||||
|
|
||||||
let firstTab = tabsRef.current.firstChild;
|
let firstTab = tabsRef.current.firstChild;
|
||||||
let lastTab = tabsRef.current.lastChild;
|
let lastTab = tabsRef.current.lastChild;
|
||||||
|
|
||||||
|
// Don't allow to move tab beyond the second and the last tab
|
||||||
if (Zotero.rtl) {
|
if (Zotero.rtl) {
|
||||||
if (tab.offsetLeft + x < lastTab.offsetLeft
|
if (tab.offsetLeft + x < lastTab.offsetLeft
|
||||||
|| tab.offsetLeft + tab.offsetWidth + x > firstTab.offsetLeft) {
|
|| tab.offsetLeft + tab.offsetWidth + x > firstTab.offsetLeft) {
|
||||||
|
@ -88,18 +94,24 @@ const TabBar = forwardRef(function (props, ref) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragStart(event, id, index) {
|
function handleDragStart(event, id, index) {
|
||||||
|
// Library tab is not draggable
|
||||||
if (index === 0) {
|
if (index === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.dataTransfer.effectAllowed = 'move';
|
event.dataTransfer.effectAllowed = 'move';
|
||||||
// Empty drag image
|
// We don't want the generated image from the target element,
|
||||||
|
// therefore setting an empty image
|
||||||
let img = document.createElement('img');
|
let img = document.createElement('img');
|
||||||
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
event.dataTransfer.setDragImage(img, 0, 0);
|
event.dataTransfer.setDragImage(img, 0, 0);
|
||||||
|
// Some data needs to be set, although this is not used anywhere
|
||||||
event.dataTransfer.setData('zotero/tab', id);
|
event.dataTransfer.setData('zotero/tab', id);
|
||||||
draggingDeltaXRef.current = event.clientX - event.target.offsetLeft;
|
// Store the relative mouse to tab X position where the tab was grabbed
|
||||||
|
dragGrabbedDeltaXRef.current = event.clientX - event.target.offsetLeft;
|
||||||
|
// Enable dragging
|
||||||
setDragging(true);
|
setDragging(true);
|
||||||
draggingIDRef.current = id;
|
// Store the current tab id
|
||||||
|
dragIDRef.current = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragEnd() {
|
function handleDragEnd() {
|
||||||
|
@ -109,23 +121,31 @@ const TabBar = forwardRef(function (props, ref) {
|
||||||
function handleTabBarDragOver(event) {
|
function handleTabBarDragOver(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.dataTransfer.dropEffect = 'move';
|
event.dataTransfer.dropEffect = 'move';
|
||||||
if (!draggingIDRef.current || mouseMoveWaitUntil.current > Date.now()) {
|
// Throttle
|
||||||
|
if (!dragIDRef.current || mouseMoveWaitUntil.current > Date.now()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setDraggingX(event.clientX);
|
setDragMouseX(event.clientX);
|
||||||
|
|
||||||
let tabIndex = Array.from(tabsRef.current.children).findIndex(x => x.dataset.id === draggingIDRef.current);
|
// Get the current tab DOM node
|
||||||
|
let tabIndex = Array.from(tabsRef.current.children).findIndex(x => x.dataset.id === dragIDRef.current);
|
||||||
let tab = tabsRef.current.children[tabIndex];
|
let tab = tabsRef.current.children[tabIndex];
|
||||||
|
|
||||||
|
// Calculate the center points of each tab
|
||||||
let points = Array.from(tabsRef.current.children).map((child) => {
|
let points = Array.from(tabsRef.current.children).map((child) => {
|
||||||
return child.offsetLeft + child.offsetWidth / 2;
|
return child.offsetLeft + child.offsetWidth / 2;
|
||||||
});
|
});
|
||||||
|
|
||||||
let x1 = event.clientX - draggingDeltaXRef.current;
|
// Calculate where the new tab left and right (x1, x2) side points should
|
||||||
let x2 = event.clientX - draggingDeltaXRef.current + tab.offsetWidth;
|
// be relative to the current mouse position, and take into account
|
||||||
|
// the initial relative mouse to tab position where the tab was grabbed
|
||||||
|
let x1 = event.clientX - dragGrabbedDeltaXRef.current;
|
||||||
|
let x2 = event.clientX - dragGrabbedDeltaXRef.current + tab.offsetWidth;
|
||||||
|
|
||||||
let index = null;
|
let index = null;
|
||||||
|
// Try to determine if the new tab left or right side is crossing
|
||||||
|
// the middle point of the previous or the next tab, and use its index if so
|
||||||
for (let i = 0; i < points.length - 1; i++) {
|
for (let i = 0; i < points.length - 1; i++) {
|
||||||
if (i === tabIndex || i + 1 === tabIndex) {
|
if (i === tabIndex || i + 1 === tabIndex) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -141,6 +161,8 @@ const TabBar = forwardRef(function (props, ref) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the new tab position doesn't fit between the central points
|
||||||
|
// of other tabs, check if it's moved beyond the last tab
|
||||||
if (index === null) {
|
if (index === null) {
|
||||||
let p = points[points.length - 1];
|
let p = points[points.length - 1];
|
||||||
if (Zotero.rtl && x1 < p || !Zotero.rtl && x2 > p) {
|
if (Zotero.rtl && x1 < p || !Zotero.rtl && x2 > p) {
|
||||||
|
@ -149,7 +171,7 @@ const TabBar = forwardRef(function (props, ref) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index !== null) {
|
if (index !== null) {
|
||||||
props.onTabMove(draggingIDRef.current, index);
|
props.onTabMove(dragIDRef.current, index);
|
||||||
}
|
}
|
||||||
mouseMoveWaitUntil.current = Date.now() + 20;
|
mouseMoveWaitUntil.current = Date.now() + 20;
|
||||||
}
|
}
|
||||||
|
@ -177,7 +199,7 @@ const TabBar = forwardRef(function (props, ref) {
|
||||||
<div
|
<div
|
||||||
key={id}
|
key={id}
|
||||||
data-id={id}
|
data-id={id}
|
||||||
className={cx('tab', { selected, dragging: dragging && id === draggingIDRef.current })}
|
className={cx('tab', { selected, dragging: dragging && id === dragIDRef.current })}
|
||||||
draggable={true}
|
draggable={true}
|
||||||
onMouseMove={() => handleTabMouseMove(title)}
|
onMouseMove={() => handleTabMouseMove(title)}
|
||||||
onMouseDown={(event) => handleTabMouseDown(event, id)}
|
onMouseDown={(event) => handleTabMouseDown(event, id)}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue