Merge branch 'release/v1.3.0' into develop
[oweals/peertube.git] / client / src / app / shared / video / abstract-video-list.ts
1 import { debounceTime } from 'rxjs/operators'
2 import { OnDestroy, OnInit } from '@angular/core'
3 import { ActivatedRoute, Router } from '@angular/router'
4 import { fromEvent, Observable, Subscription } from 'rxjs'
5 import { AuthService } from '../../core/auth'
6 import { ComponentPagination } from '../rest/component-pagination.model'
7 import { VideoSortField } from './sort-field.type'
8 import { Video } from './video.model'
9 import { ScreenService } from '@app/shared/misc/screen.service'
10 import { MiniatureDisplayOptions, OwnerDisplayType } from '@app/shared/video/video-miniature.component'
11 import { Syndication } from '@app/shared/video/syndication.model'
12 import { Notifier, ServerService } from '@app/core'
13 import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook'
14 import { I18n } from '@ngx-translate/i18n-polyfill'
15 import { isThisMonth, isThisWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date'
16
17 enum GroupDate {
18   UNKNOWN = 0,
19   TODAY = 1,
20   YESTERDAY = 2,
21   THIS_WEEK = 3,
22   THIS_MONTH = 4,
23   OLDER = 5
24 }
25
26 export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook {
27   pagination: ComponentPagination = {
28     currentPage: 1,
29     itemsPerPage: 25,
30     totalItems: null
31   }
32   sort: VideoSortField = '-publishedAt'
33
34   categoryOneOf?: number
35   defaultSort: VideoSortField = '-publishedAt'
36
37   syndicationItems: Syndication[] = []
38
39   loadOnInit = true
40   videos: Video[] = []
41   ownerDisplayType: OwnerDisplayType = 'account'
42   displayModerationBlock = false
43   titleTooltip: string
44   displayVideoActions = true
45   groupByDate = false
46
47   disabled = false
48
49   displayOptions: MiniatureDisplayOptions = {
50     date: true,
51     views: true,
52     by: true,
53     privacyLabel: true,
54     privacyText: false,
55     state: false,
56     blacklistInfo: false
57   }
58
59   protected abstract notifier: Notifier
60   protected abstract authService: AuthService
61   protected abstract route: ActivatedRoute
62   protected abstract serverService: ServerService
63   protected abstract screenService: ScreenService
64   protected abstract router: Router
65   protected abstract i18n: I18n
66   abstract titlePage: string
67
68   private resizeSubscription: Subscription
69   private angularState: number
70
71   private groupedDateLabels: { [id in GroupDate]: string }
72   private groupedDates: { [id: number]: GroupDate } = {}
73
74   abstract getVideosObservable (page: number): Observable<{ videos: Video[], totalVideos: number }>
75
76   abstract generateSyndicationList (): void
77
78   get user () {
79     return this.authService.getUser()
80   }
81
82   ngOnInit () {
83     this.groupedDateLabels = {
84       [GroupDate.UNKNOWN]: null,
85       [GroupDate.TODAY]: this.i18n('Today'),
86       [GroupDate.YESTERDAY]: this.i18n('Yesterday'),
87       [GroupDate.THIS_WEEK]: this.i18n('This week'),
88       [GroupDate.THIS_MONTH]: this.i18n('This month'),
89       [GroupDate.OLDER]: this.i18n('Older')
90     }
91
92     // Subscribe to route changes
93     const routeParams = this.route.snapshot.queryParams
94     this.loadRouteParams(routeParams)
95
96     this.resizeSubscription = fromEvent(window, 'resize')
97       .pipe(debounceTime(500))
98       .subscribe(() => this.calcPageSizes())
99
100     this.calcPageSizes()
101     if (this.loadOnInit === true) this.loadMoreVideos()
102   }
103
104   ngOnDestroy () {
105     if (this.resizeSubscription) this.resizeSubscription.unsubscribe()
106   }
107
108   disableForReuse () {
109     this.disabled = true
110   }
111
112   enabledForReuse () {
113     this.disabled = false
114   }
115
116   videoById (index: number, video: Video) {
117     return video.id
118   }
119
120   onNearOfBottom () {
121     if (this.disabled) return
122
123     // Last page
124     if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
125
126     this.pagination.currentPage += 1
127
128     this.setScrollRouteParams()
129
130     this.loadMoreVideos()
131   }
132
133   loadMoreVideos () {
134     const observable = this.getVideosObservable(this.pagination.currentPage)
135
136     observable.subscribe(
137       ({ videos, totalVideos }) => {
138         this.pagination.totalItems = totalVideos
139         this.videos = this.videos.concat(videos)
140
141         if (this.groupByDate) this.buildGroupedDateLabels()
142
143         this.onMoreVideos()
144       },
145
146       error => this.notifier.error(error.message)
147     )
148   }
149
150   reloadVideos () {
151     this.pagination.currentPage = 1
152     this.videos = []
153     this.loadMoreVideos()
154   }
155
156   toggleModerationDisplay () {
157     throw new Error('toggleModerationDisplay is not implemented')
158   }
159
160   removeVideoFromArray (video: Video) {
161     this.videos = this.videos.filter(v => v.id !== video.id)
162   }
163
164   buildGroupedDateLabels () {
165     let currentGroupedDate: GroupDate = GroupDate.UNKNOWN
166
167     for (const video of this.videos) {
168       const publishedDate = video.publishedAt
169
170       if (currentGroupedDate < GroupDate.TODAY && isToday(publishedDate)) {
171         currentGroupedDate = GroupDate.TODAY
172         this.groupedDates[ video.id ] = currentGroupedDate
173         continue
174       }
175
176       if (currentGroupedDate < GroupDate.YESTERDAY && isYesterday(publishedDate)) {
177         currentGroupedDate = GroupDate.YESTERDAY
178         this.groupedDates[ video.id ] = currentGroupedDate
179         continue
180       }
181
182       if (currentGroupedDate < GroupDate.THIS_WEEK && isThisWeek(publishedDate)) {
183         currentGroupedDate = GroupDate.THIS_WEEK
184         this.groupedDates[ video.id ] = currentGroupedDate
185         continue
186       }
187
188       if (currentGroupedDate < GroupDate.THIS_MONTH && isThisMonth(publishedDate)) {
189         currentGroupedDate = GroupDate.THIS_MONTH
190         this.groupedDates[ video.id ] = currentGroupedDate
191         continue
192       }
193
194       if (currentGroupedDate < GroupDate.OLDER) {
195         currentGroupedDate = GroupDate.OLDER
196         this.groupedDates[ video.id ] = currentGroupedDate
197       }
198     }
199   }
200
201   getCurrentGroupedDateLabel (video: Video) {
202     if (this.groupByDate === false) return undefined
203
204     return this.groupedDateLabels[this.groupedDates[video.id]]
205   }
206
207   // On videos hook for children that want to do something
208   protected onMoreVideos () { /* empty */ }
209
210   protected loadRouteParams (routeParams: { [ key: string ]: any }) {
211     this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort
212     this.categoryOneOf = routeParams[ 'categoryOneOf' ]
213     this.angularState = routeParams[ 'a-state' ]
214   }
215
216   private calcPageSizes () {
217     if (this.screenService.isInMobileView()) {
218       this.pagination.itemsPerPage = 5
219     }
220   }
221
222   private setScrollRouteParams () {
223     // Already set
224     if (this.angularState) return
225
226     this.angularState = 42
227
228     const queryParams = {
229       'a-state': this.angularState,
230       categoryOneOf: this.categoryOneOf
231     }
232
233     let path = this.router.url
234     if (!path || path === '/') path = this.serverService.getConfig().instance.defaultClientRoute
235
236     this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' })
237   }
238 }