Skip to content

Commit e638ed8

Browse files
authored
Merge pull request #132 from Xingkai98/feat/dashboard-file-class-views
feat(dashboard): add file/class dual-view toggle to reduce graph clutter
2 parents a381c41 + e29f461 commit e638ed8

3 files changed

Lines changed: 100 additions & 3 deletions

File tree

understand-anything-plugin/packages/dashboard/src/App.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@ function Dashboard({ accessToken }: { accessToken: string }) {
109109
const togglePathFinder = useDashboardStore((s) => s.togglePathFinder);
110110
const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters);
111111
const toggleNodeTypeFilter = useDashboardStore((s) => s.toggleNodeTypeFilter);
112+
const detailLevel = useDashboardStore((s) => s.detailLevel);
113+
const setDetailLevel = useDashboardStore((s) => s.setDetailLevel);
114+
const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView);
115+
const toggleShowFunctionsInClassView = useDashboardStore((s) => s.toggleShowFunctionsInClassView);
112116
const [loadError, setLoadError] = useState<string | null>(null);
113117
const [graphIssues, setGraphIssues] = useState<GraphIssue[]>([]);
114118
const [showKeyboardHelp, setShowKeyboardHelp] = useState(false);
@@ -443,6 +447,52 @@ function Dashboard({ accessToken }: { accessToken: string }) {
443447
<div className="flex-1 min-w-0 overflow-x-auto scrollbar-hide">
444448
<div className="flex items-center gap-4 w-max">
445449
<DiffToggle />
450+
{/* Detail level: file view (architecture) / class view (code structure) */}
451+
{!isKnowledgeGraph && viewMode !== "domain" && (
452+
<>
453+
<div className="w-px h-5 bg-border-subtle" />
454+
<div className="flex items-center bg-elevated rounded-lg p-0.5">
455+
<button
456+
type="button"
457+
onClick={() => setDetailLevel("file")}
458+
title="Files only — architecture-level dependencies (fast)"
459+
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
460+
detailLevel === "file"
461+
? "bg-accent/20 text-accent"
462+
: "text-text-muted hover:text-text-secondary"
463+
}`}
464+
>
465+
Files
466+
</button>
467+
<button
468+
type="button"
469+
onClick={() => setDetailLevel("class")}
470+
title="Files + Classes — code structure with inheritance"
471+
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
472+
detailLevel === "class"
473+
? "bg-accent/20 text-accent"
474+
: "text-text-muted hover:text-text-secondary"
475+
}`}
476+
>
477+
+Classes
478+
</button>
479+
</div>
480+
{detailLevel === "class" && (
481+
<button
482+
type="button"
483+
onClick={toggleShowFunctionsInClassView}
484+
title="Toggle function nodes (may slow down rendering)"
485+
className={`text-[10px] font-semibold uppercase tracking-wider px-2 py-1 rounded border transition-colors ${
486+
showFunctionsInClassView
487+
? "border-amber-500/50 bg-amber-500/10 text-amber-400"
488+
: "border-border-medium bg-elevated text-text-muted hover:text-text-secondary"
489+
}`}
490+
>
491+
fn
492+
</button>
493+
)}
494+
</>
495+
)}
446496
<div className="flex items-center gap-1">
447497
{(isKnowledgeGraph ? [
448498
{ key: "knowledge" as const, label: "All", color: "var(--color-node-article)" },

understand-anything-plugin/packages/dashboard/src/components/GraphView.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,7 @@ function useLayerDetailTopology(): LayerDetailTopology & {
366366
layoutStatus: "computing" | "ready";
367367
} {
368368
const graph = useDashboardStore((s) => s.graph);
369+
const nodesById = useDashboardStore((s) => s.nodesById);
369370
const activeLayerId = useDashboardStore((s) => s.activeLayerId);
370371
const selectNode = useDashboardStore((s) => s.selectNode);
371372
const persona = useDashboardStore((s) => s.persona);
@@ -375,6 +376,8 @@ function useLayerDetailTopology(): LayerDetailTopology & {
375376
const focusNodeId = useDashboardStore((s) => s.focusNodeId);
376377
const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters);
377378
const drillIntoLayer = useDashboardStore((s) => s.drillIntoLayer);
379+
const detailLevel = useDashboardStore((s) => s.detailLevel);
380+
const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView);
378381

379382
const handleNodeSelect = useCallback(
380383
(nodeId: string) => {
@@ -404,10 +407,20 @@ function useLayerDetailTopology(): LayerDetailTopology & {
404407

405408
// Expand layer membership to include sub-file nodes (function/class)
406409
// whose parent file is in this layer. Joined via "contains" edges.
410+
// File view: file nodes only (architecture-level dependencies).
411+
// Class view: file nodes + class nodes, functions only when toggle is on.
407412
const expandedLayerNodeIds = new Set(layerNodeIds);
408-
for (const edge of graph.edges) {
409-
if (edge.type === "contains" && layerNodeIds.has(edge.source)) {
410-
expandedLayerNodeIds.add(edge.target);
413+
if (detailLevel !== "file") {
414+
for (const edge of graph.edges) {
415+
if (edge.type === "contains" && layerNodeIds.has(edge.source)) {
416+
const child = nodesById.get(edge.target);
417+
if (!child) continue;
418+
if (child.type === "class") {
419+
expandedLayerNodeIds.add(edge.target);
420+
} else if (child.type === "function" && showFunctionsInClassView) {
421+
expandedLayerNodeIds.add(edge.target);
422+
}
423+
}
411424
}
412425
}
413426

@@ -626,6 +639,7 @@ function useLayerDetailTopology(): LayerDetailTopology & {
626639
};
627640
}, [
628641
graph,
642+
nodesById,
629643
activeLayerId,
630644
persona,
631645
diffMode,
@@ -634,6 +648,8 @@ function useLayerDetailTopology(): LayerDetailTopology & {
634648
focusNodeId,
635649
nodeTypeFilters,
636650
drillIntoLayer,
651+
detailLevel,
652+
showFunctionsInClassView,
637653
handleNodeSelect,
638654
handleContainerToggle,
639655
]);

understand-anything-plugin/packages/dashboard/src/store.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export type NodeType = "file" | "function" | "class" | "module" | "concept" | "c
1515
export type Complexity = "simple" | "moderate" | "complex";
1616
export type EdgeCategory = "structural" | "behavioral" | "data-flow" | "dependencies" | "semantic" | "infrastructure" | "domain" | "knowledge";
1717
export type ViewMode = "structural" | "domain" | "knowledge";
18+
export type DetailLevel = "file" | "class";
1819

1920
export interface FilterState {
2021
nodeTypes: Set<NodeType>;
@@ -146,6 +147,13 @@ interface DashboardStore {
146147
nodeTypeFilters: Record<NodeCategory, boolean>;
147148
toggleNodeTypeFilter: (category: NodeCategory) => void;
148149

150+
// Detail level: "file" shows only file nodes (architecture view),
151+
// "class" shows files + class nodes (code structure view) with optional function expansion.
152+
detailLevel: DetailLevel;
153+
setDetailLevel: (level: DetailLevel) => void;
154+
showFunctionsInClassView: boolean;
155+
toggleShowFunctionsInClassView: () => void;
156+
149157
setGraph: (graph: KnowledgeGraph) => void;
150158
selectNode: (nodeId: string | null) => void;
151159
navigateToNode: (nodeId: string) => void;
@@ -331,6 +339,29 @@ export const useDashboardStore = create<DashboardStore>()((set, get) => ({
331339
pendingFocusContainer: null,
332340
})),
333341

342+
detailLevel: "file",
343+
setDetailLevel: (level) =>
344+
set({
345+
detailLevel: level,
346+
// Detail level changes which nodes are visible; cached positions stale.
347+
// Reset fn toggle so it doesn't resurrect when re-entering class view.
348+
showFunctionsInClassView: false,
349+
containerLayoutCache: new Map(),
350+
containerSizeMemory: new Map(),
351+
expandedContainers: new Set(),
352+
pendingFocusContainer: null,
353+
}),
354+
355+
showFunctionsInClassView: false,
356+
toggleShowFunctionsInClassView: () =>
357+
set((state) => ({
358+
showFunctionsInClassView: !state.showFunctionsInClassView,
359+
containerLayoutCache: new Map(),
360+
containerSizeMemory: new Map(),
361+
expandedContainers: new Set(),
362+
pendingFocusContainer: null,
363+
})),
364+
334365
setGraph: (graph) => {
335366
const searchEngine = new SearchEngine(graph.nodes);
336367
const query = get().searchQuery;

0 commit comments

Comments
 (0)