feat: header graph flat design — 3 zones + barre statut + recherche unifiée
All checks were successful
CI / lint (push) Successful in 14s
CI / security (push) Successful in 8s
CI / test (push) Successful in 16s
CI / build (push) Successful in 2s

This commit is contained in:
Bruno Charest 2026-05-30 00:00:59 -04:00
parent 4a916e80db
commit a88be85623
2 changed files with 59 additions and 68 deletions

View File

@ -700,53 +700,43 @@
<!-- Graph View Modal -->
<div class="editor-modal" id="graph-modal">
<div class="editor-container" style="max-width:95vw;width:1000px;height:85vh;display:flex;flex-direction:column;">
<div class="editor-header" style="flex-wrap:wrap;gap:2px">
<div class="editor-actions" style="width:100%;display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding-bottom:4px">
<span id="graph-info" style="font-size:0.75rem;color:var(--text-muted);white-space:nowrap"></span>
<label style="font-size:0.7rem;color:var(--text-muted)">Profondeur:</label>
<input type="range" id="graph-depth" min="0" max="3" value="1" style="width:50px" title="Profondeur d'exploration">
<button class="editor-btn" id="graph-full-vault" title="Vue complète du vault" aria-label="Vault complet" style="font-size:0.7rem">🌐 Tout</button>
<span style="color:var(--border-color);margin:0 4px"></span>
<i data-lucide="search" style="width:14px;height:14px;color:var(--text-muted);flex-shrink:0"></i>
<input type="text" id="graph-search" placeholder="Rechercher un nœud..." style="width:180px;font-size:0.75rem;padding:2px 8px;border-radius:4px;border:1px solid var(--border-color);background:var(--bg-secondary);color:var(--text-primary)">
<i data-lucide="tag" style="width:14px;height:14px;color:var(--text-muted);flex-shrink:0;margin-left:4px"></i>
<input type="text" id="graph-tag-filter" placeholder="Filtrer par tag..." style="width:140px;font-size:0.75rem;padding:2px 8px;border-radius:4px;border:1px solid var(--border-color);background:var(--bg-secondary);color:var(--text-primary)">
<span style="font-size:0.65rem;color:var(--text-muted);margin-left:4px;opacity:0.6;white-space:nowrap">Ctrl+survol = aperçu</span>
<!-- 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-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 style="flex:1;display:flex;justify-content:center">
<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>
<input type="text" id="graph-search" placeholder="Rechercher ou #tag..." style="width:100%;font-size:0.8rem;padding:6px 12px 6px 32px;border-radius:8px;border:none;background:var(--bg-secondary);color:var(--text-primary);outline:none;box-shadow:inset 0 1px 3px rgba(0,0,0,0.2)">
<span style="position:absolute;right:10px;top:50%;transform:translateY(-50%);font-size:0.6rem;color:var(--text-muted);opacity:0.5;pointer-events:none">Ctrl+survol</span>
</div>
</div>
<div class="editor-actions" style="width:100%;display:flex;align-items:center;gap:4px;border-top:1px solid var(--border-color);padding-top:3px">
<span style="flex:1"></span>
<button class="editor-btn" id="graph-zoom-in" title="Zoom avant" aria-label="Zoom avant">
<i data-lucide="zoom-in" style="width:16px;height:16px"></i>
</button>
<button class="editor-btn" id="graph-zoom-out" title="Zoom arrière" aria-label="Zoom arrière">
<i data-lucide="zoom-out" style="width:16px;height:16px"></i>
</button>
<button class="editor-btn" id="graph-reset" title="Réinitialiser la vue" aria-label="Réinitialiser">
<i data-lucide="maximize" style="width:16px;height:16px"></i>
</button>
<button class="editor-btn" id="graph-export" title="Exporter en PNG" aria-label="Export PNG">
<i data-lucide="download" style="width:16px;height:16px"></i>
</button>
<button class="editor-btn" id="graph-fullscreen" title="Plein écran" aria-label="Plein écran">
<i data-lucide="expand" style="width:16px;height:16px"></i>
</button>
<button class="editor-btn" id="graph-close" title="Fermer" aria-label="Fermer">
<i data-lucide="x" style="width:16px;height:16px"></i>
</button>
<div style="display:flex;align-items:center;gap:2px;flex-shrink:0">
<button class="editor-btn" id="graph-full-vault" title="Vue complète du vault" style="font-size:0.7rem;padding:4px 8px">🌐 Tout</button>
<span style="font-size:0.65rem;color:var(--text-muted);margin:0 2px">Prof.</span>
<input type="range" id="graph-depth" min="0" max="3" value="1" style="width:50px" title="Profondeur">
<button class="editor-btn" id="graph-zoom-out" title="Zoom arrière"><i data-lucide="zoom-out" style="width:15px;height:15px"></i></button>
<button class="editor-btn" id="graph-zoom-in" title="Zoom avant"><i data-lucide="zoom-in" style="width:15px;height:15px"></i></button>
<button class="editor-btn" id="graph-reset" title="Réinitialiser"><i data-lucide="maximize" style="width:15px;height:15px"></i></button>
<button class="editor-btn" id="graph-export" title="Exporter PNG"><i data-lucide="download" style="width:15px;height:15px"></i></button>
<button class="editor-btn" id="graph-fullscreen" title="Plein écran"><i data-lucide="expand" style="width:15px;height:15px"></i></button>
<button class="editor-btn" id="graph-close" title="Fermer"><i data-lucide="x" style="width:16px;height:16px"></i></button>
</div>
<div class="editor-title" id="graph-title" style="width:100%;font-size:0.85rem;padding-top:2px;border-top:1px solid var(--border-color)">Vue Graphique</div>
</div>
<div class="editor-body" style="flex:1;overflow:hidden;position:relative;padding:0">
<canvas id="graph-canvas" style="width:100%;height:100%;cursor:grab"></canvas>
<div id="graph-tooltip" style="display:none;position:absolute;pointer-events:none;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:6px;padding:6px 10px;font-size:0.75rem;color:var(--text-primary);max-width:250px;z-index:10;box-shadow:0 2px 8px rgba(0,0,0,0.3)"></div>
<div id="graph-legend" style="position:absolute;bottom:12px;left:12px;display:flex;gap:12px;font-size:0.7rem;color:var(--text-muted);background:var(--bg-primary);padding:6px 12px;border-radius:6px;border:1px solid var(--border-color);align-items:center;flex-wrap:wrap">
<label style="cursor:pointer;display:flex;align-items:center;gap:3px"><input type="checkbox" id="graph-filter-dir" checked> 🟦 Dossier</label>
<label style="cursor:pointer;display:flex;align-items:center;gap:3px"><input type="checkbox" id="graph-filter-md" checked> 🟩 .md</label>
<label style="cursor:pointer;display:flex;align-items:center;gap:3px"><input type="checkbox" id="graph-filter-other" checked> ⬜ Autre</label>
<span style="color:var(--text-muted);margin:0 4px"></span>
<span style="color:var(--text-muted)">── Parent</span>
<span style="color:var(--accent-color,#2563eb)">┅┅ Wikilink</span>
<span style="color:#e74c3c">← Backlink</span>
<!-- Status bar + legend -->
<div style="position:absolute;bottom:0;left:0;right:0;display:flex;align-items:center;justify-content:space-between;padding:4px 12px;background:var(--bg-primary);border-top:1px solid var(--border-color);font-size:0.7rem;color:var(--text-muted)">
<span id="graph-status"></span>
<div style="display:flex;align-items:center;gap:10px">
<label style="cursor:pointer;display:flex;align-items:center;gap:3px"><input type="checkbox" id="graph-filter-dir" checked> 🟦 Dossier</label>
<label style="cursor:pointer;display:flex;align-items:center;gap:3px"><input type="checkbox" id="graph-filter-md" checked> 🟩 .md</label>
<label style="cursor:pointer;display:flex;align-items:center;gap:3px"><input type="checkbox" id="graph-filter-other" checked> ⬜ Autre</label>
<span style="opacity:0.5"></span>
<span>── Parent</span>
<span style="color:var(--accent-color,#2563eb)">┅┅ Wikilink</span>
<span style="color:#e74c3c">← Backlink</span>
</div>
</div>
</div>
</div>

View File

@ -63,7 +63,7 @@ export const GraphViewManager = {
const modal = document.getElementById('graph-modal');
const title = document.getElementById('graph-title');
const info = document.getElementById('graph-info');
const statusEl = document.getElementById('graph-status');
const canvas = document.getElementById('graph-canvas');
const depthSlider = document.getElementById('graph-depth');
@ -74,7 +74,7 @@ export const GraphViewManager = {
this._scope = 'directory';
title.textContent = `Vue Graphique — ${vault}${path ? '/' + path : ''}`;
info.textContent = 'Chargement...';
statusEl.textContent = 'Chargement...';
modal.classList.add('active');
this._canvas = canvas;
@ -93,8 +93,8 @@ export const GraphViewManager = {
},
async _fetchAndRender() {
const info = document.getElementById('graph-info');
if (!info) return;
const statusEl = document.getElementById('graph-status');
if (!statusEl) return;
const cacheKey = this._getCacheKey();
@ -105,7 +105,7 @@ export const GraphViewManager = {
this._edges = cached.edges;
this._scope = cached.scope;
const scopeLabel = this._scope === 'full' ? 'Vault complet' : 'Dossier';
info.textContent = `${this._nodes.length} nœuds, ${this._edges.length} liens · ${scopeLabel} · prof=${this._depth} (cache)`;
statusEl.textContent = `${this._nodes.length} nœuds, ${this._edges.length} liens · ${scopeLabel} · prof=${this._depth} (cache)`;
this._initLayout();
this._startRender();
return;
@ -134,11 +134,11 @@ export const GraphViewManager = {
this._cacheKey = cacheKey;
const scopeLabel = this._scope === 'full' ? 'Vault complet' : 'Dossier';
info.textContent = `${this._nodes.length} nœuds, ${this._edges.length} liens · ${scopeLabel} · prof=${this._depth}`;
statusEl.textContent = `${this._nodes.length} nœuds, ${this._edges.length} liens · ${scopeLabel} · prof=${this._depth}`;
this._initLayout();
this._startRender();
} catch (err) {
info.textContent = 'Erreur de chargement';
statusEl.textContent = 'Erreur de chargement';
console.error('Graph error:', err);
}
},
@ -163,19 +163,30 @@ export const GraphViewManager = {
setSearch(term) {
this._searchTerm = term;
// For now, use tag filter on backend; client-side highlighting on draw
if (term && term.length >= 2) {
this.reload();
} else if (!term && this._searchTerm !== term) {
this.reload();
// Parse unified search: #tag → tag filter, otherwise → node search
if (term.startsWith('#')) {
const tag = term.substring(1).trim();
if (tag && tag !== this._tagFilter) {
this._tagFilter = tag;
this.reload();
} else if (!tag && this._tagFilter) {
this._tagFilter = '';
this.reload();
}
} else {
if (this._tagFilter) {
this._tagFilter = '';
this.reload();
return;
}
if (term && term.length >= 2) {
this.reload();
} else if (!term && this._searchTerm !== term) {
this.reload();
}
}
},
setTagFilter(tag) {
this._tagFilter = tag;
this.reload();
},
close() {
const modal = document.getElementById('graph-modal');
if (modal) modal.classList.remove('active');
@ -801,16 +812,6 @@ export function initGraphView() {
});
}
// Tag filter input
const tagFilterInput = document.getElementById('graph-tag-filter');
if (tagFilterInput) {
let tagDebounce;
tagFilterInput.addEventListener('input', () => {
clearTimeout(tagDebounce);
tagDebounce = setTimeout(() => gm.setTagFilter(tagFilterInput.value.trim()), 400);
});
}
// Export PNG
const exportBtn = document.getElementById('graph-export');
if (exportBtn) exportBtn.addEventListener('click', () => gm.exportPNG());