import { observable } from 'mobx' ; import { useEffect , useState } from 'react' ; import { IServiceConstructor , useService } from '@cloudbeaver/core-di' ; import { NotificationService } from '@cloudbeaver/core-events' ; import { CachedResourceIncludeArgs , CachedMapResource , CachedMapResourceGetter , ResourceKey , CachedMapResourceValue , CachedMapResourceKey , CachedMapResourceArguments , CachedMapResourceLoader , ResourceKeyList , CachedMapResourceListGetter } from '@cloudbeaver/core-sdk' ; import type { ILoadableState } from './Loader/Loader' ; import { useObservableRef } from './useObservableRef' ; interface IActions < TKeyArg extends ResourceKey < CachedMapResourceKey < TResource > > , TResource extends CachedMapResource < any , any , any > , TIncludes > { isActive ? : ( resource : TResource ) => Promise < boolean > | boolean ; onLoad ? : ( resource : TResource ) => Promise < boolean | void > | boolean | void ; onData ? : ( data : CachedMapResourceLoader < TKeyArg , CachedMapResourceKey < TResource > , CachedMapResourceValue < TResource > , TIncludes > , resource : TResource , prevData : CachedMapResourceLoader < TKeyArg , CachedMapResourceKey < TResource > , CachedMapResourceValue < TResource > , TIncludes > | undefined ) => Promise < any > | any ; onError ? : ( exception : Error ) => void ; preload ? : Array < IMapResourceState < any > > ; } interface KeyWithIncludes < TKey , TIncludes > { key : TKey | null ; includes : TIncludes ; } interface IMapResourceState < TResource extends CachedMapResource < any , any , any > > extends ILoadableState { resource : TResource ; isOutdated : ( ) => boolean ; } interface IMapResourceListResult < TResource extends CachedMapResource < any , any , any > , TIncludes > extends IMapResourceState < TResource > { data : CachedMapResourceListGetter < CachedMapResourceValue < TResource > , TIncludes > ; exception : Error [ ] | null ; } interface IMapResourceResult < TResource extends CachedMapResource < any , any , any > , TIncludes > extends IMapResourceState < TResource > { data : CachedMapResourceGetter < CachedMapResourceValue < TResource > , TIncludes > ; exception : Error | null ; } export function useMapResource < TResource extends CachedMapResource < any , any , any > , TIncludes extends CachedResourceIncludeArgs < CachedMapResourceValue < TResource > , CachedMapResourceArguments < TResource > > = [ ] > ( component , ctor , keyObj , actions ? ) ; export function useMapResource < TResource extends CachedMapResource < any , any , any > , TIncludes extends CachedResourceIncludeArgs < CachedMapResourceValue < TResource > , CachedMapResourceArguments < TResource > > = [ ] > ( component , ctor , keyObj , actions ? ) ; export function useMapResource < TResource extends CachedMapResource < any , any , any > , TKeyArg extends ResourceKey < CachedMapResourceKey < TResource > > , TIncludes extends CachedResourceIncludeArgs < CachedMapResourceValue < TResource > , CachedMapResourceArguments < TResource > > = [ ] > ( component , ctor , keyObj , actions ? ) { const resource = ctor instanceof CachedMapResource ? ctor : useService ( ctor ) ; const notifications = useService ( NotificationService ) ; const [ exception , setException ] = useState < Error | null > ( null ) ; let key = keyObj as TKeyArg ; let includes = [ ] as TIncludes ; const [ loadFunctionName ] = useState ( component . name + '_' + resource . getName ( ) ) ; if ( isKeyWithIncludes < TKeyArg , TIncludes > ( keyObj ) ) { key = keyObj . key ; includes = keyObj . includes ; } const refObj = useObservableRef ( ( ) => ( { loading : false , firstRender : true , prevData : undefined as CachedMapResourceLoader < TKeyArg , CachedMapResourceKey < TResource > , CachedMapResourceValue < TResource > , TIncludes > | undefined , get preloaded ( ) : boolean { if ( this . actions ?. preload ) { for ( const preload of this . actions . preload ) { if ( ! preload . isLoaded ( ) || preload . isOutdated ( ) || ( Array . isArray ( preload . exception ) ? preload . exception . some ( Boolean ) : ! ! preload . exception ) ) { return false ; } } } return true ; } , async [ loadFunctionName ] ( refresh ? ) { const { resource , actions , prevData , key , includes } = this ; if ( this . loading ) { return ; } try { const active = await actions ?. isActive ?. ( resource ) ; if ( active === false ) { return ; } this . firstRender = false ; this . loading = true ; const prevent = await actions ?. onLoad ?. ( resource ) ; if ( key === null || prevent === true ) { setException ( null ) ; return ; } if ( refresh ) { resource . markOutdated ( key ) ; } const newData = await resource . load ( key , includes as any ) ; this . prevData = newData ; await actions ?. onData ?. ( newData , resource , prevData ) ; setException ( null ) ; } catch ( exception ) { const resourceException = resource . getException ( key ) ; if ( ! resourceException || ( Array . isArray ( resourceException ) && ! resourceException . some ( Boolean ) ) ) { setException ( exception ) ; } actions ?. onError ?. ( exception ) ; if ( ! this . exceptionObserved ) { notifications . logException ( exception , 'Can\'t load data' ) ; } } finally { this . loading = false ; } } , } ) , { loading : observable . ref , } , { exceptionObserved : false , resource , key , exception , includes , actions , } ) ; const outdated = ( ( resource . isOutdated ( key ) || ! resource . isLoaded ( key , includes as any ) ) && ! resource . isDataLoading ( key ) ) ; const [ result ] = useState < IMapResourceResult < TResource , TIncludes > | IMapResourceListResult < TResource , TIncludes > > ( ( ) => ( { get resource ( ) { return refObj . resource ; } , get exception ( ) { refObj . exceptionObserved = true ; return refObj . exception || resource . getException ( key ) ; } , get data ( ) { if ( refObj . key === null ) { return undefined ; } return resource . get ( refObj . key ) ; } , isOutdated : ( ) => { if ( refObj . key === null || ! refObj . preloaded ) { return true ; } return resource . isOutdated ( key ) ; } , isLoaded ( ) { if ( refObj . key === null ) { return true ; } if ( Array . isArray ( this . exception ) ? this . exception . some ( Boolean ) : ! ! this . exception ) { return false ; } return resource . isLoaded ( refObj . key , refObj . includes as any ) ; } , reload : ( ) => { ( refObj as any ) [ loadFunctionName ] ( true ) ; } , isLoading : ( ) => { if ( refObj . key === null ) { return false ; } return refObj . loading || resource . isDataLoading ( refObj . key ) ; } , } ) ) ; const preloaded = refObj . preloaded ; useEffect ( ( ) => { if ( ! preloaded || ( ! outdated && ! refObj . firstRender ) ) { return ; } if ( result . exception === null || ( Array . isArray ( result . exception ) && ! result . exception . some ( Boolean ) ) ) { ( refObj as any ) [ loadFunctionName ] ( ) ; } } ) ; return result ; } function isKeyWithIncludes < TKey , TIncludes > ( obj ) { return obj && typeof obj === 'object' && 'includes' in obj && 'key' in obj ; }