import { ElementRef , Injectable , Renderer2 , RendererFactory2 } from '@angular/core' ; import * as _ from 'lodash' ; import { v4 as uuid } from 'uuid' ; import { GatewayService } from './gateway.service' ; import { NodeUtils } from './node.utils' ; import * as assert from 'assert' ; export type onRun = ( ) => Promise < any > | any ; export type onSuccess = ( res ? : any , allRes ? : RunResultMap ) => void ; export type onFailure = ( reason : Error , allReasons ? : RunResultMap ) => void ; export interface OnExec { dvOnExec : onRun ; } export interface OnExecSuccess { dvOnExecSuccess : onSuccess ; } export interface OnExecFailure { dvOnExecFailure : onFailure ; } export interface OnEval { dvOnEval : onRun ; } export interface OnEvalSuccess { dvOnEvalSuccess : onSuccess ; } export interface OnEvalFailure { dvOnEvalFailure : onFailure ; } interface ComponentInfo { component : any ; node : any ; } export interface RunResultMap { [ componentId ] : any ; } type RunType = 'eval' | 'exec' ; const runFunctionNames = { eval : { onRun : 'dvOnEval' , onSuccess : 'dvOnEvalSuccess' , onFailure : 'dvOnEvalFailure' } , exec : { onRun : 'dvOnExec' , onSuccess : 'dvOnExecSuccess' , onFailure : 'dvOnExecFailure' } } ; @ Injectable ( ) export class RunService { private renderer : Renderer2 ; private componentTable : { [ id ] : ComponentInfo ; } = { } ; constructor ( rendererFactory ) { this . renderer = rendererFactory . createRenderer ( null , null ) ; } /**
     * Register a new component. Should be called on init. Only components that
     * can be run are required to be registered.
     */ register ( elem , component ) { const componentId = uuid ( ) ; const node = elem . nativeElement ; NodeUtils . SetComponentId ( node , componentId ) ; this . componentTable [ componentId ] = { component : component , node : node } ; } registerAppComponent ( elem , component ) { this . register ( elem , component ) ; NodeUtils . MarkAsAppComponent ( elem . nativeElement ) ; } getComponentInstance ( componentId ) { return _ . get ( this . componentTable [ componentId ] , 'component' , null ) ; } /**
     * Cause the component given by `elem` to execute.
     */ async exec ( elem ) { this . run ( 'exec' , elem ) ; } /**
     * Cause the component given by `elem` to evaluate.
     */ async eval ( elem ) { this . run ( 'eval' , elem ) ; } private getTargetComponent ( initialNode ) { let pComponent = this . renderer . parentNode ( initialNode ) ; NodeUtils . WalkUpFromNode ( pComponent , this . renderer , ( node ) => { if ( NodeUtils . IsComponent ( node ) ) { pComponent = node ; return true ; } return false ; } ) ; return NodeUtils . IsDvTx ( pComponent ) ? pComponent : initialNode ; } private async run ( runType , elem ) { console . log ( `Running ${ runType } request from ` + NodeUtils . GetFqTagOfNode ( elem . nativeElement ) ) ; const targetComponent = this . getTargetComponent ( elem . nativeElement ) ; const targetComponentFqTag = NodeUtils . GetFqTagOfNode ( targetComponent ) ; const runId = NodeUtils . GetRunId ( targetComponent ) ; if ( runId ) { console . log ( `Skipping ${ runType } (${ runId }) since there's already one in ` + `progress` ) ; return ; } let id ; if ( NodeUtils . IsDvTx ( targetComponent ) ) { id = uuid ( ) ; NodeUtils . SetRunId ( targetComponent , id ) ; } console . log ( `Target component is ${ targetComponentFqTag } (${ runType }, ${ id })` ) ; let runResultMap ; try { runResultMap = await this . callDvOnRun ( runType , targetComponent , id ) ; } catch ( error ) { throw error ; } const runFailed = _ . filter ( _ . values ( runResultMap ) , _ . isError ) . length > 0 ; if ( runFailed ) { console . error ( `Got error on ${ runType } ${ id } (${ targetComponentFqTag })` ) ; console . error ( runResultMap ) ; console . log ( `Finalizing ${ runType } ${ id } (${ targetComponentFqTag })` ) ; this . removeRunIds ( targetComponent ) ; this . callDvOnRunX ( 'onFailure' , runType , targetComponent , runResultMap ) ; } else { console . log ( `Finalizing ${ runType } ${ id } (${ targetComponentFqTag })` ) ; this . removeRunIds ( targetComponent ) ; this . callDvOnRunX ( 'onSuccess' , runType , targetComponent , runResultMap ) ; } } /**
     * Walks the dom starting from `node` calling `onComponent` with the component
     * info when a component is encountered. No child of components are traversed.
     */ private walkComponents ( node , onComponent ) { const componentId = NodeUtils . GetComponentId ( node ) ; if ( ! componentId ) { _ . each ( node . childNodes , ( n ) => this . walkComponents ( n , onComponent ) ) ; return ; } const target = this . componentTable [ componentId ] ; onComponent ( target , componentId ) ; } private async callDvOnRun ( runType , node , id ) { const dvOnRun = runFunctionNames [ runType ] . onRun ; const runs = [ ] ; this . walkComponents ( node , ( componentInfo , componentId ) => { const fqTag = NodeUtils . GetFqTagOfNode ( componentInfo . node ) ; if ( componentInfo . component [ dvOnRun ] ) { if ( id !== undefined ) { NodeUtils . SetRunId ( componentInfo . node , id ) ; } console . log ( `Calling ${ dvOnRun } on ${ fqTag }` ) ; runs . push ( Promise . resolve ( componentInfo . component [ dvOnRun ] ( ) ) . then ( ( result ) => ( { [ componentId ] : result } ) ) . catch ( ( e ) => { console . error ( `Error in ${ fqTag }: ${ e }` ) ; assert . ok ( ! _ . isNil ( e ) ) ; assert . ok ( _ . isError ( e ) ) ; return { [ componentId ] : e } ; } ) ) ; } else { console . log ( `Skipped calling ${ dvOnRun } on ${ fqTag }: there's no method` ) ; } } ) ; if ( GatewayService . txBatches [ id ] ) { GatewayService . txBatches [ id ] . setNumComponents ( runs . length ) ; } else if ( id ) { console . log ( `Set num comps pending for id ${ id } to ${ runs . length }` ) ; GatewayService . txPendingNumComponents [ id ] = runs . length ; } const resultMaps = await Promise . all ( runs ) ; return _ . assign ( { } , ... resultMaps ) ; } private removeRunIds ( node ) { NodeUtils . RemoveRunId ( node ) ; this . walkComponents ( node , ( componentInfo ) => { NodeUtils . RemoveRunId ( componentInfo . node ) ; } ) ; } private callDvOnRunX ( x , runType , node , runResultMap ) { const dvOnX = runFunctionNames [ runType ] [ x ] ; this . walkComponents ( node , ( componentInfo , componentId ) => { if ( componentInfo . component [ dvOnX ] ) { componentInfo . component [ dvOnX ] ( runResultMap [ componentId ] , runResultMap ) ; } } ) ; } }