Merge pull request #36 from kristijanribaric/save-workspaces-in-places-db

feat(workspaces): Store workspaces in a database
This commit is contained in:
mauro 🤙 2024-10-03 19:13:02 +02:00 committed by GitHub
commit a4a77f4dc0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 132 additions and 71 deletions

View file

@ -19,6 +19,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
this._expandWorkspacesStrip.bind(this) this._expandWorkspacesStrip.bind(this)
); );
ChromeUtils.defineLazyGetter(this, 'tabContainer', () => document.getElementById('tabbrowser-tabs')); ChromeUtils.defineLazyGetter(this, 'tabContainer', () => document.getElementById('tabbrowser-tabs'));
await ZenWorkspacesStorage.init();
await this.initializeWorkspaces(); await this.initializeWorkspaces();
console.info('ZenWorkspaces: ZenWorkspaces initialized'); console.info('ZenWorkspaces: ZenWorkspaces initialized');
} }
@ -44,22 +45,28 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
getActiveWorkspaceFromCache() { getActiveWorkspaceFromCache() {
try { try {
return this._workspaceCache.workspaces.find((workspace) => workspace.used); const activeWorkspaceId = Services.prefs.getStringPref("zen.workspaces.active", "");
return this._workspaceCache.workspaces.find((workspace) => workspace.uuid === activeWorkspaceId);
} catch (e) { } catch (e) {
return null; return null;
} }
} }
// Wrorkspaces saving/loading
get _storeFile() {
return PathUtils.join(PathUtils.profileDir, 'zen-workspaces', 'Workspaces.json');
}
async _workspaces() { async _workspaces() {
if (!this._workspaceCache) { if (!this._workspaceCache) {
this._workspaceCache = await IOUtils.readJSON(this._storeFile); this._workspaceCache = { workspaces: await ZenWorkspacesStorage.getWorkspaces() };
if (!this._workspaceCache.workspaces) { // Get the active workspace ID from preferences
this._workspaceCache.workspaces = []; const activeWorkspaceId = Services.prefs.getStringPref("zen.workspaces.active", "");
if (activeWorkspaceId) {
const activeWorkspace = this._workspaceCache.workspaces.find(w => w.uuid === activeWorkspaceId);
// Set the active workspace ID to the first one if the one with selected id doesn't exist
if (!activeWorkspace) {
Services.prefs.setStringPref("zen.workspaces.active", this._workspaceCache.workspaces[0]?.uuid);
}
} else {
// Set the active workspace ID to the first one if active workspace doesn't exist
Services.prefs.setStringPref("zen.workspaces.active", this._workspaceCache.workspaces[0]?.uuid);
} }
} }
return this._workspaceCache; return this._workspaceCache;
@ -79,10 +86,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
async initializeWorkspaces() { async initializeWorkspaces() {
Services.prefs.addObserver('zen.workspaces.enabled', this.onWorkspacesEnabledChanged.bind(this)); Services.prefs.addObserver('zen.workspaces.enabled', this.onWorkspacesEnabledChanged.bind(this));
let file = new FileUtils.File(this._storeFile);
if (!file.exists()) {
await IOUtils.writeJSON(this._storeFile, {});
}
await this.initializeWorkspacesButton(); await this.initializeWorkspacesButton();
if (this.workspaceEnabled) { if (this.workspaceEnabled) {
this._initializeWorkspaceCreationIcons(); this._initializeWorkspaceCreationIcons();
@ -94,16 +98,14 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
if (workspaces.workspaces.length === 0) { if (workspaces.workspaces.length === 0) {
await this.createAndSaveWorkspace('Default Workspace', true); await this.createAndSaveWorkspace('Default Workspace', true);
} else { } else {
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); let activeWorkspace = await this.getActiveWorkspace();
if (!activeWorkspace) { if (!activeWorkspace) {
activeWorkspace = workspaces.workspaces.find((workspace) => workspace.default); activeWorkspace = workspaces.workspaces.find((workspace) => workspace.default);
activeWorkspace.used = true; Services.prefs.setStringPref("zen.workspaces.active", activeWorkspace.uuid);
await this.saveWorkspaces();
} }
if (!activeWorkspace) { if (!activeWorkspace) {
activeWorkspace = workspaces.workspaces[0]; activeWorkspace = workspaces.workspaces[0];
activeWorkspace.used = true; Services.prefs.setStringPref("zen.workspaces.active", activeWorkspace.uuid);
await this.saveWorkspaces();
} }
this.changeWorkspace(activeWorkspace, true); this.changeWorkspace(activeWorkspace, true);
} }
@ -171,45 +173,34 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
} }
async saveWorkspace(workspaceData) { async saveWorkspace(workspaceData) {
let json = await IOUtils.readJSON(this._storeFile); await ZenWorkspacesStorage.saveWorkspace(workspaceData);
if (typeof json.workspaces === 'undefined') {
json.workspaces = [];
}
let existing = json.workspaces.findIndex((workspace) => workspace.uuid === workspaceData.uuid);
if (existing >= 0) {
json.workspaces[existing] = workspaceData;
} else {
json.workspaces.push(workspaceData);
}
console.info('ZenWorkspaces: Saving workspace', workspaceData);
await IOUtils.writeJSON(this._storeFile, json);
this._workspaceCache = null; this._workspaceCache = null;
await this._updateWorkspacesChangeContextMenu();
}
async removeWorkspace(windowID) {
let json = await this._workspaces();
console.info('ZenWorkspaces: Removing workspace', windowID);
await this.changeWorkspace(json.workspaces.find((workspace) => workspace.uuid !== windowID));
this._deleteAllTabsInWorkspace(windowID);
delete this._lastSelectedWorkspaceTabs[windowID];
json.workspaces = json.workspaces.filter((workspace) => workspace.uuid !== windowID);
await this.unsafeSaveWorkspaces(json);
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
await this._updateWorkspacesChangeContextMenu(); await this._updateWorkspacesChangeContextMenu();
} }
async saveWorkspaces() { async removeWorkspace(windowID) {
await IOUtils.writeJSON(this._storeFile, await this._workspaces()); let workspacesData = await this._workspaces();
console.info('ZenWorkspaces: Removing workspace', windowID);
await this.changeWorkspace(workspacesData.workspaces.find((workspace) => workspace.uuid !== windowID));
this._deleteAllTabsInWorkspace(windowID);
delete this._lastSelectedWorkspaceTabs[windowID];
await ZenWorkspacesStorage.removeWorkspace(windowID);
this._workspaceCache = null; this._workspaceCache = null;
await this._propagateWorkspaceData();
await this._updateWorkspacesChangeContextMenu();
} }
async unsafeSaveWorkspaces(workspaces) { isWorkspaceActive(workspace) {
await IOUtils.writeJSON(this._storeFile, workspaces); const activeWorkspaceId = Services.prefs.getStringPref("zen.workspaces.active", "");
this._workspaceCache = workspaces; return workspace.uuid === activeWorkspaceId;
} }
async getActiveWorkspace() {
const workspaces = await this._workspaces();
const activeWorkspaceId = Services.prefs.getStringPref("zen.workspaces.active", "");
return workspaces.workspaces.find(workspace => workspace.uuid === activeWorkspaceId);
}
// Workspaces dialog UI management // Workspaces dialog UI management
openSaveDialog() { openSaveDialog() {
@ -272,7 +263,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
element.className = 'subviewbutton'; element.className = 'subviewbutton';
element.setAttribute('tooltiptext', workspace.name); element.setAttribute('tooltiptext', workspace.name);
element.setAttribute('zen-workspace-id', workspace.uuid); element.setAttribute('zen-workspace-id', workspace.uuid);
if (workspace.used) { if (this.isWorkspaceActive(workspace)) {
element.setAttribute('active', 'true'); element.setAttribute('active', 'true');
} }
if (workspace.default) { if (workspace.default) {
@ -328,7 +319,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
}; };
browser.ZenWorkspaces._workspaceCache = null; browser.ZenWorkspaces._workspaceCache = null;
let workspaces = await browser.ZenWorkspaces._workspaces(); let workspaces = await browser.ZenWorkspaces._workspaces();
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); let activeWorkspace = await this.getActiveWorkspace();
currentContainer.innerHTML = ''; currentContainer.innerHTML = '';
workspaceList.innerHTML = ''; workspaceList.innerHTML = '';
workspaceList.parentNode.style.display = 'flex'; workspaceList.parentNode.style.display = 'flex';
@ -343,7 +334,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
currentContainer.appendChild(currentWorkspace); currentContainer.appendChild(currentWorkspace);
} }
for (let workspace of workspaces.workspaces) { for (let workspace of workspaces.workspaces) {
if (workspace.used) { if (this.isWorkspaceActive(workspace)) {
continue; continue;
} }
let workspaceElement = createWorkspaceElement(workspace); let workspaceElement = createWorkspaceElement(workspace);
@ -396,7 +387,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
button.className = 'subviewbutton'; button.className = 'subviewbutton';
button.setAttribute('tooltiptext', workspace.name); button.setAttribute('tooltiptext', workspace.name);
button.setAttribute('zen-workspace-id', workspace.uuid); button.setAttribute('zen-workspace-id', workspace.uuid);
if (workspace.used) { if (this.isWorkspaceActive(workspace)) {
button.setAttribute('active', 'true'); button.setAttribute('active', 'true');
} }
if (workspace.default) { if (workspace.default) {
@ -435,7 +426,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
if (!button) { if (!button) {
return; return;
} }
let activeWorkspace = (await browser.ZenWorkspaces._workspaces()).workspaces.find((workspace) => workspace.used); let activeWorkspace = await this.getActiveWorkspace();
if (activeWorkspace) { if (activeWorkspace) {
button.setAttribute('as-button', 'true'); button.setAttribute('as-button', 'true');
button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button'); button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button');
@ -579,11 +570,9 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
if (!this.workspaceEnabled) { if (!this.workspaceEnabled) {
return; return;
} }
let workspaces = await this._workspaces();
for (let workspace of workspaces.workspaces) { Services.prefs.setStringPref("zen.workspaces.active", window.uuid);
workspace.used = workspace.uuid === window.uuid;
}
await this.unsafeSaveWorkspaces(workspaces);
const shouldAllowPinnedTabs = this._shouldAllowPinTab; const shouldAllowPinnedTabs = this._shouldAllowPinTab;
await this.foreachWindowAsActive(async (browser) => { await this.foreachWindowAsActive(async (browser) => {
browser.ZenWorkspaces.tabContainer._invalidateCachedTabs(); browser.ZenWorkspaces.tabContainer._invalidateCachedTabs();
@ -624,7 +613,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
browser.document.getElementById('tabbrowser-tabs')._positionPinnedTabs(); browser.document.getElementById('tabbrowser-tabs')._positionPinnedTabs();
}); });
await this.saveWorkspaces();
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
} }
@ -635,7 +624,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
menuPopup.innerHTML = ''; menuPopup.innerHTML = '';
const activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); const activeWorkspace = await this.getActiveWorkspace();
for (let workspace of workspaces.workspaces) { for (let workspace of workspaces.workspaces) {
const menuItem = document.createXULElement('menuitem'); const menuItem = document.createXULElement('menuitem');
@ -676,8 +665,8 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
if (tab.getAttribute('zen-workspace-id') || !this.workspaceEnabled) { if (tab.getAttribute('zen-workspace-id') || !this.workspaceEnabled) {
return; return;
} }
let workspaces = await this._workspaces();
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); let activeWorkspace = await this.getActiveWorkspace();
if (!activeWorkspace) { if (!activeWorkspace) {
return; return;
} }
@ -688,8 +677,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
let tab = gBrowser.getTabForBrowser(browser); let tab = gBrowser.getTabForBrowser(browser);
let workspaceID = tab.getAttribute('zen-workspace-id'); let workspaceID = tab.getAttribute('zen-workspace-id');
if (!workspaceID) { if (!workspaceID) {
let workspaces = await this._workspaces(); let activeWorkspace = await this.getActiveWorkspace();
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used);
if (!activeWorkspace || tab.hasAttribute('hidden')) { if (!activeWorkspace || tab.hasAttribute('hidden')) {
return; return;
} }
@ -724,7 +712,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
defaultMenuItem.removeAttribute('disabled'); defaultMenuItem.removeAttribute('disabled');
} }
let openMenuItem = document.getElementById('context_zenOpenWorkspace'); let openMenuItem = document.getElementById('context_zenOpenWorkspace');
if (workspaces.workspaces.find((workspace) => workspace.uuid === this._contextMenuId).used) { if (workspaces.workspaces.find((workspace) => workspace.uuid === this._contextMenuId && this.isWorkspaceActive(workspace))) {
openMenuItem.setAttribute('disabled', 'true'); openMenuItem.setAttribute('disabled', 'true');
} else { } else {
openMenuItem.removeAttribute('disabled'); openMenuItem.removeAttribute('disabled');
@ -757,11 +745,8 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
} }
async setDefaultWorkspace() { async setDefaultWorkspace() {
let workspaces = await this._workspaces(); await ZenWorkspacesStorage.setDefaultWorkspace(this._contextMenuId);
for (let workspace of workspaces.workspaces) { this._workspaceCache = null;
workspace.default = workspace.uuid === this._contextMenuId;
}
await this.unsafeSaveWorkspaces(workspaces);
await this._propagateWorkspaceData(); await this._propagateWorkspaceData();
} }
@ -786,7 +771,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature {
async changeWorkspaceShortcut(offset = 1) { async changeWorkspaceShortcut(offset = 1) {
// Cycle through workspaces // Cycle through workspaces
let workspaces = await this._workspaces(); let workspaces = await this._workspaces();
let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); let activeWorkspace = await this.getActiveWorkspace();
let workspaceIndex = workspaces.workspaces.indexOf(activeWorkspace); let workspaceIndex = workspaces.workspaces.indexOf(activeWorkspace);
// note: offset can be negative // note: offset can be negative
let nextWorkspace = workspaces.workspaces[(workspaceIndex + offset + workspaces.workspaces.length) % workspaces.workspaces.length]; let nextWorkspace = workspaces.workspaces[(workspaceIndex + offset + workspaces.workspaces.length) % workspaces.workspaces.length];

View file

@ -0,0 +1,76 @@
var ZenWorkspacesStorage = {
async init() {
console.log('ZenWorkspacesStorage: Initializing...');
await this._ensureTable();
},
async _ensureTable() {
await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage._ensureTable", async db => {
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,
is_default INTEGER NOT NULL DEFAULT 0,
container_id INTEGER,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
});
},
async saveWorkspace(workspace) {
await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.saveWorkspace", async db => {
const now = Date.now();
await db.executeCached(`
INSERT OR REPLACE INTO zen_workspaces (
uuid, name, icon, is_default, container_id, created_at, updated_at
) VALUES (
:uuid, :name, :icon, :is_default, :container_id,
COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now),
:now
)
`, {
uuid: workspace.uuid,
name: workspace.name,
icon: workspace.icon || null,
is_default: workspace.default ? 1 : 0,
container_id: workspace.containerTabId || null,
now
});
});
},
async getWorkspaces() {
const db = await PlacesUtils.promiseDBConnection();
const rows = await db.execute(`
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"),
default: !!row.getResultByName("is_default"),
containerTabId: row.getResultByName("container_id")
}));
},
async removeWorkspace(uuid) {
await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.removeWorkspace", async db => {
await db.execute(`
DELETE FROM zen_workspaces WHERE uuid = :uuid
`, { uuid });
});
},
async setDefaultWorkspace(uuid) {
await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setDefaultWorkspace", async db => {
await db.executeTransaction(async function() {
await db.execute(`UPDATE zen_workspaces SET is_default = 0`);
await db.execute(`UPDATE zen_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid });
});
});
}
};