Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
745 changes: 33 additions & 712 deletions codegraph/web/static/app.js

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions codegraph/web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,15 @@ <h1 class="text-lg font-semibold mt-0.5 tracking-tight truncate" id="page-title"
</div>
<div id="tooltip" class="pointer-events-none fixed opacity-0 transition-opacity z-50 bg-ink-700/95 backdrop-blur border border-ink-500/80 rounded-md px-3 py-2 text-xs shadow-card max-w-sm"></div>
<div id="toast-host" class="fixed bottom-4 right-4 z-50 space-y-2"></div>
<!-- Shared UI helpers (defines window.CGUI and window.CGViews) -->
<script src="/static/ui/helpers.js"></script>
<!-- Per-view modules (depend on CGUI; loaded before app.js) -->
<script src="/static/views/overview.js"></script>
<script src="/static/views/hld.js"></script>
<script src="/static/views/flows.js"></script>
<script src="/static/views/matrix.js"></script>
<script src="/static/views/sankey.js"></script>
<script src="/static/views/treemap.js"></script>
<script src="/static/views/graph3d_transform.js"></script>
<script src="/static/views/graph3d.js"></script>
<script src="/static/views/architecture.js"></script>
Expand Down
101 changes: 101 additions & 0 deletions codegraph/web/static/ui/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/* codegraph dashboard - shared UI helpers */
'use strict';

// Global namespaces used by all view scripts.
window.CGUI = window.CGUI || {};
window.CGViews = window.CGViews || {};

// ---- esc ----
CGUI.esc = function esc(s) {
return String(s ?? '').replace(/[&<>"']/g, c =>
({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
};

// ---- shortQn ----
CGUI.shortQn = function shortQn(qn) {
const parts = String(qn).split('.');
return parts.length > 3 ? '…' + parts.slice(-3).join('.') : qn;
};

/* HTML version of shortQn that dims the parent path and highlights the leaf.
Returns sanitized markup. */
CGUI.formatQn = function formatQn(qn, opts) {
const esc = CGUI.esc;
const max = (opts && opts.maxParts) || 3;
const parts = String(qn ?? '').split('.');
if (!parts.length) return '';
const leaf = parts[parts.length - 1];
const headParts = parts.slice(0, -1);
const truncated = headParts.length > max - 1;
const visibleHead = truncated ? headParts.slice(-(max - 1)) : headParts;
const prefix = truncated ? '…' : '';
const head = visibleHead.length
? `<span class="qn-dim">${prefix}${esc(visibleHead.join('.'))}.</span>`
: (truncated ? `<span class="qn-dim">${prefix}</span>` : '');
return `${head}<span class="qn-key">${esc(leaf)}</span>`;
};

// ---- Tooltip ----
CGUI.showTip = function showTip(html, x, y) {
const tt = document.getElementById('tooltip');
tt.innerHTML = html;
const r = tt.getBoundingClientRect();
let lx = x + 14, ly = y + 14;
if (lx + r.width > innerWidth - 8) lx = x - r.width - 14;
if (ly + r.height > innerHeight - 8) ly = y - r.height - 14;
tt.style.left = lx + 'px';
tt.style.top = ly + 'px';
tt.style.opacity = '1';
};
CGUI.hideTip = function hideTip() {
document.getElementById('tooltip').style.opacity = '0';
};

// ---- Toast ----
CGUI.toast = function toast(msg, kind) {
const host = document.getElementById('toast-host');
const el = document.createElement('div');
el.className = 'toast ' + (kind || '');
el.textContent = msg;
host.appendChild(el);
setTimeout(() => { el.style.opacity = '0'; el.style.transition = 'opacity 0.3s';
setTimeout(() => el.remove(), 350); }, 2400);
};

// ---- Mermaid theme helpers ----
CGUI.mermaidThemeVars = function mermaidThemeVars() {
const light = document.documentElement.classList.contains('theme-light');
return light
? { fontFamily: 'Inter, system-ui, sans-serif', fontSize: '14px',
background: 'transparent',
primaryColor: '#eef2ff', primaryTextColor: '#0f172a',
primaryBorderColor: '#a5b4fc', lineColor: '#6366f1',
secondaryColor: '#f5f3ff', tertiaryColor: '#ffffff',
clusterBkg: 'rgba(238,242,255,0.7)', clusterBorder: '#a5b4fc',
nodeBorder: '#a5b4fc', mainBkg: '#eef2ff',
edgeLabelBackground: '#ffffff', titleColor: '#1e293b' }
: { fontFamily: 'Inter, system-ui, sans-serif', fontSize: '14px',
background: 'transparent',
primaryColor: '#1d2942', primaryTextColor: '#e6ecf5',
primaryBorderColor: '#3b4a6a', lineColor: '#5b6b8c',
secondaryColor: '#161f33', tertiaryColor: '#0f1626',
clusterBkg: 'rgba(15,22,38,0.6)', clusterBorder: '#3b4a6a',
nodeBorder: '#3b4a6a', mainBkg: '#1d2942',
edgeLabelBackground: '#0a0f1c', titleColor: '#c4cfe2' };
};

// ---- HLD symbol kind helpers (shared by hld.js) ----
CGUI.kindIcon = function kindIcon(k) {
return k === 'CLASS' ? 'box' : k === 'METHOD' ? 'corner-down-right' : 'function-square';
};
CGUI.kindColor = function kindColor(k) {
return k === 'CLASS' ? 'var(--accent-violet)'
: k === 'METHOD' ? 'var(--accent-cyan)'
: 'var(--accent-emerald)';
};

// ---- pyvisHref (shared by explorers + files views) ----
CGUI.pyvisHref = function pyvisHref(path) {
const t = document.documentElement.classList.contains('theme-light') ? 'light' : 'dark';
return path + '?theme=' + t;
};
68 changes: 68 additions & 0 deletions codegraph/web/static/views/flows.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/* codegraph dashboard - Flows view */
'use strict';

CGViews.flows = function renderFlows(host) {
const esc = CGUI.esc;
const formatQn = CGUI.formatQn;
const flows = window.state.data.flows;
if (!flows.length) {
host.innerHTML = `<div class="p-8 text-ink-200">No call chains found.</div>`;
return;
}
if (window.state.flowSel >= flows.length) window.state.flowSel = 0;
host.innerHTML = `
<div class="p-8 max-w-7xl mx-auto">
<div class="help-card mb-6">
<i data-lucide="git-fork" class="icon w-4 h-4"></i>
<div><b>Call flow inspector.</b> Pick an entry point on the left to see its real downstream call tree (BFS depth 4).
Highlighted node = entry; arrows = CALLS edges from the actual graph.</div>
</div>
<div class="grid grid-cols-[300px_1fr] gap-4">
<div class="panel p-3 max-h-[78vh] overflow-y-auto">
<div class="search-wrap mb-3">
<i data-lucide="search"></i>
<input class="search" id="flow-search" placeholder="Filter entry points…">
</div>
<div id="flow-list" class="space-y-1"></div>
</div>
<div class="panel p-5 min-h-[600px]">
<div class="section-h"><h2 id="flow-title">Flow</h2>
<span class="pill" id="flow-meta"></span></div>
<div class="mermaid-host" id="flow-canvas"></div>
</div>
</div>
</div>`;
const list = document.getElementById('flow-list');
flows.forEach((f, i) => {
const el = document.createElement('div');
el.className = 'flow-item';
el.innerHTML = `<div class="qn">${formatQn(f.qualname, {maxParts: 3})}</div>
<div class="meta"><i data-lucide="zap" style="width:11px;height:11px"></i>
${esc(f.reason)} <span class="text-ink-300">· ${esc(f.file)}</span></div>`;
el.onclick = () => _selectFlow(i);
list.appendChild(el);
});
document.getElementById('flow-search').addEventListener('input', e => {
const q = e.target.value.toLowerCase();
[...list.children].forEach((el, i) => {
el.style.display = JSON.stringify(flows[i]).toLowerCase().includes(q) ? '' : 'none';
});
});
_selectFlow(window.state.flowSel);
};

function _selectFlow(i) {
const esc = CGUI.esc;
const formatQn = CGUI.formatQn;
window.state.flowSel = i;
const flow = window.state.data.flows[i];
if (!flow) return;
document.querySelectorAll('.flow-item').forEach((el, j) =>
el.classList.toggle('active', j === i));
document.getElementById('flow-title').innerHTML = formatQn(flow.qualname, {maxParts: 4});
document.getElementById('flow-title').classList.add('qn-mono');
document.getElementById('flow-meta').textContent = flow.reason;
const canvas = document.getElementById('flow-canvas');
canvas.innerHTML = `<pre class="mermaid">${esc(flow.mermaid)}</pre>`;
mermaid.run({ nodes: canvas.querySelectorAll('.mermaid') });
}
Loading
Loading