Use layoutTree to construct splitview. No longer make use of a grid.

This commit is contained in:
brahim 2024-10-02 14:45:21 +02:00
parent 9eb42269de
commit 966ada7f0d

View file

@ -1,3 +1,19 @@
class SplitNode {
constructor(direction, widthInParent = 100, heightInParent = 100) {
this.direction = direction; // row or column
this.children = [];
this.widthInParent = widthInParent;
this.heightInParent = heightInParent;
}
}
class SplitLeafNode {
constructor(tabContainerId,widthInParent = 100, heightInParent = 100) {
this.id = tabContainerId;
this.widthInParent = widthInParent;
this.heightInParent = heightInParent;
}
}
var gZenViewSplitter = new class {
constructor() {
this._data = [];
@ -53,8 +69,6 @@ var gZenViewSplitter = new class {
* @param {boolean} forUnsplit - Indicates if the tab is being removed for unsplitting.
*/
removeTabFromGroup(tab, groupIndex, forUnsplit) {
this.disableTabSwitchView();
const group = this._data[groupIndex];
const tabIndex = group.tabs.indexOf(tab);
group.tabs.splice(tabIndex, 1);
@ -69,33 +83,18 @@ var gZenViewSplitter = new class {
}
enableTabSwitchView() {
if (!this.splitViewActive) return;
this.switchViewEnabled = true;
const browsers = this._data[this.currentView].tabs.map(t => t.linkedBrowser);
browsers.forEach(b => {
b.style.pointerEvents = 'none';
b.style.opacity = '.7';
});
if (!this._thumnailCanvas) {
this._thumnailCanvas = document.createElement("canvas");
this._thumnailCanvas.width = 280 * devicePixelRatio;
this._thumnailCanvas.height = 140 * devicePixelRatio;
}
browsers.forEach(b => {
const container = b.closest('.browserContainer');
});
this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart);
this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver);
this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop);
this.tabBrowserPanel.addEventListener('click', this.disableTabSwitchView, {once: true});
}
disableTabSwitchView = () => {
if (!this.switchViewEnabled) return;
this.switchViewEnabled = false;
this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart);
@ -109,6 +108,7 @@ var gZenViewSplitter = new class {
}
onBrowserDragStart = (event) => {
if (!this.splitViewActive) return;
let browser = event.target.querySelector('browser');
if (!browser) {
return;
@ -184,10 +184,12 @@ var gZenViewSplitter = new class {
}
onBrowserDragOver = (event) => {
if (!this.splitViewActive) return;
event.preventDefault();
}
onBrowserDrop = (event) => {
if (!this.splitViewActive) return;
console.log(event);
const containerId = event.dataTransfer.getData('text/plain');
@ -474,107 +476,68 @@ var gZenViewSplitter = new class {
this.setTabsDocShellState(splitData.tabs, true);
this.updateSplitViewButton(false);
this.applyGridToTabs(splitData.tabs, gridType, activeTab);
const gridAreas = this.calculateGridAreas(splitData.tabs, gridType);
this.applyGridLayout(gridAreas);
this.applyGridToTabs(splitData.tabs, activeTab);
const layout = this.calculateLayoutTree(splitData.tabs, gridType);
splitData.layoutTree = layout;
this.applyGridLayout(layout);
}
calculateLayoutTree(tabs, gridType) {
const containerIds = tabs.map(t => t.linkedBrowser.closest('.browserSidebarContainer').id);
let rootNode;
if (gridType === 'vsep') {
rootNode = new SplitNode('row');
rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100 / tabs.length, 100));
} else if (gridType === 'hsep') {
rootNode = new SplitNode('column');
rootNode.children = containerIds.map(id => new SplitLeafNode(id, 100, 100 / tabs.length));
}
return rootNode;
}
/**
* Applies the grid layout to the tabs.
*
* @param {Tab[]} tabs - The tabs to apply the grid layout to.
* @param {string} gridType - The type of grid layout.
* @param {Tab} activeTab - The active tab.
*/
applyGridToTabs(tabs, gridType, activeTab) {
applyGridToTabs(tabs,activeTab) {
tabs.forEach((tab, index) => {
tab.splitView = true;
const container = tab.linkedBrowser.closest('.browserSidebarContainer');
this.styleContainer(container, tab === activeTab, index, gridType);
this.styleContainer(container, tab === activeTab, index);
});
}
/**
* Apply grid layout to tabBrowserPanel
*
* @param gridTemplateAreas
* @param {SplitNode} splitNode SplitNode
* @param {{top, bottom, left, right}} nodeRootPosition position of node relative to root of split
*/
applyGridLayout(gridTemplateAreas) {
const finalLayout = this.calculateLayoutWithSplitters(gridTemplateAreas);
applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 100, left: 0, right: 100}) {
const rootToNodeWidthRatio = (nodeRootPosition.right - nodeRootPosition.left) / 100;
const rootToNodeHeightRatio = (nodeRootPosition.bottom - nodeRootPosition.top) / 100;
this.tabBrowserPanel.style.gridTemplateAreas = finalLayout.templateAreas;
this.removeSplitters();
finalLayout.splitters.forEach(s => this.insertSplitter(s.nr, s.orient, s.gridIdx));
this.updateGridDimensions(this._data[this.currentView], finalLayout.nrOfRows, finalLayout.nrOfColumns);
this.applyGridSizes();
}
/**
* Takes a gridLayout and returns a new one with splitters.
*
* @param girdTemplateAreas
* @returns {{splitters: *[], templateAreas: string, nrOfColumns: number, nrOfRows: number}}
*/
calculateLayoutWithSplitters(girdTemplateAreas) {
const rows = girdTemplateAreas.split(/['"]\s+['"]/).map(r => r.replaceAll(/['"]/g, '').split(/\s+/));
let finalTemplateAreas = '';
const splitters = [];
let vSplitterCount = 0;
let hSplitterCount = 0;
for (let i = 0; i < rows.length; i++) {
let nextRow = '';
finalTemplateAreas += `'`;
for (let j = 0; j < rows[i].length; j++) {
const current = rows[i][j];
const rightNeighbor = rows[i][j + 1];
finalTemplateAreas += " " + current;
if (rightNeighbor) {
if (rightNeighbor !== current) {
finalTemplateAreas += ` vSplitter${vSplitterCount + 1}`;
vSplitterCount++;
splitters.push({nr: vSplitterCount, orient: 'vertical', gridIdx: j + 1, panels: current + 1});
} else {
finalTemplateAreas += " " + current;
}
}
if (!rows[i + 1]) {
continue;
}
const underNeighbor = rows[i + 1][j];
if (underNeighbor !== current) {
nextRow += ` hSplitter${hSplitterCount + 1}`;
hSplitterCount++;
let panels = 1;
while (rows[i][j - panels] === current) {
panels++;
}
splitters.push({nr: hSplitterCount, orient: 'horizontal', gridIdx: i + 1, panels: panels});
} else {
nextRow += ' ' + current;
}
if (j === rows[i].length - 1) {
continue;
}
const rightNeighborJoinedWithUnderNeighbor = rightNeighbor === rows[i + 1][j + 1];
if (rightNeighborJoinedWithUnderNeighbor && (underNeighbor === current)) {
if (current === rightNeighbor) nextRow += ' ' + current; // square
else nextRow += ` vSplitter${vSplitterCount}`;
} else {
nextRow += ` hSplitter${hSplitterCount + 1}`;
}
let leftOffset = nodeRootPosition.left;
let topOffset = nodeRootPosition.top;
splitNode.children.forEach((childNode) => {
const childRootPosition = {top: topOffset, right: 100 - (leftOffset + childNode.widthInParent * rootToNodeWidthRatio), bottom: 100 - (topOffset + childNode.heightInParent * rootToNodeHeightRatio), left: leftOffset};
if (!childNode.children) {
const browserContainer = document.getElementById(childNode.id);
browserContainer.style.inset = `${childRootPosition.top}% ${childRootPosition.right}% ${childRootPosition.bottom}% ${childRootPosition.left}%`;
} else {
this.applyGridLayout(childNode, childRootPosition);
}
finalTemplateAreas += `'`;
if (nextRow) {
finalTemplateAreas += `'${nextRow}'`;
}
}
return {templateAreas: finalTemplateAreas, splitters: splitters, nrOfRows: rows.length, nrOfColumns: rows[0]?.length || 0};
if (splitNode.direction === 'column') {
topOffset += childNode.heightInParent * rootToNodeWidthRatio;
} else {
leftOffset += childNode.widthInParent * rootToNodeHeightRatio;
}
});
}
insertSplitter(nr, orient, gridIdx) {
@ -587,82 +550,20 @@ var gZenViewSplitter = new class {
this.tabBrowserPanel.insertAdjacentElement("afterbegin", splitter);
}
/**
* Initialize splitData with default widths and heights if dimensions of grid don't match
*
* @param splitData the splits data
* @param nrOfRows number of rows in the grid (excluding splitters)
* @param nrOfColumns number of columns in the grid (excluding splitters)
*/
updateGridDimensions(splitData, nrOfRows, nrOfColumns) {
if (splitData.widths?.length !== nrOfColumns || splitData.heights?.length !== nrOfRows) {
splitData.widths = Array( nrOfColumns).fill(100 / nrOfColumns);
splitData.heights = Array(nrOfRows).fill(100 / nrOfRows);
}
}
removeSplitters() {
[...gZenViewSplitter.tabBrowserPanel.children]
.filter(e => e.classList.contains('zen-split-view-splitter'))
.forEach(s => s.remove());
}
/**
* Calculates the grid areas for the tabs.
*
* @param {Tab[]} 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 '';
}
/**
* Calculates the grid areas for the tabs in a grid layout.
*
* @param {Tab[]} 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()}'`;
}
/**
* 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) {
styleContainer(container, isActive, index) {
container.removeAttribute('zen-split-active');
if (isActive) {
container.setAttribute('zen-split-active', 'true');
@ -671,7 +572,8 @@ var gZenViewSplitter = new class {
container.addEventListener('click', this.handleTabEvent);
container.addEventListener('mouseover', this.handleTabEvent);
container.style.gridArea = `tab${index + 1}`;
container.style.position = 'absolute';
container.setAttribute('zen-split-id', index);
}
/**