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(``);
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(
+ `
+
+
`,
+ );
+
+ 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;
+ }
+
}
}
}