import * as _ from "lodash" ; import * as $ from "jquery" ; import { Helper } from "../../helper" ; import { ActivityInfoModel , AnalysisDataModel , AthleteSnapshotModel , SpeedUnitDataModel , UserSettingsModel } from "@elevate/shared/models" ; import { AppResourcesModel } from "../../models/app-resources.model" ; import { ActivityProcessor } from "../../processors/activity-processor" ; import { AbstractDataView } from "./views/abstract-data.view" ; import { FeaturedDataView } from "./views/featured-data.view" ; import { HeaderView } from "./views/header.view" ; import { HeartRateDataView } from "./views/heart-rate-data.view" ; export abstract class AbstractExtendedDataModifier { public static TYPE_ACTIVITY = 0 ; public static TYPE_SEGMENT = 1 ; protected activityProcessor : ActivityProcessor ; protected activityType : string ; protected appResources : AppResourcesModel ; protected userSettings : UserSettingsModel ; protected athleteSnapshot : AthleteSnapshotModel ; protected activityInfo : ActivityInfoModel ; protected speedUnitsData : SpeedUnitDataModel ; protected type : number ; protected analysisData : AnalysisDataModel ; protected summaryGrid : JQuery ; protected segmentEffortButtonId : number ; protected content : string ; protected dataViews : AbstractDataView [ ] = [ ] ; protected constructor ( activityProcessor , activityInfo , appResources , userSettings , type ) { this . activityProcessor = activityProcessor ; this . appResources = appResources ; this . userSettings = userSettings ; this . activityInfo = activityInfo ; this . speedUnitsData = Helper . getSpeedUnitData ( window . currentAthlete . get ( "measurement_preference" ) ) ; this . type = type ; } public apply ( ) { if ( _ . isNull ( this . type ) ) { console . error ( "ExtendedDataModifier must be set" ) ; } this . getFullAnalysisData ( ) . then ( ( result ) => { this . athleteSnapshot = result . athleteSnapshot ; this . analysisData = result . analysisData ; if ( this . type === AbstractExtendedDataModifier . TYPE_ACTIVITY ) { this . placeSummaryPanel ( ( ) => { this . placeExtendedStatsButton ( ( ) => { console . debug ( "Extended Button for segment has been placed..." ) ; } ) ; } ) ; } else if ( this . type === AbstractExtendedDataModifier . TYPE_SEGMENT ) { this . placeExtendedStatsButtonSegment ( ( ) => { console . debug ( "Extended Button for segment has been placed..." ) ; } ) ; } } ) ; } protected placeSummaryPanel ( panelAdded ) { this . insertContentSummaryGridContent ( ) ; $ ( ".inline-stats.section" ) . first ( ) . after ( this . summaryGrid . html ( ) ) . each ( ( ) => { if ( panelAdded ) { panelAdded ( ) ; } } ) ; } protected makeSummaryGrid ( columns , rows ) { let summaryGrid = "" ; summaryGrid += "<div>" ; summaryGrid += "<div class=\"summaryGrid\">" ; summaryGrid += "<table>" ; for ( let i = 0 ; i < rows ; i ++ ) { summaryGrid += "<tr>" ; for ( let j = 0 ; j < columns ; j ++ ) { summaryGrid += "<td data-column=\"" + j + "\" data-row=\"" + i + "\">" ; summaryGrid += "</td>" ; } summaryGrid += "</tr>" ; } summaryGrid += "</table>" ; summaryGrid += "</div>" ; summaryGrid += "</div>" ; this . summaryGrid = $ ( summaryGrid ) ; } protected insertContentSummaryGridContent ( ) { let moveRatio = "-" ; if ( this . analysisData . moveRatio && this . userSettings . displayActivityRatio ) { moveRatio = this . printNumber ( this . analysisData . moveRatio , 2 ) ; } this . insertContentAtGridPosition ( 0 , 0 , moveRatio , "Move Ratio" , "" , "displayActivityRatio" ) ; let trainingImpulse = "-" ; let hrss = "-" ; let best20minHr = "-" ; let best20minHrUnit = "" ; let activityHeartRateReserve = "-" ; let activityHeartRateReserveUnit = "" ; if ( this . analysisData . heartRateData && this . userSettings . displayAdvancedHrData ) { trainingImpulse = this . printNumber ( this . analysisData . heartRateData . TRIMP ) + " <span class=\"summarySubGridTitle\">(" + this . printNumber ( this . analysisData . heartRateData . TRIMPPerHour , 1 ) + " / hour)</span>" ; hrss = this . printNumber ( this . analysisData . heartRateData . HRSS ) + " <span class=\"summarySubGridTitle\">(" + this . printNumber ( this . analysisData . heartRateData . HRSSPerHour , 1 ) + " / hour)</span>" ; activityHeartRateReserve = this . printNumber ( this . analysisData . heartRateData . activityHeartRateReserve ) ; if ( _ . isNumber ( this . analysisData . heartRateData . best20min ) ) { best20minHr = this . printNumber ( this . analysisData . heartRateData . best20min ) ; best20minHrUnit = "bpm" ; } activityHeartRateReserveUnit = "%  <span class=\"summarySubGridTitle\">(Max: " + this . printNumber ( this . analysisData . heartRateData . activityHeartRateReserveMax ) + "% @ " + this . analysisData . heartRateData . maxHeartRate + "bpm)</span>" ; } this . insertContentAtGridPosition ( 0 , 1 , hrss , "Heart Rate Stress Score" , "" , "displayAdvancedHrData" ) ; this . insertContentAtGridPosition ( 1 , 1 , trainingImpulse , "TRaining IMPulse" , "" , "displayAdvancedHrData" ) ; this . insertContentAtGridPosition ( 0 , 2 , best20minHr , "Best 20min Heart Rate" , best20minHrUnit , "displayAdvancedHrData" ) ; this . insertContentAtGridPosition ( 1 , 2 , activityHeartRateReserve , "Heart Rate Reserve Avg" , activityHeartRateReserveUnit , "displayAdvancedHrData" ) ; let climbTime = "-" ; let climbTimeExtra = "" ; if ( this . analysisData . gradeData && this . userSettings . displayAdvancedGradeData ) { climbTime = Helper . secondsToHHMMSS ( this . analysisData . gradeData . upFlatDownInSeconds . up ) ; climbTimeExtra = "<span class=\"summarySubGridTitle\">(" + this . printNumber ( ( this . analysisData . gradeData . upFlatDownInSeconds . up / this . analysisData . gradeData . upFlatDownInSeconds . total * 100 ) ) + "% of time)</span>" ; } this . insertContentAtGridPosition ( 0 , 3 , climbTime , "Time climbing" , climbTimeExtra , "displayAdvancedGradeData" ) ; } protected placeExtendedStatsButton ( buttonAdded ) { let htmlButton = "<section style=\"text-align: center;\">" ; htmlButton += "<a class=\"button btn-block btn-primary\" id=\"extendedStatsButton\" href=\"#\">" ; htmlButton += "Display elevate extended stats" ; htmlButton += "</a>" ; htmlButton += "</section>" ; $ ( ".inline-stats.section" ) . first ( ) . after ( htmlButton ) . each ( ( ) => { $ ( "#extendedStatsButton" ) . click ( ( ) => { this . getFullAnalysisData ( ) . then ( ( result ) => { if ( ! this . athleteSnapshot ) { this . athleteSnapshot = result . athleteSnapshot ; } this . analysisData = result . analysisData ; this . renderViews ( ) ; this . showResultsAndRefreshGraphs ( ) ; } ) ; } ) ; if ( buttonAdded ) { buttonAdded ( ) ; } } ) ; } protected getFullAnalysisData ( ) { return new Promise < { athleteSnapshot : AthleteSnapshotModel ; analysisData : AnalysisDataModel ; } > ( resolve => { this . activityProcessor . getAnalysisData ( this . activityInfo , null , ( athleteSnapshot , analysisData ) => { resolve ( { athleteSnapshot : athleteSnapshot , analysisData : analysisData } ) ; } ) ; } ) ; } protected placeExtendedStatsButtonSegment ( buttonAdded ) { $ ( "#" + this . segmentEffortButtonId ) . on ( "click" , ( evt ) => { this . getSegmentInfos ( ( segmentInfosResponse ) => { if ( ! _ . isNumber ( segmentInfosResponse . start_index ) || ! _ . isNumber ( segmentInfosResponse . end_index ) ) { return ; } this . activityInfo . segmentEffort = { name : segmentInfosResponse . display_name , elapsedTimeSec : segmentInfosResponse . elapsed_time_raw , } ; this . activityProcessor . getAnalysisData ( this . activityInfo , [ segmentInfosResponse . start_index , segmentInfosResponse . end_index ] , ( athleteSnapshot , analysisData ) => { if ( ! this . athleteSnapshot ) { this . athleteSnapshot = athleteSnapshot ; } this . analysisData = analysisData ; this . renderViews ( ) ; this . showResultsAndRefreshGraphs ( ) ; } ) ; } ) ; evt . preventDefault ( ) ; evt . stopPropagation ( ) ; } ) ; if ( buttonAdded ) { buttonAdded ( ) ; } } protected getSegmentInfos ( callback ) { const effortId = _ . last ( window . location . href . match ( /(\d+)$/g ) ) ; if ( ! effortId ) { console . error ( "No effort id found" ) ; return ; } let segmentInfosResponse ; $ . when ( $ . ajax ( { url : "/segment_efforts/" + effortId , type : "GET" , beforeSend : ( xhr ) => { xhr . setRequestHeader ( "X-Requested-With" , "XMLHttpRequest" ) ; } , dataType : "json" , success : ( xhrResponseText ) => { segmentInfosResponse = xhrResponseText ; } , error : ( err ) => { console . error ( err ) ; } , } ) ) . then ( ( ) => { callback ( segmentInfosResponse ) ; } ) ; } protected renderViews ( ) { this . content = "" ; this . setDataViewsNeeded ( ) ; _ . forEach ( this . dataViews , ( view ) => { if ( ! view ) { console . warn ( view ) ; } view . render ( ) ; this . content += view . getContent ( ) ; } ) ; } protected showResultsAndRefreshGraphs ( ) { $ . fancybox ( { padding : 0 , margin : 15 , width : "100%" , height : "100%" , autoScale : true , transitionIn : "none" , transitionOut : "none" , closeBtn : false , type : "iframe" , content : "<div class=\"elevateExtendedData\">" + this . content + "</div>" , } ) ; _ . forEach ( this . dataViews , ( view ) => { view . displayGraph ( ) ; } ) ; } protected setDataViewsNeeded ( ) { this . cleanDataViews ( ) ; const headerView = new HeaderView ( this . activityInfo ) ; headerView . setAppResources ( this . appResources ) ; headerView . setAppResources ( this . appResources ) ; headerView . setIsAuthorOfViewedActivity ( this . activityInfo . isOwner ) ; headerView . setActivityType ( this . activityType ) ; headerView . setIsSegmentEffortView ( this . type === AbstractExtendedDataModifier . TYPE_SEGMENT ) ; this . dataViews . push ( headerView ) ; if ( this . analysisData ) { const featuredDataView = new FeaturedDataView ( this . analysisData , this . userSettings , this . activityInfo ) ; featuredDataView . setAppResources ( this . appResources ) ; featuredDataView . setIsAuthorOfViewedActivity ( this . activityInfo . isOwner ) ; featuredDataView . setActivityType ( this . activityType ) ; featuredDataView . setIsSegmentEffortView ( this . type === AbstractExtendedDataModifier . TYPE_SEGMENT ) ; this . dataViews . push ( featuredDataView ) ; } if ( this . analysisData . heartRateData && this . userSettings . displayAdvancedHrData ) { const heartRateDataView = new HeartRateDataView ( this . analysisData . heartRateData , "hrr" , this . athleteSnapshot ) ; heartRateDataView . setAppResources ( this . appResources ) ; heartRateDataView . setIsAuthorOfViewedActivity ( this . activityInfo . isOwner ) ; heartRateDataView . setActivityType ( this . activityType ) ; heartRateDataView . setIsSegmentEffortView ( this . type === AbstractExtendedDataModifier . TYPE_SEGMENT ) ; this . dataViews . push ( heartRateDataView ) ; } } protected cleanDataViews ( ) { if ( ! _ . isEmpty ( this . dataViews ) ) { for ( let i = 0 ; i < this . dataViews . length ; i ++ ) { this . dataViews [ i ] = null ; delete this . dataViews [ i ] ; } this . dataViews = [ ] ; } } protected insertContentAtGridPosition ( columnId , rowId , data , title , units , userSettingKey ) { let onClickHtmlBehaviour = "" ; if ( userSettingKey ) { onClickHtmlBehaviour = "onclick='javascript:window.open(\"" + this . appResources . settingsLink + "#/globalSettings?viewOptionHelperId=" + userSettingKey + "\",\"_blank\");'" ; } if ( this . summaryGrid ) { const content = "<span class=\"summaryGridDataContainer\" " + onClickHtmlBehaviour + ">" + data + " <span class=\"summaryGridUnits\">" + units + "</span><br /><span class=\"summaryGridTitle\">" + title + "</span></span>" ; this . summaryGrid . find ( "[data-column=" + columnId + "][data-row=" + rowId + "]" ) . html ( content ) ; } else { console . error ( "Grid is not initialized" ) ; } } public printNumber ( value , decimals ? ) { return ( _ . isNumber ( value ) && ! _ . isNaN ( value ) && _ . isFinite ( value ) ) ? value . toFixed ( ( decimals ) ? decimals : 0 ) : "-" ; } }