feat(app): virtualize relationship items

This commit is contained in:
dextmorgn
2025-11-12 23:19:18 +01:00
parent 3fe6184fc5
commit a31df42086
@@ -5,8 +5,11 @@ import { Badge } from '@/components/ui/badge'
import { useGraphStore } from '@/stores/graph-store'
import { useQuery } from '@tanstack/react-query'
import { ArrowRight } from 'lucide-react'
import { memo, useCallback } from 'react'
import { memo, useCallback, useRef } from 'react'
import { GraphEdge, GraphNode } from '@/types'
import { useVirtualizer } from '@tanstack/react-virtual'
const ITEM_HEIGHT = 36 // 32px badge height + 4px gap
type Relation = {
source: GraphNode
@@ -34,32 +37,92 @@ const Relationships = memo(
const relationships = getInlineRelationships(neighborsData?.nds || [], neighborsData?.rls || [])
if (isLoading)
if (isLoading) {
return (
<div className="flex items-center justify-center grow h-full">
<div className="flex items-center justify-center h-full">
<Loader />
</div>
)
}
return (
<div className="h-full overflow-auto py-3 px-3 space-y-1">
{relationships.map((rel, index) => (
<Badge key={index} variant={'outline'} className="h-8 px-2 py-1 w-full">
<div className="flex items-center gap-1 w-full min-w-0 h-full">
<div className="min-w-0 flex-1 flex items-center">
<RelationshipItem node={rel.source} />
if (relationships.length === 0) {
return (
<div className="flex items-center justify-center h-full text-muted-foreground text-sm">
No relationships found
</div>
)
}
return <VirtualizedList relationships={relationships} />
}
)
const VirtualizedList = memo(({ relationships }: { relationships: Relation[] }) => {
const parentRef = useRef<HTMLDivElement>(null)
const virtualizer = useVirtualizer({
count: relationships.length,
getScrollElement: () => parentRef.current,
estimateSize: () => ITEM_HEIGHT,
overscan: 5
})
const virtualItems = virtualizer.getVirtualItems()
console.log('VirtualizedList Debug:', {
relationshipsCount: relationships.length,
totalSize: virtualizer.getTotalSize(),
virtualItemsCount: virtualItems.length,
firstItem: virtualItems[0],
parentRefCurrent: parentRef.current
})
return (
<div ref={parentRef} className="h-full overflow-auto">
<div
style={{
height: `${virtualizer.getTotalSize()}px`,
width: '100%',
position: 'relative',
paddingTop: '0.75rem',
paddingBottom: '0.75rem'
}}
>
{virtualItems.map((virtualRow) => {
const rel = relationships[virtualRow.index]
return (
<div
key={virtualRow.key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${virtualRow.size}px`,
transform: `translateY(${virtualRow.start}px)`
}}
className="pb-1 px-3"
>
<Badge variant={'outline'} className="h-8 px-2 py-1 w-full block">
<div className="flex items-center gap-1 w-full min-w-0 h-full">
<div className="min-w-0 flex-1 flex items-center">
<RelationshipItem node={rel.source} />
</div>
<ArrowRight className="flex-shrink-0 opacity-60 h-4 w-4" />
<div className="min-w-0 flex-1 flex items-center justify-center">
<span className="opacity-60 text-xs truncate block">{rel.edge.label}</span>
</div>
<ArrowRight className="flex-shrink-0 opacity-60 h-4 w-4" />
<div className="min-w-0 flex-1 flex items-center justify-end">
<RelationshipItem node={rel.target} />
</div>
</div>
</Badge>
</div>
<ArrowRight className="flex-shrink-0 opacity-60 h-4 w-4" />
<div className="min-w-0 flex-1 flex items-center justify-center">
<span className="opacity-60 text-xs truncate block">{rel.edge.label}</span>
</div>
<ArrowRight className="flex-shrink-0 opacity-60 h-4 w-4" />
<div className="min-w-0 flex-1 flex items-center justify-end">
<RelationshipItem node={rel.target} />
</div>
</div>
</Badge>
))}
)
})}
</div>
</div>
)
}
@@ -71,7 +134,7 @@ const RelationshipItem = memo(({ node }: { node: GraphNode }) => {
const setCurrentNode = useGraphStore((s) => s.setCurrentNode)
const handleClick = useCallback(() => {
setCurrentNode(node)
}, [setCurrentNode])
}, [setCurrentNode, node])
return (
<button
className="w-full h-full text-left hover:underline cursor-pointer flex items-center"