mirror of
https://github.com/zen-browser/desktop.git
synced 2026-01-08 22:33:22 +00:00
feat: Added support for essential tabs, b=no-bug, c=split-view, tabs, workspaces
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..1ac51353e63000f49075b12da83292312996a73c 100644
|
||||
index 97b931c3c7385a52d20204369fcf6d6999053687..6a136cf14d0bc081507a05f298f12ac7a7914601 100644
|
||||
--- a/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
+++ b/browser/components/tabbrowser/content/drag-and-drop.js
|
||||
@@ -32,6 +32,9 @@
|
||||
@@ -22,7 +22,18 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
if (
|
||||
(dropEffect == "move" || dropEffect == "copy") &&
|
||||
document == draggedTab.ownerDocument &&
|
||||
@@ -266,6 +272,15 @@
|
||||
@@ -130,10 +136,6 @@
|
||||
|
||||
// Pinned tabs in expanded vertical mode are on a grid format and require
|
||||
// different logic to drag and drop.
|
||||
- if (this._isContainerVerticalPinnedGrid(draggedTab)) {
|
||||
- this._animateExpandedPinnedTabMove(event);
|
||||
- return;
|
||||
- }
|
||||
this._animateTabMove(event);
|
||||
return;
|
||||
}
|
||||
@@ -266,6 +268,15 @@
|
||||
|
||||
this._tabDropIndicator.hidden = true;
|
||||
event.stopPropagation();
|
||||
@@ -38,7 +49,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
if (draggedTab && dropEffect == "copy") {
|
||||
let duplicatedDraggedTab;
|
||||
let duplicatedTabs = [];
|
||||
@@ -291,8 +306,9 @@
|
||||
@@ -291,8 +302,9 @@
|
||||
let translateOffsetY = oldTranslateY % tabHeight;
|
||||
let newTranslateX = oldTranslateX - translateOffsetX;
|
||||
let newTranslateY = oldTranslateY - translateOffsetY;
|
||||
@@ -50,7 +61,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
|
||||
if (this._isContainerVerticalPinnedGrid(draggedTab)) {
|
||||
// Update both translate axis for pinned vertical expanded tabs
|
||||
@@ -308,8 +324,8 @@
|
||||
@@ -308,8 +320,8 @@
|
||||
}
|
||||
} else {
|
||||
let tabs = this._tabbrowserTabs.ariaFocusableItems.slice(
|
||||
@@ -61,7 +72,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
);
|
||||
let size = this._tabbrowserTabs.verticalMode ? "height" : "width";
|
||||
let screenAxis = this._tabbrowserTabs.verticalMode
|
||||
@@ -362,11 +378,13 @@
|
||||
@@ -362,11 +374,13 @@
|
||||
this._dragToPinPromoCard,
|
||||
];
|
||||
let shouldPin =
|
||||
@@ -75,7 +86,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
isTab(draggedTab) &&
|
||||
draggedTab.pinned &&
|
||||
this._tabbrowserTabs.arrowScrollbox.contains(event.target);
|
||||
@@ -384,6 +402,7 @@
|
||||
@@ -384,6 +398,7 @@
|
||||
(oldTranslateY && oldTranslateY != newTranslateY);
|
||||
} else if (this._tabbrowserTabs.verticalMode) {
|
||||
shouldTranslate &&= oldTranslateY && oldTranslateY != newTranslateY;
|
||||
@@ -83,7 +94,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
} else {
|
||||
shouldTranslate &&= oldTranslateX && oldTranslateX != newTranslateX;
|
||||
}
|
||||
@@ -440,7 +459,7 @@
|
||||
@@ -440,7 +455,7 @@
|
||||
item.removeAttribute("tabdrop-samewindow");
|
||||
resolve();
|
||||
};
|
||||
@@ -92,7 +103,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
postTransitionCleanup();
|
||||
} else {
|
||||
let onTransitionEnd = transitionendEvent => {
|
||||
@@ -581,6 +600,7 @@
|
||||
@@ -581,6 +596,7 @@
|
||||
|
||||
let nextItem = this._tabbrowserTabs.ariaFocusableItems[newIndex];
|
||||
let tabGroup = isTab(nextItem) && nextItem.group;
|
||||
@@ -100,7 +111,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
gBrowser.loadTabs(urls, {
|
||||
inBackground,
|
||||
replace,
|
||||
@@ -618,7 +638,16 @@
|
||||
@@ -618,7 +634,16 @@
|
||||
this._expandGroupOnDrop(draggedTab);
|
||||
}
|
||||
this._resetTabsAfterDrop(draggedTab.ownerDocument);
|
||||
@@ -118,7 +129,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
if (
|
||||
dt.mozUserCancelled ||
|
||||
dt.dropEffect != "none" ||
|
||||
@@ -822,7 +851,10 @@
|
||||
@@ -822,7 +847,10 @@
|
||||
_getDragTarget(event, { ignoreSides = false } = {}) {
|
||||
let { target } = event;
|
||||
while (target) {
|
||||
@@ -130,7 +141,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
break;
|
||||
}
|
||||
target = target.parentNode;
|
||||
@@ -839,14 +871,17 @@
|
||||
@@ -839,14 +867,17 @@
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -150,7 +161,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
!this._tabbrowserTabs.expandOnHover
|
||||
);
|
||||
}
|
||||
@@ -877,7 +912,8 @@
|
||||
@@ -877,7 +908,8 @@
|
||||
isTabGroupLabel(draggedTab) &&
|
||||
draggedTab._dragData?.expandGroupOnDrop
|
||||
) {
|
||||
@@ -160,19 +171,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
}
|
||||
}
|
||||
|
||||
@@ -942,10 +978,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
|
||||
- let pinnedTabs = this._tabbrowserTabs.visibleTabs.slice(
|
||||
- 0,
|
||||
- gBrowser.pinnedTabCount
|
||||
- );
|
||||
+ let pinnedTabs = this._tabbrowserTabs.ariaFocusableItems.slice(0, gBrowser._numZenEssentials);
|
||||
let tabsPerRow = 0;
|
||||
let position = RTL_UI
|
||||
? window.windowUtils.getBoundsWithoutFlushing(
|
||||
@@ -1055,7 +1088,6 @@
|
||||
@@ -1055,7 +1087,6 @@
|
||||
// using updateDragImage. On Linux, we can use a panel.
|
||||
if (platform == "win" || platform == "macosx") {
|
||||
captureListener = function () {
|
||||
@@ -180,7 +179,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
};
|
||||
} else {
|
||||
// Create a panel to use it in setDragImage
|
||||
@@ -1093,7 +1125,6 @@
|
||||
@@ -1093,7 +1124,6 @@
|
||||
);
|
||||
dragImageOffset = dragImageOffset * scale;
|
||||
}
|
||||
@@ -188,7 +187,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
|
||||
// _dragData.offsetX/Y give the coordinates that the mouse should be
|
||||
// positioned relative to the corner of the new window created upon
|
||||
@@ -1112,7 +1143,7 @@
|
||||
@@ -1112,7 +1142,7 @@
|
||||
let dropEffect = this.getDropEffectForTabDrag(event);
|
||||
let isMovingInTabStrip = !fromTabList && dropEffect == "move";
|
||||
let collapseTabGroupDuringDrag =
|
||||
@@ -197,7 +196,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
|
||||
tab._dragData = {
|
||||
offsetX: this._tabbrowserTabs.verticalMode
|
||||
@@ -1122,7 +1153,7 @@
|
||||
@@ -1122,7 +1152,7 @@
|
||||
? event.screenY - window.screenY - tabOffset
|
||||
: event.screenY - window.screenY,
|
||||
scrollPos:
|
||||
@@ -206,7 +205,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
? this._tabbrowserTabs.pinnedTabsContainer.scrollPosition
|
||||
: this._tabbrowserTabs.arrowScrollbox.scrollPosition,
|
||||
screenX: event.screenX,
|
||||
@@ -1149,6 +1180,7 @@
|
||||
@@ -1149,6 +1179,7 @@
|
||||
|
||||
if (collapseTabGroupDuringDrag) {
|
||||
tab.group.collapsed = true;
|
||||
@@ -214,7 +213,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1173,6 +1205,7 @@
|
||||
@@ -1173,6 +1204,7 @@
|
||||
if (tabStripItemElement.hasAttribute("dragtarget")) {
|
||||
return;
|
||||
}
|
||||
@@ -222,7 +221,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
let isPinned = tab.pinned;
|
||||
let numPinned = gBrowser.pinnedTabCount;
|
||||
let allTabs = this._tabbrowserTabs.ariaFocusableItems;
|
||||
@@ -2457,7 +2490,7 @@
|
||||
@@ -2457,7 +2489,7 @@
|
||||
tab.style.left = "";
|
||||
tab.style.top = "";
|
||||
tab.style.maxWidth = "";
|
||||
@@ -231,7 +230,7 @@ index 97b931c3c7385a52d20204369fcf6d6999053687..1ac51353e63000f49075b12da8329231
|
||||
}
|
||||
for (let label of draggedTabDocument.getElementsByClassName(
|
||||
"tab-group-label-container"
|
||||
@@ -2467,7 +2500,7 @@
|
||||
@@ -2467,7 +2499,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..69af8c986add4f74f1c3105ccd6e5804fd33b80f 100644
|
||||
index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..23b134a8ba674978182415a8bac926f7600f564a 100644
|
||||
--- a/browser/components/tabbrowser/content/tab.js
|
||||
+++ b/browser/components/tabbrowser/content/tab.js
|
||||
@@ -21,6 +21,7 @@
|
||||
@@ -101,15 +101,6 @@ index 2dacb325190b6ae42ebeb3e9f0e862dc690ecdca..69af8c986add4f74f1c3105ccd6e5804
|
||||
}
|
||||
|
||||
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 (
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
#lastDropTarget = null;
|
||||
originalDragImageArgs = [];
|
||||
#isOutOfWindow = false;
|
||||
#maxTabsPerRow = 0;
|
||||
|
||||
constructor(tabbrowserTabs) {
|
||||
super(tabbrowserTabs);
|
||||
@@ -68,6 +69,8 @@
|
||||
super.init();
|
||||
this.handle_windowDragEnter = this.handle_windowDragEnter.bind(this);
|
||||
window.addEventListener('dragleave', this.handle_windowDragLeave.bind(this), true);
|
||||
const dragOverBind = this.handle_dragover.bind(this);
|
||||
gZenWorkspaces.workspaceIcons.addEventListener('dragover', dragOverBind);
|
||||
}
|
||||
|
||||
startTabDrag(event, tab, ...args) {
|
||||
@@ -92,6 +95,10 @@
|
||||
for (let i = 0; i < movingTabs.length; i++) {
|
||||
const tab = movingTabs[i];
|
||||
const tabClone = tab.cloneNode(true);
|
||||
if (tabClone.hasAttribute('zen-essential')) {
|
||||
tabClone.style.minWidth = tab.style.maxWidth = '54px';
|
||||
tabClone.style.minHeight = tab.style.maxHeight = '50px';
|
||||
}
|
||||
if (i > 0) {
|
||||
tabClone.style.transform = `translate(${i * 4}px, -${i * (tabRect.height - 4)}px)`;
|
||||
tabClone.style.opacity = '0.2';
|
||||
@@ -99,6 +106,17 @@
|
||||
}
|
||||
wrapper.appendChild(tabClone);
|
||||
}
|
||||
this.#maybeCreateDragImageDot(movingTabs, wrapper);
|
||||
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;
|
||||
}
|
||||
|
||||
#maybeCreateDragImageDot(movingTabs, wrapper) {
|
||||
if (movingTabs.length > 1) {
|
||||
const dot = document.createElement('div');
|
||||
dot.textContent = movingTabs.length;
|
||||
@@ -116,17 +134,15 @@
|
||||
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);
|
||||
if (event.target.closest('#zen-essentials') && isTab(draggedTab)) {
|
||||
return this.#animateVerticalPinnedGridDragOver(event);
|
||||
} else if (this._fakeEssentialTab) {
|
||||
this.#makeDragImageNonEssential(event);
|
||||
}
|
||||
let dragData = draggedTab._dragData;
|
||||
let movingTabs = dragData.movingTabs;
|
||||
let movingTabsSet = dragData.movingTabsSet;
|
||||
@@ -532,9 +548,6 @@
|
||||
|
||||
handle_dragover(event) {
|
||||
super.handle_dragover(event);
|
||||
if (event.target.closest('#tabbrowser-tabbox')) {
|
||||
gZenViewSplitter.onBrowserDragOverToSplit(event);
|
||||
}
|
||||
}
|
||||
|
||||
handle_windowDragEnter(event) {
|
||||
@@ -563,12 +576,16 @@
|
||||
this.#isOutOfWindow = true;
|
||||
this.clearDragOverVisuals();
|
||||
const dt = event.dataTransfer;
|
||||
let dragData = draggedTab._dragData;
|
||||
let movingTabs = dragData.movingTabs;
|
||||
if (!this._browserDragImageWrapper) {
|
||||
const wrappingDiv = document.createXULElement('vbox');
|
||||
wrappingDiv.style.borderRadius = canvas.style.borderRadius = '8px';
|
||||
wrappingDiv.style.border = '2px solid white';
|
||||
wrappingDiv.style.width = 200 + 'px';
|
||||
wrappingDiv.style.height = 130 + 'px';
|
||||
wrappingDiv.style.position = 'relative';
|
||||
this.#maybeCreateDragImageDot(movingTabs, wrappingDiv);
|
||||
wrappingDiv.appendChild(canvas);
|
||||
this._browserDragImageWrapper = wrappingDiv;
|
||||
document.documentElement.appendChild(wrappingDiv);
|
||||
@@ -585,12 +602,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -599,7 +610,9 @@
|
||||
draggedTab = draggedTab.group;
|
||||
}
|
||||
if (
|
||||
!gZenStartup.isReady ||
|
||||
gReduceMotion ||
|
||||
!dropElement ||
|
||||
dropElement.group !== draggedTab.group ||
|
||||
dropElement.hasAttribute('zen-essential') ||
|
||||
draggedTab.hasAttribute('zen-essential')
|
||||
@@ -666,10 +679,12 @@
|
||||
}
|
||||
|
||||
handle_dragend(event) {
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
this.ZenDragAndDropService.onDragEnd();
|
||||
super.handle_dragend(event);
|
||||
this.#removeDragOverBackground();
|
||||
gZenPinnedTabManager.removeTabContainersDragoverClass();
|
||||
this.#maybeClearVerticalPinnedGridDragOver(draggedTab);
|
||||
this.originalDragImageArgs = [];
|
||||
window.removeEventListener('dragover', this.handle_windowDragEnter, { capture: true });
|
||||
this.#isOutOfWindow = false;
|
||||
@@ -715,26 +730,33 @@
|
||||
const separation = 4;
|
||||
const dropZoneSelector = ':is(.tabbrowser-tab, .zen-drop-target, .tab-group-label)';
|
||||
let shouldPlayHapticFeedback = false;
|
||||
let showIndicatorUnderNewTabButton = false;
|
||||
let dropElement = event.target.closest(dropZoneSelector);
|
||||
let dropBefore;
|
||||
if (!dropElement) {
|
||||
const numEssentials = gBrowser._numZenEssentials;
|
||||
const numPinned = gBrowser.pinnedTabCount - numEssentials;
|
||||
const tabToUse = event.target.closest(dropZoneSelector);
|
||||
if (!tabToUse) {
|
||||
this.clearDragOverVisuals();
|
||||
return;
|
||||
if (event.target.classList.contains('zen-workspace-empty-space')) {
|
||||
dropElement = this._tabbrowserTabs.ariaFocusableItems.at(-1);
|
||||
// Only if there are no normal tabs to drop after
|
||||
showIndicatorUnderNewTabButton = !tabs.some((tab) => !(tab.group || tab).pinned);
|
||||
} else {
|
||||
const numEssentials = gBrowser._numZenEssentials;
|
||||
const numPinned = gBrowser.pinnedTabCount - numEssentials;
|
||||
const tabToUse = event.target.closest(dropZoneSelector);
|
||||
if (!tabToUse) {
|
||||
this.clearDragOverVisuals();
|
||||
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];
|
||||
}
|
||||
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._isContainerVerticalPinnedGrid(dropElement) && isTab(draggedTab)) {
|
||||
console.log('TODO: Handle essential tab dragover in vertical pinned grid');
|
||||
return;
|
||||
}
|
||||
this.#maybeClearVerticalPinnedGridDragOver(draggedTab);
|
||||
if (this.#lastDropTarget !== dropElement) {
|
||||
shouldPlayHapticFeedback = this.#lastDropTarget !== null;
|
||||
this.#removeDragOverBackground();
|
||||
@@ -742,7 +764,7 @@
|
||||
let isZenFolder = dropElement.parentElement?.isZenFolder;
|
||||
let canHightlightGroup =
|
||||
gZenFolders.highlightGroupOnDragOver(dropElement.parentElement, movingTabs) || !isZenFolder;
|
||||
let rect = dropElement.getBoundingClientRect();
|
||||
let rect = window.windowUtils.getBoundsWithoutFlushing(dropElement);
|
||||
const overlapPercent = (event.clientY - rect.top) / rect.height;
|
||||
// We wan't to leave a small threshold (20% for example) so we can drag tabs below and above
|
||||
// a folder label without dragging into the folder.
|
||||
@@ -758,7 +780,10 @@
|
||||
this.clearDragOverVisuals();
|
||||
return;
|
||||
}
|
||||
if (isTab(dropElement) || dropIntoFolder) {
|
||||
if (isTab(dropElement) || dropIntoFolder || showIndicatorUnderNewTabButton) {
|
||||
if (showIndicatorUnderNewTabButton) {
|
||||
rect = window.windowUtils.getBoundsWithoutFlushing(this.#dragShiftableItems.at(-1));
|
||||
}
|
||||
const indicator = gZenPinnedTabManager.dragIndicator;
|
||||
let top = 0;
|
||||
threshold =
|
||||
@@ -808,5 +833,277 @@
|
||||
offsetY: event.clientY - rect.top,
|
||||
};
|
||||
}
|
||||
|
||||
#animateVerticalPinnedGridDragOver(event) {
|
||||
let draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
let dragData = draggedTab._dragData;
|
||||
let movingTabs = dragData.movingTabs;
|
||||
this.clearDragOverVisuals();
|
||||
|
||||
if (!this._fakeEssentialTab) {
|
||||
const numEssentials = gBrowser._numZenEssentials;
|
||||
let pinnedTabs = this._tabbrowserTabs.ariaFocusableItems.slice(0, numEssentials);
|
||||
this._fakeEssentialTab = document.createXULElement('vbox');
|
||||
this._fakeEssentialTab.elementIndex = numEssentials;
|
||||
this.#makeDragImageEssential(event);
|
||||
delete dragData.animDropElementIndex;
|
||||
if (draggedTab.hasAttribute('zen-essential')) {
|
||||
draggedTab.style.visibility = 'hidden';
|
||||
} else {
|
||||
event.target.closest('.zen-essentials-container').appendChild(this._fakeEssentialTab);
|
||||
gZenWorkspaces.updateTabsContainers();
|
||||
pinnedTabs.push(this._fakeEssentialTab);
|
||||
}
|
||||
let tabsPerRow = 0;
|
||||
let position = RTL_UI
|
||||
? window.windowUtils.getBoundsWithoutFlushing(this._tabbrowserTabs.pinnedTabsContainer)
|
||||
.right
|
||||
: 0;
|
||||
for (let pinnedTab of pinnedTabs) {
|
||||
let tabPosition;
|
||||
let rect = window.windowUtils.getBoundsWithoutFlushing(pinnedTab);
|
||||
if (RTL_UI) {
|
||||
tabPosition = rect.right;
|
||||
if (tabPosition > position) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
tabPosition = rect.left;
|
||||
if (tabPosition < position) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tabsPerRow++;
|
||||
position = tabPosition;
|
||||
}
|
||||
this.#maxTabsPerRow = tabsPerRow;
|
||||
}
|
||||
let usingFakeElement = !!this._fakeEssentialTab.parentElement;
|
||||
let elementMoving = usingFakeElement ? this._fakeEssentialTab : draggedTab;
|
||||
if (usingFakeElement) {
|
||||
movingTabs = [this._fakeEssentialTab];
|
||||
}
|
||||
|
||||
let dragDataScreenX = usingFakeElement ? this._fakeEssentialTab.screenX : dragData.screenX;
|
||||
let dragDataScreenY = usingFakeElement ? this._fakeEssentialTab.screenY : dragData.screenY;
|
||||
|
||||
dragData.animLastScreenX ??= dragDataScreenX;
|
||||
dragData.animLastScreenY ??= dragDataScreenY;
|
||||
|
||||
let screenX = event.screenX;
|
||||
let screenY = event.screenY;
|
||||
|
||||
if (screenY == dragData.animLastScreenY && screenX == dragData.animLastScreenX) {
|
||||
return;
|
||||
}
|
||||
|
||||
let tabs = this._tabbrowserTabs.visibleTabs.slice(0, gBrowser._numZenEssentials);
|
||||
if (usingFakeElement) {
|
||||
tabs.push(this._fakeEssentialTab);
|
||||
}
|
||||
|
||||
let directionX = screenX > dragData.animLastScreenX;
|
||||
let directionY = screenY > dragData.animLastScreenY;
|
||||
dragData.animLastScreenY = screenY;
|
||||
dragData.animLastScreenX = screenX;
|
||||
|
||||
let { width: tabWidth, height: tabHeight } = elementMoving.getBoundingClientRect();
|
||||
tabWidth += 4; // Add 4px to account for the gap
|
||||
tabHeight += 4;
|
||||
let shiftSizeX = tabWidth;
|
||||
let shiftSizeY = tabHeight;
|
||||
dragData.tabWidth = tabWidth;
|
||||
dragData.tabHeight = tabHeight;
|
||||
|
||||
// Move the dragged tab based on the mouse position.
|
||||
let firstTabInRow;
|
||||
let lastTabInRow;
|
||||
let lastTab = tabs.at(-1);
|
||||
if (RTL_UI) {
|
||||
firstTabInRow =
|
||||
tabs.length >= this.#maxTabsPerRow ? tabs[this.#maxTabsPerRow - 1] : lastTab;
|
||||
lastTabInRow = tabs[0];
|
||||
} else {
|
||||
firstTabInRow = tabs[0];
|
||||
lastTabInRow = tabs.length >= this.#maxTabsPerRow ? tabs[this.#maxTabsPerRow - 1] : lastTab;
|
||||
}
|
||||
let lastMovingTabScreenX = movingTabs.at(-1).screenX;
|
||||
let lastMovingTabScreenY = movingTabs.at(-1).screenY;
|
||||
let firstMovingTabScreenX = movingTabs[0].screenX;
|
||||
let firstMovingTabScreenY = movingTabs[0].screenY;
|
||||
let translateX = screenX - dragDataScreenX;
|
||||
let translateY = screenY - dragDataScreenY;
|
||||
let firstBoundX = firstTabInRow.screenX - firstMovingTabScreenX;
|
||||
let firstBoundY = this._tabbrowserTabs.screenY - firstMovingTabScreenY;
|
||||
let lastBoundX =
|
||||
lastTabInRow.screenX +
|
||||
lastTabInRow.getBoundingClientRect().width -
|
||||
(lastMovingTabScreenX + tabWidth);
|
||||
let lastBoundY = lastTab.screenY - lastMovingTabScreenY;
|
||||
translateX = Math.min(Math.max(translateX, firstBoundX), lastBoundX);
|
||||
translateY = Math.min(Math.max(translateY, firstBoundY), lastBoundY);
|
||||
|
||||
// Center the tab under the cursor if the tab is not under the cursor while dragging
|
||||
if (
|
||||
screen < elementMoving.screenY + translateY ||
|
||||
screen > elementMoving.screenY + tabHeight + translateY
|
||||
) {
|
||||
translateY = screen - elementMoving.screenY - tabHeight / 2;
|
||||
}
|
||||
|
||||
dragData.translateX = translateX;
|
||||
dragData.translateY = translateY;
|
||||
|
||||
// Determine what tab we're dragging over.
|
||||
// * Single tab dragging: Point of reference is the center of the dragged tab. If that
|
||||
// point touches a background tab, the dragged tab would take that
|
||||
// tab's position when dropped.
|
||||
// * Multiple tabs dragging: All dragged tabs are one "giant" tab with two
|
||||
// points of reference (center of tabs on the extremities). When
|
||||
// mouse is moving from top to bottom, the bottom reference gets activated,
|
||||
// otherwise the top reference will be used. Everything else works the same
|
||||
// as single tab dragging.
|
||||
// * 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 == elementMoving);
|
||||
let firstTabCenterX = firstMovingTabScreenX + translateX + tabWidth / 2;
|
||||
let lastTabCenterX = lastMovingTabScreenX + translateX + tabWidth / 2;
|
||||
let tabCenterX = directionX ? lastTabCenterX : firstTabCenterX;
|
||||
let firstTabCenterY = firstMovingTabScreenY + translateY + tabHeight / 2;
|
||||
let lastTabCenterY = lastMovingTabScreenY + translateY + tabHeight / 2;
|
||||
let tabCenterY = directionY ? lastTabCenterY : firstTabCenterY;
|
||||
|
||||
let shiftNumber = this.#maxTabsPerRow - movingTabs.length;
|
||||
|
||||
let getTabShift = (tab, dropIndex) => {
|
||||
if (tab.elementIndex < elementMoving.elementIndex && tab.elementIndex >= dropIndex) {
|
||||
// If tab is at the end of a row, shift back and down
|
||||
let tabRow = Math.ceil((tab.elementIndex + 1) / this.#maxTabsPerRow);
|
||||
let shiftedTabRow = Math.ceil(
|
||||
(tab.elementIndex + 1 + movingTabs.length) / this.#maxTabsPerRow
|
||||
);
|
||||
if (tab.elementIndex && tabRow != shiftedTabRow) {
|
||||
return [RTL_UI ? tabWidth * shiftNumber : -tabWidth * shiftNumber, shiftSizeY];
|
||||
}
|
||||
return [RTL_UI ? -shiftSizeX : shiftSizeX, 0];
|
||||
}
|
||||
if (tab.elementIndex > elementMoving.elementIndex && tab.elementIndex < dropIndex) {
|
||||
// If tab is not index 0 and at the start of a row, shift across and up
|
||||
let tabRow = Math.floor(tab.elementIndex / this.#maxTabsPerRow);
|
||||
let shiftedTabRow = Math.floor(
|
||||
(tab.elementIndex - movingTabs.length) / this.#maxTabsPerRow
|
||||
);
|
||||
if (tab.elementIndex && tabRow != shiftedTabRow) {
|
||||
return [RTL_UI ? -tabWidth * shiftNumber : tabWidth * shiftNumber, -shiftSizeY];
|
||||
}
|
||||
return [RTL_UI ? shiftSizeX : -shiftSizeX, 0];
|
||||
}
|
||||
return [0, 0];
|
||||
};
|
||||
|
||||
let low = 0;
|
||||
let high = tabs.length - 1;
|
||||
let newIndex = -1;
|
||||
let oldIndex = dragData.animDropElementIndex ?? movingTabs[0].elementIndex;
|
||||
while (low <= high) {
|
||||
let mid = Math.floor((low + high) / 2);
|
||||
if (tabs[mid] == elementMoving && ++mid > high) {
|
||||
break;
|
||||
}
|
||||
let [shiftX, shiftY] = getTabShift(tabs[mid], oldIndex);
|
||||
screenX = tabs[mid].screenX + shiftX;
|
||||
screenY = tabs[mid].screenY + shiftY;
|
||||
|
||||
if (screenY + tabHeight < tabCenterY) {
|
||||
low = mid + 1;
|
||||
} else if (screenY > tabCenterY) {
|
||||
high = mid - 1;
|
||||
} else if (RTL_UI ? screenX + tabWidth < tabCenterX : screenX > tabCenterX) {
|
||||
high = mid - 1;
|
||||
} else if (RTL_UI ? screenX > tabCenterX : screenX + tabWidth < tabCenterX) {
|
||||
low = mid + 1;
|
||||
} else {
|
||||
newIndex = tabs[mid].elementIndex;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (newIndex >= oldIndex && newIndex < tabs.length) {
|
||||
newIndex++;
|
||||
}
|
||||
|
||||
if (newIndex < 0) {
|
||||
newIndex = oldIndex;
|
||||
}
|
||||
|
||||
if (newIndex == dragData.animDropElementIndex) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragData.animDropElementIndex = newIndex;
|
||||
dragData.dropElement = tabs[Math.min(newIndex, tabs.length - 1)];
|
||||
dragData.dropBefore = newIndex < tabs.length;
|
||||
|
||||
// Shift background tabs to leave a gap where the dragged tab
|
||||
// would currently be dropped.
|
||||
for (let tab of tabs) {
|
||||
if (tab != draggedTab) {
|
||||
let [shiftX, shiftY] = getTabShift(tab, newIndex);
|
||||
tab.style.transform = shiftX || shiftY ? `translate(${shiftX}px, ${shiftY}px)` : '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#maybeClearVerticalPinnedGridDragOver(draggedTab) {
|
||||
if (this._fakeEssentialTab) {
|
||||
this._fakeEssentialTab.remove();
|
||||
delete this._fakeEssentialTab;
|
||||
draggedTab.style.visibility = '';
|
||||
for (let tab of this._tabbrowserTabs.visibleTabs.slice(0, gBrowser._numZenEssentials)) {
|
||||
tab.style.transform = '';
|
||||
}
|
||||
gZenWorkspaces.updateTabsContainers();
|
||||
}
|
||||
}
|
||||
|
||||
#makeDragImageEssential(event) {
|
||||
const dt = event.dataTransfer;
|
||||
const draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
const dragData = draggedTab._dragData;
|
||||
const [wrapper] = this.originalDragImageArgs;
|
||||
const tab = wrapper.firstElementChild;
|
||||
tab.setAttribute('zen-essential', 'true');
|
||||
tab.setAttribute('pinned', 'true');
|
||||
tab.setAttribute('selected', 'true');
|
||||
tab.style.minWidth = tab.style.maxWidth = wrapper.style.width = '54px';
|
||||
tab.style.minHeight = tab.style.maxHeight = wrapper.style.height = '50px';
|
||||
const offsetY = dragData.offsetY;
|
||||
const offsetX = dragData.offsetX;
|
||||
// Apply a transform translate to the tab in order to center it within the drag image
|
||||
tab.style.transform = `translate(${(54 - offsetX) / 2}px, ${(50 - offsetY) / 2}px)`;
|
||||
gZenPinnedTabManager.setEssentialTabIcon(tab);
|
||||
dt.updateDragImage(wrapper, -16, -16);
|
||||
}
|
||||
|
||||
#makeDragImageNonEssential(event) {
|
||||
const dt = event.dataTransfer;
|
||||
const draggedTab = event.dataTransfer.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
const wrapper = this.originalDragImageArgs[0];
|
||||
const tab = wrapper.firstElementChild;
|
||||
tab.style.setProperty('transition', 'none', 'important');
|
||||
tab.removeAttribute('zen-essential');
|
||||
tab.removeAttribute('pinned');
|
||||
tab.style.minWidth = tab.style.maxWidth = '';
|
||||
tab.style.minHeight = tab.style.maxHeight = '';
|
||||
tab.style.transform = '';
|
||||
const rect = window.windowUtils.getBoundsWithoutFlushing(draggedTab);
|
||||
wrapper.style.width = rect.width + 'px';
|
||||
wrapper.style.height = rect.height + 'px';
|
||||
setTimeout(() => {
|
||||
tab.style.transition = '';
|
||||
dt.updateDragImage(...this.originalDragImageArgs);
|
||||
}, 50);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -262,17 +262,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
gBrowser.tabContainer.tabDragAndDrop.finishMoveTogetherSelectedTabs(draggedTab);
|
||||
}
|
||||
if (
|
||||
!draggedTab ||
|
||||
this._canDrop ||
|
||||
this._hasAnimated ||
|
||||
this.fakeBrowser ||
|
||||
!this._lastOpenedTab ||
|
||||
(this._lastOpenedTab &&
|
||||
this._lastOpenedTab.getAttribute('zen-workspace-id') !==
|
||||
draggedTab.getAttribute('zen-workspace-id') &&
|
||||
!this._lastOpenedTab.hasAttribute('zen-essential')) ||
|
||||
draggedTab === this._lastOpenedTab
|
||||
(this._lastOpenedTab.getAttribute('zen-workspace-id') !==
|
||||
draggedTab.getAttribute('zen-workspace-id') &&
|
||||
!this._lastOpenedTab.hasAttribute('zen-essential'))
|
||||
) {
|
||||
this._lastOpenedTab = gBrowser.selectedTab;
|
||||
}
|
||||
if (!draggedTab || this._canDrop || this._hasAnimated || this.fakeBrowser) {
|
||||
return;
|
||||
}
|
||||
if (draggedTab.splitView) {
|
||||
@@ -315,17 +312,12 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
const oldTab = this._lastOpenedTab;
|
||||
this._canDrop = true;
|
||||
Services.zen.playHapticFeedback();
|
||||
{
|
||||
this._draggingTab = draggedTab;
|
||||
gBrowser.selectedTab = oldTab;
|
||||
this._hasAnimated = true;
|
||||
this.tabBrowserPanel.setAttribute('dragging-split', 'true');
|
||||
for (const tab of gBrowser.tabs) {
|
||||
tab.style.removeProperty('transform');
|
||||
if (tab.group) {
|
||||
tab.group.style.removeProperty('transform');
|
||||
}
|
||||
}
|
||||
// Add a min width to all the browser elements to prevent them from resizing
|
||||
const panelsWidth = gBrowser.tabbox.getBoundingClientRect().width;
|
||||
let numOfTabsToDivide = 2;
|
||||
@@ -394,10 +386,12 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
]);
|
||||
if (this._finishAllAnimatingPromise) {
|
||||
this._finishAllAnimatingPromise.then(() => {
|
||||
draggedTab.linkedBrowser.docShellIsActive = false;
|
||||
draggedTab.linkedBrowser
|
||||
.closest('.browserSidebarContainer')
|
||||
.classList.remove('deck-selected');
|
||||
if (draggedTab !== oldTab) {
|
||||
draggedTab.linkedBrowser.docShellIsActive = false;
|
||||
draggedTab.linkedBrowser
|
||||
.closest('.browserSidebarContainer')
|
||||
.classList.remove('deck-selected');
|
||||
}
|
||||
this.fakeBrowser.addEventListener('dragleave', this.onBrowserDragEndToSplit);
|
||||
this._canDrop = true;
|
||||
});
|
||||
@@ -1748,6 +1742,12 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
return false;
|
||||
}
|
||||
|
||||
const droppedOnTab = gZenGlanceManager.getTabOrGlanceParent(gBrowser.getTabForBrowser(browser));
|
||||
if (droppedOnTab === gBrowser.selectedTab) {
|
||||
this.createEmptySplit(dropSide == 'right');
|
||||
return true;
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = this._draggingTab;
|
||||
this._draggingTab = null;
|
||||
const browserContainer = draggedTab.linkedBrowser?.closest('.browserSidebarContainer');
|
||||
@@ -1755,7 +1755,6 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
browserContainer.style.opacity = '0';
|
||||
}
|
||||
|
||||
const droppedOnTab = gZenGlanceManager.getTabOrGlanceParent(gBrowser.getTabForBrowser(browser));
|
||||
if (droppedOnTab && droppedOnTab !== draggedTab) {
|
||||
// Calculate which side of the target browser the drop occurred
|
||||
// const browserRect = browser.getBoundingClientRect();
|
||||
@@ -1997,13 +1996,14 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
createEmptySplit() {
|
||||
createEmptySplit(rightSide = true) {
|
||||
const selectedTab = gBrowser.selectedTab;
|
||||
const emptyTab = gZenWorkspaces._emptyTab;
|
||||
let tabs = rightSide ? [selectedTab, emptyTab] : [emptyTab, selectedTab];
|
||||
const data = {
|
||||
tabs: [selectedTab, emptyTab],
|
||||
tabs: tabs,
|
||||
gridType: 'grid',
|
||||
layoutTree: this.calculateLayoutTree([selectedTab, emptyTab], 'grid'),
|
||||
layoutTree: this.calculateLayoutTree(tabs, 'grid'),
|
||||
};
|
||||
this._data.push(data);
|
||||
this.activateSplitView(data);
|
||||
@@ -2036,7 +2036,11 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
this.removeTabFromGroup(emptyTab, groupIndex, { forUnsplit: true });
|
||||
gBrowser.selectedTab = selectedTab;
|
||||
this.resetTabState(emptyTab, false);
|
||||
this.splitTabs([selectedTab, newSelectedTab], 'grid', 1);
|
||||
this.splitTabs(
|
||||
rightSide ? [selectedTab, newSelectedTab] : [newSelectedTab, selectedTab],
|
||||
'grid',
|
||||
rightSide ? 1 : 0
|
||||
);
|
||||
} else {
|
||||
cleanup();
|
||||
}
|
||||
|
||||
@@ -84,12 +84,16 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
|
||||
onTabIconChanged(tab, url = null) {
|
||||
tab.dispatchEvent(new CustomEvent('ZenTabIconChanged', { bubbles: true, detail: { tab } }));
|
||||
const iconUrl = url ?? tab.iconImage.src;
|
||||
if (tab.hasAttribute('zen-essential')) {
|
||||
tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`);
|
||||
this.setEssentialTabIcon(tab, url);
|
||||
}
|
||||
}
|
||||
|
||||
setEssentialTabIcon(tab, url = null) {
|
||||
const iconUrl = url ?? tab.getAttribute('image') ?? '';
|
||||
tab.style.setProperty('--zen-essential-tab-icon', `url(${iconUrl})`);
|
||||
}
|
||||
|
||||
_onTabResetPinButton(event, tab) {
|
||||
event.stopPropagation();
|
||||
this._resetTabToStoredState(tab);
|
||||
|
||||
@@ -715,7 +715,6 @@
|
||||
}
|
||||
|
||||
& .zen-essentials-container {
|
||||
will-change: transform;
|
||||
justify-content: center;
|
||||
grid-template-columns: 1fr !important;
|
||||
padding: 0 !important;
|
||||
@@ -1108,8 +1107,6 @@
|
||||
}
|
||||
|
||||
.zen-essentials-container {
|
||||
will-change: transform;
|
||||
|
||||
overflow: hidden;
|
||||
gap: 4px;
|
||||
transition:
|
||||
@@ -1373,6 +1370,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Drag and drop */
|
||||
|
||||
#zen-dragover-background {
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
|
||||
@@ -2404,7 +2404,7 @@ class nsZenWorkspaces {
|
||||
return workspaceData;
|
||||
}
|
||||
|
||||
async updateTabsContainers(target = undefined, forAnimation = false) {
|
||||
updateTabsContainers(target = undefined, forAnimation = false) {
|
||||
this.makeSureEmptyTabIsFirst();
|
||||
if (target && !target.target?.parentNode) {
|
||||
target = null;
|
||||
@@ -2414,7 +2414,7 @@ class nsZenWorkspaces {
|
||||
if (target?.type === 'TabClose' || target?.type === 'TabOpen') {
|
||||
animateContainer = target.target.pinned;
|
||||
}
|
||||
await this.onPinnedTabsResize(
|
||||
this.onPinnedTabsResize(
|
||||
// This is what happens when we join a resize observer, an event listener
|
||||
// while using it as a method.
|
||||
[{ target: (target?.target ? target.target : target) ?? this.pinnedTabsContainer }],
|
||||
@@ -2462,7 +2462,7 @@ class nsZenWorkspaces {
|
||||
}
|
||||
}
|
||||
|
||||
async onPinnedTabsResize(entries, forAnimation = false, animateContainer = false) {
|
||||
onPinnedTabsResize(entries, forAnimation = false, animateContainer = false) {
|
||||
if (
|
||||
document.documentElement.hasAttribute('inDOMFullscreen') ||
|
||||
!this._hasInitializedTabsStrip ||
|
||||
@@ -2486,9 +2486,7 @@ class nsZenWorkspaces {
|
||||
// Get all workspaces that have the same userContextId
|
||||
const activeWorkspace = this.getActiveWorkspace();
|
||||
const userContextId = activeWorkspace.containerTabId;
|
||||
const workspaces = this._workspaceCache.filter(
|
||||
(w) => w.containerTabId === userContextId && w.uuid !== originalWorkspaceId
|
||||
);
|
||||
const workspaces = this.getWorkspaces().filter((w) => w.containerTabId === userContextId);
|
||||
workspacesIds.push(...workspaces.map((w) => w.uuid));
|
||||
} else {
|
||||
workspacesIds.push(originalWorkspaceId);
|
||||
|
||||
Reference in New Issue
Block a user