mirror of
https://github.com/zen-browser/desktop.git
synced 2026-08-13 02:05:18 +00:00
feat: Implement workspace sync store into the session file, b=closes #10857, c=common, tabs, tests, workspaces
This commit is contained in:
@@ -6,7 +6,6 @@
|
||||
# the window is fully loaded.
|
||||
# Make sure they are loaded before the global-scripts.inc file.
|
||||
<script type="text/javascript" src="chrome://browser/content/zen-sets.js"></script>
|
||||
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenWorkspacesSync.mjs"></script>
|
||||
|
||||
<script type="module" src="chrome://browser/content/zen-components/ZenHasPolyfill.mjs"></script>
|
||||
<script type="module" src="chrome://browser/content/zen-components/ZenWorkspaces.mjs"></script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/places/content/editBookmark.js b/browser/components/places/content/editBookmark.js
|
||||
index f562f19741d882d92365da531b55e2810a0e79ea..9339e1158b074c41fc19bf91cbfde3c4016594b9 100644
|
||||
index f562f19741d882d92365da531b55e2810a0e79ea..a68ce8191314845c589f3a9f14b56028e0532628 100644
|
||||
--- a/browser/components/places/content/editBookmark.js
|
||||
+++ b/browser/components/places/content/editBookmark.js
|
||||
@@ -387,6 +387,10 @@ var gEditItemOverlay = {
|
||||
@@ -31,34 +31,11 @@ index f562f19741d882d92365da531b55e2810a0e79ea..9339e1158b074c41fc19bf91cbfde3c4
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1280,6 +1288,148 @@ var gEditItemOverlay = {
|
||||
@@ -1280,6 +1288,128 @@ var gEditItemOverlay = {
|
||||
get bookmarkState() {
|
||||
return this._bookmarkState;
|
||||
},
|
||||
+
|
||||
+ async _initWorkspaceSelector() {
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
+ }
|
||||
+ this._workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
+
|
||||
+ const selectElement = this._workspaceSelect;
|
||||
+
|
||||
+ // Clear any existing options
|
||||
+ while (selectElement.firstChild) {
|
||||
+ selectElement.removeChild(selectElement.firstChild);
|
||||
+ }
|
||||
+
|
||||
+ // For each workspace, create an option element
|
||||
+ for (let workspace of this._workspaces) {
|
||||
+ const option = document.createElementNS("http://www.w3.org/1999/xhtml", "option");
|
||||
+ option.textContent = workspace.name;
|
||||
+ option.value = workspace.uuid;
|
||||
+ selectElement.appendChild(option);
|
||||
+ }
|
||||
+
|
||||
+ selectElement.disabled = this.readOnly;
|
||||
+ },
|
||||
+ async onWorkspaceSelectionChange(event) {
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
@@ -129,7 +106,10 @@ index f562f19741d882d92365da531b55e2810a0e79ea..9339e1158b074c41fc19bf91cbfde3c4
|
||||
+ if(document.documentElement.getAttribute("windowtype") === "Places:Organizer") {
|
||||
+ return;
|
||||
+ }
|
||||
+ this._workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
+ const { ZenSessionStore } = ChromeUtils.importESModule(
|
||||
+ "resource:///modules/zen/ZenSessionManager.sys.mjs"
|
||||
+ );
|
||||
+ this._workspaces = ZenSessionStore.getClonedSpaces();
|
||||
+ const workspaceList = this._workspaceList;
|
||||
+ if(aInfo.node?.bookmarkGuid) {
|
||||
+ this._selectedWorkspaces = await ZenWorkspaceBookmarksStorage.getBookmarkWorkspaces(aInfo.node.bookmarkGuid);
|
||||
@@ -180,7 +160,7 @@ index f562f19741d882d92365da531b55e2810a0e79ea..9339e1158b074c41fc19bf91cbfde3c4
|
||||
};
|
||||
|
||||
ChromeUtils.defineLazyGetter(gEditItemOverlay, "_folderTree", () => {
|
||||
@@ -1318,6 +1468,9 @@ for (let elt of [
|
||||
@@ -1318,6 +1448,9 @@ for (let elt of [
|
||||
"locationField",
|
||||
"keywordField",
|
||||
"tagsField",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
diff --git a/browser/components/sessionstore/SessionStore.sys.mjs b/browser/components/sessionstore/SessionStore.sys.mjs
|
||||
index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9bbfa3d264 100644
|
||||
index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..5ce4eb0b21cf4ed2b8e7c6ad0c57e77416a2ab48 100644
|
||||
--- a/browser/components/sessionstore/SessionStore.sys.mjs
|
||||
+++ b/browser/components/sessionstore/SessionStore.sys.mjs
|
||||
@@ -127,6 +127,8 @@ const TAB_EVENTS = [
|
||||
@@ -56,9 +56,9 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
+ for (let window of this._browserWindows) {
|
||||
+ windowPromises.push(lazy.TabStateFlusher.flushWindow(window));
|
||||
+ }
|
||||
+ aWindow._zenPromiseNewWindowRestored = new Promise((resolve) => Promise.all(windowPromises).finally(() => {
|
||||
+ lazy.ZenSessionStore.restoreNewWindow(aWindow, this, resolve);
|
||||
+ }));
|
||||
+ Promise.all(windowPromises).finally(() => {
|
||||
+ lazy.ZenSessionStore.restoreNewWindow(aWindow, this);
|
||||
+ });
|
||||
+ }
|
||||
|
||||
if (this._restoreLastWindow && aWindow.toolbar.visible) {
|
||||
@@ -137,7 +137,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
// window data is still in _statesToRestore
|
||||
continue;
|
||||
}
|
||||
@@ -5625,11 +5650,15 @@ var SessionStoreInternal = {
|
||||
@@ -5625,11 +5650,16 @@ var SessionStoreInternal = {
|
||||
}
|
||||
|
||||
let tabbrowser = aWindow.gBrowser;
|
||||
@@ -150,11 +150,12 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
+ winData.activeZenSpace = aWindow.gZenWorkspaces?.activeWorkspace || null;
|
||||
+ winData.splitViewData = aWindow.gZenViewSplitter?.storeDataForSessionStore();
|
||||
+ winData.folders = aWindow.gZenFolders?.storeDataForSessionStore() || [];
|
||||
+ winData.spaces = aWindow.gZenWorkspaces?.getWorkspaces();
|
||||
+
|
||||
// update the internal state data for this window
|
||||
for (let tab of tabs) {
|
||||
if (tab == aWindow.FirefoxViewHandler.tab) {
|
||||
@@ -5652,7 +5681,7 @@ var SessionStoreInternal = {
|
||||
@@ -5652,7 +5682,7 @@ var SessionStoreInternal = {
|
||||
// a window is closed, point to the first item in the tab strip instead (it will never be the Firefox View tab,
|
||||
// since it's only inserted into the tab strip after it's selected).
|
||||
if (aWindow.FirefoxViewHandler.tab?.selected) {
|
||||
@@ -163,7 +164,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
winData.title = tabbrowser.tabs[0].label;
|
||||
}
|
||||
winData.selected = selectedIndex;
|
||||
@@ -5765,8 +5794,8 @@ var SessionStoreInternal = {
|
||||
@@ -5765,8 +5795,8 @@ var SessionStoreInternal = {
|
||||
// selectTab represents.
|
||||
let selectTab = 0;
|
||||
if (overwriteTabs) {
|
||||
@@ -174,17 +175,17 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
selectTab = Math.min(selectTab, winData.tabs.length);
|
||||
}
|
||||
|
||||
@@ -5809,6 +5838,9 @@ var SessionStoreInternal = {
|
||||
@@ -5809,6 +5839,9 @@ var SessionStoreInternal = {
|
||||
winData.tabs,
|
||||
winData.groups ?? []
|
||||
);
|
||||
+ aWindow.gZenFolders?.restoreDataFromSessionStore(winData.folders);
|
||||
+ aWindow.gZenViewSplitter?.restoreDataFromSessionStore(winData.splitViewData);
|
||||
+ aWindow.gZenWorkspaces.activeWorkspace = winData.activeZenSpace || null;
|
||||
+ aWindow.gZenWorkspaces?.restoreWorkspacesFromSessionStore(winData);
|
||||
this._log.debug(
|
||||
`restoreWindow, createTabsForSessionRestore returned ${tabs.length} tabs`
|
||||
);
|
||||
@@ -6372,6 +6404,25 @@ var SessionStoreInternal = {
|
||||
@@ -6372,6 +6405,25 @@ var SessionStoreInternal = {
|
||||
|
||||
// Most of tabData has been restored, now continue with restoring
|
||||
// attributes that may trigger external events.
|
||||
@@ -210,7 +211,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
|
||||
if (tabData.pinned) {
|
||||
tabbrowser.pinTab(tab);
|
||||
@@ -7290,7 +7341,7 @@ var SessionStoreInternal = {
|
||||
@@ -7290,7 +7342,7 @@ var SessionStoreInternal = {
|
||||
|
||||
let groupsToSave = new Map();
|
||||
for (let tIndex = 0; tIndex < window.tabs.length; ) {
|
||||
@@ -219,7 +220,7 @@ index 2c2f43bf743ef458b378e85e9ed44a971711e1d9..cc9185308c83abfa173041a10edbcb9b
|
||||
// Adjust window.selected
|
||||
if (tIndex + 1 < window.selected) {
|
||||
window.selected -= 1;
|
||||
@@ -7305,7 +7356,7 @@ var SessionStoreInternal = {
|
||||
@@ -7305,7 +7357,7 @@ var SessionStoreInternal = {
|
||||
);
|
||||
// We don't want to increment tIndex here.
|
||||
continue;
|
||||
|
||||
@@ -12,12 +12,7 @@ class ZenStartup {
|
||||
|
||||
isReady = false;
|
||||
|
||||
async init() {
|
||||
// important: We do this to ensure that some firefox components
|
||||
// are initialized before we start our own initialization.
|
||||
// please, do not remove this line and if you do, make sure to
|
||||
// test the startup process.
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
init() {
|
||||
this.openWatermark();
|
||||
this.#initBrowserBackground();
|
||||
this.#changeSidebarLocation();
|
||||
@@ -97,6 +92,7 @@ class ZenStartup {
|
||||
// Just in case we didn't get the right size.
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
this.closeWatermark();
|
||||
document.getElementById('tabbrowser-arrowscrollbox').setAttribute('orient', 'vertical');
|
||||
this.isReady = true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,138 +6,135 @@ document.addEventListener(
|
||||
'MozBeforeInitialXULLayout',
|
||||
() => {
|
||||
// <commandset id="mainCommandSet"> defined in browser-sets.inc
|
||||
document
|
||||
.getElementById('zenCommandSet')
|
||||
|
||||
.addEventListener('command', (event) => {
|
||||
switch (event.target.id) {
|
||||
case 'cmd_zenCompactModeToggle':
|
||||
gZenCompactModeManager.toggle();
|
||||
break;
|
||||
case 'cmd_zenCompactModeShowSidebar':
|
||||
gZenCompactModeManager.toggleSidebar();
|
||||
break;
|
||||
case 'cmd_toggleCompactModeIgnoreHover':
|
||||
gZenCompactModeManager.toggle(true);
|
||||
break;
|
||||
case 'cmd_zenWorkspaceForward':
|
||||
gZenWorkspaces.changeWorkspaceShortcut();
|
||||
break;
|
||||
case 'cmd_zenWorkspaceBackward':
|
||||
gZenWorkspaces.changeWorkspaceShortcut(-1);
|
||||
break;
|
||||
case 'cmd_zenSplitViewGrid':
|
||||
gZenViewSplitter.toggleShortcut('grid');
|
||||
break;
|
||||
case 'cmd_zenSplitViewVertical':
|
||||
gZenViewSplitter.toggleShortcut('vsep');
|
||||
break;
|
||||
case 'cmd_zenSplitViewHorizontal':
|
||||
gZenViewSplitter.toggleShortcut('hsep');
|
||||
break;
|
||||
case 'cmd_zenSplitViewUnsplit':
|
||||
gZenViewSplitter.toggleShortcut('unsplit');
|
||||
break;
|
||||
case 'cmd_zenSplitViewContextMenu':
|
||||
gZenViewSplitter.contextSplitTabs();
|
||||
break;
|
||||
case 'cmd_zenCopyCurrentURLMarkdown':
|
||||
gZenCommonActions.copyCurrentURLAsMarkdownToClipboard();
|
||||
break;
|
||||
case 'cmd_zenCopyCurrentURL':
|
||||
gZenCommonActions.copyCurrentURLToClipboard();
|
||||
break;
|
||||
case 'cmd_zenPinnedTabReset':
|
||||
gZenPinnedTabManager.resetPinnedTab(gBrowser.selectedTab);
|
||||
break;
|
||||
case 'cmd_zenPinnedTabResetNoTab':
|
||||
gZenPinnedTabManager.resetPinnedTab();
|
||||
break;
|
||||
case 'cmd_zenToggleSidebar':
|
||||
gZenVerticalTabsManager.toggleExpand();
|
||||
break;
|
||||
case 'cmd_zenOpenZenThemePicker':
|
||||
gZenThemePicker.openThemePicker(event);
|
||||
break;
|
||||
case 'cmd_zenChangeWorkspaceTab':
|
||||
gZenWorkspaces.changeTabWorkspace(
|
||||
event.sourceEvent.target.getAttribute('zen-workspace-id')
|
||||
);
|
||||
break;
|
||||
case 'cmd_zenToggleTabsOnRight':
|
||||
gZenVerticalTabsManager.toggleTabsOnRight();
|
||||
break;
|
||||
case 'cmd_zenSplitViewLinkInNewTab':
|
||||
gZenViewSplitter.splitLinkInNewTab();
|
||||
break;
|
||||
case 'cmd_zenNewEmptySplit':
|
||||
setTimeout(() => {
|
||||
gZenViewSplitter.createEmptySplit();
|
||||
}, 0);
|
||||
break;
|
||||
case 'cmd_zenReplacePinnedUrlWithCurrent':
|
||||
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
|
||||
break;
|
||||
case 'cmd_contextZenAddToEssentials':
|
||||
gZenPinnedTabManager.addToEssentials();
|
||||
break;
|
||||
case 'cmd_contextZenRemoveFromEssentials':
|
||||
gZenPinnedTabManager.removeEssentials();
|
||||
break;
|
||||
case 'cmd_zenCtxDeleteWorkspace':
|
||||
gZenWorkspaces.contextDeleteWorkspace(event);
|
||||
break;
|
||||
case 'cmd_zenChangeWorkspaceName':
|
||||
gZenVerticalTabsManager.renameTabStart({
|
||||
target: gZenWorkspaces.activeWorkspaceIndicator.querySelector(
|
||||
'.zen-current-workspace-indicator-name'
|
||||
),
|
||||
});
|
||||
break;
|
||||
case 'cmd_zenChangeWorkspaceIcon':
|
||||
gZenWorkspaces.changeWorkspaceIcon();
|
||||
break;
|
||||
case 'cmd_zenReorderWorkspaces':
|
||||
gZenUIManager.showToast('zen-workspaces-how-to-reorder-title', {
|
||||
timeout: 9000,
|
||||
descriptionId: 'zen-workspaces-how-to-reorder-desc',
|
||||
});
|
||||
break;
|
||||
case 'cmd_zenOpenWorkspaceCreation':
|
||||
gZenWorkspaces.openWorkspaceCreation(event);
|
||||
break;
|
||||
case 'cmd_zenOpenFolderCreation':
|
||||
gZenFolders.createFolder([], {
|
||||
renameFolder: true,
|
||||
});
|
||||
break;
|
||||
case 'cmd_zenTogglePinTab': {
|
||||
const currentTab = gBrowser.selectedTab;
|
||||
if (currentTab && !currentTab.hasAttribute('zen-empty-tab')) {
|
||||
if (currentTab.pinned) {
|
||||
gBrowser.unpinTab(currentTab);
|
||||
} else {
|
||||
gBrowser.pinTab(currentTab);
|
||||
}
|
||||
document.getElementById('zenCommandSet').addEventListener('command', (event) => {
|
||||
switch (event.target.id) {
|
||||
case 'cmd_zenCompactModeToggle':
|
||||
gZenCompactModeManager.toggle();
|
||||
break;
|
||||
case 'cmd_zenCompactModeShowSidebar':
|
||||
gZenCompactModeManager.toggleSidebar();
|
||||
break;
|
||||
case 'cmd_toggleCompactModeIgnoreHover':
|
||||
gZenCompactModeManager.toggle(true);
|
||||
break;
|
||||
case 'cmd_zenWorkspaceForward':
|
||||
gZenWorkspaces.changeWorkspaceShortcut();
|
||||
break;
|
||||
case 'cmd_zenWorkspaceBackward':
|
||||
gZenWorkspaces.changeWorkspaceShortcut(-1);
|
||||
break;
|
||||
case 'cmd_zenSplitViewGrid':
|
||||
gZenViewSplitter.toggleShortcut('grid');
|
||||
break;
|
||||
case 'cmd_zenSplitViewVertical':
|
||||
gZenViewSplitter.toggleShortcut('vsep');
|
||||
break;
|
||||
case 'cmd_zenSplitViewHorizontal':
|
||||
gZenViewSplitter.toggleShortcut('hsep');
|
||||
break;
|
||||
case 'cmd_zenSplitViewUnsplit':
|
||||
gZenViewSplitter.toggleShortcut('unsplit');
|
||||
break;
|
||||
case 'cmd_zenSplitViewContextMenu':
|
||||
gZenViewSplitter.contextSplitTabs();
|
||||
break;
|
||||
case 'cmd_zenCopyCurrentURLMarkdown':
|
||||
gZenCommonActions.copyCurrentURLAsMarkdownToClipboard();
|
||||
break;
|
||||
case 'cmd_zenCopyCurrentURL':
|
||||
gZenCommonActions.copyCurrentURLToClipboard();
|
||||
break;
|
||||
case 'cmd_zenPinnedTabReset':
|
||||
gZenPinnedTabManager.resetPinnedTab(gBrowser.selectedTab);
|
||||
break;
|
||||
case 'cmd_zenPinnedTabResetNoTab':
|
||||
gZenPinnedTabManager.resetPinnedTab();
|
||||
break;
|
||||
case 'cmd_zenToggleSidebar':
|
||||
gZenVerticalTabsManager.toggleExpand();
|
||||
break;
|
||||
case 'cmd_zenOpenZenThemePicker':
|
||||
gZenThemePicker.openThemePicker(event);
|
||||
break;
|
||||
case 'cmd_zenChangeWorkspaceTab':
|
||||
gZenWorkspaces.changeTabWorkspace(
|
||||
event.sourceEvent.target.getAttribute('zen-workspace-id')
|
||||
);
|
||||
break;
|
||||
case 'cmd_zenToggleTabsOnRight':
|
||||
gZenVerticalTabsManager.toggleTabsOnRight();
|
||||
break;
|
||||
case 'cmd_zenSplitViewLinkInNewTab':
|
||||
gZenViewSplitter.splitLinkInNewTab();
|
||||
break;
|
||||
case 'cmd_zenNewEmptySplit':
|
||||
setTimeout(() => {
|
||||
gZenViewSplitter.createEmptySplit();
|
||||
}, 0);
|
||||
break;
|
||||
case 'cmd_zenReplacePinnedUrlWithCurrent':
|
||||
gZenPinnedTabManager.replacePinnedUrlWithCurrent();
|
||||
break;
|
||||
case 'cmd_contextZenAddToEssentials':
|
||||
gZenPinnedTabManager.addToEssentials();
|
||||
break;
|
||||
case 'cmd_contextZenRemoveFromEssentials':
|
||||
gZenPinnedTabManager.removeEssentials();
|
||||
break;
|
||||
case 'cmd_zenCtxDeleteWorkspace':
|
||||
gZenWorkspaces.contextDeleteWorkspace(event);
|
||||
break;
|
||||
case 'cmd_zenChangeWorkspaceName':
|
||||
gZenVerticalTabsManager.renameTabStart({
|
||||
target: gZenWorkspaces.activeWorkspaceIndicator.querySelector(
|
||||
'.zen-current-workspace-indicator-name'
|
||||
),
|
||||
});
|
||||
break;
|
||||
case 'cmd_zenChangeWorkspaceIcon':
|
||||
gZenWorkspaces.changeWorkspaceIcon();
|
||||
break;
|
||||
case 'cmd_zenReorderWorkspaces':
|
||||
gZenUIManager.showToast('zen-workspaces-how-to-reorder-title', {
|
||||
timeout: 9000,
|
||||
descriptionId: 'zen-workspaces-how-to-reorder-desc',
|
||||
});
|
||||
break;
|
||||
case 'cmd_zenOpenWorkspaceCreation':
|
||||
gZenWorkspaces.openWorkspaceCreation(event);
|
||||
break;
|
||||
case 'cmd_zenOpenFolderCreation':
|
||||
gZenFolders.createFolder([], {
|
||||
renameFolder: true,
|
||||
});
|
||||
break;
|
||||
case 'cmd_zenTogglePinTab': {
|
||||
const currentTab = gBrowser.selectedTab;
|
||||
if (currentTab && !currentTab.hasAttribute('zen-empty-tab')) {
|
||||
if (currentTab.pinned) {
|
||||
gBrowser.unpinTab(currentTab);
|
||||
} else {
|
||||
gBrowser.pinTab(currentTab);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'cmd_zenCloseUnpinnedTabs':
|
||||
gZenWorkspaces.closeAllUnpinnedTabs();
|
||||
break;
|
||||
case 'cmd_zenUnloadWorkspace': {
|
||||
gZenWorkspaces.unloadWorkspace();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
gZenGlanceManager.handleMainCommandSet(event);
|
||||
if (event.target.id.startsWith('cmd_zenWorkspaceSwitch')) {
|
||||
const index = parseInt(event.target.id.replace('cmd_zenWorkspaceSwitch', ''), 10) - 1;
|
||||
gZenWorkspaces.shortcutSwitchTo(index);
|
||||
}
|
||||
break;
|
||||
break;
|
||||
}
|
||||
});
|
||||
case 'cmd_zenCloseUnpinnedTabs':
|
||||
gZenWorkspaces.closeAllUnpinnedTabs();
|
||||
break;
|
||||
case 'cmd_zenUnloadWorkspace': {
|
||||
gZenWorkspaces.unloadWorkspace();
|
||||
break;
|
||||
}
|
||||
default:
|
||||
gZenGlanceManager.handleMainCommandSet(event);
|
||||
if (event.target.id.startsWith('cmd_zenWorkspaceSwitch')) {
|
||||
const index = parseInt(event.target.id.replace('cmd_zenWorkspaceSwitch', ''), 10) - 1;
|
||||
gZenWorkspaces.shortcutSwitchTo(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
@@ -76,6 +76,39 @@ export class nsZenSessionManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the spaces data from the Places database for migration.
|
||||
* This is only called once during the first run after updating
|
||||
* to a version that uses the new session manager.
|
||||
*/
|
||||
async #getSpacesFromDBForMigration() {
|
||||
try {
|
||||
const { PlacesUtils } = ChromeUtils.importESModule(
|
||||
'resource://gre/modules/PlacesUtils.sys.mjs'
|
||||
);
|
||||
const db = await PlacesUtils.promiseDBConnection();
|
||||
const rows = await db.executeCached('SELECT * FROM zen_workspaces ORDER BY created_at ASC');
|
||||
this._migrationSpaceData = rows.map((row) => ({
|
||||
uuid: row.getResultByName('uuid'),
|
||||
name: row.getResultByName('name'),
|
||||
icon: row.getResultByName('icon'),
|
||||
containerTabId: row.getResultByName('container_id') ?? 0,
|
||||
position: row.getResultByName('position'),
|
||||
theme: row.getResultByName('theme_type')
|
||||
? {
|
||||
type: row.getResultByName('theme_type'),
|
||||
gradientColors: JSON.parse(row.getResultByName('theme_colors')),
|
||||
opacity: row.getResultByName('theme_opacity'),
|
||||
rotation: row.getResultByName('theme_rotation'),
|
||||
texture: row.getResultByName('theme_texture'),
|
||||
}
|
||||
: null,
|
||||
}));
|
||||
} catch {
|
||||
/* ignore errors during migration */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the session file and populates the sidebar object.
|
||||
* This should be only called once at startup.
|
||||
@@ -83,7 +116,12 @@ export class nsZenSessionManager {
|
||||
*/
|
||||
async readFile() {
|
||||
try {
|
||||
await this.#file.load();
|
||||
let promises = [];
|
||||
promises.push(this.#file.load());
|
||||
if (!Services.prefs.getBoolPref(MIGRATION_PREF, false)) {
|
||||
promises.push(this.#getSpacesFromDBForMigration());
|
||||
}
|
||||
await Promise.all(promises);
|
||||
} catch (e) {
|
||||
console.error('ZenSessionManager: Failed to read session file', e);
|
||||
}
|
||||
@@ -104,6 +142,10 @@ export class nsZenSessionManager {
|
||||
// gotten the opportunity to save the session yet.
|
||||
if (!Services.prefs.getBoolPref(MIGRATION_PREF, false)) {
|
||||
Services.prefs.setBoolPref(MIGRATION_PREF, true);
|
||||
for (const winData of initialState.windows || []) {
|
||||
winData.spaces = this._migrationSpaceData || [];
|
||||
}
|
||||
delete this._migrationSpaceData;
|
||||
return;
|
||||
}
|
||||
// If there's no initial state, nothing to restore. This would
|
||||
@@ -226,6 +268,7 @@ export class nsZenSessionManager {
|
||||
sidebarData.folders = state.windows[0].folders;
|
||||
sidebarData.splitViewData = state.windows[0].splitViewData;
|
||||
sidebarData.groups = state.windows[0].groups;
|
||||
sidebarData.spaces = state.windows[0].spaces;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -245,6 +288,7 @@ export class nsZenSessionManager {
|
||||
aWindowData.splitViewData = sidebar.splitViewData;
|
||||
aWindowData.folders = sidebar.folders;
|
||||
aWindowData.groups = sidebar.groups;
|
||||
aWindowData.spaces = sidebar.spaces;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -263,9 +307,9 @@ export class nsZenSessionManager {
|
||||
* into a race condition if we try to restore the window synchronously
|
||||
* here.
|
||||
*/
|
||||
restoreNewWindow(aWindow, SessionStoreInternal, resolvePromise) {
|
||||
restoreNewWindow(aWindow, SessionStoreInternal) {
|
||||
if (aWindow.gZenWorkspaces?.privateWindowOrDisabled) {
|
||||
return resolvePromise();
|
||||
return;
|
||||
}
|
||||
this.log('Restoring new window with Zen session data');
|
||||
const state = lazy.SessionStore.getCurrentState(true);
|
||||
@@ -300,17 +344,24 @@ export class nsZenSessionManager {
|
||||
const newState = { windows: [newWindow] };
|
||||
this.log(`Cloning window with ${newWindow.tabs.length} tabs`);
|
||||
|
||||
aWindow.addEventListener(
|
||||
'SSWindowRestored',
|
||||
() => {
|
||||
lazy.setTimeout(resolvePromise);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
SessionStoreInternal._deferredInitialState = newState;
|
||||
SessionStoreInternal.initializeWindow(aWindow, newState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the cloned spaces data from the sidebar object.
|
||||
* This is used during migration to restore spaces into
|
||||
* the initial session state.
|
||||
*
|
||||
* @returns {Array} The cloned spaces data.
|
||||
*/
|
||||
getClonedSpaces() {
|
||||
const sidebar = this.#sidebar;
|
||||
if (!sidebar || !sidebar.spaces) {
|
||||
return [];
|
||||
}
|
||||
return Cu.cloneInto(sidebar.spaces, {});
|
||||
}
|
||||
}
|
||||
|
||||
export const ZenSessionStore = new nsZenSessionManager();
|
||||
|
||||
@@ -742,24 +742,11 @@ class nsZenWindowSync {
|
||||
|
||||
/* Mark: Public API */
|
||||
|
||||
shouldLoadTab(aTab) {
|
||||
if (!lazy.gWindowSyncEnabled) {
|
||||
// Since we are never going to sync the tab, we can always load it.
|
||||
return true;
|
||||
}
|
||||
if (aTab._zenContentsVisible) {
|
||||
// This tab is already active in this window.
|
||||
return true;
|
||||
}
|
||||
// We don't want to trigger a new browser kick-off if there's
|
||||
// another window where this tab is already active.
|
||||
return !this.#getActiveTabFromOtherWindows(
|
||||
aTab.ownerGlobal,
|
||||
aTab.id,
|
||||
(tab) => tab?._zenContentsVisible
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the initial pinned state for a tab across all windows.
|
||||
*
|
||||
* @param {Object} aTab - The tab to set the pinned state for.
|
||||
*/
|
||||
setPinnedTabState(aTab) {
|
||||
const state = this.#getTabState(aTab);
|
||||
const initialState = {
|
||||
@@ -774,6 +761,23 @@ class nsZenWindowSync {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Propagates the workspaces to all windows.
|
||||
* @param {Array} aWorkspaces - The workspaces to propagate.
|
||||
*/
|
||||
propagateWorkspacesToAllWindows(aWorkspaces) {
|
||||
this.#runOnAllWindows(null, (win) => {
|
||||
win.gZenWorkspaces.propagateWorkspaces(aWorkspaces);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves all tabs from a window to a synced workspace in another window.
|
||||
* If no synced window exists, creates a new one.
|
||||
*
|
||||
* @param {Window} aWindow - The window to move tabs from.
|
||||
* @param {string} aWorkspaceId - The ID of the workspace to move tabs to.
|
||||
*/
|
||||
moveTabsToSyncedWorkspace(aWindow, aWorkspaceId) {
|
||||
const tabsToMove = aWindow.gZenWorkspaces.allStoredTabs.filter(
|
||||
(tab) => !tab.hasAttribute('zen-empty-tab')
|
||||
|
||||
@@ -64,7 +64,7 @@ class nsZenPinnedTabManager extends nsZenDOMOperatedFeature {
|
||||
this._resolvePinnedInitializedInternal = resolve;
|
||||
});
|
||||
|
||||
async init() {
|
||||
init() {
|
||||
if (!this.enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
add_task(async function test_Container_Essentials_Auto_Swithc() {
|
||||
await gZenWorkspaces.createAndSaveWorkspace('Container Profile 1', undefined, false, 1);
|
||||
const workspaces = await gZenWorkspaces._workspaces();
|
||||
ok(workspaces.workspaces.length === 2, 'Two workspaces should exist.');
|
||||
ok(workspaces.length === 2, 'Two workspaces should exist.');
|
||||
|
||||
let newTab = BrowserTestUtils.addTab(gBrowser, 'about:blank', {
|
||||
skipAnimation: true,
|
||||
@@ -27,11 +27,11 @@ add_task(async function test_Container_Essentials_Auto_Swithc() {
|
||||
const newWorkspaceUUID = gZenWorkspaces.activeWorkspace;
|
||||
Assert.equal(
|
||||
gZenWorkspaces.activeWorkspace,
|
||||
workspaces.workspaces[1].uuid,
|
||||
workspaces[1].uuid,
|
||||
'The new workspace should be active.'
|
||||
);
|
||||
|
||||
// Change to the original workspace, there should be no essential tabs
|
||||
await gZenWorkspaces.changeWorkspace(workspaces.workspaces[0]);
|
||||
await gZenWorkspaces.changeWorkspace(workspaces[0]);
|
||||
await gZenWorkspaces.removeWorkspace(newWorkspaceUUID);
|
||||
});
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
add_task(async function test_Check_Creation() {
|
||||
await gZenWorkspaces.createAndSaveWorkspace('Container Profile 1', undefined, false, 1);
|
||||
const workspaces = await gZenWorkspaces._workspaces();
|
||||
ok(workspaces.workspaces.length === 2, 'Two workspaces should exist.');
|
||||
ok(workspaces.length === 2, 'Two workspaces should exist.');
|
||||
|
||||
await gZenWorkspaces.changeWorkspace(workspaces.workspaces[1]);
|
||||
await gZenWorkspaces.changeWorkspace(workspaces[1]);
|
||||
let newTab = BrowserTestUtils.addTab(gBrowser, 'about:blank', {
|
||||
skipAnimation: true,
|
||||
userContextId: 1,
|
||||
@@ -28,7 +28,7 @@ add_task(async function test_Check_Creation() {
|
||||
const newWorkspaceUUID = gZenWorkspaces.activeWorkspace;
|
||||
|
||||
// Change to the original workspace, there should be no essential tabs
|
||||
await gZenWorkspaces.changeWorkspace(workspaces.workspaces[0]);
|
||||
await gZenWorkspaces.changeWorkspace(workspaces[0]);
|
||||
ok(
|
||||
!gBrowser.tabs.find(
|
||||
(t) => t.hasAttribute('zen-essential') && t.getAttribute('usercontextid') == 1
|
||||
|
||||
@@ -9,9 +9,9 @@ add_task(async function test_Check_Creation() {
|
||||
const currentWorkspaceUUID = gZenWorkspaces.activeWorkspace;
|
||||
await gZenWorkspaces.createAndSaveWorkspace('Test Workspace 2');
|
||||
const workspaces = await gZenWorkspaces._workspaces();
|
||||
ok(workspaces.workspaces.length === 2, 'Two workspaces should exist.');
|
||||
ok(workspaces.length === 2, 'Two workspaces should exist.');
|
||||
ok(
|
||||
currentWorkspaceUUID !== workspaces.workspaces[1].uuid,
|
||||
currentWorkspaceUUID !== workspaces[1].uuid,
|
||||
'The new workspace should be different from the current one.'
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ add_task(async function test_Check_Creation() {
|
||||
const workspacesAfterRemove = await gZenWorkspaces._workspaces();
|
||||
ok(workspacesAfterRemove.workspaces.length === 1, 'One workspace should exist.');
|
||||
ok(
|
||||
workspacesAfterRemove.workspaces[0].uuid === currentWorkspaceUUID,
|
||||
workspacesAfterRemove[0].uuid === currentWorkspaceUUID,
|
||||
'The workspace should be the one we started with.'
|
||||
);
|
||||
ok(gBrowser.tabs.length === 2, 'There should be one tab.');
|
||||
|
||||
@@ -19,6 +19,6 @@ add_task(async function test_Change_To_Empty() {
|
||||
);
|
||||
|
||||
const workspacesAfterRemove = await gZenWorkspaces._workspaces();
|
||||
ok(workspacesAfterRemove.workspaces.length === 1, 'One workspace should exist.');
|
||||
ok(workspacesAfterRemove.length === 1, 'One workspace should exist.');
|
||||
ok(gBrowser.tabs.length === 2, 'There should be two tabs.');
|
||||
});
|
||||
|
||||
@@ -107,9 +107,9 @@ add_task(async function test_workspace_bookmark() {
|
||||
await withBookmarksShowing(async () => {
|
||||
await gZenWorkspaces.createAndSaveWorkspace('Test Workspace 2');
|
||||
const workspaces = await gZenWorkspaces._workspaces();
|
||||
ok(workspaces.workspaces.length === 2, 'Two workspaces should exist.');
|
||||
const firstWorkspace = workspaces.workspaces[0];
|
||||
const secondWorkspace = workspaces.workspaces[1];
|
||||
ok(workspaces.length === 2, 'Two workspaces should exist.');
|
||||
const firstWorkspace = workspaces[0];
|
||||
const secondWorkspace = workspaces[1];
|
||||
ok(
|
||||
firstWorkspace.uuid !== secondWorkspace.uuid,
|
||||
'The new workspace should be different from the current one.'
|
||||
|
||||
@@ -76,7 +76,7 @@ const globalActionsTemplate = [
|
||||
command: 'cmd_zenWorkspaceForward',
|
||||
icon: 'chrome://browser/skin/zen-icons/forward.svg',
|
||||
isAvailable: (window) => {
|
||||
return window.gZenWorkspaces._workspaceCache.workspaces.length > 1;
|
||||
return window.gZenWorkspaces._workspaceCache.length > 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -85,7 +85,7 @@ const globalActionsTemplate = [
|
||||
icon: 'chrome://browser/skin/zen-icons/back.svg',
|
||||
isAvailable: (window) => {
|
||||
// This also covers the case of being in private mode
|
||||
return window.gZenWorkspaces._workspaceCache.workspaces.length > 1;
|
||||
return window.gZenWorkspaces._workspaceCache.length > 1;
|
||||
},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1332,7 +1332,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
// Do not rebuild if the workspace is not the same as the current one
|
||||
const windowWorkspace = await browser.gZenWorkspaces.getActiveWorkspace();
|
||||
const windowWorkspace = browser.gZenWorkspaces.getActiveWorkspace();
|
||||
if (windowWorkspace.uuid !== uuid) {
|
||||
return;
|
||||
}
|
||||
@@ -1630,13 +1630,12 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
||||
};
|
||||
});
|
||||
const gradient = nsZenThemePicker.getTheme(colors, this.currentOpacity, this.currentTexture);
|
||||
let currentWorkspace = await gZenWorkspaces.getActiveWorkspace();
|
||||
let currentWorkspace = gZenWorkspaces.getActiveWorkspace();
|
||||
|
||||
if (!skipSave) {
|
||||
await ZenWorkspacesStorage.saveWorkspaceTheme(currentWorkspace.uuid, gradient);
|
||||
await gZenWorkspaces._propagateWorkspaceData();
|
||||
currentWorkspace.theme = gradient;
|
||||
gZenWorkspaces.saveWorkspace(currentWorkspace);
|
||||
gZenUIManager.showToast('zen-panel-ui-gradient-generator-saved-message');
|
||||
currentWorkspace = await gZenWorkspaces.getActiveWorkspace();
|
||||
}
|
||||
|
||||
await this.onWorkspaceChange(currentWorkspace, skipSave, skipSave ? gradient : null);
|
||||
@@ -1691,7 +1690,7 @@ export class nsZenThemePicker extends nsZenMultiWindowFeature {
|
||||
|
||||
invalidateGradientCache() {
|
||||
this.#gradientsCache = {};
|
||||
window.dispatchEvent(new Event('ZenGradientCacheChanged'));
|
||||
window.dispatchEvent(new Event('ZenGradientCacheChanged', { bubbles: true }));
|
||||
}
|
||||
|
||||
getGradientForWorkspace(workspace) {
|
||||
|
||||
@@ -231,7 +231,7 @@ class nsZenWorkspace extends MozXULElement {
|
||||
if (newName === '') {
|
||||
return;
|
||||
}
|
||||
let workspaces = (await gZenWorkspaces.getWorkspaces()).workspaces;
|
||||
let workspaces = gZenWorkspaces.getWorkspaces();
|
||||
let workspaceData = workspaces.find((workspace) => workspace.uuid === this.workspaceUuid);
|
||||
workspaceData.name = newName;
|
||||
await gZenWorkspaces.saveWorkspace(workspaceData);
|
||||
@@ -286,28 +286,27 @@ class nsZenWorkspace extends MozXULElement {
|
||||
const popup = document.getElementById('zenMoveTabsToSyncedWorkspacePopup');
|
||||
popup.innerHTML = '';
|
||||
|
||||
gZenWorkspaces.getWorkspaces(true).then((workspaces) => {
|
||||
for (const workspace of workspaces.workspaces) {
|
||||
const item = gZenWorkspaces.generateMenuItemForWorkspace(workspace);
|
||||
item.addEventListener('command', async () => {
|
||||
const { ZenWindowSync } = ChromeUtils.importESModule(
|
||||
'resource:///modules/zen/ZenWindowSync.sys.mjs'
|
||||
);
|
||||
ZenWindowSync.moveTabsToSyncedWorkspace(window, workspace.uuid);
|
||||
});
|
||||
popup.appendChild(item);
|
||||
}
|
||||
const workspaces = gZenWorkspaces.getWorkspaces(true);
|
||||
for (const workspace of workspaces) {
|
||||
const item = gZenWorkspaces.generateMenuItemForWorkspace(workspace);
|
||||
item.addEventListener('command', async () => {
|
||||
const { ZenWindowSync } = ChromeUtils.importESModule(
|
||||
'resource:///modules/zen/ZenWindowSync.sys.mjs'
|
||||
);
|
||||
ZenWindowSync.moveTabsToSyncedWorkspace(window, workspace.uuid);
|
||||
});
|
||||
popup.appendChild(item);
|
||||
}
|
||||
|
||||
button.setAttribute('open', 'true');
|
||||
popup.addEventListener(
|
||||
'popuphidden',
|
||||
() => {
|
||||
button.removeAttribute('open');
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
popup.openPopup(button, 'after_start', 0, 0, true /* isContextMenu */);
|
||||
});
|
||||
button.setAttribute('open', 'true');
|
||||
popup.addEventListener(
|
||||
'popuphidden',
|
||||
() => {
|
||||
button.removeAttribute('open');
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
popup.openPopup(button, 'after_start', 0, 0, true /* isContextMenu */);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -199,7 +199,7 @@ class nsZenWorkspaceCreation extends MozXULElement {
|
||||
}
|
||||
|
||||
async onCreateButtonCommand() {
|
||||
const workspace = await gZenWorkspaces.getActiveWorkspace();
|
||||
const workspace = gZenWorkspaces.getActiveWorkspace();
|
||||
workspace.name = this.inputName.value.trim();
|
||||
workspace.icon = this.inputIcon.image || this.inputIcon.label || undefined;
|
||||
workspace.containerTabId = this.currentProfile;
|
||||
@@ -320,8 +320,8 @@ class nsZenWorkspaceCreation extends MozXULElement {
|
||||
this.remove();
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
|
||||
const workspace = await gZenWorkspaces.getActiveWorkspace();
|
||||
await gZenWorkspaces._organizeWorkspaceStripLocations(workspace, true);
|
||||
const workspace = gZenWorkspaces.getActiveWorkspace();
|
||||
await gZenWorkspaces._organizeWorkspaceStripLocations(workspace);
|
||||
await gZenWorkspaces.updateTabsContainers();
|
||||
|
||||
await gZenUIManager.motion.animate(
|
||||
|
||||
@@ -126,13 +126,13 @@ class nsZenWorkspaceIcons extends MozXULElement {
|
||||
}
|
||||
|
||||
async #updateIcons() {
|
||||
const workspaces = await gZenWorkspaces.getWorkspaces();
|
||||
const workspaces = gZenWorkspaces.getWorkspaces();
|
||||
this.innerHTML = '';
|
||||
for (const workspace of workspaces.workspaces) {
|
||||
for (const workspace of workspaces) {
|
||||
const button = this.#createWorkspaceIcon(workspace);
|
||||
this.appendChild(button);
|
||||
}
|
||||
if (workspaces.workspaces.length <= 1) {
|
||||
if (workspaces.length <= 1) {
|
||||
this.setAttribute('dont-show', 'true');
|
||||
} else {
|
||||
this.removeAttribute('dont-show');
|
||||
@@ -168,6 +168,9 @@ class nsZenWorkspaceIcons extends MozXULElement {
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (selected == -1) {
|
||||
return;
|
||||
}
|
||||
buttons[selected].setAttribute('active', true);
|
||||
this.scrollLeft = buttons[selected].offsetLeft - 10;
|
||||
this.setAttribute('selected', selected);
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
// 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/.
|
||||
|
||||
import { nsZenMultiWindowFeature } from 'chrome://browser/content/zen-components/ZenCommonUtils.mjs';
|
||||
import { nsZenThemePicker } from 'chrome://browser/content/zen-components/ZenGradientGenerator.mjs';
|
||||
|
||||
class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
class nsZenWorkspaces {
|
||||
/**
|
||||
* Stores workspace IDs and their last selected tabs.
|
||||
*/
|
||||
@@ -13,6 +12,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
#inChangingWorkspace = false;
|
||||
draggedElement = null;
|
||||
|
||||
#hasInitialized = false;
|
||||
|
||||
#canDebug = Services.prefs.getBoolPref('zen.workspaces.debug', false);
|
||||
#activeWorkspace = '';
|
||||
|
||||
@@ -32,18 +33,10 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
'BMB_mobileBookmarks',
|
||||
];
|
||||
|
||||
promiseDBInitialized = new Promise((resolve) => {
|
||||
this._resolveDBInitialized = resolve;
|
||||
});
|
||||
|
||||
promisePinnedInitialized = new Promise((resolve) => {
|
||||
this._resolvePinnedInitialized = resolve;
|
||||
});
|
||||
|
||||
promiseSectionsInitialized = new Promise((resolve) => {
|
||||
this._resolveSectionsInitialized = resolve;
|
||||
});
|
||||
|
||||
promiseInitialized = new Promise((resolve) => {
|
||||
this._resolveInitialized = resolve;
|
||||
});
|
||||
@@ -52,11 +45,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
if (this.privateWindowOrDisabled) {
|
||||
return;
|
||||
}
|
||||
await Promise.all([
|
||||
this.promiseDBInitialized,
|
||||
this.promisePinnedInitialized,
|
||||
SessionStore.promiseAllWindowsRestored,
|
||||
]);
|
||||
await Promise.all([this.promisePinnedInitialized, SessionStore.promiseAllWindowsRestored]);
|
||||
}
|
||||
|
||||
async init() {
|
||||
@@ -129,8 +118,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
this.addPopupListeners();
|
||||
|
||||
await this.#waitForPromises();
|
||||
await this.getWorkspaces();
|
||||
|
||||
await this.afterLoadInit();
|
||||
}
|
||||
|
||||
@@ -145,8 +132,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
await this.delayedStartup();
|
||||
}
|
||||
this._initializeWorkspaceTabContextMenus();
|
||||
await this.initializeWorkspaces();
|
||||
await this.promiseSectionsInitialized;
|
||||
|
||||
// Non UI related initializations
|
||||
if (
|
||||
@@ -157,24 +142,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
this.initializeGestureHandlers();
|
||||
this.initializeWorkspaceNavigation();
|
||||
}
|
||||
|
||||
if (!this.privateWindowOrDisabled) {
|
||||
const observerFunction = async () => {
|
||||
this._workspaceBookmarksCache = null;
|
||||
await this.workspaceBookmarks();
|
||||
this._invalidateBookmarkContainers();
|
||||
};
|
||||
Services.obs.addObserver(this, 'weave:engine:sync:finish');
|
||||
Services.obs.addObserver(observerFunction, 'workspace-bookmarks-updated');
|
||||
window.addEventListener(
|
||||
'unload',
|
||||
() => {
|
||||
Services.obs.removeObserver(this, 'weave:engine:sync:finish');
|
||||
Services.obs.removeObserver(observerFunction, 'workspace-bookmarks-updated');
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Validate browser state before tab operations
|
||||
@@ -315,14 +282,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
});
|
||||
};
|
||||
this._pinnedTabsResizeObserver = new ResizeObserver(onResize);
|
||||
await this._createDefaultWorkspaceIfNeeded();
|
||||
}
|
||||
|
||||
async _createDefaultWorkspaceIfNeeded() {
|
||||
const workspaces = await this.getWorkspaces();
|
||||
if (!workspaces.workspaces.length) {
|
||||
await this.createAndSaveWorkspace('Space', null, true);
|
||||
this._workspaceCache = null;
|
||||
if (this.privateWindowOrDisabled) {
|
||||
await this.restoreWorkspacesFromSessionStore({});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,47 +372,38 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
await SessionStore.promiseAllWindowsRestored;
|
||||
const perifery = document.getElementById('tabbrowser-arrowscrollbox-periphery');
|
||||
perifery.setAttribute('hidden', 'true');
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
setTimeout(async () => {
|
||||
await window._zenPromiseNewWindowRestored;
|
||||
const tabs = gBrowser.tabContainer.allTabs;
|
||||
const workspaces = await this.getWorkspaces();
|
||||
for (const workspace of workspaces.workspaces) {
|
||||
await this._createWorkspaceTabsSection(workspace, tabs);
|
||||
const tabs = gBrowser.tabContainer.allTabs;
|
||||
const workspaces = this.getWorkspaces();
|
||||
for (const workspace of workspaces) {
|
||||
await this.#createWorkspaceTabsSection(workspace, tabs);
|
||||
}
|
||||
if (tabs.length) {
|
||||
const defaultSelectedContainer = this.workspaceElement(this.activeWorkspace)?.querySelector(
|
||||
'.zen-workspace-normal-tabs-section'
|
||||
);
|
||||
const pinnedContainer = this.workspaceElement(this.activeWorkspace).querySelector(
|
||||
'.zen-workspace-pinned-tabs-section'
|
||||
);
|
||||
// New profile with no workspaces does not have a default selected container
|
||||
if (defaultSelectedContainer) {
|
||||
for (const tab of tabs) {
|
||||
if (tab.hasAttribute('zen-essential')) {
|
||||
this.getEssentialsSection(tab).appendChild(tab);
|
||||
continue;
|
||||
} else if (tab.pinned) {
|
||||
pinnedContainer.insertBefore(tab, pinnedContainer.lastChild);
|
||||
continue;
|
||||
}
|
||||
if (tabs.length) {
|
||||
const defaultSelectedContainer = this.workspaceElement(
|
||||
this.activeWorkspace
|
||||
)?.querySelector('.zen-workspace-normal-tabs-section');
|
||||
const pinnedContainer = this.workspaceElement(this.activeWorkspace).querySelector(
|
||||
'.zen-workspace-pinned-tabs-section'
|
||||
);
|
||||
// New profile with no workspaces does not have a default selected container
|
||||
if (defaultSelectedContainer) {
|
||||
for (const tab of tabs) {
|
||||
if (tab.hasAttribute('zen-essential')) {
|
||||
this.getEssentialsSection(tab).appendChild(tab);
|
||||
continue;
|
||||
} else if (tab.pinned) {
|
||||
pinnedContainer.insertBefore(tab, pinnedContainer.lastChild);
|
||||
continue;
|
||||
}
|
||||
// before to the last child (perifery)
|
||||
defaultSelectedContainer.insertBefore(tab, defaultSelectedContainer.lastChild);
|
||||
}
|
||||
}
|
||||
gBrowser.tabContainer._invalidateCachedTabs();
|
||||
}
|
||||
perifery.setAttribute('hidden', 'true');
|
||||
this._hasInitializedTabsStrip = true;
|
||||
this.registerPinnedResizeObserver();
|
||||
this._fixIndicatorsNames(workspaces);
|
||||
this._resolveSectionsInitialized();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
});
|
||||
// before to the last child (perifery)
|
||||
defaultSelectedContainer.insertBefore(tab, defaultSelectedContainer.lastChild);
|
||||
}
|
||||
}
|
||||
gBrowser.tabContainer._invalidateCachedTabs();
|
||||
}
|
||||
perifery.setAttribute('hidden', 'true');
|
||||
this._hasInitializedTabsStrip = true;
|
||||
this.registerPinnedResizeObserver();
|
||||
this._fixIndicatorsNames(workspaces);
|
||||
}
|
||||
|
||||
getEssentialsSection(container = 0) {
|
||||
@@ -489,7 +441,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return this.getEssentialsSection(currentWorkspace?.containerTabId);
|
||||
}
|
||||
|
||||
async _createWorkspaceTabsSection(workspace, tabs = []) {
|
||||
async #createWorkspaceTabsSection(workspace, tabs = []) {
|
||||
const workspaceWrapper = document.createXULElement('zen-workspace');
|
||||
const container = document.getElementById('tabbrowser-arrowscrollbox');
|
||||
workspaceWrapper.id = workspace.uuid;
|
||||
@@ -794,6 +746,13 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
set activeWorkspace(value) {
|
||||
if (value === this.#activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
const spaces = this.getWorkspaces();
|
||||
if (!spaces.some((ws) => ws.uuid === value)) {
|
||||
value = spaces[0]?.uuid || '';
|
||||
}
|
||||
this.#activeWorkspace = value;
|
||||
if (this.privateWindowOrDisabled) {
|
||||
return;
|
||||
@@ -801,27 +760,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
Services.prefs.setStringPref('zen.workspaces.active', value);
|
||||
}
|
||||
|
||||
async observe(subject, topic, data) {
|
||||
if (topic === 'weave:engine:sync:finish' && data === 'workspaces') {
|
||||
try {
|
||||
const lastChangeTimestamp = await ZenWorkspacesStorage.getLastChangeTimestamp();
|
||||
|
||||
if (
|
||||
!this._workspaceCache ||
|
||||
!this._workspaceCache.lastChangeTimestamp ||
|
||||
lastChangeTimestamp > this._workspaceCache.lastChangeTimestamp
|
||||
) {
|
||||
await this._propagateWorkspaceData();
|
||||
|
||||
const currentWorkspace = await this.getActiveWorkspace();
|
||||
await gZenThemePicker.onWorkspaceChange(currentWorkspace);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating workspaces after sync:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get shouldHaveWorkspaces() {
|
||||
if (typeof this._shouldHaveWorkspaces === 'undefined') {
|
||||
let chromeFlags = window.docShell.treeOwner
|
||||
@@ -866,52 +804,24 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
|
||||
getWorkspaceFromId(id) {
|
||||
try {
|
||||
return this._workspaceCache.workspaces.find((workspace) => workspace.uuid === id);
|
||||
return this._workspaceCache.find((workspace) => workspace.uuid === id);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async getWorkspaces(lieToMe = false) {
|
||||
if (this._workspaceCache && !lieToMe) {
|
||||
return this._workspaceCache;
|
||||
getWorkspaces(lieToMe = false) {
|
||||
if (lieToMe) {
|
||||
const { ZenSessionStore } = ChromeUtils.importESModule(
|
||||
'resource:///modules/zen/ZenSessionManager.sys.mjs'
|
||||
);
|
||||
return ZenSessionStore.getClonedSpaces();
|
||||
}
|
||||
|
||||
if (!this.currentWindowIsSyncing && !lieToMe) {
|
||||
this._workspaceCache = {
|
||||
workspaces: this._tempWorkspace ? [this._tempWorkspace] : [],
|
||||
lastChangeTimestamp: 0,
|
||||
};
|
||||
if (!this.currentWindowIsSyncing) {
|
||||
this._workspaceCache = this._tempWorkspace ? [this._tempWorkspace] : [];
|
||||
this.#activeWorkspace = this._tempWorkspace?.uuid;
|
||||
return this._workspaceCache;
|
||||
}
|
||||
|
||||
const [workspaces, lastChangeTimestamp] = await Promise.all([
|
||||
ZenWorkspacesStorage.getWorkspaces(),
|
||||
ZenWorkspacesStorage.getLastChangeTimestamp(),
|
||||
]);
|
||||
|
||||
this._workspaceCache = { workspaces, lastChangeTimestamp };
|
||||
// Get the active workspace ID from preferences
|
||||
const activeWorkspaceId = this.activeWorkspace;
|
||||
|
||||
if (!lieToMe) {
|
||||
if (activeWorkspaceId) {
|
||||
const activeWorkspace = this.getWorkspaceFromId(activeWorkspaceId);
|
||||
// Set the active workspace ID to the first one if the one with selected id doesn't exist
|
||||
if (!activeWorkspace) {
|
||||
this.activeWorkspace = this._workspaceCache.workspaces[0]?.uuid;
|
||||
}
|
||||
} else {
|
||||
// Set the active workspace ID to the first one if active workspace doesn't exist
|
||||
this.activeWorkspace = this._workspaceCache.workspaces[0]?.uuid;
|
||||
}
|
||||
}
|
||||
|
||||
// sort by position
|
||||
this._workspaceCache.workspaces.sort(
|
||||
(a, b) => (a.position ?? Infinity) - (b.position ?? Infinity)
|
||||
);
|
||||
return this._workspaceCache;
|
||||
}
|
||||
|
||||
@@ -938,8 +848,17 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return this._workspaceCache;
|
||||
}
|
||||
|
||||
async restoreWorkspacesFromSessionStore(aWinData) {
|
||||
this._workspaceCache = aWinData.spaces || [
|
||||
await this.createAndSaveWorkspace('Space', undefined, true),
|
||||
];
|
||||
this.activeWorkspace = aWinData.activeZenSpace || this._workspaceCache[0].uuid;
|
||||
await this.initializeWorkspaces();
|
||||
this.#hasInitialized = true;
|
||||
}
|
||||
|
||||
async initializeWorkspaces() {
|
||||
let activeWorkspace = await this.getActiveWorkspace();
|
||||
let activeWorkspace = this.getActiveWorkspace();
|
||||
this.activeWorkspace = activeWorkspace?.uuid;
|
||||
await gZenSessionStore.promiseInitialized;
|
||||
try {
|
||||
@@ -950,15 +869,15 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
} catch (e) {
|
||||
console.error('gZenWorkspaces: Error initializing theme picker', e);
|
||||
}
|
||||
await this.workspaceBookmarks();
|
||||
await this.#initializeTabsStripSections();
|
||||
this.#initializeEmptyTab();
|
||||
await this.workspaceBookmarks();
|
||||
await this.changeWorkspace(activeWorkspace, { onInit: true });
|
||||
this.#fixTabPositions();
|
||||
this.onWindowResize();
|
||||
this._resolveInitialized();
|
||||
this.#clearAnyZombieTabs(); // Dont call with await
|
||||
delete window._zenPromiseNewWindowRestored;
|
||||
delete this._resolveInitialized;
|
||||
|
||||
const tabUpdateListener = this.updateTabsContainers.bind(this);
|
||||
window.addEventListener('TabOpen', tabUpdateListener);
|
||||
@@ -969,8 +888,9 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
window.addEventListener('TabUnpinned', tabUpdateListener);
|
||||
window.addEventListener('aftercustomization', tabUpdateListener);
|
||||
window.addEventListener('TabSelect', this.onLocationChange.bind(this));
|
||||
|
||||
window.addEventListener('TabBrowserInserted', this.onTabBrowserInserted.bind(this));
|
||||
|
||||
this.#updateWorkspacesChangeContextMenu();
|
||||
}
|
||||
|
||||
async selectStartPage() {
|
||||
@@ -1134,13 +1054,13 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
|
||||
async #clearAnyZombieTabs() {
|
||||
const tabs = this.allStoredTabs;
|
||||
const workspaces = await this.getWorkspaces();
|
||||
const workspaces = this.getWorkspaces();
|
||||
for (let tab of tabs) {
|
||||
const workspaceID = tab.getAttribute('zen-workspace-id');
|
||||
if (
|
||||
(workspaceID &&
|
||||
!tab.hasAttribute('zen-essential') &&
|
||||
!workspaces.workspaces.find((workspace) => workspace.uuid === workspaceID)) ||
|
||||
!workspaces.find((workspace) => workspace.uuid === workspaceID)) ||
|
||||
// Also remove empty tabs that are supposed to be from parent folders but
|
||||
// they dont exist anymore
|
||||
(tab.pinned && tab.hasAttribute('zen-empty-tab') && !tab.group)
|
||||
@@ -1282,7 +1202,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
if (!this.#contextMenuData.workspaceId) {
|
||||
separator.hidden = false;
|
||||
for (const workspace of [...this._workspaceCache.workspaces].reverse()) {
|
||||
for (const workspace of [...this._workspaceCache].reverse()) {
|
||||
const item = this.generateMenuItemForWorkspace(workspace);
|
||||
item.addEventListener('command', (e) => {
|
||||
this.changeWorkspaceWithID(e.target.closest('menuitem').getAttribute('zen-workspace-id'));
|
||||
@@ -1316,48 +1236,35 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
});
|
||||
}
|
||||
|
||||
async saveWorkspace(workspaceData, preventPropagation = false) {
|
||||
saveWorkspace(workspaceData) {
|
||||
if (this.privateWindowOrDisabled) {
|
||||
return;
|
||||
}
|
||||
await ZenWorkspacesStorage.saveWorkspace(workspaceData);
|
||||
if (!preventPropagation) {
|
||||
await this._propagateWorkspaceData();
|
||||
await this._updateWorkspacesChangeContextMenu();
|
||||
const workspacesData = this.getWorkspaces();
|
||||
const index = workspacesData.findIndex((ws) => ws.uuid === workspaceData.uuid);
|
||||
if (index !== -1) {
|
||||
workspacesData[index] = workspaceData;
|
||||
} else {
|
||||
workspacesData.push(workspaceData);
|
||||
}
|
||||
this.#propagateWorkspaceData();
|
||||
}
|
||||
|
||||
async removeWorkspace(windowID) {
|
||||
let workspacesData = await this.getWorkspaces();
|
||||
await this.changeWorkspace(
|
||||
workspacesData.workspaces.find((workspace) => workspace.uuid !== windowID)
|
||||
);
|
||||
await this.#deleteAllTabsInWorkspace(windowID);
|
||||
delete this.lastSelectedWorkspaceTabs[windowID];
|
||||
await ZenWorkspacesStorage.removeWorkspace(windowID);
|
||||
removeWorkspace(windowID) {
|
||||
let workspacesData = this.getWorkspaces();
|
||||
// Remove the workspace from the cache
|
||||
this._workspaceCache.workspaces = this._workspaceCache.workspaces.filter(
|
||||
(workspace) => workspace.uuid !== windowID
|
||||
);
|
||||
await this._propagateWorkspaceData();
|
||||
await this._updateWorkspacesChangeContextMenu();
|
||||
this.workspaceElement(windowID)?.remove();
|
||||
this.onWindowResize();
|
||||
this.registerPinnedResizeObserver();
|
||||
workspacesData = workspacesData.filter((workspace) => workspace.uuid !== windowID);
|
||||
this.#propagateWorkspaceData(workspacesData);
|
||||
}
|
||||
|
||||
isWorkspaceActive(workspace) {
|
||||
return workspace.uuid === this.activeWorkspace;
|
||||
}
|
||||
|
||||
async getActiveWorkspace() {
|
||||
const workspaces = await this.getWorkspaces();
|
||||
return (
|
||||
workspaces.workspaces.find((workspace) => workspace.uuid === this.activeWorkspace) ??
|
||||
workspaces.workspaces[0]
|
||||
);
|
||||
getActiveWorkspace() {
|
||||
return this.getActiveWorkspaceFromCache();
|
||||
}
|
||||
// Workspaces dialog UI management
|
||||
|
||||
workspaceHasIcon(workspace) {
|
||||
return workspace.icon && workspace.icon !== '';
|
||||
}
|
||||
@@ -1380,65 +1287,75 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
);
|
||||
}
|
||||
|
||||
async _propagateWorkspaceDataForWindow(browser, { ignoreStrip = false, clearCache = true } = {}) {
|
||||
if (clearCache) {
|
||||
browser.gZenWorkspaces._workspaceCache = null;
|
||||
browser.gZenWorkspaces._workspaceBookmarksCache = null;
|
||||
}
|
||||
let workspaces = await browser.gZenWorkspaces.getWorkspaces();
|
||||
browser.document
|
||||
.getElementById('cmd_zenCtxDeleteWorkspace')
|
||||
.setAttribute('disabled', workspaces.workspaces.length <= 1);
|
||||
if (clearCache) {
|
||||
browser.dispatchEvent(
|
||||
new CustomEvent('ZenWorkspacesUIUpdate', {
|
||||
bubbles: true,
|
||||
detail: { activeIndex: browser.gZenWorkspaces.activeWorkspace },
|
||||
})
|
||||
);
|
||||
for (const workspace of workspaces.workspaces) {
|
||||
// Add workspace elements if they dont exist on other windows
|
||||
if (!browser.gZenWorkspaces.workspaceElement(workspace.uuid)) {
|
||||
await browser.gZenWorkspaces._createWorkspaceTabsSection(workspace);
|
||||
}
|
||||
}
|
||||
}
|
||||
await browser.gZenWorkspaces.workspaceBookmarks();
|
||||
if (!ignoreStrip) {
|
||||
browser.gZenWorkspaces._fixIndicatorsNames(workspaces);
|
||||
#propagateWorkspaceData(aSpaceData = null) {
|
||||
if (!this.#hasInitialized || this.privateWindowOrDisabled) {
|
||||
return;
|
||||
}
|
||||
window.gZenWindowSync.propagateWorkspacesToAllWindows(aSpaceData ?? this._workspaceCache);
|
||||
}
|
||||
|
||||
async _propagateWorkspaceData({ ignoreStrip = false, clearCache = true, onInit = false } = {}) {
|
||||
const currentWindowIsPrivate = !this.currentWindowIsSyncing;
|
||||
if (onInit) {
|
||||
if (currentWindowIsPrivate) return;
|
||||
return await this._propagateWorkspaceDataForWindow(this.ownerWindow, {
|
||||
ignoreStrip,
|
||||
clearCache,
|
||||
});
|
||||
}
|
||||
await this.foreachWindowAsActive(async (browser) => {
|
||||
// Do not update the window if workspaces are not enabled in it.
|
||||
// For example, when the window is in private browsing mode.
|
||||
propagateWorkspaces(aWorkspaces) {
|
||||
const previousWorkspaces = this._workspaceCache || [];
|
||||
this._workspaceCache = aWorkspaces;
|
||||
let hasChanged = false;
|
||||
// Remove any workspace elements here that no longer exist
|
||||
for (const previousWorkspace of previousWorkspaces) {
|
||||
if (
|
||||
!browser.gZenWorkspaces.workspaceEnabled ||
|
||||
!browser.gZenWorkspaces.currentWindowIsSyncing !== currentWindowIsPrivate
|
||||
this.workspaceElement(previousWorkspace.uuid) &&
|
||||
!aWorkspaces.find((w) => w.uuid === previousWorkspace.uuid)
|
||||
) {
|
||||
return;
|
||||
if (this.isWorkspaceActive(previousWorkspace)) {
|
||||
// If the removed workspace was active, switch to another one
|
||||
const newActiveWorkspace =
|
||||
aWorkspaces.find((w) => w.uuid !== previousWorkspace.uuid) || null;
|
||||
this.changeWorkspace(newActiveWorkspace);
|
||||
}
|
||||
this.workspaceElement(previousWorkspace.uuid)?.remove();
|
||||
delete this.lastSelectedWorkspaceTabs[previousWorkspace.uuid];
|
||||
hasChanged = true;
|
||||
}
|
||||
this._propagateWorkspaceDataForWindow(browser, {
|
||||
ignoreStrip,
|
||||
clearCache,
|
||||
}).catch(console.error);
|
||||
}
|
||||
// Add any new workspace elements here
|
||||
for (const workspace of aWorkspaces) {
|
||||
if (!this.workspaceElement(workspace.uuid)) {
|
||||
this.#createWorkspaceTabsSection(workspace).catch((e) => {
|
||||
console.error('Error creating workspace tabs section:', e);
|
||||
});
|
||||
hasChanged = true;
|
||||
}
|
||||
}
|
||||
// Order the workspace elements correctly
|
||||
let previousElement = null;
|
||||
for (const workspace of aWorkspaces) {
|
||||
const workspaceElement = this.workspaceElement(workspace.uuid);
|
||||
if (workspaceElement) {
|
||||
if (previousElement === null) {
|
||||
gZenUIManager.tabsWrapper.insertBefore(
|
||||
workspaceElement,
|
||||
gZenUIManager.tabsWrapper.firstChild
|
||||
);
|
||||
hasChanged = true;
|
||||
} else if (previousElement.nextSibling !== workspaceElement) {
|
||||
gZenUIManager.tabsWrapper.insertBefore(workspaceElement, previousElement.nextSibling);
|
||||
hasChanged = true;
|
||||
}
|
||||
previousElement = workspaceElement;
|
||||
}
|
||||
}
|
||||
if (hasChanged) {
|
||||
this.#fireSpaceUIUpdate();
|
||||
}
|
||||
this._organizeWorkspaceStripLocations(this.getActiveWorkspaceFromCache()).finally(() => {
|
||||
this.updateTabsContainers();
|
||||
});
|
||||
this.#updateWorkspacesChangeContextMenu();
|
||||
}
|
||||
|
||||
async reorderWorkspace(id, newPosition) {
|
||||
if (this.privateWindowOrDisabled) {
|
||||
return;
|
||||
}
|
||||
const workspaces = (await this.getWorkspaces()).workspaces;
|
||||
const workspaces = this.getWorkspaces();
|
||||
const workspace = workspaces.find((w) => w.uuid === id);
|
||||
if (!workspace) {
|
||||
console.warn(`Workspace with ID ${id} not found for reordering.`);
|
||||
@@ -1457,26 +1374,13 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return;
|
||||
}
|
||||
workspaces.splice(newPosition, 0, workspace);
|
||||
// Update the positions in the storage
|
||||
await ZenWorkspacesStorage.updateWorkspacePositions(workspaces);
|
||||
// Propagate the changes
|
||||
await this._propagateWorkspaceData();
|
||||
}
|
||||
|
||||
async moveWorkspace(draggedWorkspaceId, targetWorkspaceId) {
|
||||
const workspaces = (await this.getWorkspaces()).workspaces;
|
||||
const draggedIndex = workspaces.findIndex((w) => w.uuid === draggedWorkspaceId);
|
||||
const draggedWorkspace = workspaces.splice(draggedIndex, 1)[0];
|
||||
const targetIndex = workspaces.findIndex((w) => w.uuid === targetWorkspaceId);
|
||||
workspaces.splice(targetIndex, 0, draggedWorkspace);
|
||||
|
||||
await ZenWorkspacesStorage.updateWorkspacePositions(workspaces);
|
||||
await this._propagateWorkspaceData();
|
||||
this.#propagateWorkspaceData();
|
||||
}
|
||||
|
||||
async openWorkspaceCreation() {
|
||||
let createForm;
|
||||
const previousWorkspace = await this.getActiveWorkspace();
|
||||
const previousWorkspace = this.getActiveWorkspace();
|
||||
document.documentElement.setAttribute('zen-creating-workspace', 'true');
|
||||
await this.createAndSaveWorkspace('Space', undefined, false, 0, {
|
||||
beforeChangeCallback: async (workspace) => {
|
||||
@@ -1490,22 +1394,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
createForm.finishSetup();
|
||||
}
|
||||
|
||||
// Workspaces management
|
||||
|
||||
async #deleteAllTabsInWorkspace(workspaceID) {
|
||||
const tabs = Array.from(this.allStoredTabs).filter(
|
||||
(tab) =>
|
||||
tab.getAttribute('zen-workspace-id') === workspaceID &&
|
||||
!tab.hasAttribute('zen-empty-tab') &&
|
||||
!tab.hasAttribute('zen-essential')
|
||||
);
|
||||
gBrowser.removeTabs(tabs, {
|
||||
animate: false,
|
||||
skipSessionStore: true,
|
||||
closeWindowWithLastTab: false,
|
||||
});
|
||||
}
|
||||
|
||||
#unpinnedTabsInWorkspace(workspaceID) {
|
||||
return Array.from(this.allStoredTabs).filter(
|
||||
(tab) => tab.getAttribute('zen-workspace-id') === workspaceID && tab.visible && !tab.pinned
|
||||
@@ -1669,7 +1557,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
workspace,
|
||||
{ onInit = false, alwaysChange = false, whileScrolling = false } = {}
|
||||
) {
|
||||
const previousWorkspace = await this.getActiveWorkspace();
|
||||
const previousWorkspace = this.getActiveWorkspace();
|
||||
alwaysChange = alwaysChange || onInit;
|
||||
this.activeWorkspace = workspace.uuid;
|
||||
if (previousWorkspace && previousWorkspace.uuid === workspace.uuid && !alwaysChange) {
|
||||
@@ -1677,11 +1565,11 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaces = await this.getWorkspaces();
|
||||
const workspaces = this.getWorkspaces();
|
||||
gZenFolders.cancelPopupTimer();
|
||||
|
||||
// Refresh tab cache
|
||||
for (const otherWorkspace of workspaces.workspaces) {
|
||||
for (const otherWorkspace of workspaces) {
|
||||
const container = this.workspaceElement(otherWorkspace.uuid);
|
||||
container.active = otherWorkspace.uuid === workspace.uuid;
|
||||
}
|
||||
@@ -1703,9 +1591,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
gBrowser.warmupTab(tabToSelect);
|
||||
|
||||
// Update UI and state
|
||||
const previousWorkspaceIndex = workspaces.workspaces.findIndex(
|
||||
(w) => w.uuid === previousWorkspace.uuid
|
||||
);
|
||||
const previousWorkspaceIndex = workspaces.findIndex((w) => w.uuid === previousWorkspace.uuid);
|
||||
await this._updateWorkspaceState(workspace, onInit, tabToSelect, {
|
||||
previousWorkspaceIndex,
|
||||
previousWorkspace,
|
||||
@@ -1793,8 +1679,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return;
|
||||
}
|
||||
this._organizingWorkspaceStrip = true;
|
||||
const workspaces = await this.getWorkspaces();
|
||||
let workspaceIndex = workspaces.workspaces.findIndex((w) => w.uuid === workspace.uuid);
|
||||
const workspaces = this.getWorkspaces();
|
||||
let workspaceIndex = workspaces.findIndex((w) => w.uuid === workspace.uuid);
|
||||
if (!justMove) {
|
||||
this._fixIndicatorsNames(workspaces);
|
||||
}
|
||||
@@ -1803,10 +1689,10 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
);
|
||||
const workspaceContextId = workspace.containerTabId;
|
||||
const nextWorkspaceContextId =
|
||||
workspaces.workspaces[workspaceIndex + (offsetPixels > 0 ? -1 : 1)]?.containerTabId;
|
||||
for (const otherWorkspace of workspaces.workspaces) {
|
||||
workspaces[workspaceIndex + (offsetPixels > 0 ? -1 : 1)]?.containerTabId;
|
||||
for (const otherWorkspace of workspaces) {
|
||||
const element = this.workspaceElement(otherWorkspace.uuid);
|
||||
const newTransform = -(workspaceIndex - workspaces.workspaces.indexOf(otherWorkspace)) * 100;
|
||||
const newTransform = -(workspaceIndex - workspaces.indexOf(otherWorkspace)) * 100;
|
||||
element.style.transform = `translateX(${newTransform + offsetPixels / 2}%)`;
|
||||
}
|
||||
// Hide other essentials with different containerTabId
|
||||
@@ -1840,7 +1726,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
if (offsetPixels) {
|
||||
// Find the next workspace we are scrolling to
|
||||
const nextWorkspace = workspaces.workspaces[workspaceIndex + (offsetPixels > 0 ? -1 : 1)];
|
||||
const nextWorkspace = workspaces[workspaceIndex + (offsetPixels > 0 ? -1 : 1)];
|
||||
if (nextWorkspace) {
|
||||
const {
|
||||
gradient: nextGradient,
|
||||
@@ -1911,7 +1797,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
_fixIndicatorsNames(workspaces) {
|
||||
for (const workspace of workspaces.workspaces) {
|
||||
for (const workspace of workspaces) {
|
||||
const workspaceIndicator = this.workspaceElement(workspace.uuid)?.indicator;
|
||||
this.updateWorkspaceIndicator(workspace, workspaceIndicator);
|
||||
}
|
||||
@@ -1927,12 +1813,12 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
const kGlobalAnimationDuration = 0.2;
|
||||
this._animatingChange = true;
|
||||
const animations = [];
|
||||
const workspaces = await this.getWorkspaces();
|
||||
const newWorkspaceIndex = workspaces.workspaces.findIndex((w) => w.uuid === newWorkspace.uuid);
|
||||
const workspaces = this.getWorkspaces();
|
||||
const newWorkspaceIndex = workspaces.findIndex((w) => w.uuid === newWorkspace.uuid);
|
||||
const isGoingLeft = newWorkspaceIndex <= previousWorkspaceIndex;
|
||||
const clonedEssentials = [];
|
||||
if (shouldAnimate && this.shouldAnimateEssentials && previousWorkspace) {
|
||||
for (const workspace of workspaces.workspaces) {
|
||||
for (const workspace of workspaces) {
|
||||
const essentialsContainer = this.getEssentialsSection(workspace.containerTabId);
|
||||
if (clonedEssentials[clonedEssentials.length - 1]?.contextId == workspace.containerTabId) {
|
||||
clonedEssentials[clonedEssentials.length - 1].repeat++;
|
||||
@@ -2000,9 +1886,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
const existingTransform = element.style.transform;
|
||||
const elementWorkspaceId = element.id;
|
||||
const elementWorkspaceIndex = workspaces.workspaces.findIndex(
|
||||
(w) => w.uuid === elementWorkspaceId
|
||||
);
|
||||
const elementWorkspaceIndex = workspaces.findIndex((w) => w.uuid === elementWorkspaceId);
|
||||
const offset = -(newWorkspaceIndex - elementWorkspaceIndex) * 100;
|
||||
const newTransform = `translateX(${offset}%)`;
|
||||
if (shouldAnimate) {
|
||||
@@ -2039,10 +1923,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
// Get a list of essentials containers that are in between the first and last workspace
|
||||
const essentialsContainersInBetween = clonedEssentials.filter((cloned) => {
|
||||
const essentialsWorkspaces = cloned.workspaces;
|
||||
const firstIndex = workspaces.workspaces.findIndex(
|
||||
(w) => w.uuid === essentialsWorkspaces[0].uuid
|
||||
);
|
||||
const lastIndex = workspaces.workspaces.findIndex(
|
||||
const firstIndex = workspaces.findIndex((w) => w.uuid === essentialsWorkspaces[0].uuid);
|
||||
const lastIndex = workspaces.findIndex(
|
||||
(w) => w.uuid === essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
|
||||
);
|
||||
|
||||
@@ -2071,10 +1953,10 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
// will slide in from the right
|
||||
|
||||
// Get the index from first and last workspace
|
||||
const firstWorkspaceIndex = workspaces.workspaces.findIndex(
|
||||
const firstWorkspaceIndex = workspaces.findIndex(
|
||||
(w) => w.uuid === essentialsWorkspaces[0].uuid
|
||||
);
|
||||
const lastWorkspaceIndex = workspaces.workspaces.findIndex(
|
||||
const lastWorkspaceIndex = workspaces.findIndex(
|
||||
(w) => w.uuid === essentialsWorkspaces[essentialsWorkspaces.length - 1].uuid
|
||||
);
|
||||
cloned.originalContainer.style.removeProperty('transform');
|
||||
@@ -2230,7 +2112,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
tab,
|
||||
currentWorkspace.uuid,
|
||||
currentWorkspace.containerTabId,
|
||||
await this.getWorkspaces()
|
||||
this.getWorkspaces()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2265,9 +2147,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return (
|
||||
!tabContextId ||
|
||||
tabContextId === '0' ||
|
||||
!workspaces.workspaces.some(
|
||||
(workspace) => workspace.containerTabId === parseInt(tabContextId, 10)
|
||||
)
|
||||
!workspaces.some((workspace) => workspace.containerTabId === parseInt(tabContextId, 10))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -2289,7 +2169,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
const lastSelectedTab = this.lastSelectedWorkspaceTabs[workspace.uuid];
|
||||
|
||||
const containerId = workspace.containerTabId?.toString();
|
||||
const workspaces = await this.getWorkspaces();
|
||||
const workspaces = this.getWorkspaces();
|
||||
|
||||
// Save current tab as last selected for old workspace if it shouldn't be visible in new workspace
|
||||
if (oldWorkspaceId && oldWorkspaceId !== workspace.uuid) {
|
||||
@@ -2349,9 +2229,6 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
gBrowser.tabContainer.arrowScrollbox = this.activeScrollbox;
|
||||
|
||||
// Update workspace UI
|
||||
await this._updateWorkspacesChangeContextMenu();
|
||||
await this._propagateWorkspaceData({ clearCache: false, onInit });
|
||||
|
||||
gZenThemePicker.onWorkspaceChange(workspace);
|
||||
|
||||
gZenUIManager.tabsWrapper.scrollbarWidth = 'none';
|
||||
@@ -2396,15 +2273,19 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
tab.setAttribute('zen-workspace-id', workspace.uuid);
|
||||
}
|
||||
}
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ZenWorkspacesUIUpdate', {
|
||||
bubbles: true,
|
||||
detail: { activeIndex: workspace.uuid },
|
||||
})
|
||||
);
|
||||
this.#fireSpaceUIUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
#fireSpaceUIUpdate() {
|
||||
window.dispatchEvent(
|
||||
new CustomEvent('ZenWorkspacesUIUpdate', {
|
||||
bubbles: true,
|
||||
detail: { activeIndex: this.activeWorkspace },
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async _fixCtrlTabBehavior() {
|
||||
ctrlTab.uninit();
|
||||
ctrlTab.readPref();
|
||||
@@ -2420,9 +2301,9 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
}
|
||||
|
||||
async _updateWorkspacesChangeContextMenu() {
|
||||
#updateWorkspacesChangeContextMenu() {
|
||||
if (gZenWorkspaces.privateWindowOrDisabled) return;
|
||||
const workspaces = await this.getWorkspaces();
|
||||
const workspaces = this.getWorkspaces();
|
||||
|
||||
const menuPopup = document.getElementById('context-zen-change-workspace-tab-menu-popup');
|
||||
if (!menuPopup) {
|
||||
@@ -2430,9 +2311,9 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
menuPopup.innerHTML = '';
|
||||
|
||||
const activeWorkspace = await this.getActiveWorkspace();
|
||||
const activeWorkspace = this.getActiveWorkspace();
|
||||
|
||||
for (let workspace of workspaces.workspaces) {
|
||||
for (let workspace of workspaces) {
|
||||
const menuItem = document.createXULElement('menuitem');
|
||||
menuItem.setAttribute('label', workspace.name);
|
||||
menuItem.setAttribute('zen-workspace-id', workspace.uuid);
|
||||
@@ -2456,7 +2337,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
};
|
||||
if (moveTabs) {
|
||||
this.#prepareNewWorkspace(workspace);
|
||||
await this._createWorkspaceTabsSection(workspace, tabs);
|
||||
await this.#createWorkspaceTabsSection(workspace, tabs);
|
||||
await this._organizeWorkspaceStripLocations(workspace);
|
||||
}
|
||||
return workspace;
|
||||
@@ -2493,7 +2374,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
if (!this.currentWindowIsSyncing) {
|
||||
this._tempWorkspace = workspaceData;
|
||||
} else {
|
||||
await this.saveWorkspace(workspaceData, dontChange);
|
||||
this.saveWorkspace(workspaceData);
|
||||
}
|
||||
if (!dontChange) {
|
||||
if (beforeChangeCallback) {
|
||||
@@ -2598,9 +2479,9 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
const workspacesIds = [];
|
||||
if (entry.target.closest('#zen-essentials')) {
|
||||
// Get all workspaces that have the same userContextId
|
||||
const activeWorkspace = await this.getActiveWorkspace();
|
||||
const activeWorkspace = this.getActiveWorkspace();
|
||||
const userContextId = activeWorkspace.containerTabId;
|
||||
const workspaces = this._workspaceCache.workspaces.filter(
|
||||
const workspaces = this._workspaceCache.filter(
|
||||
(w) => w.containerTabId === userContextId && w.uuid !== originalWorkspaceId
|
||||
);
|
||||
workspacesIds.push(...workspaces.map((w) => w.uuid));
|
||||
@@ -2663,7 +2544,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return;
|
||||
}
|
||||
|
||||
let activeWorkspace = await this.getActiveWorkspace();
|
||||
let activeWorkspace = this.getActiveWorkspace();
|
||||
if (!activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
@@ -2702,7 +2583,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
if (!isEssential) {
|
||||
const activeWorkspace = await this.getActiveWorkspace();
|
||||
const activeWorkspace = this.getActiveWorkspace();
|
||||
if (!activeWorkspace) {
|
||||
return;
|
||||
}
|
||||
@@ -2726,16 +2607,13 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
// Context menu management
|
||||
async contextChangeContainerTab(event) {
|
||||
this._organizingWorkspaceStrip = true;
|
||||
let workspaces = await this.getWorkspaces();
|
||||
let workspace = workspaces.workspaces.find(
|
||||
let workspaces = this.getWorkspaces();
|
||||
let workspace = workspaces.find(
|
||||
(workspace) => workspace.uuid === (this.#contextMenuData?.workspaceId || this.activeWorkspace)
|
||||
);
|
||||
let userContextId = parseInt(event.target.getAttribute('data-usercontextid'));
|
||||
workspace.containerTabId = userContextId + 0; // +0 to convert to number
|
||||
await this.saveWorkspace(workspace);
|
||||
await this._organizeWorkspaceStripLocations(this.getActiveWorkspaceFromCache(), true);
|
||||
await this.updateTabsContainers();
|
||||
this.tabContainer._invalidateCachedTabs();
|
||||
this.saveWorkspace(workspace);
|
||||
}
|
||||
|
||||
async closeAllUnpinnedTabs() {
|
||||
@@ -2767,7 +2645,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
},
|
||||
]);
|
||||
if (Services.prompt.confirm(null, title, body)) {
|
||||
await this.removeWorkspace(workspaceId);
|
||||
this.removeWorkspace(workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2780,21 +2658,21 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
|
||||
async changeWorkspaceShortcut(offset = 1, whileScrolling = false) {
|
||||
// Cycle through workspaces
|
||||
let workspaces = await this.getWorkspaces();
|
||||
let activeWorkspace = await this.getActiveWorkspace();
|
||||
let workspaceIndex = workspaces.workspaces.indexOf(activeWorkspace);
|
||||
let workspaces = this.getWorkspaces();
|
||||
let activeWorkspace = this.getActiveWorkspace();
|
||||
let workspaceIndex = workspaces.indexOf(activeWorkspace);
|
||||
|
||||
// note: offset can be negative
|
||||
let targetIndex = workspaceIndex + offset;
|
||||
if (this.shouldWrapAroundNavigation) {
|
||||
// Add length to handle negative indices and loop
|
||||
targetIndex = (targetIndex + workspaces.workspaces.length) % workspaces.workspaces.length;
|
||||
targetIndex = (targetIndex + workspaces.length) % workspaces.length;
|
||||
} else {
|
||||
// Clamp within bounds to disable looping
|
||||
targetIndex = Math.max(0, Math.min(workspaces.workspaces.length - 1, targetIndex));
|
||||
targetIndex = Math.max(0, Math.min(workspaces.length - 1, targetIndex));
|
||||
}
|
||||
|
||||
let nextWorkspace = workspaces.workspaces[targetIndex];
|
||||
let nextWorkspace = workspaces[targetIndex];
|
||||
await this.changeWorkspace(nextWorkspace, { whileScrolling });
|
||||
}
|
||||
|
||||
@@ -2844,10 +2722,8 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
this.lastSelectedWorkspaceTabs[workspaceID] = gZenGlanceManager.getTabOrGlanceParent(
|
||||
tabs[tabs.length - 1]
|
||||
);
|
||||
const workspaces = await this.getWorkspaces();
|
||||
await this.changeWorkspace(
|
||||
workspaces.workspaces.find((workspace) => workspace.uuid === workspaceID)
|
||||
);
|
||||
const workspaces = this.getWorkspaces();
|
||||
await this.changeWorkspace(workspaces.find((workspace) => workspace.uuid === workspaceID));
|
||||
}
|
||||
|
||||
// Tab browser utilities
|
||||
@@ -2860,11 +2736,11 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
if (
|
||||
this.shouldForceContainerTabsToWorkspace &&
|
||||
typeof userContextId !== 'undefined' &&
|
||||
this._workspaceCache?.workspaces &&
|
||||
this._workspaceCache &&
|
||||
!fromExternal
|
||||
) {
|
||||
// Find all workspaces that match the given userContextId
|
||||
const matchingWorkspaces = this._workspaceCache.workspaces.filter(
|
||||
const matchingWorkspaces = this._workspaceCache.filter(
|
||||
(workspace) => workspace.containerTabId === userContextId
|
||||
);
|
||||
|
||||
@@ -2903,12 +2779,12 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
async shortcutSwitchTo(index) {
|
||||
const workspaces = await this.getWorkspaces();
|
||||
const workspaces = this.getWorkspaces();
|
||||
// The index may be out of bounds, if it doesnt exist, don't do anything
|
||||
if (index >= workspaces.workspaces.length || index < 0) {
|
||||
if (index >= workspaces.length || index < 0) {
|
||||
return;
|
||||
}
|
||||
const workspaceToSwitch = workspaces.workspaces[index];
|
||||
const workspaceToSwitch = workspaces[index];
|
||||
await this.changeWorkspace(workspaceToSwitch);
|
||||
}
|
||||
|
||||
@@ -2952,7 +2828,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
pinnedContainers = [document.getElementById('pinned-tabs-container')];
|
||||
normalContainers = [this.activeWorkspaceStrip];
|
||||
} else {
|
||||
let workspaces = Array.from(this._workspaceCache?.workspaces || []);
|
||||
let workspaces = Array.from(this._workspaceCache || []);
|
||||
// Make the active workspace first
|
||||
workspaces = workspaces.sort((a, b) =>
|
||||
a.uuid === this.activeWorkspace ? -1 : b.uuid === this.activeWorkspace ? 1 : 0
|
||||
@@ -2998,7 +2874,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
}
|
||||
const pinnedContainers = [];
|
||||
const normalContainers = [];
|
||||
for (const workspace of this._workspaceCache.workspaces) {
|
||||
for (const workspace of this._workspaceCache) {
|
||||
const container = this.workspaceElement(workspace.uuid);
|
||||
if (container) {
|
||||
pinnedContainers.push(container.pinnedTabsContainer);
|
||||
@@ -3096,7 +2972,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
// Find first workspace with the same container
|
||||
const containerTabId = parseInt(tab.parentNode.getAttribute('container'));
|
||||
// +0 to convert to number
|
||||
workspaceToSwitch = this._workspaceCache.workspaces.find(
|
||||
workspaceToSwitch = this._workspaceCache.find(
|
||||
(workspace) => workspace.containerTabId + 0 === containerTabId
|
||||
);
|
||||
} else {
|
||||
@@ -3134,7 +3010,7 @@ class nsZenWorkspaces extends nsZenMultiWindowFeature {
|
||||
return 0;
|
||||
}
|
||||
const activeWorkspace = this.activeWorkspace;
|
||||
const workspace = workspaces.workspaces.find((workspace) => workspace.uuid === activeWorkspace);
|
||||
const workspace = workspaces.find((workspace) => workspace.uuid === activeWorkspace);
|
||||
return workspace.containerTabId;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,437 +2,20 @@
|
||||
// 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/.
|
||||
|
||||
window.ZenWorkspacesStorage = {
|
||||
// Integration of workspace-specific bookmarks into Places
|
||||
window.ZenWorkspaceBookmarksStorage = {
|
||||
lazy: {},
|
||||
|
||||
async init() {
|
||||
ChromeUtils.defineESModuleGetters(this.lazy, {
|
||||
PlacesUtils: 'resource://gre/modules/PlacesUtils.sys.mjs',
|
||||
Weave: 'resource://services-sync/main.sys.mjs',
|
||||
});
|
||||
|
||||
if (!window.gZenWorkspaces) return;
|
||||
await this._ensureTable();
|
||||
await ZenWorkspaceBookmarksStorage.init();
|
||||
},
|
||||
|
||||
async _ensureTable() {
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage._ensureTable',
|
||||
async (db) => {
|
||||
// Create the main workspaces table if it doesn't exist
|
||||
await db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS zen_workspaces (
|
||||
id INTEGER PRIMARY KEY,
|
||||
uuid TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
icon TEXT,
|
||||
container_id INTEGER,
|
||||
position INTEGER NOT NULL DEFAULT 0,
|
||||
created_at INTEGER NOT NULL,
|
||||
updated_at INTEGER NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Add new columns if they don't exist
|
||||
// SQLite doesn't have a direct "ADD COLUMN IF NOT EXISTS" syntax,
|
||||
// so we need to check if the columns exist first
|
||||
const columns = await db.execute(`PRAGMA table_info(zen_workspaces)`);
|
||||
const columnNames = columns.map((row) => row.getResultByName('name'));
|
||||
|
||||
// Helper function to add column if it doesn't exist
|
||||
const addColumnIfNotExists = async (columnName, definition) => {
|
||||
if (!columnNames.includes(columnName)) {
|
||||
await db.execute(`ALTER TABLE zen_workspaces ADD COLUMN ${columnName} ${definition}`);
|
||||
}
|
||||
};
|
||||
|
||||
// Add each new column if it doesn't exist
|
||||
await addColumnIfNotExists('theme_type', 'TEXT');
|
||||
await addColumnIfNotExists('theme_colors', 'TEXT');
|
||||
await addColumnIfNotExists('theme_opacity', 'REAL');
|
||||
await addColumnIfNotExists('theme_rotation', 'INTEGER');
|
||||
await addColumnIfNotExists('theme_texture', 'REAL');
|
||||
|
||||
// Create an index on the uuid column
|
||||
await db.execute(`
|
||||
CREATE INDEX IF NOT EXISTS idx_zen_workspaces_uuid ON zen_workspaces(uuid)
|
||||
`);
|
||||
|
||||
// Create the changes tracking table if it doesn't exist
|
||||
await db.execute(`
|
||||
CREATE TABLE IF NOT EXISTS zen_workspaces_changes (
|
||||
uuid TEXT PRIMARY KEY,
|
||||
timestamp INTEGER NOT NULL
|
||||
)
|
||||
`);
|
||||
|
||||
// Create an index on the uuid column for changes tracking table
|
||||
await db.execute(`
|
||||
CREATE INDEX IF NOT EXISTS idx_zen_workspaces_changes_uuid ON zen_workspaces_changes(uuid)
|
||||
`);
|
||||
|
||||
if (!this.lazy.Weave.Service.engineManager.get('workspaces')) {
|
||||
this.lazy.Weave.Service.engineManager.register(ZenWorkspacesEngine);
|
||||
await ZenWorkspacesStorage.migrateWorkspacesFromJSON();
|
||||
}
|
||||
|
||||
gZenWorkspaces._resolveDBInitialized();
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async migrateWorkspacesFromJSON() {
|
||||
const oldWorkspacesPath = PathUtils.join(
|
||||
PathUtils.profileDir,
|
||||
'zen-workspaces',
|
||||
'Workspaces.json'
|
||||
);
|
||||
if (await IOUtils.exists(oldWorkspacesPath)) {
|
||||
console.info('ZenWorkspacesStorage: Migrating workspaces from JSON...');
|
||||
const oldWorkspaces = await IOUtils.readJSON(oldWorkspacesPath);
|
||||
if (oldWorkspaces.workspaces) {
|
||||
for (const workspace of oldWorkspaces.workspaces) {
|
||||
await this.saveWorkspace(workspace);
|
||||
}
|
||||
}
|
||||
await IOUtils.remove(oldWorkspacesPath);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Private helper method to notify observers with a list of changed UUIDs.
|
||||
* @param {string} event - The observer event name.
|
||||
* @param {Array<string>} uuids - Array of changed workspace UUIDs.
|
||||
*/
|
||||
_notifyWorkspacesChanged(event, uuids) {
|
||||
if (uuids.length === 0) return; // No changes to notify
|
||||
|
||||
// Convert the array of UUIDs to a JSON string
|
||||
const data = JSON.stringify(uuids);
|
||||
|
||||
Services.obs.notifyObservers(null, event, data);
|
||||
},
|
||||
|
||||
async saveWorkspace(workspace, notifyObservers = true) {
|
||||
const changedUUIDs = new Set();
|
||||
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage.saveWorkspace',
|
||||
async (db) => {
|
||||
await db.executeTransaction(async () => {
|
||||
const now = Date.now();
|
||||
|
||||
let newPosition;
|
||||
if ('position' in workspace && Number.isFinite(workspace.position)) {
|
||||
newPosition = workspace.position;
|
||||
} else {
|
||||
// Get the maximum position
|
||||
const maxPositionResult = await db.execute(
|
||||
`SELECT MAX("position") as max_position FROM zen_workspaces`
|
||||
);
|
||||
const maxPosition = maxPositionResult[0].getResultByName('max_position') || 0;
|
||||
newPosition = maxPosition + 1000; // Add a large increment to avoid frequent reordering
|
||||
}
|
||||
|
||||
// Insert or replace the workspace
|
||||
await db.executeCached(
|
||||
`
|
||||
INSERT OR REPLACE INTO zen_workspaces (
|
||||
uuid, name, icon, container_id, created_at, updated_at, "position",
|
||||
theme_type, theme_colors, theme_opacity, theme_rotation, theme_texture
|
||||
) VALUES (
|
||||
:uuid, :name, :icon, :container_id,
|
||||
COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now),
|
||||
:now,
|
||||
:position,
|
||||
:theme_type, :theme_colors, :theme_opacity, :theme_rotation, :theme_texture
|
||||
)
|
||||
`,
|
||||
{
|
||||
uuid: workspace.uuid,
|
||||
name: workspace.name,
|
||||
icon: workspace.icon || null,
|
||||
container_id: workspace.containerTabId || null,
|
||||
now,
|
||||
position: newPosition,
|
||||
theme_type: workspace.theme?.type || null,
|
||||
theme_colors: workspace.theme ? JSON.stringify(workspace.theme.gradientColors) : null,
|
||||
theme_opacity: workspace.theme?.opacity || null,
|
||||
theme_rotation: workspace.theme?.rotation || null,
|
||||
theme_texture: workspace.theme?.texture || null,
|
||||
}
|
||||
);
|
||||
|
||||
// Record the change
|
||||
await db.execute(
|
||||
`
|
||||
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
|
||||
VALUES (:uuid, :timestamp)
|
||||
`,
|
||||
{
|
||||
uuid: workspace.uuid,
|
||||
timestamp: Math.floor(now / 1000),
|
||||
}
|
||||
);
|
||||
|
||||
changedUUIDs.add(workspace.uuid);
|
||||
|
||||
await this.updateLastChangeTimestamp(db);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
if (notifyObservers) {
|
||||
this._notifyWorkspacesChanged('zen-workspace-updated', Array.from(changedUUIDs));
|
||||
}
|
||||
},
|
||||
|
||||
async getWorkspaces() {
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
const rows = await db.executeCached(`
|
||||
SELECT * FROM zen_workspaces ORDER BY created_at ASC
|
||||
`);
|
||||
return rows.map((row) => ({
|
||||
uuid: row.getResultByName('uuid'),
|
||||
name: row.getResultByName('name'),
|
||||
icon: row.getResultByName('icon'),
|
||||
containerTabId: row.getResultByName('container_id') ?? 0,
|
||||
position: row.getResultByName('position'),
|
||||
theme: row.getResultByName('theme_type')
|
||||
? {
|
||||
type: row.getResultByName('theme_type'),
|
||||
gradientColors: JSON.parse(row.getResultByName('theme_colors')),
|
||||
opacity: row.getResultByName('theme_opacity'),
|
||||
rotation: row.getResultByName('theme_rotation'),
|
||||
texture: row.getResultByName('theme_texture'),
|
||||
}
|
||||
: null,
|
||||
}));
|
||||
},
|
||||
|
||||
async removeWorkspace(uuid, notifyObservers = true) {
|
||||
const changedUUIDs = [uuid];
|
||||
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage.removeWorkspace',
|
||||
async (db) => {
|
||||
await db.execute(
|
||||
`
|
||||
DELETE FROM zen_workspaces WHERE uuid = :uuid
|
||||
`,
|
||||
{ uuid }
|
||||
);
|
||||
|
||||
// Record the removal as a change
|
||||
const now = Date.now();
|
||||
await db.execute(
|
||||
`
|
||||
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
|
||||
VALUES (:uuid, :timestamp)
|
||||
`,
|
||||
{
|
||||
uuid,
|
||||
timestamp: Math.floor(now / 1000),
|
||||
}
|
||||
);
|
||||
|
||||
await this.updateLastChangeTimestamp(db);
|
||||
}
|
||||
);
|
||||
|
||||
if (notifyObservers) {
|
||||
this._notifyWorkspacesChanged('zen-workspace-removed', changedUUIDs);
|
||||
}
|
||||
},
|
||||
|
||||
async wipeAllWorkspaces() {
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage.wipeAllWorkspaces',
|
||||
async (db) => {
|
||||
await db.execute(`DELETE FROM zen_workspaces`);
|
||||
await db.execute(`DELETE FROM zen_workspaces_changes`);
|
||||
await this.updateLastChangeTimestamp(db);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async markChanged(uuid) {
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage.markChanged',
|
||||
async (db) => {
|
||||
const now = Date.now();
|
||||
await db.execute(
|
||||
`
|
||||
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
|
||||
VALUES (:uuid, :timestamp)
|
||||
`,
|
||||
{
|
||||
uuid,
|
||||
timestamp: Math.floor(now / 1000),
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
async saveWorkspaceTheme(uuid, theme, notifyObservers = true) {
|
||||
const changedUUIDs = [uuid];
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper('saveWorkspaceTheme', async (db) => {
|
||||
await db.execute(
|
||||
`
|
||||
UPDATE zen_workspaces
|
||||
SET
|
||||
theme_type = :type,
|
||||
theme_colors = :colors,
|
||||
theme_opacity = :opacity,
|
||||
theme_rotation = :rotation,
|
||||
theme_texture = :texture,
|
||||
updated_at = :now
|
||||
WHERE uuid = :uuid
|
||||
`,
|
||||
{
|
||||
type: theme.type,
|
||||
colors: JSON.stringify(theme.gradientColors),
|
||||
opacity: theme.opacity,
|
||||
rotation: theme.rotation,
|
||||
texture: theme.texture,
|
||||
now: Date.now(),
|
||||
uuid,
|
||||
}
|
||||
);
|
||||
|
||||
await this.markChanged(uuid);
|
||||
await this.updateLastChangeTimestamp(db);
|
||||
});
|
||||
|
||||
if (notifyObservers) {
|
||||
this._notifyWorkspacesChanged('zen-workspace-updated', changedUUIDs);
|
||||
}
|
||||
},
|
||||
|
||||
async getChangedIDs() {
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
const rows = await db.execute(`
|
||||
SELECT uuid, timestamp FROM zen_workspaces_changes
|
||||
`);
|
||||
const changes = {};
|
||||
for (const row of rows) {
|
||||
changes[row.getResultByName('uuid')] = row.getResultByName('timestamp');
|
||||
}
|
||||
return changes;
|
||||
},
|
||||
|
||||
async clearChangedIDs() {
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage.clearChangedIDs',
|
||||
async (db) => {
|
||||
await db.execute(`DELETE FROM zen_workspaces_changes`);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
shouldReorderWorkspaces(before, current, after) {
|
||||
const minGap = 1; // Minimum allowed gap between positions
|
||||
return (
|
||||
(before !== null && current - before < minGap) || (after !== null && after - current < minGap)
|
||||
);
|
||||
},
|
||||
|
||||
async reorderAllWorkspaces(db, changedUUIDs) {
|
||||
const workspaces = await db.execute(`
|
||||
SELECT uuid
|
||||
FROM zen_workspaces
|
||||
ORDER BY "position" ASC
|
||||
`);
|
||||
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const newPosition = (i + 1) * 1000; // Use large increments
|
||||
await db.execute(
|
||||
`
|
||||
UPDATE zen_workspaces
|
||||
SET "position" = :newPosition
|
||||
WHERE uuid = :uuid
|
||||
`,
|
||||
{ newPosition, uuid: workspaces[i].getResultByName('uuid') }
|
||||
);
|
||||
changedUUIDs.add(workspaces[i].getResultByName('uuid'));
|
||||
}
|
||||
},
|
||||
|
||||
async updateLastChangeTimestamp(db) {
|
||||
const now = Date.now();
|
||||
await db.execute(
|
||||
`
|
||||
INSERT OR REPLACE INTO moz_meta (key, value)
|
||||
VALUES ('zen_workspaces_last_change', :now)
|
||||
`,
|
||||
{ now }
|
||||
);
|
||||
},
|
||||
|
||||
async getLastChangeTimestamp() {
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
const result = await db.executeCached(`
|
||||
SELECT value FROM moz_meta WHERE key = 'zen_workspaces_last_change'
|
||||
`);
|
||||
return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0;
|
||||
},
|
||||
|
||||
async updateWorkspacePositions(workspaces) {
|
||||
const changedUUIDs = new Set();
|
||||
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspacesStorage.updateWorkspacePositions',
|
||||
async (db) => {
|
||||
await db.executeTransaction(async () => {
|
||||
const now = Date.now();
|
||||
|
||||
for (let i = 0; i < workspaces.length; i++) {
|
||||
const workspace = workspaces[i];
|
||||
const newPosition = (i + 1) * 1000;
|
||||
|
||||
await db.execute(
|
||||
`
|
||||
UPDATE zen_workspaces
|
||||
SET "position" = :newPosition
|
||||
WHERE uuid = :uuid
|
||||
`,
|
||||
{ newPosition, uuid: workspace.uuid }
|
||||
);
|
||||
|
||||
changedUUIDs.add(workspace.uuid);
|
||||
|
||||
// Record the change
|
||||
await db.execute(
|
||||
`
|
||||
INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp)
|
||||
VALUES (:uuid, :timestamp)
|
||||
`,
|
||||
{
|
||||
uuid: workspace.uuid,
|
||||
timestamp: Math.floor(now / 1000),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
await this.updateLastChangeTimestamp(db);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
this._notifyWorkspacesChanged('zen-workspace-updated', Array.from(changedUUIDs));
|
||||
},
|
||||
};
|
||||
|
||||
// Integration of workspace-specific bookmarks into Places
|
||||
window.ZenWorkspaceBookmarksStorage = {
|
||||
async init() {
|
||||
await this._ensureTable();
|
||||
},
|
||||
|
||||
async _ensureTable() {
|
||||
await ZenWorkspacesStorage.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspaceBookmarksStorage.init',
|
||||
async (db) => {
|
||||
// Create table using GUIDs instead of IDs
|
||||
@@ -498,7 +81,7 @@ window.ZenWorkspaceBookmarksStorage = {
|
||||
* @returns {Promise<number>} The timestamp of the last change.
|
||||
*/
|
||||
async getLastChangeTimestamp() {
|
||||
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
const result = await db.executeCached(`
|
||||
SELECT value FROM moz_meta WHERE key = 'zen_bookmarks_workspaces_last_change'
|
||||
`);
|
||||
@@ -506,7 +89,7 @@ window.ZenWorkspaceBookmarksStorage = {
|
||||
},
|
||||
|
||||
async getBookmarkWorkspaces(bookmarkGuid) {
|
||||
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
|
||||
const rows = await db.execute(
|
||||
`
|
||||
@@ -531,7 +114,7 @@ window.ZenWorkspaceBookmarksStorage = {
|
||||
* }
|
||||
*/
|
||||
async getBookmarkGuidsByWorkspace() {
|
||||
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
|
||||
const rows = await db.execute(`
|
||||
SELECT workspace_uuid, GROUP_CONCAT(bookmark_guid) as bookmark_guids
|
||||
@@ -554,7 +137,7 @@ window.ZenWorkspaceBookmarksStorage = {
|
||||
* @returns {Promise<Object>} An object mapping bookmark+workspace pairs to their change data.
|
||||
*/
|
||||
async getChangedIDs() {
|
||||
const db = await ZenWorkspacesStorage.lazy.PlacesUtils.promiseDBConnection();
|
||||
const db = await this.lazy.PlacesUtils.promiseDBConnection();
|
||||
const rows = await db.execute(`
|
||||
SELECT bookmark_guid, workspace_uuid, change_type, timestamp
|
||||
FROM zen_bookmarks_workspaces_changes
|
||||
@@ -575,7 +158,7 @@ window.ZenWorkspaceBookmarksStorage = {
|
||||
* Clear all recorded changes.
|
||||
*/
|
||||
async clearChangedIDs() {
|
||||
await ZenWorkspacesStorage.lazy.PlacesUtils.withConnectionWrapper(
|
||||
await this.lazy.PlacesUtils.withConnectionWrapper(
|
||||
'ZenWorkspaceBookmarksStorage.clearChangedIDs',
|
||||
async (db) => {
|
||||
await db.execute(`DELETE FROM zen_bookmarks_workspaces_changes`);
|
||||
@@ -584,4 +167,4 @@ window.ZenWorkspaceBookmarksStorage = {
|
||||
},
|
||||
};
|
||||
|
||||
ZenWorkspacesStorage.init();
|
||||
ZenWorkspaceBookmarksStorage.init();
|
||||
|
||||
@@ -1,459 +0,0 @@
|
||||
// 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/.
|
||||
|
||||
var { Tracker, Store, SyncEngine } = ChromeUtils.importESModule(
|
||||
'resource://services-sync/engines.sys.mjs'
|
||||
);
|
||||
var { CryptoWrapper } = ChromeUtils.importESModule('resource://services-sync/record.sys.mjs');
|
||||
var { Utils } = ChromeUtils.importESModule('resource://services-sync/util.sys.mjs');
|
||||
var { SCORE_INCREMENT_XLARGE } = ChromeUtils.importESModule(
|
||||
'resource://services-sync/constants.sys.mjs'
|
||||
);
|
||||
|
||||
// Define ZenWorkspaceRecord
|
||||
function ZenWorkspaceRecord(collection, id) {
|
||||
CryptoWrapper.call(this, collection, id);
|
||||
}
|
||||
|
||||
ZenWorkspaceRecord.prototype = Object.create(CryptoWrapper.prototype);
|
||||
ZenWorkspaceRecord.prototype.constructor = ZenWorkspaceRecord;
|
||||
|
||||
ZenWorkspaceRecord.prototype._logName = 'Sync.Record.ZenWorkspace';
|
||||
|
||||
Utils.deferGetSet(ZenWorkspaceRecord, 'cleartext', [
|
||||
'name',
|
||||
'icon',
|
||||
'default',
|
||||
'containerTabId',
|
||||
'position',
|
||||
'theme_type',
|
||||
'theme_colors',
|
||||
'theme_opacity',
|
||||
'theme_rotation',
|
||||
'theme_texture',
|
||||
]);
|
||||
|
||||
// Define ZenWorkspacesStore
|
||||
function ZenWorkspacesStore(name, engine) {
|
||||
Store.call(this, name, engine);
|
||||
}
|
||||
|
||||
ZenWorkspacesStore.prototype = Object.create(Store.prototype);
|
||||
ZenWorkspacesStore.prototype.constructor = ZenWorkspacesStore;
|
||||
|
||||
/**
|
||||
* Initializes the store by loading the current changeset.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.initialize = async function () {
|
||||
await Store.prototype.initialize.call(this);
|
||||
// Additional initialization if needed
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves all workspace IDs from the storage.
|
||||
* @returns {Object} An object mapping workspace UUIDs to true.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.getAllIDs = async function () {
|
||||
try {
|
||||
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
const ids = {};
|
||||
for (const workspace of workspaces) {
|
||||
ids[workspace.uuid] = true;
|
||||
}
|
||||
return ids;
|
||||
} catch (error) {
|
||||
this._log.error('Error fetching all workspace IDs', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles changing the ID of a workspace.
|
||||
* @param {String} oldID - The old UUID.
|
||||
* @param {String} newID - The new UUID.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.changeItemID = async function (oldID, newID) {
|
||||
try {
|
||||
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
const workspace = workspaces.find((ws) => ws.uuid === oldID);
|
||||
if (workspace) {
|
||||
workspace.uuid = newID;
|
||||
await ZenWorkspacesStorage.saveWorkspace(workspace, false);
|
||||
// Mark the new ID as changed for sync
|
||||
await ZenWorkspacesStorage.markChanged(newID);
|
||||
}
|
||||
} catch (error) {
|
||||
this._log.error(`Error changing workspace ID from ${oldID} to ${newID}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a workspace exists.
|
||||
* @param {String} id - The UUID of the workspace.
|
||||
* @returns {Boolean} True if the workspace exists, false otherwise.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.itemExists = async function (id) {
|
||||
try {
|
||||
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
return workspaces.some((ws) => ws.uuid === id);
|
||||
} catch (error) {
|
||||
this._log.error(`Error checking if workspace exists with ID ${id}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a record for a workspace.
|
||||
* @param {String} id - The UUID of the workspace.
|
||||
* @param {String} collection - The collection name.
|
||||
* @returns {ZenWorkspaceRecord} The workspace record.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.createRecord = async function (id, collection) {
|
||||
try {
|
||||
const workspaces = await ZenWorkspacesStorage.getWorkspaces();
|
||||
const workspace = workspaces.find((ws) => ws.uuid === id);
|
||||
const record = new ZenWorkspaceRecord(collection, id);
|
||||
|
||||
if (workspace) {
|
||||
record.name = workspace.name;
|
||||
record.icon = workspace.icon;
|
||||
record.default = workspace.default;
|
||||
record.containerTabId = workspace.containerTabId;
|
||||
record.position = workspace.position;
|
||||
if (workspace.theme) {
|
||||
record.theme_type = workspace.theme.type;
|
||||
record.theme_colors = JSON.stringify(workspace.theme.gradientColors);
|
||||
record.theme_opacity = workspace.theme.opacity;
|
||||
record.theme_rotation = workspace.theme.rotation;
|
||||
record.theme_texture = workspace.theme.texture;
|
||||
}
|
||||
record.deleted = false;
|
||||
} else {
|
||||
record.deleted = true;
|
||||
}
|
||||
|
||||
return record;
|
||||
} catch (error) {
|
||||
this._log.error(`Error creating record for workspace ID ${id}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new workspace.
|
||||
* @param {ZenWorkspaceRecord} record - The workspace record to create.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.create = async function (record) {
|
||||
try {
|
||||
this._validateRecord(record);
|
||||
const workspace = {
|
||||
uuid: record.id,
|
||||
name: record.name,
|
||||
icon: record.icon,
|
||||
default: record.default,
|
||||
containerTabId: record.containerTabId,
|
||||
position: record.position,
|
||||
theme: record.theme_type
|
||||
? {
|
||||
type: record.theme_type,
|
||||
gradientColors: JSON.parse(record.theme_colors),
|
||||
opacity: record.theme_opacity,
|
||||
rotation: record.theme_rotation,
|
||||
texture: record.theme_texture,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
await ZenWorkspacesStorage.saveWorkspace(workspace, false);
|
||||
} catch (error) {
|
||||
this._log.error(`Error creating workspace with ID ${record.id}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates an existing workspace.
|
||||
* @param {ZenWorkspaceRecord} record - The workspace record to update.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.update = async function (record) {
|
||||
try {
|
||||
this._validateRecord(record);
|
||||
await this.create(record); // Reuse create for update
|
||||
} catch (error) {
|
||||
this._log.error(`Error updating workspace with ID ${record.id}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a workspace.
|
||||
* @param {ZenWorkspaceRecord} record - The workspace record to remove.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.remove = async function (record) {
|
||||
try {
|
||||
await ZenWorkspacesStorage.removeWorkspace(record.id, false);
|
||||
} catch (error) {
|
||||
this._log.error(`Error removing workspace with ID ${record.id}`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wipes all workspaces from the storage.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.wipe = async function () {
|
||||
try {
|
||||
await ZenWorkspacesStorage.wipeAllWorkspaces();
|
||||
} catch (error) {
|
||||
this._log.error('Error wiping all workspaces', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Validates a workspace record.
|
||||
* @param {ZenWorkspaceRecord} record - The workspace record to validate.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype._validateRecord = function (record) {
|
||||
if (!record.id || typeof record.id !== 'string') {
|
||||
throw new Error('Invalid workspace ID');
|
||||
}
|
||||
if (!record.name || typeof record.name !== 'string') {
|
||||
throw new Error(`Invalid workspace name for ID ${record.id}`);
|
||||
}
|
||||
if (typeof record.default !== 'boolean') {
|
||||
record.default = false;
|
||||
}
|
||||
if (record.icon != null && typeof record.icon !== 'string') {
|
||||
throw new Error(`Invalid icon for workspace ID ${record.id}`);
|
||||
}
|
||||
if (record.containerTabId != null && typeof record.containerTabId !== 'number') {
|
||||
throw new Error(`Invalid containerTabId for workspace ID ${record.id}`);
|
||||
}
|
||||
if (record.position != null && typeof record.position !== 'number') {
|
||||
throw new Error(`Invalid position for workspace ID ${record.id}`);
|
||||
}
|
||||
|
||||
// Validate theme properties if they exist
|
||||
if (record.theme_type) {
|
||||
if (typeof record.theme_type !== 'string') {
|
||||
throw new Error(`Invalid theme_type for workspace ID ${record.id}`);
|
||||
}
|
||||
if (!record.theme_colors || typeof record.theme_colors !== 'string') {
|
||||
throw new Error(`Invalid theme_colors for workspace ID ${record.id}`);
|
||||
}
|
||||
try {
|
||||
JSON.parse(record.theme_colors);
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`Invalid theme_colors JSON for workspace ID ${record.id}. Error: ${e.message}`
|
||||
);
|
||||
}
|
||||
if (record.theme_opacity != null && typeof record.theme_opacity !== 'number') {
|
||||
throw new Error(`Invalid theme_opacity for workspace ID ${record.id}`);
|
||||
}
|
||||
if (record.theme_rotation != null && typeof record.theme_rotation !== 'number') {
|
||||
throw new Error(`Invalid theme_rotation for workspace ID ${record.id}`);
|
||||
}
|
||||
if (record.theme_texture != null && typeof record.theme_texture !== 'number') {
|
||||
throw new Error(`Invalid theme_texture for workspace ID ${record.id}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves changed workspace IDs since the last sync.
|
||||
* @returns {Object} An object mapping workspace UUIDs to their change timestamps.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.getChangedIDs = async function () {
|
||||
try {
|
||||
return await ZenWorkspacesStorage.getChangedIDs();
|
||||
} catch (error) {
|
||||
this._log.error('Error retrieving changed IDs from storage', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears all recorded changes after a successful sync.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.clearChangedIDs = async function () {
|
||||
try {
|
||||
await ZenWorkspacesStorage.clearChangedIDs();
|
||||
} catch (error) {
|
||||
this._log.error('Error clearing changed IDs in storage', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Marks a workspace as changed.
|
||||
* @param {String} uuid - The UUID of the workspace that changed.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.markChanged = async function (uuid) {
|
||||
try {
|
||||
await ZenWorkspacesStorage.markChanged(uuid);
|
||||
} catch (error) {
|
||||
this._log.error(`Error marking workspace ${uuid} as changed`, error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finalizes the store by ensuring all pending operations are completed.
|
||||
*/
|
||||
ZenWorkspacesStore.prototype.finalize = async function () {
|
||||
await Store.prototype.finalize.call(this);
|
||||
};
|
||||
|
||||
// Define ZenWorkspacesTracker
|
||||
function ZenWorkspacesTracker(name, engine) {
|
||||
Tracker.call(this, name, engine);
|
||||
this._ignoreAll = false;
|
||||
|
||||
// Observe profile-before-change to stop the tracker gracefully
|
||||
Services.obs.addObserver(this.asyncObserver, 'profile-before-change');
|
||||
}
|
||||
|
||||
ZenWorkspacesTracker.prototype = Object.create(Tracker.prototype);
|
||||
ZenWorkspacesTracker.prototype.constructor = ZenWorkspacesTracker;
|
||||
|
||||
/**
|
||||
* Retrieves changed workspace IDs by delegating to the store.
|
||||
* @returns {Object} An object mapping workspace UUIDs to their change timestamps.
|
||||
*/
|
||||
ZenWorkspacesTracker.prototype.getChangedIDs = async function () {
|
||||
try {
|
||||
return await this.engine._store.getChangedIDs();
|
||||
} catch (error) {
|
||||
this._log.error('Error retrieving changed IDs from store', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clears all recorded changes after a successful sync.
|
||||
*/
|
||||
ZenWorkspacesTracker.prototype.clearChangedIDs = async function () {
|
||||
try {
|
||||
await this.engine._store.clearChangedIDs();
|
||||
} catch (error) {
|
||||
this._log.error('Error clearing changed IDs in store', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the tracker starts. Registers observers to listen for workspace changes.
|
||||
*/
|
||||
ZenWorkspacesTracker.prototype.onStart = function () {
|
||||
if (this._started) {
|
||||
return;
|
||||
}
|
||||
this._log.trace('Starting tracker');
|
||||
// Register observers for workspace changes
|
||||
Services.obs.addObserver(this.asyncObserver, 'zen-workspace-added');
|
||||
Services.obs.addObserver(this.asyncObserver, 'zen-workspace-removed');
|
||||
Services.obs.addObserver(this.asyncObserver, 'zen-workspace-updated');
|
||||
this._started = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called when the tracker stops. Unregisters observers.
|
||||
*/
|
||||
ZenWorkspacesTracker.prototype.onStop = function () {
|
||||
if (!this._started) {
|
||||
return;
|
||||
}
|
||||
this._log.trace('Stopping tracker');
|
||||
// Unregister observers for workspace changes
|
||||
Services.obs.removeObserver(this.asyncObserver, 'zen-workspace-added');
|
||||
Services.obs.removeObserver(this.asyncObserver, 'zen-workspace-removed');
|
||||
Services.obs.removeObserver(this.asyncObserver, 'zen-workspace-updated');
|
||||
this._started = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles observed events and marks workspaces as changed accordingly.
|
||||
* @param {nsISupports} subject - The subject of the notification.
|
||||
* @param {String} topic - The topic of the notification.
|
||||
* @param {String} data - Additional data (JSON stringified array of UUIDs).
|
||||
*/
|
||||
ZenWorkspacesTracker.prototype.observe = async function (subject, topic, data) {
|
||||
if (this.ignoreAll) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
switch (topic) {
|
||||
case 'profile-before-change':
|
||||
await this.stop();
|
||||
break;
|
||||
case 'zen-workspace-removed':
|
||||
case 'zen-workspace-updated':
|
||||
case 'zen-workspace-added': {
|
||||
let workspaceIDs;
|
||||
if (data) {
|
||||
try {
|
||||
workspaceIDs = JSON.parse(data);
|
||||
if (!Array.isArray(workspaceIDs)) {
|
||||
throw new Error('Parsed data is not an array');
|
||||
}
|
||||
} catch (parseError) {
|
||||
this._log.error(`Failed to parse workspace UUIDs from data: ${data}`, parseError);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this._log.error(`No data received for event ${topic}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.trace(`Observed ${topic} for UUIDs: ${workspaceIDs.join(', ')}`);
|
||||
|
||||
// Process each UUID
|
||||
for (const workspaceID of workspaceIDs) {
|
||||
if (typeof workspaceID === 'string') {
|
||||
// Inform the store about the change
|
||||
await this.engine._store.markChanged(workspaceID);
|
||||
} else {
|
||||
this._log.warn(`Invalid workspace ID encountered: ${workspaceID}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Bump the score once after processing all changes
|
||||
if (workspaceIDs.length > 0) {
|
||||
this.score += SCORE_INCREMENT_XLARGE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this._log.error(`Error handling ${topic} in observe method`, error);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Finalizes the tracker by ensuring all pending operations are completed.
|
||||
*/
|
||||
ZenWorkspacesTracker.prototype.finalize = async function () {
|
||||
await Tracker.prototype.finalize.call(this);
|
||||
};
|
||||
|
||||
// Define ZenWorkspacesEngine
|
||||
function ZenWorkspacesEngine(service) {
|
||||
SyncEngine.call(this, 'Workspaces', service);
|
||||
}
|
||||
|
||||
ZenWorkspacesEngine.prototype = Object.create(SyncEngine.prototype);
|
||||
ZenWorkspacesEngine.prototype.constructor = ZenWorkspacesEngine;
|
||||
|
||||
ZenWorkspacesEngine.prototype._storeObj = ZenWorkspacesStore;
|
||||
ZenWorkspacesEngine.prototype._trackerObj = ZenWorkspacesTracker;
|
||||
ZenWorkspacesEngine.prototype._recordObj = ZenWorkspaceRecord;
|
||||
ZenWorkspacesEngine.prototype.version = 2;
|
||||
|
||||
ZenWorkspacesEngine.prototype.syncPriority = 10;
|
||||
ZenWorkspacesEngine.prototype.allowSkippedRecord = false;
|
||||
|
||||
Object.setPrototypeOf(ZenWorkspacesEngine.prototype, SyncEngine.prototype);
|
||||
@@ -7,7 +7,6 @@
|
||||
content/browser/zen-components/ZenWorkspaces.mjs (../../zen/workspaces/ZenWorkspaces.mjs)
|
||||
content/browser/zen-components/ZenWorkspaceCreation.mjs (../../zen/workspaces/ZenWorkspaceCreation.mjs)
|
||||
content/browser/zen-components/ZenWorkspacesStorage.mjs (../../zen/workspaces/ZenWorkspacesStorage.mjs)
|
||||
content/browser/zen-components/ZenWorkspacesSync.mjs (../../zen/workspaces/ZenWorkspacesSync.mjs)
|
||||
content/browser/zen-components/ZenGradientGenerator.mjs (../../zen/workspaces/ZenGradientGenerator.mjs)
|
||||
* content/browser/zen-styles/zen-workspaces.css (../../zen/workspaces/zen-workspaces.css)
|
||||
content/browser/zen-styles/zen-gradient-generator.css (../../zen/workspaces/zen-gradient-generator.css)
|
||||
@@ -20,7 +20,6 @@ export default [
|
||||
'gZenWorkspaces',
|
||||
'gZenKeyboardShortcutsManager',
|
||||
'ZenWorkspacesEngine',
|
||||
'ZenWorkspacesStorage',
|
||||
'ZenWorkspaceBookmarksStorage',
|
||||
|
||||
'ZEN_KEYSET_ID',
|
||||
|
||||
Reference in New Issue
Block a user