mirror of
https://github.com/reconurge/flowsint.git
synced 2026-07-19 23:50:14 -05:00
feat(app): virtualize relationship items
This commit is contained in:
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user