From dd24f1bb0a4b252e5342b251ba36853364da7b8e Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Thu, 19 Aug 2021 09:24:29 +0200 Subject: Add video filters to common video pages --- .../shared/comment/video-comments.component.html | 8 +- .../playlist/video-watch-playlist.component.html | 2 +- client/src/app/+videos/video-list/index.ts | 4 +- .../overview/video-overview.component.html | 2 +- .../src/app/+videos/video-list/trending/index.ts | 2 - .../trending/video-trending-header.component.html | 8 - .../trending/video-trending-header.component.scss | 20 -- .../trending/video-trending-header.component.ts | 109 ---------- .../trending/video-trending.component.ts | 127 ------------ .../+videos/video-list/video-local.component.ts | 81 -------- .../video-list/video-recently-added.component.ts | 73 ------- .../video-user-subscriptions.component.html | 17 ++ .../video-user-subscriptions.component.ts | 133 ++++++------- .../videos-list-common-page.component.html | 22 +++ .../videos-list-common-page.component.ts | 219 +++++++++++++++++++++ client/src/app/+videos/videos-routing.module.ts | 54 ++--- client/src/app/+videos/videos.module.ts | 12 +- 17 files changed, 342 insertions(+), 551 deletions(-) delete mode 100644 client/src/app/+videos/video-list/trending/index.ts delete mode 100644 client/src/app/+videos/video-list/trending/video-trending-header.component.html delete mode 100644 client/src/app/+videos/video-list/trending/video-trending-header.component.scss delete mode 100644 client/src/app/+videos/video-list/trending/video-trending-header.component.ts delete mode 100644 client/src/app/+videos/video-list/trending/video-trending.component.ts delete mode 100644 client/src/app/+videos/video-list/video-local.component.ts delete mode 100644 client/src/app/+videos/video-list/video-recently-added.component.ts create mode 100644 client/src/app/+videos/video-list/video-user-subscriptions.component.html create mode 100644 client/src/app/+videos/video-list/videos-list-common-page.component.html create mode 100644 client/src/app/+videos/video-list/videos-list-common-page.component.ts (limited to 'client/src/app/+videos') diff --git a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html index 9e6fde2e0..0e00c9c0e 100644 --- a/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html +++ b/client/src/app/+videos/+video-watch/shared/comment/video-comments.component.html @@ -27,13 +27,7 @@
No comments.
-
+
diff --git a/client/src/app/+videos/video-list/index.ts b/client/src/app/+videos/video-list/index.ts index dc27e29e2..3492f43f4 100644 --- a/client/src/app/+videos/video-list/index.ts +++ b/client/src/app/+videos/video-list/index.ts @@ -1,4 +1,2 @@ export * from './overview' -export * from './trending' -export * from './video-local.component' -export * from './video-recently-added.component' +export * from './videos-list-common-page.component' diff --git a/client/src/app/+videos/video-list/overview/video-overview.component.html b/client/src/app/+videos/video-list/overview/video-overview.component.html index d3c602aa5..1a715560c 100644 --- a/client/src/app/+videos/video-list/overview/video-overview.component.html +++ b/client/src/app/+videos/video-list/overview/video-overview.component.html @@ -4,7 +4,7 @@
No results.
diff --git a/client/src/app/+videos/video-list/trending/index.ts b/client/src/app/+videos/video-list/trending/index.ts deleted file mode 100644 index 70835885a..000000000 --- a/client/src/app/+videos/video-list/trending/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './video-trending-header.component' -export * from './video-trending.component' diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.html b/client/src/app/+videos/video-list/trending/video-trending-header.component.html deleted file mode 100644 index db81ce6a1..000000000 --- a/client/src/app/+videos/video-list/trending/video-trending-header.component.html +++ /dev/null @@ -1,8 +0,0 @@ -
- - - -
diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.scss b/client/src/app/+videos/video-list/trending/video-trending-header.component.scss deleted file mode 100644 index 54b072314..000000000 --- a/client/src/app/+videos/video-list/trending/video-trending-header.component.scss +++ /dev/null @@ -1,20 +0,0 @@ -@use '_mixins' as *; - -.btn-group label { - border: 1px solid transparent; - border-radius: 9999px !important; - padding: 5px 16px; - opacity: .8; - - &:not(:first-child) { - @include margin-left(.5rem); - } - - my-global-icon { - @include margin-right(.1rem); - - position: relative; - top: -2px; - height: 1rem; - } -} diff --git a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts deleted file mode 100644 index c94655c74..000000000 --- a/client/src/app/+videos/video-list/trending/video-trending-header.component.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { Subscription } from 'rxjs' -import { Component, HostBinding, Inject, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, RedirectService } from '@app/core' -import { ServerService } from '@app/core/server/server.service' -import { GlobalIconName } from '@app/shared/shared-icons' -import { VideoListHeaderComponent } from '@app/shared/shared-video-miniature' - -interface VideoTrendingHeaderItem { - label: string - iconName: GlobalIconName - value: string - tooltip?: string - hidden?: boolean -} - -@Component({ - selector: 'my-video-trending-title-page', - styleUrls: [ './video-trending-header.component.scss' ], - templateUrl: './video-trending-header.component.html' -}) -export class VideoTrendingHeaderComponent extends VideoListHeaderComponent implements OnInit, OnDestroy { - @HostBinding('class') class = 'title-page title-page-single' - - buttons: VideoTrendingHeaderItem[] - - private algorithmChangeSub: Subscription - - constructor ( - @Inject('data') public data: any, - private route: ActivatedRoute, - private router: Router, - private auth: AuthService, - private serverService: ServerService, - private redirectService: RedirectService - ) { - super(data) - - this.buttons = [ - { - label: $localize`:A variant of Trending videos based on the number of recent interactions, minus user history:Best`, - iconName: 'award', - value: 'best', - tooltip: $localize`Videos with the most interactions for recent videos, minus user history`, - hidden: true - }, - { - label: $localize`:A variant of Trending videos based on the number of recent interactions:Hot`, - iconName: 'flame', - value: 'hot', - tooltip: $localize`Videos with the most interactions for recent videos`, - hidden: true - }, - { - label: $localize`:Main variant of Trending videos based on number of recent views:Views`, - iconName: 'trending', - value: 'most-viewed', - tooltip: $localize`Videos with the most views during the last 24 hours` - }, - { - label: $localize`:A variant of Trending videos based on the number of likes:Likes`, - iconName: 'like', - value: 'most-liked', - tooltip: $localize`Videos that have the most likes` - } - ] - } - - ngOnInit () { - const serverConfig = this.serverService.getHTMLConfig() - const algEnabled = serverConfig.trending.videos.algorithms.enabled - - this.buttons = this.buttons.map(b => { - b.hidden = !algEnabled.includes(b.value) - - // Best is adapted by the user history so - if (b.value === 'best' && !this.auth.isLoggedIn()) { - b.hidden = true - } - - return b - }) - - this.algorithmChangeSub = this.route.queryParams.subscribe( - queryParams => { - this.data.model = queryParams['alg'] || this.redirectService.getDefaultTrendingAlgorithm() - } - ) - } - - ngOnDestroy () { - if (this.algorithmChangeSub) this.algorithmChangeSub.unsubscribe() - } - - setSort () { - const alg = this.data.model !== this.redirectService.getDefaultTrendingAlgorithm() - ? this.data.model - : undefined - - this.router.navigate( - [], - { - relativeTo: this.route, - queryParams: { alg }, - queryParamsHandling: 'merge' - } - ) - } -} diff --git a/client/src/app/+videos/video-list/trending/video-trending.component.ts b/client/src/app/+videos/video-list/trending/video-trending.component.ts deleted file mode 100644 index 085f29a8b..000000000 --- a/client/src/app/+videos/video-list/trending/video-trending.component.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { Subscription } from 'rxjs' -import { first, switchMap } from 'rxjs/operators' -import { Component, ComponentFactoryResolver, Injector, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Params, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, RedirectService, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { VideoSortField } from '@shared/models' -import { VideoTrendingHeaderComponent } from './video-trending-header.component' - -@Component({ - selector: 'my-videos-hot', - styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { - HeaderComponent = VideoTrendingHeaderComponent - titlePage: string - defaultSort: VideoSortField = '-trending' - - loadUserVideoPreferences = true - - private algorithmChangeSub: Subscription - - constructor ( - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - protected cfr: ComponentFactoryResolver, - private videoService: VideoService, - private redirectService: RedirectService, - private hooks: HooksService - ) { - super() - - this.defaultSort = this.parseAlgorithm(this.redirectService.getDefaultTrendingAlgorithm()) - - this.headerComponentInjector = this.getInjector() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - - // Subscribe to alg change after we loaded the data - // The initial alg load is handled by the parent class - this.algorithmChangeSub = this.onDataSubject - .pipe( - first(), - switchMap(() => this.route.queryParams) - ).subscribe(queryParams => { - const oldSort = this.sort - - this.loadPageRouteParams(queryParams) - - if (oldSort !== this.sort) this.reloadVideos() - } - ) - } - - ngOnDestroy () { - super.ngOnDestroy() - if (this.algorithmChangeSub) this.algorithmChangeSub.unsubscribe() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.trending-videos.videos.list.params', - 'filter:api.trending-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } - - getInjector () { - return Injector.create({ - providers: [ { - provide: 'data', - useValue: { - model: this.defaultSort - } - } ] - }) - } - - protected loadPageRouteParams (queryParams: Params) { - const algorithm = queryParams['alg'] || this.redirectService.getDefaultTrendingAlgorithm() - - this.sort = this.parseAlgorithm(algorithm) - } - - private parseAlgorithm (algorithm: string): VideoSortField { - switch (algorithm) { - case 'most-viewed': - return '-trending' - - case 'most-liked': - return '-likes' - - default: - return '-' + algorithm as VideoSortField - } - } -} diff --git a/client/src/app/+videos/video-list/video-local.component.ts b/client/src/app/+videos/video-list/video-local.component.ts deleted file mode 100644 index b576883d1..000000000 --- a/client/src/app/+videos/video-list/video-local.component.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { VideoFilter, VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-local', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - sort = '-publishedAt' as VideoSortField - filter: VideoFilter = 'local' - - loadUserVideoPreferences = true - - constructor ( - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - protected cfr: ComponentFactoryResolver, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.titlePage = $localize`Local videos` - } - - ngOnInit () { - super.ngOnInit() - - this.enableAllFilterIfPossible() - this.generateSyndicationList() - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - filter: this.filter, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.local-videos.videos.list.params', - 'filter:api.local-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, this.filter, this.categoryOneOf) - } - - toggleModerationDisplay () { - this.filter = this.buildLocalFilter(this.filter, 'local') - - this.reloadVideos() - } -} diff --git a/client/src/app/+videos/video-list/video-recently-added.component.ts b/client/src/app/+videos/video-list/video-recently-added.component.ts deleted file mode 100644 index 506f92d25..000000000 --- a/client/src/app/+videos/video-list/video-recently-added.component.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' -import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' -import { VideoService } from '@app/shared/shared-main' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { VideoSortField } from '@shared/models' - -@Component({ - selector: 'my-videos-recently-added', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - sort: VideoSortField = '-publishedAt' - groupByDate = true - - loadUserVideoPreferences = true - - constructor ( - protected route: ActivatedRoute, - protected serverService: ServerService, - protected router: Router, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, - protected cfr: ComponentFactoryResolver, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - - this.titlePage = $localize`Recently added` - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) - const params = { - videoPagination: newPagination, - sort: this.sort, - categoryOneOf: this.categoryOneOf, - languageOneOf: this.languageOneOf, - nsfwPolicy: this.nsfwPolicy, - skipCount: true - } - - return this.hooks.wrapObsFun( - this.videoService.getVideos.bind(this.videoService), - params, - 'common', - 'filter:api.recently-added-videos.videos.list.params', - 'filter:api.recently-added-videos.videos.list.result' - ) - } - - generateSyndicationList () { - this.syndicationItems = this.videoService.getVideoFeedUrls(this.sort, undefined, this.categoryOneOf) - } -} diff --git a/client/src/app/+videos/video-list/video-user-subscriptions.component.html b/client/src/app/+videos/video-list/video-user-subscriptions.component.html new file mode 100644 index 000000000..2675b58bf --- /dev/null +++ b/client/src/app/+videos/video-list/video-user-subscriptions.component.html @@ -0,0 +1,17 @@ + + diff --git a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts index a1498e797..43cbab9f6 100644 --- a/client/src/app/+videos/video-list/video-user-subscriptions.component.ts +++ b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts @@ -1,94 +1,53 @@ -import { switchMap } from 'rxjs/operators' -import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' -import { ActivatedRoute, Router } from '@angular/router' -import { AuthService, LocalStorageService, Notifier, ScopedTokensService, ScreenService, ServerService, UserService } from '@app/core' +import { firstValueFrom } from 'rxjs' +import { switchMap, tap } from 'rxjs/operators' +import { Component } from '@angular/core' +import { AuthService, ComponentPaginationLight, DisableForReuseHook, ScopedTokensService } from '@app/core' import { HooksService } from '@app/core/plugins/hooks.service' -import { immutableAssign } from '@app/helpers' import { VideoService } from '@app/shared/shared-main' import { UserSubscriptionService } from '@app/shared/shared-user-subscription' -import { AbstractVideoList } from '@app/shared/shared-video-miniature' -import { FeedFormat, VideoSortField } from '@shared/models' -import { environment } from '../../../environments/environment' -import { copyToClipboard } from '../../../root-helpers/utils' +import { VideoFilters } from '@app/shared/shared-video-miniature' +import { VideoSortField } from '@shared/models' @Component({ selector: 'my-videos-user-subscriptions', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' + templateUrl: './video-user-subscriptions.component.html' }) -export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy { - titlePage: string - sort = '-publishedAt' as VideoSortField - groupByDate = true +export class VideoUserSubscriptionsComponent implements DisableForReuseHook { + getVideosObservableFunction = this.getVideosObservable.bind(this) + getSyndicationItemsFunction = this.getSyndicationItems.bind(this) + + defaultSort = '-publishedAt' as VideoSortField + + actions = [ + { + routerLink: '/my-library/subscriptions', + label: $localize`Subscriptions`, + iconName: 'cog' + } + ] + + titlePage = $localize`Videos from your subscriptions` + + disabled = false + + private feedToken: string constructor ( - protected router: Router, - protected serverService: ServerService, - protected route: ActivatedRoute, - protected notifier: Notifier, - protected authService: AuthService, - protected userService: UserService, - protected screenService: ScreenService, - protected storageService: LocalStorageService, + private authService: AuthService, private userSubscription: UserSubscriptionService, - protected cfr: ComponentFactoryResolver, private hooks: HooksService, private videoService: VideoService, private scopedTokensService: ScopedTokensService ) { - super() - this.titlePage = $localize`Videos from your subscriptions` - - this.actions.push({ - routerLink: '/my-library/subscriptions', - label: $localize`Subscriptions`, - iconName: 'cog' - }) } - ngOnInit () { - super.ngOnInit() - - const user = this.authService.getUser() - let feedUrl = environment.originServerUrl - - this.authService.userInformationLoaded - .pipe(switchMap(() => this.scopedTokensService.getScopedTokens())) - .subscribe({ - next: tokens => { - const feeds = this.videoService.getVideoSubscriptionFeedUrls(user.account.id, tokens.feedToken) - feedUrl = feedUrl + feeds.find(f => f.format === FeedFormat.RSS).url - - this.actions.unshift({ - label: $localize`Copy feed URL`, - iconName: 'syndication', - justIcon: true, - href: feedUrl, - click: e => { - e.preventDefault() - copyToClipboard(feedUrl) - this.activateCopiedMessage() - } - }) - }, - - error: err => { - this.notifier.error(err.message) - } - }) - } - - ngOnDestroy () { - super.ngOnDestroy() - } - - getVideosObservable (page: number) { - const newPagination = immutableAssign(this.pagination, { currentPage: page }) + getVideosObservable (pagination: ComponentPaginationLight, filters: VideoFilters) { const params = { - videoPagination: newPagination, - sort: this.sort, + ...filters.toVideosAPIObject(), + + videoPagination: pagination, skipCount: true } @@ -101,12 +60,32 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement ) } - generateSyndicationList () { - /* method disabled: the view provides its own */ - throw new Error('Method not implemented.') + getSyndicationItems () { + return this.loadFeedToken() + .then(() => { + const user = this.authService.getUser() + + return this.videoService.getVideoSubscriptionFeedUrls(user.account.id, this.feedToken) + }) } - activateCopiedMessage () { - this.notifier.success($localize`Feed URL copied`) + disableForReuse () { + this.disabled = true + } + + enabledForReuse () { + this.disabled = false + } + + private loadFeedToken () { + if (this.feedToken) return Promise.resolve(this.feedToken) + + const obs = this.authService.userInformationLoaded + .pipe( + switchMap(() => this.scopedTokensService.getScopedTokens()), + tap(tokens => this.feedToken = tokens.feedToken) + ) + + return firstValueFrom(obs) } } diff --git a/client/src/app/+videos/video-list/videos-list-common-page.component.html b/client/src/app/+videos/video-list/videos-list-common-page.component.html new file mode 100644 index 000000000..2831f996f --- /dev/null +++ b/client/src/app/+videos/video-list/videos-list-common-page.component.html @@ -0,0 +1,22 @@ + + diff --git a/client/src/app/+videos/video-list/videos-list-common-page.component.ts b/client/src/app/+videos/video-list/videos-list-common-page.component.ts new file mode 100644 index 000000000..ba64d4fec --- /dev/null +++ b/client/src/app/+videos/video-list/videos-list-common-page.component.ts @@ -0,0 +1,219 @@ +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' +import { VideoFilters, VideoFilterScope } from '@app/shared/shared-video-miniature/video-filters.model' +import { ClientFilterHookName, VideoSortField } from '@shared/models' +import { Subscription } from 'rxjs' + +export type VideosListCommonPageRouteData = { + sort: VideoSortField + + scope: VideoFilterScope + hookParams: ClientFilterHookName + hookResult: ClientFilterHookName +} + +@Component({ + templateUrl: './videos-list-common-page.component.html' +}) +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.filter) + } + + 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' + + 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 ([ 'best', 'hot', 'trending', 'likes' ].includes(sanitizedSort)) { + this.title = $localize`Trending` + + if (sanitizedSort === 'best') this.titleTooltip = $localize`Videos with the most interactions for recent videos, minus user history` + if (sanitizedSort === 'hot') this.titleTooltip = $localize`Videos with the most interactions for recent videos` + if (sanitizedSort === 'likes') this.titleTooltip = $localize`Videos that have the most likes` + + if (sanitizedSort === 'trending') { + if (this.trendingDays === 1) this.titleTooltip = $localize`Videos with the most views during the last 24 hours` + else this.titleTooltip = $localize`Videos with the most views during the last ${this.trendingDays} days` + } + + return + } + } + + private getSanitizedSort (sort: VideoSortField) { + return sort.replace(/^-/, '') as VideoSortField + } +} diff --git a/client/src/app/+videos/videos-routing.module.ts b/client/src/app/+videos/videos-routing.module.ts index 926dfaab0..7db519615 100644 --- a/client/src/app/+videos/videos-routing.module.ts +++ b/client/src/app/+videos/videos-routing.module.ts @@ -1,10 +1,8 @@ import { NgModule } from '@angular/core' -import { RouterModule, Routes } from '@angular/router' +import { RouterModule, Routes, UrlSegment } from '@angular/router' import { LoginGuard } from '@app/core' -import { VideoTrendingComponent } from './video-list' +import { VideosListCommonPageComponent } from './video-list' import { VideoOverviewComponent } from './video-list/overview/video-overview.component' -import { VideoLocalComponent } from './video-list/video-local.component' -import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' import { VideosComponent } from './videos.component' @@ -22,32 +20,35 @@ const videosRoutes: Routes = [ } } }, + { - path: 'trending', - component: VideoTrendingComponent, - data: { - meta: { - title: $localize`Trending videos` - } - } - }, - { + // Old URL redirection path: 'most-liked', - redirectTo: 'trending?alg=most-liked' + redirectTo: 'trending?sort=most-liked' }, { - path: 'recently-added', - component: VideoRecentlyAddedComponent, + matcher: (url: UrlSegment[]) => { + if (url.length === 1 && [ 'recently-added', 'trending', 'local' ].includes(url[0].path)) { + return { + consumed: url, + posParams: { + page: new UrlSegment(url[0].path, {}) + } + } + } + + return null + }, + + component: VideosListCommonPageComponent, data: { - meta: { - title: $localize`Recently added videos` - }, reuse: { enabled: true, - key: 'recently-added-videos-list' + key: 'videos-list' } } }, + { path: 'subscriptions', canActivate: [ LoginGuard ], @@ -61,19 +62,6 @@ const videosRoutes: Routes = [ key: 'subscription-videos-list' } } - }, - { - path: 'local', - component: VideoLocalComponent, - data: { - meta: { - title: $localize`Local videos` - }, - reuse: { - enabled: true, - key: 'local-videos-list' - } - } } ] } diff --git a/client/src/app/+videos/videos.module.ts b/client/src/app/+videos/videos.module.ts index 8a35015d6..523533c11 100644 --- a/client/src/app/+videos/videos.module.ts +++ b/client/src/app/+videos/videos.module.ts @@ -5,11 +5,8 @@ import { SharedGlobalIconModule } from '@app/shared/shared-icons' import { SharedMainModule } from '@app/shared/shared-main' import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscription' import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' -import { OverviewService, VideoTrendingComponent } from './video-list' +import { OverviewService, VideosListCommonPageComponent } from './video-list' import { VideoOverviewComponent } from './video-list/overview/video-overview.component' -import { VideoTrendingHeaderComponent } from './video-list/trending/video-trending-header.component' -import { VideoLocalComponent } from './video-list/video-local.component' -import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' import { VideosRoutingModule } from './videos-routing.module' import { VideosComponent } from './videos.component' @@ -29,12 +26,9 @@ import { VideosComponent } from './videos.component' declarations: [ VideosComponent, - VideoTrendingHeaderComponent, - VideoTrendingComponent, - VideoRecentlyAddedComponent, - VideoLocalComponent, VideoUserSubscriptionsComponent, - VideoOverviewComponent + VideoOverviewComponent, + VideosListCommonPageComponent ], exports: [ -- cgit v1.2.3