import React , { useRef , useEffect , useState , useLayoutEffect } from 'react' ; import classNames from 'classnames' ; import { Observable , Subscription } from 'rxjs' ; import { filter } from 'rxjs/operators' ; import useShallowCompareEffect from 'react-use/lib/useShallowCompareEffect' ; import { EuiLoadingChart , EuiProgress } from '@elastic/eui' ; import theme from '@elastic/eui/dist/eui_theme_light.json' ; import { IExpressionLoaderParams , ExpressionRenderError } from './types' ; import { ExpressionAstExpression , IInterpreterRenderHandlers } from '../common' ; import { ExpressionLoader } from './loader' ; import { ExpressionRendererEvent } from './render' ; export interface ReactExpressionRendererProps extends IExpressionLoaderParams { className ? : string ; dataAttrs ? : string [ ] ; expression : string | ExpressionAstExpression ; renderError ? : ( message ? : string | null , error ? : ExpressionRenderError | null ) => React . ReactElement | React . ReactElement [ ] ; padding ? : 'xs' | 's' | 'm' | 'l' | 'xl' ; onEvent ? : ( event : ExpressionRendererEvent ) => void ; /**
     * An observable which can be used to re-run the expression without destroying the component
     */ reload$ ? : Observable < unknown > ; } export type ReactExpressionRendererType = React . ComponentType < ReactExpressionRendererProps > ; interface State { isEmpty : boolean ; isLoading : boolean ; error : null | ExpressionRenderError ; } export type ExpressionRendererComponent = React . FC < ReactExpressionRendererProps > ; const defaultState = { isEmpty : true , isLoading : false , error : null , } ; export const ReactExpressionRenderer = ( { className , dataAttrs , padding , renderError , expression , onEvent , reload$ , ... expressionLoaderOptions } ) => { const mountpoint = useRef ( null ) ; const [ state , setState ] = useState < State > ( { ... defaultState } ) ; const hasCustomRenderErrorHandler = ! ! renderError ; const expressionLoaderRef = useRef ( null ) ; const hasHandledErrorRef = useRef ( false ) ; const errorRenderHandlerRef = useRef ( null ) ; useEffect ( ( ) => { const subs = [ ] ; expressionLoaderRef . current = new ExpressionLoader ( mountpoint . current ! , expression , { ... expressionLoaderOptions , onRenderError : hasCustomRenderErrorHandler ? ( domNode , error , handlers ) => { errorRenderHandlerRef . current = handlers ; setState ( ( ) => ( { ... defaultState , isEmpty : false , error , } ) ) ; if ( expressionLoaderOptions . onRenderError ) { expressionLoaderOptions . onRenderError ( domNode , error , handlers ) ; } } : expressionLoaderOptions . onRenderError , } ) ; if ( onEvent ) { subs . push ( expressionLoaderRef . current . events$ . subscribe ( ( event ) => { onEvent ( event ) ; } ) ) ; } subs . push ( expressionLoaderRef . current . loading$ . subscribe ( ( ) => { hasHandledErrorRef . current = false ; setState ( ( prevState ) => ( { ... prevState , isLoading : true } ) ) ; } ) , expressionLoaderRef . current . render$ . pipe ( filter ( ( ) => ! hasHandledErrorRef . current ) ) . subscribe ( ( item ) => { setState ( ( ) => ( { ... defaultState , isEmpty : false , } ) ) ; } ) ) ; return ( ) => { subs . forEach ( ( s ) => s . unsubscribe ( ) ) ; if ( expressionLoaderRef . current ) { expressionLoaderRef . current . destroy ( ) ; expressionLoaderRef . current = null ; } errorRenderHandlerRef . current = null ; } ; } , [ hasCustomRenderErrorHandler , onEvent ] ) ; useEffect ( ( ) => { const subscription = reload$ ?. subscribe ( ( ) => { if ( expressionLoaderRef . current ) { expressionLoaderRef . current . update ( expression , expressionLoaderOptions ) ; } } ) ; return ( ) => subscription ?. unsubscribe ( ) ; } , [ reload$ , expression , ... Object . values ( expressionLoaderOptions ) ] ) ; useShallowCompareEffect ( ( ) => { if ( expressionLoaderRef . current ) { expressionLoaderRef . current . update ( expression , expressionLoaderOptions ) ; } } , [ { expression , ... expressionLoaderOptions } ] ) ; useLayoutEffect ( ( ) => { if ( state . error && errorRenderHandlerRef . current ) { hasHandledErrorRef . current = true ; errorRenderHandlerRef . current . done ( ) ; errorRenderHandlerRef . current = null ; } } , [ state . error ] ) ; const classes = classNames ( 'expExpressionRenderer' , { 'expExpressionRenderer-isEmpty' : state . isEmpty , 'expExpressionRenderer-hasError' : ! ! state . error , className , } ) ; const expressionStyles = { } ; if ( padding ) { expressionStyles . padding = theme . paddingSizes [ padding ] ; } return ( < div { ... dataAttrs } className = { classes } > { state . isEmpty && < EuiLoadingChart mono size = "l" / > } { state . isLoading && < EuiProgress size = "xs" color = "accent" position = "absolute" / > } { ! state . isLoading && state . error && renderError && renderError ( state . error . message , state . error ) } < div className = "expExpressionRenderer__expression" style = { expressionStyles } ref = { mountpoint } / > < /div>); } ;