-
-
-
-
-
-
-
-
-
{{ video.name }}
-
{{ video.createdAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
-
-
-
-
-
-
- Cancel
-
-
-
-
- Delete
-
+
diff --git a/client/src/app/account/account-videos/account-videos.component.scss b/client/src/app/account/account-videos/account-videos.component.scss
index 707bd66ad..449cc6af4 100644
--- a/client/src/app/account/account-videos/account-videos.component.scss
+++ b/client/src/app/account/account-videos/account-videos.component.scss
@@ -45,16 +45,13 @@
display: flex;
min-height: 130px;
padding-bottom: 20px;
+ margin-bottom: 20px;
+ border-bottom: 1px solid #C6C6C6;
&:first-child {
margin-top: 47px;
}
- &:not(:last-child) {
- margin-bottom: 20px;
- border-bottom: 1px solid #C6C6C6;
- }
-
.checkbox-container {
display: flex;
align-items: center;
diff --git a/client/src/app/account/account-videos/account-videos.component.ts b/client/src/app/account/account-videos/account-videos.component.ts
index bce135557..e9d044dbf 100644
--- a/client/src/app/account/account-videos/account-videos.component.ts
+++ b/client/src/app/account/account-videos/account-videos.component.ts
@@ -1,5 +1,7 @@
import { Component, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
+import { immutableAssign } from '@app/shared/misc/utils'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
import { NotificationsService } from 'angular2-notifications'
import 'rxjs/add/observable/from'
import 'rxjs/add/operator/concatAll'
@@ -19,7 +21,9 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
titlePage = 'My videos'
currentRoute = '/account/videos'
checkedVideos: { [ id: number ]: boolean } = {}
- pagination = {
+ videoHeight = 155
+ videoWidth = -1
+ pagination: ComponentPagination = {
currentPage: 1,
itemsPerPage: 10,
totalItems: null
@@ -46,8 +50,10 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
return Object.keys(this.checkedVideos).some(k => this.checkedVideos[k] === true)
}
- getVideosObservable () {
- return this.videoService.getMyVideos(this.pagination, this.sort)
+ getVideosObservable (page: number) {
+ const newPagination = immutableAssign(this.pagination, { currentPage: page })
+
+ return this.videoService.getMyVideos(newPagination, this.sort)
}
deleteSelectedVideos () {
@@ -71,9 +77,12 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
Observable.from(observables)
.concatAll()
.subscribe(
- res => this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`),
+ res => {
+ this.notificationsService.success('Success', `${toDeleteVideosIds.length} videos deleted.`)
+ this.buildVideoPages()
+ },
- err => this.notificationsService.error('Error', err.message)
+ err => this.notificationsService.error('Error', err.message)
)
}
)
@@ -89,6 +98,7 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
status => {
this.notificationsService.success('Success', `Video ${video.name} deleted.`)
this.spliceVideosById(video.id)
+ this.buildVideoPages()
},
error => this.notificationsService.error('Error', error.message)
@@ -98,7 +108,14 @@ export class AccountVideosComponent extends AbstractVideoList implements OnInit
}
private spliceVideosById (id: number) {
- const index = this.videos.findIndex(v => v.id === id)
- this.videos.splice(index, 1)
+ for (const key of Object.keys(this.loadedPages)) {
+ const videos = this.loadedPages[key]
+ const index = videos.findIndex(v => v.id === id)
+
+ if (index !== -1) {
+ videos.splice(index, 1)
+ return
+ }
+ }
}
}
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index 6620ac973..e6a697098 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -55,6 +55,10 @@ function dateToHuman (date: string) {
return datePipe.transform(date, 'medium')
}
+function immutableAssign
(target: A, source: B) {
+ return Object.assign({}, target, source)
+}
+
function isInSmallView () {
return window.innerWidth < 600
}
@@ -70,5 +74,6 @@ export {
getAbsoluteAPIUrl,
dateToHuman,
isInSmallView,
- isInMobileView
+ isInMobileView,
+ immutableAssign
}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index d8f98bdf6..330a0ba84 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -4,13 +4,13 @@ import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { RouterModule } from '@angular/router'
import { MarkdownTextareaComponent } from '@app/shared/forms/markdown-textarea.component'
+import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
import { MarkdownService } from '@app/videos/shared'
import { LoadingBarHttpClientModule } from '@ngx-loading-bar/http-client'
import { BsDropdownModule } from 'ngx-bootstrap/dropdown'
import { ModalModule } from 'ngx-bootstrap/modal'
import { TabsModule } from 'ngx-bootstrap/tabs'
-import { InfiniteScrollModule } from 'ngx-infinite-scroll'
import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
@@ -42,7 +42,6 @@ import { VideoService } from './video/video.service'
ModalModule.forRoot(),
PrimeSharedModule,
- InfiniteScrollModule,
NgPipesModule,
TabsModule.forRoot()
],
@@ -55,7 +54,8 @@ import { VideoService } from './video/video.service'
EditButtonComponent,
NumberFormatterPipe,
FromNowPipe,
- MarkdownTextareaComponent
+ MarkdownTextareaComponent,
+ InfiniteScrollerDirective
],
exports: [
@@ -70,7 +70,6 @@ import { VideoService } from './video/video.service'
BsDropdownModule,
ModalModule,
PrimeSharedModule,
- InfiniteScrollModule,
BytesPipe,
KeysPipe,
@@ -80,6 +79,7 @@ import { VideoService } from './video/video.service'
DeleteButtonComponent,
EditButtonComponent,
MarkdownTextareaComponent,
+ InfiniteScrollerDirective,
NumberFormatterPipe,
FromNowPipe
diff --git a/client/src/app/shared/video/abstract-video-list.html b/client/src/app/shared/video/abstract-video-list.html
index 60a2563b3..fb7f86852 100644
--- a/client/src/app/shared/video/abstract-video-list.html
+++ b/client/src/app/shared/video/abstract-video-list.html
@@ -6,17 +6,17 @@
No results.
diff --git a/client/src/app/shared/video/abstract-video-list.ts b/client/src/app/shared/video/abstract-video-list.ts
index a25fc532c..034d0d879 100644
--- a/client/src/app/shared/video/abstract-video-list.ts
+++ b/client/src/app/shared/video/abstract-video-list.ts
@@ -1,6 +1,7 @@
-import { OnInit } from '@angular/core'
+import { ElementRef, OnInit, ViewChild, ViewChildren } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
-import { isInMobileView, isInSmallView } from '@app/shared/misc/utils'
+import { isInMobileView } from '@app/shared/misc/utils'
+import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.directive'
import { NotificationsService } from 'angular2-notifications'
import { Observable } from 'rxjs/Observable'
import { AuthService } from '../../core/auth'
@@ -9,30 +10,35 @@ import { SortField } from './sort-field.type'
import { Video } from './video.model'
export abstract class AbstractVideoList implements OnInit {
+ private static LINES_PER_PAGE = 3
+
+ @ViewChild('videoElement') videosElement: ElementRef
+ @ViewChild(InfiniteScrollerDirective) infiniteScroller: InfiniteScrollerDirective
+
pagination: ComponentPagination = {
currentPage: 1,
- itemsPerPage: 25,
+ itemsPerPage: 10,
totalItems: null
}
sort: SortField = '-createdAt'
defaultSort: SortField = '-createdAt'
- videos: Video[] = []
loadOnInit = true
+ pageHeight: number
+ videoWidth = 215
+ videoHeight = 230
+ videoPages: Video[][]
protected abstract notificationsService: NotificationsService
protected abstract authService: AuthService
protected abstract router: Router
protected abstract route: ActivatedRoute
-
protected abstract currentRoute: string
-
abstract titlePage: string
- protected otherParams = {}
+ protected loadedPages: { [ id: number ]: Video[] } = {}
+ protected otherRouteParams = {}
- private loadedPages: { [ id: number ]: boolean } = {}
-
- abstract getVideosObservable (): Observable<{ videos: Video[], totalVideos: number}>
+ abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number}>
get user () {
return this.authService.getUser()
@@ -45,15 +51,26 @@ export abstract class AbstractVideoList implements OnInit {
if (isInMobileView()) {
this.pagination.itemsPerPage = 5
+ this.videoWidth = -1
}
- if (this.loadOnInit === true) this.loadMoreVideos('after')
+ if (this.videoWidth !== -1) {
+ const videosWidth = this.videosElement.nativeElement.offsetWidth
+ this.pagination.itemsPerPage = Math.floor(videosWidth / this.videoWidth) * AbstractVideoList.LINES_PER_PAGE
+ }
+
+ // Video takes all the width
+ if (this.videoWidth === -1) {
+ this.pageHeight = this.pagination.itemsPerPage * this.videoHeight
+ } else {
+ this.pageHeight = this.videoHeight * AbstractVideoList.LINES_PER_PAGE
+ }
+
+ if (this.loadOnInit === true) this.loadMoreVideos(this.pagination.currentPage)
}
onNearOfTop () {
- if (this.pagination.currentPage > 1) {
- this.previousPage()
- }
+ this.previousPage()
}
onNearOfBottom () {
@@ -62,16 +79,20 @@ export abstract class AbstractVideoList implements OnInit {
}
}
- reloadVideos () {
- this.videos = []
- this.loadedPages = {}
- this.loadMoreVideos('before')
+ onPageChanged (page: number) {
+ this.pagination.currentPage = page
+ this.setNewRouteParams()
}
- loadMoreVideos (where: 'before' | 'after') {
- if (this.loadedPages[this.pagination.currentPage] === true) return
+ reloadVideos () {
+ this.loadedPages = {}
+ this.loadMoreVideos(this.pagination.currentPage)
+ }
- const observable = this.getVideosObservable()
+ loadMoreVideos (page: number) {
+ if (this.loadedPages[page] !== undefined) return
+
+ const observable = this.getVideosObservable(page)
observable.subscribe(
({ videos, totalVideos }) => {
@@ -82,13 +103,14 @@ export abstract class AbstractVideoList implements OnInit {
return this.reloadVideos()
}
- this.loadedPages[this.pagination.currentPage] = true
+ this.loadedPages[page] = videos
+ this.buildVideoPages()
this.pagination.totalItems = totalVideos
- if (where === 'before') {
- this.videos = videos.concat(this.videos)
- } else {
- this.videos = this.videos.concat(videos)
+ // Initialize infinite scroller now we loaded the first page
+ if (Object.keys(this.loadedPages).length === 1) {
+ // Wait elements creation
+ setTimeout(() => this.infiniteScroller.initialize(), 500)
}
},
error => this.notificationsService.error('Error', error.message)
@@ -107,17 +129,15 @@ export abstract class AbstractVideoList implements OnInit {
}
protected previousPage () {
- this.pagination.currentPage--
+ const min = this.minPageLoaded()
- this.setNewRouteParams()
- this.loadMoreVideos('before')
+ if (min > 1) {
+ this.loadMoreVideos(min - 1)
+ }
}
protected nextPage () {
- this.pagination.currentPage++
-
- this.setNewRouteParams()
- this.loadMoreVideos('after')
+ this.loadMoreVideos(this.maxPageLoaded() + 1)
}
protected buildRouteParams () {
@@ -127,7 +147,7 @@ export abstract class AbstractVideoList implements OnInit {
page: this.pagination.currentPage
}
- return Object.assign(params, this.otherParams)
+ return Object.assign(params, this.otherRouteParams)
}
protected loadRouteParams (routeParams: { [ key: string ]: any }) {
@@ -144,4 +164,16 @@ export abstract class AbstractVideoList implements OnInit {
const routeParams = this.buildRouteParams()
this.router.navigate([ this.currentRoute, routeParams ])
}
+
+ protected buildVideoPages () {
+ this.videoPages = Object.values(this.loadedPages)
+ }
+
+ private minPageLoaded () {
+ return Math.min(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
+ }
+
+ private maxPageLoaded () {
+ return Math.max(...Object.keys(this.loadedPages).map(e => parseInt(e, 10)))
+ }
}
diff --git a/client/src/app/shared/video/infinite-scroller.directive.ts b/client/src/app/shared/video/infinite-scroller.directive.ts
new file mode 100644
index 000000000..43e014cbd
--- /dev/null
+++ b/client/src/app/shared/video/infinite-scroller.directive.ts
@@ -0,0 +1,77 @@
+import { Directive, EventEmitter, Input, OnInit, Output } from '@angular/core'
+import 'rxjs/add/operator/distinct'
+import 'rxjs/add/operator/startWith'
+import { fromEvent } from 'rxjs/observable/fromEvent'
+
+@Directive({
+ selector: '[myInfiniteScroller]'
+})
+export class InfiniteScrollerDirective implements OnInit {
+ private static PAGE_VIEW_TOP_MARGIN = 500
+
+ @Input() containerHeight: number
+ @Input() pageHeight: number
+ @Input() percentLimit = 70
+ @Input() autoLoading = false
+
+ @Output() nearOfBottom = new EventEmitter
()
+ @Output() nearOfTop = new EventEmitter()
+ @Output() pageChanged = new EventEmitter()
+
+ private decimalLimit = 0
+ private lastCurrentBottom = -1
+ private lastCurrentTop = 0
+
+ constructor () {
+ this.decimalLimit = this.percentLimit / 100
+ }
+
+ ngOnInit () {
+ if (this.autoLoading === true) return this.initialize()
+ }
+
+ initialize () {
+ const scrollObservable = fromEvent(window, 'scroll')
+ .startWith(true)
+ .map(() => ({ current: window.scrollY, maximumScroll: document.body.clientHeight - window.innerHeight }))
+
+ // Scroll Down
+ scrollObservable
+ // Check we scroll down
+ .filter(({ current }) => {
+ const res = this.lastCurrentBottom < current
+
+ this.lastCurrentBottom = current
+ return res
+ })
+ .filter(({ current, maximumScroll }) => maximumScroll <= 0 || (current / maximumScroll) > this.decimalLimit)
+ .debounceTime(200)
+ .distinct()
+ .subscribe(() => this.nearOfBottom.emit())
+
+ // Scroll up
+ scrollObservable
+ // Check we scroll up
+ .filter(({ current }) => {
+ const res = this.lastCurrentTop > current
+
+ this.lastCurrentTop = current
+ return res
+ })
+ .filter(({ current, maximumScroll }) => {
+ return current !== 0 && (1 - (current / maximumScroll)) > this.decimalLimit
+ })
+ .debounceTime(200)
+ .distinct()
+ .subscribe(() => this.nearOfTop.emit())
+
+ // Page change
+ scrollObservable
+ .debounceTime(500)
+ .distinct()
+ .map(({ current }) => Math.max(1, Math.round((current + InfiniteScrollerDirective.PAGE_VIEW_TOP_MARGIN) / this.pageHeight)))
+ .distinctUntilChanged()
+ .subscribe(res => this.pageChanged.emit(res))
+ }
+
+}
diff --git a/client/src/app/videos/+video-watch/comment/video-comments.component.html b/client/src/app/videos/+video-watch/comment/video-comments.component.html
index 80b200931..7f2e96e93 100644
--- a/client/src/app/videos/+video-watch/comment/video-comments.component.html
+++ b/client/src/app/videos/+video-watch/comment/video-comments.component.html
@@ -15,10 +15,9 @@