feat: panneaux fixes — tuile info top-right + preview pleine hauteur droite
All checks were successful
CI / lint (push) Successful in 16s
CI / security (push) Successful in 9s
CI / test (push) Successful in 17s
CI / build (push) Successful in 3s

This commit is contained in:
Bruno Charest 2026-05-30 07:52:01 -04:00
parent a88be85623
commit de9f4b0bb5
2 changed files with 72 additions and 59 deletions

View File

@ -724,7 +724,12 @@
</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>
<!-- Info panel: top-right, shows on hover -->
<div id="graph-info-panel" style="display:none;position:absolute;top:10px;right:10px;max-width:320px;background:var(--bg-primary);border:1px solid var(--border-color);border-radius:8px;padding:12px 14px;font-size:0.78rem;color:var(--text-primary);z-index:10;box-shadow:0 4px 16px rgba(0,0,0,0.4);pointer-events:none">
</div>
<!-- Preview panel: right sidebar, full height, shows on Ctrl+hover -->
<div id="graph-preview-panel" style="display:none;position:absolute;top:0;right:0;width:380px;height:100%;background:var(--bg-primary);border-left:1px solid var(--border-color);z-index:9;overflow-y:auto;padding:16px;font-size:0.8rem;color:var(--text-primary);box-shadow:-4px 0 16px rgba(0,0,0,0.3)">
</div>
<!-- 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>

View File

@ -52,7 +52,8 @@ export const GraphViewManager = {
_searchTerm: '',
_tagFilter: '',
_hoveredNode: null,
_tooltipEl: null,
_infoPanel: null,
_previewPanel: null,
_previewCache: {},
_previewPending: null,
_ctrlHoverNode: null,
@ -69,7 +70,8 @@ export const GraphViewManager = {
if (!modal || !canvas) return;
this._tooltipEl = document.getElementById('graph-tooltip');
this._infoPanel = document.getElementById('graph-info-panel');
this._previewPanel = document.getElementById('graph-preview-panel');
this._depth = depthSlider ? parseInt(depthSlider.value) : 1;
this._scope = 'directory';
@ -532,42 +534,80 @@ export const GraphViewManager = {
},
_showTooltip(node, screenX, screenY, isPreview) {
if (!this._tooltipEl) return;
if (isPreview) return; // Preview tooltip handled by _showPreviewTooltip
if (isPreview) return; // Handled by _showPreviewTooltip
if (!this._infoPanel) return;
const cacheKey = `${this._vault}:${node.path}`;
const cached = this._previewCache[cacheKey];
const meta = cached?.meta;
// Build metadata summary if available
let metaHtml = '';
// Build metadata summary
let metaRows = '';
if (meta?.frontmatter && Object.keys(meta.frontmatter).length > 0) {
const fm = meta.frontmatter;
const parts = [];
if (fm.auteur) parts.push(`✍️ ${fm.auteur}`);
if (fm.date || fm.creation_date) parts.push(`📅 ${fm.date || fm.creation_date}`);
if (fm.statut) parts.push(`📌 ${fm.statut}`);
if (fm.publish) parts.push('✅ publié');
if (fm.favoris) parts.push('⭐ favoris');
if (parts.length > 0) metaHtml = `<br><span style="color:${COLORS.muted};font-size:0.65rem">${parts.join(' · ')}</span>`;
const rows = [];
if (fm.auteur) rows.push(`<div><span style="color:var(--text-muted)">✍️ Auteur</span> <span>${fm.auteur}</span></div>`);
if (fm.date || fm.creation_date) rows.push(`<div><span style="color:var(--text-muted)">📅 Date</span> <span>${fm.date || fm.creation_date}</span></div>`);
if (fm.statut) rows.push(`<div><span style="color:var(--text-muted)">📌 Statut</span> <span>${fm.statut}</span></div>`);
if (fm.catégorie || fm.categorie) rows.push(`<div><span style="color:var(--text-muted)">📂 Catégorie</span> <span>${fm.catégorie || fm.categorie}</span></div>`);
if (fm.publish) rows.push('<div><span style="color:var(--text-muted)">✅</span> <span>Publié</span></div>');
if (fm.favoris) rows.push('<div><span style="color:var(--text-muted)">⭐</span> <span>Favoris</span></div>');
if (rows.length > 0) metaRows = rows.join('');
}
const tags = (meta?.tags || node.tags || []).slice(0, 5).join(', ');
const tags = (meta?.tags || node.tags || []).slice(0, 6);
const inc = node.incoming_count || 0;
const out = node.outgoing_count || 0;
this._tooltipEl.innerHTML = `
<strong>${meta?.title || node.name}</strong>
${node.type === 'file' ? `<br><span style="color:${COLORS.muted};font-size:0.7rem">${node.path}</span>` : ''}
${metaHtml}
${tags ? `<br>🏷️ ${tags}` : ''}
${inc + out > 0 ? `<br>🔗 ${out} sortants · ${inc} entrants` : ''}
${node.type === 'file' ? `<br><span style="color:${COLORS.muted};font-size:0.6rem;opacity:0.7">Ctrl+survol pour aperçu</span>` : ''}
const typeIcon = node.type === 'directory' ? '📁' : '📄';
const typeLabel = node.type === 'directory' ? 'Dossier' : (node.path || '').endsWith('.md') ? 'Markdown' : 'Fichier';
this._infoPanel.innerHTML = `
<div style="display:flex;align-items:flex-start;gap:8px;margin-bottom:8px">
<span style="font-size:1.2rem">${typeIcon}</span>
<div style="flex:1;min-width:0">
<div style="font-weight:600;font-size:0.85rem;word-break:break-word">${meta?.title || node.name}</div>
<div style="color:var(--text-muted);font-size:0.7rem;word-break:break-all">${node.path}</div>
</div>
</div>
<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:6px">
<span style="font-size:0.6rem;background:var(--bg-secondary);padding:1px 6px;border-radius:3px;color:var(--text-muted)">${typeLabel}</span>
${inc + out > 0 ? `<span style="font-size:0.6rem;background:var(--bg-secondary);padding:1px 6px;border-radius:3px;color:var(--text-muted)">🔗 ${out}→ · ${inc}←</span>` : ''}
</div>
${metaRows ? `<div style="font-size:0.72rem;display:flex;flex-direction:column;gap:2px;padding:6px 0;border-top:1px solid var(--border-color);border-bottom:1px solid var(--border-color);margin-bottom:6px">${metaRows}</div>` : ''}
${tags.length > 0 ? `<div style="display:flex;gap:4px;flex-wrap:wrap">${tags.map(t => `<span style="font-size:0.65rem;background:var(--accent-color);color:#fff;padding:1px 6px;border-radius:3px;opacity:0.8">#${t}</span>`).join('')}</div>` : ''}
${node.type === 'file' ? '<div style="font-size:0.6rem;color:var(--text-muted);margin-top:6px;opacity:0.6">Ctrl+survol = aperçu complet</div>' : ''}
`;
this._tooltipEl.style.display = 'block';
this._tooltipEl.style.maxWidth = '280px';
this._tooltipEl.style.left = (screenX + 15) + 'px';
this._tooltipEl.style.top = (screenY - 10) + 'px';
this._infoPanel.style.display = 'block';
},
_hideTooltip() {
if (this._tooltipEl) this._tooltipEl.style.display = 'none';
if (this._infoPanel) this._infoPanel.style.display = 'none';
if (this._previewPanel) this._previewPanel.style.display = 'none';
},
_showPreviewTooltip(node, preview, screenX, screenY) {
if (!this._previewPanel || this._ctrlHoverNode !== node) return;
const cacheKey = `${this._vault}:${node.path}`;
const cached = this._previewCache[cacheKey];
const meta = cached?.meta;
// Format frontmatter summary
let fmSummary = '';
if (meta?.frontmatter && Object.keys(meta.frontmatter).length > 0) {
const fm = meta.frontmatter;
const items = [];
if (fm.auteur) items.push(`✍️ ${fm.auteur}`);
if (fm.date || fm.creation_date) items.push(`📅 ${fm.date || fm.creation_date}`);
if (fm.statut) items.push(`📌 ${fm.statut}`);
if (fm.publish) items.push('✅ publié');
if (fm.favoris) items.push('⭐ favoris');
if (items.length > 0) fmSummary = `<div style="font-size:0.7rem;color:var(--text-muted);margin:4px 0">${items.join(' · ')}</div>`;
}
const tags = (meta?.tags || node.tags || []).slice(0, 8);
this._previewPanel.innerHTML = `
<div style="font-weight:700;font-size:0.95rem;margin-bottom:4px;word-break:break-word">${meta?.title || node.name}</div>
<div style="color:var(--text-muted);font-size:0.7rem;margin-bottom:6px;word-break:break-all">${node.path}</div>
${fmSummary}
${tags.length > 0 ? `<div style="display:flex;gap:4px;flex-wrap:wrap;margin-bottom:10px">${tags.map(t => `<span style="font-size:0.65rem;background:var(--accent-color);color:#fff;padding:1px 6px;border-radius:3px;opacity:0.8">#${t}</span>`).join('')}</div>` : ''}
<div style="border-top:1px solid var(--border-color);padding-top:10px;font-size:0.82rem;line-height:1.6;white-space:pre-wrap;word-break:break-word">${preview}</div>
`;
this._previewPanel.style.display = 'block';
},
// --- Advanced controls ---
@ -702,38 +742,6 @@ export const GraphViewManager = {
}
},
_showPreviewTooltip(node, preview, screenX, screenY) {
if (!this._tooltipEl || this._ctrlHoverNode !== node) return;
const cacheKey = `${this._vault}:${node.path}`;
const cached = this._previewCache[cacheKey];
const meta = cached?.meta;
// Format frontmatter summary
let fmSummary = '';
if (meta?.frontmatter && Object.keys(meta.frontmatter).length > 0) {
const fm = meta.frontmatter;
const items = [];
if (fm.auteur) items.push(`✍️ ${fm.auteur}`);
if (fm.date || fm.creation_date) items.push(`📅 ${fm.date || fm.creation_date}`);
if (fm.statut) items.push(`📌 ${fm.statut}`);
if (fm.catégorie || fm.categorie) items.push(`📂 ${fm.catégorie || fm.categorie}`);
if (fm.publish) items.push('✅ publié');
if (fm.favoris) items.push('⭐ favoris');
if (items.length > 0) fmSummary = `<div style="font-size:0.65rem;color:${COLORS.muted};margin:3px 0">${items.join(' · ')}</div>`;
}
const tags = (meta?.tags || node.tags || []).slice(0, 5).join(', ');
this._tooltipEl.innerHTML = `
<strong>${meta?.title || node.name}</strong>
<span style="color:${COLORS.muted};font-size:0.65rem;display:block;margin:2px 0">${node.path}</span>
${fmSummary}
${tags ? `<span style="color:${COLORS.accent};font-size:0.65rem">🏷️ ${tags}</span>` : ''}
<div style="margin-top:4px;padding-top:4px;border-top:1px solid ${COLORS.border};font-size:0.75rem;color:${COLORS.text};line-height:1.5;max-height:150px;overflow-y:auto;white-space:pre-wrap;word-break:break-word">${preview}</div>
`;
this._tooltipEl.style.display = 'block';
this._tooltipEl.style.maxWidth = '420px';
this._tooltipEl.style.left = Math.min(screenX + 15, window.innerWidth - 440) + 'px';
this._tooltipEl.style.top = Math.min(screenY - 10, window.innerHeight - 220) + 'px';
},
_onMouseUp(e) {
if (this._dragging && this._dragNode) {
const node = this._dragNode.node;