feat: Added new workspace creation form, b=no-bug, c=tabs, common, compact-mode, workspaces

This commit is contained in:
Mr. M
2025-06-10 20:49:14 +02:00
parent e1974d9f81
commit 847aef5b02
22 changed files with 574 additions and 45 deletions

View File

@@ -69,15 +69,20 @@
initContextMenu() {
const menu = window.MozXULElement.parseXULToFragment(`
<menuitem id="zenToolbarThemePicker"
data-lazy-l10n-id="zen-workspaces-change-gradient"
data-lazy-l10n-id="zen-workspaces-change-theme"
command="cmd_zenOpenZenThemePicker"/>
`);
document.getElementById('toolbar-context-customize').before(menu);
}
openThemePicker(event) {
PanelMultiView.openPopup(this.panel, this.toolbox, {
position: 'topright topleft',
const target = event.explicitOriginalTarget?.classList?.contains(
'zen-workspace-creation-edit-theme-button'
)
? event.explicitOriginalTarget
: this.toolbox;
PanelMultiView.openPopup(this.panel, target, {
position: 'bottomright topright',
triggerEvent: event,
});
}

View File

@@ -5,7 +5,7 @@
class ZenWorkspace extends MozXULElement {
static get markup() {
return `
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1">
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1" context="zenWorkspaceMoreActions">
<hbox class="zen-current-workspace-indicator-icon"></hbox>
<hbox class="zen-current-workspace-indicator-name" flex="1"></hbox>
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions"></toolbarbutton>
@@ -199,16 +199,16 @@
onActionsCommand(event) {
event.stopPropagation();
const popup = document.getElementById('zenWorkspaceMoreActions');
event.target.setAttribute('open', 'true');
const target = event.target;
target.setAttribute('open', 'true');
this.indicator.setAttribute('open', 'true');
popup.addEventListener(
'popuphidden',
() => {
event.target.removeAttribute('open');
this.indicator.removeAttribute('open');
},
{ once: true }
);
const handlePopupHidden = (event) => {
if (event.target !== popup) return;
target.removeAttribute('open');
this.indicator.removeAttribute('open');
popup.removeEventListener('popuphidden', handlePopupHidden);
};
popup.addEventListener('popuphidden', handlePopupHidden);
popup.openPopup(event.target, 'after_start');
}
}

View File

@@ -0,0 +1,246 @@
{
class ZenWorkspaceCreation extends MozXULElement {
#wasInCollapsedMode = false;
promiseInitialized = new Promise((resolve) => {
this.resolveInitialized = resolve;
});
#hiddenElements = [];
static get elementsToDisable() {
return [
'cmd_zenOpenWorkspacePanel',
'cmd_zenOpenWorkspaceCreation',
'cmd_zenToggleSidebar',
'cmd_newNavigatorTab',
'cmd_newNavigatorTabNoEvent',
];
}
static get markup() {
return `
<vbox class="zen-workspace-creation" flex="1">
<form>
<vbox>
<html:h1 data-l10n-id="zen-workspace-creation-title" class="zen-workspace-creation-title" />
<label data-l10n-id="zen-workspace-creation-label" class="zen-workspace-creation-label" />
</vbox>
<vbox class="zen-workspace-creation-form">
<hbox class="zen-workspace-creation-name-wrapper">
<toolbarbutton class="zen-workspace-creation-icon-label" />
<html:input
class="zen-workspace-creation-name"
type="text"
data-l10n-id="zen-workspace-creation-name" />
</hbox>
<hbox class="zen-workspace-creation-profile-wrapper">
<label class="zen-workspace-creation-profile-label" data-l10n-id="zen-workspace-creation-profile" />
<button class="zen-workspace-creation-profile" />
</hbox>
<button
class="zen-workspace-creation-edit-theme-button"
data-l10n-id="zen-workspaces-change-theme"
command="cmd_zenOpenZenThemePicker" />
<menupopup class="zen-workspace-creation-profiles-popup" />
</vbox>
<vbox class="zen-workspace-creation-buttons">
<button class="zen-workspace-creation-create-button footer-button primary"
data-l10n-id="zen-panel-ui-workspaces-create" disabled="true" />
<button class="zen-workspace-creation-cancel-button footer-button"
data-l10n-id="zen-general-cancel-label" />
</vbox>
</form>
</vbox>
`;
}
get workspaceId() {
return this.getAttribute('workspace-id');
}
connectedCallback() {
if (this.delayConnectedCallback()) {
// If we are not ready yet, or if we have already connected, we
// don't need to do anything.
return;
}
document.documentElement.setAttribute('zen-creating-workspace', 'true');
this.appendChild(this.constructor.fragment);
this.initializeAttributeInheritance();
this.#wasInCollapsedMode =
document.documentElement.getAttribute('zen-sidebar-expanded') !== 'true';
gNavToolbox.setAttribute('zen-sidebar-expanded', 'true');
document.documentElement.setAttribute('zen-sidebar-expanded', 'true');
window.docShell.treeOwner
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIAppWindow)
.rollupAllPopups();
this.handleZenWorkspacesChangeBind = this.handleZenWorkspacesChange.bind(this);
for (const element of this.parentElement.children) {
if (element !== this) {
element.hidden = true;
this.#hiddenElements.push(element);
}
}
gBrowser.tabContainer.style.visibility = 'collapse';
if (gZenVerticalTabsManager._hasSetSingleToolbar) {
document.getElementById('nav-bar').style.visibility = 'collapse';
}
for (const element of ZenWorkspaceCreation.elementsToDisable) {
const el = document.getElementById(element);
if (el) {
el.setAttribute('disabled', 'true');
}
}
this.inputName = this.querySelector('.zen-workspace-creation-name');
this.inputIcon = this.querySelector('.zen-workspace-creation-icon-label');
this.inputProfile = this.querySelector('.zen-workspace-creation-profile');
this.createButton = this.querySelector('.zen-workspace-creation-create-button');
this.cancelButton = this.querySelector('.zen-workspace-creation-cancel-button');
this.createButton.addEventListener('command', this.onCreateButtonCommand.bind(this));
this.cancelButton.addEventListener('command', this.onCancelButtonCommand.bind(this));
this.inputName.addEventListener('input', () => {
this.createButton.disabled = !this.inputName.value.trim();
});
this.inputIcon.addEventListener('command', this.onIconCommand.bind(this));
this.profilesPopup = this.querySelector('.zen-workspace-creation-profiles-popup');
if (gZenWorkspaces.shouldShowContainers) {
this.inputProfile.addEventListener('command', this.onProfileCommand.bind(this));
this.profilesPopup.addEventListener('popupshown', this.onProfilePopupShown.bind(this));
this.profilesPopup.addEventListener('command', this.onProfilePopupCommand.bind(this));
this.currentProfile = {
id: 0,
name: 'Default',
};
} else {
this.inputProfile.parentNode.hidden = true;
}
this.resolveInitialized();
}
async onCreateButtonCommand() {
const workspace = await gZenWorkspaces.getActiveWorkspace();
workspace.name = this.inputName.value.trim();
workspace.icon = this.inputIcon.label || undefined;
workspace.containerTabId = this.currentProfile;
await gZenWorkspaces.saveWorkspace(workspace);
this.#cleanup();
await gZenWorkspaces._organizeWorkspaceStripLocations(workspace, true);
await gZenWorkspaces.updateTabsContainers();
this.tabContainer._invalidateCachedTabs();
if (gZenVerticalTabsManager._canReplaceNewTab) {
BrowserCommands.openTab();
}
}
async onCancelButtonCommand() {
const workspaces = await gZenWorkspaces._workspaces();
await gZenWorkspaces.changeWorkspace(workspaces.workspaces[workspaces.workspaces.length - 2]);
}
onIconCommand(event) {
gZenEmojiPicker
.open(event.target)
.then(async (emoji) => {
this.inputIcon.label = emoji || '';
})
.catch((error) => {
console.warn('Error changing workspace icon:', error);
});
}
set currentProfile(profile) {
this.inputProfile.label = profile.name;
this._profileId = profile.id;
}
get currentProfile() {
return this._profileId;
}
onProfileCommand(event) {
this.profilesPopup.openPopup(event.target, 'after_start');
}
onProfilePopupShown(event) {
return window.createUserContextMenu(event, {
isContextMenu: true,
showDefaultTab: true,
});
}
onProfilePopupCommand(event) {
let userContextId = parseInt(event.target.getAttribute('data-usercontextid'));
if (isNaN(userContextId)) {
return;
}
this.currentProfile = {
id: userContextId,
name: event.target.label,
};
}
finishSetup() {
gZenWorkspaces.addChangeListeners(this.handleZenWorkspacesChangeBind, { once: true });
}
async handleZenWorkspacesChange() {
await gZenWorkspaces.removeWorkspace(this.workspaceId);
this.#cleanup();
}
#cleanup() {
gZenWorkspaces.removeChangeListeners(this.handleZenWorkspacesChangeBind);
for (const element of this.constructor.elementsToDisable) {
const el = document.getElementById(element);
if (el) {
el.removeAttribute('disabled');
}
}
if (this.#wasInCollapsedMode) {
gNavToolbox.removeAttribute('zen-sidebar-expanded');
document.documentElement.removeAttribute('zen-sidebar-expanded');
}
document.documentElement.removeAttribute('zen-creating-workspace');
gBrowser.tabContainer.style.visibility = '';
if (gZenVerticalTabsManager._hasSetSingleToolbar) {
document.getElementById('nav-bar').style.visibility = '';
}
for (const element of this.#hiddenElements) {
element.hidden = false;
}
this.#hiddenElements = [];
this.remove();
}
}
customElements.define('zen-workspace-creation', ZenWorkspaceCreation);
}

View File

@@ -20,8 +20,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
_lastScrollTime = 0;
#workspaceCreationArgs = {};
bookmarkMenus = [
'PlacesToolbar',
'bookmarks-menu-button',
@@ -368,6 +366,13 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
return this.tabboxChildren.filter((child) => !child.hasAttribute('zen-empty-tab'));
}
get shouldAnimateEssentials() {
return (
this.containerSpecificEssentials ||
document.documentElement.hasAttribute('zen-creating-workspace')
);
}
workspaceElement(workspaceId) {
if (typeof workspaceId !== 'string') {
workspaceId = workspaceId?.uuid;
@@ -999,6 +1004,9 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
const hasNoIcon = anchor.hasAttribute('no-icon');
anchor.removeAttribute('no-icon');
if (hasNoIcon) {
anchor.textContent = '';
}
gZenEmojiPicker
.open(anchor)
.then(async (emoji) => {
@@ -1555,6 +1563,19 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}).catch(console.error);
}
async openWorkspaceCreation(event) {
let createForm;
await this.createAndSaveWorkspace('Space', undefined, false, 0, {
beforeChangeCallback: async (workspace) => {
createForm = document.createXULElement('zen-workspace-creation');
createForm.setAttribute('workspace-id', workspace.uuid);
gBrowser.tabContainer.after(createForm);
await createForm.promiseInitialized;
},
});
createForm.finishSetup();
}
closeWorkspacesSubView() {
let parentPanel = document.getElementById('PanelUI-zen-workspaces-multiview');
parentPanel.goBack(parentPanel);
@@ -1627,11 +1648,23 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
}
addChangeListeners(func) {
addChangeListeners(
func,
opts = {
once: false,
}
) {
if (!this._changeListeners) {
this._changeListeners = [];
}
this._changeListeners.push(func);
this._changeListeners.push({ func, opts });
}
removeChangeListeners(func) {
if (!this._changeListeners) {
return;
}
this._changeListeners = this._changeListeners.filter((listener) => listener.func !== func);
}
async changeWorkspaceWithID(workspaceID, ...args) {
@@ -1780,7 +1813,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
// if it's not we do apply it
if (
container.getAttribute('container') != workspace.containerTabId &&
this.containerSpecificEssentials
this.shouldAnimateEssentials
) {
container.setAttribute('hidden', 'true');
} else {
@@ -1789,7 +1822,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
if (
nextWorkspaceContextId !== workspaceContextId &&
offsetPixels &&
this.containerSpecificEssentials &&
this.shouldAnimateEssentials &&
(container.getAttribute('container') == nextWorkspaceContextId ||
container.getAttribute('container') == workspaceContextId)
) {
@@ -1815,11 +1848,10 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
if (this.workspaceHasIcon(currentWorkspace)) {
indicatorIcon.removeAttribute('no-icon');
indicatorIcon.textContent = this.getWorkspaceIcon(currentWorkspace);
} else {
indicatorIcon.setAttribute('no-icon', 'true');
indicatorIcon.textContent = '';
}
indicatorIcon.textContent = this.getWorkspaceIcon(currentWorkspace);
indicatorName.textContent = currentWorkspace.name;
}
@@ -1844,7 +1876,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
const newWorkspaceIndex = workspaces.workspaces.findIndex((w) => w.uuid === newWorkspace.uuid);
const isGoingLeft = newWorkspaceIndex <= previousWorkspaceIndex;
const clonedEssentials = [];
if (shouldAnimate && this.containerSpecificEssentials && previousWorkspace) {
if (shouldAnimate && this.shouldAnimateEssentials && previousWorkspace) {
for (const workspace of workspaces.workspaces) {
const essentialsContainer = this.getEssentialsSection(workspace.containerTabId);
if (clonedEssentials[clonedEssentials.length - 1]?.contextId == workspace.containerTabId) {
@@ -1938,7 +1970,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
}
}
if (this.containerSpecificEssentials && previousWorkspace) {
if (this.shouldAnimateEssentials && previousWorkspace) {
// Animate essentials
const newWorkspaceEssentialsContainer = clonedEssentials.find((cloned) =>
cloned.workspaces.some((w) => w.uuid === newWorkspace.uuid)
@@ -2237,7 +2269,11 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
// Notify listeners
if (this._changeListeners?.length) {
for (const listener of this._changeListeners) {
await listener(workspace, onInit);
const { func, opts } = listener;
await func({ workspace, onInit });
if (opts.once) {
this.removeChangeListeners(func);
}
}
}
@@ -2332,7 +2368,8 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
name = 'Space',
icon = undefined,
dontChange = false,
containerTabId = 0
containerTabId = 0,
{ beforeChangeCallback } = { beforeChangeCallback: null } // Callback to run before changing workspace
) {
if (!this.workspaceEnabled) {
return;
@@ -2361,6 +2398,13 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
await this.saveWorkspace(workspaceData, dontChange);
}
if (!dontChange) {
if (beforeChangeCallback) {
try {
await beforeChangeCallback(workspaceData);
} catch (e) {
console.error('Error in beforeChangeCallback:', e);
}
}
this.registerPinnedResizeObserver();
let changed = extraTabs.length > 0;
if (changed) {
@@ -2545,10 +2589,13 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
let userContextId = parseInt(event.target.getAttribute('data-usercontextid'));
workspace.containerTabId = userContextId + 0; // +0 to convert to number
await this.saveWorkspace(workspace);
await this._organizeWorkspaceStripLocations(workspace, true);
await gZenWorkspaces.updateTabsContainers();
this.tabContainer._invalidateCachedTabs();
}
async contextDeleteWorkspace() {
await this.removeWorkspace(this.activeWorkspace, true);
await this.removeWorkspace(this.activeWorkspace);
}
findTabToBlur(tab) {
@@ -2592,6 +2639,10 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
menu.appendChild(menuPopup);
document.getElementById('context_closeDuplicateTabs').after(menu);
document
.getElementById('cmd_zenOpenWorkspaceCreation')
.setAttribute('disabled', this.privateWindowOrDisabled);
}
async changeTabWorkspace(workspaceID) {

View File

@@ -0,0 +1,140 @@
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
zen-workspace-creation {
flex: 1;
max-width: calc(var(--zen-sidebar-width) - var(--zen-toolbox-padding) * 2);
& .zen-workspace-creation {
justify-content: center;
& .zen-workspace-creation-title {
font-size: large;
}
& .zen-workspace-creation-label {
margin: 0;
opacity: 0.6;
}
& form {
--input-border-color: light-dark(rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.2));
--input-bgcolor: light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15));
display: flex;
flex-direction: column;
width: calc(100% - 10px);
margin: auto;
gap: 1rem;
& .zen-workspace-creation-form {
gap: 0.6rem;
}
& xul|button {
border: none;
margin: 0;
}
& .zen-workspace-creation-name-wrapper {
padding: 9px 6px;
border-radius: var(--zen-button-border-radius);
margin: 0;
background-color: var(--input-bgcolor);
gap: 8px;
align-items: center;
padding-left: 8px;
& .zen-workspace-creation-icon-label {
position: relative;
width: 24px;
height: 20px;
justify-content: center;
align-items: center;
cursor: pointer;
background: transparent !important;
appearance: none;
align-content: center;
padding: 0;
& image {
display: none;
}
& label {
display: flex;
font-size: 12px;
justify-content: center;
}
&::before {
border: 1px dashed light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.5));
border-radius: 4px;
width: calc(100% + 2px);
height: calc(100% + 2px);
content: '';
position: absolute;
top: -2px;
left: -2px;
pointer-events: none;
}
}
& .zen-workspace-creation-name {
--input-border-color: transparent;
--input-bgcolor: transparent;
padding: 0 !important;
width: 100%;
}
}
& .zen-workspace-creation-profile-wrapper {
padding: 4px;
border-radius: var(--zen-button-border-radius);
margin: 0;
background-color: var(--input-bgcolor);
gap: 4px;
align-items: center;
flex-wrap: wrap;
& .zen-workspace-creation-profile-label {
cursor: help;
}
& .zen-workspace-creation-profile {
margin: 0;
padding: 6px !important;
border-radius: 99px;
padding-inline-end: 0;
appearance: none;
background: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
margin-left: auto;
min-width: unset !important;
}
}
& .zen-workspace-creation-edit-theme-button {
border-radius: var(--zen-button-border-radius);
margin: 0;
background-color: var(--input-bgcolor);
justify-content: center;
align-items: center;
appearance: none;
padding: 10px !important;
}
& .zen-workspace-creation-buttons {
gap: 0.5rem;
margin-top: 2rem;
& .zen-workspace-creation-create-button {
color: var(--button-text-color-primary) !important;
background: var(--color-accent-primary) !important;
}
}
}
}
}

View File

@@ -402,7 +402,7 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: text;
cursor: auto;
}
.zen-workspaces-actions {
@@ -506,3 +506,5 @@ zen-workspace {
#wrapper-zen-workspaces-button {
width: 100%;
}
%include create-workspace-form.css