import * as _ from "lodash" ; import $ from "../../modules/jquery.appear" ; import { Helper } from "../helper" ; import { UserSettingsModel } from "@elevate/shared/models" ; import { AppResourcesModel } from "../models/app-resources.model" ; import { AbstractModifier } from "./abstract.modifier" ; export interface EffortInfo { id : number ; activity_id : number ; segment_id : number ; elapsed_time_raw : number ; avg_watts : number ; avg_heart_rate : number ; start_date_local : Date ; start_date_local_raw : string ; rank : number ; hazard_segment : boolean ; overall_rank : string ; overall_count : string ; qom_time : string ; kom_time : string ; __dateTime : Date ; } export interface LeaderBoardData { top_results : EffortInfo [ ] ; } export class ActivitySegmentTimeComparisonModifier extends AbstractModifier { protected showDifferenceToKOM : boolean ; protected showDifferenceToPR : boolean ; protected showDifferenceToCurrentYearPR : boolean ; protected displaySegmentTimeComparisonPosition : boolean ; protected appResources : AppResourcesModel ; protected isBike : boolean ; protected isFemale : boolean ; protected firstAppearDone : boolean ; protected deltaYearPRLabel : string ; protected deltaPRLabel : string ; protected deltaKomLabel : string ; constructor ( userSettings , appResources , activityType , isMyOwn ) { super ( ) ; this . showDifferenceToKOM = userSettings . displaySegmentTimeComparisonToKOM ; this . showDifferenceToPR = isMyOwn && userSettings . displaySegmentTimeComparisonToPR ; this . showDifferenceToCurrentYearPR = isMyOwn && userSettings . displaySegmentTimeComparisonToCurrentYearPR ; this . displaySegmentTimeComparisonPosition = userSettings . displaySegmentTimeComparisonPosition ; this . appResources = appResources ; this . isBike = ( activityType === "Ride" ) ; } protected crTitle ( ) { return this . isBike ? this . isFemale ? "QOM" : "KOM" : "CR" ; } public modify ( ) { if ( ! this . showDifferenceToKOM && ! this . showDifferenceToPR && ! this . showDifferenceToCurrentYearPR && ! this . displaySegmentTimeComparisonPosition ) { return ; } const segments = $ ( "#segments" ) ; if ( segments . length === 0 ) { setTimeout ( ( ) => { this . modify ( ) ; } , 500 ) ; return ; } segments . find ( "#segment-filter" ) . show ( ) ; segments . addClass ( "time-comparison-enabled" ) ; this . findOutGender ( ) ; this . setNewLabelsValues ( ) ; this . firstAppearDone = false ; $ ( "tr[data-segment-effort-id]" ) . appear ( ) . on ( "appear" , ( event , $items ) => { if ( ! this . firstAppearDone ) { let timeColumnHeader = segments . find ( "table.segments th.time-col" ) ; if ( timeColumnHeader . length == 0 ) { timeColumnHeader = segments . find ( "table.segments th:contains('Time')" ) ; } if ( this . showDifferenceToPR && this . showDifferenceToCurrentYearPR ) { timeColumnHeader . after ( "<th style='font-size:11px;' title='Column shows the difference between the activity segment time and your current year PR on that segment.'>" + this . deltaYearPRLabel + "</th>" ) ; } if ( this . showDifferenceToPR ) { timeColumnHeader . after ( "<th style='font-size:11px;' title='Column shows the difference between the activity segment time and your previous PR on that segment.'>" + this . deltaPRLabel + "</th>" ) ; } if ( this . showDifferenceToKOM ) { timeColumnHeader . after ( "<th style='font-size:11px;' title='Column shows the difference between the current " + this . crTitle ( ) + " time and the activity segment time.'>" + this . deltaKomLabel + "</th>" ) ; } if ( this . displaySegmentTimeComparisonPosition ) { timeColumnHeader . after ( "<th title='Column shows your current position on that segment.'>Rank</th>" ) ; } this . firstAppearDone = true ; } $items . each ( ( ) => { let $row = $ ( event . currentTarget ) , $timeCell = $row . find ( "td.time-col" ) , segmentEffortId = $row . data ( "segment-effort-id" ) , segmentEffortInfoUrl = "/segment_efforts/" + segmentEffortId , positionCell , deltaKomCell , deltaPRCell , deltaYearPRCell ; if ( $row . hasClass ( "selected" ) || $row . data ( "segment-time-comparison" ) ) { return ; } $row . data ( "segment-time-comparison" , true ) ; if ( this . showDifferenceToPR && this . showDifferenceToCurrentYearPR ) { deltaYearPRCell = $ ( "<td><span class='ajax-loading-image'></span></td>" ) ; $timeCell . after ( deltaYearPRCell ) ; } if ( this . showDifferenceToPR ) { deltaPRCell = $ ( "<td><span class='ajax-loading-image'></span></td>" ) ; $timeCell . after ( deltaPRCell ) ; } if ( this . showDifferenceToKOM ) { deltaKomCell = $ ( "<td><span class='ajax-loading-image'></span></td>" ) ; $timeCell . after ( deltaKomCell ) ; } if ( this . displaySegmentTimeComparisonPosition ) { positionCell = $ ( "<td><span class='ajax-loading-image'></span></td>" ) ; $timeCell . after ( positionCell ) ; } $ . getJSON ( segmentEffortInfoUrl , ( segmentEffortInfo ) => { if ( ! segmentEffortInfo ) { return ; } if ( segmentEffortInfo . hazard_segment ) { positionCell . html ( "-" ) ; deltaKomCell . html ( "-" ) ; deltaPRCell . html ( "-" ) ; deltaYearPRCell . html ( "-" ) ; return ; } if ( this . displaySegmentTimeComparisonPosition && segmentEffortInfo . overall_rank ) { const overallRank = ( ( segmentEffortInfo . overall_rank ) ? parseInt ( segmentEffortInfo . overall_rank ) : 0 ) ; const percentRank = overallRank / parseInt ( segmentEffortInfo . overall_count ) ; positionCell . html ( "<div title=\"Your position\" style=\"text-align: center; font-size:11px; padding: 1px 1px; background-color: #565656; color:" + this . getColorForPercentage ( percentRank ) + "\">" + overallRank + "&nbsp;/&nbsp;" + segmentEffortInfo . overall_count + "<br/>" + ( percentRank * 100 ) . toFixed ( 1 ) + "%</div>" ) ; } else { positionCell . html ( "n/a" ) ; } const komSeconds = Helper . HHMMSStoSeconds ( ( this . isFemale ? segmentEffortInfo . qom_time : segmentEffortInfo . kom_time ) . replace ( /[^0-9:]/gi , "" ) ) . toString ( ) , elapsedTime = segmentEffortInfo . elapsed_time_raw , komDiffTime = ( elapsedTime - parseInt ( komSeconds ) ) , komPercentTime = ( ( ( elapsedTime / parseInt ( komSeconds ) ) - 1 ) * 100 ) ; if ( komSeconds == "NaN" ) { deltaKomCell . html ( "N/A" ) ; } else if ( this . showDifferenceToKOM ) { const sign = ( Math . sign ( komDiffTime ) == 1 ) ? "+" : "-" ; deltaKomCell . html ( "<span title=\"Time difference with current " + this . crTitle ( ) + " (" + Helper . secondsToHHMMSS ( Math . abs ( parseInt ( komSeconds ) ) , true ) + ")\" style='font-size:11px; color:" + ( komDiffTime > 0 ? "#FF5555" : "#2EB92E" ) + ";'>" + sign + Helper . secondsToHHMMSS ( Math . abs ( komDiffTime ) , true ) + "<br/>" + ( ( Math . sign ( komPercentTime ) == 1 ) ? "+" : "" ) + komPercentTime . toFixed ( 1 ) + "%</span>" ) ; } if ( ! this . showDifferenceToPR && ! this . showDifferenceToCurrentYearPR ) { return ; } this . findCurrentSegmentEffortDate ( segmentEffortInfo . segment_id , segmentEffortId ) . then ( ( currentSegmentEffortDateTime , leaderBoardData ) => { this . handleTimeDifferenceAlongUserLeaderBoard ( leaderBoardData , currentSegmentEffortDateTime , elapsedTime , segmentEffortId , deltaPRCell , deltaYearPRCell ) ; } ) ; } ) ; } ) ; } ) ; $ . force_appear ( ) ; const waitForSegmentsSectionRemoved = ( ) => { if ( $ ( "#segments.time-comparison-enabled" ) . length !== 0 ) { setTimeout ( ( ) => { waitForSegmentsSectionRemoved ( ) ; } , 1000 ) ; return ; } this . modify ( ) ; } ; waitForSegmentsSectionRemoved ( ) ; } protected findOutGender ( ) { this . isFemale = false ; if ( ! _ . isUndefined ( window . pageView ) ) { this . isFemale = window . pageView . activityAthlete ( ) && window . pageView . activityAthlete ( ) . get ( "gender" ) != "M" ; } } protected setNewLabelsValues ( ) { this . deltaKomLabel = "&Delta;" + this . crTitle ( ) ; this . deltaPRLabel = "&Delta;PR" ; this . deltaYearPRLabel = "&Delta;yPR" ; } protected findCurrentSegmentEffortDate ( segmentId , segmentEffortId , page ? , deferred ? , fetchedLeaderboardData ? ) { if ( ! page ) { page = 1 ; } if ( ! deferred ) { deferred = $ . Deferred ( ) ; } if ( ! fetchedLeaderboardData ) { fetchedLeaderboardData = [ ] ; } const perPage = 50 ; const jqxhr = $ . getJSON ( "/segments/" + segmentId + "/leaderboard?raw=true&page=" + page + "&per_page=" + perPage + "&viewer_context=false&filter=my_results" ) ; let currentSegmentEffortDateTime = null ; jqxhr . done ( ( leaderBoardData ) => { for ( let i = 0 , max = leaderBoardData . top_results . length ; i < max ; i ++ ) { leaderBoardData . top_results [ i ] . __dateTime = new Date ( leaderBoardData . top_results [ i ] . start_date_local_raw ) ; if ( leaderBoardData . top_results [ i ] . id == segmentEffortId ) { currentSegmentEffortDateTime = leaderBoardData . top_results [ i ] . __dateTime ; } } fetchedLeaderboardData = _ . flatten ( _ . union ( leaderBoardData . top_results , fetchedLeaderboardData ) ) ; if ( currentSegmentEffortDateTime ) { deferred . resolve ( currentSegmentEffortDateTime , fetchedLeaderboardData ) ; } else { this . findCurrentSegmentEffortDate ( segmentId , segmentEffortId , page + 1 , deferred , fetchedLeaderboardData ) ; } } ) . fail ( ( error ) => { deferred . reject ( error ) ; } ) ; return deferred . promise ( ) ; } protected handleTimeDifferenceAlongUserLeaderBoard ( leaderBoardData , currentSegmentEffortDateTime , elapsedTime , segmentEffortId , deltaPRCell , deltaYearPRCell ) { let previousPersonalSeconds , previousPersonalDate , currentYearPRSeconds , currentYearPRDate ; if ( ! currentSegmentEffortDateTime ) { deltaPRCell . html ( "-" ) ; deltaYearPRCell . html ( "-" ) ; return ; } leaderBoardData = leaderBoardData . sort ( ( left , right ) => { return left . rank - right . rank ; } ) ; let deltaTime ; let percentTime ; if ( this . showDifferenceToPR ) { for ( let i = 0 ; i < leaderBoardData . length ; i ++ ) { if ( leaderBoardData [ i ] . __dateTime < currentSegmentEffortDateTime ) { previousPersonalSeconds = leaderBoardData [ i ] . elapsed_time_raw ; previousPersonalDate = leaderBoardData [ i ] . start_date_local ; break ; } } if ( previousPersonalSeconds ) { deltaTime = ( elapsedTime - previousPersonalSeconds ) ; percentTime = ( ( ( elapsedTime / previousPersonalSeconds ) - 1 ) * 100 ) ; deltaPRCell . html ( "<span title='Time difference with your previous PR time (" + Helper . secondsToHHMMSS ( previousPersonalSeconds , true ) + " on " + previousPersonalDate + ")' style='font-size:11px; color:" + ( deltaTime > 0 ? "#FF5555" : "#2EB92E" ) + ";'>" + ( ( Math . sign ( deltaTime ) == 1 ) ? "+" : "-" ) + Helper . secondsToHHMMSS ( Math . abs ( deltaTime ) , true ) + "<br/>" + ( ( Math . sign ( percentTime ) == 1 ) ? "+" : "" ) + percentTime . toFixed ( 1 ) + "%</span>" ) ; } else { deltaPRCell . html ( "<span title='First cross' style='font-size:11px; color: grey;'>1X</span>" ) ; } } if ( this . showDifferenceToPR && this . showDifferenceToCurrentYearPR ) { let resultsThisYear = [ ] ; for ( let j = 0 ; j < leaderBoardData . length ; j ++ ) { if ( leaderBoardData [ j ] . __dateTime . getFullYear ( ) === currentSegmentEffortDateTime . getFullYear ( ) ) { currentYearPRSeconds = leaderBoardData [ j ] . elapsed_time_raw ; currentYearPRDate = leaderBoardData [ j ] . start_date_local ; resultsThisYear . push ( leaderBoardData [ j ] ) ; } } resultsThisYear = resultsThisYear . sort ( ( left , right ) => { return left . elapsed_time_raw - right . elapsed_time_raw ; } ) ; const predicate = { __dateTime : currentSegmentEffortDateTime , } as Partial < EffortInfo > ; const currentActivityResult = _ . find ( resultsThisYear , predicate ) ; let previousBestResultThisYear = null ; _ . some ( resultsThisYear , ( result ) => { if ( result . activity_id !== currentActivityResult . activity_id && result . __dateTime < currentActivityResult . __dateTime ) { previousBestResultThisYear = result ; return true ; } } ) ; if ( currentYearPRSeconds ) { if ( ! previousPersonalSeconds ) { deltaYearPRCell . html ( "<span title='First cross this year' style='font-size:11px; color: grey;'>1X</span>" ) ; } else if ( currentYearPRSeconds - previousPersonalSeconds < 0 ) { if ( previousBestResultThisYear ) { deltaTime = currentActivityResult . elapsed_time_raw - previousBestResultThisYear . elapsed_time_raw ; percentTime = ( ( ( currentActivityResult . elapsed_time_raw / previousBestResultThisYear . elapsed_time_raw ) - 1 ) * 100 ) ; deltaYearPRCell . html ( "<span title='Time difference with your previous best result this year (" + Helper . secondsToHHMMSS ( previousBestResultThisYear . elapsed_time_raw , true ) + " on " + previousBestResultThisYear . start_date_local + ")' style='font-size:11px; color:" + ( deltaTime > 0 ? "#FF5555" : "#2EB92E" ) + ";'>" + ( ( Math . sign ( deltaTime ) == 1 ) ? "+" : "-" ) + Helper . secondsToHHMMSS ( Math . abs ( deltaTime ) , true ) + "<br/>" + ( ( Math . sign ( percentTime ) == 1 ) ? "+" : "" ) + percentTime . toFixed ( 1 ) + "%</span>" ) ; } else { deltaYearPRCell . html ( "<span title='This time beats previous PR. Time difference with your previous PR time  (" + Helper . secondsToHHMMSS ( previousPersonalSeconds , true ) + " on " + previousPersonalDate + ")' style='font-size:11px; color: grey;'>&#9733;</span>" ) ; } } else { if ( previousBestResultThisYear ) { deltaTime = currentActivityResult . elapsed_time_raw - previousBestResultThisYear . elapsed_time_raw ; percentTime = ( ( ( currentActivityResult . elapsed_time_raw / previousBestResultThisYear . elapsed_time_raw ) - 1 ) * 100 ) ; deltaYearPRCell . html ( "<span title='Time difference with your previous best result this year (" + Helper . secondsToHHMMSS ( previousBestResultThisYear . elapsed_time_raw , true ) + " on " + previousBestResultThisYear . start_date_local + ")' style='font-size:11px; color:" + ( deltaTime > 0 ? "#FF5555" : "#2EB92E" ) + ";'>" + ( ( Math . sign ( deltaTime ) == 1 ) ? "+" : "-" ) + Helper . secondsToHHMMSS ( Math . abs ( deltaTime ) , true ) + "<br/>" + ( ( Math . sign ( percentTime ) == 1 ) ? "+" : "" ) + percentTime . toFixed ( 1 ) + "%</span>" ) ; } else { deltaTime = ( elapsedTime - currentYearPRSeconds ) ; if ( deltaTime ) { deltaYearPRCell . html ( "<span title='Time difference with your current year PR time (" + Helper . secondsToHHMMSS ( currentYearPRSeconds , true ) + " on " + currentYearPRDate + ")' style='font-size:11px; color:" + ( deltaTime > 0 ? "#FF5555" : "#2EB92E" ) + ";'>" + ( ( Math . sign ( deltaTime ) == 1 ) ? "+" : "-" ) + Helper . secondsToHHMMSS ( Math . abs ( deltaTime ) , true ) + "</span>" ) ; } else { deltaYearPRCell . html ( "<span title='First cross this year' style='font-size:11px; color: grey;'>1X</span>" ) ; } } } } else { deltaYearPRCell . html ( "<span title='First cross this year' style='font-size:11px; color: grey;'>1X</span>" ) ; } } } protected getColorForPercentage ( pct ) { pct = 1 - pct ; const percentColors = [ { pct : 0.0 , color : { r : 0xff , g : 0x55 , b : 0x55 , } , } , { pct : 0.5 , color : { r : 0xff , g : 0xff , b : 0 , } , } , { pct : 1.0 , color : { r : 0x00 , g : 0xff , b : 0x00 , } , } ] ; let i ; for ( i = 1 ; i < percentColors . length - 1 ; i ++ ) { if ( pct < percentColors [ i ] . pct ) { break ; } } const lower = percentColors [ i - 1 ] ; const upper = percentColors [ i ] ; const range = upper . pct - lower . pct ; const rangePct = ( pct - lower . pct ) / range ; const pctLower = 1 - rangePct ; const pctUpper = rangePct ; const color = { r : Math . floor ( lower . color . r * pctLower + upper . color . r * pctUpper ) , g : Math . floor ( lower . color . g * pctLower + upper . color . g * pctUpper ) , b : Math . floor ( lower . color . b * pctLower + upper . color . b * pctUpper ) , } ; return "rgb(" + [ color . r , color . g , color . b ] . join ( "," ) + ")" ; } }