mirror of
https://github.com/zen-browser/desktop.git
synced 2025-09-05 19:08:18 +00:00
Merge pull request #4784 from kristijanribaric/feature/pin-tab-by-drag-and-drop
Enhance Tab Reordering: Add drag-and-drop between tab containers (normal tabs, pinned and essentials)
This commit is contained in:
@@ -1071,3 +1071,29 @@
|
||||
%include vertical-tabs-topbuttons-fix.css
|
||||
}
|
||||
}
|
||||
|
||||
#vertical-pinned-tabs-container .tabbrowser-tab,
|
||||
#tabbrowser-arrowscrollbox .tabbrowser-tab,
|
||||
#zen-essentials-container .tabbrowser-tab {
|
||||
transition: box-shadow 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* Vertical tabs reordering indicators */
|
||||
#vertical-pinned-tabs-container .tabbrowser-tab.drag-over-before,
|
||||
#tabbrowser-arrowscrollbox .tabbrowser-tab.drag-over-before {
|
||||
box-shadow: 0 3px 6px -2px var(--toolbarbutton-active-background, rgba(0, 0, 255, 0.2));
|
||||
}
|
||||
|
||||
#vertical-pinned-tabs-container .tabbrowser-tab.drag-over-after,
|
||||
#tabbrowser-arrowscrollbox .tabbrowser-tab.drag-over-after {
|
||||
box-shadow: 0 -3px 6px -2px var(--toolbarbutton-active-background, rgba(0, 0, 255, 0.2));
|
||||
}
|
||||
|
||||
/* Horizontal tabs reordering indicators */
|
||||
#zen-essentials-container .tabbrowser-tab.drag-over-before {
|
||||
box-shadow: 3px 0 6px -2px var(--toolbarbutton-active-background, rgba(0, 255, 0, 0.2));
|
||||
}
|
||||
|
||||
#zen-essentials-container .tabbrowser-tab.drag-over-after {
|
||||
box-shadow: -3px 0 6px -2px var(--toolbarbutton-active-background, rgba(0, 255, 0, 0.2));
|
||||
}
|
||||
|
@@ -327,6 +327,10 @@
|
||||
}
|
||||
|
||||
const actualPin = this._pinsCache.find((pin) => pin.uuid === tab.getAttribute('zen-pin-id'));
|
||||
|
||||
if(!actualPin) {
|
||||
return;
|
||||
}
|
||||
actualPin.position = tab.position;
|
||||
await ZenPinnedTabsStorage.savePin(actualPin);
|
||||
}
|
||||
@@ -557,8 +561,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
addToEssentials() {
|
||||
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
|
||||
addToEssentials(tab) {
|
||||
const tabs = tab ? [tab] : TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tab = tabs[i];
|
||||
tab.setAttribute('zen-essential', 'true');
|
||||
@@ -575,8 +579,8 @@
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
}
|
||||
|
||||
removeEssentials() {
|
||||
const tabs = TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
|
||||
removeEssentials(tab) {
|
||||
const tabs = tab ? [tab] : TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tab = tabs[i];
|
||||
tab.removeAttribute('zen-essential');
|
||||
@@ -640,6 +644,138 @@
|
||||
document.getElementById('context_unpinSelectedTabs').hidden || contextTab.getAttribute('zen-essential');
|
||||
document.getElementById('context_zen-pinned-tab-separator').hidden = !isVisible;
|
||||
}
|
||||
|
||||
moveToAnotherTabContainerIfNecessary(event, draggedTab) {
|
||||
const pinnedTabsTarget = event.target.closest("#vertical-pinned-tabs-container");
|
||||
const essentialTabsTarget = event.target.closest("#zen-essentials-container");
|
||||
const tabsTarget = event.target.closest("#tabbrowser-arrowscrollbox");
|
||||
|
||||
let moved = false;
|
||||
let isVertical = true;
|
||||
let isRegularTabs = false;
|
||||
// Check for pinned tabs container
|
||||
if (pinnedTabsTarget) {
|
||||
if (!draggedTab.pinned) {
|
||||
gBrowser.pinTab(draggedTab);
|
||||
moved = true;
|
||||
} else if (draggedTab.hasAttribute("zen-essential")) {
|
||||
this.removeEssentials(draggedTab);
|
||||
gBrowser.pinTab(draggedTab);
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
// Check for essentials container
|
||||
else if (essentialTabsTarget) {
|
||||
if (!draggedTab.hasAttribute("zen-essential")) {
|
||||
this.addToEssentials(draggedTab);
|
||||
moved = true;
|
||||
isVertical = false;
|
||||
}
|
||||
}
|
||||
// Check for normal tabs container
|
||||
else if (tabsTarget) {
|
||||
if (draggedTab.pinned && !draggedTab.hasAttribute("zen-essential")) {
|
||||
gBrowser.unpinTab(draggedTab);
|
||||
moved = true;
|
||||
isRegularTabs = true;
|
||||
} else if (draggedTab.hasAttribute("zen-essential")) {
|
||||
this.removeEssentials(draggedTab);
|
||||
moved = true;
|
||||
isRegularTabs = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the tab was moved, adjust its position relative to the target tab
|
||||
if (moved) {
|
||||
const targetTab = event.target.closest(".tabbrowser-tab");
|
||||
if (targetTab) {
|
||||
const rect = targetTab.getBoundingClientRect();
|
||||
let newIndex = targetTab._tPos;
|
||||
|
||||
if (isVertical) {
|
||||
const middleY = targetTab.screenY + rect.height / 2;
|
||||
if(!isRegularTabs && event.screenY > middleY) {
|
||||
newIndex++;
|
||||
} else if(isRegularTabs && event.screenY < middleY) {
|
||||
newIndex--;
|
||||
}
|
||||
|
||||
} else {
|
||||
const middleX = targetTab.screenX + rect.width / 2;
|
||||
if (event.screenX > middleX) {
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
gBrowser.moveTabTo(draggedTab, newIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return moved;
|
||||
}
|
||||
|
||||
removeTabContainersDragoverClass() {
|
||||
document
|
||||
.querySelectorAll(".tabbrowser-tab.drag-over-before, .tabbrowser-tab.drag-over-after")
|
||||
.forEach(tab => {
|
||||
tab.classList.remove("drag-over-before", "drag-over-after");
|
||||
});
|
||||
}
|
||||
|
||||
applyDragoverClass(event, draggedTab) {
|
||||
this.removeTabContainersDragoverClass();
|
||||
|
||||
const pinnedTabsTarget = event.target.closest("#vertical-pinned-tabs-container");
|
||||
const essentialTabsTarget = event.target.closest("#zen-essentials-container");
|
||||
const tabsTarget = event.target.closest("#tabbrowser-arrowscrollbox");
|
||||
const targetTab = event.target.closest(".tabbrowser-tab");
|
||||
|
||||
// If there's no valid target tab, nothing to do
|
||||
if (!targetTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
let shouldAddDragOverElement = false;
|
||||
let isVertical = true;
|
||||
|
||||
// Decide whether we should show a dragover class for the given target
|
||||
if (pinnedTabsTarget) {
|
||||
if (!draggedTab.pinned || draggedTab.hasAttribute("zen-essential")) {
|
||||
shouldAddDragOverElement = true;
|
||||
}
|
||||
} else if (essentialTabsTarget) {
|
||||
if (!draggedTab.hasAttribute("zen-essential")) {
|
||||
shouldAddDragOverElement = true;
|
||||
isVertical = false;
|
||||
}
|
||||
} else if (tabsTarget) {
|
||||
if (draggedTab.pinned || draggedTab.hasAttribute("zen-essential")) {
|
||||
shouldAddDragOverElement = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldAddDragOverElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate middle to decide 'before' or 'after'
|
||||
const rect = targetTab.getBoundingClientRect();
|
||||
|
||||
if (isVertical) {
|
||||
const middleY = targetTab.screenY + rect.height / 2;
|
||||
if (event.screenY > middleY) {
|
||||
targetTab.classList.add("drag-over-before");
|
||||
} else {
|
||||
targetTab.classList.add("drag-over-after");
|
||||
}
|
||||
} else {
|
||||
const middleX = targetTab.screenX + rect.width / 2;
|
||||
if (event.screenX > middleX) {
|
||||
targetTab.classList.add("drag-over-before");
|
||||
} else {
|
||||
targetTab.classList.add("drag-over-after");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gZenPinnedTabManager = new ZenPinnedTabManager();
|
||||
|
@@ -29,7 +29,40 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
let tabsPerRow = 0;
|
||||
let position = 0;
|
||||
for (let pinnedTab of pinnedTabs) {
|
||||
@@ -1010,7 +1010,7 @@
|
||||
@@ -859,6 +859,9 @@
|
||||
}
|
||||
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
+ if (draggedTab && effects === "move") {
|
||||
+ gZenPinnedTabManager.applyDragoverClass(event, draggedTab);
|
||||
+ }
|
||||
if (
|
||||
(effects == "move" || effects == "copy") &&
|
||||
this == draggedTab.container &&
|
||||
@@ -955,6 +958,7 @@
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
on_drop(event) {
|
||||
+ gZenPinnedTabManager.removeTabContainersDragoverClass();
|
||||
var dt = event.dataTransfer;
|
||||
var dropEffect = dt.dropEffect;
|
||||
var draggedTab;
|
||||
@@ -972,6 +976,14 @@
|
||||
|
||||
this._tabDropIndicator.hidden = true;
|
||||
event.stopPropagation();
|
||||
+ if (draggedTab && dropEffect == "move") {
|
||||
+ let moved = gZenPinnedTabManager.moveToAnotherTabContainerIfNecessary(event, draggedTab);
|
||||
+
|
||||
+ if (moved) {
|
||||
+ this._finishMoveTogetherSelectedTabs(draggedTab);
|
||||
+ return;
|
||||
+ }
|
||||
+ }
|
||||
if (draggedTab && dropEffect == "copy") {
|
||||
// copy the dropped tab (wherever it's from)
|
||||
let newIndex = this._getDropIndex(event);
|
||||
@@ -1010,7 +1022,7 @@
|
||||
}
|
||||
} else {
|
||||
let pinned = draggedTab.pinned;
|
||||
@@ -38,7 +71,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
let tabs = this.visibleTabs.slice(
|
||||
pinned ? 0 : numPinned,
|
||||
pinned ? numPinned : undefined
|
||||
@@ -1090,7 +1090,7 @@
|
||||
@@ -1090,7 +1102,7 @@
|
||||
let postTransitionCleanup = () => {
|
||||
tab.removeAttribute("tabdrop-samewindow");
|
||||
|
||||
@@ -47,7 +80,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
if (dropIndex !== false) {
|
||||
gBrowser.moveTabTo(tab, dropIndex);
|
||||
if (!directionForward) {
|
||||
@@ -1100,7 +1100,7 @@
|
||||
@@ -1100,7 +1112,7 @@
|
||||
|
||||
gBrowser.syncThrobberAnimations(tab);
|
||||
};
|
||||
@@ -56,7 +89,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
postTransitionCleanup();
|
||||
} else {
|
||||
let onTransitionEnd = transitionendEvent => {
|
||||
@@ -1263,7 +1263,8 @@
|
||||
@@ -1263,7 +1275,8 @@
|
||||
if (
|
||||
dt.mozUserCancelled ||
|
||||
dt.dropEffect != "none" ||
|
||||
@@ -66,7 +99,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
) {
|
||||
delete draggedTab._dragData;
|
||||
return;
|
||||
@@ -1512,7 +1513,7 @@
|
||||
@@ -1512,7 +1525,7 @@
|
||||
}
|
||||
|
||||
this.#allTabs = [
|
||||
@@ -75,7 +108,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
...children,
|
||||
];
|
||||
return this.#allTabs;
|
||||
@@ -1593,6 +1594,7 @@
|
||||
@@ -1593,6 +1606,7 @@
|
||||
}
|
||||
|
||||
this.#focusableItems = [
|
||||
@@ -83,7 +116,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
...verticalPinnedTabsContainer.children,
|
||||
...focusableItems,
|
||||
];
|
||||
@@ -1617,8 +1619,8 @@
|
||||
@@ -1617,8 +1631,8 @@
|
||||
#isContainerVerticalPinnedExpanded(tab) {
|
||||
return (
|
||||
this.verticalMode &&
|
||||
@@ -94,7 +127,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1816,7 +1818,7 @@
|
||||
@@ -1816,7 +1830,7 @@
|
||||
let rect = ele => {
|
||||
return window.windowUtils.getBoundsWithoutFlushing(ele);
|
||||
};
|
||||
@@ -103,7 +136,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
if (tab && rect(tab).width <= this._tabClipWidth) {
|
||||
this.setAttribute("closebuttons", "activetab");
|
||||
} else {
|
||||
@@ -1832,6 +1834,7 @@
|
||||
@@ -1832,6 +1846,7 @@
|
||||
this.arrowScrollbox.ensureElementIsVisible(selectedTab, aInstant);
|
||||
}
|
||||
|
||||
@@ -111,7 +144,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
selectedTab._notselectedsinceload = false;
|
||||
}
|
||||
|
||||
@@ -1879,7 +1882,7 @@
|
||||
@@ -1879,7 +1894,7 @@
|
||||
if (isEndTab && !this._hasTabTempMaxWidth) {
|
||||
return;
|
||||
}
|
||||
@@ -120,7 +153,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
// Force tabs to stay the same width, unless we're closing the last tab,
|
||||
// which case we need to let them expand just enough so that the overall
|
||||
// tabbar width is the same.
|
||||
@@ -1894,7 +1897,7 @@
|
||||
@@ -1894,7 +1909,7 @@
|
||||
let tabsToReset = [];
|
||||
for (let i = numPinned; i < tabs.length; i++) {
|
||||
let tab = tabs[i];
|
||||
@@ -129,7 +162,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
if (!isEndTab) {
|
||||
// keep tabs the same width
|
||||
tab.style.transition = "none";
|
||||
@@ -1963,13 +1966,13 @@
|
||||
@@ -1963,13 +1978,13 @@
|
||||
let verticalTabsContainer = document.getElementById(
|
||||
"vertical-pinned-tabs-container"
|
||||
);
|
||||
@@ -146,7 +179,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1993,7 +1996,7 @@
|
||||
@@ -1993,7 +2008,7 @@
|
||||
|
||||
_positionPinnedTabs() {
|
||||
let tabs = this.visibleTabs;
|
||||
@@ -155,7 +188,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
let absPositionHorizontalTabs =
|
||||
this.overflowing && tabs.length > numPinned && numPinned > 0;
|
||||
|
||||
@@ -2074,7 +2077,7 @@
|
||||
@@ -2074,7 +2089,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -164,7 +197,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
|
||||
let directionX = screenX > dragData.animLastScreenX;
|
||||
let directionY = screenY > dragData.animLastScreenY;
|
||||
@@ -2257,9 +2260,9 @@
|
||||
@@ -2257,9 +2272,9 @@
|
||||
}
|
||||
|
||||
let pinned = draggedTab.pinned;
|
||||
@@ -176,7 +209,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
pinned ? numPinned : undefined
|
||||
);
|
||||
|
||||
@@ -2502,8 +2505,8 @@
|
||||
@@ -2502,8 +2517,8 @@
|
||||
);
|
||||
}
|
||||
|
||||
@@ -187,7 +220,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2668,9 +2671,9 @@
|
||||
@@ -2668,9 +2683,9 @@
|
||||
function newIndex(aTab, index) {
|
||||
// Don't allow mixing pinned and unpinned tabs.
|
||||
if (aTab.pinned) {
|
||||
@@ -199,7 +232,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2754,7 +2757,7 @@
|
||||
@@ -2754,7 +2769,7 @@
|
||||
}
|
||||
|
||||
_notifyBackgroundTab(aTab) {
|
||||
@@ -208,7 +241,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2772,12 +2775,14 @@
|
||||
@@ -2772,12 +2787,14 @@
|
||||
selectedTab = {
|
||||
left: selectedTab.left,
|
||||
right: selectedTab.right,
|
||||
@@ -224,7 +257,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
selectedTab,
|
||||
];
|
||||
})
|
||||
@@ -2794,8 +2799,11 @@
|
||||
@@ -2794,8 +2811,11 @@
|
||||
delete this._lastTabToScrollIntoView;
|
||||
// Is the new tab already completely visible?
|
||||
if (
|
||||
@@ -238,7 +271,7 @@ index 8aeb244ffca9f48661805f5b7d860b5896055562..aa672d49e5d3e8e0ce4747187becbc79
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -2803,21 +2811,29 @@
|
||||
@@ -2803,21 +2823,29 @@
|
||||
if (this.arrowScrollbox.smoothScroll) {
|
||||
// Can we make both the new tab and the selected tab completely visible?
|
||||
if (
|
||||
|
Reference in New Issue
Block a user