diff --git a/src/browser/app/profile/zen-browser.js b/src/browser/app/profile/zen-browser.js
index 387141da..7a9aa9fb 100644
--- a/src/browser/app/profile/zen-browser.js
+++ b/src/browser/app/profile/zen-browser.js
@@ -153,4 +153,4 @@ pref('zen.sidebar.floating', true);
pref('zen.splitView.working', false);
// Zen Workspaces
-pref('zen.workspaces.enabled', false);
+pref('zen.workspaces.enabled', true);
diff --git a/src/browser/base/content/ZenWorkspaces.mjs b/src/browser/base/content/ZenWorkspaces.mjs
index 37f9e774..c300f898 100644
--- a/src/browser/base/content/ZenWorkspaces.mjs
+++ b/src/browser/base/content/ZenWorkspaces.mjs
@@ -1,6 +1,7 @@
var ZenWorkspaces = {
async init() {
+ console.log("Initializing ZenWorkspaces...");
await this.initializeWorkspaces();
console.log("ZenWorkspaces initialized");
},
@@ -18,29 +19,48 @@ var ZenWorkspaces = {
);
},
+ async _workspaces() {
+ if (!this._workspaceCache) {
+ this._workspaceCache = await IOUtils.readJSON(this._storeFile);
+ if (!this._workspaceCache.workspaces) {
+ this._workspaceCache.workspaces = [];
+ }
+ }
+ return this._workspaceCache;
+ },
+
async initializeWorkspaces() {
+ this.initializeWorkspacesButton();
let file = new FileUtils.File(this._storeFile);
if (!file.exists()) {
await IOUtils.writeJSON(this._storeFile, {});
}
+ if (this.workspaceEnabled) {
+ let workspaces = await this._workspaces();
+ console.log("Workspaces loaded", workspaces);
+ if (workspaces.workspaces.length === 0) {
+ await this.createAndSaveWorkspace("Default Workspace");
+ } else {
+ let activeWorkspace = workspaces.workspaces.find(workspace => workspace.used);
+ if (!activeWorkspace) {
+ activeWorkspace = workspaces.workspaces.find(workspace => workspace.default);
+ activeWorkspace.used = true;
+ await this.saveWorkspaces();
+ }
+ await this.changeWorkspace(activeWorkspace);
+ }
+ }
},
async saveWorkspace(workspaceData) {
let json = await IOUtils.readJSON(this._storeFile);
- if (!json.workspaces) {
+ if (typeof json.workspaces === "undefined") {
json.workspaces = [];
}
json.workspaces.push(workspaceData);
console.log("Saving workspace", workspaceData);
await IOUtils.writeJSON(this._storeFile, json);
- },
-
- async loadWorkspace(windowID) {
- let json = await IOUtils.readJSON(this._storeFile);
- if (!json.workspaces) {
- return [];
- }
- return json.workspaces.filter(workspace => workspace.uuid === windowID);
+ this._workspaceCache = null;
},
async removeWorkspace(windowID) {
@@ -52,38 +72,175 @@ var ZenWorkspaces = {
await IOUtils.writeJSON(this._storeFile, json);
},
- async getWorkspaces() {
- let json = await IOUtils.readJSON(this._storeFile);
- return json;
+ async saveWorkspaces() {
+ await IOUtils.writeJSON(this._storeFile, await this._workspaces());
+ this._workspaceCache = null;
+ },
+
+ async unsafeSaveWorkspaces(workspaces) {
+ await IOUtils.writeJSON(this._storeFile, workspaces);
+ this._workspaceCache = null;
},
// Workspaces dialog UI management
-
+ async _propagateWorkspaceData() {
+ let currentContainer = document.getElementById("PanelUI-zen-workspaces-current-info");
+ let workspaceList = document.getElementById("PanelUI-zen-workspaces-list");
+ const createWorkspaceElement = (workspace) => {
+ let element = document.createElement("toolbarbutton");
+ element.className = "subviewbutton";
+ element.setAttribute("tooltiptext", workspace.name);
+ element.setAttribute("zen-workspace-id", workspace.uuid);
+ element.innerHTML = `
+
+ ${workspace.name[0].toUpperCase()}
+
+
+ ${workspace.name}
+
+ `;
+ element.onclick = (async () => await this.changeWorkspace(workspace)).bind(this, workspace);
+ return element;
+ }
+ let workspaces = await this._workspaces();
+ let activeWorkspace = workspaces.workspaces.find(workspace => workspace.used);
+ currentContainer.innerHTML = "";
+ workspaceList.innerHTML = "";
+ workspaceList.parentNode.style.display = "flex";
+ if (workspaces.workspaces.length - 1 <= 0) {
+ workspaceList.parentNode.style.display = "none";
+ }
+ if (activeWorkspace) {
+ let currentWorkspace = createWorkspaceElement(activeWorkspace);
+ currentContainer.appendChild(currentWorkspace);
+ }
+ for (let workspace of workspaces.workspaces) {
+ if (workspace.used) {
+ continue;
+ }
+ let workspaceElement = createWorkspaceElement(workspace);
+ workspaceList.appendChild(workspaceElement);
+ }
+ },
+
+ async openWorkspacesDialog(event) {
+ if (!this.workspaceEnabled) {
+ return;
+ }
+ let target = event.target;
+ let panel = document.getElementById("PanelUI-zen-workspaces");
+ await this._propagateWorkspaceData();
+ PanelMultiView.openPopup(panel, target, {
+ position: "bottomright topright",
+ triggerEvent: event,
+ }).catch(console.error);
+ },
+
+ initializeWorkspacesButton() {
+ if (!this.workspaceEnabled) {
+ return;
+ }
+ let browserTabs = document.getElementById("tabbrowser-tabs");
+ let button = document.createElement("toolbarbutton");
+ button.id = "zen-workspaces-button";
+ button.className = "toolbarbutton-1 chromeclass-toolbar-additional";
+ button.setAttribute("label", "Workspaces");
+ button.setAttribute("tooltiptext", "Workspaces");
+ button.onclick = this.openWorkspacesDialog.bind(this);
+ browserTabs.insertAdjacentElement("beforebegin", button);
+ },
+
+ async _updateWorkspacesButton() {
+ let button = document.getElementById("zen-workspaces-button");
+ if (!button) {
+ return;
+ }
+ let activeWorkspace = (await this._workspaces()).workspaces.find(workspace => workspace.used);
+ if (activeWorkspace) {
+ button.innerHTML = activeWorkspace.name[0].toUpperCase();
+ }
+ },
// Workspaces management
_prepareNewWorkspace(window) {
- for (let tab of window.gBrowser.tabs) {
- tab.addAttribute("zen-workspace-id", window.uuid);
+ document.documentElement.setAttribute("zen-workspace-id", window.uuid);
+ for (let tab of gBrowser.tabs) {
+ if (!tab.getAttribute("zen-workspace-id")) {
+ tab.setAttribute("zen-workspace-id", window.uuid);
+ }
}
- window.document.documentElement.setAttribute("zen-workspace-id", window.uuid);
},
- _createWorkspaceData() {
+ _createNewTabForWorkspace(window) {
+ gZenUIManager.openAndChangeToTab(Services.prefs.getStringPref("browser.startup.homepage"));
+ let tab = gBrowser.selectedTab;
+ tab.setAttribute("zen-workspace-id", window.uuid);
+ },
+
+ async changeWorkspace(window) {
+ if (!this.workspaceEnabled) {
+ return;
+ }
+ if (document.documentElement.getAttribute("zen-workspace-id") === window.uuid) {
+ return;
+ }
+ let firstTab = undefined;
+ // Get the number of tabs that are hidden before we start hiding them
+ let numHiddenTabs = gBrowser.tabs.reduce((acc, tab) => {
+ return tab.getAttribute("zen-workspace-id") !== window.uuid ? acc + 1 : acc;
+ }, 0);
+ if (numHiddenTabs === gBrowser.tabs.length) {
+ // If all tabs are hidden, we need to create a new tab
+ // to show the workspace
+ this._createNewTabForWorkspace(window);
+ }
+ for (let tab of gBrowser.tabs) {
+ if (tab.getAttribute("zen-workspace-id") === window.uuid) {
+ if (!firstTab) {
+ firstTab = tab;
+ }
+ tab.removeAttribute("hidden");
+ }
+ }
+ for (let tab of gBrowser.tabs) {
+ if (tab.getAttribute("zen-workspace-id") !== window.uuid) {
+ tab.setAttribute("hidden", "true");
+ }
+ }
+ let workspaces = await this._workspaces();
+ for (let workspace of workspaces.workspaces) {
+ workspace.used = workspace.uuid === window.uuid;
+ }
+ this.unsafeSaveWorkspaces(workspaces);
+ // TODO: Handle the case when there are no tabs in the workspace
+ gBrowser.selectedTab = firstTab;
+ document.documentElement.setAttribute("zen-workspace-id", window.uuid);
+ await this.saveWorkspaces();
+ await this._updateWorkspacesButton();
+ await this._propagateWorkspaceData();
+ },
+
+ _createWorkspaceData(name) {
let window = {
uuid: gZenUIManager.generateUuidv4(),
default: false,
+ used: true,
icon: "",
- name: `New Workspace`,
+ name: name,
};
this._prepareNewWorkspace(window);
return window;
},
- async createAndSaveWorkspace() {
- let workspaceData = this._createWorkspaceData();
+ async createAndSaveWorkspace(name = "New Workspace") {
+ if (!this.workspaceEnabled) {
+ return;
+ }
+ let workspaceData = this._createWorkspaceData(name);
await this.saveWorkspace(workspaceData);
+ await this.changeWorkspace(workspaceData);
},
};
diff --git a/src/browser/base/content/zen-locales.inc.xhtml b/src/browser/base/content/zen-locales.inc.xhtml
index 71060498..9269e0f5 100644
--- a/src/browser/base/content/zen-locales.inc.xhtml
+++ b/src/browser/base/content/zen-locales.inc.xhtml
@@ -1 +1,2 @@
-
\ No newline at end of file
+
+
diff --git a/src/browser/base/content/zen-popupset.inc.xhtml b/src/browser/base/content/zen-popupset.inc.xhtml
index 0abd8ce6..07a34243 100644
--- a/src/browser/base/content/zen-popupset.inc.xhtml
+++ b/src/browser/base/content/zen-popupset.inc.xhtml
@@ -75,3 +75,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/browser/components/sessionstore/TabState-sys-mjs.patch b/src/browser/components/sessionstore/TabState-sys-mjs.patch
new file mode 100644
index 00000000..95f9ace9
--- /dev/null
+++ b/src/browser/components/sessionstore/TabState-sys-mjs.patch
@@ -0,0 +1,13 @@
+diff --git a/browser/components/sessionstore/TabState.sys.mjs b/browser/components/sessionstore/TabState.sys.mjs
+index 26f5671c849d9b0a126d79b07bc7d3d7870826ec..decc4c975507c9111df78dbc43434fa46d5f5e82 100644
+--- a/browser/components/sessionstore/TabState.sys.mjs
++++ b/browser/components/sessionstore/TabState.sys.mjs
+@@ -98,6 +98,8 @@ var TabStateInternal = {
+ tabData.muteReason = tab.muteReason;
+ }
+
++ tabData.zenWorkspace = tab.getAttribute("zen-workspace-id");
++
+ tabData.searchMode = tab.ownerGlobal.gURLBar.getSearchMode(browser, true);
+
+ tabData.userContextId = tab.userContextId || 0;
diff --git a/src/browser/components/tabbrowser/content/tabbrowser-js.patch b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
index 0be59b0d..de327c07 100644
--- a/src/browser/components/tabbrowser/content/tabbrowser-js.patch
+++ b/src/browser/components/tabbrowser/content/tabbrowser-js.patch
@@ -1,8 +1,20 @@
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
-index 3bca0b6d30468dc3a755219723f673ec80dfce6e..200a609ea31a8c904e2d3e39a3a06ff67fb313ed 100644
+index 2674dc2bebf436529a46d45c52cb56e86b82c03f..c70801bed55e0182eb56e9fd7bea5be9986c7120 100644
--- a/browser/components/tabbrowser/content/tabbrowser.js
+++ b/browser/components/tabbrowser/content/tabbrowser.js
-@@ -7623,6 +7623,11 @@ var TabContextMenu = {
+@@ -3208,6 +3208,11 @@
+ ) {
+ tabWasReused = true;
+ tab = this.selectedTab;
++
++ if (tabData.zenWorkspace) {
++ tab.setAttribute("zen-workspace-id", tabData.zenWorkspace);
++ }
++
+ if (!tabData.pinned) {
+ this.unpinTab(tab);
+ } else {
+@@ -7878,6 +7883,11 @@ var TabContextMenu = {
this.contextTab.linkedBrowser,
document.getElementById("context_sendTabToDevice")
);
diff --git a/src/browser/locales/en-US/browser/zen-workspaces.ftl b/src/browser/locales/en-US/browser/zen-workspaces.ftl
new file mode 100644
index 00000000..04b2a5fc
--- /dev/null
+++ b/src/browser/locales/en-US/browser/zen-workspaces.ftl
@@ -0,0 +1,8 @@
+
+zen-panel-ui-current-window-text = Current Window
+
+zen-panel-ui-workspaces-text = Other Workspaces
+
+zen-panel-ui-workspaces-new
+ .label = New Workspace
+ .accesskey = N
diff --git a/src/browser/themes/shared/zen-icons/icons.css b/src/browser/themes/shared/zen-icons/icons.css
index c003fe16..f571ee8d 100644
--- a/src/browser/themes/shared/zen-icons/icons.css
+++ b/src/browser/themes/shared/zen-icons/icons.css
@@ -153,7 +153,8 @@
#TabsToolbar #new-tab-button,
#appMenu-zoomEnlarge-button2,
#PanelUI-zen-profiles-newProfile,
-#zen-sidebar-add-panel-button {
+#zen-sidebar-add-panel-button,
+#PanelUI-zen-workspaces-new {
list-style-image: url("plus.svg") !important;
}
diff --git a/src/browser/themes/shared/zen-panel-ui.css b/src/browser/themes/shared/zen-panel-ui.css
index ca5a0098..6d417002 100644
--- a/src/browser/themes/shared/zen-panel-ui.css
+++ b/src/browser/themes/shared/zen-panel-ui.css
@@ -234,3 +234,85 @@
#zenSplitViewModifierViewDefault .zen-split-view-modifier-preview.grid box:nth-child(3) {
grid-area: c;
}
+
+/* Workspaces */
+
+#zen-workspaces-button {
+ border: 1px solid var(--zen-colors-border);
+ border-radius: 50px;
+ height: calc(var(--zen-sidebar-action-button-width) - 10px) !important;
+ margin-bottom: 5px !important;
+ justify-content: center;
+ align-items: center;
+ display: flex;
+ font-weight: 600;
+}
+
+#PanelUI-zen-workspaces {
+ width: 300px;
+ min-height: 200px;
+}
+
+#PanelUI-zen-workspaces > vbox {
+ width: 100%;
+ padding: 15px 20px;
+ position: relative;
+}
+
+#PanelUI-zen-workspaces-list {
+ display: flex;
+ flex-direction: column;
+}
+
+#PanelUI-zen-workspaces > vbox > vbox:nth-child(2) {
+ margin-top: 10px;
+}
+
+#PanelUI-zen-workspaces-current-info toolbarbutton:last-child {
+ margin-bottom: 0 !important;
+}
+
+#PanelUI-zen-workspaces-list toolbarbutton,
+#PanelUI-zen-workspaces-current-info toolbarbutton {
+ padding: 5px 10px;
+ border-radius: 5px;
+
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+
+ display: flex;
+ align-items: center;
+
+ &:first-child {
+ margin-top: 10px;
+ }
+
+ & .zen-workspace-icon {
+ width: 30px;
+ height: 30px;
+ border-radius: 5px;
+ margin-right: 15px;
+ border: 1px solid var(--zen-colors-border);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-weight: 600;
+ }
+
+ & .zen-workspace-name {
+ font-weight: 600;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+}
+
+#PanelUI-zen-workspaces-new {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ padding: 3px;
+ border-radius: 4px;
+ width: 20px;
+ height: 20px;
+}