aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/src/app/+accounts/account-videos/account-videos.component.ts9
-rw-r--r--client/src/app/+accounts/accounts-routing.module.ts4
-rw-r--r--client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html75
-rw-r--r--client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts13
-rw-r--r--client/src/app/+my-account/my-account-history/my-account-history.component.html16
-rw-r--r--client/src/app/+my-account/my-account-history/my-account-history.component.ts9
-rw-r--r--client/src/app/+my-account/my-account-routing.module.ts8
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.html75
-rw-r--r--client/src/app/+my-account/my-account-videos/my-account-videos.component.ts47
-rw-r--r--client/src/app/+video-channels/video-channel-videos/video-channel-videos.component.ts9
-rw-r--r--client/src/app/+video-channels/video-channels-routing.module.ts4
-rw-r--r--client/src/app/app-routing.module.ts9
-rw-r--r--client/src/app/app.component.ts131
-rw-r--r--client/src/app/core/routing/custom-reuse-strategy.ts81
-rw-r--r--client/src/app/core/routing/disable-for-reuse-hook.ts7
-rw-r--r--client/src/app/shared/video/abstract-video-list.html11
-rw-r--r--client/src/app/shared/video/abstract-video-list.ts244
-rw-r--r--client/src/app/shared/video/infinite-scroller.directive.ts47
-rw-r--r--client/src/app/videos/video-list/video-local.component.ts8
-rw-r--r--client/src/app/videos/video-list/video-recently-added.component.ts10
-rw-r--r--client/src/app/videos/video-list/video-trending.component.ts7
-rw-r--r--client/src/app/videos/video-list/video-user-subscriptions.component.ts8
-rw-r--r--client/src/app/videos/videos-routing.module.ts16
23 files changed, 393 insertions, 455 deletions
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 13b634a01..7535eef08 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,5 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { Location } from '@angular/common'
4import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
5import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
6import { ConfirmService } from '../../core/confirm' 5import { ConfirmService } from '../../core/confirm'
@@ -12,7 +11,7 @@ import { tap } from 'rxjs/operators'
12import { I18n } from '@ngx-translate/i18n-polyfill' 11import { I18n } from '@ngx-translate/i18n-polyfill'
13import { Subscription } from 'rxjs' 12import { Subscription } from 'rxjs'
14import { ScreenService } from '@app/shared/misc/screen.service' 13import { ScreenService } from '@app/shared/misc/screen.service'
15import { Notifier } from '@app/core' 14import { Notifier, ServerService } from '@app/core'
16 15
17@Component({ 16@Component({
18 selector: 'my-account-videos', 17 selector: 'my-account-videos',
@@ -25,7 +24,6 @@ import { Notifier } from '@app/core'
25export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { 24export class AccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
26 titlePage: string 25 titlePage: string
27 marginContent = false // Disable margin 26 marginContent = false // Disable margin
28 currentRoute = '/accounts/videos'
29 loadOnInit = false 27 loadOnInit = false
30 28
31 private account: Account 29 private account: Account
@@ -33,13 +31,13 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
33 31
34 constructor ( 32 constructor (
35 protected router: Router, 33 protected router: Router,
34 protected serverService: ServerService,
36 protected route: ActivatedRoute, 35 protected route: ActivatedRoute,
37 protected authService: AuthService, 36 protected authService: AuthService,
38 protected notifier: Notifier, 37 protected notifier: Notifier,
39 protected confirmService: ConfirmService, 38 protected confirmService: ConfirmService,
40 protected location: Location,
41 protected screenService: ScreenService, 39 protected screenService: ScreenService,
42 protected i18n: I18n, 40 private i18n: I18n,
43 private accountService: AccountService, 41 private accountService: AccountService,
44 private videoService: VideoService 42 private videoService: VideoService
45 ) { 43 ) {
@@ -55,7 +53,6 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit,
55 this.accountSub = this.accountService.accountLoaded 53 this.accountSub = this.accountService.accountLoaded
56 .subscribe(account => { 54 .subscribe(account => {
57 this.account = account 55 this.account = account
58 this.currentRoute = '/accounts/' + this.account.nameWithHost + '/videos'
59 56
60 this.reloadVideos() 57 this.reloadVideos()
61 this.generateSyndicationList() 58 this.generateSyndicationList()
diff --git a/client/src/app/+accounts/accounts-routing.module.ts b/client/src/app/+accounts/accounts-routing.module.ts
index ffe606b43..531d763c4 100644
--- a/client/src/app/+accounts/accounts-routing.module.ts
+++ b/client/src/app/+accounts/accounts-routing.module.ts
@@ -23,6 +23,10 @@ const accountsRoutes: Routes = [
23 data: { 23 data: {
24 meta: { 24 meta: {
25 title: 'Account videos' 25 title: 'Account videos'
26 },
27 reuse: {
28 enabled: true,
29 key: 'account-videos-list'
26 } 30 }
27 } 31 }
28 }, 32 },
diff --git a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html
index fe579ffd7..961ac51d3 100644
--- a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html
+++ b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.html
@@ -1,49 +1,42 @@
1<div i18n *ngIf="pagination.totalItems === 0">No results.</div> 1<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
2<div
3 myInfiniteScroller
4 [pageHeight]="pageHeight"
5 (nearOfTop)="onNearOfTop()"
6 (nearOfBottom)="onNearOfBottom()"
7 (pageChanged)="onPageChanged($event)"
8 class="videos" #videosElement
9>
10 <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
11 <div class="video" *ngFor="let video of videos; let j = index">
12 <div class="checkbox-container">
13 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
14 </div>
15 <my-video-thumbnail [video]="video"></my-video-thumbnail>
16 2
17 <div class="video-info"> 3<div myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" class="videos">
18 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> 4 <div class="video" *ngFor="let video of videos; let i = index">
19 <div>{{ video.account.displayName }}</div> 5 <div class="checkbox-container">
20 <div>{{ video.publishedAt | myFromNow }}</div> 6 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
21 <div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div> 7 </div>
22 <div><span i18n>Sensitve: </span><span> {{ video.nsfw }}</span></div>
23 </div>
24 8
25 <!-- Display only once --> 9 <my-video-thumbnail [video]="video"></my-video-thumbnail>
26 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
27 <div class="action-selection-mode-child">
28 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
29 Cancel
30 </span>
31 10
32 <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()"> 11 <div class="video-info">
33 <my-global-icon iconName="tick"></my-global-icon> 12 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
34 <ng-container i18n>Unblacklist</ng-container> 13 <div>{{ video.account.displayName }}</div>
35 </span> 14 <div>{{ video.publishedAt | myFromNow }}</div>
36 </div> 15 <div><span i18n>Privacy: </span><span>{{ video.privacy.label }}</span></div>
37 </div> 16 <div><span i18n>Sensitive: </span><span> {{ video.nsfw }}</span></div>
17 </div>
18
19 <!-- Display only once -->
20 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0">
21 <div class="action-selection-mode-child">
22 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
23 Cancel
24 </span>
38 25
39 <div class="video-buttons" *ngIf="isInSelectionMode() === false"> 26 <span class="action-button action-button-unblacklist-selection" (click)="removeSelectedVideosFromBlacklist()">
40 <my-button 27 <my-global-icon iconName="tick"></my-global-icon>
41 i18n-label 28 <ng-container i18n>Unblacklist</ng-container>
42 label="Unblacklist" 29 </span>
43 icon="tick"
44 (click)="removeVideoFromBlacklist(video)"
45 ></my-button>
46 </div> 30 </div>
47 </div> 31 </div>
48 32
49</div> \ No newline at end of file 33 <div class="video-buttons" *ngIf="isInSelectionMode() === false">
34 <my-button
35 i18n-label
36 label="Unblacklist"
37 icon="tick"
38 (click)="removeVideoFromBlacklist(video)"
39 ></my-button>
40 </div>
41 </div>
42</div>
diff --git a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts
index b79f574c9..af68d7e2e 100644
--- a/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts
+++ b/client/src/app/+admin/moderation/video-auto-blacklist-list/video-auto-blacklist-list.component.ts
@@ -4,7 +4,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
4import { Router, ActivatedRoute } from '@angular/router' 4import { Router, ActivatedRoute } from '@angular/router'
5import { AbstractVideoList } from '@app/shared/video/abstract-video-list' 5import { AbstractVideoList } from '@app/shared/video/abstract-video-list'
6import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 6import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
7import { Notifier, AuthService } from '@app/core' 7import { Notifier, AuthService, ServerService } from '@app/core'
8import { Video } from '@shared/models' 8import { Video } from '@shared/models'
9import { VideoBlacklistService } from '@app/shared' 9import { VideoBlacklistService } from '@app/shared'
10import { immutableAssign } from '@app/shared/misc/utils' 10import { immutableAssign } from '@app/shared/misc/utils'
@@ -17,7 +17,6 @@ import { ScreenService } from '@app/shared/misc/screen.service'
17}) 17})
18export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy { 18export class VideoAutoBlacklistListComponent extends AbstractVideoList implements OnInit, OnDestroy {
19 titlePage: string 19 titlePage: string
20 currentRoute = '/admin/moderation/video-auto-blacklist/list'
21 checkedVideos: { [ id: number ]: boolean } = {} 20 checkedVideos: { [ id: number ]: boolean } = {}
22 pagination: ComponentPagination = { 21 pagination: ComponentPagination = {
23 currentPage: 1, 22 currentPage: 1,
@@ -25,18 +24,15 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
25 totalItems: null 24 totalItems: null
26 } 25 }
27 26
28 protected baseVideoWidth = -1
29 protected baseVideoHeight = 155
30
31 constructor ( 27 constructor (
32 protected router: Router, 28 protected router: Router,
33 protected route: ActivatedRoute, 29 protected route: ActivatedRoute,
34 protected i18n: I18n,
35 protected notifier: Notifier, 30 protected notifier: Notifier,
36 protected location: Location,
37 protected authService: AuthService, 31 protected authService: AuthService,
38 protected screenService: ScreenService, 32 protected screenService: ScreenService,
39 private videoBlacklistService: VideoBlacklistService, 33 protected serverService: ServerService,
34 private i18n: I18n,
35 private videoBlacklistService: VideoBlacklistService
40 ) { 36 ) {
41 super() 37 super()
42 38
@@ -96,5 +92,4 @@ export class VideoAutoBlacklistListComponent extends AbstractVideoList implement
96 error => this.notifier.error(error.message) 92 error => this.notifier.error(error.message)
97 ) 93 )
98 } 94 }
99
100} 95}
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.html b/client/src/app/+my-account/my-account-history/my-account-history.component.html
index 2349f02f5..00ee5fbd1 100644
--- a/client/src/app/+my-account/my-account-history/my-account-history.component.html
+++ b/client/src/app/+my-account/my-account-history/my-account-history.component.html
@@ -13,16 +13,14 @@
13 13
14<div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div> 14<div class="no-history" i18n *ngIf="pagination.totalItems === 0">You don't have videos history yet.</div>
15 15
16<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" class="videos" #videosElement> 16<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
17 <div *ngFor="let videos of videoPages;" class="videos-page"> 17 <div class="video" *ngFor="let video of videos">
18 <div class="video" *ngFor="let video of videos"> 18 <my-video-thumbnail [video]="video"></my-video-thumbnail>
19 <my-video-thumbnail [video]="video"></my-video-thumbnail>
20 19
21 <div class="video-info"> 20 <div class="video-info">
22 <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> 21 <a tabindex="-1" class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
23 <span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span> 22 <span i18n class="video-info-date-views">{{ video.views | myNumberFormatter }} views</span>
24 <a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a> 23 <a tabindex="-1" class="video-info-account" [routerLink]="[ '/accounts', video.byAccount ]">{{ video.byAccount }}</a>
25 </div>
26 </div> 24 </div>
27 </div> 25 </div>
28</div> 26</div>
diff --git a/client/src/app/+my-account/my-account-history/my-account-history.component.ts b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
index 394091bad..73340d21a 100644
--- a/client/src/app/+my-account/my-account-history/my-account-history.component.ts
+++ b/client/src/app/+my-account/my-account-history/my-account-history.component.ts
@@ -1,6 +1,5 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { Location } from '@angular/common'
4import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
5import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 4import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
6import { AuthService } from '../../core/auth' 5import { AuthService } from '../../core/auth'
@@ -11,7 +10,7 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
11import { ScreenService } from '@app/shared/misc/screen.service' 10import { ScreenService } from '@app/shared/misc/screen.service'
12import { UserHistoryService } from '@app/shared/users/user-history.service' 11import { UserHistoryService } from '@app/shared/users/user-history.service'
13import { UserService } from '@app/shared' 12import { UserService } from '@app/shared'
14import { Notifier } from '@app/core' 13import { Notifier, ServerService } from '@app/core'
15 14
16@Component({ 15@Component({
17 selector: 'my-account-history', 16 selector: 'my-account-history',
@@ -20,7 +19,6 @@ import { Notifier } from '@app/core'
20}) 19})
21export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy { 20export class MyAccountHistoryComponent extends AbstractVideoList implements OnInit, OnDestroy {
22 titlePage: string 21 titlePage: string
23 currentRoute = '/my-account/history/videos'
24 pagination: ComponentPagination = { 22 pagination: ComponentPagination = {
25 currentPage: 1, 23 currentPage: 1,
26 itemsPerPage: 5, 24 itemsPerPage: 5,
@@ -28,16 +26,13 @@ export class MyAccountHistoryComponent extends AbstractVideoList implements OnIn
28 } 26 }
29 videosHistoryEnabled: boolean 27 videosHistoryEnabled: boolean
30 28
31 protected baseVideoWidth = -1
32 protected baseVideoHeight = 155
33
34 constructor ( 29 constructor (
35 protected router: Router, 30 protected router: Router,
31 protected serverService: ServerService,
36 protected route: ActivatedRoute, 32 protected route: ActivatedRoute,
37 protected authService: AuthService, 33 protected authService: AuthService,
38 protected userService: UserService, 34 protected userService: UserService,
39 protected notifier: Notifier, 35 protected notifier: Notifier,
40 protected location: Location,
41 protected screenService: ScreenService, 36 protected screenService: ScreenService,
42 protected i18n: I18n, 37 protected i18n: I18n,
43 private confirmService: ConfirmService, 38 private confirmService: ConfirmService,
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts
index 07557a029..018d6f996 100644
--- a/client/src/app/+my-account/my-account-routing.module.ts
+++ b/client/src/app/+my-account/my-account-routing.module.ts
@@ -118,6 +118,10 @@ const myAccountRoutes: Routes = [
118 data: { 118 data: {
119 meta: { 119 meta: {
120 title: 'Account videos' 120 title: 'Account videos'
121 },
122 reuse: {
123 enabled: true,
124 key: 'my-account-videos-list'
121 } 125 }
122 } 126 }
123 }, 127 },
@@ -172,6 +176,10 @@ const myAccountRoutes: Routes = [
172 data: { 176 data: {
173 meta: { 177 meta: {
174 title: 'Videos history' 178 title: 'Videos history'
179 },
180 reuse: {
181 enabled: true,
182 key: 'my-videos-history-list'
175 } 183 }
176 } 184 }
177 }, 185 },
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
index b09e845ac..1f3ac0005 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.html
@@ -1,54 +1,47 @@
1<div i18n *ngIf="pagination.totalItems === 0">No results.</div> 1<div i18n *ngIf="pagination.totalItems === 0">No results.</div>
2 2
3<div 3<div myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true" class="videos">
4 myInfiniteScroller 4 <div class="video" *ngFor="let video of videos; let i = index">
5 [pageHeight]="pageHeight" 5 <div class="checkbox-container">
6 (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)" 6 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
7 class="videos" #videosElement 7 </div>
8>
9 <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
10 <div class="video" *ngFor="let video of videos; let j = index">
11 <div class="checkbox-container">
12 <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
13 </div>
14 8
15 <my-video-thumbnail [video]="video"></my-video-thumbnail> 9 <my-video-thumbnail [video]="video"></my-video-thumbnail>
16 10
17 <div class="video-info"> 11 <div class="video-info">
18 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a> 12 <a class="video-info-name" [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name">{{ video.name }}</a>
19 <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span> 13 <span i18n class="video-info-date-views">{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views</span>
20 <div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div> 14 <div class="video-info-privacy">{{ video.privacy.label }}{{ getStateLabel(video) }}</div>
21 <div *ngIf="video.blacklisted" class="video-info-blacklisted"> 15 <div *ngIf="video.blacklisted" class="video-info-blacklisted">
22 <span class="blacklisted-label" i18n>Blacklisted</span> 16 <span class="blacklisted-label" i18n>Blacklisted</span>
23 <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span> 17 <span class="blacklisted-reason" *ngIf="video.blacklistedReason">{{ video.blacklistedReason }}</span>
24 </div>
25 </div> 18 </div>
19 </div>
26 20
27 <!-- Display only once --> 21 <!-- Display only once -->
28 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0 && j === 0"> 22 <div class="action-selection-mode" *ngIf="isInSelectionMode() === true && i === 0">
29 <div class="action-selection-mode-child"> 23 <div class="action-selection-mode-child">
30 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()"> 24 <span i18n class="action-button action-button-cancel-selection" (click)="abortSelectionMode()">
31 Cancel 25 Cancel
32 </span> 26 </span>
33 27
34 <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()"> 28 <span class="action-button action-button-delete-selection" (click)="deleteSelectedVideos()">
35 <my-global-icon iconName="delete"></my-global-icon> 29 <my-global-icon iconName="delete"></my-global-icon>
36 <ng-container i18n>Delete</ng-container> 30 <ng-container i18n>Delete</ng-container>
37 </span> 31 </span>
38 </div>
39 </div> 32 </div>
33 </div>
40 34
41 <div class="video-buttons" *ngIf="isInSelectionMode() === false"> 35 <div class="video-buttons" *ngIf="isInSelectionMode() === false">
42 <my-delete-button (click)="deleteVideo(video)"></my-delete-button> 36 <my-delete-button (click)="deleteVideo(video)"></my-delete-button>
43 37
44 <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button> 38 <my-edit-button [routerLink]="[ '/videos', 'update', video.uuid ]"></my-edit-button>
45 39
46 <my-button i18n-label label="Change ownership" 40 <my-button i18n-label label="Change ownership"
47 className="action-button-change-ownership" 41 className="action-button-change-ownership"
48 icon="im-with-her" 42 icon="im-with-her"
49 (click)="changeOwnership($event, video)" 43 (click)="changeOwnership($event, video)"
50 ></my-button> 44 ></my-button>
51 </div>
52 </div> 45 </div>
53 </div> 46 </div>
54</div> 47</div>
diff --git a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
index 41608f796..eb5096a5e 100644
--- a/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
+++ b/client/src/app/+my-account/my-account-videos/my-account-videos.component.ts
@@ -1,11 +1,10 @@
1import { from as observableFrom, Observable } from 'rxjs' 1import { concat, Observable } from 'rxjs'
2import { concatAll, tap } from 'rxjs/operators' 2import { tap, toArray } from 'rxjs/operators'
3import { Component, OnDestroy, OnInit, Inject, LOCALE_ID, ViewChild } from '@angular/core' 3import { Component, Inject, LOCALE_ID, OnDestroy, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router' 4import { ActivatedRoute, Router } from '@angular/router'
5import { Location } from '@angular/common'
6import { immutableAssign } from '@app/shared/misc/utils' 5import { immutableAssign } from '@app/shared/misc/utils'
7import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 6import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
8import { Notifier } from '@app/core' 7import { Notifier, ServerService } from '@app/core'
9import { AuthService } from '../../core/auth' 8import { AuthService } from '../../core/auth'
10import { ConfirmService } from '../../core/confirm' 9import { ConfirmService } from '../../core/confirm'
11import { AbstractVideoList } from '../../shared/video/abstract-video-list' 10import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -22,8 +21,9 @@ import { VideoChangeOwnershipComponent } from './video-change-ownership/video-ch
22 styleUrls: [ './my-account-videos.component.scss' ] 21 styleUrls: [ './my-account-videos.component.scss' ]
23}) 22})
24export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { 23export class MyAccountVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
24 @ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
25
25 titlePage: string 26 titlePage: string
26 currentRoute = '/my-account/videos'
27 checkedVideos: { [ id: number ]: boolean } = {} 27 checkedVideos: { [ id: number ]: boolean } = {}
28 pagination: ComponentPagination = { 28 pagination: ComponentPagination = {
29 currentPage: 1, 29 currentPage: 1,
@@ -31,19 +31,14 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
31 totalItems: null 31 totalItems: null
32 } 32 }
33 33
34 protected baseVideoWidth = -1
35 protected baseVideoHeight = 155
36
37 @ViewChild('videoChangeOwnershipModal') videoChangeOwnershipModal: VideoChangeOwnershipComponent
38
39 constructor ( 34 constructor (
40 protected router: Router, 35 protected router: Router,
36 protected serverService: ServerService,
41 protected route: ActivatedRoute, 37 protected route: ActivatedRoute,
42 protected authService: AuthService, 38 protected authService: AuthService,
43 protected notifier: Notifier, 39 protected notifier: Notifier,
44 protected location: Location,
45 protected screenService: ScreenService, 40 protected screenService: ScreenService,
46 protected i18n: I18n, 41 private i18n: I18n,
47 private confirmService: ConfirmService, 42 private confirmService: ConfirmService,
48 private videoService: VideoService, 43 private videoService: VideoService,
49 @Inject(LOCALE_ID) private localeId: string 44 @Inject(LOCALE_ID) private localeId: string
@@ -93,19 +88,18 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
93 const observables: Observable<any>[] = [] 88 const observables: Observable<any>[] = []
94 for (const videoId of toDeleteVideosIds) { 89 for (const videoId of toDeleteVideosIds) {
95 const o = this.videoService.removeVideo(videoId) 90 const o = this.videoService.removeVideo(videoId)
96 .pipe(tap(() => this.spliceVideosById(videoId))) 91 .pipe(tap(() => this.removeVideoFromArray(videoId)))
97 92
98 observables.push(o) 93 observables.push(o)
99 } 94 }
100 95
101 observableFrom(observables) 96 concat(...observables)
102 .pipe(concatAll()) 97 .pipe(toArray())
103 .subscribe( 98 .subscribe(
104 res => { 99 () => {
105 this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length })) 100 this.notifier.success(this.i18n('{{deleteLength}} videos deleted.', { deleteLength: toDeleteVideosIds.length }))
106 101
107 this.abortSelectionMode() 102 this.abortSelectionMode()
108 this.reloadVideos()
109 }, 103 },
110 104
111 err => this.notifier.error(err.message) 105 err => this.notifier.error(err.message)
@@ -156,20 +150,7 @@ export class MyAccountVideosComponent extends AbstractVideoList implements OnIni
156 return ' - ' + suffix 150 return ' - ' + suffix
157 } 151 }
158 152
159 protected buildVideoHeight () { 153 private removeVideoFromArray (id: number) {
160 // In account videos, the video height is fixed 154 this.videos = this.videos.filter(v => v.id !== id)
161 return this.baseVideoHeight
162 }
163
164 private spliceVideosById (id: number) {
165 for (const key of Object.keys(this.loadedPages)) {
166 const videos: Video[] = this.loadedPages[ key ]
167 const index = videos.findIndex(v => v.id === id)
168
169 if (index !== -1) {
170 videos.splice(index, 1)
171 return
172 }
173 }
174 } 155 }
175} 156}
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 dea378a6e..8af31000e 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,5 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { Location } from '@angular/common'
4import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
5import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
6import { ConfirmService } from '../../core/confirm' 5import { ConfirmService } from '../../core/confirm'
@@ -12,7 +11,7 @@ import { tap } from 'rxjs/operators'
12import { I18n } from '@ngx-translate/i18n-polyfill' 11import { I18n } from '@ngx-translate/i18n-polyfill'
13import { Subscription } from 'rxjs' 12import { Subscription } from 'rxjs'
14import { ScreenService } from '@app/shared/misc/screen.service' 13import { ScreenService } from '@app/shared/misc/screen.service'
15import { Notifier } from '@app/core' 14import { Notifier, ServerService } from '@app/core'
16 15
17@Component({ 16@Component({
18 selector: 'my-video-channel-videos', 17 selector: 'my-video-channel-videos',
@@ -25,7 +24,6 @@ import { Notifier } from '@app/core'
25export class VideoChannelVideosComponent extends AbstractVideoList implements OnInit, OnDestroy { 24export class VideoChannelVideosComponent extends AbstractVideoList implements OnInit, OnDestroy {
26 titlePage: string 25 titlePage: string
27 marginContent = false // Disable margin 26 marginContent = false // Disable margin
28 currentRoute = '/video-channels/videos'
29 loadOnInit = false 27 loadOnInit = false
30 28
31 private videoChannel: VideoChannel 29 private videoChannel: VideoChannel
@@ -33,13 +31,13 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
33 31
34 constructor ( 32 constructor (
35 protected router: Router, 33 protected router: Router,
34 protected serverService: ServerService,
36 protected route: ActivatedRoute, 35 protected route: ActivatedRoute,
37 protected authService: AuthService, 36 protected authService: AuthService,
38 protected notifier: Notifier, 37 protected notifier: Notifier,
39 protected confirmService: ConfirmService, 38 protected confirmService: ConfirmService,
40 protected location: Location,
41 protected screenService: ScreenService, 39 protected screenService: ScreenService,
42 protected i18n: I18n, 40 private i18n: I18n,
43 private videoChannelService: VideoChannelService, 41 private videoChannelService: VideoChannelService,
44 private videoService: VideoService 42 private videoService: VideoService
45 ) { 43 ) {
@@ -55,7 +53,6 @@ export class VideoChannelVideosComponent extends AbstractVideoList implements On
55 this.videoChannelSub = this.videoChannelService.videoChannelLoaded 53 this.videoChannelSub = this.videoChannelService.videoChannelLoaded
56 .subscribe(videoChannel => { 54 .subscribe(videoChannel => {
57 this.videoChannel = videoChannel 55 this.videoChannel = videoChannel
58 this.currentRoute = '/video-channels/' + this.videoChannel.nameWithHost + '/videos'
59 56
60 this.reloadVideos() 57 this.reloadVideos()
61 this.generateSyndicationList() 58 this.generateSyndicationList()
diff --git a/client/src/app/+video-channels/video-channels-routing.module.ts b/client/src/app/+video-channels/video-channels-routing.module.ts
index cedd07d39..d4872a0a5 100644
--- a/client/src/app/+video-channels/video-channels-routing.module.ts
+++ b/client/src/app/+video-channels/video-channels-routing.module.ts
@@ -23,6 +23,10 @@ const videoChannelsRoutes: Routes = [
23 data: { 23 data: {
24 meta: { 24 meta: {
25 title: 'Video channel videos' 25 title: 'Video channel videos'
26 },
27 reuse: {
28 enabled: true,
29 key: 'video-channel-videos-list'
26 } 30 }
27 } 31 }
28 }, 32 },
diff --git a/client/src/app/app-routing.module.ts b/client/src/app/app-routing.module.ts
index cff37a7d6..db8888dba 100644
--- a/client/src/app/app-routing.module.ts
+++ b/client/src/app/app-routing.module.ts
@@ -1,8 +1,9 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router' 2import { RouteReuseStrategy, RouterModule, Routes } from '@angular/router'
3 3
4import { PreloadSelectedModulesList } from './core' 4import { PreloadSelectedModulesList } from './core'
5import { AppComponent } from '@app/app.component' 5import { AppComponent } from '@app/app.component'
6import { CustomReuseStrategy } from '@app/core/routing/custom-reuse-strategy'
6 7
7const routes: Routes = [ 8const routes: Routes = [
8 { 9 {
@@ -43,12 +44,14 @@ const routes: Routes = [
43 imports: [ 44 imports: [
44 RouterModule.forRoot(routes, { 45 RouterModule.forRoot(routes, {
45 useHash: Boolean(history.pushState) === false, 46 useHash: Boolean(history.pushState) === false,
47 scrollPositionRestoration: 'disabled',
46 preloadingStrategy: PreloadSelectedModulesList, 48 preloadingStrategy: PreloadSelectedModulesList,
47 anchorScrolling: 'enabled' 49 anchorScrolling: 'disabled'
48 }) 50 })
49 ], 51 ],
50 providers: [ 52 providers: [
51 PreloadSelectedModulesList 53 PreloadSelectedModulesList,
54 { provide: RouteReuseStrategy, useClass: CustomReuseStrategy }
52 ], 55 ],
53 exports: [ RouterModule ] 56 exports: [ RouterModule ]
54}) 57})
diff --git a/client/src/app/app.component.ts b/client/src/app/app.component.ts
index c5c5a8f66..ad0588b99 100644
--- a/client/src/app/app.component.ts
+++ b/client/src/app/app.component.ts
@@ -1,13 +1,14 @@
1import { Component, OnInit } from '@angular/core' 1import { Component, OnInit } from '@angular/core'
2import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 2import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
3import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router' 3import { Event, GuardsCheckStart, NavigationEnd, Router, Scroll } from '@angular/router'
4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core' 4import { AuthService, RedirectService, ServerService, ThemeService } from '@app/core'
5import { is18nPath } from '../../../shared/models/i18n' 5import { is18nPath } from '../../../shared/models/i18n'
6import { ScreenService } from '@app/shared/misc/screen.service' 6import { ScreenService } from '@app/shared/misc/screen.service'
7import { skip, debounceTime } from 'rxjs/operators' 7import { debounceTime, filter, map, pairwise, skip } from 'rxjs/operators'
8import { HotkeysService, Hotkey } from 'angular2-hotkeys' 8import { Hotkey, HotkeysService } from 'angular2-hotkeys'
9import { I18n } from '@ngx-translate/i18n-polyfill' 9import { I18n } from '@ngx-translate/i18n-polyfill'
10import { fromEvent } from 'rxjs' 10import { fromEvent } from 'rxjs'
11import { ViewportScroller } from '@angular/common'
11 12
12@Component({ 13@Component({
13 selector: 'my-app', 14 selector: 'my-app',
@@ -22,6 +23,7 @@ export class AppComponent implements OnInit {
22 23
23 constructor ( 24 constructor (
24 private i18n: I18n, 25 private i18n: I18n,
26 private viewportScroller: ViewportScroller,
25 private router: Router, 27 private router: Router,
26 private authService: AuthService, 28 private authService: AuthService,
27 private serverService: ServerService, 29 private serverService: ServerService,
@@ -52,15 +54,6 @@ export class AppComponent implements OnInit {
52 ngOnInit () { 54 ngOnInit () {
53 document.getElementById('incompatible-browser').className += ' browser-ok' 55 document.getElementById('incompatible-browser').className += ' browser-ok'
54 56
55 this.router.events.subscribe(e => {
56 if (e instanceof NavigationEnd) {
57 const pathname = window.location.pathname
58 if (!pathname || pathname === '/' || is18nPath(pathname)) {
59 this.redirectService.redirectToHomepage(true)
60 }
61 }
62 })
63
64 this.authService.loadClientCredentials() 57 this.authService.loadClientCredentials()
65 58
66 if (this.isUserLoggedIn()) { 59 if (this.isUserLoggedIn()) {
@@ -81,15 +74,94 @@ export class AppComponent implements OnInit {
81 this.isMenuDisplayed = false 74 this.isMenuDisplayed = false
82 } 75 }
83 76
84 this.router.events.subscribe( 77 this.initRouteEvents()
85 e => { 78 this.injectJS()
86 // User clicked on a link in the menu, change the page 79 this.injectCSS()
87 if (e instanceof GuardsCheckStart && this.screenService.isInSmallView()) { 80
88 this.isMenuDisplayed = false 81 this.initHotkeys()
89 } 82
83 fromEvent(window, 'resize')
84 .pipe(debounceTime(200))
85 .subscribe(() => this.onResize())
86 }
87
88 isUserLoggedIn () {
89 return this.authService.isLoggedIn()
90 }
91
92 toggleMenu () {
93 this.isMenuDisplayed = !this.isMenuDisplayed
94 this.isMenuChangedByUser = true
95 }
96
97 onResize () {
98 this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
99 }
100
101 private initRouteEvents () {
102 let resetScroll = true
103 const eventsObs = this.router.events
104
105 const scrollEvent = eventsObs.pipe(filter((e: Event): e is Scroll => e instanceof Scroll))
106 const navigationEndEvent = eventsObs.pipe(filter((e: Event): e is NavigationEnd => e instanceof NavigationEnd))
107
108 scrollEvent.subscribe(e => {
109 if (e.position) {
110 return this.viewportScroller.scrollToPosition(e.position)
90 } 111 }
91 )
92 112
113 if (e.anchor) {
114 return this.viewportScroller.scrollToAnchor(e.anchor)
115 }
116
117 if (resetScroll) {
118 return this.viewportScroller.scrollToPosition([ 0, 0 ])
119 }
120 })
121
122 // When we add the a-state parameter, we don't want to alter the scroll
123 navigationEndEvent.pipe(pairwise())
124 .subscribe(([ e1, e2 ]) => {
125 try {
126 resetScroll = false
127
128 const previousUrl = new URL(window.location.origin + e1.url)
129 const nextUrl = new URL(window.location.origin + e2.url)
130
131 if (previousUrl.pathname !== nextUrl.pathname) {
132 resetScroll = true
133 return
134 }
135
136 const nextSearchParams = nextUrl.searchParams
137 nextSearchParams.delete('a-state')
138
139 const previousSearchParams = previousUrl.searchParams
140
141 nextSearchParams.sort()
142 previousSearchParams.sort()
143
144 if (nextSearchParams.toString() !== previousSearchParams.toString()) {
145 resetScroll = true
146 }
147 } catch (e) {
148 console.error('Cannot parse URL to check next scroll.', e)
149 resetScroll = true
150 }
151 })
152
153 navigationEndEvent.pipe(
154 map(() => window.location.pathname),
155 filter(pathname => !pathname || pathname === '/' || is18nPath(pathname))
156 ).subscribe(() => this.redirectService.redirectToHomepage(true))
157
158 eventsObs.pipe(
159 filter((e: Event): e is GuardsCheckStart => e instanceof GuardsCheckStart),
160 filter(() => this.screenService.isInSmallView())
161 ).subscribe(() => this.isMenuDisplayed = false) // User clicked on a link in the menu, change the page
162 }
163
164 private injectJS () {
93 // Inject JS 165 // Inject JS
94 this.serverService.configLoaded 166 this.serverService.configLoaded
95 .subscribe(() => { 167 .subscribe(() => {
@@ -104,7 +176,9 @@ export class AppComponent implements OnInit {
104 } 176 }
105 } 177 }
106 }) 178 })
179 }
107 180
181 private injectCSS () {
108 // Inject CSS if modified (admin config settings) 182 // Inject CSS if modified (admin config settings)
109 this.serverService.configLoaded 183 this.serverService.configLoaded
110 .pipe(skip(1)) // We only want to subscribe to reloads, because the CSS is already injected by the server 184 .pipe(skip(1)) // We only want to subscribe to reloads, because the CSS is already injected by the server
@@ -120,7 +194,9 @@ export class AppComponent implements OnInit {
120 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag) 194 this.customCSS = this.domSanitizer.bypassSecurityTrustHtml(styleTag)
121 } 195 }
122 }) 196 })
197 }
123 198
199 private initHotkeys () {
124 this.hotkeysService.add([ 200 this.hotkeysService.add([
125 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => { 201 new Hotkey(['/', 's'], (event: KeyboardEvent): boolean => {
126 document.getElementById('search-video').focus() 202 document.getElementById('search-video').focus()
@@ -155,22 +231,5 @@ export class AppComponent implements OnInit {
155 return false 231 return false
156 }, undefined, this.i18n('Toggle Dark theme')) 232 }, undefined, this.i18n('Toggle Dark theme'))
157 ]) 233 ])
158
159 fromEvent(window, 'resize')
160 .pipe(debounceTime(200))
161 .subscribe(() => this.onResize())
162 }
163
164 isUserLoggedIn () {
165 return this.authService.isLoggedIn()
166 }
167
168 toggleMenu () {
169 this.isMenuDisplayed = !this.isMenuDisplayed
170 this.isMenuChangedByUser = true
171 }
172
173 onResize () {
174 this.isMenuDisplayed = window.innerWidth >= 800 && !this.isMenuChangedByUser
175 } 234 }
176} 235}
diff --git a/client/src/app/core/routing/custom-reuse-strategy.ts b/client/src/app/core/routing/custom-reuse-strategy.ts
new file mode 100644
index 000000000..a9f61acec
--- /dev/null
+++ b/client/src/app/core/routing/custom-reuse-strategy.ts
@@ -0,0 +1,81 @@
1import { ActivatedRouteSnapshot, DetachedRouteHandle, RouteReuseStrategy } from '@angular/router'
2
3export class CustomReuseStrategy implements RouteReuseStrategy {
4 storedRouteHandles = new Map<string, DetachedRouteHandle>()
5 recentlyUsed: string
6
7 private readonly MAX_SIZE = 2
8
9 // Decides if the route should be stored
10 shouldDetach (route: ActivatedRouteSnapshot): boolean {
11 return this.isReuseEnabled(route)
12 }
13
14 // Store the information for the route we're destructing
15 store (route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
16 if (!handle) return
17
18 const key = this.generateKey(route)
19 this.recentlyUsed = key
20
21 console.log('Storing component %s to reuse later.', key);
22
23 (handle as any).componentRef.instance.disableForReuse()
24
25 this.storedRouteHandles.set(key, handle)
26
27 this.gb()
28 }
29
30 // Return true if we have a stored route object for the next route
31 shouldAttach (route: ActivatedRouteSnapshot): boolean {
32 const key = this.generateKey(route)
33 return this.isReuseEnabled(route) && this.storedRouteHandles.has(key)
34 }
35
36 // If we returned true in shouldAttach(), now return the actual route data for restoration
37 retrieve (route: ActivatedRouteSnapshot): DetachedRouteHandle {
38 if (!this.isReuseEnabled(route)) return undefined
39
40 const key = this.generateKey(route)
41 this.recentlyUsed = key
42
43 console.log('Reusing component %s.', key)
44
45 const handle = this.storedRouteHandles.get(key)
46 if (!handle) return handle;
47
48 (handle as any).componentRef.instance.enabledForReuse()
49
50 return handle
51 }
52
53 // Reuse the route if we're going to and from the same route
54 shouldReuseRoute (future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
55 return future.routeConfig === curr.routeConfig
56 }
57
58 private gb () {
59 if (this.storedRouteHandles.size >= this.MAX_SIZE) {
60 this.storedRouteHandles.forEach((r, key) => {
61 if (key === this.recentlyUsed) return
62
63 console.log('Removing stored component %s.', key);
64
65 (r as any).componentRef.destroy()
66 this.storedRouteHandles.delete(key)
67 })
68 }
69 }
70
71 private generateKey (route: ActivatedRouteSnapshot) {
72 const reuse = route.data.reuse
73 if (!reuse) return undefined
74
75 return reuse.key + JSON.stringify(route.queryParams)
76 }
77
78 private isReuseEnabled (route: ActivatedRouteSnapshot) {
79 return route.data.reuse && route.data.reuse.enabled && route.queryParams['a-state']
80 }
81}
diff --git a/client/src/app/core/routing/disable-for-reuse-hook.ts b/client/src/app/core/routing/disable-for-reuse-hook.ts
new file mode 100644
index 000000000..c5eb5c578
--- /dev/null
+++ b/client/src/app/core/routing/disable-for-reuse-hook.ts
@@ -0,0 +1,7 @@
1export interface DisableForReuseHook {
2
3 disableForReuse (): void
4
5 enabledForReuse (): void
6
7}
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index 1f97bc389..e134654a3 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -19,13 +19,10 @@
19 19
20 <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div> 20 <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div>
21 <div 21 <div
22 myInfiniteScroller 22 myInfiniteScroller (nearOfBottom)="onNearOfBottom()" [autoInit]="true"
23 [pageHeight]="pageHeight" [firstLoadedPage]="firstLoadedPage" 23 class="videos"
24 (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)"
25 class="videos" #videosElement
26 > 24 >
27 <div *ngFor="let videos of videoPages; trackBy: pageByVideoId" class="videos-page"> 25 <my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType">
28 <my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature> 26 </my-video-miniature>
29 </div>
30 </div> 27 </div>
31</div> 28</div>
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index 2cd5bc393..467f629ea 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -1,66 +1,52 @@
1import { debounceTime } from 'rxjs/operators' 1import { debounceTime } from 'rxjs/operators'
2import { ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/core' 2import { OnDestroy, OnInit } from '@angular/core'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { Location } from '@angular/common'
5import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
6import { fromEvent, Observable, Subscription } from 'rxjs' 4import { fromEvent, Observable, Subscription } from 'rxjs'
7import { AuthService } from '../../core/auth' 5import { AuthService } from '../../core/auth'
8import { ComponentPagination } from '../rest/component-pagination.model' 6import { ComponentPagination } from '../rest/component-pagination.model'
9import { VideoSortField } from './sort-field.type' 7import { VideoSortField } from './sort-field.type'
10import { Video } from './video.model' 8import { Video } from './video.model'
11import { I18n } from '@ngx-translate/i18n-polyfill'
12import { ScreenService } from '@app/shared/misc/screen.service' 9import { ScreenService } from '@app/shared/misc/screen.service'
13import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' 10import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
14import { Syndication } from '@app/shared/video/syndication.model' 11import { Syndication } from '@app/shared/video/syndication.model'
15import { Notifier } from '@app/core' 12import { Notifier, ServerService } from '@app/core'
16 13import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
17export abstract class AbstractVideoList implements OnInit, OnDestroy {
18 private static LINES_PER_PAGE = 4
19
20 @ViewChild('videosElement') videosElement: ElementRef
21 @ViewChild(InfiniteScrollerDirective) infiniteScroller: InfiniteScrollerDirective
22 14
15export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook {
23 pagination: ComponentPagination = { 16 pagination: ComponentPagination = {
24 currentPage: 1, 17 currentPage: 1,
25 itemsPerPage: 10, 18 itemsPerPage: 25,
26 totalItems: null 19 totalItems: null
27 } 20 }
28 sort: VideoSortField = '-publishedAt' 21 sort: VideoSortField = '-publishedAt'
22
29 categoryOneOf?: number 23 categoryOneOf?: number
30 defaultSort: VideoSortField = '-publishedAt' 24 defaultSort: VideoSortField = '-publishedAt'
25
31 syndicationItems: Syndication[] = [] 26 syndicationItems: Syndication[] = []
32 27
33 loadOnInit = true 28 loadOnInit = true
34 marginContent = true 29 marginContent = true
35 pageHeight: number 30 videos: Video[] = []
36 videoWidth: number
37 videoHeight: number
38 videoPages: Video[][] = []
39 ownerDisplayType: OwnerDisplayType = 'account' 31 ownerDisplayType: OwnerDisplayType = 'account'
40 firstLoadedPage: number
41 displayModerationBlock = false 32 displayModerationBlock = false
42 titleTooltip: string 33 titleTooltip: string
43 34
44 protected baseVideoWidth = 238 35 disabled = false
45 protected baseVideoHeight = 225
46 36
47 protected abstract notifier: Notifier 37 protected abstract notifier: Notifier
48 protected abstract authService: AuthService 38 protected abstract authService: AuthService
49 protected abstract router: Router
50 protected abstract route: ActivatedRoute 39 protected abstract route: ActivatedRoute
40 protected abstract serverService: ServerService
51 protected abstract screenService: ScreenService 41 protected abstract screenService: ScreenService
52 protected abstract i18n: I18n 42 protected abstract router: Router
53 protected abstract location: Location
54 protected abstract currentRoute: string
55 abstract titlePage: string 43 abstract titlePage: string
56 44
57 protected loadedPages: { [ id: number ]: Video[] } = {}
58 protected loadingPage: { [ id: number ]: boolean } = {}
59 protected otherRouteParams = {}
60
61 private resizeSubscription: Subscription 45 private resizeSubscription: Subscription
46 private angularState: number
47
48 abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
62 49
63 abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number}>
64 abstract generateSyndicationList (): void 50 abstract generateSyndicationList (): void
65 51
66 get user () { 52 get user () {
@@ -77,207 +63,87 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy {
77 .subscribe(() => this.calcPageSizes()) 63 .subscribe(() => this.calcPageSizes())
78 64
79 this.calcPageSizes() 65 this.calcPageSizes()
80 if (this.loadOnInit === true) this.loadMoreVideos(this.pagination.currentPage) 66 if (this.loadOnInit === true) this.loadMoreVideos()
81 } 67 }
82 68
83 ngOnDestroy () { 69 ngOnDestroy () {
84 if (this.resizeSubscription) this.resizeSubscription.unsubscribe() 70 if (this.resizeSubscription) this.resizeSubscription.unsubscribe()
85 } 71 }
86 72
87 pageByVideoId (index: number, page: Video[]) { 73 disableForReuse () {
88 // Video are unique in all pages 74 this.disabled = true
89 return page.length !== 0 ? page[0].id : 0
90 } 75 }
91 76
92 videoById (index: number, video: Video) { 77 enabledForReuse () {
93 return video.id 78 this.disabled = false
94 } 79 }
95 80
96 onNearOfTop () { 81 videoById (index: number, video: Video) {
97 this.previousPage() 82 return video.id
98 } 83 }
99 84
100 onNearOfBottom () { 85 onNearOfBottom () {
101 if (this.hasMoreVideos()) { 86 if (this.disabled) return
102 this.nextPage()
103 }
104 }
105 87
106 onPageChanged (page: number) { 88 // Last page
107 this.pagination.currentPage = page 89 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
108 this.setNewRouteParams()
109 }
110 90
111 reloadVideos () { 91 this.pagination.currentPage += 1
112 this.loadedPages = {}
113 this.loadMoreVideos(this.pagination.currentPage)
114 }
115
116 loadMoreVideos (page: number, loadOnTop = false) {
117 this.adjustVideoPageHeight()
118 92
119 const currentY = window.scrollY 93 this.setScrollRouteParams()
120 94
121 if (this.loadedPages[page] !== undefined) return 95 this.loadMoreVideos()
122 if (this.loadingPage[page] === true) return 96 }
123 97
124 this.loadingPage[page] = true 98 loadMoreVideos () {
125 const observable = this.getVideosObservable(page) 99 const observable = this.getVideosObservable(this.pagination.currentPage)
126 100
127 observable.subscribe( 101 observable.subscribe(
128 ({ videos, totalVideos }) => { 102 ({ videos, totalVideos }) => {
129 this.loadingPage[page] = false
130
131 if (this.firstLoadedPage === undefined || this.firstLoadedPage > page) this.firstLoadedPage = page
132
133 // Paging is too high, return to the first one
134 if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) {
135 this.pagination.currentPage = 1
136 this.setNewRouteParams()
137 return this.reloadVideos()
138 }
139
140 this.loadedPages[page] = videos
141 this.buildVideoPages()
142 this.pagination.totalItems = totalVideos 103 this.pagination.totalItems = totalVideos
143 104 this.videos = this.videos.concat(videos)
144 // Initialize infinite scroller now we loaded the first page
145 if (Object.keys(this.loadedPages).length === 1) {
146 // Wait elements creation
147 setTimeout(() => {
148 this.infiniteScroller.initialize()
149
150 // At our first load, we did not load the first page
151 // Load the previous page so the user can move on the top (and browser previous pages)
152 if (this.pagination.currentPage > 1) this.loadMoreVideos(this.pagination.currentPage - 1, true)
153 }, 500)
154 }
155
156 // Insert elements on the top but keep the scroll in the previous position
157 if (loadOnTop) setTimeout(() => { window.scrollTo(0, currentY + this.pageHeight) }, 0)
158 }, 105 },
159 error => {
160 this.loadingPage[page] = false
161 this.notifier.error(error.message)
162 }
163 )
164 }
165
166 toggleModerationDisplay () {
167 throw new Error('toggleModerationDisplay is not implemented')
168 }
169 106
170 protected hasMoreVideos () { 107 error => this.notifier.error(error.message)
171 // No results 108 )
172 if (this.pagination.totalItems === 0) return false
173
174 // Not loaded yet
175 if (!this.pagination.totalItems) return true
176
177 const maxPage = this.pagination.totalItems / this.pagination.itemsPerPage
178 return maxPage > this.maxPageLoaded()
179 }
180
181 protected previousPage () {
182 const min = this.minPageLoaded()
183
184 if (min > 1) {
185 this.loadMoreVideos(min - 1, true)
186 }
187 } 109 }
188 110
189 protected nextPage () { 111 reloadVideos () {
190 this.loadMoreVideos(this.maxPageLoaded() + 1) 112 this.pagination.currentPage = 1
113 this.videos = []
114 this.loadMoreVideos()
191 } 115 }
192 116
193 protected buildRouteParams () { 117 toggleModerationDisplay () {
194 // There is always a sort and a current page 118 throw new Error('toggleModerationDisplay is not implemented')
195 const params = {
196 sort: this.sort,
197 page: this.pagination.currentPage
198 }
199
200 return Object.assign(params, this.otherRouteParams)
201 } 119 }
202 120
203 protected loadRouteParams (routeParams: { [ key: string ]: any }) { 121 protected loadRouteParams (routeParams: { [ key: string ]: any }) {
204 this.sort = routeParams['sort'] as VideoSortField || this.defaultSort 122 this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
205 this.categoryOneOf = routeParams['categoryOneOf'] 123 this.categoryOneOf = routeParams[ 'categoryOneOf' ]
206 if (routeParams['page'] !== undefined) { 124 this.angularState = routeParams[ 'a-state' ]
207 this.pagination.currentPage = parseInt(routeParams['page'], 10)
208 } else {
209 this.pagination.currentPage = 1
210 }
211 }
212
213 protected setNewRouteParams () {
214 const paramsObject = this.buildRouteParams()
215
216 const queryParams = Object.keys(paramsObject)
217 .map(p => p + '=' + paramsObject[p])
218 .join('&')
219 this.location.replaceState(this.currentRoute, queryParams)
220 }
221
222 protected buildVideoPages () {
223 this.videoPages = Object.values(this.loadedPages)
224 }
225
226 protected adjustVideoPageHeight () {
227 const numberOfPagesLoaded = Object.keys(this.loadedPages).length
228 if (!numberOfPagesLoaded) return
229
230 this.pageHeight = this.videosElement.nativeElement.offsetHeight / numberOfPagesLoaded
231 }
232
233 protected buildVideoHeight () {
234 // Same ratios than base width/height
235 return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth)
236 }
237
238 private minPageLoaded () {
239 return Math.min(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
240 }
241
242 private maxPageLoaded () {
243 return Math.max(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
244 } 125 }
245 126
246 private calcPageSizes () { 127 private calcPageSizes () {
247 if (this.screenService.isInMobileView() || this.baseVideoWidth === -1) { 128 if (this.screenService.isInMobileView()) {
248 this.pagination.itemsPerPage = 5 129 this.pagination.itemsPerPage = 5
249
250 // Video takes all the width
251 this.videoWidth = -1
252 this.videoHeight = this.buildVideoHeight()
253 this.pageHeight = this.pagination.itemsPerPage * this.videoHeight
254 } else {
255 this.videoWidth = this.baseVideoWidth
256 this.videoHeight = this.baseVideoHeight
257
258 const videosWidth = this.videosElement.nativeElement.offsetWidth
259 this.pagination.itemsPerPage = Math.floor(videosWidth / this.videoWidth) * AbstractVideoList.LINES_PER_PAGE
260 this.pageHeight = this.videoHeight * AbstractVideoList.LINES_PER_PAGE
261 } 130 }
131 }
262 132
263 // Rebuild pages because maybe we modified the number of items per page 133 private setScrollRouteParams () {
264 const videos = [].concat(...this.videoPages) 134 // Already set
265 this.loadedPages = {} 135 if (this.angularState) return
266 136
267 let i = 1 137 this.angularState = 42
268 // Don't include the last page if it not complete
269 while (videos.length >= this.pagination.itemsPerPage && i < 10000) { // 10000 -> Hard limit in case of infinite loop
270 this.loadedPages[i] = videos.splice(0, this.pagination.itemsPerPage)
271 i++
272 }
273 138
274 // Re fetch the last page 139 const queryParams = {
275 if (videos.length !== 0) { 140 'a-state': this.angularState,
276 this.loadMoreVideos(i) 141 categoryOneOf: this.categoryOneOf
277 } else {
278 this.buildVideoPages()
279 } 142 }
280 143
281 console.log('Rebuilt pages with %s elements per page.', this.pagination.itemsPerPage) 144 let path = this.router.url
145 if (!path || path === '/') path = this.serverService.getConfig().instance.defaultClientRoute
146
147 this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' })
282 } 148 }
283} 149}
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts
index a9e75007c..5f8a1dd6e 100644
--- a/client/src/app/shared/video/infinite-scroller.directive.ts
+++ b/client/src/app/shared/video/infinite-scroller.directive.ts
@@ -6,24 +6,15 @@ import { fromEvent, Subscription } from 'rxjs'
6 selector: '[myInfiniteScroller]' 6 selector: '[myInfiniteScroller]'
7}) 7})
8export class InfiniteScrollerDirective implements OnInit, OnDestroy { 8export class InfiniteScrollerDirective implements OnInit, OnDestroy {
9 @Input() containerHeight: number
10 @Input() pageHeight: number
11 @Input() firstLoadedPage = 1
12 @Input() percentLimit = 70 9 @Input() percentLimit = 70
13 @Input() autoInit = false 10 @Input() autoInit = false
14 @Input() onItself = false 11 @Input() onItself = false
15 12
16 @Output() nearOfBottom = new EventEmitter<void>() 13 @Output() nearOfBottom = new EventEmitter<void>()
17 @Output() nearOfTop = new EventEmitter<void>()
18 @Output() pageChanged = new EventEmitter<number>()
19 14
20 private decimalLimit = 0 15 private decimalLimit = 0
21 private lastCurrentBottom = -1 16 private lastCurrentBottom = -1
22 private lastCurrentTop = 0
23 private scrollDownSub: Subscription 17 private scrollDownSub: Subscription
24 private scrollUpSub: Subscription
25 private pageChangeSub: Subscription
26 private middleScreen: number
27 private container: HTMLElement 18 private container: HTMLElement
28 19
29 constructor (private el: ElementRef) { 20 constructor (private el: ElementRef) {
@@ -36,8 +27,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
36 27
37 ngOnDestroy () { 28 ngOnDestroy () {
38 if (this.scrollDownSub) this.scrollDownSub.unsubscribe() 29 if (this.scrollDownSub) this.scrollDownSub.unsubscribe()
39 if (this.scrollUpSub) this.scrollUpSub.unsubscribe()
40 if (this.pageChangeSub) this.pageChangeSub.unsubscribe()
41 } 30 }
42 31
43 initialize () { 32 initialize () {
@@ -45,8 +34,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
45 this.container = this.el.nativeElement 34 this.container = this.el.nativeElement
46 } 35 }
47 36
48 this.middleScreen = window.innerHeight / 2
49
50 // Emit the last value 37 // Emit the last value
51 const throttleOptions = { leading: true, trailing: true } 38 const throttleOptions = { leading: true, trailing: true }
52 39
@@ -72,40 +59,6 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy {
72 filter(({ current, maximumScroll }) => maximumScroll <= 0 || (current / maximumScroll) > this.decimalLimit) 59 filter(({ current, maximumScroll }) => maximumScroll <= 0 || (current / maximumScroll) > this.decimalLimit)
73 ) 60 )
74 .subscribe(() => this.nearOfBottom.emit()) 61 .subscribe(() => this.nearOfBottom.emit())
75
76 // Scroll up
77 this.scrollUpSub = scrollObservable
78 .pipe(
79 // Check we scroll up
80 filter(({ current }) => {
81 const res = this.lastCurrentTop > current
82
83 this.lastCurrentTop = current
84 return res
85 }),
86 filter(({ current, maximumScroll }) => {
87 return current !== 0 && (1 - (current / maximumScroll)) > this.decimalLimit
88 })
89 )
90 .subscribe(() => this.nearOfTop.emit())
91
92 // Page change
93 this.pageChangeSub = scrollObservable
94 .pipe(
95 distinct(),
96 map(({ current }) => this.calculateCurrentPage(current)),
97 distinctUntilChanged()
98 )
99 .subscribe(res => this.pageChanged.emit(res))
100 }
101
102 private calculateCurrentPage (current: number) {
103 const scrollY = current + this.middleScreen
104
105 const page = Math.max(1, Math.ceil(scrollY / this.pageHeight))
106
107 // Offset page
108 return page + (this.firstLoadedPage - 1)
109 } 62 }
110 63
111 private getScrollInfo () { 64 private getScrollInfo () {
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 c0be4b885..13d4023c2 100644
--- a/client/src/app/videos/video-list/video-local.component.ts
+++ b/client/src/app/videos/video-list/video-local.component.ts
@@ -1,7 +1,6 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
4import { Location } from '@angular/common'
5import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
6import { AbstractVideoList } from '../../shared/video/abstract-video-list' 5import { AbstractVideoList } from '../../shared/video/abstract-video-list'
7import { VideoSortField } from '../../shared/video/sort-field.type' 6import { VideoSortField } from '../../shared/video/sort-field.type'
@@ -10,7 +9,7 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ
10import { I18n } from '@ngx-translate/i18n-polyfill' 9import { I18n } from '@ngx-translate/i18n-polyfill'
11import { ScreenService } from '@app/shared/misc/screen.service' 10import { ScreenService } from '@app/shared/misc/screen.service'
12import { UserRight } from '../../../../../shared/models/users' 11import { UserRight } from '../../../../../shared/models/users'
13import { Notifier } from '@app/core' 12import { Notifier, ServerService } from '@app/core'
14 13
15@Component({ 14@Component({
16 selector: 'my-videos-local', 15 selector: 'my-videos-local',
@@ -19,18 +18,17 @@ import { Notifier } from '@app/core'
19}) 18})
20export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy { 19export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy {
21 titlePage: string 20 titlePage: string
22 currentRoute = '/videos/local'
23 sort = '-publishedAt' as VideoSortField 21 sort = '-publishedAt' as VideoSortField
24 filter: VideoFilter = 'local' 22 filter: VideoFilter = 'local'
25 23
26 constructor ( 24 constructor (
27 protected router: Router, 25 protected router: Router,
26 protected serverService: ServerService,
28 protected route: ActivatedRoute, 27 protected route: ActivatedRoute,
29 protected notifier: Notifier, 28 protected notifier: Notifier,
30 protected authService: AuthService, 29 protected authService: AuthService,
31 protected location: Location,
32 protected i18n: I18n,
33 protected screenService: ScreenService, 30 protected screenService: ScreenService,
31 private i18n: I18n,
34 private videoService: VideoService 32 private videoService: VideoService
35 ) { 33 ) {
36 super() 34 super()
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 f99c8abb6..80cef813e 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,6 +1,5 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { Location } from '@angular/common'
4import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
5import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
6import { AbstractVideoList } from '../../shared/video/abstract-video-list' 5import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -8,7 +7,7 @@ import { VideoSortField } from '../../shared/video/sort-field.type'
8import { VideoService } from '../../shared/video/video.service' 7import { VideoService } from '../../shared/video/video.service'
9import { I18n } from '@ngx-translate/i18n-polyfill' 8import { I18n } from '@ngx-translate/i18n-polyfill'
10import { ScreenService } from '@app/shared/misc/screen.service' 9import { ScreenService } from '@app/shared/misc/screen.service'
11import { Notifier } from '@app/core' 10import { Notifier, ServerService } from '@app/core'
12 11
13@Component({ 12@Component({
14 selector: 'my-videos-recently-added', 13 selector: 'my-videos-recently-added',
@@ -17,17 +16,16 @@ import { Notifier } from '@app/core'
17}) 16})
18export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { 17export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
19 titlePage: string 18 titlePage: string
20 currentRoute = '/videos/recently-added'
21 sort: VideoSortField = '-publishedAt' 19 sort: VideoSortField = '-publishedAt'
22 20
23 constructor ( 21 constructor (
24 protected router: Router,
25 protected route: ActivatedRoute, 22 protected route: ActivatedRoute,
26 protected location: Location, 23 protected serverService: ServerService,
24 protected router: Router,
27 protected notifier: Notifier, 25 protected notifier: Notifier,
28 protected authService: AuthService, 26 protected authService: AuthService,
29 protected i18n: I18n,
30 protected screenService: ScreenService, 27 protected screenService: ScreenService,
28 private i18n: I18n,
31 private videoService: VideoService 29 private videoService: VideoService
32 ) { 30 ) {
33 super() 31 super()
diff --git a/client/src/app/videos/video-list/video-trending.component.ts b/client/src/app/videos/video-list/video-trending.component.ts
index a66a0f97c..e2ad95bc4 100644
--- a/client/src/app/videos/video-list/video-trending.component.ts
+++ b/client/src/app/videos/video-list/video-trending.component.ts
@@ -1,6 +1,5 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { Location } from '@angular/common'
4import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
5import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
6import { AbstractVideoList } from '../../shared/video/abstract-video-list' 5import { AbstractVideoList } from '../../shared/video/abstract-video-list'
@@ -17,18 +16,16 @@ import { Notifier, ServerService } from '@app/core'
17}) 16})
18export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { 17export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
19 titlePage: string 18 titlePage: string
20 currentRoute = '/videos/trending'
21 defaultSort: VideoSortField = '-trending' 19 defaultSort: VideoSortField = '-trending'
22 20
23 constructor ( 21 constructor (
24 protected router: Router, 22 protected router: Router,
23 protected serverService: ServerService,
25 protected route: ActivatedRoute, 24 protected route: ActivatedRoute,
26 protected notifier: Notifier, 25 protected notifier: Notifier,
27 protected authService: AuthService, 26 protected authService: AuthService,
28 protected location: Location,
29 protected screenService: ScreenService, 27 protected screenService: ScreenService,
30 private serverService: ServerService, 28 private i18n: I18n,
31 protected i18n: I18n,
32 private videoService: VideoService 29 private videoService: VideoService
33 ) { 30 ) {
34 super() 31 super()
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 bee828e12..2f0685ccc 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,7 +1,6 @@
1import { Component, OnDestroy, OnInit } from '@angular/core' 1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router' 2import { ActivatedRoute, Router } from '@angular/router'
3import { immutableAssign } from '@app/shared/misc/utils' 3import { immutableAssign } from '@app/shared/misc/utils'
4import { Location } from '@angular/common'
5import { AuthService } from '../../core/auth' 4import { AuthService } from '../../core/auth'
6import { AbstractVideoList } from '../../shared/video/abstract-video-list' 5import { AbstractVideoList } from '../../shared/video/abstract-video-list'
7import { VideoSortField } from '../../shared/video/sort-field.type' 6import { VideoSortField } from '../../shared/video/sort-field.type'
@@ -9,7 +8,7 @@ import { VideoService } from '../../shared/video/video.service'
9import { I18n } from '@ngx-translate/i18n-polyfill' 8import { I18n } from '@ngx-translate/i18n-polyfill'
10import { ScreenService } from '@app/shared/misc/screen.service' 9import { ScreenService } from '@app/shared/misc/screen.service'
11import { OwnerDisplayType } from '@app/shared/video/video-miniature.component' 10import { OwnerDisplayType } from '@app/shared/video/video-miniature.component'
12import { Notifier } from '@app/core' 11import { Notifier, ServerService } from '@app/core'
13 12
14@Component({ 13@Component({
15 selector: 'my-videos-user-subscriptions', 14 selector: 'my-videos-user-subscriptions',
@@ -18,18 +17,17 @@ import { Notifier } from '@app/core'
18}) 17})
19export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy { 18export class VideoUserSubscriptionsComponent extends AbstractVideoList implements OnInit, OnDestroy {
20 titlePage: string 19 titlePage: string
21 currentRoute = '/videos/subscriptions'
22 sort = '-publishedAt' as VideoSortField 20 sort = '-publishedAt' as VideoSortField
23 ownerDisplayType: OwnerDisplayType = 'auto' 21 ownerDisplayType: OwnerDisplayType = 'auto'
24 22
25 constructor ( 23 constructor (
26 protected router: Router, 24 protected router: Router,
25 protected serverService: ServerService,
27 protected route: ActivatedRoute, 26 protected route: ActivatedRoute,
28 protected notifier: Notifier, 27 protected notifier: Notifier,
29 protected authService: AuthService, 28 protected authService: AuthService,
30 protected location: Location,
31 protected i18n: I18n,
32 protected screenService: ScreenService, 29 protected screenService: ScreenService,
30 private i18n: I18n,
33 private videoService: VideoService 31 private videoService: VideoService
34 ) { 32 ) {
35 super() 33 super()
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 69a9232ce..505173a5b 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -29,6 +29,10 @@ const videosRoutes: Routes = [
29 data: { 29 data: {
30 meta: { 30 meta: {
31 title: 'Trending videos' 31 title: 'Trending videos'
32 },
33 reuse: {
34 enabled: true,
35 key: 'trending-videos-list'
32 } 36 }
33 } 37 }
34 }, 38 },
@@ -38,6 +42,10 @@ const videosRoutes: Routes = [
38 data: { 42 data: {
39 meta: { 43 meta: {
40 title: 'Recently added videos' 44 title: 'Recently added videos'
45 },
46 reuse: {
47 enabled: true,
48 key: 'recently-added-videos-list'
41 } 49 }
42 } 50 }
43 }, 51 },
@@ -47,6 +55,10 @@ const videosRoutes: Routes = [
47 data: { 55 data: {
48 meta: { 56 meta: {
49 title: 'Subscriptions' 57 title: 'Subscriptions'
58 },
59 reuse: {
60 enabled: true,
61 key: 'subscription-videos-list'
50 } 62 }
51 } 63 }
52 }, 64 },
@@ -56,6 +68,10 @@ const videosRoutes: Routes = [
56 data: { 68 data: {
57 meta: { 69 meta: {
58 title: 'Local videos' 70 title: 'Local videos'
71 },
72 reuse: {
73 enabled: true,
74 key: 'local-videos-list'
59 } 75 }
60 } 76 }
61 }, 77 },