From 5bcbcbe338ef5a1ed14f084311d013fbb25dabcf Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Fri, 22 Jan 2021 00:12:44 +0100 Subject: modularize abstract video list header and implement video hotness recommendation variant --- .../account-search/account-search.component.ts | 10 +-- .../account-videos/account-videos.component.ts | 5 +- .../+my-library/my-history/my-history.component.ts | 6 +- .../video-channel-videos.component.ts | 3 +- client/src/app/+videos/video-list/index.ts | 3 +- .../src/app/+videos/video-list/trending/index.ts | 4 + .../video-list/trending/video-hot.component.ts | 85 +++++++++++++++++++ .../trending/video-most-liked.component.ts | 81 ++++++++++++++++++ .../trending/video-trending-header.component.html | 6 ++ .../trending/video-trending-header.component.scss | 17 ++++ .../trending/video-trending-header.component.ts | 59 +++++++++++++ .../trending/video-trending.component.ts | 99 ++++++++++++++++++++++ .../+videos/video-list/video-local.component.ts | 3 +- .../video-list/video-most-liked.component.ts | 68 --------------- .../video-list/video-recently-added.component.ts | 3 +- .../+videos/video-list/video-trending.component.ts | 83 ------------------ .../video-user-subscriptions.component.ts | 6 +- client/src/app/+videos/videos-routing.module.ts | 18 +++- client/src/app/+videos/videos.module.ts | 9 +- client/src/app/menu/menu.component.html | 5 -- .../shared/shared-icons/global-icon.component.ts | 1 + .../app/shared/shared-main/shared-main.module.ts | 5 +- .../abstract-video-list.html | 8 +- .../abstract-video-list.scss | 10 +-- .../shared-video-miniature/abstract-video-list.ts | 50 ++++++++++- .../src/app/shared/shared-video-miniature/index.ts | 2 +- .../shared-video-miniature.module.ts | 4 +- .../video-list-header.component.ts | 20 +++++ .../videos-selection.component.ts | 4 +- client/src/assets/images/misc/flame.svg | 4 + 30 files changed, 487 insertions(+), 194 deletions(-) create mode 100644 client/src/app/+videos/video-list/trending/index.ts create mode 100644 client/src/app/+videos/video-list/trending/video-hot.component.ts create mode 100644 client/src/app/+videos/video-list/trending/video-most-liked.component.ts create mode 100644 client/src/app/+videos/video-list/trending/video-trending-header.component.html create mode 100644 client/src/app/+videos/video-list/trending/video-trending-header.component.scss create mode 100644 client/src/app/+videos/video-list/trending/video-trending-header.component.ts create mode 100644 client/src/app/+videos/video-list/trending/video-trending.component.ts delete mode 100644 client/src/app/+videos/video-list/video-most-liked.component.ts delete mode 100644 client/src/app/+videos/video-list/video-trending.component.ts create mode 100644 client/src/app/shared/shared-video-miniature/video-list-header.component.ts create mode 100644 client/src/assets/images/misc/flame.svg (limited to 'client') diff --git a/client/src/app/+accounts/account-search/account-search.component.ts b/client/src/app/+accounts/account-search/account-search.component.ts index 10c7a12d8..378aa78c4 100644 --- a/client/src/app/+accounts/account-search/account-search.component.ts +++ b/client/src/app/+accounts/account-search/account-search.component.ts @@ -1,6 +1,6 @@ import { Subscription } from 'rxjs' import { first, tap } from 'rxjs/operators' -import { Component, OnDestroy, OnInit } from '@angular/core' +import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' import { immutableAssign } from '@app/helpers' @@ -11,9 +11,7 @@ import { VideoFilter } from '@shared/models' @Component({ selector: 'my-account-search', templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html', - styleUrls: [ - '../../shared/shared-video-miniature/abstract-video-list.scss' - ] + styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ] }) export class AccountSearchComponent extends AbstractVideoList implements OnInit, OnDestroy { titlePage: string @@ -35,6 +33,7 @@ export class AccountSearchComponent extends AbstractVideoList implements OnInit, protected confirmService: ConfirmService, protected screenService: ScreenService, protected storageService: LocalStorageService, + protected cfr: ComponentFactoryResolver, private accountService: AccountService, private videoService: VideoService ) { @@ -99,6 +98,7 @@ export class AccountSearchComponent extends AbstractVideoList implements OnInit, } generateSyndicationList () { - /* disable syndication */ + /* method disabled */ + throw new Error('Method not implemented.') } } diff --git a/client/src/app/+accounts/account-videos/account-videos.component.ts b/client/src/app/+accounts/account-videos/account-videos.component.ts index 58d0719fd..da3903d2c 100644 --- a/client/src/app/+accounts/account-videos/account-videos.component.ts +++ b/client/src/app/+accounts/account-videos/account-videos.component.ts @@ -1,6 +1,6 @@ import { Subscription } from 'rxjs' import { first, tap } from 'rxjs/operators' -import { Component, OnDestroy, OnInit } from '@angular/core' +import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' import { immutableAssign } from '@app/helpers' @@ -35,7 +35,8 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit, protected screenService: ScreenService, protected storageService: LocalStorageService, private accountService: AccountService, - private videoService: VideoService + private videoService: VideoService, + protected cfr: ComponentFactoryResolver ) { super() } diff --git a/client/src/app/+my-library/my-history/my-history.component.ts b/client/src/app/+my-library/my-history/my-history.component.ts index 0c8e4b83f..1695bd7ad 100644 --- a/client/src/app/+my-library/my-history/my-history.component.ts +++ b/client/src/app/+my-library/my-history/my-history.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' +import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, @@ -42,7 +42,8 @@ export class MyHistoryComponent extends AbstractVideoList implements OnInit, OnD protected screenService: ScreenService, protected storageService: LocalStorageService, private confirmService: ConfirmService, - private userHistoryService: UserHistoryService + private userHistoryService: UserHistoryService, + protected cfr: ComponentFactoryResolver ) { super() @@ -95,6 +96,7 @@ export class MyHistoryComponent extends AbstractVideoList implements OnInit, OnD } generateSyndicationList () { + /* method disabled */ throw new Error('Method not implemented.') } diff --git a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts index 645696f48..a49fd0d5d 100644 --- a/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts +++ b/client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts @@ -1,6 +1,6 @@ import { Subscription } from 'rxjs' import { first, tap } from 'rxjs/operators' -import { Component, OnDestroy, OnInit } from '@angular/core' +import { Component, ComponentFactoryResolver, OnDestroy, OnInit } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, ConfirmService, LocalStorageService, Notifier, ScreenService, ServerService, UserService } from '@app/core' import { immutableAssign } from '@app/helpers' @@ -34,6 +34,7 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On protected confirmService: ConfirmService, protected screenService: ScreenService, protected storageService: LocalStorageService, + protected cfr: ComponentFactoryResolver, private videoChannelService: VideoChannelService, private videoService: VideoService ) { diff --git a/client/src/app/+videos/video-list/index.ts b/client/src/app/+videos/video-list/index.ts index af1bd58b7..dc27e29e2 100644 --- a/client/src/app/+videos/video-list/index.ts +++ b/client/src/app/+videos/video-list/index.ts @@ -1,5 +1,4 @@ export * from './overview' +export * from './trending' export * from './video-local.component' export * from './video-recently-added.component' -export * from './video-trending.component' -export * from './video-most-liked.component' diff --git a/client/src/app/+videos/video-list/trending/index.ts b/client/src/app/+videos/video-list/trending/index.ts new file mode 100644 index 000000000..8bae205a5 --- /dev/null +++ b/client/src/app/+videos/video-list/trending/index.ts @@ -0,0 +1,4 @@ +export * from './video-trending-header.component' +export * from './video-trending.component' +export * from './video-hot.component' +export * from './video-most-liked.component' diff --git a/client/src/app/+videos/video-list/trending/video-hot.component.ts b/client/src/app/+videos/video-list/trending/video-hot.component.ts new file mode 100644 index 000000000..1617eb21e --- /dev/null +++ b/client/src/app/+videos/video-list/trending/video-hot.component.ts @@ -0,0 +1,85 @@ +import { Component, ComponentFactoryResolver, Injector, 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' +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 VideoHotComponent extends AbstractVideoList implements OnInit, OnDestroy { + HeaderComponent = VideoTrendingHeaderComponent + titlePage: string + defaultSort: VideoSortField = '-hot' + + useUserVideoPreferences = 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.headerComponentInjector = this.getInjector() + } + + 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.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 + } + }] + }) + } +} diff --git a/client/src/app/+videos/video-list/trending/video-most-liked.component.ts b/client/src/app/+videos/video-list/trending/video-most-liked.component.ts new file mode 100644 index 000000000..1781cc6aa --- /dev/null +++ b/client/src/app/+videos/video-list/trending/video-most-liked.component.ts @@ -0,0 +1,81 @@ +import { Component, ComponentFactoryResolver, Injector, 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' +import { VideoTrendingHeaderComponent } from './video-trending-header.component' + +@Component({ + selector: 'my-videos-most-liked', + styleUrls: [ '../../../shared/shared-video-miniature/abstract-video-list.scss' ], + templateUrl: '../../../shared/shared-video-miniature/abstract-video-list.html' +}) +export class VideoMostLikedComponent extends AbstractVideoList implements OnInit { + HeaderComponent = VideoTrendingHeaderComponent + titlePage: string + defaultSort: VideoSortField = '-likes' + + useUserVideoPreferences = 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.headerComponentInjector = this.getInjector() + } + + ngOnInit () { + super.ngOnInit() + + this.generateSyndicationList() + } + + 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.most-liked-videos.videos.list.params', + 'filter:api.most-liked-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 + } + }] + }) + } +} 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 new file mode 100644 index 000000000..6319ee6d3 --- /dev/null +++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.html @@ -0,0 +1,6 @@ +
+ +
\ No newline at end of file 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 new file mode 100644 index 000000000..923a1d67a --- /dev/null +++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.scss @@ -0,0 +1,17 @@ +.btn-group label { + border: 1px solid transparent; + border-radius: 9999px !important; + padding: 5px 16px; + opacity: .8; + + &:not(:first-child) { + margin-left: .5rem; + } + + my-global-icon { + position: relative; + top: -2px; + height: 1rem; + margin-right: .1rem; + } +} \ No newline at end of file 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 new file mode 100644 index 000000000..125f14e33 --- /dev/null +++ b/client/src/app/+videos/video-list/trending/video-trending-header.component.ts @@ -0,0 +1,59 @@ +import { Component, Inject } from '@angular/core' +import { Router } from '@angular/router' +import { VideoListHeaderComponent } from '@app/shared/shared-video-miniature' +import { GlobalIconName } from '@app/shared/shared-icons' +import { VideoSortField } from '@shared/models' + +interface VideoTrendingHeaderItem { + label: string + iconName: GlobalIconName + value: VideoSortField + path: string + tooltip?: string +} + +@Component({ + selector: 'video-trending-title-page', + host: { 'class': 'title-page title-page-single' }, + styleUrls: [ './video-trending-header.component.scss' ], + templateUrl: './video-trending-header.component.html' +}) +export class VideoTrendingHeaderComponent extends VideoListHeaderComponent { + buttons: VideoTrendingHeaderItem[] + + constructor ( + @Inject('data') public data: any, + private router: Router + ) { + super(data) + + this.buttons = [ + { + label: $localize`:A variant of Trending videos based on the number of recent interactions:Hot`, + iconName: 'flame', + value: '-hot', + path: 'hot', + tooltip: $localize`Videos totalizing the most interactions for recent videos`, + }, + { + label: $localize`:Main variant of Trending videos based on number of recent views:Views`, + iconName: 'trending', + value: '-trending', + path: 'trending', + tooltip: $localize`Videos totalizing 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: '-likes', + path: 'most-liked', + tooltip: $localize`Videos that have the most likes` + } + ] + } + + setSort () { + const path = this.buttons.find(b => b.value === this.data.model).path + this.router.navigate([ `/videos/${path}` ]) + } +} 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 new file mode 100644 index 000000000..e77231586 --- /dev/null +++ b/client/src/app/+videos/video-list/trending/video-trending.component.ts @@ -0,0 +1,99 @@ +import { Component, ComponentFactoryResolver, Injector, 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' +import { VideoTrendingHeaderComponent } from './video-trending-header.component' + +@Component({ + selector: 'my-videos-trending', + 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' + + useUserVideoPreferences = 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.headerComponentInjector = this.getInjector() + } + + ngOnInit () { + super.ngOnInit() + + this.generateSyndicationList() + + this.serverService.getConfig().subscribe( + config => { + const trendingDays = config.trending.videos.intervalDays + + if (trendingDays === 1) { + this.titleTooltip = $localize`Trending videos are those totalizing the greatest number of views during the last 24 hours` + } else { + this.titleTooltip = $localize`Trending videos are those totalizing the greatest number of views during the last ${trendingDays} days` + } + + this.headerComponentInjector = this.getInjector() + this.setHeader() + }) + } + + 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.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 + } + }] + }) + } +} diff --git a/client/src/app/+videos/video-list/video-local.component.ts b/client/src/app/+videos/video-list/video-local.component.ts index 20dd61db9..af7eecff4 100644 --- a/client/src/app/+videos/video-list/video-local.component.ts +++ b/client/src/app/+videos/video-list/video-local.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' +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' @@ -28,6 +28,7 @@ export class VideoLocalComponent extends AbstractVideoList implements OnInit, On protected userService: UserService, protected screenService: ScreenService, protected storageService: LocalStorageService, + protected cfr: ComponentFactoryResolver, private videoService: VideoService, private hooks: HooksService ) { diff --git a/client/src/app/+videos/video-list/video-most-liked.component.ts b/client/src/app/+videos/video-list/video-most-liked.component.ts deleted file mode 100644 index 93408d76b..000000000 --- a/client/src/app/+videos/video-list/video-most-liked.component.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Component, 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-most-liked', - styleUrls: [ '../../shared/shared-video-miniature/abstract-video-list.scss' ], - templateUrl: '../../shared/shared-video-miniature/abstract-video-list.html' -}) -export class VideoMostLikedComponent extends AbstractVideoList implements OnInit { - titlePage: string - defaultSort: VideoSortField = '-likes' - - useUserVideoPreferences = 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, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - - this.titlePage = $localize`Most liked videos` - this.titleTooltip = $localize`Videos that have the most likes.` - } - - 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.most-liked-videos.videos.list.params', - 'filter:api.most-liked-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-recently-added.component.ts b/client/src/app/+videos/video-list/video-recently-added.component.ts index 34db6aabd..2f4908074 100644 --- a/client/src/app/+videos/video-list/video-recently-added.component.ts +++ b/client/src/app/+videos/video-list/video-recently-added.component.ts @@ -1,4 +1,4 @@ -import { Component, OnDestroy, OnInit } from '@angular/core' +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' @@ -28,6 +28,7 @@ export class VideoRecentlyAddedComponent extends AbstractVideoList implements On protected userService: UserService, protected screenService: ScreenService, protected storageService: LocalStorageService, + protected cfr: ComponentFactoryResolver, private videoService: VideoService, private hooks: HooksService ) { diff --git a/client/src/app/+videos/video-list/video-trending.component.ts b/client/src/app/+videos/video-list/video-trending.component.ts deleted file mode 100644 index a188795d1..000000000 --- a/client/src/app/+videos/video-list/video-trending.component.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { Component, 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-trending', - 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 { - titlePage: string - defaultSort: VideoSortField = '-trending' - - useUserVideoPreferences = 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, - private videoService: VideoService, - private hooks: HooksService - ) { - super() - } - - ngOnInit () { - super.ngOnInit() - - this.generateSyndicationList() - - this.serverService.getConfig().subscribe( - config => { - const trendingDays = config.trending.videos.intervalDays - - if (trendingDays === 1) { - this.titlePage = $localize`Trending for the last 24 hours` - this.titleTooltip = $localize`Trending videos are those totalizing the greatest number of views during the last 24 hours` - return - } - - this.titlePage = $localize`Trending for the last ${trendingDays} days` - this.titleTooltip = $localize`Trending videos are those totalizing the greatest number of views during the last ${trendingDays} days` - }) - } - - 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.trending-videos.videos.list.params', - 'filter:api.trending-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.ts b/client/src/app/+videos/video-list/video-user-subscriptions.component.ts index 62d862ec9..e352a2b2c 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,6 +1,6 @@ import { switchMap } from 'rxjs/operators' -import { Component, OnDestroy, OnInit } from '@angular/core' +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 { HooksService } from '@app/core/plugins/hooks.service' @@ -33,6 +33,7 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement protected screenService: ScreenService, protected storageService: LocalStorageService, private userSubscription: UserSubscriptionService, + protected cfr: ComponentFactoryResolver, private hooks: HooksService, private videoService: VideoService, private scopedTokensService: ScopedTokensService @@ -102,7 +103,8 @@ export class VideoUserSubscriptionsComponent extends AbstractVideoList implement } generateSyndicationList () { - // not implemented yet + /* method disabled: the view provides its own */ + throw new Error('Method not implemented.') } activateCopiedMessage () { diff --git a/client/src/app/+videos/videos-routing.module.ts b/client/src/app/+videos/videos-routing.module.ts index f658182e0..b6850b436 100644 --- a/client/src/app/+videos/videos-routing.module.ts +++ b/client/src/app/+videos/videos-routing.module.ts @@ -3,10 +3,11 @@ import { RouterModule, Routes } from '@angular/router' import { LoginGuard } from '@app/core' import { MetaGuard } from '@ngx-meta/core' import { VideoOverviewComponent } from './video-list/overview/video-overview.component' +import { VideoHotComponent } from './video-list/trending/video-hot.component' +import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' +import { VideoTrendingComponent } from './video-list/trending/video-trending.component' import { VideoLocalComponent } from './video-list/video-local.component' -import { VideoMostLikedComponent } from './video-list/video-most-liked.component' import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' -import { VideoTrendingComponent } from './video-list/video-trending.component' import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' import { VideosComponent } from './videos.component' @@ -38,6 +39,19 @@ const videosRoutes: Routes = [ } } }, + { + path: 'hot', + component: VideoHotComponent, + data: { + meta: { + title: $localize`Hot videos` + }, + reuse: { + enabled: true, + key: 'hot-videos-list' + } + } + }, { path: 'most-liked', component: VideoMostLikedComponent, diff --git a/client/src/app/+videos/videos.module.ts b/client/src/app/+videos/videos.module.ts index 1cf68bf83..4c88a0397 100644 --- a/client/src/app/+videos/videos.module.ts +++ b/client/src/app/+videos/videos.module.ts @@ -1,3 +1,4 @@ +import { CommonModule } from '@angular/common' import { NgModule } from '@angular/core' import { SharedFormModule } from '@app/shared/shared-forms' import { SharedGlobalIconModule } from '@app/shared/shared-icons' @@ -6,10 +7,12 @@ import { SharedUserSubscriptionModule } from '@app/shared/shared-user-subscripti import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature' import { OverviewService } from './video-list' import { VideoOverviewComponent } from './video-list/overview/video-overview.component' +import { VideoTrendingHeaderComponent } from './video-list/trending/video-trending-header.component' +import { VideoHotComponent } from './video-list/trending/video-hot.component' +import { VideoTrendingComponent } from './video-list/trending/video-trending.component' +import { VideoMostLikedComponent } from './video-list/trending/video-most-liked.component' import { VideoLocalComponent } from './video-list/video-local.component' -import { VideoMostLikedComponent } from './video-list/video-most-liked.component' import { VideoRecentlyAddedComponent } from './video-list/video-recently-added.component' -import { VideoTrendingComponent } from './video-list/video-trending.component' import { VideoUserSubscriptionsComponent } from './video-list/video-user-subscriptions.component' import { VideosRoutingModule } from './videos-routing.module' import { VideosComponent } from './videos.component' @@ -28,7 +31,9 @@ import { VideosComponent } from './videos.component' declarations: [ VideosComponent, + VideoTrendingHeaderComponent, VideoTrendingComponent, + VideoHotComponent, VideoMostLikedComponent, VideoRecentlyAddedComponent, VideoLocalComponent, diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index f9e8ec2f4..9aa397edd 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html @@ -132,11 +132,6 @@ Trending - - - Most liked - - Recently added diff --git a/client/src/app/shared/shared-icons/global-icon.component.ts b/client/src/app/shared/shared-icons/global-icon.component.ts index 0924b8119..def488df0 100644 --- a/client/src/app/shared/shared-icons/global-icon.component.ts +++ b/client/src/app/shared/shared-icons/global-icon.component.ts @@ -16,6 +16,7 @@ const icons = { 'playlist-add': require('!!raw-loader?!../../../assets/images/misc/playlist-add.svg').default, // material ui 'follower': require('!!raw-loader?!../../../assets/images/misc/account-arrow-left.svg').default, // material ui 'following': require('!!raw-loader?!../../../assets/images/misc/account-arrow-right.svg').default, // material ui + 'flame': require('!!raw-loader?!../../../assets/images/misc/flame.svg').default, // feather icons 'flag': require('!!raw-loader?!../../../assets/images/feather/flag.svg').default, diff --git a/client/src/app/shared/shared-main/shared-main.module.ts b/client/src/app/shared/shared-main/shared-main.module.ts index c69a4c8b2..9d550996d 100644 --- a/client/src/app/shared/shared-main/shared-main.module.ts +++ b/client/src/app/shared/shared-main/shared-main.module.ts @@ -11,7 +11,8 @@ import { NgbModalModule, NgbNavModule, NgbPopoverModule, - NgbTooltipModule + NgbTooltipModule, + NgbButtonsModule } from '@ng-bootstrap/ng-bootstrap' import { LoadingBarModule } from '@ngx-loading-bar/core' import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client' @@ -53,6 +54,7 @@ import { VideoChannelService } from './video-channel' NgbNavModule, NgbTooltipModule, NgbCollapseModule, + NgbButtonsModule, ClipboardModule, @@ -110,6 +112,7 @@ import { VideoChannelService } from './video-channel' NgbNavModule, NgbTooltipModule, NgbCollapseModule, + NgbButtonsModule, ClipboardModule, diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.html b/client/src/app/shared/shared-video-miniature/abstract-video-list.html index 368a7d70e..07f79cd6d 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.html +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.html @@ -1,13 +1,9 @@
-

-
- {{ titlePage }} -
-

+
- + diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss index 1c27c58c3..2eaf0dc70 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.scss +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.scss @@ -5,16 +5,16 @@ $iconSize: 16px; +::ng-deep .title-page.title-page-single { + display: flex; + flex-grow: 1; +} + .videos-header { display: flex; justify-content: space-between; align-items: center; - .title-page.title-page-single { - display: flex; - flex-grow: 1; - } - .action-block { ::ng-deep my-feed { my-global-icon { diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts index a5f22585d..3e84589cd 100644 --- a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts @@ -1,6 +1,16 @@ import { fromEvent, Observable, Subject, Subscription } from 'rxjs' import { debounceTime, switchMap, tap } from 'rxjs/operators' -import { Directive, OnDestroy, OnInit } from '@angular/core' +import { + AfterContentInit, + ComponentFactoryResolver, + Directive, + Injector, + OnDestroy, + OnInit, + Type, + ViewChild, + ViewContainerRef +} from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { AuthService, @@ -19,6 +29,7 @@ import { ServerConfig, UserRight, VideoFilter, VideoSortField } from '@shared/mo import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' import { Syndication, Video } from '../shared-main' import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' +import { GenericHeaderComponent, VideoListHeaderComponent } from './video-list-header.component' enum GroupDate { UNKNOWN = 0, @@ -32,7 +43,12 @@ enum GroupDate { @Directive() // tslint:disable-next-line: directive-class-suffix -export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook { +export abstract class AbstractVideoList implements OnInit, OnDestroy, AfterContentInit, DisableForReuseHook { + @ViewChild('videoListHeader', { static: true, read: ViewContainerRef }) videoListHeader: ViewContainerRef + + HeaderComponent: Type = VideoListHeaderComponent + headerComponentInjector: Injector + pagination: ComponentPaginationLight = { currentPage: 1, itemsPerPage: 25 @@ -92,6 +108,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor protected abstract screenService: ScreenService protected abstract storageService: LocalStorageService protected abstract router: Router + protected abstract cfr: ComponentFactoryResolver abstract titlePage: string private resizeSubscription: Subscription @@ -153,6 +170,13 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor if (this.resizeSubscription) this.resizeSubscription.unsubscribe() } + ngAfterContentInit () { + if (this.videoListHeader) { + // some components don't use the header: they use their own template, like my-history.component.html + this.setHeader.apply(this, [ this.HeaderComponent, this.headerComponentInjector ]) + } + } + disableForReuse () { this.disabled = true } @@ -268,7 +292,27 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableFor } toggleModerationDisplay () { - throw new Error('toggleModerationDisplay is not implemented') + throw new Error('toggleModerationDisplay ' + $localize`function is not implemented`) + } + + setHeader ( + t: Type = this.HeaderComponent, + i: Injector = this.headerComponentInjector + ) { + const injector = i || Injector.create({ + providers: [{ + provide: 'data', + useValue: { + titlePage: this.titlePage, + titleTooltip: this.titleTooltip + } + }] + }) + const viewContainerRef = this.videoListHeader + viewContainerRef.clear() + + const componentFactory = this.cfr.resolveComponentFactory(t) + viewContainerRef.createComponent(componentFactory, 0, injector) } // On videos hook for children that want to do something diff --git a/client/src/app/shared/shared-video-miniature/index.ts b/client/src/app/shared/shared-video-miniature/index.ts index 47ca6f51b..a8fd82bb9 100644 --- a/client/src/app/shared/shared-video-miniature/index.ts +++ b/client/src/app/shared/shared-video-miniature/index.ts @@ -3,5 +3,5 @@ export * from './video-actions-dropdown.component' export * from './video-download.component' export * from './video-miniature.component' export * from './videos-selection.component' - +export * from './video-list-header.component' export * from './shared-video-miniature.module' diff --git a/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts index 3035bcfb3..7a7868853 100644 --- a/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts +++ b/client/src/app/shared/shared-video-miniature/shared-video-miniature.module.ts @@ -12,6 +12,7 @@ import { VideoActionsDropdownComponent } from './video-actions-dropdown.componen import { VideoDownloadComponent } from './video-download.component' import { VideoMiniatureComponent } from './video-miniature.component' import { VideosSelectionComponent } from './videos-selection.component' +import { VideoListHeaderComponent } from './video-list-header.component' @NgModule({ imports: [ @@ -29,7 +30,8 @@ import { VideosSelectionComponent } from './videos-selection.component' VideoActionsDropdownComponent, VideoDownloadComponent, VideoMiniatureComponent, - VideosSelectionComponent + VideosSelectionComponent, + VideoListHeaderComponent ], exports: [ diff --git a/client/src/app/shared/shared-video-miniature/video-list-header.component.ts b/client/src/app/shared/shared-video-miniature/video-list-header.component.ts new file mode 100644 index 000000000..a07248b96 --- /dev/null +++ b/client/src/app/shared/shared-video-miniature/video-list-header.component.ts @@ -0,0 +1,20 @@ +import { Component, Inject } from '@angular/core' + +export abstract class GenericHeaderComponent { + constructor (@Inject('data') public data: any) {} +} + +@Component({ + selector: 'h1', + host: { 'class': 'title-page title-page-single' }, + template: ` +
+ {{ data.titlePage }} +
+ ` +}) +export class VideoListHeaderComponent extends GenericHeaderComponent { + constructor (@Inject('data') public data: any) { + super(data) + } +} diff --git a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts index 2b060b130..ef59975d4 100644 --- a/client/src/app/shared/shared-video-miniature/videos-selection.component.ts +++ b/client/src/app/shared/shared-video-miniature/videos-selection.component.ts @@ -2,6 +2,7 @@ import { Observable } from 'rxjs' import { AfterContentInit, Component, + ComponentFactoryResolver, ContentChildren, EventEmitter, Input, @@ -51,7 +52,8 @@ export class VideosSelectionComponent extends AbstractVideoList implements OnIni protected userService: UserService, protected screenService: ScreenService, protected storageService: LocalStorageService, - protected serverService: ServerService + protected serverService: ServerService, + protected cfr: ComponentFactoryResolver ) { super() } diff --git a/client/src/assets/images/misc/flame.svg b/client/src/assets/images/misc/flame.svg new file mode 100644 index 000000000..be478bdd4 --- /dev/null +++ b/client/src/assets/images/misc/flame.svg @@ -0,0 +1,4 @@ + + + + -- cgit v1.2.3