diff --git a/sections/workspace.js b/sections/workspace.js index 03c6b5e..b9e81ce 100644 --- a/sections/workspace.js +++ b/sections/workspace.js @@ -5,10 +5,11 @@ function getGradientCSS(theme) { return "transparent"; const angle = Math.round(theme.rotation || 0); + const opacity = theme.opacity ?? 1; const stops = theme.gradientColors .map(({ c }) => { const [r, g, b] = c; - return `rgb(${r}, ${g}, ${b})`; + return `rgba(${r}, ${g}, ${b}, ${opacity})`; }) .join(", "); @@ -22,7 +23,63 @@ export const workspacesSection = { `, init: function() { - const container = parseElement(`
`); + const container = parseElement( + `
`, + ); + const innerContainer = container.querySelector( + "#haven-workspace-inner-container", + ); + // const outerContainer = container.querySelector('#haven-workspace-outer-container') + + function getDragAfterElement(container, x) { + const draggableElements = [ + ...container.querySelectorAll(".haven-workspace:not(.dragging)"), + ]; + + return draggableElements.reduce( + (closest, child) => { + const box = child.getBoundingClientRect(); + const offset = x - box.left - box.width / 2; + if (offset < 0 && offset > closest.offset) { + return { offset: offset, element: child }; + } else { + return closest; + } + }, + { offset: Number.NEGATIVE_INFINITY }, + ).element; + } + + innerContainer.addEventListener("dragover", (e) => { + e.preventDefault(); + const afterElement = getDragAfterElement(innerContainer, e.clientX); + const dragging = innerContainer.querySelector(".dragging"); + if (dragging) { + if (afterElement == null) { + innerContainer.appendChild(dragging); + } else { + innerContainer.insertBefore(dragging, afterElement); + } + } + }); + + innerContainer.addEventListener("drop", async (e) => { + e.preventDefault(); + const draggedElement = innerContainer.querySelector(".dragging"); + if (!draggedElement) return; + + const draggedUuid = draggedElement.dataset.uuid; + const workspaceElements = [ + ...innerContainer.querySelectorAll(".haven-workspace"), + ]; + const newIndex = workspaceElements.findIndex( + (el) => el === draggedElement, + ); + + if (newIndex !== -1) { + await gZenWorkspaces.reorderWorkspace(draggedUuid, newIndex); + } + }); const addWorkspaceButton = parseElement(`
@@ -30,114 +87,268 @@ export const workspacesSection = {
`); addWorkspaceButton.addEventListener("click", () => { try { - if (typeof ZenWorkspaces?.openSaveDialog === "function") { - console.log("[ZenHaven] Attempting to open workspace save dialog..."); - ZenWorkspaces.openSaveDialog(); + if (typeof gZenWorkspaces?.openWorkspaceCreation === "function") { + console.log( + "[ZenHaven] Attempting to open workspace creation dialog...", + ); + + // close haven + window.haven.destroyUI(); + + gZenWorkspaces.openWorkspaceCreation(); } else { - throw new Error("ZenWorkspaces.openSaveDialog is not available"); + throw new Error( + "gZenWorkspaces.openWorkspaceCreation is not available", + ); } } catch (error) { console.error("[ZenHaven] Error opening workspace dialog:", error); } }); - const workspacesButton = document.getElementById("zen-workspaces-button"); - if (workspacesButton) { - console.log("[ZenHaven] Found workspace button:", workspacesButton); - const workspaceElements = Array.from(workspacesButton.children); - console.log("[ZenHaven] Workspace elements:", workspaceElements); - - workspaceElements.forEach((workspace) => { - // Create base workspace div - const workspaceDiv = parseElement( - `
`, - ); - const uuid = workspace.getAttribute("zen-workspace-id"); - - ZenWorkspacesStorage.getWorkspaces().then((allWorkspaces) => { - const data = allWorkspaces.find((ws) => ws.uuid === uuid); - if ( - data?.theme?.type === "gradient" && - data.theme.gradientColors?.length - ) { - workspaceDiv.style.background = getGradientCSS(data.theme); - workspaceDiv.style.opacity = data.theme.opacity ?? 1; + if (typeof gZenWorkspaces === "undefined") { + console.error("[ZenHaven] gZenWorkspaces is not available."); + innerContainer.appendChild(addWorkspaceButton); + return container; + } + + gZenWorkspaces + ._workspaces() + .then(({ workspaces: allWorkspaces }) => { + const allTabs = gZenWorkspaces.allStoredTabs || []; + + allWorkspaces.forEach((workspace) => { + const { uuid, theme, name, icon } = workspace; + const workspaceDiv = parseElement( + `
+
+ + + ${icon} + ${name} +
+
`, + ); + + workspaceDiv.dataset.uuid = uuid; + + const dragHandle = workspaceDiv.querySelector( + ".workspace-drag-handle", + ); + + const iconEl = workspaceDiv.querySelector(".workspace-icon"); + iconEl.addEventListener("click", () => { + gZenEmojiPicker + .open(iconEl) + .then(async (newIcon) => { + console.log("Selected emoji:", newIcon); + iconEl.innerText = newIcon; + const currentWorkspace = + gZenWorkspaces.getWorkspaceFromId(uuid); + if (currentWorkspace && newIcon && newIcon !== icon) { + currentWorkspace.icon = newIcon; + await gZenWorkspaces.saveWorkspace(currentWorkspace); + } else { + workspaceNameEl.textContent = originalName; + } + }) + .catch((e) => console.error(e)); + }); + + dragHandle.addEventListener("dragstart", (e) => { + const workspaceElement = e.target.closest(".haven-workspace"); + workspaceElement.classList.add("dragging"); + e.dataTransfer.effectAllowed = "move"; + }); + + dragHandle.addEventListener("dragend", (e) => { + const workspaceElement = e.target.closest(".haven-workspace"); + workspaceElement.classList.remove("dragging"); + }); + + if (theme?.type === "gradient" && theme.gradientColors?.length) { + workspaceDiv.style.background = getGradientCSS(theme); } else { workspaceDiv.style.background = "var(--zen-colors-border)"; - workspaceDiv.style.opacity = 1; } - }); - // Create content container - const contentDiv = parseElement( - `
`, - ); - - // Find workspace sections using the workspace's own ID - const sections = document.querySelectorAll( - `.zen-workspace-tabs-section[zen-workspace-id="${workspace.getAttribute( - "zen-workspace-id", - )}"]`, - ); - - sections.forEach((section) => { - const root = section.shadowRoot || section; - const sectionWrapper = parseElement( - `
`, + const header = workspaceDiv.querySelector(".haven-workspace-header"); + const popupOpenButton = parseElement( + ` + `, + "xul", ); - - // Copy computed styles from original section - const computedStyle = window.getComputedStyle(section); - sectionWrapper.style.cssText = Array.from(computedStyle).reduce( - (str, property) => { - return `${str}${property}:${computedStyle.getPropertyValue( - property, - )};`; - }, - "", + const menuPopup = parseElement( + ` + + + + + + + `, + "xul", ); + header.appendChild(popupOpenButton); + container.appendChild(menuPopup); - // Clone tab groups with their styles - const tabGroups = root.querySelectorAll("tab-group"); - tabGroups.forEach((group) => { - const groupClone = group.cloneNode(true); - const groupStyle = window.getComputedStyle(group); - groupClone.style.cssText = Array.from(groupStyle).reduce( - (str, property) => { - return `${str}${property}:${groupStyle.getPropertyValue( - property, - )};`; - }, - "", - ); - sectionWrapper.appendChild(groupClone); + popupOpenButton.addEventListener("click", (event) => { + event.stopPropagation(); + menuPopup.openPopup(popupOpenButton, "after_start"); }); - // Clone remaining children with their styles - Array.from(root.children).forEach((child) => { - if (!child.classList.contains("zen-tab-group")) { - const clone = child.cloneNode(true); - const childStyle = window.getComputedStyle(child); - clone.style.cssText = Array.from(childStyle).reduce( - (str, property) => { - return `${str}${property}:${childStyle.getPropertyValue( - property, - )};`; - }, - "", - ); - sectionWrapper.appendChild(clone); - } + const workspaceNameEl = workspaceDiv.querySelector(".workspace-name"); + + const enableRename = () => { + const originalName = workspace.name; + workspaceNameEl.contentEditable = true; + workspaceNameEl.style.cursor = "text"; + workspaceNameEl.focus(); + + const selection = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(workspaceNameEl); + selection.removeAllRanges(); + selection.addRange(range); + + const cleanup = () => { + workspaceNameEl.removeEventListener("blur", handleBlur); + workspaceNameEl.removeEventListener("keydown", handleKeyDown); + workspaceNameEl.contentEditable = false; + workspaceNameEl.style.cursor = ""; + }; + + const handleBlur = async () => { + cleanup(); + const newName = workspaceNameEl.textContent.trim(); + const currentWorkspace = gZenWorkspaces.getWorkspaceFromId(uuid); + if (currentWorkspace && newName && newName !== originalName) { + currentWorkspace.name = newName; + await gZenWorkspaces.saveWorkspace(currentWorkspace); + workspace.name = newName; + } else { + workspaceNameEl.textContent = originalName; + } + }; + + const handleKeyDown = (e) => { + if (e.key === "Enter") { + e.preventDefault(); + workspaceNameEl.blur(); + } else if (e.key === "Escape") { + e.preventDefault(); + workspaceNameEl.textContent = originalName; + workspaceNameEl.blur(); + } + }; + + workspaceNameEl.addEventListener("blur", handleBlur); + workspaceNameEl.addEventListener("keydown", handleKeyDown); + }; + + workspaceNameEl.addEventListener("dblclick", enableRename); + + menuPopup + .querySelector(".rename") + .addEventListener("click", enableRename); + + menuPopup + .querySelector(".switch") + .addEventListener("click", async () => { + await gZenWorkspaces.changeWorkspaceWithID(uuid); + + // close haven + window.haven.destroyUI(); + }); + + menuPopup + .querySelector(".delete-workspace") + .addEventListener("click", async () => { + if ( + confirm(`Are you sure you want to delete workspace "${name}"?`) + ) { + await gZenWorkspaces.removeWorkspace(uuid); + workspaceDiv.remove(); + } + }); + + const contentDiv = parseElement( + `
`, + ); + const pinnedTabsContainer = parseElement( + `
`, + ); + const regularTabsContainer = parseElement( + `
`, + ); + + allTabs + .filter( + (tabEl) => + tabEl && + tabEl.getAttribute("zen-workspace-id") === uuid && + !tabEl.hasAttribute("zen-essential"), + ) + .forEach((tabEl) => { + const clonedTab = tabEl.cloneNode(true); + let url; + try { + const browser = gBrowser.getBrowserForTab(tabEl); + url = browser.currentURI.spec; + // save url in tab + clonedTab.setAttribute("data-url", url); + } catch (e) { + console.error("Could not get tab URL", e); + } + if (clonedTab.hasAttribute("pinned")) { + pinnedTabsContainer.appendChild(clonedTab); + } else { + regularTabsContainer.appendChild(clonedTab); + } + }); + + if (pinnedTabsContainer.hasChildNodes()) { + contentDiv.appendChild(pinnedTabsContainer); + } + if (regularTabsContainer.hasChildNodes()) { + contentDiv.appendChild(regularTabsContainer); + } + + workspaceDiv.appendChild(contentDiv); + const closeButtons = + workspaceDiv.querySelectorAll(".tab-close-button"); + closeButtons.forEach((btn) => { + btn.addEventListener("click", (e) => { + e.stopPropagation(); + e.preventDefault(); + const tab = e.target.closest("tab.tabbrowser-tab"); + // get saved url + const url = tab.getAttribute("data-url"); + if (url) { + try { + navigator.clipboard.writeText(url); + gZenUIManager.showToast("zen-copy-current-url-confirmation"); + } catch { + (err) => { + console.error("Failed to copy URL:", err); + }; + } + } + }); }); - contentDiv.appendChild(sectionWrapper); + innerContainer.appendChild(workspaceDiv); }); - - workspaceDiv.appendChild(contentDiv); - container.appendChild(workspaceDiv); + }) + .catch((error) => { + console.error("[ZenHaven] Error building workspaces section:", error); }); - } - container.appendChild(addWorkspaceButton); + innerContainer.appendChild(addWorkspaceButton); return container; }, }; diff --git a/style.css b/style.css index badb828..9ba7334 100644 --- a/style.css +++ b/style.css @@ -104,7 +104,8 @@ box-shadow: inset 10px 0 20px rgba(0, 0, 0, 0.5), /* left */ inset 0 0 20px rgba(0, 0, 0, 0.5), /* bottom */ inset 0 0 20px rgba(0, 0, 0, 0.5); /* top */ - + max-height:94vh !important; + &[haven-workspaces] { div { display: flex !important; @@ -114,6 +115,15 @@ } } + #haven-workspace-outer-container{ + width: 100% !important; + overflow-x: auto !important; + position: relative !important; + } + #haven-workspace-inner-container{ + gap:30px !important; + } + .haven-workspace { height: 85% !important; min-width: 225px; @@ -134,6 +144,7 @@ } .haven-workspace-header { + height: fit-content !important; margin: 2px !important; .workspace-icon { @@ -150,21 +161,26 @@ margin: 0 !important; padding: 10px !important; display: flex !important; - align-items: center !important; - height: fit-content !important; width: 100% !important; - overflow: hidden !important; - align-items: flex-start; - - .haven-workspace-section { + align-items: flex-start !important; + .haven-workspace-pinned-tabs,.haven-workspace-regular-tabs{ display: flex !important; - position: relative !important; - min-height: 70px !important; - margin: 0 !important; - padding-inline: 2px !important; - transform: translateX(0) !important; + height: fit-content !important; + flex-direction: column !important; + } + .haven-workspace-pinned-tabs{ + border-bottom:2px solid rgba(0,0,0,0.3) !important; } } + + .workspace-drag-handle{ + width: 50% !important; + height: 20px !important; + background: red !important; + position: absolute !important; + bottom:0 !important; + } + } } }