mirror of
https://github.com/zen-browser/desktop.git
synced 2025-07-09 16:45:30 +02:00
feat: Enable Zen Workspaces functionality
This commit is contained in:
parent
c63cfebd4f
commit
aa812d1fea
9 changed files with 315 additions and 25 deletions
|
@ -153,4 +153,4 @@ pref('zen.sidebar.floating', true);
|
||||||
pref('zen.splitView.working', false);
|
pref('zen.splitView.working', false);
|
||||||
|
|
||||||
// Zen Workspaces
|
// Zen Workspaces
|
||||||
pref('zen.workspaces.enabled', false);
|
pref('zen.workspaces.enabled', true);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
|
||||||
var ZenWorkspaces = {
|
var ZenWorkspaces = {
|
||||||
async init() {
|
async init() {
|
||||||
|
console.log("Initializing ZenWorkspaces...");
|
||||||
await this.initializeWorkspaces();
|
await this.initializeWorkspaces();
|
||||||
console.log("ZenWorkspaces initialized");
|
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() {
|
async initializeWorkspaces() {
|
||||||
|
this.initializeWorkspacesButton();
|
||||||
let file = new FileUtils.File(this._storeFile);
|
let file = new FileUtils.File(this._storeFile);
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
await IOUtils.writeJSON(this._storeFile, {});
|
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) {
|
async saveWorkspace(workspaceData) {
|
||||||
let json = await IOUtils.readJSON(this._storeFile);
|
let json = await IOUtils.readJSON(this._storeFile);
|
||||||
if (!json.workspaces) {
|
if (typeof json.workspaces === "undefined") {
|
||||||
json.workspaces = [];
|
json.workspaces = [];
|
||||||
}
|
}
|
||||||
json.workspaces.push(workspaceData);
|
json.workspaces.push(workspaceData);
|
||||||
console.log("Saving workspace", workspaceData);
|
console.log("Saving workspace", workspaceData);
|
||||||
await IOUtils.writeJSON(this._storeFile, json);
|
await IOUtils.writeJSON(this._storeFile, json);
|
||||||
},
|
this._workspaceCache = null;
|
||||||
|
|
||||||
async loadWorkspace(windowID) {
|
|
||||||
let json = await IOUtils.readJSON(this._storeFile);
|
|
||||||
if (!json.workspaces) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
return json.workspaces.filter(workspace => workspace.uuid === windowID);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeWorkspace(windowID) {
|
async removeWorkspace(windowID) {
|
||||||
|
@ -52,38 +72,175 @@ var ZenWorkspaces = {
|
||||||
await IOUtils.writeJSON(this._storeFile, json);
|
await IOUtils.writeJSON(this._storeFile, json);
|
||||||
},
|
},
|
||||||
|
|
||||||
async getWorkspaces() {
|
async saveWorkspaces() {
|
||||||
let json = await IOUtils.readJSON(this._storeFile);
|
await IOUtils.writeJSON(this._storeFile, await this._workspaces());
|
||||||
return json;
|
this._workspaceCache = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
async unsafeSaveWorkspaces(workspaces) {
|
||||||
|
await IOUtils.writeJSON(this._storeFile, workspaces);
|
||||||
|
this._workspaceCache = null;
|
||||||
},
|
},
|
||||||
|
|
||||||
// Workspaces dialog UI management
|
// 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 = `
|
||||||
|
<div class="zen-workspace-icon">
|
||||||
|
${workspace.name[0].toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<div class="zen-workspace-name">
|
||||||
|
${workspace.name}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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
|
// Workspaces management
|
||||||
|
|
||||||
_prepareNewWorkspace(window) {
|
_prepareNewWorkspace(window) {
|
||||||
for (let tab of window.gBrowser.tabs) {
|
document.documentElement.setAttribute("zen-workspace-id", window.uuid);
|
||||||
tab.addAttribute("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 = {
|
let window = {
|
||||||
uuid: gZenUIManager.generateUuidv4(),
|
uuid: gZenUIManager.generateUuidv4(),
|
||||||
default: false,
|
default: false,
|
||||||
|
used: true,
|
||||||
icon: "",
|
icon: "",
|
||||||
name: `New Workspace`,
|
name: name,
|
||||||
};
|
};
|
||||||
this._prepareNewWorkspace(window);
|
this._prepareNewWorkspace(window);
|
||||||
return window;
|
return window;
|
||||||
},
|
},
|
||||||
|
|
||||||
async createAndSaveWorkspace() {
|
async createAndSaveWorkspace(name = "New Workspace") {
|
||||||
let workspaceData = this._createWorkspaceData();
|
if (!this.workspaceEnabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let workspaceData = this._createWorkspaceData(name);
|
||||||
await this.saveWorkspace(workspaceData);
|
await this.saveWorkspace(workspaceData);
|
||||||
|
await this.changeWorkspace(workspaceData);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
<link rel="localization" href="browser/zen-web-side-panels.ftl"/>
|
<link rel="localization" href="browser/zen-web-side-panels.ftl"/>
|
||||||
|
<link rel="localization" href="browser/zen-workspaces.ftl"/>
|
||||||
|
|
|
@ -75,3 +75,19 @@
|
||||||
</vbox>
|
</vbox>
|
||||||
</vbox>
|
</vbox>
|
||||||
</panelview>
|
</panelview>
|
||||||
|
|
||||||
|
<panel id="PanelUI-zen-workspaces" position="bottomright topright" mainview="true" side="left">
|
||||||
|
<vbox>
|
||||||
|
<vbox>
|
||||||
|
<h3 data-l10n-id="zen-panel-ui-current-window-text"></h3>
|
||||||
|
<html:div id="PanelUI-zen-workspaces-current-info">
|
||||||
|
</html:div>
|
||||||
|
</vbox>
|
||||||
|
<vbox>
|
||||||
|
<h3 data-l10n-id="zen-panel-ui-workspaces-text"></h3>
|
||||||
|
<html:div id="PanelUI-zen-workspaces-list">
|
||||||
|
</html:div>
|
||||||
|
</vbox>
|
||||||
|
<toolbarbutton id="PanelUI-zen-workspaces-new" oncommand="ZenWorkspaces.createAndSaveWorkspace();" class="subviewbutton" data-l10n-id="zen-panel-ui-workspaces-new"></toolbarbutton>
|
||||||
|
</vbox>
|
||||||
|
</panel>
|
13
src/browser/components/sessionstore/TabState-sys-mjs.patch
Normal file
13
src/browser/components/sessionstore/TabState-sys-mjs.patch
Normal file
|
@ -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;
|
|
@ -1,8 +1,20 @@
|
||||||
diff --git a/browser/components/tabbrowser/content/tabbrowser.js b/browser/components/tabbrowser/content/tabbrowser.js
|
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
|
--- a/browser/components/tabbrowser/content/tabbrowser.js
|
||||||
+++ b/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,
|
this.contextTab.linkedBrowser,
|
||||||
document.getElementById("context_sendTabToDevice")
|
document.getElementById("context_sendTabToDevice")
|
||||||
);
|
);
|
||||||
|
|
8
src/browser/locales/en-US/browser/zen-workspaces.ftl
Normal file
8
src/browser/locales/en-US/browser/zen-workspaces.ftl
Normal file
|
@ -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
|
|
@ -153,7 +153,8 @@
|
||||||
#TabsToolbar #new-tab-button,
|
#TabsToolbar #new-tab-button,
|
||||||
#appMenu-zoomEnlarge-button2,
|
#appMenu-zoomEnlarge-button2,
|
||||||
#PanelUI-zen-profiles-newProfile,
|
#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;
|
list-style-image: url("plus.svg") !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -234,3 +234,85 @@
|
||||||
#zenSplitViewModifierViewDefault .zen-split-view-modifier-preview.grid box:nth-child(3) {
|
#zenSplitViewModifierViewDefault .zen-split-view-modifier-preview.grid box:nth-child(3) {
|
||||||
grid-area: c;
|
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;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue