Chocobozzz/PeerTube

View on GitHub
client/src/app/core/routing/scroll.service.ts

Summary

Maintainability
A
1 hr
Test Coverage
import debug from 'debug'
import { pairwise } from 'rxjs'
import { ViewportScroller } from '@angular/common'
import { Injectable } from '@angular/core'
import { RouterSetting } from '../'
import { PeerTubeRouterService } from './peertube-router.service'
import { logger } from '@root-helpers/logger'

const debugLogger = debug('peertube:main:ScrollService')

@Injectable()
export class ScrollService {

  private resetScroll = true

  constructor (
    private viewportScroller: ViewportScroller,
    private peertubeRouter: PeerTubeRouterService
  ) { }

  enableScrollRestoration () {
    // We'll manage scroll restoration ourselves
    this.viewportScroller.setHistoryScrollRestoration('manual')

    this.consumeScroll()
    this.produceScroll()
  }

  private produceScroll () {
    // When we add the a-state parameter, we don't want to alter the scroll
    this.peertubeRouter.getNavigationEndEvents().pipe(pairwise())
                      .subscribe(([ e1, e2 ]) => {
                        try {
                          this.resetScroll = false

                          const previousUrl = new URL(window.location.origin + e1.urlAfterRedirects)
                          const nextUrl = new URL(window.location.origin + e2.urlAfterRedirects)

                          if (previousUrl.pathname !== nextUrl.pathname) {
                            this.resetScroll = true
                            return
                          }

                          if (this.peertubeRouter.hasRouteSetting(RouterSetting.DISABLE_SCROLL_RESTORE)) {
                            this.resetScroll = false
                            return
                          }

                          // Remove route settings from the comparison
                          const nextSearchParams = nextUrl.searchParams
                          nextSearchParams.delete(PeerTubeRouterService.ROUTE_SETTING_NAME)

                          const previousSearchParams = previousUrl.searchParams

                          nextSearchParams.sort()
                          previousSearchParams.sort()

                          if (nextSearchParams.toString() !== previousSearchParams.toString()) {
                            this.resetScroll = true
                          }
                        } catch (err) {
                          logger.error('Cannot parse URL to check next scroll.', err)
                          this.resetScroll = true
                        }
                      })
  }

  private consumeScroll () {
    // Handle anchors/restore position
    this.peertubeRouter.getScrollEvents().subscribe(e => {
      debugLogger('Will schedule scroll after router event %o.', { e, resetScroll: this.resetScroll })

      // scrollToAnchor first to preserve anchor position when using history navigation
      if (e.anchor) {
        setTimeout(() => this.viewportScroller.scrollToAnchor(e.anchor))

        return
      }

      if (e.position) {
        setTimeout(() => this.viewportScroller.scrollToPosition(e.position))

        return
      }

      if (this.resetScroll) {
        return this.viewportScroller.scrollToPosition([ 0, 0 ])
      }
    })
  }

}