Chocobozzz/PeerTube

View on GitHub
client/src/app/+videos/video-list/videos-list-common-page.component.ts

Summary

Maintainability
B
5 hrs
Test Coverage
import { Component, OnDestroy, OnInit } from '@angular/core'
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router'
import { ComponentPaginationLight, DisableForReuseHook, MetaService, RedirectService, ServerService } from '@app/core'
import { HooksService } from '@app/core/plugins/hooks.service'
import { VideoService } from '@app/shared/shared-main/video/video.service'
import { VideoFilterScope, VideoFilters } from '@app/shared/shared-video-miniature/video-filters.model'
import { ClientFilterHookName, VideoSortField } from '@peertube/peertube-models'
import { Subscription } from 'rxjs'
import { VideosListComponent } from '../../shared/shared-video-miniature/videos-list.component'

export type VideosListCommonPageRouteData = {
  sort: VideoSortField

  scope: VideoFilterScope
  hookParams: ClientFilterHookName
  hookResult: ClientFilterHookName
}

@Component({
  templateUrl: './videos-list-common-page.component.html',
  standalone: true,
  imports: [ VideosListComponent ]
})
export class VideosListCommonPageComponent implements OnInit, OnDestroy, DisableForReuseHook {
  getVideosObservableFunction = this.getVideosObservable.bind(this)
  getSyndicationItemsFunction = this.getSyndicationItems.bind(this)
  baseRouteBuilderFunction = this.baseRouteBuilder.bind(this)

  title: string
  titleTooltip: string

  groupByDate: boolean

  defaultSort: VideoSortField
  defaultScope: VideoFilterScope

  hookParams: ClientFilterHookName
  hookResult: ClientFilterHookName

  loadUserVideoPreferences = true

  displayFilters = true

  disabled = false

  private trendingDays: number
  private routeSub: Subscription

  constructor (
    private server: ServerService,
    private route: ActivatedRoute,
    private videoService: VideoService,
    private hooks: HooksService,
    private meta: MetaService,
    private redirectService: RedirectService
  ) {
  }

  ngOnInit () {
    this.trendingDays = this.server.getHTMLConfig().trending.videos.intervalDays

    this.routeSub = this.route.params.subscribe(params => {
      this.update(params['page'])
    })
  }

  ngOnDestroy () {
    if (this.routeSub) this.routeSub.unsubscribe()
  }

  getVideosObservable (pagination: ComponentPaginationLight, filters: VideoFilters) {
    const params = {
      ...filters.toVideosAPIObject(),

      videoPagination: { ...pagination },
      skipCount: true
    }

    return this.hooks.wrapObsFun(
      this.videoService.getVideos.bind(this.videoService),
      params,
      'common',
      this.hookParams,
      this.hookResult
    )
  }

  getSyndicationItems (filters: VideoFilters) {
    const result = filters.toVideosAPIObject()

    return this.videoService.getVideoFeedUrls(result.sort, result.isLocal)
  }

  onFiltersChanged (filters: VideoFilters) {
    this.buildTitle(filters.scope, filters.sort)
    this.updateGroupByDate(filters.sort)
  }

  baseRouteBuilder (filters: VideoFilters) {
    const sanitizedSort = this.getSanitizedSort(filters.sort)

    let suffix: string

    if (filters.scope === 'local') suffix = 'local'
    else if (sanitizedSort === 'publishedAt') suffix = 'recently-added'
    else suffix = 'trending'

    return [ '/videos', suffix ]
  }

  disableForReuse () {
    this.disabled = true
  }

  enabledForReuse () {
    this.disabled = false
  }

  update (page: string) {
    const data = this.getData(page)

    this.hookParams = data.hookParams
    this.hookResult = data.hookResult

    this.defaultSort = data.sort
    this.defaultScope = data.scope

    this.buildTitle()
    this.updateGroupByDate(this.defaultSort)

    this.meta.setTitle(this.title)
  }

  private getData (page: string) {
    if (page === 'trending') return this.generateTrendingData(this.route.snapshot)

    if (page === 'local') return this.generateLocalData()

    return this.generateRecentlyAddedData()
  }

  private generateRecentlyAddedData (): VideosListCommonPageRouteData {
    return {
      sort: '-publishedAt',
      scope: 'federated',
      hookParams: 'filter:api.recently-added-videos.videos.list.params',
      hookResult: 'filter:api.recently-added-videos.videos.list.result'
    }
  }

  private generateLocalData (): VideosListCommonPageRouteData {
    return {
      sort: '-publishedAt' as VideoSortField,
      scope: 'local',
      hookParams: 'filter:api.local-videos.videos.list.params',
      hookResult: 'filter:api.local-videos.videos.list.result'
    }
  }

  private generateTrendingData (route: ActivatedRouteSnapshot): VideosListCommonPageRouteData {
    const sort = route.queryParams['sort'] ?? this.parseTrendingAlgorithm(this.redirectService.getDefaultTrendingAlgorithm())

    return {
      sort,
      scope: 'federated',
      hookParams: 'filter:api.trending-videos.videos.list.params',
      hookResult: 'filter:api.trending-videos.videos.list.result'
    }
  }

  private parseTrendingAlgorithm (algorithm: string): VideoSortField {
    switch (algorithm) {
      case 'most-viewed':
        return '-trending'

      case 'most-liked':
        return '-likes'

      // We'll automatically apply "best" sort if using "hot" sort with a logged user
      case 'best':
        return '-hot'

      default:
        return '-' + algorithm as VideoSortField
    }
  }

  private updateGroupByDate (sort: VideoSortField) {
    this.groupByDate = sort === '-publishedAt' || sort === 'publishedAt'
  }

  private buildTitle (scope: VideoFilterScope = this.defaultScope, sort: VideoSortField = this.defaultSort) {
    const sanitizedSort = this.getSanitizedSort(sort)

    if (scope === 'local') {
      this.title = $localize`Local videos`
      this.titleTooltip = $localize`Only videos uploaded on this instance are displayed`
      return
    }

    if (sanitizedSort === 'publishedAt') {
      this.title = $localize`Recently added`
      this.titleTooltip = undefined
      return
    }

    if ([ 'hot', 'trending', 'likes', 'views' ].includes(sanitizedSort)) {
      this.title = $localize`Trending`

      if (sanitizedSort === 'hot') {
        this.titleTooltip = $localize`Videos with the most interactions for recent videos`
        return
      }

      if (sanitizedSort === 'likes') {
        this.titleTooltip = $localize`Videos that have the most likes`
        return
      }

      if (sanitizedSort === 'views') {
        this.titleTooltip = undefined
        return
      }

      if (sanitizedSort === 'trending') {
        if (this.trendingDays === 1) {
          this.titleTooltip = $localize`Videos with the most views during the last 24 hours`
          return
        }

        this.titleTooltip = $localize`Videos with the most views during the last ${this.trendingDays} days`
      }

      return
    }
  }

  private getSanitizedSort (sort: VideoSortField) {
    return sort.replace(/^-/, '') as VideoSortField
  }
}