import { Dispatch , IObserverHandle , Message } from "./dispatch" ; import { deepEquals } from "./immutable" ; class PropChangedMessage extends Message { constructor ( readonly property , public newValue , public oldValue ? ) { super ( ) ; } get canConflate ( ) : boolean { return true ; } conflate ( other ) { if ( this . property === other . property ) { this . newValue = other . newValue ; return true ; } return false ; } void ( ) { return deepEquals ( this . newValue , this . oldValue ) ; } } export interface IEvent { id : string ; oldValue : any ; newValue : any ; } export type StatePropCallback = ( changes : IEvent ) => void ; export type StateCallback = ( changes : IEvent [ ] ) => void ; export type StateEvents = "propChanged" | "changed" ; export class StateObject < U , I > { private _espState : Partial < U > = { } as U ; private _dispatch = new Dispatch ( ) ; private _monitorHandle ? : number ; protected _monitorTickCount : number = 0 ; protected clear ( newVals ? ) { this . _espState = { } as U ; if ( newVals !== void 0 ) { this . set ( newVals as I ) ; } this . _monitorTickCount = 0 ; } protected get ( ) ; protected get < K extends keyof U > ( key , defValue ? ) ; protected get < K extends keyof U > ( key ? , defValue ? ) { if ( key === void 0 ) { return this . _espState ; } return this . has ( key ) ? this . _espState [ key ] : defValue ; } protected set ( newVals ) ; protected set < K extends keyof U > ( key , newVal , batchMode ? ) ; protected set < K extends keyof U > ( keyOrNewVals , newVal ? ) { if ( typeof keyOrNewVals === "string" ) { return this . setSingle ( keyOrNewVals as any , newVal as U [ K ] ) ; } this . setAll ( keyOrNewVals as Partial < U > ) ; } private setSingle < K extends keyof U > ( key , newVal ) { const oldVal = this . _espState [ key ] ; this . _espState [ key ] = newVal ; this . _dispatch . post ( new PropChangedMessage ( key as string , newVal , oldVal ) ) ; } private setAll ( _ ) { for ( const key in _ ) { if ( _ . hasOwnProperty ( key ) ) { this . setSingle ( key , _ [ key ] ) ; } } } protected has < K extends keyof U > ( key ) { return this . _espState [ key ] !== void 0 ; } addObserver ( eventID , callback ) ; addObserver ( eventID , propID , callback ) ; addObserver ( eventID , propIDOrCallback , callback ? ) { if ( this . isCallback ( propIDOrCallback ) ) { if ( eventID !== "changed" ) throw new Error ( "Invalid eventID:  " + eventID ) ; return this . _dispatch . attach ( ( messages ) => { propIDOrCallback ( messages . map ( m => ( { id : m . property , oldValue : m . oldValue , newValue : m . newValue } ) ) ) ; } ) ; } else { if ( eventID !== "propChanged" ) throw new Error ( "Invalid eventID:  " + eventID ) ; return this . _dispatch . attach ( ( messages ) => { const filteredMessages = messages . filter ( m => m . property === propIDOrCallback ) ; if ( filteredMessages . length ) { if ( filteredMessages . length > 1 ) { console . warn ( "Should only be 1 message?" ) ; } const event = filteredMessages [ filteredMessages . length - 1 ] ; callback ! ( { id : event . property , oldValue : event . oldValue , newValue : event . newValue } ) ; } } ) ; } } on ( eventID , callback ) ; on ( eventID , propID , callback ) ; on ( eventID , propIDOrCallback , callback ? ) { this . addObserver ( eventID , propIDOrCallback as any , callback as any ) ; return this ; } protected isCallback ( propIDOrCallback ) { return ( typeof propIDOrCallback === "function" ) ; } protected hasEventListener ( ) { return this . _dispatch . hasObserver ( ) ; } protected async refresh ( full = false ) { await Promise . resolve ( ) ; return this ; } protected _monitor ( ) { if ( this . _monitorHandle ) { this . _monitorTickCount = 0 ; return ; } this . _monitorHandle = setTimeout ( ( ) => { const refreshPromise = this . hasEventListener ( ) ? this . refresh ( ) : Promise . resolve ( ) ; refreshPromise . then ( ( ) => { this . _monitor ( ) ; } ) ; delete this . _monitorHandle ; } , this . _monitorTimeoutDuraction ( ) ) ; } protected _monitorTimeoutDuraction ( ) { ++ this . _monitorTickCount ; if ( this . _monitorTickCount <= 1 ) { return 0 ; } return 30000 ; } watch ( callback , triggerChange = true ) { if ( typeof callback !== "function" ) { throw new Error ( "Invalid Callback" ) ; } if ( triggerChange ) { setTimeout ( ( ) => { const props = this . get ( ) ; const changes = [ ] ; for ( const key in props ) { if ( props . hasOwnProperty ( props ) ) { changes . push ( { id : key , newValue : props [ key ] , oldValue : undefined } ) ; } } callback ( changes ) ; } , 0 ) ; } const retVal = this . addObserver ( "changed" , callback ) ; this . _monitor ( ) ; return retVal ; } }