Files
desktop/src/zen/workspaces/ZenWorkspace.mjs

259 lines
9.3 KiB
JavaScript

// 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/.
{
class nsZenWorkspace extends MozXULElement {
static get markup() {
return `
<vbox class="zen-workspace-tabs-section zen-current-workspace-indicator" flex="1" context="zenWorkspaceMoreActions">
<hbox class="zen-current-workspace-indicator-icon" />
<label class="zen-current-workspace-indicator-name" flex="1" />
<toolbarbutton class="toolbarbutton-1 chromeclass-toolbar-additional zen-workspaces-actions" context="zenWorkspaceMoreActions" />
</vbox>
<arrowscrollbox orient="vertical" class="workspace-arrowscrollbox">
<vbox class="zen-workspace-tabs-section zen-workspace-pinned-tabs-section" hide-separator="true">
<html:div class="pinned-tabs-container-separator"></html:div>
</vbox>
<vbox class="zen-workspace-tabs-section zen-workspace-normal-tabs-section">
<!-- Let it me as an ID to mantain compatibility with firefox's tabbrowser -->
<hbox id="tabbrowser-arrowscrollbox-periphery">
<toolbartabstop/>
<toolbarbutton id="tabs-newtab-button"
class="toolbarbutton-1"
command="cmd_newNavigatorTab"
tooltip="dynamic-shortcut-tooltip"
data-l10n-id="tabs-toolbar-new-tab"/>
<spacer class="closing-tabs-spacer" style="width: 0;"/>
</hbox>
</vbox>
</arrowscrollbox>
<vbox class="zen-workspace-empty-space" flex="1" />
`;
}
static get inheritedAttributes() {
return {
'.zen-workspace-tabs-section': 'zen-workspace-id=id',
};
}
constructor() {
super();
}
connectedCallback() {
if (this.delayConnectedCallback() || this._hasConnected) {
// If we are not ready yet, or if we have already connected, we
// don't need to do anything.
return;
}
this._hasConnected = true;
this.appendChild(this.constructor.fragment);
this.tabsContainer = this.querySelector('.zen-workspace-normal-tabs-section');
this.indicator = this.querySelector('.zen-current-workspace-indicator');
this.pinnedTabsContainer = this.querySelector('.zen-workspace-pinned-tabs-section');
this.initializeAttributeInheritance();
this.scrollbox = this.querySelector('arrowscrollbox');
this.scrollbox.smoothScroll = Services.prefs.getBoolPref(
'zen.startup.smooth-scroll-in-tabs',
false
);
this.scrollbox.addEventListener('wheel', this, true);
this.scrollbox.addEventListener('underflow', this);
this.scrollbox.addEventListener('overflow', this);
this.indicator.querySelector('.zen-current-workspace-indicator-name').onRenameFinished =
this.onIndicatorRenameFinished.bind(this);
this.pinnedTabsContainer.scrollbox = this.scrollbox;
this.indicator
.querySelector('.zen-workspaces-actions')
.addEventListener('click', this.onActionsCommand.bind(this));
this.indicator
.querySelector('.zen-current-workspace-indicator-icon')
.addEventListener('dblclick', (event) => {
event.stopPropagation();
gZenWorkspaces.changeWorkspaceIcon();
});
this.scrollbox._getScrollableElements = () => {
const children = [...this.pinnedTabsContainer.children, ...this.tabsContainer.children];
if (Services.prefs.getBoolPref('zen.view.show-newtab-button-top', false)) {
// Move the perifery to the first non-pinned tab
const periphery = this.tabsContainer.querySelector(
'#tabbrowser-arrowscrollbox-periphery'
);
if (periphery) {
const firstNonPinnedTabIndex = children.findIndex(
(child) => gBrowser.isTab(child) && !child.pinned
);
if (firstNonPinnedTabIndex > -1) {
// Change to new location and remove from the old one on the list
const peripheryIndex = children.indexOf(periphery);
if (peripheryIndex > -1) {
children.splice(peripheryIndex, 1);
}
children.splice(firstNonPinnedTabIndex, 0, periphery);
}
}
}
return Array.prototype.filter.call(
children,
this.scrollbox._canScrollToElement,
this.scrollbox
);
};
this.scrollbox._canScrollToElement = (element) => {
if (gBrowser.isTab(element)) {
return (
!element.hasAttribute('zen-essential') &&
!this.hasAttribute('positionpinnedtabs') &&
!element.hasAttribute('zen-empty-tab')
);
}
return true;
};
// Override for performance reasons. This is the size of a single element
// that can be scrolled when using mouse wheel scrolling. If we don't do
// this then arrowscrollbox computes this value by calling
// _getScrollableElements and dividing the box size by that number.
// However in the tabstrip case we already know the answer to this as,
// when we're overflowing, it is always the same as the tab min width or
// height. For tab group labels, the number won't exactly match, but
// that shouldn't be a problem in practice since the arrowscrollbox
// stops at element bounds when finishing scrolling.
try {
Object.defineProperty(this.scrollbox, 'lineScrollAmount', {
get: () => 36,
});
} catch (e) {
console.warn('Failed to set lineScrollAmount', e);
}
// Add them manually since attribute inheritance doesn't work
// for multiple layers of shadow DOM.
this.tabsContainer.setAttribute('zen-workspace-id', this.id);
this.pinnedTabsContainer.setAttribute('zen-workspace-id', this.id);
this.#updateOverflow();
this.onGradientCacheChanged = this.#onGradientCacheChanged.bind(this);
window.addEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged);
this.dispatchEvent(
new CustomEvent('ZenWorkspaceAttached', {
bubbles: true,
composed: true,
detail: { workspace: this },
})
);
}
disconnectedCallback() {
window.removeEventListener('ZenGradientCacheChanged', this.onGradientCacheChanged);
}
get active() {
return this.hasAttribute('active');
}
set active(value) {
if (value) {
this.setAttribute('active', 'true');
} else {
this.removeAttribute('active');
}
this.#updateOverflow();
}
#updateOverflow() {
if (!this.scrollbox) return;
if (this.overflows) {
this.#dispatchEventFromScrollbox('overflow');
} else {
this.#dispatchEventFromScrollbox('underflow');
}
}
#dispatchEventFromScrollbox(type) {
this.scrollbox.dispatchEvent(new CustomEvent(type, {}));
}
get overflows() {
return this.scrollbox.overflowing;
}
handleEvent(event) {
if (this.active) {
gBrowser.tabContainer.handleEvent(event);
}
}
get workspaceUuid() {
return this.id;
}
async onIndicatorRenameFinished(newName) {
if (newName === '') {
return;
}
let workspaces = (await gZenWorkspaces._workspaces()).workspaces;
let workspaceData = workspaces.find((workspace) => workspace.uuid === this.workspaceUuid);
workspaceData.name = newName;
await gZenWorkspaces.saveWorkspace(workspaceData);
this.indicator.querySelector('.zen-current-workspace-indicator-name').textContent = newName;
gZenUIManager.showToast('zen-workspace-renamed-toast');
}
onActionsCommand(event) {
event.stopPropagation();
const popup = document.getElementById('zenWorkspaceMoreActions');
const target = event.target;
target.setAttribute('open', 'true');
this.indicator.setAttribute('open', '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');
}
get newTabButton() {
return this.querySelector('#tabs-newtab-button');
}
#onGradientCacheChanged() {
const { isDarkMode, isExplicitMode, toolbarColor, primaryColor } =
gZenThemePicker.getGradientForWorkspace(
gZenWorkspaces.getWorkspaceFromId(this.workspaceUuid)
);
if (isExplicitMode) {
this.style.colorScheme = isDarkMode ? 'dark' : 'light';
} else {
this.style.colorScheme = '';
}
this.style.setProperty('--toolbox-textcolor', `rgba(${toolbarColor.join(',')})`);
this.style.setProperty('--zen-primary-color', primaryColor);
}
clearThemeStyles() {
this.style.colorScheme = '';
this.style.removeProperty('--toolbox-textcolor');
this.style.removeProperty('--zen-primary-color');
}
}
customElements.define('zen-workspace', nsZenWorkspace);
}