From de8a857549fea089b86875e387b1fd6a2654f0dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Wed, 2 Oct 2024 09:27:29 +0200 Subject: [PATCH 1/5] feat(workspaces): Store workspaces in a database This commit introduces a new `ZenWorkspacesStorage` class to handle the persistence of workspaces in a database. The previous implementation used JSON files, but this approach brings several advantages, including: - Improved performance and scalability - Easier data management and synchronization - Enhanced security and data integrity This change removes the reliance on JSON files and streamlines workspace management, leading to a more robust and reliable system. --- src/ZenWorkspaces.mjs | 45 +++++++----------- src/ZenWorkspacesStorage.mjs | 89 ++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 28 deletions(-) create mode 100644 src/ZenWorkspacesStorage.mjs diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index e43699f..df1caa9 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -19,6 +19,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { this._expandWorkspacesStrip.bind(this) ); ChromeUtils.defineLazyGetter(this, 'tabContainer', () => document.getElementById('tabbrowser-tabs')); + await ZenWorkspacesStorage.init(); await this.initializeWorkspaces(); console.info('ZenWorkspaces: ZenWorkspaces initialized'); } @@ -57,10 +58,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { async _workspaces() { if (!this._workspaceCache) { - this._workspaceCache = await IOUtils.readJSON(this._storeFile); - if (!this._workspaceCache.workspaces) { - this._workspaceCache.workspaces = []; - } + this._workspaceCache = { workspaces: await ZenWorkspacesStorage.getWorkspaces() }; } return this._workspaceCache; } @@ -171,42 +169,34 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { } async saveWorkspace(workspaceData) { - let json = await IOUtils.readJSON(this._storeFile); - 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); + await ZenWorkspacesStorage.saveWorkspace(workspaceData); 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 ZenWorkspacesStorage.removeWorkspace(windowID); + this._workspaceCache = null; await this._propagateWorkspaceData(); await this._updateWorkspacesChangeContextMenu(); } async saveWorkspaces() { - await IOUtils.writeJSON(this._storeFile, await this._workspaces()); + const workspaces = await this._workspaces(); + for (const workspace of workspaces.workspaces) { + await ZenWorkspacesStorage.saveWorkspace(workspace); + } this._workspaceCache = null; } async unsafeSaveWorkspaces(workspaces) { - await IOUtils.writeJSON(this._storeFile, workspaces); + for (const workspace of workspaces.workspaces) { + await ZenWorkspacesStorage.saveWorkspace(workspace); + } this._workspaceCache = workspaces; } @@ -624,6 +614,8 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { browser.document.getElementById('tabbrowser-tabs')._positionPinnedTabs(); }); + + await ZenWorkspacesStorage.setActiveWorkspace(window.uuid); await this.saveWorkspaces(); await this._propagateWorkspaceData(); } @@ -748,7 +740,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { onContextMenuClose() { let target = document.querySelector( - `#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions` + `#PanelUI-zen-workspaces [zen-workspace-id="${this._contextMenuId}"] .zen-workspace-actions` ); if (target) { target.removeAttribute('active'); @@ -757,11 +749,8 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { } async setDefaultWorkspace() { - let workspaces = await this._workspaces(); - for (let workspace of workspaces.workspaces) { - workspace.default = workspace.uuid === this._contextMenuId; - } - await this.unsafeSaveWorkspaces(workspaces); + await ZenWorkspacesStorage.setDefaultWorkspace(this._contextMenuId); + this._workspaceCache = null; await this._propagateWorkspaceData(); } diff --git a/src/ZenWorkspacesStorage.mjs b/src/ZenWorkspacesStorage.mjs new file mode 100644 index 0000000..325497d --- /dev/null +++ b/src/ZenWorkspacesStorage.mjs @@ -0,0 +1,89 @@ +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 moz_workspaces ( + id INTEGER PRIMARY KEY, + uuid TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, + icon TEXT, + is_default INTEGER NOT NULL DEFAULT 0, + is_active 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 moz_workspaces ( + uuid, name, icon, is_default, is_active, container_id, created_at, updated_at + ) VALUES ( + :uuid, :name, :icon, :is_default, :is_active, :container_id, + COALESCE((SELECT created_at FROM moz_workspaces WHERE uuid = :uuid), :now), + :now + ) + `, { + uuid: workspace.uuid, + name: workspace.name, + icon: workspace.icon || null, + is_default: workspace.default ? 1 : 0, + is_active: workspace.used ? 1 : 0, + container_id: workspace.containerTabId || null, + now + }); + }); + }, + + async getWorkspaces() { + const db = await PlacesUtils.promiseDBConnection(); + const rows = await db.execute(` + SELECT * FROM moz_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"), + used: !!row.getResultByName("is_active"), + containerTabId: row.getResultByName("container_id") + })); + }, + + async removeWorkspace(uuid) { + await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.removeWorkspace", async db => { + await db.execute(` + DELETE FROM moz_workspaces WHERE uuid = :uuid + `, { uuid }); + }); + }, + + async setActiveWorkspace(uuid) { + await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setActiveWorkspace", async db => { + await db.executeTransaction(async function() { + await db.execute(`UPDATE moz_workspaces SET is_active = 0`); + await db.execute(`UPDATE moz_workspaces SET is_active = 1 WHERE uuid = :uuid`, { uuid }); + }); + }); + }, + + async setDefaultWorkspace(uuid) { + await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setDefaultWorkspace", async db => { + await db.executeTransaction(async function() { + await db.execute(`UPDATE moz_workspaces SET is_default = 0`); + await db.execute(`UPDATE moz_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid }); + }); + }); + } +}; \ No newline at end of file From 62be9d1fdd76ed9cc6f8214085e2d42c61225848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Wed, 2 Oct 2024 14:44:48 +0200 Subject: [PATCH 2/5] Refactor: Remove unused workspace storage logic --- src/ZenWorkspaces.mjs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index df1caa9..f62efcd 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -51,11 +51,6 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { } } - // Wrorkspaces saving/loading - get _storeFile() { - return PathUtils.join(PathUtils.profileDir, 'zen-workspaces', 'Workspaces.json'); - } - async _workspaces() { if (!this._workspaceCache) { this._workspaceCache = { workspaces: await ZenWorkspacesStorage.getWorkspaces() }; @@ -77,10 +72,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { async initializeWorkspaces() { 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(); if (this.workspaceEnabled) { this._initializeWorkspaceCreationIcons(); @@ -175,8 +167,9 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { } async removeWorkspace(windowID) { + let workspacesData = await this._workspaces(); console.info('ZenWorkspaces: Removing workspace', windowID); - await this.changeWorkspace(json.workspaces.find((workspace) => workspace.uuid !== windowID)); + await this.changeWorkspace(workspacesData.workspaces.find((workspace) => workspace.uuid !== windowID)); this._deleteAllTabsInWorkspace(windowID); delete this._lastSelectedWorkspaceTabs[windowID]; await ZenWorkspacesStorage.removeWorkspace(windowID); From 511281c6881e2b5e7ed27ac40eda950b1dd3e7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Wed, 2 Oct 2024 15:32:47 +0200 Subject: [PATCH 3/5] feat(db): rename workspace table to `zen_workspaces` Renames the `moz_workspaces` table to `zen_workspaces` for consistency and better organization. --- src/ZenWorkspacesStorage.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/ZenWorkspacesStorage.mjs b/src/ZenWorkspacesStorage.mjs index 325497d..75bbb4c 100644 --- a/src/ZenWorkspacesStorage.mjs +++ b/src/ZenWorkspacesStorage.mjs @@ -7,7 +7,7 @@ var ZenWorkspacesStorage = { async _ensureTable() { await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage._ensureTable", async db => { await db.execute(` - CREATE TABLE IF NOT EXISTS moz_workspaces ( + CREATE TABLE IF NOT EXISTS zen_workspaces ( id INTEGER PRIMARY KEY, uuid TEXT UNIQUE NOT NULL, name TEXT NOT NULL, @@ -26,11 +26,11 @@ var ZenWorkspacesStorage = { await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.saveWorkspace", async db => { const now = Date.now(); await db.executeCached(` - INSERT OR REPLACE INTO moz_workspaces ( + INSERT OR REPLACE INTO zen_workspaces ( uuid, name, icon, is_default, is_active, container_id, created_at, updated_at ) VALUES ( :uuid, :name, :icon, :is_default, :is_active, :container_id, - COALESCE((SELECT created_at FROM moz_workspaces WHERE uuid = :uuid), :now), + COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now), :now ) `, { @@ -48,7 +48,7 @@ var ZenWorkspacesStorage = { async getWorkspaces() { const db = await PlacesUtils.promiseDBConnection(); const rows = await db.execute(` - SELECT * FROM moz_workspaces ORDER BY created_at ASC + SELECT * FROM zen_workspaces ORDER BY created_at ASC `); return rows.map(row => ({ @@ -64,7 +64,7 @@ var ZenWorkspacesStorage = { async removeWorkspace(uuid) { await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.removeWorkspace", async db => { await db.execute(` - DELETE FROM moz_workspaces WHERE uuid = :uuid + DELETE FROM zen_workspaces WHERE uuid = :uuid `, { uuid }); }); }, @@ -72,8 +72,8 @@ var ZenWorkspacesStorage = { async setActiveWorkspace(uuid) { await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setActiveWorkspace", async db => { await db.executeTransaction(async function() { - await db.execute(`UPDATE moz_workspaces SET is_active = 0`); - await db.execute(`UPDATE moz_workspaces SET is_active = 1 WHERE uuid = :uuid`, { uuid }); + await db.execute(`UPDATE zen_workspaces SET is_active = 0`); + await db.execute(`UPDATE zen_workspaces SET is_active = 1 WHERE uuid = :uuid`, { uuid }); }); }); }, @@ -81,8 +81,8 @@ var ZenWorkspacesStorage = { async setDefaultWorkspace(uuid) { await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setDefaultWorkspace", async db => { await db.executeTransaction(async function() { - await db.execute(`UPDATE moz_workspaces SET is_default = 0`); - await db.execute(`UPDATE moz_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid }); + await db.execute(`UPDATE zen_workspaces SET is_default = 0`); + await db.execute(`UPDATE zen_workspaces SET is_default = 1 WHERE uuid = :uuid`, { uuid }); }); }); } From 9bcb66c768eccef9b15312666602c104cf94bf9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Thu, 3 Oct 2024 10:19:18 +0200 Subject: [PATCH 4/5] feat(workspaces): Use preferences to manage active workspace This commit updates the workspaces system to use preferences to manage the active workspace, instead of storing the active state in each workspace object. The following changes were made: - **ZenWorkspaces.mjs:** - Removed the `used` property from workspace objects and replaced it with a preference (`zen.workspaces.active`) to store the active workspace ID. - Modified the `getActiveWorkspace` and `changeWorkspace` methods to use preferences instead of the `used` property. - Added a new `isWorkspaceActive` method to check if a workspace is active. - **ZenWorkspacesStorage.mjs:** - Removed the `is_active` column from the `zen_workspaces` table. - Removed the `setActiveWorkspace` method as the active workspace is now managed by preferences. This change simplifies the code and makes it easier to manage the active workspace across multiple browser windows and doesn't write to the database on every workspace change. Additionaly, it enables local active workspace selection when workspace sync is implemented. --- src/ZenWorkspaces.mjs | 79 +++++++++++++++++++----------------- src/ZenWorkspacesStorage.mjs | 17 +------- 2 files changed, 43 insertions(+), 53 deletions(-) diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index f62efcd..1f340a0 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -45,7 +45,8 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { getActiveWorkspaceFromCache() { 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) { return null; } @@ -54,6 +55,19 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { async _workspaces() { if (!this._workspaceCache) { this._workspaceCache = { workspaces: await ZenWorkspacesStorage.getWorkspaces() }; + // Get the active workspace ID from preferences + 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; } @@ -84,16 +98,14 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { if (workspaces.workspaces.length === 0) { await this.createAndSaveWorkspace('Default Workspace', true); } else { - let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); + let activeWorkspace = await this.getActiveWorkspace(); if (!activeWorkspace) { activeWorkspace = workspaces.workspaces.find((workspace) => workspace.default); - activeWorkspace.used = true; - await this.saveWorkspaces(); + Services.prefs.setStringPref("zen.workspaces.active", activeWorkspace.uuid); } if (!activeWorkspace) { activeWorkspace = workspaces.workspaces[0]; - activeWorkspace.used = true; - await this.saveWorkspaces(); + Services.prefs.setStringPref("zen.workspaces.active", activeWorkspace.uuid); } this.changeWorkspace(activeWorkspace, true); } @@ -163,6 +175,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { async saveWorkspace(workspaceData) { await ZenWorkspacesStorage.saveWorkspace(workspaceData); this._workspaceCache = null; + await this._propagateWorkspaceData(); await this._updateWorkspacesChangeContextMenu(); } @@ -178,21 +191,16 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { await this._updateWorkspacesChangeContextMenu(); } - async saveWorkspaces() { + isWorkspaceActive(workspace) { + const activeWorkspaceId = Services.prefs.getStringPref("zen.workspaces.active", ""); + return workspace.uuid === activeWorkspaceId; + } + + async getActiveWorkspace() { const workspaces = await this._workspaces(); - for (const workspace of workspaces.workspaces) { - await ZenWorkspacesStorage.saveWorkspace(workspace); - } - this._workspaceCache = null; + const activeWorkspaceId = Services.prefs.getStringPref("zen.workspaces.active", ""); + return workspaces.workspaces.find(workspace => workspace.uuid === activeWorkspaceId); } - - async unsafeSaveWorkspaces(workspaces) { - for (const workspace of workspaces.workspaces) { - await ZenWorkspacesStorage.saveWorkspace(workspace); - } - this._workspaceCache = workspaces; - } - // Workspaces dialog UI management openSaveDialog() { @@ -255,7 +263,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { element.className = 'subviewbutton'; element.setAttribute('tooltiptext', workspace.name); element.setAttribute('zen-workspace-id', workspace.uuid); - if (workspace.used) { + if (this.isWorkspaceActive(workspace)) { element.setAttribute('active', 'true'); } if (workspace.default) { @@ -311,7 +319,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { }; browser.ZenWorkspaces._workspaceCache = null; let workspaces = await browser.ZenWorkspaces._workspaces(); - let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); + let activeWorkspace = await this.getActiveWorkspace(); currentContainer.innerHTML = ''; workspaceList.innerHTML = ''; workspaceList.parentNode.style.display = 'flex'; @@ -326,7 +334,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { currentContainer.appendChild(currentWorkspace); } for (let workspace of workspaces.workspaces) { - if (workspace.used) { + if (this.isWorkspaceActive(workspace)) { continue; } let workspaceElement = createWorkspaceElement(workspace); @@ -379,7 +387,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { button.className = 'subviewbutton'; button.setAttribute('tooltiptext', workspace.name); button.setAttribute('zen-workspace-id', workspace.uuid); - if (workspace.used) { + if (this.isWorkspaceActive(workspace)) { button.setAttribute('active', 'true'); } if (workspace.default) { @@ -418,7 +426,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { if (!button) { return; } - let activeWorkspace = (await browser.ZenWorkspaces._workspaces()).workspaces.find((workspace) => workspace.used); + let activeWorkspace = await this.getActiveWorkspace(); if (activeWorkspace) { button.setAttribute('as-button', 'true'); button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button'); @@ -562,11 +570,9 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { if (!this.workspaceEnabled) { return; } - let workspaces = await this._workspaces(); - for (let workspace of workspaces.workspaces) { - workspace.used = workspace.uuid === window.uuid; - } - await this.unsafeSaveWorkspaces(workspaces); + + Services.prefs.setStringPref("zen.workspaces.active", window.uuid); + const shouldAllowPinnedTabs = this._shouldAllowPinTab; await this.foreachWindowAsActive(async (browser) => { browser.ZenWorkspaces.tabContainer._invalidateCachedTabs(); @@ -608,8 +614,6 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { browser.document.getElementById('tabbrowser-tabs')._positionPinnedTabs(); }); - await ZenWorkspacesStorage.setActiveWorkspace(window.uuid); - await this.saveWorkspaces(); await this._propagateWorkspaceData(); } @@ -620,7 +624,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { menuPopup.innerHTML = ''; - const activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); + const activeWorkspace = await this.getActiveWorkspace(); for (let workspace of workspaces.workspaces) { const menuItem = document.createXULElement('menuitem'); @@ -661,8 +665,8 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { if (tab.getAttribute('zen-workspace-id') || !this.workspaceEnabled) { return; } - let workspaces = await this._workspaces(); - let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); + + let activeWorkspace = await this.getActiveWorkspace(); if (!activeWorkspace) { return; } @@ -673,8 +677,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { let tab = gBrowser.getTabForBrowser(browser); let workspaceID = tab.getAttribute('zen-workspace-id'); if (!workspaceID) { - let workspaces = await this._workspaces(); - let activeWorkspace = workspaces.workspaces.find((workspace) => workspace.used); + let activeWorkspace = await this.getActiveWorkspace(); if (!activeWorkspace || tab.hasAttribute('hidden')) { return; } @@ -709,7 +712,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { defaultMenuItem.removeAttribute('disabled'); } 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'); } else { openMenuItem.removeAttribute('disabled'); @@ -768,7 +771,7 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { async changeWorkspaceShortcut(offset = 1) { // Cycle through 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); // note: offset can be negative let nextWorkspace = workspaces.workspaces[(workspaceIndex + offset + workspaces.workspaces.length) % workspaces.workspaces.length]; diff --git a/src/ZenWorkspacesStorage.mjs b/src/ZenWorkspacesStorage.mjs index 75bbb4c..cb427f7 100644 --- a/src/ZenWorkspacesStorage.mjs +++ b/src/ZenWorkspacesStorage.mjs @@ -13,7 +13,6 @@ var ZenWorkspacesStorage = { name TEXT NOT NULL, icon TEXT, is_default INTEGER NOT NULL DEFAULT 0, - is_active INTEGER NOT NULL DEFAULT 0, container_id INTEGER, created_at INTEGER NOT NULL, updated_at INTEGER NOT NULL @@ -27,9 +26,9 @@ var ZenWorkspacesStorage = { const now = Date.now(); await db.executeCached(` INSERT OR REPLACE INTO zen_workspaces ( - uuid, name, icon, is_default, is_active, container_id, created_at, updated_at + uuid, name, icon, is_default, container_id, created_at, updated_at ) VALUES ( - :uuid, :name, :icon, :is_default, :is_active, :container_id, + :uuid, :name, :icon, :is_default, :container_id, COALESCE((SELECT created_at FROM zen_workspaces WHERE uuid = :uuid), :now), :now ) @@ -38,7 +37,6 @@ var ZenWorkspacesStorage = { name: workspace.name, icon: workspace.icon || null, is_default: workspace.default ? 1 : 0, - is_active: workspace.used ? 1 : 0, container_id: workspace.containerTabId || null, now }); @@ -50,13 +48,11 @@ var ZenWorkspacesStorage = { 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"), - used: !!row.getResultByName("is_active"), containerTabId: row.getResultByName("container_id") })); }, @@ -69,15 +65,6 @@ var ZenWorkspacesStorage = { }); }, - async setActiveWorkspace(uuid) { - await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setActiveWorkspace", async db => { - await db.executeTransaction(async function() { - await db.execute(`UPDATE zen_workspaces SET is_active = 0`); - await db.execute(`UPDATE zen_workspaces SET is_active = 1 WHERE uuid = :uuid`, { uuid }); - }); - }); - }, - async setDefaultWorkspace(uuid) { await PlacesUtils.withConnectionWrapper("ZenWorkspacesStorage.setDefaultWorkspace", async db => { await db.executeTransaction(async function() { From 05acfdb27efe3dab229914282802ceb6551bc84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Thu, 3 Oct 2024 11:52:18 +0200 Subject: [PATCH 5/5] Fix: Prevent potential crash when workspace doesn't exist This commit addresses a potential crash that could occur when the active workspace no longer exists. The issue was that the code attempted to access the `uuid` property of the first workspace in the list even if the list was empty. This commit fixes the issue by using optional chaining (`?.`) to safely access the `uuid` property, ensuring the code doesn't crash if the workspace list is empty. --- src/ZenWorkspaces.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index 1f340a0..add6900 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -62,11 +62,11 @@ var ZenWorkspaces = new class extends ZenMultiWindowFeature { 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); + 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); + Services.prefs.setStringPref("zen.workspaces.active", this._workspaceCache.workspaces[0]?.uuid); } } return this._workspaceCache;