From fc7f10aef13fc1b7e38d8a22a184fe293cb3ab0f Mon Sep 17 00:00:00 2001 From: Andrey Bochkarev <50177704+octaviusz@users.noreply.github.com> Date: Sat, 10 Jan 2026 16:41:28 +0300 Subject: [PATCH] feat: Add dnd switch support for groups, p=#11854 * feat: Add dnd switch support for groups * refactor: Use `changeFolderToSpace` for drag-and-drop workspace changes * refactor: Use optional parameter instead of attribute * fix: Formatting * refactor: Move condition inside function * Refactor tab group handling in drag-and-drop Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com> * feat: Only allow double click rename on labels, b=no-bug, c=common --------- Signed-off-by: mr. m <91018726+mr-cheffy@users.noreply.github.com> Co-authored-by: mr. m <91018726+mr-cheffy@users.noreply.github.com> --- prefs/zen/workspaces.yaml | 3 ++ src/zen/common/modules/ZenUIManager.mjs | 6 +++- src/zen/drag-and-drop/ZenDragAndDrop.js | 14 +++++---- src/zen/folders/ZenFolders.mjs | 33 ++++++++++++++-------- src/zen/sessionstore/ZenWindowSync.sys.mjs | 11 +++++++- src/zen/workspaces/ZenWorkspaces.mjs | 10 +++++-- 6 files changed, 56 insertions(+), 21 deletions(-) diff --git a/prefs/zen/workspaces.yaml b/prefs/zen/workspaces.yaml index 9b61e17bb..fdd9a2877 100644 --- a/prefs/zen/workspaces.yaml +++ b/prefs/zen/workspaces.yaml @@ -32,6 +32,9 @@ - name: zen.workspaces.separate-essentials value: true +- name: zen.workspaces.dnd-switch-padding + value: 10 + - name: zen.workspaces.debug value: '@cond' condition: '!defined(MOZILLA_OFFICIAL)' # Section: Pinned tabs management diff --git a/src/zen/common/modules/ZenUIManager.mjs b/src/zen/common/modules/ZenUIManager.mjs index 300f649a5..55b793e4a 100644 --- a/src/zen/common/modules/ZenUIManager.mjs +++ b/src/zen/common/modules/ZenUIManager.mjs @@ -1389,8 +1389,12 @@ window.gZenVerticalTabsManager = { Services.prefs.getBoolPref('browser.tabs.closeTabByDblclick')) && isTab) || !gZenVerticalTabsManager._prefsSidebarExpanded - ) + ) { return; + } + if (isTab && !target.closest('.tab-label-container')) { + return; + } this._tabEdited = target.closest('.tabbrowser-tab') || target.closest('.zen-current-workspace-indicator-name') || diff --git a/src/zen/drag-and-drop/ZenDragAndDrop.js b/src/zen/drag-and-drop/ZenDragAndDrop.js index 8a4809f19..1fe6ff121 100644 --- a/src/zen/drag-and-drop/ZenDragAndDrop.js +++ b/src/zen/drag-and-drop/ZenDragAndDrop.js @@ -577,7 +577,7 @@ } #shouldSwitchSpace(event) { - const padding = 10; + const padding = Services.prefs.getIntPref('zen.workspaces.dnd-switch-padding'); // If we are hovering over the edges of the gNavToolbox or the splitter, we // can change the workspace after a short delay. const splitter = document.getElementById('zen-sidebar-splitter'); @@ -601,8 +601,7 @@ #handle_sidebarDragOver(event) { const dt = event.dataTransfer; const draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); - // TODO: Add support for switching spaces when dragging folders and split-view groups. - if (!isTab(draggedTab) || draggedTab.hasAttribute('zen-essential')) { + if (draggedTab.hasAttribute('zen-essential')) { this.clearSpaceSwitchTimer(); return; } @@ -683,18 +682,23 @@ this.clearSpaceSwitchTimer(); super.handle_drop(event); const dt = event.dataTransfer; + const activeWorkspace = gZenWorkspaces.activeWorkspace; let draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0); if ( isTab(draggedTab) && !draggedTab.hasAttribute('zen-essential') && - draggedTab.getAttribute('zen-workspace-id') != gZenWorkspaces.activeWorkspace + draggedTab.getAttribute('zen-workspace-id') != activeWorkspace ) { const movingTabs = draggedTab._dragData?.movingTabs || [draggedTab]; for (let tab of movingTabs) { - tab.setAttribute('zen-workspace-id', gZenWorkspaces.activeWorkspace); + tab.setAttribute('zen-workspace-id', activeWorkspace); } gBrowser.selectedTab = draggedTab; } + if (isTabGroupLabel(draggedTab)) { + draggedTab = draggedTab.group; + gZenFolders.changeFolderToSpace(draggedTab, activeWorkspace, { hasDndSwitch: true }); + } gZenWorkspaces.updateTabsContainers(); } diff --git a/src/zen/folders/ZenFolders.mjs b/src/zen/folders/ZenFolders.mjs index 49ba35bc5..b51a912c8 100644 --- a/src/zen/folders/ZenFolders.mjs +++ b/src/zen/folders/ZenFolders.mjs @@ -442,29 +442,40 @@ class nsZenFolders extends nsZenDOMOperatedFeature { } } - changeFolderToSpace(folder, workspaceId) { - const currentWorkspace = gZenWorkspaces.getActiveWorkspaceFromCache(); - if (currentWorkspace.uuid === workspaceId) { + changeFolderToSpace(folder, workspaceId, { hasDndSwitch = false } = {}) { + if (folder.getAttribute('zen-workspace-id') == workspaceId) { return; } + const workspaceElement = gZenWorkspaces.workspaceElement(workspaceId); - const pinnedTabsContainer = workspaceElement.pinnedTabsContainer; - pinnedTabsContainer.insertBefore(folder, pinnedTabsContainer.lastChild); + + if (!hasDndSwitch) { + const pinnedTabsContainer = workspaceElement.pinnedTabsContainer; + pinnedTabsContainer.insertBefore(folder, pinnedTabsContainer.lastChild); + } + + const { lastSelectedWorkspaceTabs } = gZenWorkspaces; + for (const tab of folder.tabs) { - tab.setAttribute('zen-workspace-id', workspaceId); // This sets the ID for the current folder and any sub-folder // we may encounter + tab.setAttribute('zen-workspace-id', workspaceId); tab.group.setAttribute('zen-workspace-id', workspaceId); gBrowser.TabStateFlusher.flush(tab.linkedBrowser); - if (gZenWorkspaces.lastSelectedWorkspaceTabs[workspaceId] === tab) { + + if (lastSelectedWorkspaceTabs[workspaceId] === tab) { // This tab is no longer the last selected tab in the previous workspace because it's being moved to a new workspace - delete gZenWorkspaces.lastSelectedWorkspaceTabs[workspaceId]; + delete lastSelectedWorkspaceTabs[workspaceId]; } } + folder.dispatchEvent(new CustomEvent('ZenFolderChangedWorkspace', { bubbles: true })); - gZenWorkspaces.changeWorkspaceWithID(workspaceId).then(() => { - gBrowser.moveTabTo(folder, { elementIndex: 0, forceUngrouped: true }); - }); + + if (!hasDndSwitch) { + gZenWorkspaces.changeWorkspaceWithID(workspaceId).then(() => { + gBrowser.moveTabTo(folder, { elementIndex: 0, forceUngrouped: true }); + }); + } } canDropElement(element, targetElement) { diff --git a/src/zen/sessionstore/ZenWindowSync.sys.mjs b/src/zen/sessionstore/ZenWindowSync.sys.mjs index bf0c20cfa..ac693560a 100644 --- a/src/zen/sessionstore/ZenWindowSync.sys.mjs +++ b/src/zen/sessionstore/ZenWindowSync.sys.mjs @@ -461,7 +461,7 @@ class nsZenWindowSync { !originalSibling.hasAttribute('id') || originalSibling.hasAttribute('zen-empty-tab'); } - gBrowser.zenHandleTabMove(aOriginalItem, () => { + gBrowser.zenHandleTabMove(aTargetItem, () => { if (isFirstTab) { let container; const parentGroup = aOriginalItem.group; @@ -1062,6 +1062,15 @@ class nsZenWindowSync { } // Tab groups already have an ID upon creation. this.#runOnAllWindows(window, (win) => { + // Check if a group with this ID already exists in the target window. + const existingGroup = this.getItemFromWindow(win, tabGroup.id); + if (existingGroup) { + this.log( + `Attempted to create group ${tabGroup.id} in window ${win}, ` + `but it already exists.` + ); + return; // Do not proceed with creation. + } + const newGroup = isFolder ? win.gZenFolders.createFolder([], {}) : win.gBrowser.addTabGroup([]); diff --git a/src/zen/workspaces/ZenWorkspaces.mjs b/src/zen/workspaces/ZenWorkspaces.mjs index 8410f9882..67fa19790 100644 --- a/src/zen/workspaces/ZenWorkspaces.mjs +++ b/src/zen/workspaces/ZenWorkspaces.mjs @@ -685,8 +685,11 @@ class nsZenWorkspaces { const delta = event.delta * 300; const stripWidth = - window.windowUtils.getBoundsWithoutFlushing(document.getElementById('navigator-toolbox')).width + - window.windowUtils.getBoundsWithoutFlushing(document.getElementById('zen-sidebar-splitter')).width * 2; + window.windowUtils.getBoundsWithoutFlushing(document.getElementById('navigator-toolbox')) + .width + + window.windowUtils.getBoundsWithoutFlushing(document.getElementById('zen-sidebar-splitter')) + .width * + 2; let translateX = this._swipeState.lastDelta + delta; // Add a force multiplier as we are translating the strip depending on how close to the edge we are let forceMultiplier = Math.min(1, 1 - Math.abs(translateX) / (stripWidth * 4.5)); // 4.5 instead of 4 to add a bit of a buffer @@ -1681,7 +1684,8 @@ class nsZenWorkspaces { !(this.#inChangingWorkspace && !forAnimation && !this._alwaysAnimatePaddingTop) ) { delete this._alwaysAnimatePaddingTop; - const essentialsHeight = window.windowUtils.getBoundsWithoutFlushing(essentialContainer).height; + const essentialsHeight = + window.windowUtils.getBoundsWithoutFlushing(essentialContainer).height; if (!forAnimation && animateContainer && gZenUIManager.motion && gZenStartup.isReady) { gZenUIManager.motion.animate( workspaceElement,