feat(graph): Phase 3 — type filter, export PNG, fullscreen, focus node
- Type filter checkboxes (dossier, .md, autre) in legend - Export PNG button (canvas.toDataURL) - Fullscreen button (Fullscreen API) - Focus node function (center on specific node) - Filter applied during _draw() to skip hidden nodes
This commit is contained in:
parent
bf836caccc
commit
0416266dde
@ -717,6 +717,12 @@
|
||||
<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>
|
||||
@ -725,10 +731,11 @@
|
||||
<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:16px;font-size:0.7rem;color:var(--text-muted);background:var(--bg-primary);padding:6px 12px;border-radius:6px;border:1px solid var(--border-color)">
|
||||
<span>🟦 Dossier</span>
|
||||
<span>🟩 Fichier .md</span>
|
||||
<span>⬜ Autre fichier</span>
|
||||
<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>
|
||||
|
||||
@ -300,6 +300,7 @@ export const GraphViewManager = {
|
||||
// Draw nodes
|
||||
const searchLower = this._searchTerm.toLowerCase();
|
||||
for (const node of this._nodes) {
|
||||
if (!this._isNodeVisible(node)) continue;
|
||||
const p = this._nodePositions[node.id];
|
||||
if (!p) continue;
|
||||
|
||||
@ -395,6 +396,48 @@ export const GraphViewManager = {
|
||||
if (this._tooltipEl) this._tooltipEl.style.display = 'none';
|
||||
},
|
||||
|
||||
// --- Advanced controls ---
|
||||
|
||||
_isNodeVisible(node) {
|
||||
const showDir = document.getElementById('graph-filter-dir')?.checked ?? true;
|
||||
const showMd = document.getElementById('graph-filter-md')?.checked ?? true;
|
||||
const showOther = document.getElementById('graph-filter-other')?.checked ?? true;
|
||||
if (node.type === 'directory') return showDir;
|
||||
if (node.type === 'file' && (node.path || '').endsWith('.md')) return showMd;
|
||||
if (node.type === 'file') return showOther;
|
||||
return true;
|
||||
},
|
||||
|
||||
applyTypeFilter() {
|
||||
// Filters are applied during _draw() — nodes/edges not in visible set are skipped
|
||||
},
|
||||
|
||||
exportPNG() {
|
||||
const link = document.createElement('a');
|
||||
link.download = `obsigate-graph-${this._vault}-${Date.now()}.png`;
|
||||
link.href = this._canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
},
|
||||
|
||||
toggleFullscreen() {
|
||||
const container = this._canvas?.parentElement?.parentElement; // editor-container
|
||||
if (!container) return;
|
||||
if (document.fullscreenElement) {
|
||||
document.exitFullscreen();
|
||||
} else {
|
||||
container.requestFullscreen();
|
||||
}
|
||||
// Re-init layout after fullscreen change
|
||||
setTimeout(() => this._onResize(), 300);
|
||||
},
|
||||
|
||||
focusNode(nodeId) {
|
||||
const pos = this._nodePositions[nodeId];
|
||||
if (!pos) return;
|
||||
this._offsetX = this._width / 2 - pos.x * this._zoom;
|
||||
this._offsetY = this._height / 2 - pos.y * this._zoom;
|
||||
},
|
||||
|
||||
_onMouseDown(e) {
|
||||
const rect = this._canvas.getBoundingClientRect();
|
||||
const mx = e.clientX - rect.left;
|
||||
@ -520,6 +563,20 @@ export function initGraphView() {
|
||||
});
|
||||
}
|
||||
|
||||
// Export PNG
|
||||
const exportBtn = document.getElementById('graph-export');
|
||||
if (exportBtn) exportBtn.addEventListener('click', () => gm.exportPNG());
|
||||
|
||||
// Fullscreen
|
||||
const fullscreenBtn = document.getElementById('graph-fullscreen');
|
||||
if (fullscreenBtn) fullscreenBtn.addEventListener('click', () => gm.toggleFullscreen());
|
||||
|
||||
// Type filters
|
||||
['dir', 'md', 'other'].forEach(type => {
|
||||
const cb = document.getElementById(`graph-filter-${type}`);
|
||||
if (cb) cb.addEventListener('change', () => gm.applyTypeFilter());
|
||||
});
|
||||
|
||||
if (canvas) {
|
||||
canvas.addEventListener('mousedown', (e) => gm._onMouseDown(e));
|
||||
canvas.addEventListener('mousemove', (e) => gm._onMouseMove(e));
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user