feat: navigation graphe — ← → historique + ↑ Parent
All checks were successful
CI / lint (push) Successful in 13s
CI / security (push) Successful in 9s
CI / test (push) Successful in 17s
CI / build (push) Successful in 9s

This commit is contained in:
Bruno Charest 2026-05-30 08:39:29 -04:00
parent 5783811448
commit 14192f67d7
2 changed files with 85 additions and 0 deletions

View File

@ -703,6 +703,9 @@
<!-- Header: 3 zones — titre | recherche | contrôles --> <!-- Header: 3 zones — titre | recherche | contrôles -->
<div class="editor-header" style="display:flex;align-items:center;gap:12px;padding:8px 12px;border-bottom:1px solid var(--border-color)"> <div class="editor-header" style="display:flex;align-items:center;gap:12px;padding:8px 12px;border-bottom:1px solid var(--border-color)">
<div class="editor-title" id="graph-title" style="font-weight:600;font-size:0.9rem;white-space:nowrap;flex-shrink:0;min-width:0;overflow:hidden;text-overflow:ellipsis">Vue Graphique</div> <div class="editor-title" id="graph-title" style="font-weight:600;font-size:0.9rem;white-space:nowrap;flex-shrink:0;min-width:0;overflow:hidden;text-overflow:ellipsis">Vue Graphique</div>
<button class="editor-btn" id="graph-nav-back" title="Retour" style="font-size:0.75rem;padding:2px 4px;opacity:0.5;flex-shrink:0" disabled></button>
<button class="editor-btn" id="graph-nav-fwd" title="Avancer" style="font-size:0.75rem;padding:2px 4px;opacity:0.5;flex-shrink:0" disabled></button>
<button class="editor-btn" id="graph-nav-up" title="Dossier parent" style="font-size:0.7rem;padding:2px 6px;flex-shrink:0;display:none">↑ Parent</button>
<div style="flex:1;display:flex;justify-content:center"> <div style="flex:1;display:flex;justify-content:center">
<div style="position:relative;width:100%;max-width:400px"> <div style="position:relative;width:100%;max-width:400px">
<i data-lucide="search" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);width:14px;height:14px;color:var(--text-muted);pointer-events:none"></i> <i data-lucide="search" style="position:absolute;left:10px;top:50%;transform:translateY(-50%);width:14px;height:14px;color:var(--text-muted);pointer-events:none"></i>

View File

@ -59,8 +59,16 @@ export const GraphViewManager = {
_previewCache: {}, _previewCache: {},
_previewPending: null, _previewPending: null,
_ctrlHoverNode: null, _ctrlHoverNode: null,
_navHistory: [],
_navIndex: -1,
async open(vault, path, type) { 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._vault = vault;
this._path = path; this._path = path;
@ -88,6 +96,7 @@ export const GraphViewManager = {
this._resetView(); this._resetView();
await this._fetchAndRender(); await this._fetchAndRender();
this._updateNavButtons();
safeCreateIcons(); safeCreateIcons();
}, },
@ -196,6 +205,8 @@ export const GraphViewManager = {
close() { close() {
const modal = document.getElementById('graph-modal'); const modal = document.getElementById('graph-modal');
if (modal) modal.classList.remove('active'); if (modal) modal.classList.remove('active');
this._navHistory = [];
this._navIndex = -1;
if (this._animFrame) { if (this._animFrame) {
cancelAnimationFrame(this._animFrame); cancelAnimationFrame(this._animFrame);
this._animFrame = null; this._animFrame = null;
@ -210,6 +221,68 @@ export const GraphViewManager = {
this._nodePositions = {}; 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() { _initLayout() {
const w = this._canvas.parentElement.clientWidth; const w = this._canvas.parentElement.clientWidth;
const h = this._canvas.parentElement.clientHeight; const h = this._canvas.parentElement.clientHeight;
@ -810,6 +883,15 @@ export function initGraphView() {
if (closeBtn) closeBtn.addEventListener('click', () => gm.close()); if (closeBtn) closeBtn.addEventListener('click', () => gm.close());
if (modal) modal.addEventListener('click', (e) => { if (e.target === modal) 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 (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 (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; }); if (reset) reset.addEventListener('click', () => { gm._offsetX = 0; gm._offsetY = 0; gm._zoom = 1; });