mirror of
https://github.com/zen-browser/desktop.git
synced 2025-12-25 23:59:05 +00:00
feat: Add support for multi tabs dragging, b=no-bug, c=tabs
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/content/drag-and-drop.js b/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
index 97b931c3c7385a52d20204369fcf6d6999053687..6c24d636459d1d83b6d8dcf8157b3f25b0158436 100644
|
||||
index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da83292312996a73c 100644
|
||||
--- a/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
+++ b/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
@@ -32,6 +32,9 @@
|
||||
@@ -222,87 +222,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..6c24d636459d1d83b6d8dcf8157b3f25
|
||||
let isPinned = tab.pinned;
|
||||
let numPinned = gBrowser.pinnedTabCount;
|
||||
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
|
||||
@@ -1608,8 +1641,9 @@
|
||||
|
||||
_animateExpandedPinnedTabMove(event) {
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
+ let zenFakeTab = this._invisibleTempTab || draggedTab;
|
||||
let dragData = draggedTab._dragData;
|
||||
- let movingTabs = dragData.movingTabs;
|
||||
+ let movingTabs = this._invisibleTempTab ? [this._invisibleTempTab] : dragData.movingTabs;
|
||||
|
||||
dragData.animLastScreenX ??= dragData.screenX;
|
||||
dragData.animLastScreenY ??= dragData.screenY;
|
||||
@@ -1624,10 +1658,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
- let tabs = this._tabbrowserTabs.visibleTabs.slice(
|
||||
- 0,
|
||||
- gBrowser.pinnedTabCount
|
||||
- );
|
||||
+ let tabs = this._tabbrowserTabs.ariaFocusableItems.slice(0, gBrowser._numZenEssentials);
|
||||
|
||||
let directionX = screenX > dragData.animLastScreenX;
|
||||
let directionY = screenY > dragData.animLastScreenY;
|
||||
@@ -1635,7 +1666,9 @@
|
||||
dragData.animLastScreenX = screenX;
|
||||
|
||||
let { width: tabWidth, height: tabHeight } =
|
||||
- draggedTab.getBoundingClientRect();
|
||||
+ zenFakeTab.getBoundingClientRect();
|
||||
+ tabWidth += 4; // Add 4px to account for the gap
|
||||
+ tabHeight += 4;
|
||||
let shiftSizeX = tabWidth * movingTabs.length;
|
||||
let shiftSizeY = tabHeight;
|
||||
dragData.tabWidth = tabWidth;
|
||||
@@ -1672,8 +1705,8 @@
|
||||
let lastBoundX =
|
||||
lastTabInRow.screenX +
|
||||
lastTabInRow.getBoundingClientRect().width -
|
||||
- (lastMovingTabScreenX + tabWidth);
|
||||
- let lastBoundY = periphery.screenY - (lastMovingTabScreenY + tabHeight);
|
||||
+ (lastMovingTabScreenX + tabWidth) + 4;
|
||||
+ let lastBoundY = lastTab.screenY - lastMovingTabScreenY;
|
||||
translateX = Math.min(Math.max(translateX, firstBoundX), lastBoundX);
|
||||
translateY = Math.min(Math.max(translateY, firstBoundY), lastBoundY);
|
||||
|
||||
@@ -1704,7 +1737,6 @@
|
||||
// * We're doing a binary search in order to reduce the amount of
|
||||
// tabs we need to check.
|
||||
|
||||
- tabs = tabs.filter(t => !movingTabs.includes(t) || t == draggedTab);
|
||||
let firstTabCenterX = firstMovingTabScreenX + translateX + tabWidth / 2;
|
||||
let lastTabCenterX = lastMovingTabScreenX + translateX + tabWidth / 2;
|
||||
let tabCenterX = directionX ? lastTabCenterX : firstTabCenterX;
|
||||
@@ -1716,7 +1748,7 @@
|
||||
|
||||
let getTabShift = (tab, dropIndex) => {
|
||||
if (
|
||||
- tab.elementIndex < draggedTab.elementIndex &&
|
||||
+ tab.elementIndex < zenFakeTab.elementIndex &&
|
||||
tab.elementIndex >= dropIndex
|
||||
) {
|
||||
// If tab is at the end of a row, shift back and down
|
||||
@@ -1733,7 +1765,7 @@
|
||||
return [RTL_UI ? -shiftSizeX : shiftSizeX, 0];
|
||||
}
|
||||
if (
|
||||
- tab.elementIndex > draggedTab.elementIndex &&
|
||||
+ tab.elementIndex > zenFakeTab.elementIndex &&
|
||||
tab.elementIndex < dropIndex
|
||||
) {
|
||||
// If tab is not index 0 and at the start of a row, shift across and up
|
||||
@@ -1759,7 +1791,7 @@
|
||||
dragData.animDropElementIndex ?? movingTabs[0].elementIndex;
|
||||
while (low <= high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
- if (tabs[mid] == draggedTab && ++mid > high) {
|
||||
+ if (tabs[mid] == zenFakeTab && ++mid > high) {
|
||||
break;
|
||||
}
|
||||
let [shiftX, shiftY] = getTabShift(tabs[mid], oldIndex);
|
||||
@@ -2457,7 +2489,7 @@
|
||||
@@ -2457,7 +2490,7 @@
|
||||
tab.style.left = "";
|
||||
tab.style.top = "";
|
||||
tab.style.maxWidth = "";
|
||||
@@ -311,7 +231,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..6c24d636459d1d83b6d8dcf8157b3f25
|
||||
}
|
||||
for (let label of draggedTabDocument.getElementsByClassName(
|
||||
"tab-group-label-container"
|
||||
@@ -2467,7 +2499,7 @@
|
||||
@@ -2467,7 +2500,7 @@
|
||||
label.style.left = "";
|
||||
label.style.top = "";
|
||||
label.style.maxWidth = "";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/tabbrowser/content/tab.js b/browser/components/tabbrowser/content/tab.js
|
||||
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7600f564a 100644
|
||||
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..69af8c986add4f74f1c3105ccd6e5804fd33b80f 100644
|
||||
--- a/browser/components/tabbrowser/content/tab.js
|
||||
+++ b/browser/components/tabbrowser/content/tab.js
|
||||
@@ -21,6 +21,7 @@
|
||||
@@ -101,6 +101,15 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7
|
||||
}
|
||||
|
||||
get splitview() {
|
||||
@@ -446,7 +464,7 @@
|
||||
// to detach tabs. Ensure that we do not show the drag image returning
|
||||
// to its point of origin when this happens, as it makes the drag
|
||||
// finishing feel very slow.
|
||||
- event.dataTransfer.mozShowFailAnimation = false;
|
||||
+ event.dataTransfer.mozShowFailAnimation = true;
|
||||
if (event.eventPhase == Event.CAPTURING_PHASE) {
|
||||
this.style.MozUserFocus = "";
|
||||
} else if (
|
||||
@@ -473,6 +491,8 @@
|
||||
this.style.MozUserFocus = "ignore";
|
||||
} else if (
|
||||
|
||||
@@ -76,11 +76,55 @@
|
||||
super.startTabDrag(event, tab, ...args);
|
||||
const dt = event.dataTransfer;
|
||||
|
||||
const { offsetX, offsetY } = this.#getDragImageOffset(event, tab);
|
||||
this.originalDragImageArgs = [tab, offsetX, offsetY];
|
||||
const draggingTabs = tab.multiselected ? gBrowser.selectedTabs : [tab];
|
||||
const { offsetX, offsetY } = this.#getDragImageOffset(event, tab, draggingTabs);
|
||||
const dragImage = this.#createDragImageForTabs(draggingTabs);
|
||||
this.originalDragImageArgs = [dragImage, offsetX, offsetY];
|
||||
dt.setDragImage(...this.originalDragImageArgs);
|
||||
}
|
||||
|
||||
#createDragImageForTabs(movingTabs) {
|
||||
const periphery = gZenWorkspaces.activeWorkspaceElement.querySelector(
|
||||
'#tabbrowser-arrowscrollbox-periphery'
|
||||
);
|
||||
const wrapper = document.createElement('div');
|
||||
const tabRect = window.windowUtils.getBoundsWithoutFlushing(movingTabs[0]);
|
||||
for (let i = 0; i < movingTabs.length; i++) {
|
||||
const tab = movingTabs[i];
|
||||
const tabClone = tab.cloneNode(true);
|
||||
if (i > 0) {
|
||||
tabClone.style.transform = `translate(${i * 4}px, -${i * (tabRect.height - 4)}px)`;
|
||||
tabClone.style.opacity = '0.2';
|
||||
tabClone.style.zIndex = `${-i}`;
|
||||
}
|
||||
wrapper.appendChild(tabClone);
|
||||
}
|
||||
if (movingTabs.length > 1) {
|
||||
const dot = document.createElement('div');
|
||||
dot.textContent = movingTabs.length;
|
||||
dot.style.position = 'absolute';
|
||||
dot.style.top = '-10px';
|
||||
dot.style.left = '-16px';
|
||||
dot.style.background = 'red';
|
||||
dot.style.borderRadius = '50%';
|
||||
dot.style.fontWeight = 'bold';
|
||||
dot.style.fontSize = '10px';
|
||||
dot.style.lineHeight = '16px';
|
||||
dot.style.justifyContent = dot.style.alignItems = 'center';
|
||||
dot.style.height = dot.style.minWidth = '16px';
|
||||
dot.style.textAlign = 'center';
|
||||
dot.style.color = 'white';
|
||||
wrapper.appendChild(dot);
|
||||
}
|
||||
wrapper.style.width = tabRect.width + 'px';
|
||||
wrapper.style.height = tabRect.height * movingTabs.length + 'px';
|
||||
wrapper.style.position = 'fixed';
|
||||
wrapper.style.top = '-9999px';
|
||||
periphery.appendChild(wrapper);
|
||||
this._tempDragImageParent = wrapper;
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
_animateTabMove(event) {
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
let dragData = draggedTab._dragData;
|
||||
@@ -541,6 +585,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
handle_drop(event) {
|
||||
const dt = event.dataTransfer;
|
||||
dt.updateDragImage(...this.originalDragImageArgs);
|
||||
super.handle_drop(event);
|
||||
}
|
||||
|
||||
handle_drop_transition(dropElement, draggedTab, movingTabs, dropBefore) {
|
||||
if (isTabGroupLabel(dropElement)) {
|
||||
dropElement = dropElement.group;
|
||||
@@ -623,11 +673,14 @@
|
||||
this.originalDragImageArgs = [];
|
||||
window.removeEventListener('dragover', this.handle_windowDragEnter, { capture: true });
|
||||
this.#isOutOfWindow = false;
|
||||
this.#clearInvisibleTempTab();
|
||||
if (this._browserDragImageWrapper) {
|
||||
this._browserDragImageWrapper.remove();
|
||||
delete this._browserDragImageWrapper;
|
||||
}
|
||||
if (this._tempDragImageParent) {
|
||||
this._tempDragImageParent.remove();
|
||||
delete this._tempDragImageParent;
|
||||
}
|
||||
}
|
||||
|
||||
#applyDragOverBackground(element) {
|
||||
@@ -658,14 +711,6 @@
|
||||
gZenPinnedTabManager.removeTabContainersDragoverClass();
|
||||
}
|
||||
|
||||
#clearInvisibleTempTab() {
|
||||
if (this._invisibleTempTab) {
|
||||
this._invisibleTempTab.remove();
|
||||
delete this._invisibleTempTab;
|
||||
this._tabbrowserTabs._invalidateCachedTabs();
|
||||
}
|
||||
}
|
||||
|
||||
#applyDragoverIndicator(event, tabs, movingTabs, draggedTab) {
|
||||
const separation = 4;
|
||||
const dropZoneSelector = ':is(.tabbrowser-tab, .zen-drop-target, .tab-group-label)';
|
||||
@@ -687,19 +732,8 @@
|
||||
}
|
||||
dropElement = elementToMove(dropElement);
|
||||
if (this._isContainerVerticalPinnedGrid(dropElement) && isTab(draggedTab)) {
|
||||
if (!draggedTab.hasAttribute('zen-essential') && !this._invisibleTempTab) {
|
||||
this._invisibleTempTab = draggedTab.cloneNode(true);
|
||||
this._invisibleTempTab.setAttribute('zen-essential', 'true');
|
||||
//this._invisibleTempTab.style.visibility = 'hidden';
|
||||
this._tabbrowserTabs.ariaFocusableItems[gBrowser._numZenEssentials - 1].after(
|
||||
this._invisibleTempTab
|
||||
);
|
||||
this._tabbrowserTabs._invalidateCachedTabs();
|
||||
}
|
||||
this._animateExpandedPinnedTabMove(event);
|
||||
console.log('TODO: Handle essential tab dragover in vertical pinned grid');
|
||||
return;
|
||||
} else if (this._invisibleTempTab) {
|
||||
this.#clearInvisibleTempTab();
|
||||
}
|
||||
if (this.#lastDropTarget !== dropElement) {
|
||||
shouldPlayHapticFeedback = this.#lastDropTarget !== null;
|
||||
@@ -761,7 +795,13 @@
|
||||
return [dropBefore, dropElement];
|
||||
}
|
||||
|
||||
#getDragImageOffset(event, tab) {
|
||||
#getDragImageOffset(event, tab, draggingTabs) {
|
||||
if (draggingTabs.length > 1) {
|
||||
return {
|
||||
offsetX: 18,
|
||||
offsetY: 18,
|
||||
};
|
||||
}
|
||||
const rect = tab.getBoundingClientRect();
|
||||
return {
|
||||
offsetX: event.clientX - rect.left,
|
||||
|
||||
@@ -1166,7 +1166,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.zen-essentials-container > .tabbrowser-tab,
|
||||
.tabbrowser-tab[zen-essential='true'],
|
||||
#zen-welcome-initial-essentials-browser-sidebar-essentials .tabbrowser-tab {
|
||||
--toolbarbutton-inner-padding: 0;
|
||||
max-width: unset;
|
||||
|
||||
Reference in New Issue
Block a user