mirror of
https://github.com/zen-browser/desktop.git
synced 2026-08-13 02:05:18 +00:00
feat: Added partial support for split views, b=no-bug, c=split-view
This commit is contained in:
@@ -36,6 +36,9 @@ const EVENTS = [
|
||||
'TabGroupRemoved',
|
||||
'TabGroupMoved',
|
||||
|
||||
//'ZenTabRemovedFromSplit',
|
||||
//'ZenSplitViewTabsSplit',
|
||||
|
||||
'TabSelect',
|
||||
|
||||
'focus',
|
||||
@@ -358,6 +361,8 @@ class nsZenWindowSync {
|
||||
gBrowser.unpinTab(aTargetItem);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
aTargetItem.pinned = aOriginalItem.pinned;
|
||||
}
|
||||
|
||||
this.#moveItemToMatchOriginal(aOriginalItem, aTargetItem, aWindow, {
|
||||
@@ -673,10 +678,9 @@ class nsZenWindowSync {
|
||||
* @param {Object} aPreviousTab - The previously selected tab.
|
||||
*/
|
||||
async #onTabSwitchOrWindowFocus(aWindow, aPreviousTab = null) {
|
||||
const selectedTab = aWindow.gBrowser.selectedTab;
|
||||
// On some occasions, such as when closing a window, this
|
||||
// function might be called multiple times for the same tab.
|
||||
if (selectedTab === this.#lastSelectedTab) {
|
||||
if (aWindow.gBrowser.selectedTab === this.#lastSelectedTab) {
|
||||
return;
|
||||
}
|
||||
if (aPreviousTab?._zenContentsVisible) {
|
||||
@@ -691,15 +695,20 @@ class nsZenWindowSync {
|
||||
await this.#swapBrowserDocShellsAsync(otherTabToShow, aPreviousTab);
|
||||
}
|
||||
}
|
||||
if (selectedTab._zenContentsVisible || selectedTab.hasAttribute('zen-empty-tab')) {
|
||||
return;
|
||||
}
|
||||
const otherSelectedTab = this.#getActiveTabFromOtherWindows(aWindow, selectedTab.id);
|
||||
selectedTab._zenContentsVisible = true;
|
||||
if (otherSelectedTab) {
|
||||
delete otherSelectedTab._zenContentsVisible;
|
||||
await this.#swapBrowserDocShellsAsync(selectedTab, otherSelectedTab);
|
||||
let promises = [];
|
||||
for (const browserView of aWindow.gBrowser.selectedBrowsers) {
|
||||
const selectedTab = aWindow.gBrowser.getTabForBrowser(browserView);
|
||||
if (selectedTab._zenContentsVisible || selectedTab.hasAttribute('zen-empty-tab')) {
|
||||
continue;
|
||||
}
|
||||
const otherSelectedTab = this.#getActiveTabFromOtherWindows(aWindow, selectedTab.id);
|
||||
selectedTab._zenContentsVisible = true;
|
||||
if (otherSelectedTab) {
|
||||
delete otherSelectedTab._zenContentsVisible;
|
||||
promises.push(this.#swapBrowserDocShellsAsync(selectedTab, otherSelectedTab));
|
||||
}
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -931,11 +940,14 @@ class nsZenWindowSync {
|
||||
const window = tabGroup.ownerGlobal;
|
||||
const isFolder = tabGroup.isZenFolder;
|
||||
const isSplitView = tabGroup.hasAttribute('split-view-group');
|
||||
if (isSplitView) {
|
||||
return; // Split view groups are synced via ZenSplitViewTabsSplit event.
|
||||
}
|
||||
// Tab groups already have an ID upon creation.
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const newGroup = isFolder
|
||||
? win.gZenFolders.createFolder([], {})
|
||||
: win.gBrowser.addTabGroup({ splitView: isSplitView });
|
||||
: win.gBrowser.addTabGroup([]);
|
||||
newGroup.id = tabGroup.id;
|
||||
newGroup.alreadySynced = true;
|
||||
this.#syncItemWithOriginal(
|
||||
@@ -969,6 +981,34 @@ class nsZenWindowSync {
|
||||
on_TabGroupUpdate(aEvent) {
|
||||
return this.#delegateGenericSyncEvent(aEvent, SYNC_FLAG_ICON | SYNC_FLAG_LABEL);
|
||||
}
|
||||
|
||||
on_ZenTabRemovedFromSplit(aEvent) {
|
||||
const tab = aEvent.target;
|
||||
const window = tab.ownerGlobal;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const targetTab = this.#getItemFromWindow(win, tab.id);
|
||||
if (targetTab && win.gZenViewSplitter) {
|
||||
win.gZenViewSplitter.removeTabFromGroup(targetTab);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
on_ZenSplitViewTabsSplit(aEvent) {
|
||||
const tabGroup = aEvent.target;
|
||||
const window = tabGroup.ownerGlobal;
|
||||
const tabs = tabGroup.tabs;
|
||||
this.#runOnAllWindows(window, (win) => {
|
||||
const otherWindowTabs = tabs
|
||||
.map((tab) => this.#getItemFromWindow(win, tab.id))
|
||||
.filter(Boolean);
|
||||
if (otherWindowTabs.length > 0 && win.gZenViewSplitter) {
|
||||
const group = win.gZenViewSplitter.splitTabs(otherWindowTabs, 'grid');
|
||||
if (group) {
|
||||
group.id = tabGroup.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const ZenWindowSync = new nsZenWindowSync();
|
||||
|
||||
@@ -202,6 +202,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
this.resetTabState(tab, forUnsplit);
|
||||
if (tab.group && tab.group.hasAttribute('split-view-group')) {
|
||||
gBrowser.ungroupTab(tab);
|
||||
this.#dispatchItemEvent('ZenTabRemovedFromSplit', tab);
|
||||
}
|
||||
if (group.tabs.length < 2) {
|
||||
// We need to remove all remaining tabs from the group when unsplitting
|
||||
@@ -895,6 +896,21 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches a custom event on a tab.
|
||||
*
|
||||
* @param {string} eventName - The name of the event to dispatch.
|
||||
* @param {HTMLElement} item - The item on which to dispatch the event.
|
||||
*/
|
||||
#dispatchItemEvent(eventName, item) {
|
||||
const event = new CustomEvent(eventName, {
|
||||
detail: { item },
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
});
|
||||
item.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a group.
|
||||
*
|
||||
@@ -905,6 +921,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
for (const tab of group.tabs.reverse()) {
|
||||
if (tab.group?.hasAttribute('split-view-group')) {
|
||||
gBrowser.ungroupTab(tab);
|
||||
this.#dispatchItemEvent('ZenTabRemovedFromSplit', tab);
|
||||
}
|
||||
}
|
||||
if (this.currentView === groupIndex) {
|
||||
@@ -1069,7 +1086,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
* @param {string|undefined} gridType - The type of grid layout.
|
||||
*/
|
||||
splitTabs(tabs, gridType, initialIndex = 0) {
|
||||
this.#withoutSplitViewTransition(() => {
|
||||
return this.#withoutSplitViewTransition(() => {
|
||||
// TODO: Add support for splitting essential tabs
|
||||
tabs = tabs.filter((t) => !t.hidden && !t.hasAttribute('zen-empty-tab'));
|
||||
if (tabs.length < 2 || tabs.length > this.MAX_TABS) {
|
||||
@@ -1105,7 +1122,8 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
return;
|
||||
}
|
||||
this.activateSplitView(group, true);
|
||||
return;
|
||||
this.#dispatchItemEvent('ZenSplitViewTabsSplit', group);
|
||||
return group;
|
||||
}
|
||||
|
||||
// We are here if none of the tabs have been previously split
|
||||
@@ -1150,6 +1168,8 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
return;
|
||||
}
|
||||
this.activateSplitView(splitData);
|
||||
this.#dispatchItemEvent('ZenSplitViewTabsSplit', splitGroup);
|
||||
return splitData;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1855,9 +1875,10 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
}
|
||||
|
||||
// We can't create an empty group, so only create if we have tabs
|
||||
let group = null;
|
||||
if (tabs?.length) {
|
||||
// Create a new group with the initial tabs
|
||||
gBrowser.addTabGroup(tabs, {
|
||||
group = gBrowser.addTabGroup(tabs, {
|
||||
label: '',
|
||||
showCreateUI: false,
|
||||
insertBefore: tabs[0],
|
||||
@@ -1865,7 +1886,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
});
|
||||
}
|
||||
|
||||
return null;
|
||||
return group;
|
||||
}
|
||||
|
||||
storeDataForSessionStore() {
|
||||
@@ -1936,7 +1957,7 @@ class nsZenViewSplitter extends nsZenDOMOperatedFeature {
|
||||
#withoutSplitViewTransition(callback) {
|
||||
this.tabBrowserPanel.classList.add('zen-split-view-no-transition');
|
||||
try {
|
||||
callback();
|
||||
return callback();
|
||||
} finally {
|
||||
requestAnimationFrame(() => {
|
||||
this.tabBrowserPanel.classList.remove('zen-split-view-no-transition');
|
||||
|
||||
Reference in New Issue
Block a user