From 17cace30975c98d8acb928b675d87b7b5b948611 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Tue, 15 Oct 2024 19:55:53 +0200 Subject: [PATCH 1/2] feat: Allow workspace reordering This commit introduces the ability to reorder workspaces in the workspace manager. This adds functionality to move workspaces up and down within the list, allowing users to customize the order in which they appear. - Added new buttons for moving workspaces up and down. - Implemented `moveWorkspaceUp` and `moveWorkspaceDown` methods in `ZenWorkspacesStorage` to handle workspace reordering. - Updated `ZenWorkspaces` to handle the new reorder mode and button events. - Modified the workspace list UI to display reorder buttons and indicate when reorder mode is active. These changes provide users with more control over their workspace organization and improve the overall usability of the workspace manager. --- src/ZenWorkspaces.mjs | 221 +++++++++++++++++++++------------- src/ZenWorkspacesStorage.mjs | 222 +++++++++++++++++++++++++---------- 2 files changed, 301 insertions(+), 142 deletions(-) diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index 2146da2..728547f 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -305,11 +305,10 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { async _propagateWorkspaceData({ ignoreStrip = false } = {}) { await this.foreachWindowAsActive(async (browser) => { - let currentContainer = browser.document.getElementById('PanelUI-zen-workspaces-current-info'); let workspaceList = browser.document.getElementById('PanelUI-zen-workspaces-list'); const createWorkspaceElement = (workspace) => { let element = browser.document.createXULElement('toolbarbutton'); - element.className = 'subviewbutton'; + element.className = 'subviewbutton zen-workspace-button'; element.setAttribute('tooltiptext', workspace.name); element.setAttribute('zen-workspace-id', workspace.uuid); if (this.isWorkspaceActive(workspace)) { @@ -334,6 +333,15 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
+
+ + + + + + +
+ @@ -341,6 +349,16 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { // use text content instead of innerHTML to avoid XSS childs.querySelector('.zen-workspace-icon').textContent = browser.ZenWorkspaces.getWorkspaceIcon(workspace); + childs.querySelector('.zen-workspace-order-up').addEventListener('click', (async (event) => { + event.stopPropagation(); + event.preventDefault(); + await browser.ZenWorkspaces.moveWorkspaceUp(event, workspace.uuid); + }).bind(browser.ZenWorkspaces)); + childs.querySelector('.zen-workspace-order-down').addEventListener('click', (async (event) => { + event.stopPropagation(); + event.preventDefault(); + await browser.ZenWorkspaces.moveWorkspaceDown(event, workspace.uuid); + }).bind(browser.ZenWorkspaces)); childs.querySelector('.zen-workspace-name').textContent = workspace.name; if (containerGroup) { childs.querySelector('.zen-workspace-container').textContent = ContextualIdentityService.getUserContextLabel( @@ -373,24 +391,16 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { }; browser.ZenWorkspaces._workspaceCache = null; let workspaces = await browser.ZenWorkspaces._workspaces(); - let activeWorkspace = await browser.ZenWorkspaces.getActiveWorkspace(); - currentContainer.innerHTML = ''; workspaceList.innerHTML = ''; workspaceList.parentNode.style.display = 'flex'; - if (workspaces.workspaces.length - 1 <= 0) { + if (workspaces.workspaces.length <= 0) { workspaceList.innerHTML = 'No workspaces available'; workspaceList.setAttribute('empty', 'true'); } else { workspaceList.removeAttribute('empty'); } - if (activeWorkspace) { - let currentWorkspace = createWorkspaceElement(activeWorkspace); - currentContainer.appendChild(currentWorkspace); - } + for (let workspace of workspaces.workspaces) { - if (browser.ZenWorkspaces.isWorkspaceActive(workspace)) { - continue; - } let workspaceElement = createWorkspaceElement(workspace); workspaceList.appendChild(workspaceElement); } @@ -400,6 +410,38 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { }); } + isLastWorkspace(workspace) { + let workspaces = this._workspaceCache; + return workspaces.workspaces.indexOf(workspace) === workspaces.workspaces.length - 1; + } + + isFirstWorkspace(workspace) { + let workspaces = this._workspaceCache; + return workspaces.workspaces.indexOf(workspace) === 0; + } + + async moveWorkspaceUp(event, uuid) { + await ZenWorkspacesStorage.moveWorkspaceUp(uuid); + await this._propagateWorkspaceData(); + } + + async moveWorkspaceDown(event, uuid) { + await ZenWorkspacesStorage.moveWorkspaceDown(uuid); + await this._propagateWorkspaceData(); + } + + toggleReorderMode () { + const workspacesList = document.getElementById("PanelUI-zen-workspaces-list"); + const reorderModeButton = document.getElementById("PanelUI-zen-workspaces-reorder-mode"); + if(workspacesList.getAttribute('reorder-mode') === 'true' && reorderModeButton.getAttribute('active') === 'true') { + workspacesList.removeAttribute('reorder-mode'); + reorderModeButton.removeAttribute('active'); + } else { + workspacesList.setAttribute('reorder-mode', 'true'); + reorderModeButton.setAttribute('active', 'true'); + } + } + async openWorkspacesDialog(event) { if (!this.workspaceEnabled) { return; @@ -426,96 +468,116 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { await this._expandWorkspacesStrip(); } - async _expandWorkspacesStrip(browser = undefined) { - if (typeof browser === 'undefined' || typeof browser.ZenWorkspaces === 'undefined') { + async _expandWorkspacesStrip(browser = window) { + if (typeof browser.ZenWorkspaces === 'undefined') { browser = window; } - let workspaces = await browser.ZenWorkspaces._workspaces(); - let workspaceList = browser.document.getElementById('zen-workspaces-button'); - const newWorkspacesButton = browser.document.createXULElement('toolbarbutton'); - newWorkspacesButton.id = 'zen-workspaces-button'; - newWorkspacesButton.setAttribute('removable', 'true'); - newWorkspacesButton.setAttribute('showInPrivateBrowsing', 'false'); - newWorkspacesButton.setAttribute('tooltiptext', 'Workspaces'); + let button = browser.document.getElementById('zen-workspaces-button'); + + if (!button) { + button = browser.document.createXULElement('toolbarbutton'); + button.id = 'zen-workspaces-button'; + let navbar = browser.document.getElementById('nav-bar'); + navbar.appendChild(button); + } + + while (button.firstChild) { + button.firstChild.remove(); + } + + for (let attr of [...button.attributes]) { + if (attr.name !== 'id') { + button.removeAttribute(attr.name); + } + } + + button.className = ''; + + if (this._workspacesButtonClickListener) { + button.removeEventListener('click', this._workspacesButtonClickListener); + this._workspacesButtonClickListener = null; + } + if (this._workspaceButtonContextMenuListener) { + button.removeEventListener('contextmenu', this._workspaceButtonContextMenuListener); + this._workspaceButtonContextMenuListener = null; + } if (this.shouldShowIconStrip) { + button.setAttribute('removable', 'true'); + button.setAttribute('showInPrivateBrowsing', 'false'); + button.setAttribute('tooltiptext', 'Workspaces'); + + let workspaces = await this._workspaces(); + for (let workspace of workspaces.workspaces) { - let button = browser.document.createXULElement('toolbarbutton'); - button.className = 'subviewbutton'; - button.setAttribute('tooltiptext', workspace.name); - button.setAttribute('zen-workspace-id', workspace.uuid); + let workspaceButton = browser.document.createXULElement('toolbarbutton'); + workspaceButton.className = 'subviewbutton'; + workspaceButton.setAttribute('tooltiptext', workspace.name); + workspaceButton.setAttribute('zen-workspace-id', workspace.uuid); + if (this.isWorkspaceActive(workspace)) { - button.setAttribute('active', 'true'); + workspaceButton.setAttribute('active', 'true'); + } else { + workspaceButton.removeAttribute('active'); } if (workspace.default) { - button.setAttribute('default', 'true'); + workspaceButton.setAttribute('default', 'true'); + } else { + workspaceButton.removeAttribute('default'); } - button.onclick = (async (_, event) => { - // Make sure it's not a context menu event + + workspaceButton.addEventListener('click', async (event) => { if (event.button !== 0) { return; } await this.changeWorkspace(workspace); - }).bind(browser.ZenWorkspaces, workspace); + }); + let icon = browser.document.createXULElement('div'); icon.className = 'zen-workspace-icon'; icon.textContent = this.getWorkspaceIcon(workspace); - button.appendChild(icon); - newWorkspacesButton.appendChild(button); + workspaceButton.appendChild(icon); + button.appendChild(workspaceButton); } - // Listen for context menu events and open the all workspaces dialog - newWorkspacesButton.addEventListener( - 'contextmenu', - ((event) => { - event.preventDefault(); - browser.ZenWorkspaces.openWorkspacesDialog(event); - }).bind(this) - ); - } - workspaceList.after(newWorkspacesButton); - workspaceList.remove(); + this._workspaceButtonContextMenuListener = (event) => { + event.preventDefault(); + browser.ZenWorkspaces.openWorkspacesDialog(event); + }; + button.addEventListener('contextmenu', this._workspaceButtonContextMenuListener); + } else { + let activeWorkspace = await this.getActiveWorkspace(); + if (activeWorkspace) { + button.setAttribute('as-button', 'true'); + button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button'); - if (!this.shouldShowIconStrip) { - await this._updateWorkspacesButton(browser); + this._workspacesButtonClickListener = browser.ZenWorkspaces.openWorkspacesDialog.bind(browser.ZenWorkspaces); + button.addEventListener('click', this._workspacesButtonClickListener); + + const wrapper = browser.document.createXULElement('hbox'); + wrapper.className = 'zen-workspace-sidebar-wrapper'; + + const icon = browser.document.createXULElement('div'); + icon.className = 'zen-workspace-sidebar-icon'; + icon.textContent = this.getWorkspaceIcon(activeWorkspace); + + const name = browser.document.createXULElement('div'); + name.className = 'zen-workspace-sidebar-name'; + name.textContent = activeWorkspace.name; + + if (!this.workspaceHasIcon(activeWorkspace)) { + icon.setAttribute('no-icon', 'true'); + } + + wrapper.appendChild(icon); + wrapper.appendChild(name); + + button.appendChild(wrapper); + } } } - async _updateWorkspacesButton(browser = window) { - let button = browser.document.getElementById('zen-workspaces-button'); - if (!button) { - return; - } - let activeWorkspace = await this.getActiveWorkspace(); - if (activeWorkspace) { - button.setAttribute('as-button', 'true'); - button.classList.add('toolbarbutton-1', 'zen-sidebar-action-button'); - button.addEventListener('click', browser.ZenWorkspaces.openWorkspacesDialog.bind(browser.ZenWorkspaces)); - - const wrapper = browser.document.createXULElement('hbox'); - wrapper.className = 'zen-workspace-sidebar-wrapper'; - - const icon = browser.document.createElement('div'); - icon.className = 'zen-workspace-sidebar-icon'; - icon.textContent = this.getWorkspaceIcon(activeWorkspace); - - // use text content instead of innerHTML to avoid XSS - const name = browser.document.createElement('div'); - name.className = 'zen-workspace-sidebar-name'; - name.textContent = activeWorkspace.name; - - if (!this.workspaceHasIcon(activeWorkspace)) { - icon.setAttribute('no-icon', 'true'); - } - - wrapper.appendChild(icon); - wrapper.appendChild(name); - - button.innerHTML = ''; - button.appendChild(wrapper); - } - } // Workspaces management @@ -575,7 +637,6 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { let icon = document.querySelector('#PanelUI-zen-workspaces-icon-picker [selected]'); icon?.removeAttribute('selected'); await this.createAndSaveWorkspace(workspaceName, false, icon?.label); - await this._updateWorkspacesButton(); await this._propagateWorkspaceData(); this.closeWorkspacesSubView(); } diff --git a/src/ZenWorkspacesStorage.mjs b/src/ZenWorkspacesStorage.mjs index caf62b0..477d5a9 100644 --- a/src/ZenWorkspacesStorage.mjs +++ b/src/ZenWorkspacesStorage.mjs @@ -257,68 +257,6 @@ var ZenWorkspacesStorage = { }); }, - async updateWorkspaceOrder(uuid, newPosition, notifyObservers = true) { - const changedUUIDs = new Set([uuid]); - - await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.updateWorkspaceOrder', async (db) => { - await db.executeTransaction(async () => { - const now = Date.now(); - - // Get the positions of the workspaces before and after the new position - const neighborPositions = await db.execute(` - SELECT - (SELECT MAX("position") FROM zen_workspaces WHERE "position" < :newPosition) as before_position, - (SELECT MIN("position") FROM zen_workspaces WHERE "position" > :newPosition) as after_position - `, { newPosition }); - - let beforePosition = neighborPositions[0].getResultByName('before_position'); - let afterPosition = neighborPositions[0].getResultByName('after_position'); - - // Calculate the new position - if (beforePosition === null && afterPosition === null) { - // This is the only workspace - newPosition = 1000; - } else if (beforePosition === null) { - // This will be the first workspace - newPosition = Math.floor(afterPosition / 2); - } else if (afterPosition === null) { - // This will be the last workspace - newPosition = beforePosition + 1000; - } else { - // This workspace will be between two others - newPosition = Math.floor((beforePosition + afterPosition) / 2); - } - - // Update the workspace's position - await db.execute(` - UPDATE zen_workspaces - SET "position" = :newPosition - WHERE uuid = :uuid - `, { uuid, newPosition }); - - // Record the change - 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); - - // Check if reordering is necessary - if (this.shouldReorderWorkspaces(beforePosition, newPosition, afterPosition)) { - await this.reorderAllWorkspaces(db, changedUUIDs); - } - }); - }); - - if (notifyObservers) { - this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); - } - }, - shouldReorderWorkspaces(before, current, after) { const minGap = 1; // Minimum allowed gap between positions return (before !== null && current - before < minGap) || (after !== null && after - current < minGap); @@ -357,4 +295,164 @@ var ZenWorkspacesStorage = { `); return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0; }, + + async moveWorkspaceUp(uuid, notifyObservers = true) { + const changedUUIDs = new Set(); + + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.moveWorkspaceUp', async (db) => { + await db.executeTransaction(async () => { + const now = Date.now(); + + // Get the current workspace + const currentWorkspaceRows = await db.execute(` + SELECT uuid, "position" FROM zen_workspaces WHERE uuid = :uuid + `, { uuid }); + + if (currentWorkspaceRows.length === 0) { + // Workspace not found + return; + } + + const currentWorkspace = { + uuid: currentWorkspaceRows[0].getResultByName('uuid'), + position: currentWorkspaceRows[0].getResultByName('position') + }; + + // Find the workspace before this one + const previousWorkspaceRows = await db.execute(` + SELECT uuid, "position" FROM zen_workspaces + WHERE "position" < :currentPosition + ORDER BY "position" DESC + LIMIT 1 + `, { currentPosition: currentWorkspace.position }); + + if (previousWorkspaceRows.length === 0) { + // No previous workspace; already at the top + return; + } + + const previousWorkspace = { + uuid: previousWorkspaceRows[0].getResultByName('uuid'), + position: previousWorkspaceRows[0].getResultByName('position') + }; + + // Swap positions + await db.execute(` + UPDATE zen_workspaces + SET "position" = :newPosition + WHERE uuid = :uuid + `, { uuid: currentWorkspace.uuid, newPosition: previousWorkspace.position }); + + await db.execute(` + UPDATE zen_workspaces + SET "position" = :newPosition + WHERE uuid = :uuid + `, { uuid: previousWorkspace.uuid, newPosition: currentWorkspace.position }); + + // Record the changes + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid1, :timestamp), (:uuid2, :timestamp) + `, { + uuid1: currentWorkspace.uuid, + uuid2: previousWorkspace.uuid, + timestamp: Math.floor(now / 1000) + }); + + changedUUIDs.add(currentWorkspace.uuid); + changedUUIDs.add(previousWorkspace.uuid); + + await this.updateLastChangeTimestamp(db); + + // Check if reordering is necessary + if (this.shouldReorderWorkspaces(null, previousWorkspace.position, currentWorkspace.position)) { + await this.reorderAllWorkspaces(db, changedUUIDs); + } + }); + }); + + if (notifyObservers) { + this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); + } + }, + + async moveWorkspaceDown(uuid, notifyObservers = true) { + const changedUUIDs = new Set(); + + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.moveWorkspaceDown', async (db) => { + await db.executeTransaction(async () => { + const now = Date.now(); + + // Get the current workspace + const currentWorkspaceRows = await db.execute(` + SELECT uuid, "position" FROM zen_workspaces WHERE uuid = :uuid + `, { uuid }); + + if (currentWorkspaceRows.length === 0) { + // Workspace not found + return; + } + + const currentWorkspace = { + uuid: currentWorkspaceRows[0].getResultByName('uuid'), + position: currentWorkspaceRows[0].getResultByName('position') + }; + + // Find the workspace after this one + const nextWorkspaceRows = await db.execute(` + SELECT uuid, "position" FROM zen_workspaces + WHERE "position" > :currentPosition + ORDER BY "position" ASC + LIMIT 1 + `, { currentPosition: currentWorkspace.position }); + + if (nextWorkspaceRows.length === 0) { + // No next workspace; already at the bottom + return; + } + + const nextWorkspace = { + uuid: nextWorkspaceRows[0].getResultByName('uuid'), + position: nextWorkspaceRows[0].getResultByName('position') + }; + + // Swap positions + await db.execute(` + UPDATE zen_workspaces + SET "position" = :newPosition + WHERE uuid = :uuid + `, { uuid: currentWorkspace.uuid, newPosition: nextWorkspace.position }); + + await db.execute(` + UPDATE zen_workspaces + SET "position" = :newPosition + WHERE uuid = :uuid + `, { uuid: nextWorkspace.uuid, newPosition: currentWorkspace.position }); + + // Record the changes + await db.execute(` + INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) + VALUES (:uuid1, :timestamp), (:uuid2, :timestamp) + `, { + uuid1: currentWorkspace.uuid, + uuid2: nextWorkspace.uuid, + timestamp: Math.floor(now / 1000) + }); + + changedUUIDs.add(currentWorkspace.uuid); + changedUUIDs.add(nextWorkspace.uuid); + + await this.updateLastChangeTimestamp(db); + + // Check if reordering is necessary + if (this.shouldReorderWorkspaces(currentWorkspace.position, nextWorkspace.position, null)) { + await this.reorderAllWorkspaces(db, changedUUIDs); + } + }); + }); + + if (notifyObservers) { + this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); + } + }, }; From c8deb3610069a9f13222cfc840001f051bb5204c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristijan=20Ribari=C4=87?= Date: Wed, 16 Oct 2024 10:08:45 +0200 Subject: [PATCH 2/2] Re-implemented workspace reordering to be based on dragging. --- src/ZenWorkspaces.mjs | 132 ++++++++++++++++++++--------- src/ZenWorkspacesStorage.mjs | 156 ++++------------------------------- 2 files changed, 109 insertions(+), 179 deletions(-) diff --git a/src/ZenWorkspaces.mjs b/src/ZenWorkspaces.mjs index 728547f..53f458f 100644 --- a/src/ZenWorkspaces.mjs +++ b/src/ZenWorkspaces.mjs @@ -4,6 +4,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { */ _lastSelectedWorkspaceTabs = {}; _inChangingWorkspace = false; + draggedElement = null; async init() { if (!this.shouldHaveWorkspaces) { @@ -324,6 +325,66 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { element.classList.add('identity-color-' + containerGroup.color); element.setAttribute('data-usercontextid', containerGroup.userContextId); } + if (this.isReorderModeOn(browser)) { + element.setAttribute('draggable', 'true'); + } + + element.addEventListener('dragstart', function (event) { + if (this.isReorderModeOn(browser)) { + this.draggedElement = element; + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/plain', element.getAttribute('zen-workspace-id')); + element.classList.add('dragging'); + } else { + event.preventDefault(); + } + }.bind(browser.ZenWorkspaces)); + + element.addEventListener('dragover', function (event) { + if (this.isReorderModeOn(browser) && this.draggedElement) { + event.preventDefault(); + event.dataTransfer.dropEffect = 'move'; + } + }.bind(browser.ZenWorkspaces)); + + element.addEventListener('dragenter', function (event) { + if (this.isReorderModeOn(browser) && this.draggedElement) { + element.classList.add('dragover'); + } + }.bind(browser.ZenWorkspaces)); + + element.addEventListener('dragleave', function (event) { + element.classList.remove('dragover'); + }.bind(browser.ZenWorkspaces)); + + element.addEventListener('drop', async function (event) { + event.preventDefault(); + element.classList.remove('dragover'); + if (this.isReorderModeOn(browser)) { + const draggedWorkspaceId = event.dataTransfer.getData('text/plain'); + const targetWorkspaceId = element.getAttribute('zen-workspace-id'); + if (draggedWorkspaceId !== targetWorkspaceId) { + await this.moveWorkspace(draggedWorkspaceId, targetWorkspaceId); + await this._propagateWorkspaceData(); + } + if (this.draggedElement) { + this.draggedElement.classList.remove('dragging'); + this.draggedElement = null; + } + } + }.bind(browser.ZenWorkspaces)); + + element.addEventListener('dragend', function (event) { + if (this.draggedElement) { + this.draggedElement.classList.remove('dragging'); + this.draggedElement = null; + } + const workspaceElements = browser.document.querySelectorAll('.zen-workspace-button'); + for (const elem of workspaceElements) { + elem.classList.remove('dragover'); + } + }.bind(browser.ZenWorkspaces)); + let childs = browser.MozXULElement.parseXULToFragment(`
@@ -333,15 +394,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
-
- - - - - - -
- + @@ -349,16 +402,6 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { // use text content instead of innerHTML to avoid XSS childs.querySelector('.zen-workspace-icon').textContent = browser.ZenWorkspaces.getWorkspaceIcon(workspace); - childs.querySelector('.zen-workspace-order-up').addEventListener('click', (async (event) => { - event.stopPropagation(); - event.preventDefault(); - await browser.ZenWorkspaces.moveWorkspaceUp(event, workspace.uuid); - }).bind(browser.ZenWorkspaces)); - childs.querySelector('.zen-workspace-order-down').addEventListener('click', (async (event) => { - event.stopPropagation(); - event.preventDefault(); - await browser.ZenWorkspaces.moveWorkspaceDown(event, workspace.uuid); - }).bind(browser.ZenWorkspaces)); childs.querySelector('.zen-workspace-name').textContent = workspace.name; if (containerGroup) { childs.querySelector('.zen-workspace-container').textContent = ContextualIdentityService.getUserContextLabel( @@ -376,6 +419,9 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { }).bind(browser.ZenWorkspaces)); element.appendChild(childs); element.onclick = (async () => { + if (this.isReorderModeOn(browser)) { + return; // Return early if reorder mode is on + } if (event.target.closest('.zen-workspace-actions')) { return; // Ignore clicks on the actions button } @@ -410,38 +456,46 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature { }); } - isLastWorkspace(workspace) { - let workspaces = this._workspaceCache; - return workspaces.workspaces.indexOf(workspace) === workspaces.workspaces.length - 1; + isReorderModeOn(browser) { + return browser.document.getElementById('PanelUI-zen-workspaces-list').getAttribute('reorder-mode') === 'true'; } - isFirstWorkspace(workspace) { - let workspaces = this._workspaceCache; - return workspaces.workspaces.indexOf(workspace) === 0; - } - - async moveWorkspaceUp(event, uuid) { - await ZenWorkspacesStorage.moveWorkspaceUp(uuid); - await this._propagateWorkspaceData(); - } - - async moveWorkspaceDown(event, uuid) { - await ZenWorkspacesStorage.moveWorkspaceDown(uuid); - await this._propagateWorkspaceData(); - } - - toggleReorderMode () { + toggleReorderMode() { const workspacesList = document.getElementById("PanelUI-zen-workspaces-list"); const reorderModeButton = document.getElementById("PanelUI-zen-workspaces-reorder-mode"); - if(workspacesList.getAttribute('reorder-mode') === 'true' && reorderModeButton.getAttribute('active') === 'true') { + const isActive = workspacesList.getAttribute('reorder-mode') === 'true'; + if (isActive) { workspacesList.removeAttribute('reorder-mode'); reorderModeButton.removeAttribute('active'); } else { workspacesList.setAttribute('reorder-mode', 'true'); reorderModeButton.setAttribute('active', 'true'); } + + // Update draggable attribute + const workspaceElements = document.querySelectorAll('.zen-workspace-button'); + workspaceElements.forEach(elem => { + if (isActive) { + elem.removeAttribute('draggable'); + } else { + elem.setAttribute('draggable', 'true'); + } + }); } + + async moveWorkspace(draggedWorkspaceId, targetWorkspaceId) { + const workspaces = (await this._workspaces()).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); + } + + + async openWorkspacesDialog(event) { if (!this.workspaceEnabled) { return; diff --git a/src/ZenWorkspacesStorage.mjs b/src/ZenWorkspacesStorage.mjs index 477d5a9..27b6416 100644 --- a/src/ZenWorkspacesStorage.mjs +++ b/src/ZenWorkspacesStorage.mjs @@ -296,163 +296,39 @@ var ZenWorkspacesStorage = { return result.length ? parseInt(result[0].getResultByName('value'), 10) : 0; }, - async moveWorkspaceUp(uuid, notifyObservers = true) { + async updateWorkspacePositions(workspaces) { const changedUUIDs = new Set(); - await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.moveWorkspaceUp', async (db) => { + await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.updateWorkspacePositions', async (db) => { await db.executeTransaction(async () => { const now = Date.now(); - // Get the current workspace - const currentWorkspaceRows = await db.execute(` - SELECT uuid, "position" FROM zen_workspaces WHERE uuid = :uuid - `, { uuid }); + for (let i = 0; i < workspaces.length; i++) { + const workspace = workspaces[i]; + const newPosition = (i + 1) * 1000; - if (currentWorkspaceRows.length === 0) { - // Workspace not found - return; - } - - const currentWorkspace = { - uuid: currentWorkspaceRows[0].getResultByName('uuid'), - position: currentWorkspaceRows[0].getResultByName('position') - }; - - // Find the workspace before this one - const previousWorkspaceRows = await db.execute(` - SELECT uuid, "position" FROM zen_workspaces - WHERE "position" < :currentPosition - ORDER BY "position" DESC - LIMIT 1 - `, { currentPosition: currentWorkspace.position }); - - if (previousWorkspaceRows.length === 0) { - // No previous workspace; already at the top - return; - } - - const previousWorkspace = { - uuid: previousWorkspaceRows[0].getResultByName('uuid'), - position: previousWorkspaceRows[0].getResultByName('position') - }; - - // Swap positions - await db.execute(` + await db.execute(` UPDATE zen_workspaces SET "position" = :newPosition WHERE uuid = :uuid - `, { uuid: currentWorkspace.uuid, newPosition: previousWorkspace.position }); + `, { newPosition, uuid: workspace.uuid }); - await db.execute(` - UPDATE zen_workspaces - SET "position" = :newPosition - WHERE uuid = :uuid - `, { uuid: previousWorkspace.uuid, newPosition: currentWorkspace.position }); + changedUUIDs.add(workspace.uuid); - // Record the changes - await db.execute(` + // Record the change + await db.execute(` INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) - VALUES (:uuid1, :timestamp), (:uuid2, :timestamp) + VALUES (:uuid, :timestamp) `, { - uuid1: currentWorkspace.uuid, - uuid2: previousWorkspace.uuid, - timestamp: Math.floor(now / 1000) - }); - - changedUUIDs.add(currentWorkspace.uuid); - changedUUIDs.add(previousWorkspace.uuid); + uuid: workspace.uuid, + timestamp: Math.floor(now / 1000) + }); + } await this.updateLastChangeTimestamp(db); - - // Check if reordering is necessary - if (this.shouldReorderWorkspaces(null, previousWorkspace.position, currentWorkspace.position)) { - await this.reorderAllWorkspaces(db, changedUUIDs); - } }); }); - if (notifyObservers) { - this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); - } - }, - - async moveWorkspaceDown(uuid, notifyObservers = true) { - const changedUUIDs = new Set(); - - await PlacesUtils.withConnectionWrapper('ZenWorkspacesStorage.moveWorkspaceDown', async (db) => { - await db.executeTransaction(async () => { - const now = Date.now(); - - // Get the current workspace - const currentWorkspaceRows = await db.execute(` - SELECT uuid, "position" FROM zen_workspaces WHERE uuid = :uuid - `, { uuid }); - - if (currentWorkspaceRows.length === 0) { - // Workspace not found - return; - } - - const currentWorkspace = { - uuid: currentWorkspaceRows[0].getResultByName('uuid'), - position: currentWorkspaceRows[0].getResultByName('position') - }; - - // Find the workspace after this one - const nextWorkspaceRows = await db.execute(` - SELECT uuid, "position" FROM zen_workspaces - WHERE "position" > :currentPosition - ORDER BY "position" ASC - LIMIT 1 - `, { currentPosition: currentWorkspace.position }); - - if (nextWorkspaceRows.length === 0) { - // No next workspace; already at the bottom - return; - } - - const nextWorkspace = { - uuid: nextWorkspaceRows[0].getResultByName('uuid'), - position: nextWorkspaceRows[0].getResultByName('position') - }; - - // Swap positions - await db.execute(` - UPDATE zen_workspaces - SET "position" = :newPosition - WHERE uuid = :uuid - `, { uuid: currentWorkspace.uuid, newPosition: nextWorkspace.position }); - - await db.execute(` - UPDATE zen_workspaces - SET "position" = :newPosition - WHERE uuid = :uuid - `, { uuid: nextWorkspace.uuid, newPosition: currentWorkspace.position }); - - // Record the changes - await db.execute(` - INSERT OR REPLACE INTO zen_workspaces_changes (uuid, timestamp) - VALUES (:uuid1, :timestamp), (:uuid2, :timestamp) - `, { - uuid1: currentWorkspace.uuid, - uuid2: nextWorkspace.uuid, - timestamp: Math.floor(now / 1000) - }); - - changedUUIDs.add(currentWorkspace.uuid); - changedUUIDs.add(nextWorkspace.uuid); - - await this.updateLastChangeTimestamp(db); - - // Check if reordering is necessary - if (this.shouldReorderWorkspaces(currentWorkspace.position, nextWorkspace.position, null)) { - await this.reorderAllWorkspaces(db, changedUUIDs); - } - }); - }); - - if (notifyObservers) { - this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); - } + this._notifyWorkspacesChanged("zen-workspace-updated", Array.from(changedUUIDs)); }, };