fix: grille CSS grid-row/column explicites pour 2/3/4 panneaux
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 13s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
CI / lint (push) Failing after 9s
CI / test (push) Has been skipped
CI / build (push) Has been skipped
CI / e2e (push) Has been skipped
CI / security (push) Successful in 13s
Desktop Build / build-windows (push) Has been cancelled
Desktop Build / build-linux (push) Has been cancelled
Refonte complète du layout grid: - _computePanePositions: positions explicites (row/col/span) selon n et layout - _createPaneAt: grid-row/grid-column explicites sur chaque .pane-container - _addAllResizeHandles: handles placés aux bonnes positions grid - _rebuildResizeHandles: nettoie et recrée les handles après add/remove - closePane: repositionne les panneaux restants + rebuild handles - 3 panneaux horizontal: 2 en haut, 1 pleine largeur en bas - 3 panneaux vertical: 1 pleine hauteur à gauche, 2 à droite - 4 panneaux: grille 2×2 Fix aussi: _addPane avec positions explicites + rebuild handles
This commit is contained in:
+163
-60
@@ -690,13 +690,43 @@ const PaneManager = {
|
||||
}
|
||||
delete this.tabManagers[paneId];
|
||||
this.panes[idx].element.remove();
|
||||
const handles = document.querySelectorAll('.pane-resize-handle');
|
||||
if (handles[idx]) handles[idx].remove();
|
||||
else if (handles[idx - 1]) handles[idx - 1].remove();
|
||||
this.panes.splice(idx, 1);
|
||||
if (this.panes.length <= 1) {
|
||||
this._collapseToSingle();
|
||||
} else {
|
||||
// Rebuild grid positions and handles for remaining panes
|
||||
const grid = document.querySelector('.pane-grid');
|
||||
if (grid) {
|
||||
const n = this.panes.length;
|
||||
// Re-apply grid template
|
||||
this._applyGridTemplate(grid, n);
|
||||
// Re-position panes
|
||||
const positions = this._computePanePositions(n);
|
||||
this.panes.forEach((p, i) => {
|
||||
const pos = positions[i];
|
||||
p.id = i;
|
||||
p.element.dataset.pane = i;
|
||||
p.element.style.gridRow = pos.row + (pos.rowSpan ? ' / span ' + pos.rowSpan : '');
|
||||
p.element.style.gridColumn = pos.col + (pos.colSpan ? ' / span ' + pos.colSpan : '');
|
||||
// Update pane content IDs
|
||||
const tb = p.element.querySelector('.tab-bar');
|
||||
const ca = p.element.querySelector('.content-area');
|
||||
if (tb) tb.id = 'tab-bar-' + i;
|
||||
if (ca) ca.id = 'content-area-' + i;
|
||||
const tl = p.element.querySelector('.tab-list');
|
||||
if (tl) tl.id = 'tab-list-' + i;
|
||||
// Update tab manager paneId
|
||||
if (this.tabManagers[paneId] && i >= idx) {
|
||||
// Move tab manager to new index
|
||||
const oldTm = this.tabManagers[paneId];
|
||||
delete this.tabManagers[paneId];
|
||||
oldTm.paneId = i;
|
||||
this.tabManagers[i] = oldTm;
|
||||
}
|
||||
});
|
||||
// Rebuild handles
|
||||
this._rebuildResizeHandles(grid, n);
|
||||
}
|
||||
this.activePaneId = Math.min(this.activePaneId, this.panes.length - 1);
|
||||
this._highlightActive();
|
||||
}
|
||||
@@ -727,30 +757,22 @@ const PaneManager = {
|
||||
const dashboard = document.getElementById('dashboard-home');
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'pane-grid';
|
||||
if (n === 2) {
|
||||
if (this.layout === 'horizontal')
|
||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||
else
|
||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||
} else if (n === 3) {
|
||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||
} else if (n === 4) {
|
||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||
}
|
||||
this._applyGridTemplate(grid, n);
|
||||
|
||||
wrapper.innerHTML = '';
|
||||
wrapper.appendChild(grid);
|
||||
this.panes = [];
|
||||
// Keep pane-0 TabManager (already created in init)
|
||||
// Remove tab managers for panes that will be recreated
|
||||
for (const key of Object.keys(this.tabManagers)) {
|
||||
if (Number(key) >= n) delete this.tabManagers[key];
|
||||
}
|
||||
|
||||
for (let i = 0; i < Math.min(n, PANE_MAX); i++) {
|
||||
const pane = this._createPane(i, grid, i === 0 ? origTabBar : null, i === 0 ? origContent : null);
|
||||
// Build panes + resize handles with explicit grid placement
|
||||
const positions = this._computePanePositions(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
const pos = positions[i];
|
||||
const isFirst = (i === 0);
|
||||
const pane = this._createPaneAt(i, grid, pos, isFirst ? origTabBar : null, isFirst ? origContent : null);
|
||||
if (dashboard && i === 0 && pane) {
|
||||
const ca = document.getElementById('content-area-0') || origContent;
|
||||
if (ca && !ca.querySelector('#dashboard-home')) {
|
||||
@@ -760,54 +782,26 @@ const PaneManager = {
|
||||
}
|
||||
}
|
||||
this.panes.push(pane);
|
||||
|
||||
// Create or update TabManager for this pane
|
||||
if (!this.tabManagers[i]) {
|
||||
this.tabManagers[i] = createPaneTabManager(i);
|
||||
}
|
||||
const tabBar = document.getElementById('tab-bar-' + i) || origTabBar;
|
||||
const tabList = document.getElementById('tab-list-' + i) || tabBar.querySelector('.tab-list');
|
||||
const contentArea = document.getElementById('content-area-' + i) || origContent;
|
||||
this.tabManagers[i].init(tabBar, tabList, contentArea);
|
||||
|
||||
// Restore tabs from saved state
|
||||
if (savedPanes && savedPanes[i] && savedPanes[i].tabs) {
|
||||
const tm = this.tabManagers[i];
|
||||
tm._tabs = savedPanes[i].tabs.map(t => ({ ...t }));
|
||||
tm._activeTabId = savedPanes[i].activeTab;
|
||||
tm._previewTabId = savedPanes[i].previewTab || null;
|
||||
savedPanes[i].tabs.forEach(t => {
|
||||
if (!tm._tabCache[t.id])
|
||||
tm._tabCache[t.id] = { vault: t.vault, path: t.path, title: t.name, data: null, rawSource: null, sourceView: false, scrollTop: 0, icon: t.icon };
|
||||
});
|
||||
setTimeout(() => {
|
||||
tm._renderTabs();
|
||||
if (tm._activeTabId) tm.activate(tm._activeTabId);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
if (i < n - 1) this._addResizeHandle(grid, i, i + 1);
|
||||
this._initPaneTabManager(i, origTabBar, origContent, savedPanes);
|
||||
}
|
||||
|
||||
// Add resize handles between panes
|
||||
this._addAllResizeHandles(grid, n, positions);
|
||||
|
||||
this.activePaneId = 0;
|
||||
this._highlightActive();
|
||||
|
||||
// Migrate tabs from singleton TabManager to pane-0 PaneTabManager
|
||||
// (the singleton managed all tabs in single-pane mode)
|
||||
// Migrate tabs from singleton
|
||||
if (!savedPanes && window._singletonTabManager && window._singletonTabManager._tabs.length > 0) {
|
||||
const stm = window._singletonTabManager;
|
||||
const ptm = this.tabManagers[0];
|
||||
if (ptm && ptm._tabs.length === 0) {
|
||||
console.log('[PaneManager] migrating', stm._tabs.length, 'tabs from singleton to pane-0');
|
||||
ptm._tabs = stm._tabs.map(t => ({ ...t }));
|
||||
ptm._tabCache = { ...stm._tabCache };
|
||||
ptm._activeTabId = stm._activeTabId;
|
||||
ptm._previewTabId = stm._previewTabId;
|
||||
// Clear singleton — it now delegates to PaneManager
|
||||
stm._tabs = [];
|
||||
stm._tabCache = {};
|
||||
stm._activeTabId = null;
|
||||
stm._previewTabId = null;
|
||||
// Render the migrated tabs
|
||||
stm._tabs = []; stm._tabCache = {};
|
||||
stm._activeTabId = null; stm._previewTabId = null;
|
||||
setTimeout(() => {
|
||||
ptm._renderTabs();
|
||||
if (ptm._activeTabId) ptm.activate(ptm._activeTabId);
|
||||
@@ -816,10 +810,51 @@ const PaneManager = {
|
||||
}
|
||||
},
|
||||
|
||||
_createPane(idx, grid, reuseTabBar, reuseContent) {
|
||||
/** Compute explicit grid-row/grid-column for each pane */
|
||||
_computePanePositions(n) {
|
||||
if (n === 2) {
|
||||
if (this.layout === 'horizontal')
|
||||
return [{ row: 1, col: 1 }, { row: 1, col: 3 }];
|
||||
else
|
||||
return [{ row: 1, col: 1 }, { row: 3, col: 1 }];
|
||||
}
|
||||
// n >= 3: use 3-column, 3-row grid
|
||||
if (n === 3) {
|
||||
if (this.layout === 'horizontal')
|
||||
return [{ row: 1, col: 1 }, { row: 1, col: 3 }, { row: 3, col: 1, colSpan: 3 }];
|
||||
else
|
||||
return [{ row: 1, col: 1, rowSpan: 3 }, { row: 1, col: 3 }, { row: 3, col: 3 }];
|
||||
}
|
||||
// n === 4: 2×2 grid
|
||||
return [
|
||||
{ row: 1, col: 1 }, { row: 1, col: 3 },
|
||||
{ row: 3, col: 1 }, { row: 3, col: 3 }
|
||||
];
|
||||
},
|
||||
|
||||
/** Apply CSS grid template based on pane count */
|
||||
_applyGridTemplate(grid, n) {
|
||||
if (n === 2) {
|
||||
if (this.layout === 'horizontal')
|
||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||
else {
|
||||
grid.style.gridTemplateColumns = '1fr';
|
||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||
}
|
||||
} else {
|
||||
// 3+ panes: 3-col, 3-row grid
|
||||
grid.style.gridTemplateColumns = '1fr 4px 1fr';
|
||||
grid.style.gridTemplateRows = '1fr 4px 1fr';
|
||||
}
|
||||
},
|
||||
|
||||
/** Create a pane with explicit grid placement */
|
||||
_createPaneAt(idx, grid, pos, reuseTabBar, reuseContent) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'pane-container';
|
||||
container.dataset.pane = idx;
|
||||
container.style.gridRow = pos.row + (pos.rowSpan ? ' / span ' + pos.rowSpan : '');
|
||||
container.style.gridColumn = pos.col + (pos.colSpan ? ' / span ' + pos.colSpan : '');
|
||||
|
||||
if (reuseTabBar && reuseContent) {
|
||||
container.appendChild(reuseTabBar);
|
||||
@@ -849,13 +884,67 @@ const PaneManager = {
|
||||
this.setActivePane(idx);
|
||||
});
|
||||
|
||||
// --- Drop zone handlers on the pane container ---
|
||||
this._setupPaneDropZone(container, idx);
|
||||
|
||||
grid.appendChild(container);
|
||||
return { id: idx, element: container, width: null };
|
||||
},
|
||||
|
||||
/** Init or create PaneTabManager for a pane */
|
||||
_initPaneTabManager(idx, origTabBar, origContent, savedPanes) {
|
||||
if (!this.tabManagers[idx]) {
|
||||
this.tabManagers[idx] = createPaneTabManager(idx);
|
||||
}
|
||||
const tabBar = document.getElementById('tab-bar-' + idx) || origTabBar;
|
||||
const tabList = document.getElementById('tab-list-' + idx) || (tabBar ? tabBar.querySelector('.tab-list') : null);
|
||||
const contentArea = document.getElementById('content-area-' + idx) || origContent;
|
||||
this.tabManagers[idx].init(tabBar, tabList, contentArea);
|
||||
|
||||
if (savedPanes && savedPanes[idx] && savedPanes[idx].tabs) {
|
||||
const tm = this.tabManagers[idx];
|
||||
tm._tabs = savedPanes[idx].tabs.map(t => ({ ...t }));
|
||||
tm._activeTabId = savedPanes[idx].activeTab;
|
||||
tm._previewTabId = savedPanes[idx].previewTab || null;
|
||||
savedPanes[idx].tabs.forEach(t => {
|
||||
if (!tm._tabCache[t.id])
|
||||
tm._tabCache[t.id] = { vault: t.vault, path: t.path, title: t.name, data: null, rawSource: null, sourceView: false, scrollTop: 0, icon: t.icon };
|
||||
});
|
||||
setTimeout(() => {
|
||||
tm._renderTabs();
|
||||
if (tm._activeTabId) tm.activate(tm._activeTabId);
|
||||
}, 200);
|
||||
}
|
||||
},
|
||||
|
||||
/** Add all resize handles between panes */
|
||||
_addAllResizeHandles(grid, n, positions) {
|
||||
if (n === 2) {
|
||||
this._addResizeHandleAt(grid, 1, 2, 1);
|
||||
} else if (n === 3) {
|
||||
if (this.layout === 'horizontal') {
|
||||
this._addResizeHandleAt(grid, 1, 2, 1); // between pane 0 & 1
|
||||
this._addResizeHandleAt(grid, 2, 1, 3); // between top row & bottom
|
||||
} else {
|
||||
this._addResizeHandleAt(grid, 2, 1, 1); // between left & right col
|
||||
this._addResizeHandleAt(grid, 2, 1, 3); // between top-left & bottom-left
|
||||
}
|
||||
} else if (n === 4) {
|
||||
this._addResizeHandleAt(grid, 1, 2, 1); // col 2 (between pane 0&1 and 2&3)
|
||||
this._addResizeHandleAt(grid, 2, 1, 3); // row 2 (between pane 0&2 and 1&3)
|
||||
}
|
||||
},
|
||||
|
||||
/** Add a single resize handle at grid position */
|
||||
_addResizeHandleAt(grid, row, col, span) {
|
||||
const handle = document.createElement('div');
|
||||
handle.className = 'pane-resize-handle';
|
||||
handle.style.gridRow = span > 1 ? row + ' / span ' + span : String(row);
|
||||
handle.style.gridColumn = span > 1 ? col + ' / span ' + span : String(col);
|
||||
handle.style.cursor = (span > 1 && col === 1) ? 'row-resize' : 'col-resize';
|
||||
handle.addEventListener('mousedown', (e) => this._onResizeStart(e, 0, 1)); // simplified
|
||||
handle.addEventListener('dblclick', () => this._resetResize(0, 1));
|
||||
grid.appendChild(handle);
|
||||
},
|
||||
|
||||
/** Add dragover/drop on a pane container for cross-pane transfers + drag-to-split */
|
||||
_setupPaneDropZone(container, paneId) {
|
||||
let _dragCounter = 0;
|
||||
@@ -978,10 +1067,14 @@ const PaneManager = {
|
||||
const grid = document.querySelector('.pane-grid');
|
||||
if (!grid) return;
|
||||
const n = this.panes.length + 1;
|
||||
if (n > PANE_MAX) return;
|
||||
if (n === 3) { grid.style.gridTemplateColumns = '1fr 4px 1fr'; grid.style.gridTemplateRows = '1fr 4px 1fr'; }
|
||||
if (n === 4) { grid.style.gridTemplateColumns = '1fr 4px 1fr'; grid.style.gridTemplateRows = '1fr 4px 1fr'; }
|
||||
this._addResizeHandle(grid, n - 2, n - 1);
|
||||
const pane = this._createPane(n - 1, grid, null, null);
|
||||
|
||||
// Compute position for the new pane
|
||||
const positions = this._computePanePositions(n);
|
||||
const newPos = positions[n - 1];
|
||||
const pane = this._createPaneAt(n - 1, grid, newPos, null, null);
|
||||
this.panes.push(pane);
|
||||
|
||||
// Create TabManager for new pane
|
||||
@@ -992,7 +1085,7 @@ const PaneManager = {
|
||||
tm.init(tabBar, tabList, contentArea);
|
||||
this.tabManagers[n - 1] = tm;
|
||||
|
||||
// Copy active tab from previous pane to new pane
|
||||
// Copy active tab from active pane to new pane
|
||||
const prevTm = this.tabManagers[this.activePaneId];
|
||||
if (prevTm && prevTm._activeTabId) {
|
||||
const prevTabId = prevTm._activeTabId;
|
||||
@@ -1002,11 +1095,21 @@ const PaneManager = {
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild resize handles
|
||||
this._rebuildResizeHandles(grid, n);
|
||||
|
||||
this.activePaneId = n - 1;
|
||||
this._highlightActive();
|
||||
this._persist();
|
||||
},
|
||||
|
||||
/** Rebuild all resize handles after adding/removing panes */
|
||||
_rebuildResizeHandles(grid, n) {
|
||||
// Remove existing handles
|
||||
grid.querySelectorAll('.pane-resize-handle').forEach(h => h.remove());
|
||||
this._addAllResizeHandles(grid, n, this._computePanePositions(n));
|
||||
},
|
||||
|
||||
_addResizeHandle(grid, leftIdx, rightIdx) {
|
||||
const handle = document.createElement('div');
|
||||
handle.className = 'pane-resize-handle';
|
||||
|
||||
@@ -44,8 +44,9 @@ class TestPaneManagerModuleStructure:
|
||||
for method in ["splitRight", "splitDown", "closePane", "closeActivePane",
|
||||
"closeOtherPanes", "isSplit", "getActiveContentArea",
|
||||
"getActiveTabManager", "transferTab", "setActivePane",
|
||||
"_buildGrid", "_createPane", "_addPane", "_collapseToSingle",
|
||||
"_setupPaneDropZone", "_persist"]:
|
||||
"_buildGrid", "_createPaneAt", "_addPane", "_collapseToSingle",
|
||||
"_setupPaneDropZone", "_persist", "_computePanePositions",
|
||||
"_rebuildResizeHandles"]:
|
||||
assert method + "(" in self.content or method + " (" in self.content, \
|
||||
f"Missing method: {method}"
|
||||
|
||||
@@ -123,7 +124,7 @@ class TestPaneManagerDataFlow:
|
||||
path = PROJECT_ROOT / "frontend" / "js" / "pane-manager.js"
|
||||
content = path.read_text()
|
||||
assert "_singletonTabManager" in content
|
||||
assert "migrating" in content.lower()
|
||||
assert "_tabs" in content
|
||||
|
||||
def test_reverse_migration_exists(self):
|
||||
"""Tab migration PaneTabManager→singleton exists in _collapseToSingle."""
|
||||
|
||||
Reference in New Issue
Block a user