mirror of
https://github.com/zen-browser/components.git
synced 2025-07-08 03:59:58 +02:00
330 lines
7.4 KiB
TypeScript
330 lines
7.4 KiB
TypeScript
|
|
class SplitViewsUtils {
|
|
/**
|
|
* @returns {HTMLDivElement}
|
|
*/
|
|
get tabBrowser() {
|
|
if (!this._tabBrowser) {
|
|
this._tabBrowser = document.getElementById('tabbrowser-tabpanels');
|
|
}
|
|
// @ts-ignore
|
|
return this._tabBrowser;
|
|
}
|
|
}
|
|
|
|
class SplitViewsBase extends SplitViewsUtils {
|
|
/**
|
|
* @type {SplitView[]}
|
|
*/
|
|
data;
|
|
|
|
/**
|
|
* @param {SplitViewConfig} config
|
|
*/
|
|
constructor(config) {
|
|
super();
|
|
this.config = config;
|
|
this.data = [];
|
|
this.currentView = -1;
|
|
this.globalIdCounter = 0;
|
|
// Added to "navigator-toolbox" element
|
|
this.parentSplitIndicator = this.config.splitIndicator + '-view';
|
|
this.log('SplitViewsBase initialized');
|
|
}
|
|
|
|
/**
|
|
* @param {string} message
|
|
* @protected
|
|
*/
|
|
log(message) {
|
|
console.log(`SplitViews: ${message}`);
|
|
}
|
|
|
|
get isActivated() {
|
|
return this.currentView !== -1;
|
|
}
|
|
|
|
get activeView() {
|
|
if (!this.isActivated) {
|
|
throw new Error('No active view');
|
|
}
|
|
return this.data[this.currentView];
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab} tab
|
|
*/
|
|
getTabView(tab) {
|
|
return this.data.find(view => view.tabs.includes(tab));
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab} tab
|
|
*/
|
|
isTabSplit(tab) {
|
|
return tab.hasAttribute(this.config.splitIndicator);
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab} tab
|
|
* @param {SplitType} type
|
|
* @param {MockedExports.BrowserTab[]} tabs
|
|
*/
|
|
changeSplitViewBase(tab, type, tabs) {
|
|
let view = this.getTabView(tab);
|
|
if (!view) {
|
|
return -1;
|
|
}
|
|
view.type = type;
|
|
view.tabs.push(...tabs.filter(t => !view.tabs.includes(t)));
|
|
return view.id;
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab[]} tabs
|
|
* @param {SplitType} type
|
|
*/
|
|
createSplitViewBase(tabs, type) {
|
|
let view = {
|
|
id: this.globalIdCounter++,
|
|
type,
|
|
tabs,
|
|
};
|
|
this.data.push(view);
|
|
this.currentView = this.data.length - 1;
|
|
return view.id;
|
|
}
|
|
|
|
/**
|
|
* Applies the grid layout to the tabs.
|
|
*
|
|
* @param {MockedExports.BrowserTab[]} tabs - The tabs to apply the grid layout to.
|
|
* @param {string} gridType - The type of grid layout.
|
|
* @param {MockedExports.BrowserTab} activeTab - The active tab.
|
|
*/
|
|
applyGridLayout(tabs, gridType, activeTab) {
|
|
const gridAreas = this.calculateGridAreas(tabs, gridType);
|
|
this.tabBrowser.style.gridTemplateAreas = gridAreas;
|
|
|
|
tabs.forEach((tab, index) => {
|
|
tab.setAttribute(this.config.splitIndicator, "true");
|
|
const container = tab.linkedBrowser.closest(".browserSidebarContainer");
|
|
if (!container) {
|
|
throw new Error("Container not found");
|
|
}
|
|
this.styleContainer(container, tab === activeTab, index, gridType);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Styles the container for a tab.
|
|
*
|
|
* @param {Element} container - The container element.
|
|
* @param {boolean} isActive - Indicates if the tab is active.
|
|
* @param {number} index - The index of the tab.
|
|
* @param {string} gridType - The type of grid layout.
|
|
*/
|
|
styleContainer(container, isActive, index, gridType) {
|
|
container.removeAttribute("split-active");
|
|
container.setAttribute(this.config.splitIndicator, "true");
|
|
if (isActive) {
|
|
container.setAttribute("split-active", "true");
|
|
}
|
|
container.setAttribute("split-anim", "true");
|
|
container.addEventListener("click", this.handleTabClick);
|
|
|
|
if (gridType === "grid") {
|
|
// @ts-ignore
|
|
container.style.gridArea = `tab${index + 1}`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the grid areas for the tabs.
|
|
*
|
|
* @param {MockedExports.BrowserTab[]} tabs - The tabs.
|
|
* @param {string} gridType - The type of grid layout.
|
|
* @returns {string} The calculated grid areas.
|
|
*/
|
|
calculateGridAreas(tabs, gridType) {
|
|
if (gridType === "grid") {
|
|
return this.calculateGridAreasForGrid(tabs);
|
|
}
|
|
if (gridType === "vsep") {
|
|
return `'${tabs.map((_, j) => `tab${j + 1}`).join(" ")}'`;
|
|
}
|
|
if (gridType === "hsep") {
|
|
return tabs.map((_, j) => `'tab${j + 1}'`).join(" ");
|
|
}
|
|
return "";
|
|
}
|
|
|
|
/**
|
|
* Handles the tab click event.
|
|
*
|
|
* @param {Event} event - The click event.
|
|
*/
|
|
handleTabClick(event) {
|
|
const container = event.currentTarget;
|
|
// @ts-ignore
|
|
const tab = window.gBrowser.tabs.find(
|
|
// @ts-ignore
|
|
t => t.linkedBrowser.closest(".browserSidebarContainer") === container
|
|
);
|
|
if (tab) {
|
|
// @ts-ignore
|
|
window.gBrowser.selectedTab = tab;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Calculates the grid areas for the tabs in a grid layout.
|
|
*
|
|
* @param {MockedExports.BrowserTab[]} tabs - The tabs.
|
|
* @returns {string} The calculated grid areas.
|
|
*/
|
|
calculateGridAreasForGrid(tabs) {
|
|
const rows = ["", ""];
|
|
tabs.forEach((_, i) => {
|
|
if (i % 2 === 0) {
|
|
rows[0] += ` tab${i + 1}`;
|
|
} else {
|
|
rows[1] += ` tab${i + 1}`;
|
|
}
|
|
});
|
|
|
|
if (tabs.length === 2) {
|
|
return "'tab1 tab2'";
|
|
}
|
|
|
|
if (tabs.length % 2 !== 0) {
|
|
rows[1] += ` tab${tabs.length}`;
|
|
}
|
|
|
|
return `'${rows[0].trim()}' '${rows[1].trim()}'`;
|
|
}
|
|
|
|
/**
|
|
* @param {number} viewId
|
|
* @protected
|
|
*/
|
|
updateSplitView(viewId) {
|
|
let view = this.data.find(view => view.id === viewId);
|
|
this.log(`updateSplitView: ${viewId}`);
|
|
this.currentView = viewId;
|
|
if (!view) {
|
|
this.tabBrowser.removeAttribute(this.parentSplitIndicator);
|
|
throw new Error('TODO: Remove split view');
|
|
return;
|
|
}
|
|
this.tabBrowser.setAttribute(this.parentSplitIndicator, "true");
|
|
this.applyGridLayout(view.tabs, view.type, view.tabs[0]);
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab[]} tabs
|
|
* @param {SplitType} type
|
|
* @protected
|
|
*/
|
|
createOrChangeSplitView(tabs, type) {
|
|
let activeTab = tabs.find(tab => this.isTabSplit(tab));
|
|
this.log(`createOrChangeSplitView: ${type}`);
|
|
let viewId = -1;
|
|
if (activeTab) {
|
|
viewId = this.changeSplitViewBase(activeTab, type, tabs);
|
|
} else {
|
|
viewId = this.createSplitViewBase(tabs, type);
|
|
}
|
|
this.updateSplitView(viewId);
|
|
}
|
|
}
|
|
|
|
// Public API exposed by the module
|
|
export class SplitViews extends SplitViewsBase {
|
|
/**
|
|
* @param {SplitViewConfig} config
|
|
*/
|
|
constructor(config) {
|
|
super(config);
|
|
this.addEventListeners();
|
|
}
|
|
|
|
addEventListeners() {
|
|
window.addEventListener('TabClose', this);
|
|
}
|
|
|
|
/**
|
|
* @param {Event} event
|
|
*/
|
|
handleEvent(event) {
|
|
switch (event.type) {
|
|
case 'TabClose':
|
|
this.onTabClose(event);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {Event} event
|
|
*/
|
|
// @ts-ignore
|
|
// @ts-ignore
|
|
onTabClose(event) {
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.Browser} browser
|
|
*/
|
|
// @ts-ignore
|
|
// @ts-ignore
|
|
onLocationChange(browser) {
|
|
this.log('onLocationChange');
|
|
}
|
|
|
|
/**
|
|
* @param {SplitType} type
|
|
*/
|
|
// @ts-ignore
|
|
// @ts-ignore
|
|
tileCurrentView(type) {
|
|
this.log('tileCurrentView');
|
|
}
|
|
|
|
closeCurrentView() {
|
|
this.log('closeCurrentView');
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab} tab
|
|
*/
|
|
// @ts-ignore
|
|
// @ts-ignore
|
|
tabIsInActiveView(tab) {
|
|
this.log('tabIsInActiveView');
|
|
return false;
|
|
}
|
|
|
|
getActiveViewTabs() {
|
|
this.log('getActiveViewTabs');
|
|
return [];
|
|
}
|
|
|
|
getActiveViewType() {
|
|
if (!this.isActivated) {
|
|
return undefined;
|
|
}
|
|
return this.activeView.type;
|
|
}
|
|
|
|
/**
|
|
* @param {MockedExports.BrowserTab[]} tabs
|
|
* @param {SplitType} type
|
|
* @public
|
|
*/
|
|
createSplitView(tabs, type = this.config.defaultSplitView) {
|
|
if (tabs.length < 2) {
|
|
return;
|
|
}
|
|
this.createOrChangeSplitView(tabs, type);
|
|
}
|
|
};
|