import { ChangeDetectorRef , Component , NgZone , ErrorHandler , OnDestroy , DoCheck , OnInit } from '@angular/core' ; import { UncaughtErrorHandler } from './utils/uncaught-error-handler' ; import { reportUncaughtError } from '../utils/error-handling' ; import { Connection , DirectConnection } from './channel' ; import { ApplicationError , ApplicationErrorType , Message , MessageFactory , MessageResponse , MessageType , Subscription } from '../communication' ; import { ComponentInstanceState , ExpandState , Options , Tab , StateTab , Theme , ComponentViewState , AnalyticsConsent } from './state' ; import { Change , MutableTree , Node , Path , deserializeChangePath , serializePath , InstanceWithMetadata } from '../tree' ; import { createTree } from '../tree/mutable-tree-factory' ; import { UserActions } from './actions/user-actions/user-actions' ; import { Route } from '../backend/utils' ; import { select } from '@angular-redux/store' ; import { NgRedux } from '@angular-redux/store' ; import { IAppState } from './store/model' ; import { MainActions } from './actions/main-actions' ; require ( '!style!css!postcss!../styles/app.css' ) ; @ Component ( { selector : 'bt-app' , templateUrl : './app.html' , styleUrls : [ './app.css' ] } ) export class App implements OnInit , DoCheck , OnDestroy { private Theme = Theme ; private AnalyticsConsent = AnalyticsConsent ; private componentState : ComponentInstanceState ; private routerTree : Array < Route > ; private ngModules : { [ key ] : any ; } ; private ngVersion : string ; private selectedNode : Node ; private subscription : Subscription ; private tree : MutableTree ; private error : ApplicationError = null ; private unsubscribeUncaughtErrorListener ; @ select ( store => store . main . selectedTab ) selectedTab ; @ select ( store => store . main . selectedComponentsSubTab ) selectedComponentsSubTab ; @ select ( store => store . main . DOMSelectionActive ) domSelectionActive ; constructor ( private ngRedux , private mainActions , private changeDetector , private connection , private directConnection , private options , private userActions , private viewState , private zone , private errorHandler ) { if ( this . errorHandler instanceof UncaughtErrorHandler ) { this . unsubscribeUncaughtErrorListener = ( < UncaughtErrorHandler > this . errorHandler ) . addListener ( ( err ) => { this . error = new ApplicationError ( ApplicationErrorType . UncaughtException , { name : err . name , message : err . message , stack : err . stack } ) ; } ) ; } this . componentState = new ComponentInstanceState ( changeDetector ) ; this . options . changes . subscribe ( ( ) => this . requestTree ( ) ) ; this . options . load ( ) . then ( ( ) => this . changeDetector . detectChanges ( ) ) ; this . viewState . changes . subscribe ( ( ) => this . changeDetector . detectChanges ( ) ) ; this . mainActions . initializeAugury ( ) ; } hasContent ( ) { return this . tree && this . tree . roots && this . tree . roots . length > 0 ; } ngDoCheck ( ) { this . selectedNode = this . viewState . selectedTreeNode ( this . tree ) ; this . changeDetector . detectChanges ( ) ; } ngOnInit ( ) { this . subscription = this . connection . subscribe ( this . onReceiveMessage . bind ( this ) ) ; this . connection . reconnect ( ) . then ( ( ) => this . requestTree ( ) ) ; } ngOnDestroy ( ) { if ( this . subscription ) { this . subscription . unsubscribe ( ) ; } if ( this . unsubscribeUncaughtErrorListener ) { this . unsubscribeUncaughtErrorListener ( ) ; } this . connection . close ( ) ; } onSelectNode ( node , beforeLoad ? ) { this . selectedNode = node ; if ( node == null ) { this . viewState . unselect ( ) ; return ; } this . viewState . select ( node ) ; const m = MessageFactory . selectComponent ( node , node . isComponent ) ; const promise = this . directConnection . handleImmediate ( m ) . then ( response => { if ( ! response ) { return ; } if ( typeof beforeLoad === 'function' ) { beforeLoad ( ) ; } const { instance , metadata , providers , componentMetadata } = response ; return < InstanceWithMetadata > { instance , providers , metadata : new Map ( metadata ) , componentMetadata : new Map ( componentMetadata ) } ; } ) ; this . componentState . wait ( node , promise ) ; } onInspectElement ( node ) { chrome . devtools . inspectedWindow . eval ( `inspect(inspectedApplication.nodeFromPath('${ node . id }'))` ) ; } onCollapseChildren ( node ) { this . recursiveExpansionState ( node , ExpandState . Collapsed ) ; } onExpandChildren ( node ) { this . recursiveExpansionState ( node , ExpandState . Expanded ) ; } onReportError ( ) { if ( this . error && this . error . errorType === ApplicationErrorType . UncaughtException ) { reportUncaughtError ( this . error . error , this . ngVersion ) ; this . error = null ; } } onSelectedTabChange ( tab ) { this . routerTree = this . routerTree ? [ ] . concat ( this . routerTree ) : null ; this . mainActions . selectTab ( tab ) ; } onSelectedComponentsSubTabMenuChange ( tab ) { this . mainActions . selectComponentsSubTab ( tab ) ; } onDOMSelectionActiveChange ( state ) { this . mainActions . setDOMSelectionActive ( state ) ; } onEmitValue ( data ) { this . mainActions . emitValue ( data . path , data . data ) ; } onUpdateProperty ( data ) { this . mainActions . updateProperty ( data . path , data . data ) ; } private requestTree ( ) { const options = this . options . simpleOptions ( ) ; this . connection . send ( MessageFactory . initialize ( options ) ) . catch ( error => { this . error = new ApplicationError ( ApplicationErrorType . UncaughtException , error ) ; this . changeDetector . detectChanges ( ) ; } ) ; } private refresh ( ) { this . connection . send ( MessageFactory . refresh ( ) ) ; } onRefresh ( ) { this . error = null ; this . refresh ( ) ; } private restoreSelection ( ) { this . selectedNode = this . viewState . selectedTreeNode ( this . tree ) ; this . onSelectNode ( this . selectedNode , ( ) => this . componentState . reset ( ) ) ; } private processMessage ( msg , sendResponse ) { const respond = ( ) => { sendResponse ( MessageFactory . response ( msg , { processed : true } , false ) ) ; } ; switch ( msg . messageType ) { case MessageType . Ping : this . restoreSelection ( ) ; break ; case MessageType . NotNgApp : this . error = new ApplicationError ( ApplicationErrorType . NotNgApp ) ; respond ( ) ; break ; case MessageType . Push : this . directConnection . readQueue ( ( innerMessage , innerRespond ) => this . processMessage ( innerMessage , innerRespond ) ) ; break ; case MessageType . CompleteTree : this . createTree ( msg . content ) ; respond ( ) ; break ; case MessageType . TreeDiff : if ( this . tree == null ) { this . connection . send ( MessageFactory . initialize ( this . options . simpleOptions ( ) ) ) ; } else { this . updateTree ( msg . content ) ; } respond ( ) ; break ; case MessageType . TreeUnchanged : this . restoreSelection ( ) ; break ; case MessageType . RouterTree : this . routerTree = msg . content ; respond ( ) ; break ; case MessageType . NgModules : this . ngModules = msg . content ; respond ( ) ; break ; case MessageType . NgVersion : this . ngVersion = msg . content ; respond ( ) ; break ; case MessageType . FindElement : if ( msg . content . node ) { this . viewState . select ( msg . content . node ) ; this . viewState . expandState ( msg . content . node , ExpandState . Expanded ) ; this . mainActions . setDOMSelectionActive ( true ) ; if ( msg . content . stop ) { this . userActions . cancelFindElement ( ) ; this . mainActions . setDOMSelectionActive ( false ) ; } break ; } break ; case MessageType . ApplicationError : this . error = msg . content ; respond ( ) ; break ; case MessageType . ErrorCleared : if ( this . error && msg . content . errorTypes . includes ( this . error . errorType ) ) { this . error = null ; } respond ( ) ; break ; } } private createTree ( roots ) { this . componentState . reset ( ) ; this . tree = createTree ( roots ) ; this . restoreSelection ( ) ; } private updateTree ( changes ) { this . tree . patch ( changes ) ; const changedIdentifiers = this . extractIdentifiersFromChanges ( changes ) ; this . viewState . nodesChanged ( changedIdentifiers ) ; this . restoreSelection ( ) ; } private onReceiveMessage ( msg , sendResponse ) { this . zone . run ( ( ) => this . processMessage ( msg , sendResponse ) ) ; } private extractIdentifiersFromChanges ( changes ) { const identifiers = new Set < string > ( ) ; for ( const change of changes ) { const path = this . nodePathFromChangePath ( deserializeChangePath ( change . path ) ) ; identifiers . add ( serializePath ( path ) ) ; } const results = new Array < string > ( ) ; identifiers . forEach ( id => results . push ( id ) ) ; return results ; } private nodePathFromChangePath ( changePath ) { const result = new Array < string | number > ( ) ; for ( let index = 0 ; index < changePath . length ; ++ index ) { switch ( changePath [ index ] ) { case 'roots' : case 'children' : result . push ( changePath [ ++ index ] ) ; break ; } } return result ; } private recursiveExpansionState ( from , state ) { const apply = ( node ) => { this . viewState . expandState ( node , state ) ; node . children . forEach ( n => apply ( n ) ) ; } ; apply ( from ) ; } }