diff options
Diffstat (limited to 'client/src/app/shared')
6 files changed, 59 insertions, 17 deletions
diff --git a/client/src/app/shared/overview/overview.service.ts b/client/src/app/shared/overview/overview.service.ts index 4a4714af6..097079e6d 100644 --- a/client/src/app/shared/overview/overview.service.ts +++ b/client/src/app/shared/overview/overview.service.ts | |||
@@ -56,6 +56,8 @@ export class OverviewService { | |||
56 | } | 56 | } |
57 | } | 57 | } |
58 | 58 | ||
59 | if (observables.length === 0) return of(videosOverviewResult) | ||
60 | |||
59 | return forkJoin(observables) | 61 | return forkJoin(observables) |
60 | .pipe( | 62 | .pipe( |
61 | // Translate categories | 63 | // Translate categories |
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html index 0f48b9a64..d543ab7c1 100644 --- a/client/src/app/shared/video/abstract-video-list.html +++ b/client/src/app/shared/video/abstract-video-list.html | |||
@@ -7,12 +7,12 @@ | |||
7 | <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div> | 7 | <div class="no-results" i18n *ngIf="pagination.totalItems === 0">No results.</div> |
8 | <div | 8 | <div |
9 | myInfiniteScroller | 9 | myInfiniteScroller |
10 | [pageHeight]="pageHeight" | 10 | [pageHeight]="pageHeight" [firstLoadedPage]="firstLoadedPage" |
11 | (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)" | 11 | (nearOfTop)="onNearOfTop()" (nearOfBottom)="onNearOfBottom()" (pageChanged)="onPageChanged($event)" |
12 | class="videos" #videosElement | 12 | class="videos" #videosElement |
13 | > | 13 | > |
14 | <div *ngFor="let videos of videoPages" class="videos-page"> | 14 | <div *ngFor="let videos of videoPages; trackBy: pageByVideoId" class="videos-page"> |
15 | <my-video-miniature *ngFor="let video of videos" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature> | 15 | <my-video-miniature *ngFor="let video of videos; trackBy: videoById" [video]="video" [user]="user" [ownerDisplayType]="ownerDisplayType"></my-video-miniature> |
16 | </div> | 16 | </div> |
17 | </div> | 17 | </div> |
18 | </div> | 18 | </div> |
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts index b8fd7f8eb..6a758ebe0 100644 --- a/client/src/app/shared/video/abstract-video-list.ts +++ b/client/src/app/shared/video/abstract-video-list.ts | |||
@@ -36,9 +36,10 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
36 | videoHeight: number | 36 | videoHeight: number |
37 | videoPages: Video[][] = [] | 37 | videoPages: Video[][] = [] |
38 | ownerDisplayType: OwnerDisplayType = 'account' | 38 | ownerDisplayType: OwnerDisplayType = 'account' |
39 | firstLoadedPage: number | ||
39 | 40 | ||
40 | protected baseVideoWidth = 215 | 41 | protected baseVideoWidth = 215 |
41 | protected baseVideoHeight = 230 | 42 | protected baseVideoHeight = 205 |
42 | 43 | ||
43 | protected abstract notificationsService: NotificationsService | 44 | protected abstract notificationsService: NotificationsService |
44 | protected abstract authService: AuthService | 45 | protected abstract authService: AuthService |
@@ -80,6 +81,15 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
80 | if (this.resizeSubscription) this.resizeSubscription.unsubscribe() | 81 | if (this.resizeSubscription) this.resizeSubscription.unsubscribe() |
81 | } | 82 | } |
82 | 83 | ||
84 | pageByVideoId (index: number, page: Video[]) { | ||
85 | // Video are unique in all pages | ||
86 | return page[0].id | ||
87 | } | ||
88 | |||
89 | videoById (index: number, video: Video) { | ||
90 | return video.id | ||
91 | } | ||
92 | |||
83 | onNearOfTop () { | 93 | onNearOfTop () { |
84 | this.previousPage() | 94 | this.previousPage() |
85 | } | 95 | } |
@@ -100,7 +110,11 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
100 | this.loadMoreVideos(this.pagination.currentPage) | 110 | this.loadMoreVideos(this.pagination.currentPage) |
101 | } | 111 | } |
102 | 112 | ||
103 | loadMoreVideos (page: number) { | 113 | loadMoreVideos (page: number, loadOnTop = false) { |
114 | this.adjustVideoPageHeight() | ||
115 | |||
116 | const currentY = window.scrollY | ||
117 | |||
104 | if (this.loadedPages[page] !== undefined) return | 118 | if (this.loadedPages[page] !== undefined) return |
105 | if (this.loadingPage[page] === true) return | 119 | if (this.loadingPage[page] === true) return |
106 | 120 | ||
@@ -111,6 +125,8 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
111 | ({ videos, totalVideos }) => { | 125 | ({ videos, totalVideos }) => { |
112 | this.loadingPage[page] = false | 126 | this.loadingPage[page] = false |
113 | 127 | ||
128 | if (this.firstLoadedPage === undefined || this.firstLoadedPage > page) this.firstLoadedPage = page | ||
129 | |||
114 | // Paging is too high, return to the first one | 130 | // Paging is too high, return to the first one |
115 | if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) { | 131 | if (this.pagination.currentPage > 1 && totalVideos <= ((this.pagination.currentPage - 1) * this.pagination.itemsPerPage)) { |
116 | this.pagination.currentPage = 1 | 132 | this.pagination.currentPage = 1 |
@@ -125,8 +141,17 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
125 | // Initialize infinite scroller now we loaded the first page | 141 | // Initialize infinite scroller now we loaded the first page |
126 | if (Object.keys(this.loadedPages).length === 1) { | 142 | if (Object.keys(this.loadedPages).length === 1) { |
127 | // Wait elements creation | 143 | // Wait elements creation |
128 | setTimeout(() => this.infiniteScroller.initialize(), 500) | 144 | setTimeout(() => { |
145 | this.infiniteScroller.initialize() | ||
146 | |||
147 | // At our first load, we did not load the first page | ||
148 | // Load the previous page so the user can move on the top (and browser previous pages) | ||
149 | if (this.pagination.currentPage > 1) this.loadMoreVideos(this.pagination.currentPage - 1, true) | ||
150 | }, 500) | ||
129 | } | 151 | } |
152 | |||
153 | // Insert elements on the top but keep the scroll in the previous position | ||
154 | if (loadOnTop) setTimeout(() => { window.scrollTo(0, currentY + this.pageHeight) }, 0) | ||
130 | }, | 155 | }, |
131 | error => { | 156 | error => { |
132 | this.loadingPage[page] = false | 157 | this.loadingPage[page] = false |
@@ -150,7 +175,7 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
150 | const min = this.minPageLoaded() | 175 | const min = this.minPageLoaded() |
151 | 176 | ||
152 | if (min > 1) { | 177 | if (min > 1) { |
153 | this.loadMoreVideos(min - 1) | 178 | this.loadMoreVideos(min - 1, true) |
154 | } | 179 | } |
155 | } | 180 | } |
156 | 181 | ||
@@ -189,6 +214,13 @@ export abstract class AbstractVideoList implements OnInit, OnDestroy { | |||
189 | this.videoPages = Object.values(this.loadedPages) | 214 | this.videoPages = Object.values(this.loadedPages) |
190 | } | 215 | } |
191 | 216 | ||
217 | protected adjustVideoPageHeight () { | ||
218 | const numberOfPagesLoaded = Object.keys(this.loadedPages).length | ||
219 | if (!numberOfPagesLoaded) return | ||
220 | |||
221 | this.pageHeight = this.videosElement.nativeElement.offsetHeight / numberOfPagesLoaded | ||
222 | } | ||
223 | |||
192 | protected buildVideoHeight () { | 224 | protected buildVideoHeight () { |
193 | // Same ratios than base width/height | 225 | // Same ratios than base width/height |
194 | return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth) | 226 | return this.videosElement.nativeElement.offsetWidth * (this.baseVideoHeight / this.baseVideoWidth) |
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts index 4dc1f86e7..a02e9444a 100644 --- a/client/src/app/shared/video/infinite-scroller.directive.ts +++ b/client/src/app/shared/video/infinite-scroller.directive.ts | |||
@@ -6,10 +6,9 @@ import { fromEvent, Subscription } from 'rxjs' | |||
6 | selector: '[myInfiniteScroller]' | 6 | selector: '[myInfiniteScroller]' |
7 | }) | 7 | }) |
8 | export class InfiniteScrollerDirective implements OnInit, OnDestroy { | 8 | export class InfiniteScrollerDirective implements OnInit, OnDestroy { |
9 | private static PAGE_VIEW_TOP_MARGIN = 500 | ||
10 | |||
11 | @Input() containerHeight: number | 9 | @Input() containerHeight: number |
12 | @Input() pageHeight: number | 10 | @Input() pageHeight: number |
11 | @Input() firstLoadedPage = 1 | ||
13 | @Input() percentLimit = 70 | 12 | @Input() percentLimit = 70 |
14 | @Input() autoInit = false | 13 | @Input() autoInit = false |
15 | 14 | ||
@@ -23,6 +22,7 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
23 | private scrollDownSub: Subscription | 22 | private scrollDownSub: Subscription |
24 | private scrollUpSub: Subscription | 23 | private scrollUpSub: Subscription |
25 | private pageChangeSub: Subscription | 24 | private pageChangeSub: Subscription |
25 | private middleScreen: number | ||
26 | 26 | ||
27 | constructor () { | 27 | constructor () { |
28 | this.decimalLimit = this.percentLimit / 100 | 28 | this.decimalLimit = this.percentLimit / 100 |
@@ -39,6 +39,8 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
39 | } | 39 | } |
40 | 40 | ||
41 | initialize () { | 41 | initialize () { |
42 | this.middleScreen = window.innerHeight / 2 | ||
43 | |||
42 | // Emit the last value | 44 | // Emit the last value |
43 | const throttleOptions = { leading: true, trailing: true } | 45 | const throttleOptions = { leading: true, trailing: true } |
44 | 46 | ||
@@ -92,6 +94,11 @@ export class InfiniteScrollerDirective implements OnInit, OnDestroy { | |||
92 | } | 94 | } |
93 | 95 | ||
94 | private calculateCurrentPage (current: number) { | 96 | private calculateCurrentPage (current: number) { |
95 | return Math.max(1, Math.round((current + InfiniteScrollerDirective.PAGE_VIEW_TOP_MARGIN) / this.pageHeight)) | 97 | const scrollY = current + this.middleScreen |
98 | |||
99 | const page = Math.max(1, Math.ceil(scrollY / this.pageHeight)) | ||
100 | |||
101 | // Offset page | ||
102 | return page + (this.firstLoadedPage - 1) | ||
96 | } | 103 | } |
97 | } | 104 | } |
diff --git a/client/src/app/shared/video/video-miniature.component.html b/client/src/app/shared/video/video-miniature.component.html index 9cf3fb321..cfc483018 100644 --- a/client/src/app/shared/video/video-miniature.component.html +++ b/client/src/app/shared/video/video-miniature.component.html | |||
@@ -1,11 +1,11 @@ | |||
1 | <div class="video-miniature"> | 1 | <div class="video-miniature"> |
2 | <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur()"></my-video-thumbnail> | 2 | <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail> |
3 | 3 | ||
4 | <div class="video-miniature-information"> | 4 | <div class="video-miniature-information"> |
5 | <a | 5 | <a |
6 | tabindex="-1" | 6 | tabindex="-1" |
7 | class="video-miniature-name" | 7 | class="video-miniature-name" |
8 | [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }" | 8 | [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur }" |
9 | > | 9 | > |
10 | {{ video.name }} | 10 | {{ video.name }} |
11 | </a> | 11 | </a> |
diff --git a/client/src/app/shared/video/video-miniature.component.ts b/client/src/app/shared/video/video-miniature.component.ts index 07193ebd5..27098f4b4 100644 --- a/client/src/app/shared/video/video-miniature.component.ts +++ b/client/src/app/shared/video/video-miniature.component.ts | |||
@@ -1,4 +1,4 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core' |
2 | import { User } from '../users' | 2 | import { User } from '../users' |
3 | import { Video } from './video.model' | 3 | import { Video } from './video.model' |
4 | import { ServerService } from '@app/core' | 4 | import { ServerService } from '@app/core' |
@@ -8,13 +8,16 @@ export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto' | |||
8 | @Component({ | 8 | @Component({ |
9 | selector: 'my-video-miniature', | 9 | selector: 'my-video-miniature', |
10 | styleUrls: [ './video-miniature.component.scss' ], | 10 | styleUrls: [ './video-miniature.component.scss' ], |
11 | templateUrl: './video-miniature.component.html' | 11 | templateUrl: './video-miniature.component.html', |
12 | changeDetection: ChangeDetectionStrategy.OnPush | ||
12 | }) | 13 | }) |
13 | export class VideoMiniatureComponent implements OnInit { | 14 | export class VideoMiniatureComponent implements OnInit { |
14 | @Input() user: User | 15 | @Input() user: User |
15 | @Input() video: Video | 16 | @Input() video: Video |
16 | @Input() ownerDisplayType: OwnerDisplayType = 'account' | 17 | @Input() ownerDisplayType: OwnerDisplayType = 'account' |
17 | 18 | ||
19 | isVideoBlur: boolean | ||
20 | |||
18 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' | 21 | private ownerDisplayTypeChosen: 'account' | 'videoChannel' |
19 | 22 | ||
20 | constructor (private serverService: ServerService) { } | 23 | constructor (private serverService: ServerService) { } |
@@ -35,10 +38,8 @@ export class VideoMiniatureComponent implements OnInit { | |||
35 | } else { | 38 | } else { |
36 | this.ownerDisplayTypeChosen = 'videoChannel' | 39 | this.ownerDisplayTypeChosen = 'videoChannel' |
37 | } | 40 | } |
38 | } | ||
39 | 41 | ||
40 | isVideoBlur () { | 42 | this.isVideoBlur = this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) |
41 | return this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig()) | ||
42 | } | 43 | } |
43 | 44 | ||
44 | displayOwnerAccount () { | 45 | displayOwnerAccount () { |