// 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 ` `; } 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); }