import background from '@ringcentral-integration/commons/lib/background' ; import { Module } from '@ringcentral-integration/commons/lib/di' ; import proxify from '@ringcentral-integration/commons/lib/proxy/proxify' ; import { action , computed , RcUIModuleV2 , state , } from '@ringcentral-integration/core' ; import { filter , find , findIndex , map } from 'ramda' ; import { v4 } from 'uuid' ; import { ModalProps , ModalV2UIFunctions , ModalV2UIProps , } from '../../components/ModalV2/interface' ; import { AlertModalOptions , ConfirmModalOptions , CustomRenderer , CustomRendererProps , DehydratedModalState , Deps , HandlerFunction , InfoModalOptions , ModalOptions , } from './ModalUI.interface' ; import { defaultCancelRenderer , defaultCancelRendererID , defaultOKRenderer , defaultOKRendererID , infoTitleRenderer , infoTitleRendererID , } from './utils' ; @ Module ( { name : 'ModalUI' , deps : [ 'Locale' ] , } ) export class ModalUI extends RcUIModuleV2 < Deps > { constructor ( deps ) { super ( { deps , } ) ; this . registerRenderer ( defaultOKRendererID , defaultOKRenderer ) ; this . registerRenderer ( defaultCancelRendererID , defaultCancelRenderer ) ; this . registerRenderer ( infoTitleRendererID , infoTitleRenderer ) ; } @ state private _modals : DehydratedModalState [ ] = [ ] ; @ action private _addModal ( modalState ) { this . _modals . push ( modalState ) ; } @ action private _updateModal ( modalState ) { const idx = findIndex ( ( item ) => item . id === modalState . id , this . _modals ) ; if ( idx === - 1 ) { throw new Error ( `modal id "${ modalState . id } not found` ) ; } this . _modals [ idx ] = modalState ; } @ action private _removeModal ( id ) { this . _modals = filter ( ( item ) => item . id !== id , this . _modals ) ; } private _rendererRegister = new Map < string , CustomRenderer < any > > ( ) ; private _handlerRegister = new Map < string , Map < string , HandlerFunction > > ( ) ; private _promises = new Map < string , { promise : Promise < boolean > ; resolve : ( ok : boolean ) => void ; } > ( ) ; @ proxify private async _genericHandler ( id , handlerID , ... args ) { return this . _handlerRegister . get ( id ) ?. get ( handlerID ) ?. ( ... args ) ; } @ action private _setLoading ( id , loading ) { const idx = findIndex ( ( item ) => item . id === id , this . _modals ) ; if ( this . _modals [ idx ] . useLoadingOverlay ) { this . _modals [ idx ] . loadingOverlay = loading ; } else { this . _modals [ idx ] . loading = loading ; } } @ proxify private async _onConfirm ( id , onConfirm ? ) { this . _setLoading ( id , true ) ; const handler = this . _handlerRegister . get ( id ) . get ( onConfirm ) ; if ( handler ) { try { if ( ( await handler ( ) ) === false ) { this . _setLoading ( id , false ) ; return ; } } catch ( err ) { this . _setLoading ( id , false ) ; throw err ; } } this . _promises . get ( id ) . resolve ( true ) ; this . _promises . delete ( id ) ; this . close ( id ) ; } @ proxify private async _onExited ( id , onExited ? ) { this . _promises . get ( id ) ?. resolve ( false ) ; this . _handlerRegister . get ( id ) . get ( onExited ) ?. ( ) ; this . _promises . delete ( id ) ; this . _removeModal ( id ) ; this . _handlerRegister . delete ( id ) ; } @ proxify private async _onCancel ( id , onCancel ? ) { this . _handlerRegister . get ( id ) . get ( onCancel ) ?. ( ) ; this . close ( id ) ; } private _registerHandler ( id , handler ) { const handlerID = v4 ( ) ; this . _handlerRegister . get ( id ) . set ( handlerID , handler ) ; return handlerID ; } private _removeHandler ( id , handlerID ) { this . _handlerRegister . get ( id ) . delete ( handlerID ) ; } example registerRenderer < T extends CustomRendererProps = CustomRendererProps > ( id , renderer ) { this . _rendererRegister . set ( id , renderer ) ; } private _dehydrateFunctions ( id , props , handlerIDs , oldProps ? ) { if ( props === null ) { if ( oldProps ) { for ( const key in oldProps ) { if ( Object . prototype . hasOwnProperty . call ( oldProps , key ) ) { const idx = handlerIDs . findIndex ( ( id ) => id === oldProps [ key ] ) ; if ( idx > - 1 ) { this . _removeHandler ( id , oldProps [ key ] ) ; handlerIDs . splice ( idx , 1 ) ; } } } } return null ; } const result = { ... oldProps , ... props } ; for ( const key in props ) { if ( Object . prototype . hasOwnProperty . call ( props , key ) ) { if ( typeof props [ key ] === 'function' ) { const oldID = oldProps && oldProps [ key ] && find ( ( id ) => id === oldProps [ key ] , handlerIDs ) ; if ( oldID ) { this . _handlerRegister . get ( id ) . set ( oldID , props [ key ] ) ; } else { const handlerID = this . _registerHandler ( id , props [ key ] ) ; handlerIDs . push ( handlerID ) ; result [ key ] = handlerID ; } } else if ( oldProps && Object . prototype . hasOwnProperty . call ( oldProps , key ) ) { const idx = handlerIDs . findIndex ( ( id ) => id === oldProps [ key ] ) ; if ( idx > - 1 ) { this . _removeHandler ( id , oldProps [ key ] ) ; handlerIDs . splice ( idx , 1 ) ; } } } } return result ; } private _rehydrateFunctions ( id , props , handlerIDs ) { const result = { ... props } ; for ( const key in props ) { if ( Object . prototype . hasOwnProperty . call ( props , key ) && findIndex ( ( id ) => id === props [ key ] , handlerIDs ) > - 1 ) { result [ key ] = ( ... args ) => this . _genericHandler ( id , props [ key ] , ... args ) ; } } return result ; } private _getDehydratedState ( { titleProps , contentProps , footerProps , ... props } , oldState ? ) { const handlerIDs = oldState ?. handlerIDs ?. slice ( ) ?? [ ] ; const dehydratedState = { ... this . _dehydrateFunctions ( props . id , props , handlerIDs , oldState ) , handlerIDs , } ; if ( titleProps !== undefined ) { dehydratedState . titleProps = this . _dehydrateFunctions ( props . id , titleProps , handlerIDs , oldState ?. titleProps ) ; } if ( contentProps !== undefined ) { dehydratedState . contentProps = this . _dehydrateFunctions ( props . id , contentProps , handlerIDs , oldState ?. contentProps ) ; } if ( footerProps !== undefined ) { dehydratedState . footerProps = this . _dehydrateFunctions ( props . id , footerProps , handlerIDs , oldState ?. footerProps ) ; } return dehydratedState ; } public open ( props ) ; public open ( props , usePromise ) ; @ background open ( { autoDisableBackdropClick = true , disableBackdropClick = true , fullScreen = false , ... props } , usePromise ? ) { const id = v4 ( ) ; this . _handlerRegister . set ( id , new Map < string , HandlerFunction > ( ) ) ; const dehydratedState = this . _getDehydratedState ( { ... props , id , autoDisableBackdropClick , disableBackdropClick , fullScreen , open : true , } ) ; this . _addModal ( dehydratedState ) ; let resolveFn ; const promise = new Promise < boolean > ( ( resolve ) => { resolveFn = resolve ; } ) ; this . _promises . set ( id , { promise , resolve : resolveFn , } ) ; return usePromise ? promise : id ; } returns @ background update ( props ) { const oldState = find ( ( item ) => item . id === props . id , this . _modals ) ; if ( ! oldState ) { return false ; } const updatedState = this . _getDehydratedState ( props , oldState ) ; this . _updateModal ( updatedState ) ; return true ; } @ background close ( id ) { const dehydratedState = find ( ( item ) => item . id === id , this . _modals ) ; if ( ! dehydratedState ) { return false ; } const updatedState = { ... dehydratedState , open : false , } ; this . _updateModal ( updatedState ) ; return true ; } public confirm ( props ) ; public confirm ( props , usePromise ) ; @ background confirm ( props = { } , usePromise ? ) { return this . open ( { confirmButtonText : defaultOKRendererID , cancelButtonText : defaultCancelRendererID , ... props , variant : 'confirm' , } , usePromise ) as any ; } public alert ( props ) ; public alert ( props , usePromise ) ; @ background alert ( props = { } , usePromise ? ) { return this . open ( { confirmButtonText : defaultOKRendererID , ... props , variant : 'alert' , } , usePromise ) as any ; } public info ( props ) ; public info ( props , usePromise ) ; @ background info ( props , usePromise ? ) { return this . open ( { ... props , variant : 'info' , } , usePromise ) as any ; } @ computed ( ( { _modals , _deps : { locale : { currentLocale } , } , } ) => [ _modals , currentLocale ] ) get modals ( ) { const currentLocale = this . _deps . locale . currentLocale ; return map ( ( { id , onConfirm , onCancel , onExited , title , content , footer , confirmButtonText , cancelButtonText , titleProps = { } , contentProps = { } , footerProps = { } , variant , handlerIDs , autoDisableBackdropClick , disableBackdropClick , loading , loadingOverlay , useLoadingOverlay , onClose , ... rest } ) => { const isBlockClose = ( autoDisableBackdropClick && ( loading || loadingOverlay ) ) || disableBackdropClick ; const uiProps = { ... rest , key : id , loading , loadingOverlay , disableBackdropClick : isBlockClose , disableEscapeKeyDown : isBlockClose , onConfirm : ( ) => this . _onConfirm ( id , onConfirm ) , onClose : ( e , reason ) => { onClose ?. ( e , reason ) ; this . close ( id ) ; } , TransitionProps : { onExited : ( ) => this . _onExited ( id , onExited ) , } , } ; if ( onCancel || cancelButtonText ) { uiProps . onCancel = ( ) => this . _onCancel ( id , onCancel ) ; } const renderedTitle = this . _rendererRegister . get ( title ) ?. ( { currentLocale , ... this . _rehydrateFunctions ( id , titleProps , handlerIDs ) , onConfirm : uiProps . onConfirm , onCancel : uiProps . onCancel , } ) ?? title ; if ( variant === 'info' ) { uiProps . title = this . _rendererRegister . get ( infoTitleRendererID ) ( { title : renderedTitle , onConfirm : uiProps . onConfirm , currentLocale , } ) ; uiProps . TitleProps = { disableTypography : true , display : 'flex' , space : [ 0 , 6 ] , } ; } else { uiProps . title = renderedTitle ; } uiProps . children = this . _rendererRegister . get ( content ) ?. ( { currentLocale , ... this . _rehydrateFunctions ( id , contentProps , handlerIDs ) , onConfirm : uiProps . onConfirm , onCancel : uiProps . onCancel , } ) ?? content ; uiProps . footer = variant === 'info' ? null : this . _rendererRegister . get ( footer ) ?. ( { currentLocale , ... this . _rehydrateFunctions ( id , footerProps , handlerIDs ) , onConfirm : uiProps . onConfirm , onCancel : uiProps . onCancel , } ) ?? footer ; uiProps . confirmButtonText = ( this . _rendererRegister . get ( confirmButtonText ) ?. ( { currentLocale , onConfirm : uiProps . onConfirm , onCancel : uiProps . onCancel , } ) as string ) ?? confirmButtonText ; uiProps . cancelButtonText = ( this . _rendererRegister . get ( cancelButtonText ) ?. ( { currentLocale , onConfirm : uiProps . onConfirm , onCancel : uiProps . onCancel , } ) as string ) ?? cancelButtonText ; return uiProps ; } , this . _modals ) ; } @ background getPromise ( id ) { return this . _promises . get ( id ) . promise ; } getUIFunctions ( ) { return { } ; } getUIProps ( ) { return { modals : this . modals , } ; } }