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 { var gZenViewSplitter = new class {
constructor() { constructor() {
this._data = []; this._data = [];
@ -53,8 +69,6 @@ var gZenViewSplitter = new class {
* @param {boolean} forUnsplit - Indicates if the tab is being removed for unsplitting. * @param {boolean} forUnsplit - Indicates if the tab is being removed for unsplitting.
*/ */
removeTabFromGroup(tab, groupIndex, forUnsplit) { removeTabFromGroup(tab, groupIndex, forUnsplit) {
this.disableTabSwitchView();
const group = this._data[groupIndex]; const group = this._data[groupIndex];
const tabIndex = group.tabs.indexOf(tab); const tabIndex = group.tabs.indexOf(tab);
group.tabs.splice(tabIndex, 1); group.tabs.splice(tabIndex, 1);
@ -69,33 +83,18 @@ var gZenViewSplitter = new class {
} }
enableTabSwitchView() { 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) { if (!this._thumnailCanvas) {
this._thumnailCanvas = document.createElement("canvas"); this._thumnailCanvas = document.createElement("canvas");
this._thumnailCanvas.width = 280 * devicePixelRatio; this._thumnailCanvas.width = 280 * devicePixelRatio;
this._thumnailCanvas.height = 140 * devicePixelRatio; this._thumnailCanvas.height = 140 * devicePixelRatio;
} }
browsers.forEach(b => {
const container = b.closest('.browserContainer');
});
this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.addEventListener('dragstart', this.onBrowserDragStart);
this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver); this.tabBrowserPanel.addEventListener('dragover', this.onBrowserDragOver);
this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop); this.tabBrowserPanel.addEventListener('drop', this.onBrowserDrop);
this.tabBrowserPanel.addEventListener('click', this.disableTabSwitchView, {once: true});
} }
disableTabSwitchView = () => { disableTabSwitchView = () => {
if (!this.switchViewEnabled) return;
this.switchViewEnabled = false; this.switchViewEnabled = false;
this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart); this.tabBrowserPanel.removeEventListener('dragstart', this.onBrowserDragStart);
@ -109,6 +108,7 @@ var gZenViewSplitter = new class {
} }
onBrowserDragStart = (event) => { onBrowserDragStart = (event) => {
if (!this.splitViewActive) return;
let browser = event.target.querySelector('browser'); let browser = event.target.querySelector('browser');
if (!browser) { if (!browser) {
return; return;
@ -184,10 +184,12 @@ var gZenViewSplitter = new class {
} }
onBrowserDragOver = (event) => { onBrowserDragOver = (event) => {
if (!this.splitViewActive) return;
event.preventDefault(); event.preventDefault();
} }
onBrowserDrop = (event) => { onBrowserDrop = (event) => {
if (!this.splitViewActive) return;
console.log(event); console.log(event);
const containerId = event.dataTransfer.getData('text/plain'); const containerId = event.dataTransfer.getData('text/plain');
@ -474,107 +476,68 @@ var gZenViewSplitter = new class {
this.setTabsDocShellState(splitData.tabs, true); this.setTabsDocShellState(splitData.tabs, true);
this.updateSplitViewButton(false); this.updateSplitViewButton(false);
this.applyGridToTabs(splitData.tabs, gridType, activeTab); this.applyGridToTabs(splitData.tabs, activeTab);
const gridAreas = this.calculateGridAreas(splitData.tabs, gridType);
this.applyGridLayout(gridAreas); 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. * Applies the grid layout to the tabs.
* *
* @param {Tab[]} tabs - The tabs to apply the grid layout to. * @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. * @param {Tab} activeTab - The active tab.
*/ */
applyGridToTabs(tabs, gridType, activeTab) { applyGridToTabs(tabs,activeTab) {
tabs.forEach((tab, index) => { tabs.forEach((tab, index) => {
tab.splitView = true; tab.splitView = true;
const container = tab.linkedBrowser.closest('.browserSidebarContainer'); const container = tab.linkedBrowser.closest('.browserSidebarContainer');
this.styleContainer(container, tab === activeTab, index, gridType); this.styleContainer(container, tab === activeTab, index);
}); });
} }
/** /**
* Apply grid layout to tabBrowserPanel * 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) { applyGridLayout(splitNode, nodeRootPosition = {top: 0, bottom: 100, left: 0, right: 100}) {
const finalLayout = this.calculateLayoutWithSplitters(gridTemplateAreas); const rootToNodeWidthRatio = (nodeRootPosition.right - nodeRootPosition.left) / 100;
const rootToNodeHeightRatio = (nodeRootPosition.bottom - nodeRootPosition.top) / 100;
this.tabBrowserPanel.style.gridTemplateAreas = finalLayout.templateAreas; let leftOffset = nodeRootPosition.left;
this.removeSplitters(); let topOffset = nodeRootPosition.top;
finalLayout.splitters.forEach(s => this.insertSplitter(s.nr, s.orient, s.gridIdx)); splitNode.children.forEach((childNode) => {
const childRootPosition = {top: topOffset, right: 100 - (leftOffset + childNode.widthInParent * rootToNodeWidthRatio), bottom: 100 - (topOffset + childNode.heightInParent * rootToNodeHeightRatio), left: leftOffset};
this.updateGridDimensions(this._data[this.currentView], finalLayout.nrOfRows, finalLayout.nrOfColumns); if (!childNode.children) {
this.applyGridSizes(); const browserContainer = document.getElementById(childNode.id);
} browserContainer.style.inset = `${childRootPosition.top}% ${childRootPosition.right}% ${childRootPosition.bottom}% ${childRootPosition.left}%`;
/**
* 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 { } else {
finalTemplateAreas += " " + current; this.applyGridLayout(childNode, childRootPosition);
}
} }
if (!rows[i + 1]) { if (splitNode.direction === 'column') {
continue; topOffset += childNode.heightInParent * rootToNodeWidthRatio;
}
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 { } else {
nextRow += ' ' + current; leftOffset += childNode.widthInParent * rootToNodeHeightRatio;
} }
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}`;
}
}
finalTemplateAreas += `'`;
if (nextRow) {
finalTemplateAreas += `'${nextRow}'`;
}
}
return {templateAreas: finalTemplateAreas, splitters: splitters, nrOfRows: rows.length, nrOfColumns: rows[0]?.length || 0};
} }
insertSplitter(nr, orient, gridIdx) { insertSplitter(nr, orient, gridIdx) {
@ -587,82 +550,20 @@ var gZenViewSplitter = new class {
this.tabBrowserPanel.insertAdjacentElement("afterbegin", splitter); 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() { removeSplitters() {
[...gZenViewSplitter.tabBrowserPanel.children] [...gZenViewSplitter.tabBrowserPanel.children]
.filter(e => e.classList.contains('zen-split-view-splitter')) .filter(e => e.classList.contains('zen-split-view-splitter'))
.forEach(s => s.remove()); .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. * Styles the container for a tab.
* *
* @param {Element} container - The container element. * @param {Element} container - The container element.
* @param {boolean} isActive - Indicates if the tab is active. * @param {boolean} isActive - Indicates if the tab is active.
* @param {number} index - The index of the tab. * @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'); container.removeAttribute('zen-split-active');
if (isActive) { if (isActive) {
container.setAttribute('zen-split-active', 'true'); container.setAttribute('zen-split-active', 'true');
@ -671,7 +572,8 @@ var gZenViewSplitter = new class {
container.addEventListener('click', this.handleTabEvent); container.addEventListener('click', this.handleTabEvent);
container.addEventListener('mouseover', this.handleTabEvent); container.addEventListener('mouseover', this.handleTabEvent);
container.style.gridArea = `tab${index + 1}`; container.style.position = 'absolute';
container.setAttribute('zen-split-id', index);
} }
/** /**