feat: Implement new workspace management, b=no-bug, c=common, workspaces

This commit is contained in:
mr. m
2025-06-09 18:41:04 +02:00
parent 967d0dd730
commit 2a0113408c
14 changed files with 275 additions and 22 deletions

2
l10n

Submodule l10n updated: 7730ce9a6d...751793cd1f

View File

@@ -55,3 +55,4 @@
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenGlanceManager.mjs"></script>
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenMediaController.mjs"></script>
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenDownloadAnimation.mjs"></script>
<script type="text/javascript" src="chrome://browser/content/zen-components/ZenEmojiPicker.mjs"></script>

View File

@@ -12,6 +12,8 @@
content/browser/zen-components/ZenUIMigration.mjs (../../zen/common/ZenUIMigration.mjs)
content/browser/zen-components/ZenCommonUtils.mjs (../../zen/common/ZenCommonUtils.mjs)
content/browser/zen-components/ZenSessionStore.mjs (../../zen/common/ZenSessionStore.mjs)
content/browser/zen-components/ZenEmojisData.min.mjs (../../zen/common/emojis/ZenEmojisData.min.mjs)
content/browser/zen-components/ZenEmojiPicker.mjs (../../zen/common/emojis/ZenEmojiPicker.mjs)
content/browser/zen-styles/zen-theme.css (../../zen/common/styles/zen-theme.css)
content/browser/zen-styles/zen-buttons.css (../../zen/common/styles/zen-buttons.css)

View File

@@ -38,6 +38,8 @@
<command id="cmd_zenCtxDeleteWorkspace" />
<command id="cmd_zenChangeWorkspaceName" />
<command id="cmd_zenChangeWorkspaceIcon" />
<command id="cmd_zenOpenWorkspacePanel" />
<command id="cmd_zenPinnedTabReset" />
<command id="cmd_zenPinnedTabResetNoTab" />

View File

@@ -77,8 +77,17 @@
</panelmultiview>
</panel>
<panel type="arrow" orient="vertical" id="PanelUI-zen-emojis-picker" position="bottomright topright" side="bottom">
<hbox id="PanelUI-zen-emojis-picker-header">
<html:input type="search" id="PanelUI-zen-emojis-picker-search" placeholder="Search emojis" />
<toolbarbutton id="PanelUI-zen-emojis-picker-none" class="toolbarbutton-1" />
</hbox>
<hbox id="PanelUI-zen-emojis-picker-list" />
</panel>
<menupopup id="zenWorkspaceMoreActions">
<menuitem id="context_zenEditWorkspace" data-l10n-id="zen-workspaces-panel-change-name" command="cmd_zenChangeWorkspaceName"/>
<menuitem id="context_zenEditWorkspaceIcon" data-l10n-id="zen-workspaces-panel-change-icon" command="cmd_zenChangeWorkspaceIcon"/>
<menuitem class="zenToolbarThemePicker"
data-l10n-id="zen-workspaces-change-gradient"
command="cmd_zenOpenZenThemePicker"/>
@@ -91,5 +100,7 @@
<menupopup />
</menu>
<menuseparator/>
<menuitem id="context_zenOpenWorkspacePanel" data-l10n-id="zen-workspaces-panel-context-manage" command="cmd_zenOpenWorkspacePanel"/>
<menuseparator/>
<menuitem id="context_zenDeleteWorkspace" data-l10n-id="zen-workspaces-panel-context-delete" command="cmd_zenCtxDeleteWorkspace"/>
</menupopup>

View File

@@ -39,6 +39,7 @@
.close-icon,
#zen-sidebar-web-panel-close,
#zen-glance-sidebar-close,
#PanelUI-zen-emojis-picker-none,
.zen-theme-picker-custom-list-item-remove {
list-style-image: url('close.svg') !important;
}

View File

@@ -1105,15 +1105,17 @@ var gZenVerticalTabsManager = {
`);
label.after(container);
}
const containerHtml = isTab
? this._tabEdited.querySelector('.tab-editor-container')
: this._tabEdited.parentNode;
const input = document.createElement('input');
input.id = 'tab-label-input';
input.value = isTab ? this._tabEdited.label : this._tabEdited.textContent;
input.addEventListener('keydown', this.renameTabKeydown.bind(this));
containerHtml.appendChild(input);
if (isTab) {
const containerHtml = this._tabEdited.querySelector('.tab-editor-container');
containerHtml.appendChild(input);
} else {
this._tabEdited.after(input);
}
input.focus();
input.select();

View File

@@ -0,0 +1,139 @@
{
class ZenEmojiPicker extends ZenDOMOperatedFeature {
#panel;
#anchor;
#currentPromise = null;
#currentPromiseResolve = null;
#currentPromiseReject = null;
init() {
this.#panel = document.getElementById('PanelUI-zen-emojis-picker');
this.#panel.addEventListener('popupshowing', this);
this.#panel.addEventListener('popuphidden', this);
document.getElementById('PanelUI-zen-emojis-picker-none').addEventListener('command', this);
this.searchInput.addEventListener('input', this);
}
handleEvent(event) {
switch (event.type) {
case 'popupshowing':
this.#onPopupShowing(event);
break;
case 'popuphidden':
this.#onPopupHidden(event);
break;
case 'command':
if (event.target.id === 'PanelUI-zen-emojis-picker-none') {
this.#selectEmoji(null);
}
break;
case 'input':
this.#onSearchInput(event);
break;
}
}
get #emojis() {
if (this._emojis) {
return this._emojis;
}
const lazy = {};
Services.scriptloader.loadSubScript(
'chrome://browser/content/zen-components/ZenEmojisData.min.mjs',
lazy
);
this._emojis = lazy.ZenEmojisData;
return this._emojis;
}
get emojiList() {
return document.getElementById('PanelUI-zen-emojis-picker-list');
}
get searchInput() {
return document.getElementById('PanelUI-zen-emojis-picker-search');
}
#clearEmojis() {
delete this._emojis;
}
#onSearchInput(event) {
const input = event.target;
const value = input.value.trim().toLowerCase();
// search for emojis.tags and order by emojis.order
const filteredEmojis = this.#emojis
.filter((emoji) => {
return emoji.tags.some((tag) => tag.toLowerCase().includes(value));
})
.sort((a, b) => a.order - b.order);
for (const button of this.emojiList.children) {
const buttonEmoji = button.getAttribute('label');
const emojiObject = filteredEmojis.find((emoji) => emoji.emoji === buttonEmoji);
if (emojiObject) {
button.hidden = !emojiObject.tags.some((tag) => tag.toLowerCase().includes(value));
button.style.order = emojiObject.order;
} else {
button.hidden = true;
}
}
}
#onPopupShowing(event) {
if (event.target !== this.#panel) return;
const emojiList = this.emojiList;
for (const emoji of this.#emojis) {
const item = document.createXULElement('toolbarbutton');
item.className = 'toolbarbutton-1 zen-emojis-picker-emoji';
item.setAttribute('label', emoji.emoji);
item.setAttribute('tooltiptext', emoji.annotation);
item.addEventListener('command', () => {
this.#selectEmoji(emoji.emoji);
});
emojiList.appendChild(item);
}
}
#onPopupHidden(event) {
if (event.target !== this.#panel) return;
this.#clearEmojis();
const emojiList = this.emojiList;
emojiList.innerHTML = '';
if (this.#currentPromiseReject) {
this.#currentPromiseReject(new Error('Emoji picker closed without selection'));
}
this.#currentPromise = null;
this.#currentPromiseResolve = null;
this.#currentPromiseReject = null;
this.#anchor.removeAttribute('zen-emoji-open');
this.#anchor = null;
}
#selectEmoji(emoji) {
this.#currentPromiseResolve?.(emoji);
this.#panel.hidePopup();
}
open(anchor) {
if (this.#currentPromise) {
return null;
}
this.#currentPromise = new Promise((resolve, reject) => {
this.#currentPromiseResolve = resolve;
this.#currentPromiseReject = reject;
});
this.#anchor = anchor;
this.#anchor.setAttribute('zen-emoji-open', 'true');
this.#panel.openPopup(anchor, 'after_start', 0, 0, false, false);
return this.#currentPromise;
}
}
window.gZenEmojiPicker = new ZenEmojiPicker();
}

File diff suppressed because one or more lines are too long

View File

@@ -35,3 +35,52 @@ body > #confetti {
#PersonalToolbar:not([collapsed='true']) {
min-height: 30px;
}
/* Emojis picker */
#PanelUI-zen-emojis-picker {
--panel-width: 250px;
--panel-padding: 10px;
&::part(content) {
gap: 15px;
}
#PanelUI-zen-emojis-picker-header {
gap: 10px;
align-items: center;
}
#PanelUI-zen-emojis-picker-none label {
display: none;
}
#PanelUI-zen-emojis-picker-list {
flex-wrap: wrap;
max-height: 265px;
overflow-y: auto;
overflow-x: hidden;
gap: 5px;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(22px, 1fr));
.zen-emojis-picker-emoji {
& image {
display: none;
}
}
}
.zen-emojis-picker-emoji,
#PanelUI-zen-emojis-picker-none {
width: 22px;
height: 22px;
&:hover {
background-color: light-dark(rgba(0, 0, 0, 0.1), rgba(255, 255, 255, 0.1));
border-radius: 4px;
cursor: pointer;
}
}
}

View File

@@ -94,6 +94,12 @@ document.addEventListener(
case 'cmd_zenChangeWorkspaceName':
gZenVerticalTabsManager.renameTabStart(event);
break;
case 'cmd_zenChangeWorkspaceIcon':
gZenWorkspaces.changeWorkspaceIcon();
break;
case 'cmd_zenOpenWorkspacePanel':
gZenWorkspaces.openWorkspacesDialog(event);
break;
default:
if (event.target.id.startsWith('cmd_zenWorkspaceSwitch')) {
const index = parseInt(event.target.id.replace('cmd_zenWorkspaceSwitch', ''), 10) - 1;

View File

@@ -7,7 +7,7 @@
return `
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1">
<hbox class="zen-current-workspace-indicator-icon"></hbox>
<hbox class="zen-current-workspace-indicator-name"></hbox>
<hbox class="zen-current-workspace-indicator-name" flex="1"></hbox>
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions"></toolbarbutton>
</vbox>
<arrowscrollbox orient="vertical" class="workspace-arrowscrollbox">

View File

@@ -21,7 +21,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
_lastScrollTime = 0;
#workspaceCreationArgs = {};
bookmarkMenus = [
'PlacesToolbar',
'bookmarks-menu-button',
@@ -467,7 +467,6 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
workspaceWrapper.pinnedTabsContainer,
tabs
);
this.initIndicatorContextMenu(workspaceWrapper.indicator);
resolve();
},
{ once: true }
@@ -991,13 +990,32 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
}
}
initIndicatorContextMenu(indicator) {
const th = (event) => {
event.preventDefault();
event.stopPropagation();
this.openWorkspacesDialog(event);
};
indicator.addEventListener('contextmenu', th);
changeWorkspaceIcon() {
const anchor = this.activeWorkspaceIndicator?.querySelector(
'.zen-current-workspace-indicator-icon'
);
if (!anchor) {
return;
}
const hasNoIcon = anchor.hasAttribute('no-icon');
anchor.removeAttribute('no-icon');
gZenEmojiPicker
.open(anchor)
.then(async (emoji) => {
const workspace = this.getActiveWorkspaceFromCache();
if (!workspace) {
console.warn('No active workspace found to change icon');
return;
}
workspace.icon = emoji;
await this.saveWorkspace(workspace);
})
.catch((error) => {
console.warn('Error changing workspace icon:', error);
if (hasNoIcon) {
anchor.setAttribute('no-icon', 'true');
}
});
}
shouldCloseWindow() {
@@ -1525,7 +1543,7 @@ var gZenWorkspaces = new (class extends ZenMultiWindowFeature {
if (!this.workspaceEnabled || this.isPrivateWindow) {
return;
}
let target = event.target.closest('.zen-current-workspace-indicator');
let target = this.activeWorkspaceIndicator || event.target;
let panel = document.getElementById('PanelUI-zen-workspaces');
await this._propagateWorkspaceData({
ignoreStrip: true,
@@ -1797,10 +1815,11 @@ 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;
}

View File

@@ -340,7 +340,7 @@
/* Mark workspaces indicator */
.zen-current-workspace-indicator {
padding: calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
padding: calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
font-weight: 600;
position: relative;
max-height: var(--zen-workspace-indicator-height);
@@ -352,6 +352,7 @@ padding: calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
width: 100%;
font-size: small;
padding-right: 10px;
-moz-window-dragging: no-drag;
&::before {
border-radius: var(--border-radius-medium);
@@ -376,8 +377,23 @@ padding: calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
}
& .zen-current-workspace-indicator-icon {
font-size: 12px;
line-height: 1;
position: relative;
width: 16px;
height: 16px;
justify-content: center;
align-items: center;
&[zen-emoji-open='true']::before {
border: 1px dashed light-dark(rgba(0, 0, 0, .5), rgba(255, 255, 255, .5));
border-radius: 6px;
width: calc(100% + 6px);
height: calc(100% + 6px);
content: '';
position: absolute;
top: -4px;
left: -4px;
pointer-events: none;
}
}
.zen-current-workspace-indicator-name {
@@ -386,16 +402,20 @@ padding: calc(4px + var(--tab-inline-padding) + var(--zen-toolbox-padding));
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
cursor: text;
cursor: text;
}
.zen-workspaces-actions {
.zen-workspaces-actions {
--toolbarbutton-inner-padding: 4px;
margin-left: auto !important;
opacity: 0;
visibility: collapse;
transition: opacity 0.1s;
order: 5;
:root[zen-renaming-tab='true'] & {
display: none;
}
}
:root:not([zen-private-window]) &:hover .zen-workspaces-actions,