import * as _ from "lodash" ; import * as moment from "moment" ; import { Component , HostListener , OnDestroy , OnInit , Renderer2 , ViewChild } from "@angular/core" ; import { AppRoutesModel } from "./shared/models/app-routes.model" ; import { NavigationEnd , Router , RouterEvent } from "@angular/router" ; import { MatDialog , MatIconRegistry , MatSidenav , MatSnackBar } from "@angular/material" ; import { AboutDialogComponent } from "./about-dialog/about-dialog.component" ; import { SideNavService } from "./shared/services/side-nav/side-nav.service" ; import { SideNavStatus } from "./shared/services/side-nav/side-nav-status.enum" ; import { Subscription } from "rxjs" ; import { WindowService } from "./shared/services/window/window.service" ; import { SyncService } from "./shared/services/sync/sync.service" ; import { ConfirmDialogComponent } from "./shared/dialogs/confirm-dialog/confirm-dialog.component" ; import { ConfirmDialogDataModel } from "./shared/dialogs/confirm-dialog/confirm-dialog-data.model" ; import { GotItDialogComponent } from "./shared/dialogs/got-it-dialog/got-it-dialog.component" ; import { GotItDialogDataModel } from "./shared/dialogs/got-it-dialog/got-it-dialog-data.model" ; import { ImportBackupDialogComponent } from "./shared/dialogs/import-backup-dialog/import-backup-dialog.component" ; import { SyncState } from "./shared/services/sync/sync-state.enum" ; import { DomSanitizer } from "@angular/platform-browser" ; import { OverlayContainer } from "@angular/cdk/overlay" ; import { Theme } from "./shared/enums/theme.enum" ; import { SyncResultModel } from "@elevate/shared/models" ; import { SyncedBackupModel } from "./shared/services/sync/synced-backup.model" ; import { AppEventsService } from "./shared/services/external-updates/app-events-service" ; import { PromoteDesktopComponent } from "./promote-desktop/promote-desktop.component" ; class MenuItemModel { public name : string ; public icon : string ; public routerLink : string ; public routerLinkActive : boolean ; public color ? : string ; } @ Component ( { selector : "app-root" , templateUrl : "./app.component.html" , styleUrls : [ "./app.component.scss" ] } ) export class AppComponent implements OnInit , OnDestroy { public static readonly DEFAULT_SIDE_NAV_STATUS : SideNavStatus = SideNavStatus . OPENED ; public static readonly DEFAULT_SIDE_NAV_MODE : string = "side" ; public static readonly LS_SIDE_NAV_OPENED_KEY : string = "app_sideNavOpened" ; public static readonly LS_USER_THEME_PREF : string = "theme" ; public Theme = Theme ; public currentTheme : Theme ; public toolBarTitle : string ; public SyncState = SyncState ; public syncState : SyncState ; public lastSyncDateMessage : string ; public routerEventsSubscription : Subscription ; @ ViewChild ( MatSidenav , { static : true } ) public sideNav : MatSidenav ; public readonly mainMenuItems : Partial < MenuItemModel > [ ] = [ { icon : "desktop_windows" , routerLink : AppRoutesModel . desktopAppUpdate , routerLinkActive : true , color : "warn" } , { icon : "view_list" , routerLink : AppRoutesModel . activities , routerLinkActive : true } , { icon : "timeline" , routerLink : AppRoutesModel . fitnessTrend , routerLinkActive : true } , { icon : "date_range" , routerLink : AppRoutesModel . yearProgressions , routerLinkActive : true } , { icon : "settings" , routerLink : AppRoutesModel . globalSettings , routerLinkActive : true } , { icon : "accessibility" , routerLink : AppRoutesModel . athleteSettings , routerLinkActive : true } , { icon : "format_line_spacing" , routerLink : AppRoutesModel . zonesSettings , routerLinkActive : true } ] ; public static convertRouteToTitle ( route ) { if ( _ . isEmpty ( route ) ) { return null ; } const routeAsArray = _ . split ( route , "/" ) ; if ( _ . isEmpty ( _ . first ( routeAsArray ) ) ) { routeAsArray . shift ( ) ; } let title = _ . first ( routeAsArray ) ; title = _ . first ( title . split ( "?" ) ) ; return _ . startCase ( _ . upperFirst ( title ) ) ; } constructor ( public router , public syncService , public dialog , public snackBar , public sideNavService , public windowService , public overlayContainer , public renderer , public iconRegistry , public sanitizer , public appEventsService ) { this . syncState = null ; this . registerCustomIcons ( ) ; } public ngOnInit ( ) { this . setupThemeOnLoad ( ) ; const noPromoteDesktopApp = localStorage . getItem ( PromoteDesktopComponent . NO_PROMOTE_DESKTOP_MENU_LC_KEY ) ; if ( noPromoteDesktopApp ) { this . mainMenuItems . splice ( 0 , 1 ) ; } _ . forEach ( this . mainMenuItems , ( menuItemModel ) => { menuItemModel . name = AppComponent . convertRouteToTitle ( menuItemModel . routerLink ) ; } ) ; this . sideNavSetup ( ) ; this . toolBarTitle = AppComponent . convertRouteToTitle ( this . router . url ) ; this . routerEventsSubscription = this . router . events . subscribe ( ( routerEvent ) => { if ( routerEvent instanceof NavigationEnd ) { const route = ( < NavigationEnd > routerEvent ) . urlAfterRedirects ; this . toolBarTitle = AppComponent . convertRouteToTitle ( route ) ; } } ) ; this . updateLastSyncDateStatus ( ) ; setInterval ( ( ) => { this . updateLastSyncDateStatus ( ) ; } , 1000 * 60 ) ; this . appEventsService . onSyncDone . subscribe ( ( syncResult ) => { if ( syncResult ) { this . updateLastSyncDateStatus ( ) ; } } ) ; } public sideNavSetup ( ) { this . sideNav . opened = ( AppComponent . DEFAULT_SIDE_NAV_STATUS === SideNavStatus . OPENED ) ; const sideNavOpened = localStorage . getItem ( AppComponent . LS_SIDE_NAV_OPENED_KEY ) ; if ( sideNavOpened ) { this . sideNav . opened = ( sideNavOpened === "true" ) ; } } public setupThemeOnLoad ( ) { let themeToBeLoaded = Theme . DEFAULT ; const existingSavedTheme = localStorage . getItem ( AppComponent . LS_USER_THEME_PREF ) as Theme ; if ( existingSavedTheme ) { themeToBeLoaded = existingSavedTheme ; } this . setTheme ( themeToBeLoaded ) ; } public setTheme ( theme ) { this . currentTheme = theme ; const previousTheme = this . overlayContainer . getContainerElement ( ) . classList [ 1 ] as Theme ; if ( previousTheme ) { this . overlayContainer . getContainerElement ( ) . classList . remove ( previousTheme ) ; } this . overlayContainer . getContainerElement ( ) . classList . add ( this . currentTheme ) ; this . renderer . setAttribute ( document . body , "class" , this . currentTheme ) ; } public updateLastSyncDateStatus ( ) { this . syncService . getSyncState ( ) . then ( ( syncState ) => { this . syncState = syncState ; this . syncService . getLastSyncDateTime ( ) . then ( ( lastSyncDateTime ) => { if ( _ . isNumber ( lastSyncDateTime ) ) { this . lastSyncDateMessage = moment ( lastSyncDateTime ) . fromNow ( ) ; } } ) ; } ) ; } @ HostListener ( "window:resize" ) public setupWindowResizeBroadcast ( ) { this . windowService . onResize ( ) ; } public onSync ( fastSync , forceSync ) { if ( this . syncState === SyncState . NOT_SYNCED ) { const data = { title : "⚠️ First synchronisation" , content : "Your first synchronisation can take a long time and can be done in several times if you have more than 400 activities. Make sure you properly setup your " + "athlete settings before (Cycling FTP, Running FTP, Swim FTP, Heart rate, ...) or may have missing results in Elevate features. This is to avoid a redo of the first synchronisation." , confirmText : "Start sync" , cancelText : "Check my athlete settings" } ; const dialogRef = this . dialog . open ( ConfirmDialogComponent , { minWidth : ConfirmDialogComponent . MIN_WIDTH , maxWidth : "50%" , data : data } ) ; const afterClosedSubscription = dialogRef . afterClosed ( ) . subscribe ( ( confirm ) => { if ( confirm ) { this . syncService . sync ( fastSync , forceSync ) ; } else { this . router . navigate ( [ AppRoutesModel . athleteSettings ] ) ; } afterClosedSubscription . unsubscribe ( ) ; } ) ; } else { this . syncService . sync ( fastSync , forceSync ) ; } } public onClearSyncedData ( ) { const data = { title : "Clear your athlete synced data" , content : "Are you sure to perform this action? You will be able to re-import synced data through backup file " + "or a new re-synchronization." } ; const dialogRef = this . dialog . open ( ConfirmDialogComponent , { minWidth : ConfirmDialogComponent . MIN_WIDTH , maxWidth : ConfirmDialogComponent . MAX_WIDTH , data : data } ) ; const afterClosedSubscription = dialogRef . afterClosed ( ) . subscribe ( ( confirm ) => { if ( confirm ) { this . syncService . clearSyncedData ( ) . then ( ( ) => { location . reload ( ) ; } , error => { this . snackBar . open ( error , "Close" ) ; } ) ; } afterClosedSubscription . unsubscribe ( ) ; } ) ; } public onSyncedBackupExport ( ) { this . syncService . export ( ) . then ( ( result ) => { this . dialog . open ( GotItDialogComponent , { minWidth : GotItDialogComponent . MIN_WIDTH , maxWidth : GotItDialogComponent . MAX_WIDTH , data : new GotItDialogDataModel ( null , "File \"" + result . filename + "\" is being saved to your download folder." ) } ) ; } , error => { this . snackBar . open ( error , "Close" ) ; } ) ; } public onSyncedBackupImport ( ) { const dialogRef = this . dialog . open ( ImportBackupDialogComponent , { minWidth : ImportBackupDialogComponent . MIN_WIDTH , maxWidth : ImportBackupDialogComponent . MAX_WIDTH , } ) ; const afterClosedSubscription = dialogRef . afterClosed ( ) . subscribe ( ( backupModel ) => { if ( backupModel ) { this . syncService . import ( backupModel ) . then ( ( ) => { location . reload ( ) ; } , error => { this . snackBar . open ( error , "Close" ) ; } ) ; } afterClosedSubscription . unsubscribe ( ) ; } ) ; } public onThemeToggle ( ) { const targetTheme = ( this . currentTheme === Theme . LIGHT ) ? Theme . DARK : Theme . LIGHT ; this . setTheme ( targetTheme ) ; localStorage . setItem ( AppComponent . LS_USER_THEME_PREF , targetTheme ) ; } public onShowReleaseNotes ( ) { this . router . navigate ( [ AppRoutesModel . releasesNotes ] ) ; } public onShowShare ( ) { this . router . navigate ( [ AppRoutesModel . share ] ) ; } public onShowReport ( ) { this . router . navigate ( [ AppRoutesModel . report ] ) ; } public onShowFaq ( ) { this . router . navigate ( [ AppRoutesModel . frequentlyAskedQuestions ] ) ; } public onShowAbout ( ) { this . dialog . open ( AboutDialogComponent , { minWidth : AboutDialogComponent . MIN_WIDTH , maxWidth : AboutDialogComponent . MAX_WIDTH , } ) ; } public onAdvanceMenu ( ) { this . router . navigate ( [ AppRoutesModel . advancedMenu ] ) ; } public onSideNavClosed ( ) { this . sideNavService . onChange ( SideNavStatus . CLOSED ) ; } public onSideNavOpened ( ) { this . sideNavService . onChange ( SideNavStatus . OPENED ) ; } public onSideNavToggle ( ) { this . sideNav . toggle ( ) ; localStorage . setItem ( AppComponent . LS_SIDE_NAV_OPENED_KEY , ( this . sideNav . opened ) ? "true" : "false" ) ; } public onOpenLink ( url ) { window . open ( url , "_blank" ) ; } public registerCustomIcons ( ) { this . iconRegistry . addSvgIcon ( "strava" , this . sanitizer . bypassSecurityTrustResourceUrl ( "./assets/icons/strava.svg" ) ) ; } public ngOnDestroy ( ) { this . routerEventsSubscription . unsubscribe ( ) ; } }