mirror of
https://github.com/zen-browser/desktop.git
synced 2026-08-31 02:33:33 +00:00
Merge branch 'dev' into glance-buttons-overflow
This commit is contained in:
@@ -37,6 +37,9 @@ var gZenCompactModeManager = {
|
||||
|
||||
this.addMouseActions();
|
||||
this.addContextMenu();
|
||||
|
||||
// Clear hover states when window state changes (minimize, maximize, etc.)
|
||||
window.addEventListener('sizemodechange', () => this._clearAllHoverStates());
|
||||
},
|
||||
|
||||
get preference() {
|
||||
@@ -141,10 +144,17 @@ var gZenCompactModeManager = {
|
||||
this.animateCompactMode();
|
||||
},
|
||||
|
||||
getAndApplySidebarWidth() {
|
||||
// NOTE: Dont actually use event, it's just so we make sure
|
||||
// the caller is from the ResizeObserver
|
||||
getAndApplySidebarWidth(event = undefined) {
|
||||
let sidebarWidth = this.sidebar.getBoundingClientRect().width;
|
||||
if (sidebarWidth > 1) {
|
||||
gZenUIManager.restoreScrollbarState();
|
||||
// Second variable to get the genuine width of the sidebar
|
||||
this.sidebar.style.setProperty('--actual-zen-sidebar-width', `${sidebarWidth}px`);
|
||||
if (event && this.preference) {
|
||||
return;
|
||||
}
|
||||
this.sidebar.style.setProperty('--zen-sidebar-width', `${sidebarWidth}px`);
|
||||
}
|
||||
return sidebarWidth;
|
||||
@@ -158,10 +168,15 @@ var gZenCompactModeManager = {
|
||||
const canAnimate =
|
||||
lazyCompactMode.COMPACT_MODE_CAN_ANIMATE_SIDEBAR &&
|
||||
!this.sidebar.hasAttribute('zen-user-show') &&
|
||||
!this.sidebar.hasAttribute('zen-has-empty-tab') &&
|
||||
!this.sidebar.hasAttribute('zen-has-hover');
|
||||
// Do this so we can get the correct width ONCE compact mode styled have been applied
|
||||
const titlebar = this.sidebar.querySelector('#titlebar');
|
||||
if (canAnimate) {
|
||||
this.sidebar.setAttribute('animate', 'true');
|
||||
titlebar.setAttribute('has-animated-padding', 'true');
|
||||
} else {
|
||||
titlebar.removeAttribute('has-animated-padding');
|
||||
}
|
||||
this.sidebar.style.removeProperty('margin-right');
|
||||
this.sidebar.style.removeProperty('margin-left');
|
||||
@@ -191,15 +206,18 @@ var gZenCompactModeManager = {
|
||||
)
|
||||
.then(() => {
|
||||
window.requestAnimationFrame(() => {
|
||||
this.sidebar.style.transition = 'none';
|
||||
this.sidebar.removeAttribute('animate');
|
||||
this.sidebar.style.visibility = 'hidden';
|
||||
this.sidebar.style.removeProperty('margin-right');
|
||||
this.sidebar.style.removeProperty('margin-left');
|
||||
this.sidebar.style.removeProperty('transform');
|
||||
this.sidebar.style.transition = 'none';
|
||||
setTimeout(() => {
|
||||
this._animating = false;
|
||||
this.sidebar.style.removeProperty('visibility');
|
||||
this.sidebar.style.removeProperty('transition');
|
||||
});
|
||||
this.sidebar.style.removeProperty('opacity');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
} else if (canHideSidebar && !isCompactMode) {
|
||||
@@ -308,9 +326,6 @@ var gZenCompactModeManager = {
|
||||
{
|
||||
element: gZenVerticalTabsManager.actualWindowButtons,
|
||||
},
|
||||
{
|
||||
element: gZenVerticalTabsManager.actualWindowButtons,
|
||||
},
|
||||
];
|
||||
},
|
||||
|
||||
@@ -346,14 +361,13 @@ var gZenCompactModeManager = {
|
||||
addMouseActions() {
|
||||
for (let i = 0; i < this.hoverableElements.length; i++) {
|
||||
let target = this.hoverableElements[i].element;
|
||||
target.addEventListener('mouseenter', (event) => {
|
||||
if (!event.target.matches(':hover')) return;
|
||||
const onEnter = (event) => {
|
||||
if (event.type === 'mouseenter' && !event.target.matches(':hover')) return;
|
||||
this.clearFlashTimeout('has-hover' + target.id);
|
||||
window.requestAnimationFrame(() => target.setAttribute('zen-has-hover', 'true'));
|
||||
});
|
||||
};
|
||||
|
||||
target.addEventListener('mouseleave', (event) => {
|
||||
// If on Mac, ignore mouseleave in the area of window buttons
|
||||
const onLeave = (event) => {
|
||||
if (AppConstants.platform == 'macosx') {
|
||||
const buttonRect = gZenVerticalTabsManager.actualWindowButtons.getBoundingClientRect();
|
||||
const MAC_WINDOW_BUTTONS_X_BORDER = buttonRect.width + buttonRect.x;
|
||||
@@ -368,6 +382,12 @@ var gZenCompactModeManager = {
|
||||
}
|
||||
}
|
||||
|
||||
// When moving the cursor between the url bar and bookmarks, or in-between bookmarks in the bookmark bar, the
|
||||
// mouseLeave event is triggered without a relatedTarget.
|
||||
if (event.relatedTarget == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If it's a child element but not the target, ignore the event
|
||||
if (target.contains(event.explicitOriginalTarget) && event.explicitOriginalTarget !== target) {
|
||||
return;
|
||||
@@ -378,7 +398,13 @@ var gZenCompactModeManager = {
|
||||
} else {
|
||||
this._removeHoverFrames[target.id] = window.requestAnimationFrame(() => target.removeAttribute('zen-has-hover'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
target.addEventListener('mouseenter', onEnter);
|
||||
target.addEventListener('dragover', onEnter);
|
||||
|
||||
target.addEventListener('mouseleave', onLeave);
|
||||
target.addEventListener('dragleave', onLeave);
|
||||
}
|
||||
|
||||
document.documentElement.addEventListener('mouseleave', (event) => {
|
||||
@@ -427,4 +453,15 @@ var gZenCompactModeManager = {
|
||||
let toolbar = document.getElementById('zen-appcontent-navbar-container');
|
||||
toolbar.toggleAttribute('zen-user-show');
|
||||
},
|
||||
|
||||
_clearAllHoverStates() {
|
||||
// Clear hover attributes from all hoverable elements
|
||||
for (let entry of this.hoverableElements) {
|
||||
const target = entry.element;
|
||||
if (target && !target.matches(':hover') && target.hasAttribute('zen-has-hover')) {
|
||||
target.removeAttribute('zen-has-hover');
|
||||
this.clearFlashTimeout('has-hover' + target.id);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -6,8 +6,9 @@
|
||||
#glances = new Map();
|
||||
#currentGlanceID = null;
|
||||
|
||||
#confirmationTimeout = null;
|
||||
|
||||
init() {
|
||||
window.addEventListener('keydown', this.onKeyDown.bind(this));
|
||||
window.addEventListener('TabClose', this.onTabClose.bind(this));
|
||||
window.addEventListener('TabSelect', this.onLocationChange.bind(this));
|
||||
|
||||
@@ -33,16 +34,6 @@
|
||||
return this.#glances.get(this.#currentGlanceID)?.parentTab;
|
||||
}
|
||||
|
||||
// If the user presses Escape while a Glance is open, close it
|
||||
onKeyDown(event) {
|
||||
if (event.defaultPrevented) return;
|
||||
if (event.key === 'Escape' && this.#currentGlanceID) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.closeGlance({ onTabClose: true });
|
||||
}
|
||||
}
|
||||
|
||||
onOverlayClick(event) {
|
||||
// If user clicks outside content area, close glance
|
||||
if (event.target === this.overlay && event.originalTarget !== this.contentWrapper) {
|
||||
@@ -67,7 +58,7 @@
|
||||
|
||||
// Figure out where to insert new tabs
|
||||
getTabPosition(tab) {
|
||||
return Math.max(gBrowser._numVisiblePinTabs, tab._tPos);
|
||||
return Math.max(gBrowser.pinnedTabCount, tab._tPos);
|
||||
}
|
||||
|
||||
// Create a new tab for Glance
|
||||
@@ -77,7 +68,7 @@
|
||||
skipBackgroundNotify: true,
|
||||
insertTab: true,
|
||||
skipLoad: false,
|
||||
index: this.getTabPosition(currentTab),
|
||||
index: this.getTabPosition(currentTab) + 1,
|
||||
};
|
||||
currentTab._selected = true;
|
||||
|
||||
@@ -247,11 +238,26 @@
|
||||
});
|
||||
}
|
||||
|
||||
closeGlance({ noAnimation = false, onTabClose = false, setNewID = null, isDifferent = false } = {}) {
|
||||
closeGlance({ noAnimation = false, onTabClose = false, setNewID = null, isDifferent = false, hasFocused = false } = {}) {
|
||||
if (this._animating || !this.#currentBrowser || this.animatingOpen || this._duringOpening) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { permitUnload } = this.#currentBrowser.permitUnload();
|
||||
if (!permitUnload) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (onTabClose && hasFocused && !this.#confirmationTimeout) {
|
||||
const cancelButton = document.getElementById('zen-glance-sidebar-close');
|
||||
cancelButton.setAttribute('waitconfirmation', true);
|
||||
this.#confirmationTimeout = setTimeout(() => {
|
||||
cancelButton.removeAttribute('waitconfirmation');
|
||||
this.#confirmationTimeout = null;
|
||||
}, 3000);
|
||||
return;
|
||||
}
|
||||
|
||||
this.browserWrapper.removeAttribute('has-finished-animation');
|
||||
if (noAnimation) {
|
||||
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer').removeAttribute('style');
|
||||
@@ -301,16 +307,15 @@
|
||||
.then(() => {
|
||||
this.#currentParentTab.linkedBrowser.closest('.browserSidebarContainer').removeAttribute('style');
|
||||
});
|
||||
|
||||
// Animate the browser wrapper back
|
||||
this.browserWrapper.style.opacity = 1;
|
||||
gZenUIManager.motion
|
||||
.animate(
|
||||
this.browserWrapper,
|
||||
{
|
||||
...originalPosition,
|
||||
opacity: 0.3,
|
||||
opacity: 0,
|
||||
},
|
||||
{ type: 'spring', bounce: 0, duration: 0.4, easing: 'ease' }
|
||||
{ type: 'spring', bounce: 0, duration: 0.5, easing: 'ease-in' }
|
||||
)
|
||||
.then(() => {
|
||||
this.browserWrapper.removeAttribute('animate');
|
||||
@@ -336,8 +341,7 @@
|
||||
this.#currentParentTab._visuallySelected = false;
|
||||
}
|
||||
|
||||
// Reset everything
|
||||
const prevOverlay = this.overlay;
|
||||
// reset everything
|
||||
this.browserWrapper = null;
|
||||
this.overlay = null;
|
||||
this.contentWrapper = null;
|
||||
@@ -349,7 +353,7 @@
|
||||
gBrowser.selectedTab = this.#currentParentTab;
|
||||
}
|
||||
this._ignoreClose = true;
|
||||
gBrowser.removeTab(this.lastCurrentTab, { animate: true });
|
||||
gBrowser.removeTab(this.lastCurrentTab, { animate: true, skipPermitUnload: true });
|
||||
gBrowser.tabContainer._invalidateCachedTabs();
|
||||
|
||||
this.#currentParentTab.removeAttribute('glance-id');
|
||||
@@ -437,9 +441,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
// If location changes to a tab with a different glance-id, close the old one
|
||||
clearConfirmationTimeout() {
|
||||
if (this.#confirmationTimeout) {
|
||||
clearTimeout(this.#confirmationTimeout);
|
||||
this.#confirmationTimeout = null;
|
||||
}
|
||||
document.getElementById('zen-glance-sidebar-close')?.removeAttribute('waitconfirmation');
|
||||
}
|
||||
|
||||
// note: must be sync to avoid timing issues
|
||||
onLocationChange(event) {
|
||||
const tab = event.target;
|
||||
this.clearConfirmationTimeout();
|
||||
if (this.animatingFullOpen || this.closingGlance) {
|
||||
return;
|
||||
}
|
||||
@@ -502,7 +515,6 @@
|
||||
owner &&
|
||||
owner.pinned &&
|
||||
this._lazyPref.SHOULD_OPEN_EXTERNAL_TABS_IN_GLANCE &&
|
||||
owner.linkedBrowser?.docShellIsActive &&
|
||||
owner.linkedBrowser?.browsingContext?.isAppTab &&
|
||||
this.tabDomainsDiffer(owner, uri) &&
|
||||
Services.prefs.getBoolPref('zen.glance.enabled', true)
|
||||
@@ -515,7 +527,11 @@
|
||||
try {
|
||||
if (this.shouldOpenTabInGlance(tab, uri)) {
|
||||
const browserRect = gBrowser.tabbox.getBoundingClientRect();
|
||||
this.openGlance({ url: undefined, x: browserRect.width / 2, y: browserRect.height / 2, width: 0, height: 0 });
|
||||
this.openGlance(
|
||||
{ url: undefined, x: browserRect.width / 2, y: browserRect.height / 2, width: 0, height: 0 },
|
||||
tab,
|
||||
tab.owner
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
@@ -610,8 +626,12 @@
|
||||
esModuleURI: 'chrome://browser/content/zen-components/actors/ZenGlanceChild.sys.mjs',
|
||||
events: {
|
||||
DOMContentLoaded: {},
|
||||
keydown: {
|
||||
capture: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
allFrames: true,
|
||||
matches: ['https://*/*'],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -863,7 +863,7 @@
|
||||
getToolbarModifiedBase() {
|
||||
return this.isDarkMode
|
||||
? 'color-mix(in srgb, var(--zen-themed-toolbar-bg) 80%, #fff 20%)'
|
||||
: 'color-mix(in srgb, var(--zen-themed-toolbar-bg) 95%, #000 5%)';
|
||||
: 'color-mix(in srgb, var(--zen-themed-toolbar-bg) 95%, #000 6%)';
|
||||
}
|
||||
|
||||
getSingleRGBColor(color, forToolbar = false) {
|
||||
|
||||
322
src/browser/base/zen-components/ZenMediaController.mjs
Normal file
322
src/browser/base/zen-components/ZenMediaController.mjs
Normal file
@@ -0,0 +1,322 @@
|
||||
class ZenMediaController {
|
||||
_currentMediaController = null;
|
||||
_currentBrowser = null;
|
||||
_mediaUpdateInterval = null;
|
||||
|
||||
mediaTitle = null;
|
||||
mediaArtist = null;
|
||||
mediaControlBar = null;
|
||||
mediaServiceIcon = null;
|
||||
mediaServiceTitle = null;
|
||||
mediaProgressBar = null;
|
||||
mediaCurrentTime = null;
|
||||
mediaDuration = null;
|
||||
mediaFocusButton = null;
|
||||
mediaProgressBarContainer = null;
|
||||
|
||||
supportedKeys = ['playpause', 'previoustrack', 'nexttrack'];
|
||||
|
||||
init() {
|
||||
this.mediaTitle = document.querySelector('#zen-media-title');
|
||||
this.mediaArtist = document.querySelector('#zen-media-artist');
|
||||
this.mediaControlBar = document.querySelector('#zen-media-controls-toolbar');
|
||||
this.mediaServiceIcon = document.querySelector('#zen-media-service-button > image');
|
||||
this.mediaServiceTitle = document.querySelector('#zen-media-service-title');
|
||||
this.mediaProgressBar = document.querySelector('#zen-media-progress-bar');
|
||||
this.mediaCurrentTime = document.querySelector('#zen-media-current-time');
|
||||
this.mediaDuration = document.querySelector('#zen-media-duration');
|
||||
this.mediaFocusButton = document.querySelector('#zen-media-focus-button');
|
||||
this.mediaProgressBarContainer = document.querySelector('#zen-media-progress-hbox');
|
||||
|
||||
window.addEventListener('TabSelect', (event) => {
|
||||
if (this._currentBrowser) {
|
||||
if (event.target.linkedBrowser.browserId === this._currentBrowser.browserId) {
|
||||
gZenUIManager.motion
|
||||
.animate(this.mediaControlBar, {
|
||||
opacity: [1, 0],
|
||||
y: [0, 10],
|
||||
})
|
||||
.then(() => {
|
||||
this.mediaControlBar.setAttribute('hidden', 'true');
|
||||
});
|
||||
} else if (this.mediaControlBar.hasAttribute('hidden')) {
|
||||
this.mediaControlBar.removeAttribute('hidden');
|
||||
window.requestAnimationFrame(() => {
|
||||
this.mediaControlBar.style.height =
|
||||
this.mediaControlBar.querySelector('toolbaritem').getBoundingClientRect().height + 'px';
|
||||
gZenUIManager.motion.animate(
|
||||
this.mediaControlBar,
|
||||
{
|
||||
opacity: [0, 1],
|
||||
y: [10, 0],
|
||||
},
|
||||
{}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('TabClose', (event) => {
|
||||
if (this._currentBrowser) {
|
||||
if (event.target.linkedBrowser.browserId === this._currentBrowser.browserId) {
|
||||
this.deinitMediaController(this._currentMediaController);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deinitializes a media controller, removing all event listeners and resetting state.
|
||||
* @param {Object} mediaController - The media controller to deinitialize.
|
||||
*/
|
||||
deinitMediaController(mediaController) {
|
||||
if (!mediaController) return;
|
||||
|
||||
mediaController.onpositionstatechange = null;
|
||||
mediaController.onplaybackstatechange = null;
|
||||
mediaController.onsupportedkeyschange = null;
|
||||
mediaController.onmetadatachange = null;
|
||||
mediaController.ondeactivated = null;
|
||||
|
||||
this._currentMediaController = null;
|
||||
this._currentBrowser = null;
|
||||
|
||||
if (this._mediaUpdateInterval) {
|
||||
clearInterval(this._mediaUpdateInterval);
|
||||
this._mediaUpdateInterval = null;
|
||||
}
|
||||
|
||||
this.mediaControlBar.setAttribute('hidden', 'true');
|
||||
this.mediaControlBar.removeAttribute('muted');
|
||||
this.mediaControlBar.classList.remove('playing');
|
||||
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the media control UI with metadata and position state.
|
||||
* @param {Object} metadata - The media metadata (title, artist, etc.).
|
||||
* @param {Object} positionState - The position state (position, duration).
|
||||
*/
|
||||
setupMediaControl(metadata, positionState) {
|
||||
if (!this.mediaControlBar.classList.contains('playing')) {
|
||||
this.mediaControlBar.classList.add('playing');
|
||||
}
|
||||
|
||||
// Have it displayed as e.g. <white>youtube</white><grey>.com</grey>
|
||||
let host = this._currentBrowser._originalURI.displayHost;
|
||||
if (host.startsWith('www.')) host = host.slice(4);
|
||||
// note: we might have subdomains, so we need to split the host
|
||||
const [service, ...tld] = host.split('.');
|
||||
this.mediaServiceTitle.querySelector('.service').textContent = service;
|
||||
this.mediaServiceTitle.querySelector('.tld').textContent = '.' + tld.join('.');
|
||||
|
||||
this.mediaServiceIcon.src = this._currentBrowser.mIconURL;
|
||||
this.mediaFocusButton.style.listStyleImage = `url(${this._currentBrowser.mIconURL})`;
|
||||
|
||||
this.mediaTitle.textContent = metadata.title || '';
|
||||
this.mediaArtist.textContent = metadata.artist || '';
|
||||
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
|
||||
this._currentPosition = positionState.position;
|
||||
this._currentDuration = positionState.duration;
|
||||
this.updateMediaPosition();
|
||||
|
||||
for (const key of this.supportedKeys) {
|
||||
const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`);
|
||||
button.disabled = !this._currentMediaController.supportedKeys.includes(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} mediaController - The media controller to activate.
|
||||
* @param {Object} browser - The browser associated with the media controller.
|
||||
*/
|
||||
activateMediaControls(mediaController, browser) {
|
||||
this.updateMuteState();
|
||||
|
||||
if (this._currentBrowser?.browserId === browser.browserId) return;
|
||||
else {
|
||||
this.deinitMediaController(this._currentMediaController);
|
||||
this._currentMediaController = mediaController;
|
||||
this._currentBrowser = browser;
|
||||
}
|
||||
|
||||
mediaController.onpositionstatechange = this.onPositionstateChange.bind(this);
|
||||
mediaController.onplaybackstatechange = this.onPlaybackstateChange.bind(this);
|
||||
mediaController.onsupportedkeyschange = this.onSupportedKeysChange.bind(this);
|
||||
mediaController.onmetadatachange = this.onMetadataChange.bind(this);
|
||||
mediaController.ondeactivated = this.onDeactivated.bind(this);
|
||||
|
||||
const metadata = mediaController.getMetadata();
|
||||
const positionState = mediaController.getPositionState();
|
||||
|
||||
this.setupMediaControl(metadata, positionState);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event - The deactivation event.
|
||||
*/
|
||||
onDeactivated(event) {
|
||||
if (event.target === this._currentMediaController) {
|
||||
this.deinitMediaController(event.target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates playback state and UI based on changes.
|
||||
* @param {Event} event - The playback state change event.
|
||||
*/
|
||||
onPlaybackstateChange(event) {
|
||||
this.mediaControlBar.classList.toggle('playing', event.target.isPlaying);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates supported keys in the UI.
|
||||
* @param {Event} event - The supported keys change event.
|
||||
*/
|
||||
onSupportedKeysChange(event) {
|
||||
for (const key of this.supportedKeys) {
|
||||
const button = this.mediaControlBar.querySelector(`#zen-media-${key}-button`);
|
||||
button.disabled = !event.target.supportedKeys.includes(key);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates position state and UI when the media position changes.
|
||||
* @param {Event} event - The position state change event.
|
||||
*/
|
||||
onPositionstateChange(event) {
|
||||
if (event.target !== this._currentMediaController) return;
|
||||
|
||||
this._currentPosition = event.position;
|
||||
this._currentDuration = event.duration;
|
||||
this.updateMediaPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the media progress bar and time display.
|
||||
*/
|
||||
updateMediaPosition() {
|
||||
if (this._mediaUpdateInterval) {
|
||||
clearInterval(this._mediaUpdateInterval);
|
||||
this._mediaUpdateInterval = null;
|
||||
}
|
||||
|
||||
if (this._currentDuration >= 900_000) return this.mediaProgressBarContainer.setAttribute('hidden', 'true');
|
||||
else this.mediaProgressBarContainer.removeAttribute('hidden');
|
||||
|
||||
this.mediaCurrentTime.textContent = this.formatSecondsToTime(this._currentPosition);
|
||||
this.mediaDuration.textContent = this.formatSecondsToTime(this._currentDuration);
|
||||
this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100;
|
||||
|
||||
if (this._currentMediaController?.isPlaying) {
|
||||
this._mediaUpdateInterval = setInterval(() => {
|
||||
if (this._currentMediaController?.isPlaying) {
|
||||
this._currentPosition += 1;
|
||||
if (this._currentPosition > this._currentDuration) {
|
||||
this._currentPosition = this._currentDuration;
|
||||
}
|
||||
this.mediaCurrentTime.textContent = this.formatSecondsToTime(this._currentPosition);
|
||||
this.mediaProgressBar.value = (this._currentPosition / this._currentDuration) * 100;
|
||||
} else {
|
||||
clearInterval(this._mediaUpdateInterval);
|
||||
this._mediaUpdateInterval = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats seconds into a hours:minutes:seconds string.
|
||||
* @param {number} seconds - The time in seconds.
|
||||
* @returns {string} Formatted time string.
|
||||
*/
|
||||
formatSecondsToTime(seconds) {
|
||||
if (!seconds || isNaN(seconds)) return '0:00';
|
||||
|
||||
const totalSeconds = Math.max(0, Math.ceil(seconds));
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60).toString();
|
||||
const secs = (totalSeconds % 60).toString();
|
||||
|
||||
if (hours > 0) {
|
||||
return `${hours}:${minutes.padStart(2, '0')}:${secs.padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
return `${minutes}:${secs.padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates metadata in the UI.
|
||||
* @param {Event} event - The metadata change event.
|
||||
*/
|
||||
onMetadataChange(event) {
|
||||
const metadata = event.target.getMetadata();
|
||||
this.mediaTitle.textContent = metadata.title || '';
|
||||
this.mediaArtist.textContent = metadata.artist || '';
|
||||
}
|
||||
|
||||
onMediaPlayPrev() {
|
||||
if (this._currentMediaController?.supportedKeys.includes('previoustrack')) {
|
||||
this._currentMediaController.prevTrack();
|
||||
}
|
||||
}
|
||||
|
||||
onMediaPlayNext() {
|
||||
if (this._currentMediaController?.supportedKeys.includes('nexttrack')) {
|
||||
this._currentMediaController.nextTrack();
|
||||
}
|
||||
}
|
||||
|
||||
onMediaSeekDrag(event) {
|
||||
this._currentMediaController?.pause();
|
||||
const newTime = (event.target.value / 100) * this._currentDuration;
|
||||
this.mediaCurrentTime.textContent = this.formatSecondsToTime(newTime);
|
||||
}
|
||||
|
||||
onMediaSeekComplete(event) {
|
||||
const newPosition = (event.target.value / 100) * this._currentDuration;
|
||||
if (this._currentMediaController?.supportedKeys.includes('seekto')) {
|
||||
this._currentMediaController.seekTo(newPosition);
|
||||
this._currentMediaController.play();
|
||||
}
|
||||
}
|
||||
|
||||
onMediaFocus() {
|
||||
this._currentMediaController?.focus();
|
||||
}
|
||||
|
||||
onMediaMute() {
|
||||
if (!this.mediaControlBar.hasAttribute('muted')) {
|
||||
this._currentBrowser.mute();
|
||||
this.mediaControlBar.setAttribute('muted', '');
|
||||
} else {
|
||||
this._currentBrowser.unmute();
|
||||
this.mediaControlBar.removeAttribute('muted');
|
||||
}
|
||||
}
|
||||
|
||||
onMediaToggle() {
|
||||
if (this.mediaControlBar.classList.contains('playing')) {
|
||||
this._currentMediaController?.pause();
|
||||
} else {
|
||||
this._currentMediaController?.play();
|
||||
}
|
||||
}
|
||||
|
||||
updateMuteState() {
|
||||
if (!this._currentBrowser) return;
|
||||
if (this._currentBrowser._audioMuted) {
|
||||
this.mediaControlBar.setAttribute('muted', '');
|
||||
} else {
|
||||
this.mediaControlBar.removeAttribute('muted');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
window.gZenMediaController = new ZenMediaController();
|
||||
@@ -211,8 +211,9 @@
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pin.title && pin.editedTitle) {
|
||||
gBrowser._setTabLabel(tab, pin.title);
|
||||
if (pin.title && (pin.editedTitle || tab.hasAttribute('zen-has-static-label'))) {
|
||||
tab.removeAttribute('zen-has-static-label'); // So we can set it again
|
||||
gBrowser._setTabLabel(tab, pin.title, { beforeTabOpen: true });
|
||||
tab.setAttribute('zen-has-static-label', 'true');
|
||||
}
|
||||
}
|
||||
@@ -339,7 +340,7 @@
|
||||
tab.position = tab._tPos;
|
||||
|
||||
for (let otherTab of gBrowser.tabs) {
|
||||
if (otherTab.pinned) {
|
||||
if (otherTab.pinned && otherTab.getAttribute('zen-pin-id') !== tab.getAttribute('zen-pin-id')) {
|
||||
const actualPin = this._pinsCache.find((pin) => pin.uuid === otherTab.getAttribute('zen-pin-id'));
|
||||
if (!actualPin) {
|
||||
continue;
|
||||
@@ -356,6 +357,13 @@
|
||||
}
|
||||
actualPin.position = tab.position;
|
||||
actualPin.isEssential = tab.hasAttribute('zen-essential');
|
||||
|
||||
// There was a bug where the title and hasStaticLabel attribute were not being set
|
||||
// This is a workaround to fix that
|
||||
if (tab.hasAttribute('zen-has-static-label')) {
|
||||
actualPin.editedTitle = true;
|
||||
actualPin.title = tab.label;
|
||||
}
|
||||
await this.savePin(actualPin);
|
||||
}
|
||||
|
||||
@@ -458,6 +466,7 @@
|
||||
|
||||
if (!isClosing) {
|
||||
tab.removeAttribute('zen-pin-id');
|
||||
tab.removeAttribute('zen-essential'); // Just in case
|
||||
|
||||
if (!tab.hasAttribute('zen-workspace-id') && ZenWorkspaces.workspaceEnabled) {
|
||||
const workspace = await ZenWorkspaces.getActiveWorkspace();
|
||||
@@ -600,7 +609,14 @@
|
||||
}
|
||||
|
||||
addToEssentials(tab) {
|
||||
const tabs = tab ? [tab] : TabContextMenu.contextTab.multiselected ? gBrowser.selectedTabs : [TabContextMenu.contextTab];
|
||||
const tabs = tab
|
||||
? // if it's already an array, dont make it [tab]
|
||||
tab?.length
|
||||
? tab
|
||||
: [tab]
|
||||
: TabContextMenu.contextTab.multiselected
|
||||
? gBrowser.selectedTabs
|
||||
: [TabContextMenu.contextTab];
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tab = tabs[i];
|
||||
if (tab.hasAttribute('zen-essential')) {
|
||||
@@ -621,8 +637,8 @@
|
||||
} else {
|
||||
gBrowser.pinTab(tab);
|
||||
}
|
||||
this.onTabIconChanged(tab);
|
||||
this._onTabMove(tab);
|
||||
this.onTabIconChanged(tab);
|
||||
}
|
||||
gZenUIManager.updateTabsToolbar();
|
||||
}
|
||||
@@ -701,72 +717,77 @@
|
||||
}
|
||||
|
||||
moveToAnotherTabContainerIfNecessary(event, movingTabs) {
|
||||
const pinnedTabsTarget =
|
||||
event.target.closest('#vertical-pinned-tabs-container') || event.target.closest('.zen-current-workspace-indicator');
|
||||
const essentialTabsTarget = event.target.closest('#zen-essentials-container');
|
||||
const tabsTarget = event.target.closest('#tabbrowser-arrowscrollbox');
|
||||
try {
|
||||
const pinnedTabsTarget =
|
||||
event.target.closest('#vertical-pinned-tabs-container') || event.target.closest('.zen-current-workspace-indicator');
|
||||
const essentialTabsTarget = event.target.closest('#zen-essentials-container');
|
||||
const tabsTarget = event.target.closest('#tabbrowser-arrowscrollbox');
|
||||
|
||||
let isVertical = this.expandedSidebarMode;
|
||||
let moved = false;
|
||||
for (const draggedTab of movingTabs) {
|
||||
let isRegularTabs = false;
|
||||
// Check for pinned tabs container
|
||||
if (pinnedTabsTarget) {
|
||||
if (!draggedTab.pinned) {
|
||||
gBrowser.pinTab(draggedTab);
|
||||
moved = true;
|
||||
} else if (draggedTab.hasAttribute('zen-essential')) {
|
||||
this.removeEssentials(draggedTab, false);
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
// Check for essentials container
|
||||
else if (essentialTabsTarget) {
|
||||
if (!draggedTab.hasAttribute('zen-essential')) {
|
||||
this.addToEssentials(draggedTab);
|
||||
moved = true;
|
||||
isVertical = false;
|
||||
}
|
||||
}
|
||||
// Check for normal tabs container
|
||||
else if (tabsTarget || event.target.id === 'zen-tabs-wrapper') {
|
||||
if (draggedTab.pinned && !draggedTab.hasAttribute('zen-essential')) {
|
||||
gBrowser.unpinTab(draggedTab);
|
||||
moved = true;
|
||||
isRegularTabs = true;
|
||||
} else if (draggedTab.hasAttribute('zen-essential')) {
|
||||
this.removeEssentials(draggedTab);
|
||||
moved = true;
|
||||
isRegularTabs = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the tab was moved, adjust its position relative to the target tab
|
||||
if (moved) {
|
||||
const targetTab = event.target.closest('.tabbrowser-tab');
|
||||
if (targetTab) {
|
||||
const rect = targetTab.getBoundingClientRect();
|
||||
let newIndex = targetTab._tPos;
|
||||
|
||||
if (isVertical) {
|
||||
const middleY = targetTab.screenY + rect.height / 2;
|
||||
if (!isRegularTabs && event.screenY > middleY) {
|
||||
newIndex++;
|
||||
} else if (isRegularTabs && event.screenY < middleY) {
|
||||
newIndex--;
|
||||
}
|
||||
} else {
|
||||
const middleX = targetTab.screenX + rect.width / 2;
|
||||
if (event.screenX > middleX) {
|
||||
newIndex++;
|
||||
}
|
||||
let isVertical = this.expandedSidebarMode;
|
||||
let moved = false;
|
||||
for (const draggedTab of movingTabs) {
|
||||
let isRegularTabs = false;
|
||||
// Check for pinned tabs container
|
||||
if (pinnedTabsTarget) {
|
||||
if (!draggedTab.pinned) {
|
||||
gBrowser.pinTab(draggedTab);
|
||||
moved = true;
|
||||
} else if (draggedTab.hasAttribute('zen-essential')) {
|
||||
this.removeEssentials(draggedTab, false);
|
||||
moved = true;
|
||||
}
|
||||
}
|
||||
// Check for essentials container
|
||||
else if (essentialTabsTarget) {
|
||||
if (!draggedTab.hasAttribute('zen-essential') && !draggedTab?.group?.hasAttribute('split-view-group')) {
|
||||
this.addToEssentials(draggedTab);
|
||||
moved = true;
|
||||
isVertical = false;
|
||||
}
|
||||
}
|
||||
// Check for normal tabs container
|
||||
else if (tabsTarget || event.target.id === 'zen-tabs-wrapper') {
|
||||
if (draggedTab.pinned && !draggedTab.hasAttribute('zen-essential')) {
|
||||
gBrowser.unpinTab(draggedTab);
|
||||
moved = true;
|
||||
isRegularTabs = true;
|
||||
} else if (draggedTab.hasAttribute('zen-essential')) {
|
||||
this.removeEssentials(draggedTab);
|
||||
moved = true;
|
||||
isRegularTabs = true;
|
||||
}
|
||||
}
|
||||
|
||||
// If the tab was moved, adjust its position relative to the target tab
|
||||
if (moved) {
|
||||
const targetTab = event.target.closest('.tabbrowser-tab');
|
||||
if (targetTab) {
|
||||
const rect = targetTab.getBoundingClientRect();
|
||||
let newIndex = targetTab._tPos;
|
||||
|
||||
if (isVertical) {
|
||||
const middleY = targetTab.screenY + rect.height / 2;
|
||||
if (!isRegularTabs && event.screenY > middleY) {
|
||||
newIndex++;
|
||||
} else if (isRegularTabs && event.screenY < middleY) {
|
||||
newIndex--;
|
||||
}
|
||||
} else {
|
||||
const middleX = targetTab.screenX + rect.width / 2;
|
||||
if (event.screenX > middleX) {
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
gBrowser.moveTabTo(draggedTab, newIndex);
|
||||
}
|
||||
gBrowser.moveTabTo(draggedTab, newIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return moved;
|
||||
return moved;
|
||||
} catch (ex) {
|
||||
console.error('Error moving tabs:', ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async onLocationChange(browser) {
|
||||
@@ -837,6 +858,15 @@
|
||||
// update the label for the same pin across all windows
|
||||
for (const browser of browsers) {
|
||||
const tabs = browser.gBrowser.tabs;
|
||||
// Fix pinned cache for the browser
|
||||
const browserCache = browser.gZenPinnedTabManager?._pinsCache;
|
||||
if (browserCache) {
|
||||
const pin = browserCache.find((pin) => pin.uuid === uuid);
|
||||
if (pin) {
|
||||
pin.title = newTitle;
|
||||
pin.editedTitle = isEdited;
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < tabs.length; i++) {
|
||||
const tabToEdit = tabs[i];
|
||||
if (tabToEdit.getAttribute('zen-pin-id') === uuid && tabToEdit !== tab) {
|
||||
|
||||
@@ -176,7 +176,7 @@
|
||||
<checkbox data-l10n-id="zen-rice-share-include-workspace-themes" id="zen-rice-share-include-workspace-themes" />
|
||||
</vbox>
|
||||
<html:moz-button-group class="panel-footer">
|
||||
<button onclick="gZenThemePicker.riceManager.cancel()" class="footer-button" data-l10n-id="zen-rice-share-cancel" />
|
||||
<button onclick="gZenThemePicker.riceManager.cancel()" class="footer-button" data-l10n-id="zen-general-cancel" />
|
||||
<button onclick="gZenThemePicker.riceManager.submit()" class="footer-button" data-l10n-id="zen-rice-share-save" default="true" slot="primary" id="zen-rice-share-save" disabled="true" />
|
||||
</html:moz-button-group>
|
||||
</vbox>
|
||||
|
||||
@@ -27,7 +27,6 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
|
||||
this.onlySafeWidthAndHeight();
|
||||
|
||||
this.initProgressListener();
|
||||
this.update();
|
||||
this.close(); // avoid caching
|
||||
this.tabBox.prepend(this.sidebarWrapper);
|
||||
this.listenForPrefChanges();
|
||||
@@ -102,7 +101,7 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
|
||||
syncPinnedState() {
|
||||
const sidebar = document.getElementById('zen-sidebar-web-panel');
|
||||
const pinButton = document.getElementById('zen-sidebar-web-panel-pinned');
|
||||
|
||||
|
||||
if (sidebar.hasAttribute('pinned')) {
|
||||
pinButton.setAttribute('pinned', 'true');
|
||||
} else {
|
||||
@@ -182,8 +181,8 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
|
||||
const leftMouseOffset = startLeft - mouseDownEvent.screenX;
|
||||
const moveListener = (mouseMoveEvent) => {
|
||||
window.requestAnimationFrame(() => {
|
||||
let top = mouseMoveEvent.screenY + topMouseOffset;
|
||||
let left = mouseMoveEvent.screenX + leftMouseOffset;
|
||||
let top = mouseMoveEvent.clientY + topMouseOffset;
|
||||
let left = mouseMoveEvent.clientX + leftMouseOffset;
|
||||
|
||||
const wrapperBounds = this.sidebarWrapper.getBoundingClientRect();
|
||||
top = Math.max(0, Math.min(top, wrapperBounds.height - sideBarHeight));
|
||||
@@ -557,16 +556,18 @@ class ZenBrowserManagerSidebar extends ZenDOMOperatedFeature {
|
||||
_getWebPanelIcon(url, element) {
|
||||
let { preferredURI } = Services.uriFixup.getFixupURIInfo(url);
|
||||
element.setAttribute('image', `page-icon:${preferredURI.spec}`);
|
||||
fetch(`https://s2.googleusercontent.com/s2/favicons?domain_url=${preferredURI.spec}`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
let blob = await response.blob();
|
||||
let reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
element.setAttribute('image', reader.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
});
|
||||
if (Services.prefs.getBoolPref('zen.sidebar.use-google-favicons')) {
|
||||
fetch(`https://s2.googleusercontent.com/s2/favicons?domain_url=${preferredURI.spec}`).then(async (response) => {
|
||||
if (response.ok) {
|
||||
let blob = await response.blob();
|
||||
let reader = new FileReader();
|
||||
reader.onload = function () {
|
||||
element.setAttribute('image', reader.result);
|
||||
};
|
||||
reader.readAsDataURL(blob);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_getBrowserById(id) {
|
||||
|
||||
@@ -264,7 +264,7 @@
|
||||
(tab.pinned && !ignoreTimestamp) ||
|
||||
tab.selected ||
|
||||
(tab.multiselected && !ignoreTimestamp) ||
|
||||
tab.hasAttribute('busy') ||
|
||||
(tab.hasAttribute('busy') && !ignoreTimestamp) ||
|
||||
tab.hasAttribute('pending') ||
|
||||
!tab.linkedPanel ||
|
||||
tab.splitView ||
|
||||
@@ -274,7 +274,8 @@
|
||||
(tab.pictureinpicture && !ignoreTimestamp) ||
|
||||
(tab.soundPlaying && !ignoreTimestamp) ||
|
||||
(tab.zenIgnoreUnload && !ignoreTimestamp) ||
|
||||
excludedUrls.some((url) => url.test(tab.linkedBrowser?.currentURI.spec))
|
||||
(excludedUrls.some((url) => url.test(tab.linkedBrowser?.currentURI.spec)) &&
|
||||
tab.linkedBrowser?.currentURI.spec !== 'about:blank')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class SplitNode extends SplitLeafNode {
|
||||
|
||||
addChild(child) {
|
||||
child.parent = this;
|
||||
this._children.push(child);
|
||||
this._children.unshift(child);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,8 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
_edgeHoverSize;
|
||||
minResizeWidth;
|
||||
|
||||
_lastOpenedTab = null;
|
||||
|
||||
MAX_TABS = 4;
|
||||
|
||||
init() {
|
||||
@@ -81,9 +83,17 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
ChromeUtils.defineLazyGetter(this, 'dropZone', () => document.getElementById('zen-splitview-dropzone'));
|
||||
|
||||
window.addEventListener('TabClose', this.handleTabClose.bind(this));
|
||||
window.addEventListener('TabSelect', this.onTabSelect.bind(this));
|
||||
this.initializeContextMenu();
|
||||
this.insertPageActionButton();
|
||||
this.insertIntoContextMenu();
|
||||
|
||||
// Add drag over listener to the browser view
|
||||
if (Services.prefs.getBoolPref('zen.splitView.enable-tab-drop')) {
|
||||
const tabBox = document.getElementById('tabbrowser-tabbox');
|
||||
tabBox.addEventListener('dragover', this.onBrowserDragOverToSplit.bind(this));
|
||||
this.onBrowserDragEndToSplit = this.onBrowserDragEndToSplit.bind(this);
|
||||
}
|
||||
}
|
||||
|
||||
insertIntoContextMenu() {
|
||||
@@ -102,6 +112,9 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
*/
|
||||
handleTabClose(event) {
|
||||
const tab = event.target;
|
||||
if (tab === this._lastOpenedTab) {
|
||||
this._lastOpenedTab = null;
|
||||
}
|
||||
const groupIndex = this._data.findIndex((group) => group.tabs.includes(tab));
|
||||
if (groupIndex < 0) {
|
||||
return;
|
||||
@@ -109,6 +122,18 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
this.removeTabFromGroup(tab, groupIndex, event.forUnsplit);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Event} event - The event that triggered the tab select.
|
||||
* @description Handles the tab select event.
|
||||
* @returns {void}
|
||||
*/
|
||||
onTabSelect(event) {
|
||||
const previousTab = event.detail.previousTab;
|
||||
if (previousTab && !previousTab.hasAttribute('zen-empty-tab')) {
|
||||
this._lastOpenedTab = previousTab;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a tab from a group.
|
||||
*
|
||||
@@ -142,6 +167,188 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
}
|
||||
}
|
||||
|
||||
onBrowserDragOverToSplit(event) {
|
||||
var dt = event.dataTransfer;
|
||||
var draggedTab;
|
||||
if (dt.mozTypesAt(0)[0] == TAB_DROP_TYPE) {
|
||||
// tab copy or move
|
||||
draggedTab = dt.mozGetDataAt(TAB_DROP_TYPE, 0);
|
||||
// not our drop then
|
||||
if (!draggedTab || gBrowser.selectedTab.hasAttribute('zen-empty-tab')) {
|
||||
return;
|
||||
}
|
||||
draggedTab.container._finishMoveTogetherSelectedTabs(draggedTab);
|
||||
}
|
||||
if (
|
||||
!draggedTab ||
|
||||
this._canDrop ||
|
||||
this._hasAnimated ||
|
||||
this.fakeBrowser ||
|
||||
!this._lastOpenedTab ||
|
||||
(this._lastOpenedTab &&
|
||||
(this._lastOpenedTab.getAttribute('zen-workspace-id') !== draggedTab.getAttribute('zen-workspace-id') ||
|
||||
this._lastOpenedTab.hasAttribute('zen-essential')))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (draggedTab.splitView) {
|
||||
return;
|
||||
}
|
||||
const currentView = this._data[this._lastOpenedTab.splitViewValue];
|
||||
if (currentView?.tabs.length >= this.MAX_TABS) {
|
||||
return;
|
||||
}
|
||||
const panelsRect = gBrowser.tabbox.getBoundingClientRect();
|
||||
const panelsWidth = panelsRect.width;
|
||||
if (
|
||||
event.clientX > panelsRect.left + panelsWidth - 10 ||
|
||||
event.clientX < panelsRect.left + 10 ||
|
||||
event.clientY < panelsRect.top + 10 ||
|
||||
event.clientY > panelsRect.bottom - 10
|
||||
) {
|
||||
return;
|
||||
}
|
||||
// first quarter or last quarter of the screen, but not the middle
|
||||
if (!(event.clientX < panelsRect.left + panelsWidth / 4 || event.clientX > panelsRect.left + (panelsWidth / 4) * 3)) {
|
||||
return;
|
||||
}
|
||||
dt.mozCursor = 'default';
|
||||
const oldTab = this._lastOpenedTab;
|
||||
this._canDrop = true;
|
||||
{
|
||||
this._draggingTab = draggedTab;
|
||||
gBrowser.selectedTab = oldTab;
|
||||
this._hasAnimated = true;
|
||||
for (const tab of gBrowser.tabs) {
|
||||
tab.style.removeProperty('transform');
|
||||
}
|
||||
const panelsWidth = gBrowser.tabbox.getBoundingClientRect().width;
|
||||
const halfWidth = panelsWidth / 2;
|
||||
const side = event.clientX > halfWidth ? 'right' : 'left';
|
||||
this.fakeBrowser = document.createXULElement('vbox');
|
||||
this.fakeBrowser.addEventListener('dragleave', this.onBrowserDragEndToSplit);
|
||||
window.addEventListener('dragend', this.onBrowserDragEndToSplit, { once: true });
|
||||
const padding = Services.prefs.getIntPref('zen.theme.content-element-separation', 0);
|
||||
this.fakeBrowser.setAttribute('flex', '1');
|
||||
this.fakeBrowser.id = 'zen-split-view-fake-browser';
|
||||
gBrowser.tabbox.appendChild(this.fakeBrowser);
|
||||
this.fakeBrowser.style.setProperty('--zen-split-view-fake-icon', `url(${draggedTab.getAttribute('image')})`);
|
||||
draggedTab._visuallySelected = true;
|
||||
this.fakeBrowser.setAttribute('side', side);
|
||||
this._finishAllAnimatingPromise = Promise.all([
|
||||
gZenUIManager.motion.animate(
|
||||
gBrowser.tabbox,
|
||||
side === 'left'
|
||||
? {
|
||||
paddingLeft: [0, `${halfWidth}px`],
|
||||
paddingRight: 0,
|
||||
}
|
||||
: {
|
||||
paddingRight: [0, `${halfWidth}px`],
|
||||
paddingLeft: 0,
|
||||
},
|
||||
{
|
||||
duration: 0.08,
|
||||
easing: 'ease-out',
|
||||
}
|
||||
),
|
||||
gZenUIManager.motion.animate(
|
||||
this.fakeBrowser,
|
||||
{
|
||||
width: [0, `${halfWidth - padding}px`],
|
||||
...(side === 'left'
|
||||
? {
|
||||
marginLeft: [0, `${-halfWidth}px`],
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
duration: 0.08,
|
||||
easing: 'ease-out',
|
||||
}
|
||||
),
|
||||
]);
|
||||
if (this._finishAllAnimatingPromise) {
|
||||
this._finishAllAnimatingPromise.then(() => {
|
||||
this._canDrop = true;
|
||||
draggedTab._visuallySelected = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onBrowserDragEndToSplit(event) {
|
||||
if (!this._canDrop) {
|
||||
return;
|
||||
}
|
||||
const panelsRect = gBrowser.tabbox.getBoundingClientRect();
|
||||
const fakeBrowserRect = this.fakeBrowser && this.fakeBrowser.getBoundingClientRect();
|
||||
if (
|
||||
(event.target.closest('#tabbrowser-tabbox') && event.target != this.fakeBrowser) ||
|
||||
(fakeBrowserRect &&
|
||||
event.clientX > fakeBrowserRect.left &&
|
||||
event.clientX < fakeBrowserRect.left + fakeBrowserRect.width &&
|
||||
event.clientY > fakeBrowserRect.top &&
|
||||
event.clientY < fakeBrowserRect.top + fakeBrowserRect.height) ||
|
||||
(event.screenX === 0 && event.screenY === 0) // It's equivalent to 0 if the event has been dropped
|
||||
) {
|
||||
return;
|
||||
}
|
||||
if (!this._hasAnimated || !this.fakeBrowser) {
|
||||
return;
|
||||
}
|
||||
const panelsWidth = panelsRect.width;
|
||||
const halfWidth = panelsWidth / 2;
|
||||
const padding = Services.prefs.getIntPref('zen.theme.content-element-separation', 0);
|
||||
if (!this.fakeBrowser) {
|
||||
return;
|
||||
}
|
||||
this.fakeBrowser.classList.add('fade-out');
|
||||
const side = this.fakeBrowser.getAttribute('side');
|
||||
if (this._draggingTab) this._draggingTab.setAttribute('zen-has-splitted', 'true');
|
||||
this._lastOpenedTab = gBrowser.selectedTab;
|
||||
this._draggingTab = null;
|
||||
try {
|
||||
Promise.all([
|
||||
gZenUIManager.motion.animate(
|
||||
gBrowser.tabbox,
|
||||
side === 'left'
|
||||
? {
|
||||
paddingLeft: [`${halfWidth}px`, 0],
|
||||
}
|
||||
: {
|
||||
paddingRight: [`${halfWidth}px`, 0],
|
||||
},
|
||||
{
|
||||
duration: 0.1,
|
||||
easing: 'ease-out',
|
||||
}
|
||||
),
|
||||
gZenUIManager.motion.animate(
|
||||
this.fakeBrowser,
|
||||
{
|
||||
width: [`${halfWidth - padding * 2}px`, 0],
|
||||
...(side === 'left'
|
||||
? {
|
||||
marginLeft: [`${-halfWidth}px`, 0],
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
{
|
||||
duration: 0.1,
|
||||
easing: 'ease-out',
|
||||
}
|
||||
),
|
||||
]).then(() => {
|
||||
this._canDrop = false;
|
||||
this._maybeRemoveFakeBrowser();
|
||||
});
|
||||
} catch (e) {
|
||||
this._canDrop = false;
|
||||
this._maybeRemoveFakeBrowser();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a SplitNode from its tree and the view
|
||||
* @param {SplitNode} toRemove
|
||||
@@ -204,14 +411,13 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
|
||||
afterRearangeAction() {
|
||||
document.getElementById('zenSplitViewModifier').hidePopup();
|
||||
ConfirmationHint.show(document.getElementById('zen-split-views-box'), 'zen-split-view-modifier-enabled-toast', {
|
||||
gZenUIManager.showToast('zen-split-view-modifier-enabled-toast', {
|
||||
descriptionId: 'zen-split-view-modifier-enabled-toast-description',
|
||||
showDescription: true,
|
||||
});
|
||||
}
|
||||
|
||||
afterRearangeRemove() {
|
||||
ConfirmationHint.show(document.getElementById('zen-split-views-box'), 'zen-split-view-modifier-disabled-toast');
|
||||
gZenUIManager.showToast('zen-split-view-modifier-disabled-toast');
|
||||
}
|
||||
|
||||
toggleWrapperDisplay(value) {
|
||||
@@ -289,7 +495,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
let isSplitHeaderDrag = false;
|
||||
|
||||
const container = event.target.closest('.browserSidebarContainer[zen-split]');
|
||||
if (container && event.offsetY < 20) {
|
||||
if (container && event.target.closest('.zen-tab-rearrange-button')) {
|
||||
// Split tab header drag case
|
||||
const containerRect = container.getBoundingClientRect();
|
||||
const clickX = event.clientX - containerRect.left;
|
||||
@@ -549,6 +755,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
*/
|
||||
resetTabState(tab, forUnsplit) {
|
||||
tab.splitView = false;
|
||||
delete tab.splitViewValue;
|
||||
tab.removeAttribute('split-view');
|
||||
tab.linkedBrowser.zenModeActive = false;
|
||||
const container = tab.linkedBrowser.closest('.browserSidebarContainer');
|
||||
@@ -679,7 +886,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
return false;
|
||||
}
|
||||
for (const tab of window.gBrowser.selectedTabs) {
|
||||
if (tab.splitView) {
|
||||
if (tab.splitView || tab.hasAttribute('zen-empty-tab') || tab.hasAttribute('zen-essential')) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -699,6 +906,27 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
this.updateSplitView(tab);
|
||||
tab.linkedBrowser.docShellIsActive = true;
|
||||
}
|
||||
this._maybeRemoveFakeBrowser();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Tab} tab
|
||||
*/
|
||||
_moveTabsToContainer(tabs, relativeTab) {
|
||||
const relativeTabIsPinned = relativeTab.pinned;
|
||||
const relativeTabIsEssential = relativeTab.hasAttribute('zen-essential');
|
||||
|
||||
if (relativeTabIsEssential) {
|
||||
gZenPinnedTabManager.addToEssentials(tabs);
|
||||
} else {
|
||||
for (const tab of tabs) {
|
||||
if (relativeTabIsPinned) {
|
||||
gBrowser.pinTab(tab);
|
||||
} else {
|
||||
gBrowser.unpinTab(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -707,14 +935,13 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
* @param {Tab[]} tabs - The tabs to split.
|
||||
* @param {string} gridType - The type of grid layout.
|
||||
*/
|
||||
splitTabs(tabs, gridType) {
|
||||
const firstisPinned = tabs[0].pinned;
|
||||
tabs = tabs.filter(
|
||||
(t) => t.pinned === firstisPinned && !t.hidden && !t.hasAttribute('zen-empty-tab') && !t.hasAttribute('zen-essential')
|
||||
);
|
||||
splitTabs(tabs, gridType, initialIndex = 0) {
|
||||
// TODO: Add support for splitting essential tabs
|
||||
tabs = tabs.filter((t) => !t.hidden && !t.hasAttribute('zen-empty-tab') && !t.hasAttribute('zen-essential'));
|
||||
if (tabs.length < 2 || tabs.length > this.MAX_TABS) {
|
||||
return;
|
||||
}
|
||||
this._moveTabsToContainer(tabs, tabs[initialIndex]);
|
||||
|
||||
const existingSplitTab = tabs.find((tab) => tab.splitView);
|
||||
if (existingSplitTab) {
|
||||
@@ -861,9 +1088,10 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
applyGridToTabs(tabs) {
|
||||
tabs.forEach((tab, index) => {
|
||||
tab.splitView = true;
|
||||
tab.splitViewValue = this.currentView;
|
||||
tab.setAttribute('split-view', 'true');
|
||||
const container = tab.linkedBrowser.closest('.browserContainer');
|
||||
if (!container.querySelector('.zen-view-splitter-header')) {
|
||||
const container = tab.linkedBrowser?.closest('.browserSidebarContainer');
|
||||
if (!container?.querySelector('.zen-tab-rearrange-button')) {
|
||||
// insert a header into the container
|
||||
const header = this._createHeader(container);
|
||||
container.insertBefore(header, container.firstChild);
|
||||
@@ -879,6 +1107,8 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
* @private
|
||||
*/
|
||||
_createHeader(container) {
|
||||
const headerContainer = document.createElement('div');
|
||||
headerContainer.classList.add('zen-view-splitter-header-container');
|
||||
const header = document.createElement('div');
|
||||
header.classList.add('zen-view-splitter-header');
|
||||
const removeButton = document.createXULElement('toolbarbutton');
|
||||
@@ -886,12 +1116,16 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
removeButton.addEventListener('click', () => {
|
||||
this.removeTabFromSplit(container);
|
||||
});
|
||||
const rearrangeButton = document.createXULElement('toolbarbutton');
|
||||
rearrangeButton.classList.add('zen-tab-rearrange-button');
|
||||
header.appendChild(rearrangeButton);
|
||||
header.appendChild(removeButton);
|
||||
return header;
|
||||
headerContainer.appendChild(header);
|
||||
return headerContainer;
|
||||
}
|
||||
|
||||
_removeHeader(container) {
|
||||
const header = container.querySelector('.zen-view-splitter-header');
|
||||
const header = container.querySelector('.zen-view-splitter-header-container');
|
||||
if (header) {
|
||||
header.remove();
|
||||
}
|
||||
@@ -1029,7 +1263,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
this.tabBrowserPanel.setAttribute('zen-split-resizing', true);
|
||||
const isVertical = event.target.getAttribute('orient') === 'vertical';
|
||||
const dimension = isVertical ? 'width' : 'height';
|
||||
const clientAxis = isVertical ? 'screenX' : 'screenY';
|
||||
const clientAxis = isVertical ? 'clientX' : 'clientY';
|
||||
|
||||
const gridIdx = parseInt(event.target.getAttribute('gridIdx'));
|
||||
const startPosition = event[clientAxis];
|
||||
@@ -1245,7 +1479,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
return;
|
||||
}
|
||||
const tabs = gBrowser.visibleTabs;
|
||||
if (tabs.length < 2) {
|
||||
if (tabs.length < 2 || this.currentView >= 0) {
|
||||
return;
|
||||
}
|
||||
let nextTabIndex = tabs.indexOf(gBrowser.selectedTab) + 1;
|
||||
@@ -1275,6 +1509,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
const tab = gBrowser.getTabForBrowser(browser);
|
||||
if (tab) {
|
||||
const groupIndex = this._data.findIndex((group) => group.tabs.includes(tab));
|
||||
this.deactivateCurrentSplitView();
|
||||
if (groupIndex >= 0) {
|
||||
this.removeTabFromGroup(tab, groupIndex, true);
|
||||
}
|
||||
@@ -1282,6 +1517,20 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
}
|
||||
};
|
||||
|
||||
_maybeRemoveFakeBrowser(select = true) {
|
||||
gBrowser.tabbox.removeAttribute('style');
|
||||
if (this.fakeBrowser) {
|
||||
delete this._hasAnimated;
|
||||
this.fakeBrowser.remove();
|
||||
this.fakeBrowser = null;
|
||||
if (this._draggingTab) this._draggingTab._visuallySelected = false;
|
||||
if (select) {
|
||||
gBrowser.selectedTab = this._draggingTab;
|
||||
this._draggingTab = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description moves the tab to the split view if dragged on a browser
|
||||
* @param event - The event
|
||||
@@ -1289,18 +1538,41 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
* @returns {boolean} true if the tab was moved to the split view
|
||||
*/
|
||||
moveTabToSplitView(event, draggedTab) {
|
||||
const dropTarget = document.elementFromPoint(event.clientX, event.clientY);
|
||||
const canDrop = this._canDrop;
|
||||
this._canDrop = false;
|
||||
|
||||
if (!canDrop || !this.fakeBrowser) {
|
||||
this._maybeRemoveFakeBrowser(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
const dropSide = this.fakeBrowser?.getAttribute('side');
|
||||
const containerRect = this.fakeBrowser.getBoundingClientRect();
|
||||
const padding = Services.prefs.getIntPref('zen.theme.content-element-separation', 0);
|
||||
const dropTarget = document.elementFromPoint(
|
||||
dropSide === 'left' ? containerRect.left + containerRect.width + padding + 5 : containerRect.left - padding - 5,
|
||||
event.clientY
|
||||
);
|
||||
const browser = dropTarget?.closest('browser');
|
||||
|
||||
if (!browser) {
|
||||
this._maybeRemoveFakeBrowser(false);
|
||||
return false;
|
||||
}
|
||||
|
||||
gBrowser.selectedTab = this._draggingTab;
|
||||
this._draggingTab = null;
|
||||
const browserContainer = draggedTab.linkedBrowser?.closest('.browserSidebarContainer');
|
||||
if (browserContainer) {
|
||||
browserContainer.style.opacity = '0';
|
||||
}
|
||||
|
||||
const droppedOnTab = gBrowser.getTabForBrowser(browser);
|
||||
if (droppedOnTab && droppedOnTab !== draggedTab) {
|
||||
// Calculate which side of the target browser the drop occurred
|
||||
const browserRect = browser.getBoundingClientRect();
|
||||
const hoverSide = this.calculateHoverSide(event.clientX, event.clientY, browserRect);
|
||||
// const browserRect = browser.getBoundingClientRect();
|
||||
// const hoverSide = this.calculateHoverSide(event.clientX, event.clientY, browserRect);
|
||||
const hoverSide = dropSide;
|
||||
|
||||
if (droppedOnTab.splitView) {
|
||||
// Add to existing split view
|
||||
@@ -1311,6 +1583,7 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
// First move the tab to the split view group
|
||||
let splitGroup = droppedOnTab.group;
|
||||
if (splitGroup && (!draggedTab.group || draggedTab.group !== splitGroup)) {
|
||||
this._moveTabsToContainer([draggedTab], droppedOnTab);
|
||||
gBrowser.moveTabToGroup(draggedTab, splitGroup);
|
||||
}
|
||||
|
||||
@@ -1336,29 +1609,80 @@ class ZenViewSplitter extends ZenDOMOperatedFeature {
|
||||
}
|
||||
} else {
|
||||
// Create new split view with layout based on drop position
|
||||
let gridType;
|
||||
switch (hoverSide) {
|
||||
case 'left':
|
||||
case 'right':
|
||||
gridType = 'vsep';
|
||||
break;
|
||||
case 'top':
|
||||
case 'bottom':
|
||||
gridType = 'hsep';
|
||||
break;
|
||||
default:
|
||||
gridType = 'grid';
|
||||
}
|
||||
let gridType = 'vsep';
|
||||
//switch (hoverSide) {
|
||||
// case 'left':
|
||||
// case 'right':
|
||||
// gridType = 'vsep';
|
||||
// break;
|
||||
// case 'top':
|
||||
// case 'bottom':
|
||||
// gridType = 'hsep';
|
||||
// break;
|
||||
// default:
|
||||
// gridType = 'grid';
|
||||
//}
|
||||
|
||||
// Put tabs in correct order based on drop side
|
||||
const tabs = ['left', 'top'].includes(hoverSide) ? [draggedTab, droppedOnTab] : [droppedOnTab, draggedTab];
|
||||
|
||||
this.splitTabs(tabs, gridType);
|
||||
// Put tabs always as if it was dropped from the left
|
||||
this.splitTabs(dropSide == 'left' ? [draggedTab, droppedOnTab] : [droppedOnTab, draggedTab], gridType, 1);
|
||||
}
|
||||
}
|
||||
if (this._finishAllAnimatingPromise) {
|
||||
this._finishAllAnimatingPromise.then(() => {
|
||||
this._maybeRemoveFakeBrowser(false);
|
||||
});
|
||||
}
|
||||
|
||||
if (browserContainer) {
|
||||
gZenUIManager.motion
|
||||
.animate(
|
||||
browserContainer,
|
||||
{
|
||||
scale: [0.97, 1],
|
||||
opacity: [0, 1],
|
||||
},
|
||||
{
|
||||
type: 'spring',
|
||||
bounce: 0.4,
|
||||
duration: 0.2,
|
||||
delay: 0.1,
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
this._maybeRemoveFakeBrowser(false);
|
||||
this._finishAllAnimatingPromise = null;
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
handleTabDrop(event, urls, replace, inBackground) {
|
||||
if (replace || urls.length !== 1) {
|
||||
return false;
|
||||
}
|
||||
const url = urls[0];
|
||||
if (!url.startsWith('panel-')) {
|
||||
return false;
|
||||
}
|
||||
const browserContainer = document.getElementById(url);
|
||||
const browser = browserContainer?.querySelector('browser');
|
||||
if (!browser) {
|
||||
return false;
|
||||
}
|
||||
const tab = gBrowser.getTabForBrowser(browser);
|
||||
if (!tab) {
|
||||
return false;
|
||||
}
|
||||
if (tab.splitView) {
|
||||
// Unsplit the tab and exit from the drag view
|
||||
this.dropZone?.removeAttribute('enabled');
|
||||
this.disableTabRearrangeView(event);
|
||||
this.removeTabFromSplit(browserContainer);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a tab group for split view tabs
|
||||
* @param {Array} tabs Initial tabs to add to the group if creating new
|
||||
|
||||
@@ -226,6 +226,7 @@
|
||||
const sidebar = document.getElementById('zen-welcome-page-sidebar');
|
||||
sidebar.style.width = '100%';
|
||||
sidebar.appendChild(heart);
|
||||
sidebar.setAttribute('animate-heart', 'true');
|
||||
await animate(
|
||||
'#zen-welcome-heart',
|
||||
{ opacity: [0, 1, 1, 1, 0], scale: [0.5, 1, 1.2, 1, 1.2] },
|
||||
@@ -334,17 +335,17 @@
|
||||
<html:div></html:div>
|
||||
</hbox>
|
||||
<html:div id="zen-welcome-initial-essentials-browser-sidebar-essentials">
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://web.whatsapp.com" style="--zen-tab-icon: url('https://web.whatsapp.com/favicon.ico');">
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://web.whatsapp.com" style="--zen-tab-icon: url('https://web.whatsapp.com/favicon.ico');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
</html:div>
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://discord.com" style="--zen-tab-icon: url('http://www.google.com/s2/favicons?domain=discord.com');">
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://discord.com" style="--zen-tab-icon: url('https://www.google.com/s2/favicons?domain=discord.com');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
</html:div>
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://reddit.com" style="--zen-tab-icon: url('https://www.redditstatic.com/desktop2x/img/favicon/favicon-96x96.png');">
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://trello.com" style="--zen-tab-icon: url('https://trello.com/favicon.ico');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
@@ -354,7 +355,7 @@
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
</html:div>
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://google.com" style="--zen-tab-icon: url('https://www.google.com/s2/favicons?domain=google.com');">
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://github.com" style="--zen-tab-icon: url('https://github.githubassets.com/favicons/favicon-dark.png');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
@@ -364,17 +365,17 @@
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
</html:div>
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://notion.com" style="--zen-tab-icon: url('https://www.notion.so/front-static/favicon.ico');">
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://notion.com" style="--zen-tab-icon: url('https://www.notion.so/front-static/favicon.ico');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
</html:div>
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://instagram.com" style="--zen-tab-icon: url('https://www.instagram.com/static/images/ico/favicon-192.png/68d99ba29cc8.png');">
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://calendar.google.com" style="--zen-tab-icon: url('https://calendar.google.com/googlecalendar/images/favicons_2020q4/calendar_6.ico');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
</html:div>
|
||||
<html:div class="tabbrowser-tab" fadein="" visuallyselected="" data-url="https://element.io" style="--zen-tab-icon: url('http://www.google.com/s2/favicons?domain=element.io');">
|
||||
<html:div class="tabbrowser-tab" fadein="" data-url="https://youtube.com" style="--zen-tab-icon: url('https://www.youtube.com/favicon.ico');">
|
||||
<stack class="tab-stack">
|
||||
<html:div class="tab-background"></html:div>
|
||||
</stack>
|
||||
@@ -453,9 +454,7 @@
|
||||
async fadeOut() {
|
||||
gZenThemePicker.panel.removeAttribute('noautohide');
|
||||
gZenThemePicker.panel.removeAttribute('consumeoutsideclicks');
|
||||
if (AppConstants.platform != 'macosx') {
|
||||
await animate(gZenThemePicker.panel, { opacity: [1, 0] });
|
||||
}
|
||||
await animate(gZenThemePicker.panel, { opacity: [1, 0] });
|
||||
gZenThemePicker.panel.hidePopup();
|
||||
gZenThemePicker.panel.removeAttribute('style');
|
||||
},
|
||||
|
||||
@@ -36,6 +36,10 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
this._resolveInitialized = resolve;
|
||||
});
|
||||
|
||||
promiseEmptyTabInitialized = new Promise((resolve) => {
|
||||
this._resolveEmptyTabInitialized = resolve;
|
||||
});
|
||||
|
||||
workspaceIndicatorXUL = `
|
||||
<hbox class="zen-current-workspace-indicator-icon"></hbox>
|
||||
<hbox class="zen-current-workspace-indicator-name"></hbox>
|
||||
@@ -111,12 +115,13 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
);
|
||||
}
|
||||
|
||||
selectEmptyTab() {
|
||||
selectEmptyTab(newTabTarget = null) {
|
||||
if (this._emptyTab && gZenVerticalTabsManager._canReplaceNewTab) {
|
||||
gBrowser.selectedTab = this._emptyTab;
|
||||
return this._emptyTab;
|
||||
}
|
||||
let tab = gZenUIManager.openAndChangeToTab(Services.prefs.getStringPref('browser.startup.homepage'));
|
||||
const newTabUrl = newTabTarget || Services.prefs.getStringPref('browser.startup.homepage');
|
||||
let tab = gZenUIManager.openAndChangeToTab(newTabUrl);
|
||||
if (window.uuid) {
|
||||
tab.setAttribute('zen-workspace-id', this.activeWorkspace);
|
||||
}
|
||||
@@ -144,6 +149,9 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
_initializeEmptyTab() {
|
||||
if (Services.prefs.getBoolPref('zen.workspaces.disable_empty_state_for_testing', false)) {
|
||||
return;
|
||||
}
|
||||
this._emptyTab = gBrowser.addTrustedTab('about:blank', { inBackground: true, userContextId: 0, _forZenEmptyTab: true });
|
||||
}
|
||||
|
||||
@@ -602,15 +610,22 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
} catch (e) {
|
||||
console.error('ZenWorkspaces: Error initializing theme picker', e);
|
||||
}
|
||||
this._selectStartPage();
|
||||
await this._selectStartPage();
|
||||
this._fixTabPositions();
|
||||
this._resolveInitialized();
|
||||
this._clearAnyZombieTabs(); // Dont call with await
|
||||
}
|
||||
}
|
||||
|
||||
_selectStartPage() {
|
||||
async _selectStartPage() {
|
||||
if (Services.prefs.getBoolPref('zen.workspaces.disable_empty_state_for_testing', false)) {
|
||||
return;
|
||||
}
|
||||
const currentTab = gBrowser.selectedTab;
|
||||
let showed = false;
|
||||
await this.promiseEmptyTabInitialized;
|
||||
this._resolveEmptyTabInitialized = null;
|
||||
this.promiseEmptyTabInitialized = null;
|
||||
if (currentTab.pinned) {
|
||||
this.selectEmptyTab();
|
||||
try {
|
||||
@@ -625,7 +640,8 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
if (
|
||||
(currentTab.isEmpty &&
|
||||
(currentTab.getAttribute('image') === gPageIcons[currentTabURL] || !currentTab.hasAttribute('image'))) ||
|
||||
currentTab.hasAttribute('zen-empty-tab')
|
||||
currentTab.hasAttribute('zen-empty-tab') ||
|
||||
(currentTab._possibleEmptyTab && currentTab.isEmpty)
|
||||
) {
|
||||
this.selectEmptyTab();
|
||||
this._removedByStartupPage = true;
|
||||
@@ -652,6 +668,26 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
return !window.toolbar.visible || Services.prefs.getBoolPref('browser.tabs.closeWindowWithLastTab');
|
||||
}
|
||||
|
||||
async _clearAnyZombieTabs() {
|
||||
const tabs = this.allStoredTabs;
|
||||
const workspaces = await this._workspaces();
|
||||
for (let tab of tabs) {
|
||||
const workspaceID = tab.getAttribute('zen-workspace-id');
|
||||
if (
|
||||
workspaceID &&
|
||||
!tab.hasAttribute('zen-essential') &&
|
||||
!workspaces.workspaces.find((workspace) => workspace.uuid === workspaceID)
|
||||
) {
|
||||
// Remove any tabs where their workspace doesn't exist anymore
|
||||
gBrowser.removeTab(tab, {
|
||||
animate: false,
|
||||
skipSessionStore: true,
|
||||
closeWindowWithLastTab: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleTabBeforeClose(tab) {
|
||||
if (!this.workspaceEnabled || this.__contextIsDelete || this._removedByStartupPage) {
|
||||
return null;
|
||||
@@ -1382,15 +1418,16 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
_deleteAllTabsInWorkspace(workspaceID) {
|
||||
for (let tab of gBrowser.tabs) {
|
||||
if (tab.getAttribute('zen-workspace-id') === workspaceID) {
|
||||
gBrowser.removeTab(tab, {
|
||||
animate: true,
|
||||
skipSessionStore: true,
|
||||
closeWindowWithLastTab: false,
|
||||
});
|
||||
gBrowser.removeTabs(
|
||||
Array.from(gBrowser.tabs).filter(
|
||||
(tab) => tab.getAttribute('zen-workspace-id') === workspaceID && !tab.hasAttribute('zen-empty-tab')
|
||||
),
|
||||
{
|
||||
animate: false,
|
||||
skipSessionStore: true,
|
||||
closeWindowWithLastTab: false,
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
moveTabToWorkspace(tab, workspaceID) {
|
||||
@@ -1508,18 +1545,19 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
const previousWorkspace = await this.getActiveWorkspace();
|
||||
alwaysChange = alwaysChange || onInit;
|
||||
|
||||
this.activeWorkspace = window.uuid;
|
||||
if (previousWorkspace && previousWorkspace.uuid === window.uuid && !alwaysChange) {
|
||||
this._cancelSwipeAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
this.activeWorkspace = window.uuid;
|
||||
const containerId = window.containerTabId?.toString();
|
||||
const workspaces = await this._workspaces();
|
||||
|
||||
// Refresh tab cache
|
||||
gBrowser.verticalPinnedTabsContainer = this.pinnedTabsContainer;
|
||||
gBrowser.tabContainer.verticalPinnedTabsContainer = this.pinnedTabsContainer;
|
||||
gBrowser.verticalPinnedTabsContainer = this.pinnedTabsContainer || gBrowser.verticalPinnedTabsContainer;
|
||||
gBrowser.tabContainer.verticalPinnedTabsContainer =
|
||||
this.pinnedTabsContainer || gBrowser.tabContainer.verticalPinnedTabsContainer;
|
||||
// Move empty tab to the new workspace
|
||||
this._moveEmptyTabToWorkspace(window.uuid);
|
||||
|
||||
@@ -1967,6 +2005,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
}
|
||||
|
||||
async onLocationChange(browser) {
|
||||
gZenCompactModeManager.sidebar.toggleAttribute('zen-has-empty-tab', gBrowser.selectedTab.hasAttribute('zen-empty-tab'));
|
||||
if (!this.workspaceEnabled || this._inChangingWorkspace || this._isClosingWindow) {
|
||||
return;
|
||||
}
|
||||
@@ -1993,6 +2032,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
|
||||
// Switch workspace if needed
|
||||
if (workspaceID && workspaceID !== activeWorkspace.uuid && parent.ZenWorkspaces._hasInitializedTabsStrip) {
|
||||
const workspaces = await parent.ZenWorkspaces._workspaces();
|
||||
await parent.ZenWorkspaces.changeWorkspace({ uuid: workspaceID });
|
||||
}
|
||||
}
|
||||
@@ -2231,7 +2271,7 @@ var ZenWorkspaces = new (class extends ZenMultiWindowFeature {
|
||||
const activeWorkspace = this.getActiveWorkspaceFromCache();
|
||||
const activeWorkspaceUserContextId = activeWorkspace?.containerTabId;
|
||||
|
||||
if (typeof userContextId !== 'undefined' && userContextId !== activeWorkspaceUserContextId) {
|
||||
if (fromExternal !== true && typeof userContextId !== 'undefined' && userContextId !== activeWorkspaceUserContextId) {
|
||||
return [userContextId, false, undefined];
|
||||
}
|
||||
return [activeWorkspaceUserContextId, true, undefined];
|
||||
|
||||
@@ -7,6 +7,7 @@ var ZenWorkspacesStorage = {
|
||||
Weave: 'resource://services-sync/main.sys.mjs',
|
||||
});
|
||||
|
||||
if (!window.ZenWorkspaces) return;
|
||||
await this._ensureTable();
|
||||
await ZenWorkspaceBookmarksStorage.init();
|
||||
},
|
||||
|
||||
@@ -12,6 +12,9 @@ export class ZenGlanceChild extends JSWindowActorChild {
|
||||
case 'DOMContentLoaded':
|
||||
await this.initiateGlance();
|
||||
break;
|
||||
case 'keydown':
|
||||
this.onKeyDown(event);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
@@ -120,4 +123,13 @@ export class ZenGlanceChild extends JSWindowActorChild {
|
||||
this.openGlance(target);
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown(event) {
|
||||
if (event.defaultPrevented || event.key !== 'Escape') {
|
||||
return;
|
||||
}
|
||||
this.sendAsyncMessage('ZenGlance:CloseGlance', {
|
||||
hasFocused: this.contentWindow.document.activeElement !== this.contentWindow.document.body,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,16 @@ export class ZenGlanceParent extends JSWindowActorParent {
|
||||
this.openGlance(this.browsingContext.topChromeWindow, message.data);
|
||||
break;
|
||||
}
|
||||
case 'ZenGlance:CloseGlance': {
|
||||
const params = {
|
||||
onTabClose: true,
|
||||
...message.data,
|
||||
};
|
||||
this.browsingContext.topChromeWindow.gZenGlanceManager.closeGlance(params);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
console.warn(`[glance]: Unknown message: ${message.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
4
src/browser/base/zen-components/moz.build
Normal file
4
src/browser/base/zen-components/moz.build
Normal file
@@ -0,0 +1,4 @@
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
"tests/browser.toml",
|
||||
]
|
||||
Reference in New Issue
Block a user