import { EventSource } from './events' ; export abstract class BatchingScheduler { private useAnimationFrame : boolean ; private scheduled : number | undefined ; constructor ( readonly waitingTime = 0 ) { this . useAnimationFrame = waitingTime === 0 ; this . runSynchronously = this . runSynchronously . bind ( this ) ; } protected schedule ( ) { if ( typeof this . scheduled === 'undefined' ) { if ( this . useAnimationFrame ) { this . scheduled = requestAnimationFrame ( this . runSynchronously ) ; } else { this . scheduled = Number ( setTimeout ( this . runSynchronously , this . waitingTime ) ) ; } } } protected abstract run ( ) ; runSynchronously ( ) { const wasScheduled = this . cancelScheduledTimeout ( ) ; if ( wasScheduled ) { this . run ( ) ; } } dispose ( ) { this . cancelScheduledTimeout ( ) ; } private cancelScheduledTimeout ( ) { if ( typeof this . scheduled !== 'undefined' ) { if ( this . useAnimationFrame ) { cancelAnimationFrame ( this . scheduled ) ; } else { clearTimeout ( this . scheduled ) ; } this . scheduled = undefined ; return true ; } return false ; } } export class BufferingQueue < Key extends string > extends BatchingScheduler { private fetchingQueue : { [ key ] : true ; } = Object . create ( null ) ; constructor ( private onFetch , waitingTime = 200 ) { super ( waitingTime ) ; } push ( key ) { this . fetchingQueue [ key ] = true ; this . schedule ( ) ; } clear ( ) { this . fetchingQueue = Object . create ( null ) ; } protected run ( ) { const { fetchingQueue , onFetch } = this ; this . fetchingQueue = Object . create ( null ) ; onFetch ( Object . keys ( fetchingQueue ) as Key [ ] ) ; } } export class Debouncer extends BatchingScheduler { private callback : ( ( ) => void ) | undefined ; call ( callback ) { this . callback = callback ; this . schedule ( ) ; } protected run ( ) { const callback = this . callback ; callback ( ) ; } } export class Cancellation { public static NEVER_SIGNAL : CancellationToken = { aborted : false , addEventListener : ( ) => { } , removeEventListener : ( ) => { } , } ; private source : EventSource < { abort : undefined ; } > | undefined = new EventSource ( ) ; private aborted = false ; readonly signal : CancellationToken ; constructor ( ) { this . signal = new ( class { constructor ( private parent ) { } get aborted ( ) { return this . parent . aborted ; } addEventListener ( event , handler ) { if ( event !== 'abort' ) { return ; } if ( this . parent . source ) { this . parent . source . on ( 'abort' , handler ) ; } else { handler ( ) ; } } removeEventListener ( event , handler ) { if ( event !== 'abort' ) { return ; } if ( this . parent . source ) { this . parent . source . off ( 'abort' , handler ) ; } } } ) ( this ) ; } abort ( ) { if ( this . aborted ) { return ; } this . aborted = true ; this . source . trigger ( 'abort' , undefined ) ; this . source = undefined ; } } export interface CancellationToken { readonly aborted : boolean ; addEventListener ( event , handler ) : void ; removeEventListener ( event , handler ) : void ; } export const CancellationToken = { throwIfAborted ( ct ) { if ( ct . aborted ) { throw new CancelledError ( ) ; } } , mapCancelledToNull < T > ( ct , promise ) { const onResolve = ( value ) : T | null => { if ( ct . aborted ) { return null ; } return value ; } ; const onReject = ( err ) : null | Promise < null > => { if ( ct . aborted ) { return null ; } return Promise . reject ( err ) ; } ; return promise . then ( onResolve , onReject ) ; } , } ; export class CancelledError extends Error { constructor ( message = 'Operation was cancelled' ) { super ( message ) ; this . name = CancelledError . name ; Object . setPrototypeOf ( this , CancelledError . prototype ) ; } } export function delay ( timeout ) { return new Promise ( ( resolve ) => setTimeout ( ( ) => resolve ( ) , timeout ) ) ; } export function animateInterval ( duration , onProgress , cancellation ? ) { return new Promise ( ( resolve ) => { let animationFrameId ; let start ; const animate = ( time ) => { if ( cancellation && cancellation . signal . aborted ) { return ; } start = start || time ; let timePassed = time - start ; if ( timePassed > duration ) { timePassed = duration ; } onProgress ( timePassed / duration ) ; if ( timePassed < duration ) { animationFrameId = requestAnimationFrame ( animate ) ; } else { resolve ( ) ; } } ; cancellation . signal . addEventListener ( 'abort' , ( ) => { cancelAnimationFrame ( animationFrameId ) ; resolve ( ) ; } ) ; animationFrameId = requestAnimationFrame ( animate ) ; } ) ; } export function easeInOutBezier ( t ) { if ( t < 0 ) { return 0 ; } if ( t > 1 ) { return 1 ; } return t * t * ( 3.0 - 2.0 * t ) ; }