import cytoscape from 'cytoscape' ; import dagre from 'cytoscape-dagre' ; import { isEqual } from 'lodash' ; import React , { createContext , CSSProperties , memo , ReactNode , useEffect , useRef , useState , } from 'react' ; import { useTheme } from '../../../hooks/use_theme' ; import { getCytoscapeOptions } from './cytoscape_options' ; import { useCytoscapeEventHandlers } from './use_cytoscape_event_handlers' ; cytoscape . use ( dagre ) ; export const CytoscapeContext = createContext < cytoscape . Core | undefined > ( undefined ) ; export interface CytoscapeProps { children ? : ReactNode ; elements : cytoscape . ElementDefinition [ ] ; height : number ; serviceName ? : string ; style ? : CSSProperties ; } function useCytoscape ( options ) { const [ cy , setCy ] = useState < cytoscape . Core | undefined > ( undefined ) ; const ref = useRef ( null ) ; useEffect ( ( ) => { if ( ! cy ) { setCy ( cytoscape ( { ... options , container : ref . current } ) ) ; } } , [ options , cy ] ) ; useEffect ( ( ) => { return ( ) => { if ( cy ) { cy . destroy ( ) ; } } ; } , [ cy ] ) ; return [ ref , cy ] as [ React . MutableRefObject < any > , cytoscape . Core | undefined ] ; } function CytoscapeComponent ( { children , elements , height , serviceName , style , } ) { const theme = useTheme ( ) ; const [ ref , cy ] = useCytoscape ( { ... getCytoscapeOptions ( theme ) , elements , } ) ; useCytoscapeEventHandlers ( { cy , serviceName , theme } ) ; useEffect ( ( ) => { if ( cy && elements . length > 0 ) { const fit = cy . elements ( ) . length === 0 ; cy . add ( elements ) ; const elementIds = elements . map ( ( element ) => element . data . id ) ; cy . elements ( ) . forEach ( ( element ) => { if ( ! elementIds . includes ( element . data ( 'id' ) ) ) { cy . remove ( element ) ; } else { const newElement = elements . find ( ( el ) => el . data . id === element . id ( ) ) ; element . data ( newElement ?. data ?? element . data ( ) ) ; } } ) ; cy . trigger ( 'custom:data' , [ fit ] ) ; } } , [ cy , elements ] ) ; const divStyle = { ... style , height } ; return ( < CytoscapeContext . Provider value = { cy } > < div ref = { ref } style = { divStyle } > { children } < /div> < /CytoscapeContext.Provider>); } export const Cytoscape = memo ( CytoscapeComponent , ( prevProps , nextProps ) => { const prevElementIds = prevProps . elements . map ( ( element ) => element . data . id ) . sort ( ) ; const nextElementIds = nextProps . elements . map ( ( element ) => element . data . id ) . sort ( ) ; const propsAreEqual = prevProps . height === nextProps . height && prevProps . serviceName === nextProps . serviceName && isEqual ( prevProps . style , nextProps . style ) && isEqual ( prevElementIds , nextElementIds ) ; return propsAreEqual ; } ) ;