mirror of
https://github.com/zen-browser/desktop.git
synced 2025-12-16 19:35:31 +00:00
Compare commits
2 Commits
window-syn
...
drag-and-d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
04833ad090 | ||
|
|
f044433bd1 |
@@ -40,12 +40,6 @@
|
||||
- name: zen.view.window.scheme
|
||||
value: 2
|
||||
|
||||
- name: zen.view.drag-and-drop.move-over-threshold
|
||||
value: 70
|
||||
|
||||
- name: zen.view.drag-and-drop.edge-zone-threshold
|
||||
value: 25
|
||||
|
||||
- name: zen.view.context-menu.refresh
|
||||
value: false
|
||||
|
||||
|
||||
@@ -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..bc49f4f5a90638d725eca016d00f30d9548dce83 100644
|
||||
index 97b931c3c7385a52d20204369fcf6d6999053687..b028c923d24adf0e9dbe12f80deb3ad4fda535eb 100644
|
||||
--- a/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
+++ b/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
@@ -32,6 +32,9 @@
|
||||
@@ -12,18 +12,17 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
if (isTab(element)) {
|
||||
return element;
|
||||
}
|
||||
@@ -112,6 +115,10 @@
|
||||
@@ -112,6 +115,9 @@
|
||||
}
|
||||
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
+ if (draggedTab && dropEffect === "move") {
|
||||
+ gZenPinnedTabManager.applyDragoverClass(event, draggedTab);
|
||||
+ gZenViewSplitter.onBrowserDragEndToSplit(event);
|
||||
+ }
|
||||
if (
|
||||
(dropEffect == "move" || dropEffect == "copy") &&
|
||||
document == draggedTab.ownerDocument &&
|
||||
@@ -266,6 +273,18 @@
|
||||
@@ -266,6 +272,18 @@
|
||||
|
||||
this._tabDropIndicator.hidden = true;
|
||||
event.stopPropagation();
|
||||
@@ -42,7 +41,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
if (draggedTab && dropEffect == "copy") {
|
||||
let duplicatedDraggedTab;
|
||||
let duplicatedTabs = [];
|
||||
@@ -291,8 +310,9 @@
|
||||
@@ -291,8 +309,9 @@
|
||||
let translateOffsetY = oldTranslateY % tabHeight;
|
||||
let newTranslateX = oldTranslateX - translateOffsetX;
|
||||
let newTranslateY = oldTranslateY - translateOffsetY;
|
||||
@@ -54,7 +53,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
|
||||
if (this._isContainerVerticalPinnedGrid(draggedTab)) {
|
||||
// Update both translate axis for pinned vertical expanded tabs
|
||||
@@ -308,8 +328,8 @@
|
||||
@@ -308,8 +327,8 @@
|
||||
}
|
||||
} else {
|
||||
let tabs = this._tabbrowserTabs.ariaFocusableItems.slice(
|
||||
@@ -65,7 +64,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
);
|
||||
let size = this._tabbrowserTabs.verticalMode ? "height" : "width";
|
||||
let screenAxis = this._tabbrowserTabs.verticalMode
|
||||
@@ -362,11 +382,13 @@
|
||||
@@ -362,11 +381,13 @@
|
||||
this._dragToPinPromoCard,
|
||||
];
|
||||
let shouldPin =
|
||||
@@ -79,7 +78,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
isTab(draggedTab) &&
|
||||
draggedTab.pinned &&
|
||||
this._tabbrowserTabs.arrowScrollbox.contains(event.target);
|
||||
@@ -384,6 +406,7 @@
|
||||
@@ -384,6 +405,7 @@
|
||||
(oldTranslateY && oldTranslateY != newTranslateY);
|
||||
} else if (this._tabbrowserTabs.verticalMode) {
|
||||
shouldTranslate &&= oldTranslateY && oldTranslateY != newTranslateY;
|
||||
@@ -87,7 +86,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
} else {
|
||||
shouldTranslate &&= oldTranslateX && oldTranslateX != newTranslateX;
|
||||
}
|
||||
@@ -440,7 +463,7 @@
|
||||
@@ -440,7 +462,7 @@
|
||||
item.removeAttribute("tabdrop-samewindow");
|
||||
resolve();
|
||||
};
|
||||
@@ -96,7 +95,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
postTransitionCleanup();
|
||||
} else {
|
||||
let onTransitionEnd = transitionendEvent => {
|
||||
@@ -581,6 +604,7 @@
|
||||
@@ -581,6 +603,7 @@
|
||||
|
||||
let nextItem = this._tabbrowserTabs.ariaFocusableItems[newIndex];
|
||||
let tabGroup = isTab(nextItem) && nextItem.group;
|
||||
@@ -104,7 +103,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
gBrowser.loadTabs(urls, {
|
||||
inBackground,
|
||||
replace,
|
||||
@@ -618,7 +642,16 @@
|
||||
@@ -618,7 +641,16 @@
|
||||
this._expandGroupOnDrop(draggedTab);
|
||||
}
|
||||
this._resetTabsAfterDrop(draggedTab.ownerDocument);
|
||||
@@ -122,7 +121,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
if (
|
||||
dt.mozUserCancelled ||
|
||||
dt.dropEffect != "none" ||
|
||||
@@ -822,7 +855,10 @@
|
||||
@@ -822,7 +854,10 @@
|
||||
_getDragTarget(event, { ignoreSides = false } = {}) {
|
||||
let { target } = event;
|
||||
while (target) {
|
||||
@@ -134,7 +133,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
break;
|
||||
}
|
||||
target = target.parentNode;
|
||||
@@ -839,14 +875,17 @@
|
||||
@@ -839,14 +874,17 @@
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -154,7 +153,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
!this._tabbrowserTabs.expandOnHover
|
||||
);
|
||||
}
|
||||
@@ -877,7 +916,8 @@
|
||||
@@ -877,7 +915,8 @@
|
||||
isTabGroupLabel(draggedTab) &&
|
||||
draggedTab._dragData?.expandGroupOnDrop
|
||||
) {
|
||||
@@ -164,7 +163,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,10 +982,7 @@
|
||||
@@ -942,10 +981,7 @@
|
||||
if (this._isContainerVerticalPinnedGrid(tab)) {
|
||||
// In expanded vertical mode, the max number of pinned tabs per row is dynamic
|
||||
// Set this before adjusting dragged tab's position
|
||||
@@ -176,7 +175,23 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
let tabsPerRow = 0;
|
||||
let position = RTL_UI
|
||||
? window.windowUtils.getBoundsWithoutFlushing(
|
||||
@@ -1112,7 +1149,7 @@
|
||||
@@ -1055,7 +1091,6 @@
|
||||
// using updateDragImage. On Linux, we can use a panel.
|
||||
if (platform == "win" || platform == "macosx") {
|
||||
captureListener = function () {
|
||||
- dt.updateDragImage(canvas, dragImageOffset, dragImageOffset);
|
||||
};
|
||||
} else {
|
||||
// Create a panel to use it in setDragImage
|
||||
@@ -1093,7 +1128,6 @@
|
||||
);
|
||||
dragImageOffset = dragImageOffset * scale;
|
||||
}
|
||||
- dt.setDragImage(toDrag, dragImageOffset, dragImageOffset);
|
||||
|
||||
// _dragData.offsetX/Y give the coordinates that the mouse should be
|
||||
// positioned relative to the corner of the new window created upon
|
||||
@@ -1112,7 +1146,7 @@
|
||||
let dropEffect = this.getDropEffectForTabDrag(event);
|
||||
let isMovingInTabStrip = !fromTabList && dropEffect == "move";
|
||||
let collapseTabGroupDuringDrag =
|
||||
@@ -185,7 +200,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
|
||||
tab._dragData = {
|
||||
offsetX: this._tabbrowserTabs.verticalMode
|
||||
@@ -1122,7 +1159,7 @@
|
||||
@@ -1122,7 +1156,7 @@
|
||||
? event.screenY - window.screenY - tabOffset
|
||||
: event.screenY - window.screenY,
|
||||
scrollPos:
|
||||
@@ -194,7 +209,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
? this._tabbrowserTabs.pinnedTabsContainer.scrollPosition
|
||||
: this._tabbrowserTabs.arrowScrollbox.scrollPosition,
|
||||
screenX: event.screenX,
|
||||
@@ -1149,6 +1186,7 @@
|
||||
@@ -1149,6 +1183,7 @@
|
||||
|
||||
if (collapseTabGroupDuringDrag) {
|
||||
tab.group.collapsed = true;
|
||||
@@ -202,7 +217,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1173,6 +1211,16 @@
|
||||
@@ -1173,6 +1208,16 @@
|
||||
if (tabStripItemElement.hasAttribute("dragtarget")) {
|
||||
return;
|
||||
}
|
||||
@@ -219,7 +234,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
let isPinned = tab.pinned;
|
||||
let numPinned = gBrowser.pinnedTabCount;
|
||||
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
|
||||
@@ -1624,10 +1672,7 @@
|
||||
@@ -1624,10 +1669,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -231,7 +246,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
|
||||
let directionX = screenX > dragData.animLastScreenX;
|
||||
let directionY = screenY > dragData.animLastScreenY;
|
||||
@@ -1636,6 +1681,8 @@
|
||||
@@ -1636,6 +1678,8 @@
|
||||
|
||||
let { width: tabWidth, height: tabHeight } =
|
||||
draggedTab.getBoundingClientRect();
|
||||
@@ -240,7 +255,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
let shiftSizeX = tabWidth * movingTabs.length;
|
||||
let shiftSizeY = tabHeight;
|
||||
dragData.tabWidth = tabWidth;
|
||||
@@ -1672,8 +1719,8 @@
|
||||
@@ -1672,8 +1716,8 @@
|
||||
let lastBoundX =
|
||||
lastTabInRow.screenX +
|
||||
lastTabInRow.getBoundingClientRect().width -
|
||||
@@ -251,161 +266,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
translateX = Math.min(Math.max(translateX, firstBoundX), lastBoundX);
|
||||
translateY = Math.min(Math.max(translateY, firstBoundY), lastBoundY);
|
||||
|
||||
@@ -1833,13 +1880,18 @@
|
||||
this._clearDragOverGroupingTimer();
|
||||
this.#clearPinnedDropIndicatorTimer();
|
||||
|
||||
- let isPinned = draggedTab.pinned;
|
||||
- let numPinned = gBrowser.pinnedTabCount;
|
||||
+ let isPinned = draggedTab?.group ? draggedTab.group.pinned : draggedTab.pinned;
|
||||
+ let numPinned = gBrowser._numVisiblePinTabsWithoutCollapsed;
|
||||
+ let essential = draggedTab.hasAttribute("zen-essential");
|
||||
+ const isDraggingFolder = isTabGroupLabel(draggedTab) && draggedTab.group?.isZenFolder;
|
||||
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
|
||||
let tabs = allTabs.slice(
|
||||
- isPinned ? 0 : numPinned,
|
||||
- isPinned ? numPinned : undefined
|
||||
+ (isPinned && essential) ? 0 : gBrowser._numZenEssentials,
|
||||
+ isPinned ? (essential ? gBrowser._numZenEssentials : (isDraggingFolder ? numPinned : undefined)) : undefined
|
||||
);
|
||||
+ if (draggedTab.group?.hasAttribute("split-view-group")) {
|
||||
+ draggedTab = draggedTab.group.labelElement;
|
||||
+ }
|
||||
|
||||
if (this._rtlMode) {
|
||||
tabs.reverse();
|
||||
@@ -1854,7 +1906,7 @@
|
||||
let translateAxis = this._tabbrowserTabs.verticalMode
|
||||
? "translateY"
|
||||
: "translateX";
|
||||
- let { width: tabWidth, height: tabHeight } = bounds(draggedTab);
|
||||
+ let { width: tabWidth, height: tabHeight } = bounds(draggedTab.group?.hasAttribute("split-view-group") ? draggedTab.group : draggedTab);
|
||||
let tabSize = this._tabbrowserTabs.verticalMode ? tabHeight : tabWidth;
|
||||
let translateX = event.screenX - dragData.screenX;
|
||||
let translateY = event.screenY - dragData.screenY;
|
||||
@@ -1870,6 +1922,12 @@
|
||||
);
|
||||
let lastMovingTab = movingTabs.at(-1);
|
||||
let firstMovingTab = movingTabs[0];
|
||||
+ if (lastMovingTab.group?.hasAttribute("split-view-group")) {
|
||||
+ lastMovingTab = lastMovingTab.group;
|
||||
+ }
|
||||
+ if (firstMovingTab.group?.hasAttribute("split-view-group")) {
|
||||
+ firstMovingTab = firstMovingTab.group;
|
||||
+ }
|
||||
let endEdge = ele => ele[screenAxis] + bounds(ele)[size];
|
||||
let lastMovingTabScreen = endEdge(lastMovingTab);
|
||||
let firstMovingTabScreen = firstMovingTab[screenAxis];
|
||||
@@ -1884,6 +1942,13 @@
|
||||
let endBound = this._rtlMode
|
||||
? endEdge(this._tabbrowserTabs) - lastMovingTabScreen
|
||||
: periphery[screenAxis] - 1 - lastMovingTabScreen;
|
||||
+ {
|
||||
+ let firstTab = tabs.at(this._rtlMode ? -1 : 0);
|
||||
+ let lastTab = tabs.at(this._rtlMode ? 0 : -1);
|
||||
+ startBound = firstTab[screenAxis] - firstMovingTabScreen;
|
||||
+ endBound = endEdge(lastTab) - lastMovingTabScreen;
|
||||
+ endBound = gZenPinnedTabManager.getLastTabBound(endBound, lastTab, isDraggingFolder);
|
||||
+ }
|
||||
translate = Math.min(Math.max(translate, startBound), endBound);
|
||||
|
||||
// Center the tab under the cursor if the tab is not under the cursor while dragging
|
||||
@@ -2075,6 +2140,8 @@
|
||||
};
|
||||
|
||||
let dropElement = getOverlappedElement();
|
||||
+ if (dropElement?.hasAttribute("split-view-group")) dropElement = dropElement.labelElement;
|
||||
+ gZenPinnedTabManager.animateSeparatorMove(movingTabs, dropElement, isPinned, event);
|
||||
|
||||
let newDropElementIndex;
|
||||
if (dropElement) {
|
||||
@@ -2157,7 +2224,7 @@
|
||||
? Services.prefs.getIntPref(
|
||||
"browser.tabs.dragDrop.moveOverThresholdPercent"
|
||||
) / 100
|
||||
- : 0.5;
|
||||
+ : Services.prefs.getIntPref('zen.view.drag-and-drop.move-over-threshold') / 100;
|
||||
moveOverThreshold = Math.min(1, Math.max(0, moveOverThreshold));
|
||||
let shouldMoveOver = overlapPercent > moveOverThreshold;
|
||||
if (logicalForward && shouldMoveOver) {
|
||||
@@ -2190,6 +2257,7 @@
|
||||
// If dragging a group over another group, don't make it look like it is
|
||||
// possible to drop the dragged group inside the other group.
|
||||
if (
|
||||
+ false &&
|
||||
isTabGroupLabel(draggedTab) &&
|
||||
dropElement?.group &&
|
||||
(!dropElement.group.collapsed ||
|
||||
@@ -2216,20 +2284,13 @@
|
||||
let isOutOfBounds = isPinned
|
||||
? dropElement.elementIndex >= numPinned
|
||||
: dropElement.elementIndex < numPinned;
|
||||
- if (isOutOfBounds) {
|
||||
- // Drop after last pinned tab
|
||||
- dropElement = this._tabbrowserTabs.ariaFocusableItems[numPinned - 1];
|
||||
- dropBefore = false;
|
||||
- }
|
||||
}
|
||||
|
||||
- if (
|
||||
- gBrowser._tabGroupsEnabled &&
|
||||
- isTab(draggedTab) &&
|
||||
- !isPinned &&
|
||||
- (!numPinned || newDropElementIndex >= numPinned)
|
||||
- ) {
|
||||
+ if (isTab(draggedTab) || isTabGroupLabel(draggedTab)) {
|
||||
let dragOverGroupingThreshold = 1 - moveOverThreshold;
|
||||
+ if (draggedTab && !dropElement?.group) {
|
||||
+ gZenFolders.highlightGroupOnDragOver(null);
|
||||
+ }
|
||||
let groupingDelay = Services.prefs.getIntPref(
|
||||
"browser.tabs.dragDrop.createGroup.delayMS"
|
||||
);
|
||||
@@ -2237,6 +2298,7 @@
|
||||
// When dragging tab(s) over an ungrouped tab, signal to the user
|
||||
// that dropping the tab(s) will create a new tab group.
|
||||
let shouldCreateGroupOnDrop =
|
||||
+ false &&
|
||||
!movingTabsSet.has(dropElement) &&
|
||||
isTab(dropElement) &&
|
||||
!dropElement?.group &&
|
||||
@@ -2245,6 +2307,7 @@
|
||||
// When dragging tab(s) over a collapsed tab group label, signal to the
|
||||
// user that dropping the tab(s) will add them to the group.
|
||||
let shouldDropIntoCollapsedTabGroup =
|
||||
+ false &&
|
||||
isTabGroupLabel(dropElement) &&
|
||||
dropElement.group.collapsed &&
|
||||
overlapPercent > dragOverGroupingThreshold;
|
||||
@@ -2302,6 +2365,14 @@
|
||||
dropElement = dropElementGroup.tabs[0];
|
||||
dropBefore = true;
|
||||
}
|
||||
+ ({ dropElement, colorCode, dropBefore } = gZenFolders.handleDragOverTabGroupLabel(
|
||||
+ dropElement,
|
||||
+ draggedTab,
|
||||
+ overlapPercent,
|
||||
+ movingTabs,
|
||||
+ dropBefore,
|
||||
+ colorCode
|
||||
+ ));
|
||||
}
|
||||
this._setDragOverGroupColor(colorCode);
|
||||
this._tabbrowserTabs.toggleAttribute(
|
||||
@@ -2324,10 +2395,11 @@
|
||||
dragData.dropBefore = dropBefore;
|
||||
dragData.animDropElementIndex = newDropElementIndex;
|
||||
|
||||
+ gZenFolders.setFolderIndentation(movingTabs, dropElement);
|
||||
// Shift background tabs to leave a gap where the dragged tab
|
||||
// would currently be dropped.
|
||||
for (let item of tabs) {
|
||||
- if (item == draggedTab) {
|
||||
+ if (item == draggedTab || (item.group?.hasAttribute("split-view-group") && item.group == draggedTab.group)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -2417,11 +2489,13 @@
|
||||
@@ -2417,6 +2461,7 @@
|
||||
}
|
||||
|
||||
finishAnimateTabMove() {
|
||||
@@ -413,13 +274,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
if (!this.#isMovingTab()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#setMovingTabMode(false);
|
||||
+ gZenFolders.highlightGroupOnDragOver(null);
|
||||
|
||||
for (let item of this._tabbrowserTabs.ariaFocusableItems) {
|
||||
this._resetGroupTarget(item);
|
||||
@@ -2457,7 +2531,7 @@
|
||||
@@ -2457,7 +2502,7 @@
|
||||
tab.style.left = "";
|
||||
tab.style.top = "";
|
||||
tab.style.maxWidth = "";
|
||||
@@ -428,7 +283,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..bc49f4f5a90638d725eca016d00f30d9
|
||||
}
|
||||
for (let label of draggedTabDocument.getElementsByClassName(
|
||||
"tab-group-label-container"
|
||||
@@ -2467,7 +2541,7 @@
|
||||
@@ -2467,7 +2512,7 @@
|
||||
label.style.left = "";
|
||||
label.style.top = "";
|
||||
label.style.maxWidth = "";
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
diff --git a/browser/components/tabbrowser/content/tabs.js b/browser/components/tabbrowser/content/tabs.js
|
||||
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..a765f2decc3a565226ac8793422474052f476573 100644
|
||||
index 6b6c04599fe80983d13d2069ca62b99d8ad70271..009a9c398e2434b8b6704ed2c75b0f09ecc22ca1 100644
|
||||
--- a/browser/components/tabbrowser/content/tabs.js
|
||||
+++ b/browser/components/tabbrowser/content/tabs.js
|
||||
@@ -235,7 +235,7 @@
|
||||
true
|
||||
)
|
||||
? new window.TabStacking(this)
|
||||
- : new window.TabDragAndDrop(this);
|
||||
+ : new window.ZenDragAndDrop(this);
|
||||
this.tabDragAndDrop.init();
|
||||
}
|
||||
|
||||
@@ -436,7 +436,7 @@
|
||||
// and we're not hitting the scroll buttons.
|
||||
if (
|
||||
|
||||
547
src/zen/common/ZenDragAndDrop.js
Normal file
547
src/zen/common/ZenDragAndDrop.js
Normal file
@@ -0,0 +1,547 @@
|
||||
/* 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/. */
|
||||
|
||||
'use strict';
|
||||
|
||||
// Wrap in a block to prevent leaking to window scope.
|
||||
{
|
||||
const isTab = (element) => gBrowser.isTab(element);
|
||||
const isTabGroupLabel = (element) => gBrowser.isTabGroupLabel(element);
|
||||
|
||||
/**
|
||||
* The elements in the tab strip from `this.ariaFocusableItems` that contain
|
||||
* logical information are:
|
||||
*
|
||||
* - <tab> (.tabbrowser-tab)
|
||||
* - <tab-group> label element (.tab-group-label)
|
||||
*
|
||||
* The elements in the tab strip that contain the space inside of the <tabs>
|
||||
* element are:
|
||||
*
|
||||
* - <tab> (.tabbrowser-tab)
|
||||
* - <tab-group> label element wrapper (.tab-group-label-container)
|
||||
*
|
||||
* When working with tab strip items, if you need logical information, you
|
||||
* can get it directly, e.g. `element.elementIndex` or `element._tPos`. If
|
||||
* you need spatial information like position or dimensions, then you should
|
||||
* call this function. For example, `elementToMove(element).getBoundingClientRect()`
|
||||
* or `elementToMove(element).style.top`.
|
||||
*
|
||||
* @param {MozTabbrowserTab|typeof MozTabbrowserTabGroup.labelElement} element
|
||||
* @returns {MozTabbrowserTab|vbox}
|
||||
*/
|
||||
const elementToMove = (element) => {
|
||||
if (element.classList.contains('zen-current-workspace-indicator')) {
|
||||
return element;
|
||||
}
|
||||
if (element.group?.hasAttribute('split-view-group')) {
|
||||
return element.group;
|
||||
}
|
||||
if (isTab(element)) {
|
||||
return element;
|
||||
}
|
||||
if (isTabGroupLabel(element)) {
|
||||
return element.closest('.tab-group-label-container');
|
||||
}
|
||||
throw new Error(`Element "${element.tagName}" is not expected to move`);
|
||||
};
|
||||
|
||||
window.ZenDragAndDrop = class extends window.TabDragAndDrop {
|
||||
#dragOverBackground = null;
|
||||
#lastDropTarget = null;
|
||||
|
||||
constructor(tabbrowserTabs) {
|
||||
super(tabbrowserTabs);
|
||||
}
|
||||
|
||||
startTabDrag(event, tab, ...args) {
|
||||
super.startTabDrag(event, tab, ...args);
|
||||
let dt = event.dataTransfer;
|
||||
|
||||
const { offsetX, offsetY } = this.#getDragImageOffset(tab);
|
||||
}
|
||||
|
||||
_animateTabMove(event) {
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
let dragData = draggedTab._dragData;
|
||||
let movingTabs = dragData.movingTabs;
|
||||
let movingTabsSet = dragData.movingTabsSet;
|
||||
|
||||
dragData.animLastScreenPos ??= this._tabbrowserTabs.verticalMode
|
||||
? dragData.screenY
|
||||
: dragData.screenX;
|
||||
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
|
||||
let numEssentials = gBrowser._numZenEssentials;
|
||||
let isEssential = draggedTab.hasAttribute('zen-essential');
|
||||
let tabs = allTabs.slice(
|
||||
isEssential ? 0 : numEssentials,
|
||||
isEssential ? numEssentials : undefined
|
||||
);
|
||||
|
||||
let screen = this._tabbrowserTabs.verticalMode ? event.screenY : event.screenX;
|
||||
if (screen == dragData.animLastScreenPos) {
|
||||
return;
|
||||
}
|
||||
let screenForward = screen > dragData.animLastScreenPos;
|
||||
dragData.animLastScreenPos = screen;
|
||||
|
||||
this._clearDragOverGroupingTimer();
|
||||
|
||||
if (this._rtlMode) {
|
||||
tabs.reverse();
|
||||
}
|
||||
|
||||
let bounds = (ele) => window.windowUtils.getBoundsWithoutFlushing(ele);
|
||||
let logicalForward = screenForward != this._rtlMode;
|
||||
let screenAxis = this._tabbrowserTabs.verticalMode ? 'screenY' : 'screenX';
|
||||
let size = this._tabbrowserTabs.verticalMode ? 'height' : 'width';
|
||||
let { width: tabWidth, height: tabHeight } = bounds(draggedTab);
|
||||
let tabSize = this._tabbrowserTabs.verticalMode ? tabHeight : tabWidth;
|
||||
let translateX = event.screenX - dragData.screenX;
|
||||
let translateY = event.screenY - dragData.screenY;
|
||||
|
||||
dragData.tabWidth = tabWidth;
|
||||
dragData.tabHeight = tabHeight;
|
||||
dragData.translateX = translateX;
|
||||
dragData.translateY = translateY;
|
||||
|
||||
// Move the dragged tab based on the mouse position.
|
||||
let periphery = document.getElementById('tabbrowser-arrowscrollbox-periphery');
|
||||
let lastMovingTab = movingTabs.at(-1);
|
||||
let firstMovingTab = movingTabs[0];
|
||||
let endEdge = (ele) => ele[screenAxis] + bounds(ele)[size];
|
||||
let lastMovingTabScreen = endEdge(lastMovingTab);
|
||||
let firstMovingTabScreen = firstMovingTab[screenAxis];
|
||||
let shiftSize = lastMovingTabScreen - firstMovingTabScreen;
|
||||
let translate = screen - dragData[screenAxis];
|
||||
|
||||
// Constrain the range over which the moving tabs can move between the edge of the tabstrip and periphery.
|
||||
// Add 1 to periphery so we don't overlap it.
|
||||
let startBound = this._rtlMode
|
||||
? endEdge(periphery) + 1 - firstMovingTabScreen
|
||||
: this._tabbrowserTabs[screenAxis] - firstMovingTabScreen;
|
||||
let endBound = this._rtlMode
|
||||
? endEdge(this._tabbrowserTabs) - lastMovingTabScreen
|
||||
: periphery[screenAxis] - 1 - lastMovingTabScreen;
|
||||
let firstTab = tabs.at(this._rtlMode ? -1 : 0);
|
||||
let lastTab = tabs.at(this._rtlMode ? 0 : -1);
|
||||
startBound = firstTab[screenAxis] - firstMovingTabScreen;
|
||||
endBound = endEdge(lastTab) - lastMovingTabScreen;
|
||||
translate = Math.min(Math.max(translate, startBound), endBound);
|
||||
|
||||
// Center the tab under the cursor if the tab is not under the cursor while dragging
|
||||
let draggedTabScreenAxis = draggedTab[screenAxis] + translate;
|
||||
if (
|
||||
(screen < draggedTabScreenAxis || screen > draggedTabScreenAxis + tabSize) &&
|
||||
draggedTabScreenAxis + tabSize < endBound &&
|
||||
draggedTabScreenAxis > startBound
|
||||
) {
|
||||
translate = screen - draggedTab[screenAxis] - tabSize / 2;
|
||||
// Ensure, after the above calculation, we are still within bounds
|
||||
translate = Math.min(Math.max(translate, startBound), endBound);
|
||||
}
|
||||
|
||||
if (!gBrowser.pinnedTabCount && !this._dragToPinPromoCard.shouldRender) {
|
||||
let pinnedDropIndicatorMargin = parseFloat(
|
||||
window.getComputedStyle(this._pinnedDropIndicator).marginInline
|
||||
);
|
||||
this._checkWithinPinnedContainerBounds({
|
||||
firstMovingTabScreen,
|
||||
lastMovingTabScreen,
|
||||
pinnedTabsStartEdge: this._rtlMode
|
||||
? endEdge(this._tabbrowserTabs.arrowScrollbox) + pinnedDropIndicatorMargin
|
||||
: this[screenAxis],
|
||||
pinnedTabsEndEdge: this._rtlMode
|
||||
? endEdge(this._tabbrowserTabs)
|
||||
: this._tabbrowserTabs.arrowScrollbox[screenAxis] - pinnedDropIndicatorMargin,
|
||||
translate,
|
||||
draggedTab,
|
||||
});
|
||||
}
|
||||
|
||||
dragData.translatePos = translate;
|
||||
|
||||
tabs = tabs.filter((t) => !movingTabsSet.has(t) || t == draggedTab);
|
||||
|
||||
/**
|
||||
* When the `draggedTab` is just starting to move, the `draggedTab` is in
|
||||
* its original location and the `dropElementIndex == draggedTab.elementIndex`.
|
||||
* Any tabs or tab group labels passed in as `item` will result in a 0 shift
|
||||
* because all of those items should also continue to appear in their original
|
||||
* locations.
|
||||
*
|
||||
* Once the `draggedTab` is more "backward" in the tab strip than its original
|
||||
* position, any tabs or tab group labels between the `draggedTab`'s original
|
||||
* `elementIndex` and the current `dropElementIndex` should shift "forward"
|
||||
* out of the way of the dragging tabs.
|
||||
*
|
||||
* When the `draggedTab` is more "forward" in the tab strip than its original
|
||||
* position, any tabs or tab group labels between the `draggedTab`'s original
|
||||
* `elementIndex` and the current `dropElementIndex` should shift "backward"
|
||||
* out of the way of the dragging tabs.
|
||||
*
|
||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroup.label} item
|
||||
* @param {number} dropElementIndex
|
||||
* @returns {number}
|
||||
*/
|
||||
let getTabShift = (item, dropElementIndex) => {
|
||||
if (item.elementIndex < draggedTab.elementIndex && item.elementIndex >= dropElementIndex) {
|
||||
return this._rtlMode ? -shiftSize : shiftSize;
|
||||
}
|
||||
if (item.elementIndex > draggedTab.elementIndex && item.elementIndex < dropElementIndex) {
|
||||
return this._rtlMode ? shiftSize : -shiftSize;
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
|
||||
let oldDropElementIndex = dragData.animDropElementIndex ?? movingTabs[0].elementIndex;
|
||||
|
||||
/**
|
||||
* Returns the higher % by which one element overlaps another
|
||||
* in the tab strip.
|
||||
*
|
||||
* When element 1 is further forward in the tab strip:
|
||||
*
|
||||
* p1 p2 p1+s1 p2+s2
|
||||
* | | | |
|
||||
* ---------------------------------
|
||||
* ========================
|
||||
* s1
|
||||
* ===================
|
||||
* s2
|
||||
* ==========
|
||||
* overlap
|
||||
*
|
||||
* When element 2 is further forward in the tab strip:
|
||||
*
|
||||
* p2 p1 p2+s2 p1+s1
|
||||
* | | | |
|
||||
* ---------------------------------
|
||||
* ========================
|
||||
* s2
|
||||
* ===================
|
||||
* s1
|
||||
* ==========
|
||||
* overlap
|
||||
*
|
||||
* @param {number} p1
|
||||
* Position (x or y value in screen coordinates) of element 1.
|
||||
* @param {number} s1
|
||||
* Size (width or height) of element 1.
|
||||
* @param {number} p2
|
||||
* Position (x or y value in screen coordinates) of element 2.
|
||||
* @param {number} s2
|
||||
* Size (width or height) of element 1.
|
||||
* @returns {number}
|
||||
* Percent between 0.0 and 1.0 (inclusive) of element 1 or element 2
|
||||
* that is overlapped by the other element. If the elements have
|
||||
* different sizes, then this returns the larger overlap percentage.
|
||||
*/
|
||||
function greatestOverlap(p1, s1, p2, s2) {
|
||||
let overlapSize;
|
||||
if (p1 < p2) {
|
||||
// element 1 starts first
|
||||
overlapSize = p1 + s1 - p2;
|
||||
} else {
|
||||
// element 2 starts first
|
||||
overlapSize = p2 + s2 - p1;
|
||||
}
|
||||
|
||||
// No overlap if size is <= 0
|
||||
if (overlapSize <= 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Calculate the overlap fraction from each element's perspective.
|
||||
let overlapPercent = Math.max(overlapSize / s1, overlapSize / s2);
|
||||
|
||||
return Math.min(overlapPercent, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine what tab/tab group label we're dragging over.
|
||||
*
|
||||
* When dragging right or downwards, the reference point for overlap is
|
||||
* the right or bottom edge of the most forward moving tab.
|
||||
*
|
||||
* When dragging left or upwards, the reference point for overlap is the
|
||||
* left or top edge of the most backward moving tab.
|
||||
*
|
||||
* @returns {Element|null}
|
||||
* The tab or tab group label that should be used to visually shift tab
|
||||
* strip elements out of the way of the dragged tab(s) during a drag
|
||||
* operation. Note: this is not used to determine where the dragged
|
||||
* tab(s) will be dropped, it is only used for visual animation at this
|
||||
* time.
|
||||
*/
|
||||
let getOverlappedElement = () => {
|
||||
let point = (screenForward ? lastMovingTabScreen : firstMovingTabScreen) + translate;
|
||||
let low = 0;
|
||||
let high = tabs.length - 1;
|
||||
while (low <= high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
if (tabs[mid] == draggedTab && ++mid > high) {
|
||||
break;
|
||||
}
|
||||
let element = tabs[mid];
|
||||
let elementForSize = elementToMove(element);
|
||||
screen = elementForSize[screenAxis] + getTabShift(element, oldDropElementIndex);
|
||||
|
||||
if (screen > point) {
|
||||
high = mid - 1;
|
||||
} else if (screen + bounds(elementForSize)[size] < point) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
let dropElement = getOverlappedElement();
|
||||
|
||||
let newDropElementIndex;
|
||||
if (dropElement) {
|
||||
newDropElementIndex = dropElement.elementIndex;
|
||||
} else {
|
||||
// When the dragged element(s) moves past a tab strip item, the dragged
|
||||
// element's leading edge starts dragging over empty space, resulting in
|
||||
// no overlapping `dropElement`. In these cases, try to fall back to the
|
||||
// previous animation drop element index to avoid unstable animations
|
||||
// (tab strip items snapping back and forth to shift out of the way of
|
||||
// the dragged element(s)).
|
||||
newDropElementIndex = oldDropElementIndex;
|
||||
|
||||
// We always want to have a `dropElement` so that we can determine where to
|
||||
// logically drop the dragged element(s).
|
||||
//
|
||||
// It's tempting to set `dropElement` to
|
||||
// `this.ariaFocusableItems.at(oldDropElementIndex)`, and that is correct
|
||||
// for most cases, but there are edge cases:
|
||||
//
|
||||
// 1) the drop element index range needs to be one larger than the number of
|
||||
// items that can move in the tab strip. The simplest example is when all
|
||||
// tabs are ungrouped and unpinned: for 5 tabs, the drop element index needs
|
||||
// to be able to go from 0 (become the first tab) to 5 (become the last tab).
|
||||
// `this.ariaFocusableItems.at(5)` would be `undefined` when dragging to the
|
||||
// end of the tab strip. In this specific case, it works to fall back to
|
||||
// setting the drop element to the last tab.
|
||||
//
|
||||
// 2) the `elementIndex` values of the tab strip items do not change during
|
||||
// the drag operation. When dragging the last tab or multiple tabs at the end
|
||||
// of the tab strip, having `dropElement` fall back to the last tab makes the
|
||||
// drop element one of the moving tabs. This can have some unexpected behavior
|
||||
// if not careful. Falling back to the last tab that's not moving (instead of
|
||||
// just the last tab) helps ensure that `dropElement` is always a stable target
|
||||
// to drop next to.
|
||||
//
|
||||
// 3) all of the elements in the tab strip are moving, in which case there can't
|
||||
// be a drop element and it should stay `undefined`.
|
||||
//
|
||||
// 4) we just started dragging and the `oldDropElementIndex` has its default
|
||||
// valuë of `movingTabs[0].elementIndex`. In this case, the drop element
|
||||
// shouldn't be a moving tab, so keep it `undefined`.
|
||||
let lastPossibleDropElement = this._rtlMode
|
||||
? tabs.find((t) => t != draggedTab)
|
||||
: tabs.findLast((t) => t != draggedTab);
|
||||
let maxElementIndexForDropElement = lastPossibleDropElement?.elementIndex;
|
||||
if (Number.isInteger(maxElementIndexForDropElement)) {
|
||||
let index = Math.min(oldDropElementIndex, maxElementIndexForDropElement);
|
||||
let oldDropElementCandidate = this._tabbrowserTabs.ariaFocusableItems.at(index);
|
||||
if (!movingTabsSet.has(oldDropElementCandidate)) {
|
||||
dropElement = oldDropElementCandidate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let moveOverThreshold;
|
||||
let overlapPercent;
|
||||
let dropBefore;
|
||||
if (dropElement) {
|
||||
let dropElementForOverlap = elementToMove(dropElement);
|
||||
|
||||
let dropElementScreen = dropElementForOverlap[screenAxis];
|
||||
let dropElementPos = dropElementScreen + getTabShift(dropElement, oldDropElementIndex);
|
||||
let dropElementSize = bounds(dropElementForOverlap)[size];
|
||||
let firstMovingTabPos = firstMovingTabScreen + translate;
|
||||
overlapPercent = greatestOverlap(
|
||||
firstMovingTabPos,
|
||||
shiftSize,
|
||||
dropElementPos,
|
||||
dropElementSize
|
||||
);
|
||||
|
||||
moveOverThreshold = gBrowser._tabGroupsEnabled
|
||||
? Services.prefs.getIntPref('browser.tabs.dragDrop.moveOverThresholdPercent') / 100
|
||||
: 0.5;
|
||||
moveOverThreshold = Math.min(1, Math.max(0, moveOverThreshold));
|
||||
let shouldMoveOver = overlapPercent > moveOverThreshold;
|
||||
if (logicalForward && shouldMoveOver) {
|
||||
newDropElementIndex++;
|
||||
} else if (!logicalForward && !shouldMoveOver) {
|
||||
newDropElementIndex++;
|
||||
if (newDropElementIndex > oldDropElementIndex) {
|
||||
// FIXME: Not quite sure what's going on here, but this check
|
||||
// prevents jittery back-and-forth movement of background tabs
|
||||
// in certain cases.
|
||||
newDropElementIndex = oldDropElementIndex;
|
||||
}
|
||||
}
|
||||
|
||||
// Recalculate the overlap with the updated drop index for when the
|
||||
// drop element moves over.
|
||||
dropElementPos = dropElementScreen + getTabShift(dropElement, newDropElementIndex);
|
||||
overlapPercent = greatestOverlap(
|
||||
firstMovingTabPos,
|
||||
shiftSize,
|
||||
dropElementPos,
|
||||
dropElementSize
|
||||
);
|
||||
dropBefore = firstMovingTabPos < dropElementPos;
|
||||
if (this._rtlMode) {
|
||||
dropBefore = !dropBefore;
|
||||
}
|
||||
}
|
||||
|
||||
this._tabbrowserTabs.removeAttribute('movingtab-group');
|
||||
this._resetGroupTarget(document.querySelector('[dragover-groupTarget]'));
|
||||
|
||||
delete dragData.shouldDropIntoCollapsedTabGroup;
|
||||
|
||||
// Default to dropping into `dropElement`'s tab group, if it exists.
|
||||
let dropElementGroup = dropElement?.group;
|
||||
let colorCode = dropElementGroup?.color;
|
||||
|
||||
let lastUnmovingTabInGroup = dropElementGroup?.tabs.findLast((t) => !movingTabsSet.has(t));
|
||||
if (
|
||||
isTab(dropElement) &&
|
||||
dropElementGroup &&
|
||||
dropElement == lastUnmovingTabInGroup &&
|
||||
!dropBefore
|
||||
) {
|
||||
// Dragging tab over the last tab of a tab group, but not enough
|
||||
// for it to drop into the tab group. Drop it after the tab group instead.
|
||||
dropElement = dropElementGroup;
|
||||
colorCode = undefined;
|
||||
} else if (isTabGroupLabel(dropElement)) {
|
||||
// Dropping right before the first tab in the tab group.
|
||||
dropElement = dropElementGroup.tabs[0];
|
||||
dropBefore = true;
|
||||
}
|
||||
this._setDragOverGroupColor(colorCode);
|
||||
this._tabbrowserTabs.toggleAttribute('movingtab-addToGroup', colorCode);
|
||||
this._tabbrowserTabs.toggleAttribute('movingtab-ungroup', !colorCode);
|
||||
|
||||
this.#applyDragoverIndicator(event, tabs, movingTabs, overlapPercent);
|
||||
|
||||
if (
|
||||
newDropElementIndex == oldDropElementIndex &&
|
||||
dropBefore == dragData.dropBefore &&
|
||||
dropElement == dragData.dropElement
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragData.dropElement = dropElement;
|
||||
dragData.dropBefore = dropBefore;
|
||||
dragData.animDropElementIndex = newDropElementIndex;
|
||||
}
|
||||
|
||||
handle_dragend(event) {
|
||||
super.handle_dragend(event);
|
||||
this.#removeDragOverBackground();
|
||||
gZenPinnedTabManager.removeTabContainersDragoverClass();
|
||||
}
|
||||
|
||||
#applyDragOverBackground(element) {
|
||||
if (this.#dragOverBackground && this.#lastDropTarget === element) {
|
||||
return false;
|
||||
}
|
||||
const margin = 2;
|
||||
const rect = window.windowUtils.getBoundsWithoutFlushing(element);
|
||||
this.#dragOverBackground = document.createElement('div');
|
||||
this.#dragOverBackground.id = 'zen-dragover-background';
|
||||
this.#dragOverBackground.style.height = `${rect.height - margin * 2}px`;
|
||||
this.#dragOverBackground.style.top = `${rect.top + margin}px`;
|
||||
gNavToolbox.appendChild(this.#dragOverBackground);
|
||||
this.#lastDropTarget = element;
|
||||
return true;
|
||||
}
|
||||
|
||||
#removeDragOverBackground() {
|
||||
if (this.#dragOverBackground) {
|
||||
this.#dragOverBackground.remove();
|
||||
this.#dragOverBackground = null;
|
||||
this.#lastDropTarget = null;
|
||||
}
|
||||
}
|
||||
|
||||
#applyDragoverIndicator(event, tabs, movingTabs, overlapPercent) {
|
||||
const separation = 4;
|
||||
const dropZoneSelector = ':is(.tabbrowser-tab, .zen-drop-target, .tab-group-label)';
|
||||
let shouldPlayHapticFeedback = false;
|
||||
let dropElement = event.target.closest(dropZoneSelector);
|
||||
if (!dropElement) {
|
||||
const numEssentials = gBrowser._numZenEssentials;
|
||||
const numPinned = gBrowser.pinnedTabCount - numEssentials;
|
||||
const tabToUse = event.target.closest(dropZoneSelector);
|
||||
if (!tabToUse) {
|
||||
this.#removeDragOverBackground();
|
||||
gZenPinnedTabManager.removeTabContainersDragoverClass();
|
||||
return;
|
||||
}
|
||||
const isPinned = tabToUse.pinned;
|
||||
const relativeTabs = tabs.slice(isPinned ? 0 : numPinned, isPinned ? numPinned : undefined);
|
||||
const draggedTabRect = elementToMove(tabToUse).getBoundingClientRect();
|
||||
dropElement = event.clientY > draggedTabRect.top ? relativeTabs.at(-1) : relativeTabs[0];
|
||||
}
|
||||
dropElement = elementToMove(dropElement);
|
||||
if (this.#lastDropTarget !== dropElement) {
|
||||
shouldPlayHapticFeedback = this.#lastDropTarget !== null;
|
||||
this.#removeDragOverBackground();
|
||||
}
|
||||
let canHightlightGroup =
|
||||
gZenFolders.highlightGroupOnDragOver(dropElement.parentElement, movingTabs) ||
|
||||
!dropElement.parentElement?.isZenFolder;
|
||||
if (isTab(dropElement)) {
|
||||
const indicator = gZenPinnedTabManager.dragIndicator;
|
||||
let rect = dropElement.getBoundingClientRect();
|
||||
let top = 0;
|
||||
const threshold =
|
||||
Services.prefs.getIntPref('browser.tabs.dragDrop.moveOverThresholdPercent') / 100;
|
||||
if (overlapPercent > threshold) {
|
||||
top = Math.round(rect.top + rect.height) + 'px';
|
||||
} else {
|
||||
top = Math.round(rect.top) + 'px';
|
||||
}
|
||||
if (indicator.style.top !== top) {
|
||||
shouldPlayHapticFeedback = true;
|
||||
}
|
||||
indicator.setAttribute('orientation', 'horizontal');
|
||||
indicator.style.setProperty('--indicator-left', rect.left + separation / 2 + 'px');
|
||||
indicator.style.setProperty('--indicator-width', rect.width - separation + 'px');
|
||||
indicator.style.top = top;
|
||||
indicator.style.removeProperty('left');
|
||||
} else if (dropElement.classList.contains('zen-drop-target') && canHightlightGroup) {
|
||||
// removeTabContainersDragoverClass Already calls a new haptic feedback
|
||||
shouldPlayHapticFeedback =
|
||||
this.#applyDragOverBackground(dropElement) && !gZenPinnedTabManager._dragIndicator;
|
||||
gZenPinnedTabManager.removeTabContainersDragoverClass();
|
||||
}
|
||||
|
||||
if (shouldPlayHapticFeedback) {
|
||||
Services.zen.playHapticFeedback();
|
||||
}
|
||||
}
|
||||
|
||||
#getDragImageOffset(tab) {
|
||||
const { offsetX, offsetY } = tab._dragData;
|
||||
const rect = tab.getBoundingClientRect();
|
||||
return {
|
||||
offsetX: offsetX - rect.left,
|
||||
offsetY: offsetY - rect.top,
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -11,4 +11,6 @@
|
||||
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenMods.mjs", { global: "current" });
|
||||
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenKeyboardShortcuts.mjs", { global: "current" });
|
||||
ChromeUtils.importESModule("chrome://browser/content/zen-components/ZenSessionStore.mjs", { global: "current" });
|
||||
|
||||
Services.scriptloader.loadSubScript("chrome://browser/content/zen-components/ZenDragAndDrop.js", this);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
content/browser/zen-components/ZenSessionStore.mjs (../../zen/common/modules/ZenSessionStore.mjs)
|
||||
content/browser/zen-components/ZenHasPolyfill.mjs (../../zen/common/modules/ZenHasPolyfill.mjs)
|
||||
content/browser/zen-components/ZenSidebarNotification.mjs (../../zen/common/modules/ZenSidebarNotification.mjs)
|
||||
content/browser/zen-components/ZenDragAndDrop.js (../../zen/common/ZenDragAndDrop.js)
|
||||
|
||||
content/browser/zen-components/ZenEmojisData.min.mjs (../../zen/common/emojis/ZenEmojisData.min.mjs)
|
||||
content/browser/zen-components/ZenEmojiPicker.mjs (../../zen/common/emojis/ZenEmojiPicker.mjs)
|
||||
|
||||
@@ -6,7 +6,7 @@ class ZenFolder extends MozTabbrowserTabGroup {
|
||||
#initialized = false;
|
||||
|
||||
static markup = `
|
||||
<hbox class="tab-group-label-container" pack="center">
|
||||
<hbox class="tab-group-label-container zen-drop-target" pack="center">
|
||||
<html:div class="tab-group-folder-icon"/>
|
||||
<label class="tab-group-label" role="button"/>
|
||||
<image class="tab-reset-button reset-icon" role="button" keyNav="false" data-l10n-id="zen-folders-unload-all-tooltip"/>
|
||||
|
||||
@@ -33,8 +33,6 @@ function formatRelativeTime(timestamp) {
|
||||
|
||||
class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
#ZEN_MAX_SUBFOLDERS = Services.prefs.getIntPref('zen.folders.max-subfolders', 5);
|
||||
#ZEN_EDGE_ZONE_THRESHOLD =
|
||||
Services.prefs.getIntPref('zen.view.drag-and-drop.edge-zone-threshold', 25) / 100;
|
||||
|
||||
#popup = null;
|
||||
#popupTimer = null;
|
||||
@@ -100,7 +98,7 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
.getElementById('context_zenChangeFolderSpace')
|
||||
.querySelector('menupopup');
|
||||
changeFolderSpace.innerHTML = '';
|
||||
for (const workspace of [...gZenWorkspaces._workspaceCache.workspaces].reverse()) {
|
||||
for (const workspace of [...gZenWorkspaces.getWorkspaces()].reverse()) {
|
||||
const item = gZenWorkspaces.generateMenuItemForWorkspace(workspace);
|
||||
item.addEventListener('command', (event) => {
|
||||
if (!this.#lastFolderContextMenu) return;
|
||||
@@ -1065,18 +1063,16 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
* @param {Array<MozTabbrowserTab>|null} movingTabs The tabs being moved.
|
||||
*/
|
||||
highlightGroupOnDragOver(folder, movingTabs) {
|
||||
if (folder === this.#lastHighlightedGroup) return;
|
||||
if (folder === this.#lastHighlightedGroup) return true;
|
||||
const tab = movingTabs ? movingTabs[0] : null;
|
||||
if (this.#lastHighlightedGroup && this.#lastHighlightedGroup !== folder) {
|
||||
this.#lastHighlightedGroup.removeAttribute('selected');
|
||||
if (this.#lastHighlightedGroup.collapsed) {
|
||||
this.updateFolderIcon(this.#lastHighlightedGroup, 'close');
|
||||
}
|
||||
this.#lastHighlightedGroup = null;
|
||||
}
|
||||
|
||||
if (
|
||||
folder &&
|
||||
folder?.isZenFolder &&
|
||||
(!folder.hasAttribute('split-view-group') || !folder.hasAttribute('selected')) &&
|
||||
folder !== tab?.group &&
|
||||
!(
|
||||
@@ -1084,13 +1080,13 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
movingTabs?.some((t) => gBrowser.isTabGroupLabel(t))
|
||||
)
|
||||
) {
|
||||
folder.setAttribute('selected', 'true');
|
||||
folder.style.transform = '';
|
||||
if (folder.collapsed) {
|
||||
this.updateFolderIcon(folder, 'open');
|
||||
}
|
||||
this.#lastHighlightedGroup = folder;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1103,55 +1099,6 @@ class nsZenFolders extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the dragover logic when dragging a tab or tab group label over another tab group label.
|
||||
* This function determines where the dragged item should be visually dropped (before/after the group, or inside it)
|
||||
* and updates related styling and highlighting.
|
||||
*
|
||||
* @param {MozTabbrowserTabGroupLabel} currentDropElement The tab group label currently being dragged over.
|
||||
* @param {MozTabbrowserTab|MozTabbrowserTabGroupLabel} draggedTab The tab or tab group label being dragged.
|
||||
* @param {number} overlapPercent The percentage of overlap between the dragged item and the drop target.
|
||||
* @param {Array<MozTabbrowserTab>} movingTabs An array of tabs that are currently being dragged together.
|
||||
* @param {boolean} currentDropBefore Indicates if the current drop position is before the middle of the drop element.
|
||||
* @param {string|undefined} currentColorCode The current color code for dragover highlighting.
|
||||
* @returns {{dropElement: MozTabbrowserTabGroup|MozTabbrowserTab|MozTabbrowserTabGroupLabel, colorCode: string|undefined, dropBefore: boolean}}
|
||||
* An object containing the updated drop element, color code for highlighting, and drop position.
|
||||
*/
|
||||
handleDragOverTabGroupLabel(
|
||||
currentDropElement,
|
||||
draggedTab,
|
||||
overlapPercent,
|
||||
movingTabs,
|
||||
currentDropBefore,
|
||||
currentColorCode
|
||||
) {
|
||||
let dropElement = currentDropElement;
|
||||
let dropBefore = currentDropBefore;
|
||||
let colorCode = currentColorCode;
|
||||
|
||||
const dropElementGroup = dropElement?.isZenFolder ? dropElement : dropElement?.group;
|
||||
const isSplitGroup = dropElement?.group?.hasAttribute('split-view-group');
|
||||
let firstGroupElem =
|
||||
dropElementGroup?.querySelector('.zen-tab-group-start')?.nextElementSibling;
|
||||
if (gBrowser.isTabGroup(firstGroupElem)) firstGroupElem = firstGroupElem.labelElement;
|
||||
|
||||
const isInMiddleZone =
|
||||
overlapPercent >= this.#ZEN_EDGE_ZONE_THRESHOLD &&
|
||||
overlapPercent <= 1 - this.#ZEN_EDGE_ZONE_THRESHOLD;
|
||||
const shouldDropInside = isInMiddleZone && !isSplitGroup;
|
||||
|
||||
if (shouldDropInside) {
|
||||
dropElement = firstGroupElem;
|
||||
dropBefore = true;
|
||||
this.highlightGroupOnDragOver(dropElementGroup, movingTabs);
|
||||
} else {
|
||||
colorCode = undefined;
|
||||
this.highlightGroupOnDragOver(null);
|
||||
}
|
||||
|
||||
return { dropElement, colorCode, dropBefore };
|
||||
}
|
||||
|
||||
#normalizeGroupItems(items) {
|
||||
return items
|
||||
.filter((item) => !item.hasAttribute('zen-empty-tab'))
|
||||
|
||||
@@ -503,15 +503,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
|
||||
moveToAnotherTabContainerIfNecessary(event, movingTabs) {
|
||||
movingTabs = [...movingTabs];
|
||||
if (!this.enabled) {
|
||||
return false;
|
||||
}
|
||||
movingTabs = [...movingTabs];
|
||||
try {
|
||||
const pinnedTabsTarget =
|
||||
event.target.closest('.zen-current-workspace-indicator') || this._isGoingToPinnedTabs;
|
||||
const pinnedTabsTarget = event.target.closest(
|
||||
':is(.zen-current-workspace-indicator, .zen-workspace-pinned-tabs-section)'
|
||||
);
|
||||
const essentialTabsTarget = event.target.closest('.zen-essentials-container');
|
||||
const tabsTarget = !this._isGoingToPinnedTabs;
|
||||
const tabsTarget = !pinnedTabsTarget;
|
||||
|
||||
// TODO: Solve the issue of adding a tab between two groups
|
||||
// Remove group labels from the moving tabs and replace it
|
||||
@@ -703,56 +704,6 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
: [separator];
|
||||
}
|
||||
|
||||
animateSeparatorMove(movingTabs, dropElement, isPinned) {
|
||||
let draggedTab = movingTabs[0];
|
||||
if (gBrowser.isTabGroupLabel(draggedTab) && draggedTab.group.isZenFolder) {
|
||||
this._isGoingToPinnedTabs = true;
|
||||
return;
|
||||
}
|
||||
if (draggedTab?.group?.hasAttribute('split-view-group')) {
|
||||
draggedTab = draggedTab.group;
|
||||
}
|
||||
const itemsToCheck = this.dragShiftableItems;
|
||||
let translate = movingTabs[isPinned ? movingTabs.length - 1 : 0].getBoundingClientRect().top;
|
||||
if (isPinned) {
|
||||
const rect = draggedTab.getBoundingClientRect();
|
||||
translate += rect.height;
|
||||
}
|
||||
const draggingTabHeight = movingTabs.reduce((acc, item) => {
|
||||
return acc + window.windowUtils.getBoundsWithoutFlushing(item).height;
|
||||
}, 0);
|
||||
if (typeof this._topToNormalTabs === 'undefined') {
|
||||
const rects = itemsToCheck.map((item) => window.windowUtils.getBoundsWithoutFlushing(item));
|
||||
this._topToNormalTabs = rects[0].top + rects.at(-1).height / (isPinned ? 2 : 4);
|
||||
}
|
||||
let topToNormalTabs = this._topToNormalTabs;
|
||||
const isGoingToPinnedTabs =
|
||||
translate < topToNormalTabs && gBrowser.pinnedTabCount - gBrowser._numZenEssentials > 0;
|
||||
const multiplier = isGoingToPinnedTabs !== isPinned ? (isGoingToPinnedTabs ? 1 : -1) : 0;
|
||||
this._isGoingToPinnedTabs = isGoingToPinnedTabs;
|
||||
if (!dropElement) {
|
||||
itemsToCheck.forEach((item) => {
|
||||
item.style.transform = `translateY(${draggingTabHeight * multiplier}px)`;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getLastTabBound(lastBound, lastTab, isDraggingFolder = false) {
|
||||
if (!lastTab.pinned || isDraggingFolder) {
|
||||
return lastBound;
|
||||
}
|
||||
const shiftedItems = this.dragShiftableItems;
|
||||
let totalHeight = shiftedItems.reduce((acc, item) => {
|
||||
return acc + window.windowUtils.getBoundsWithoutFlushing(item).height;
|
||||
}, 0);
|
||||
if (shiftedItems.length === 1) {
|
||||
// Means the new tab button is not at the top or not visible
|
||||
const lastTabRect = window.windowUtils.getBoundsWithoutFlushing(lastTab);
|
||||
totalHeight += lastTabRect.height;
|
||||
}
|
||||
return lastBound + totalHeight + 6;
|
||||
}
|
||||
|
||||
get dragIndicator() {
|
||||
if (!this._dragIndicator) {
|
||||
this._dragIndicator = document.createElement('div');
|
||||
|
||||
@@ -1281,9 +1281,9 @@
|
||||
width: calc(var(--indicator-width) - 2 * var(--zen-drag-indicator-height) - 4px);
|
||||
height: var(--zen-drag-indicator-height);
|
||||
transition:
|
||||
top 0.1s ease-out,
|
||||
left 0.1s ease-out,
|
||||
width 0.1s ease-out;
|
||||
top 0.05s ease-out,
|
||||
left 0.05s ease-out,
|
||||
width 0.05s ease-out;
|
||||
|
||||
&::before {
|
||||
left: calc(-2 * var(--zen-drag-indicator-height));
|
||||
@@ -1370,3 +1370,13 @@
|
||||
.tab-group-label-container[zen-dragtarget] {
|
||||
z-index: 9 !important;
|
||||
}
|
||||
|
||||
#zen-dragover-background {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
/* Extra width to cover the sidebar splitter */
|
||||
width: calc(100% + var(--zen-toolbox-padding));
|
||||
left: 0;
|
||||
pointer-events: none;
|
||||
background: var(--zen-primary-color);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
class nsZenWorkspace extends MozXULElement {
|
||||
static get markup() {
|
||||
return `
|
||||
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1" context="zenWorkspaceMoreActions">
|
||||
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator zen-drop-target" flex="1" context="zenWorkspaceMoreActions">
|
||||
<hbox class="zen-current-workspace-indicator-icon" />
|
||||
<label class="zen-current-workspace-indicator-name" flex="1" />
|
||||
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions" />
|
||||
|
||||
@@ -23,8 +23,6 @@ class nsZenWorkspaces {
|
||||
direction: null,
|
||||
};
|
||||
|
||||
_workspaceCache = {};
|
||||
|
||||
#lastScrollTime = 0;
|
||||
|
||||
bookmarkMenus = [
|
||||
|
||||
Reference in New Issue
Block a user