186 lines
6.1 KiB
TypeScript
186 lines
6.1 KiB
TypeScript
import { useEffect, useState, useCallback, useMemo } from 'react'
|
|
// import { MiniMap } from '@react-sigma/minimap'
|
|
import { SigmaContainer, useRegisterEvents, useSigma } from '@react-sigma/core'
|
|
import { Settings as SigmaSettings } from 'sigma/settings'
|
|
import { GraphSearchOption, OptionItem } from '@react-sigma/graph-search'
|
|
import { EdgeArrowProgram, NodePointProgram, NodeCircleProgram } from 'sigma/rendering'
|
|
import { NodeBorderProgram } from '@sigma/node-border'
|
|
import EdgeCurveProgram, { EdgeCurvedArrowProgram } from '@sigma/edge-curve'
|
|
|
|
import FocusOnNode from '@/components/FocusOnNode'
|
|
import LayoutsControl from '@/components/LayoutsControl'
|
|
import GraphControl from '@/components/GraphControl'
|
|
import ThemeToggle from '@/components/ThemeToggle'
|
|
import ZoomControl from '@/components/ZoomControl'
|
|
import FullScreenControl from '@/components/FullScreenControl'
|
|
import Settings from '@/components/Settings'
|
|
import GraphSearch from '@/components/GraphSearch'
|
|
import GraphLabels from '@/components/GraphLabels'
|
|
import PropertiesView from '@/components/PropertiesView'
|
|
|
|
import { useSettingsStore } from '@/stores/settings'
|
|
import { useGraphStore } from '@/stores/graph'
|
|
|
|
import '@react-sigma/core/lib/style.css'
|
|
import '@react-sigma/graph-search/lib/style.css'
|
|
|
|
// Sigma settings
|
|
const defaultSigmaSettings: Partial<SigmaSettings> = {
|
|
allowInvalidContainer: true,
|
|
defaultNodeType: 'default',
|
|
defaultEdgeType: 'curvedArrow',
|
|
renderEdgeLabels: false,
|
|
edgeProgramClasses: {
|
|
arrow: EdgeArrowProgram,
|
|
curvedArrow: EdgeCurvedArrowProgram,
|
|
curvedNoArrow: EdgeCurveProgram
|
|
},
|
|
nodeProgramClasses: {
|
|
default: NodeBorderProgram,
|
|
circel: NodeCircleProgram,
|
|
point: NodePointProgram
|
|
},
|
|
labelGridCellSize: 60,
|
|
labelRenderedSizeThreshold: 12,
|
|
enableEdgeEvents: true,
|
|
labelColor: {
|
|
color: '#000',
|
|
attribute: 'labelColor'
|
|
},
|
|
edgeLabelColor: {
|
|
color: '#000',
|
|
attribute: 'labelColor'
|
|
},
|
|
edgeLabelSize: 8,
|
|
labelSize: 12
|
|
// minEdgeThickness: 2
|
|
// labelFont: 'Lato, sans-serif'
|
|
}
|
|
|
|
const GraphEvents = () => {
|
|
const registerEvents = useRegisterEvents()
|
|
const sigma = useSigma()
|
|
const [draggedNode, setDraggedNode] = useState<string | null>(null)
|
|
|
|
useEffect(() => {
|
|
// Register the events
|
|
registerEvents({
|
|
downNode: (e) => {
|
|
setDraggedNode(e.node)
|
|
sigma.getGraph().setNodeAttribute(e.node, 'highlighted', true)
|
|
},
|
|
// On mouse move, if the drag mode is enabled, we change the position of the draggedNode
|
|
mousemovebody: (e) => {
|
|
if (!draggedNode) return
|
|
// Get new position of node
|
|
const pos = sigma.viewportToGraph(e)
|
|
sigma.getGraph().setNodeAttribute(draggedNode, 'x', pos.x)
|
|
sigma.getGraph().setNodeAttribute(draggedNode, 'y', pos.y)
|
|
|
|
// Prevent sigma to move camera:
|
|
e.preventSigmaDefault()
|
|
e.original.preventDefault()
|
|
e.original.stopPropagation()
|
|
},
|
|
// On mouse up, we reset the autoscale and the dragging mode
|
|
mouseup: () => {
|
|
if (draggedNode) {
|
|
setDraggedNode(null)
|
|
sigma.getGraph().removeNodeAttribute(draggedNode, 'highlighted')
|
|
}
|
|
},
|
|
// Disable the autoscale at the first down interaction
|
|
mousedown: () => {
|
|
if (!sigma.getCustomBBox()) sigma.setCustomBBox(sigma.getBBox())
|
|
}
|
|
})
|
|
}, [registerEvents, sigma, draggedNode])
|
|
|
|
return null
|
|
}
|
|
|
|
const GraphViewer = () => {
|
|
const [sigmaSettings, setSigmaSettings] = useState(defaultSigmaSettings)
|
|
|
|
const selectedNode = useGraphStore.use.selectedNode()
|
|
const focusedNode = useGraphStore.use.focusedNode()
|
|
const moveToSelectedNode = useGraphStore.use.moveToSelectedNode()
|
|
|
|
const showPropertyPanel = useSettingsStore.use.showPropertyPanel()
|
|
const showNodeSearchBar = useSettingsStore.use.showNodeSearchBar()
|
|
const renderLabels = useSettingsStore.use.showNodeLabel()
|
|
|
|
const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents()
|
|
const enableNodeDrag = useSettingsStore.use.enableNodeDrag()
|
|
const renderEdgeLabels = useSettingsStore.use.showEdgeLabel()
|
|
|
|
useEffect(() => {
|
|
setSigmaSettings({
|
|
...defaultSigmaSettings,
|
|
enableEdgeEvents,
|
|
renderEdgeLabels,
|
|
renderLabels
|
|
})
|
|
}, [renderLabels, enableEdgeEvents, renderEdgeLabels])
|
|
|
|
const onSearchFocus = useCallback((value: GraphSearchOption | null) => {
|
|
if (value === null) useGraphStore.getState().setFocusedNode(null)
|
|
else if (value.type === 'nodes') useGraphStore.getState().setFocusedNode(value.id)
|
|
}, [])
|
|
|
|
const onSearchSelect = useCallback((value: GraphSearchOption | null) => {
|
|
if (value === null) {
|
|
useGraphStore.getState().setSelectedNode(null)
|
|
} else if (value.type === 'nodes') {
|
|
useGraphStore.getState().setSelectedNode(value.id, true)
|
|
}
|
|
}, [])
|
|
|
|
const autoFocusedNode = useMemo(() => focusedNode ?? selectedNode, [focusedNode, selectedNode])
|
|
const searchInitSelectedNode = useMemo(
|
|
(): OptionItem | null => (selectedNode ? { type: 'nodes', id: selectedNode } : null),
|
|
[selectedNode]
|
|
)
|
|
|
|
return (
|
|
<SigmaContainer settings={sigmaSettings} className="!bg-background !size-full overflow-hidden">
|
|
<GraphControl />
|
|
|
|
{enableNodeDrag && <GraphEvents />}
|
|
|
|
<FocusOnNode node={autoFocusedNode} move={moveToSelectedNode} />
|
|
|
|
<div className="absolute top-2 left-2 flex items-start gap-2">
|
|
<GraphLabels />
|
|
{showNodeSearchBar && (
|
|
<GraphSearch
|
|
value={searchInitSelectedNode}
|
|
onFocus={onSearchFocus}
|
|
onChange={onSearchSelect}
|
|
/>
|
|
)}
|
|
</div>
|
|
|
|
<div className="bg-background/60 absolute bottom-2 left-2 flex flex-col rounded-xl border-2 backdrop-blur-lg">
|
|
<Settings />
|
|
<ZoomControl />
|
|
<LayoutsControl />
|
|
<FullScreenControl />
|
|
<ThemeToggle />
|
|
</div>
|
|
|
|
{showPropertyPanel && (
|
|
<div className="absolute top-2 right-2">
|
|
<PropertiesView />
|
|
</div>
|
|
)}
|
|
|
|
{/* <div className="absolute bottom-2 right-2 flex flex-col rounded-xl border-2">
|
|
<MiniMap width="100px" height="100px" />
|
|
</div> */}
|
|
</SigmaContainer>
|
|
)
|
|
}
|
|
|
|
export default GraphViewer
|