namespace Impact { enum ShowState { None = 0 , Waiting = 1 , Zooming = 2 } export class Title { public state : ShowState ; public group : Snap . Element ; public title : any ; constructor ( public person ) { this . state = ShowState . None ; } public waiting ( ) { return this . state < ShowState . Zooming ; } public shown ( ) { return this . state >= ShowState . Zooming ; } public initiate ( ) { this . state = ShowState . Waiting ; let distance = this . distanceFromView ( ) ; if ( distance != 0 ) { this . doScroll ( distance / 9 ) ; } this . highlight ( ) ; } public draw ( ) { let p = this . person ; this . title = $ ( `<li><a href="#">${ p . details . name }</a></li>` ) ; this . title . find ( "a" ) . on ( "mouseenter" , ( _ ) => this . person . highlight ( ) ) . on ( "mouseleave" , ( _ ) => this . andClose ( ) ) . on ( "click" , ( _ ) => this . andClose ( ( ) => this . person . show ( ) ) ) ; $ ( "#people_list ul" ) . append ( this . title ) ; } public zoom ( ) { if ( this . state != ShowState . Waiting ) { return ; } let p = this . person ; let mass = p . radius * 2 ; let scale = Person . MAX_ZOOM / mass ; let pt = p . point ; let tl = p . topLeft ( ) ; let dimensions = Helpers . canvasDimensions ( p . svg ) ; let pattern = p . svg . image ( Helpers . imageSource ( "people" , p . details . picture ) , tl . x , tl . y , mass , mass ) ; let avatarBorder = p . svg . circle ( pt . x , pt . y , p . radius ) ; let avatar = avatarBorder . clone ( ) ; this . group = p . svg . group ( pattern , avatarBorder ) ; this . state = ShowState . Zooming ; this . group . attr ( { cursor : "pointer" } ) ; avatar . attr ( { fill : "#fff" } ) ; pattern . attr ( { mask : avatar } ) ; avatarBorder . attr ( { fillOpacity : 0 , stroke : "#888" , strokeWidth : ( 6 / scale ) } ) ; this . group . click ( ( e ) => { this . close ( ) ; p . show ( ) ; } ) ; this . group . hover ( null , ( _ ) => this . close ( ) ) ; if ( scale >= 1 ) { this . group . animate ( { transform : `s${ scale },${ scale }` } , 500 , mina . backout , ( ) => this . moveToView ( scale , dimensions ) ) ; } else { this . moveToView ( scale , dimensions ) ; } } public finalize ( ) { this . unhighlight ( ) ; this . state = ShowState . None ; } private moveToView ( scale , dimensions ) { let bbox = this . group . getBBox ( ) ; let checkCoords = ( a , b , c ) => { if ( a < 0 ) { return - a ; } else if ( b > c ) { return - ( b - c ) ; } else { return 0 ; } } ; let movement = new Vector ( checkCoords ( bbox . x , bbox . x2 , dimensions . x ) , checkCoords ( bbox . y , bbox . y2 , dimensions . y ) ) ; if ( ! movement . isEmpty ( ) ) { this . group . animate ( { transform : `s${ scale },${ scale } t${ movement . x / scale },${ movement . y / scale }` } , 120 ) ; } } private distanceFromView ( ) { let tOffset = this . title . offset ( ) ; let sOffset = $ ( "#inner_scroll" ) . offset ( ) ; let sBottom = sOffset . top + sOffset . height ; if ( tOffset . top > sOffset . top ) { if ( tOffset . top < sBottom ) { return 0 ; } else { return ( tOffset . top - sBottom ) + tOffset . height + 8 ; } } else { return tOffset . top - sOffset . top ; } } private doScroll ( jump , remaining = 8 ) { if ( this . state == ShowState . Waiting ) { $ ( "#inner_scroll" ) [ 0 ] . scrollTop += jump ; if ( remaining > 0 ) { requestAnimationFrame ( ( ) => this . doScroll ( jump , remaining - 1 ) ) ; } } } private andClose ( f ? ) { if ( typeof f === "function" ) { f ( ) ; } this . close ( ) ; return false ; } private highlight ( ) { this . title . addClass ( "selected" ) ; } private unhighlight ( ) { this . title . removeClass ( "selected" ) ; } private close ( ) { this . person . unhighlight ( ) ; if ( this . group ) { this . group . animate ( { transform : "s1,1" } , 200 , mina . linear , ( ) => this . group . remove ( ) ) ; } } } }