From 14192f67d7da0a042d93da1fb67cdfafe6a8da3b Mon Sep 17 00:00:00 2001 From: Bruno Charest Date: Sat, 30 May 2026 08:39:29 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20navigation=20graphe=20=E2=80=94=20?= =?UTF-8?q?=E2=86=90=20=E2=86=92=20historique=20+=20=E2=86=91=20Parent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 3 ++ frontend/js/graph.js | 82 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/frontend/index.html b/frontend/index.html index 0cf985e..645c55d 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -703,6 +703,9 @@
Vue Graphique
+ + +
diff --git a/frontend/js/graph.js b/frontend/js/graph.js index 03aa4f1..5f3c362 100644 --- a/frontend/js/graph.js +++ b/frontend/js/graph.js @@ -59,8 +59,16 @@ export const GraphViewManager = { _previewCache: {}, _previewPending: null, _ctrlHoverNode: null, + _navHistory: [], + _navIndex: -1, async open(vault, path, type) { + // Push current state to history before navigating (unless navigating via back/fwd) + if (this._vault && !this._navNavigating) { + this._pushHistory(this._vault, this._path || ''); + } + this._navNavigating = false; + this._vault = vault; this._path = path; @@ -88,6 +96,7 @@ export const GraphViewManager = { this._resetView(); await this._fetchAndRender(); + this._updateNavButtons(); safeCreateIcons(); }, @@ -196,6 +205,8 @@ export const GraphViewManager = { close() { const modal = document.getElementById('graph-modal'); if (modal) modal.classList.remove('active'); + this._navHistory = []; + this._navIndex = -1; if (this._animFrame) { cancelAnimationFrame(this._animFrame); this._animFrame = null; @@ -210,6 +221,68 @@ export const GraphViewManager = { this._nodePositions = {}; }, + _pushHistory(vault, path) { + // Truncate forward history if we navigated back then took a new path + if (this._navIndex < this._navHistory.length - 1) { + this._navHistory = this._navHistory.slice(0, this._navIndex + 1); + } + this._navHistory.push({ vault, path }); + this._navIndex = this._navHistory.length - 1; + // Keep max 50 entries + if (this._navHistory.length > 50) { + this._navHistory.shift(); + this._navIndex--; + } + this._updateNavButtons(); + }, + + _updateNavButtons() { + const backBtn = document.getElementById('graph-nav-back'); + const fwdBtn = document.getElementById('graph-nav-fwd'); + const upBtn = document.getElementById('graph-nav-up'); + if (backBtn) { + backBtn.disabled = this._navIndex <= 0; + backBtn.style.opacity = this._navIndex > 0 ? '1' : '0.3'; + } + if (fwdBtn) { + fwdBtn.disabled = this._navIndex >= this._navHistory.length - 1; + fwdBtn.style.opacity = this._navIndex < this._navHistory.length - 1 ? '1' : '0.3'; + } + // Show "↑ Parent" if we have a path (subfolder) or history + if (upBtn) { + const hasParent = !!(this._path && this._path.includes('/')); + const hasHistory = this._navIndex > 0; + upBtn.style.display = (hasParent || hasHistory) ? '' : 'none'; + } + }, + + goBack() { + if (this._navIndex <= 0) return; + this._navIndex--; + const entry = this._navHistory[this._navIndex]; + this._navNavigating = true; + this.open(entry.vault, entry.path); + }, + + goForward() { + if (this._navIndex >= this._navHistory.length - 1) return; + this._navIndex++; + const entry = this._navHistory[this._navIndex]; + this._navNavigating = true; + this.open(entry.vault, entry.path); + }, + + goUp() { + if (!this._path || !this._path.includes('/')) { + // No subfolder — go to previous history entry + this.goBack(); + return; + } + // Go to parent directory + const parentPath = this._path.split('/').slice(0, -1).join('/'); + this.open(this._vault, parentPath); + }, + _initLayout() { const w = this._canvas.parentElement.clientWidth; const h = this._canvas.parentElement.clientHeight; @@ -810,6 +883,15 @@ export function initGraphView() { if (closeBtn) closeBtn.addEventListener('click', () => gm.close()); if (modal) modal.addEventListener('click', (e) => { if (e.target === modal) gm.close(); }); + + // Navigation buttons + const navBack = document.getElementById('graph-nav-back'); + if (navBack) navBack.addEventListener('click', () => gm.goBack()); + const navFwd = document.getElementById('graph-nav-fwd'); + if (navFwd) navFwd.addEventListener('click', () => gm.goForward()); + const navUp = document.getElementById('graph-nav-up'); + if (navUp) navUp.addEventListener('click', () => gm.goUp()); + if (zoomIn) zoomIn.addEventListener('click', () => { gm._zoom = Math.min(3, gm._zoom * 1.2); }); if (zoomOut) zoomOut.addEventListener('click', () => { gm._zoom = Math.max(0.2, gm._zoom * 0.8); }); if (reset) reset.addEventListener('click', () => { gm._offsetX = 0; gm._offsetY = 0; gm._zoom = 1; });