diff options
Diffstat (limited to 'client/src/app/shared/shared-video-miniature/abstract-video-list.ts')
-rw-r--r-- | client/src/app/shared/shared-video-miniature/abstract-video-list.ts | 310 |
1 files changed, 310 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-video-miniature/abstract-video-list.ts b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts new file mode 100644 index 000000000..0ef842652 --- /dev/null +++ b/client/src/app/shared/shared-video-miniature/abstract-video-list.ts | |||
@@ -0,0 +1,310 @@ | |||
1 | import { fromEvent, Observable, Subject, Subscription } from 'rxjs' | ||
2 | import { debounceTime, switchMap, tap } from 'rxjs/operators' | ||
3 | import { OnDestroy, OnInit } from '@angular/core' | ||
4 | import { ActivatedRoute, Router } from '@angular/router' | ||
5 | import { | ||
6 | AuthService, | ||
7 | ComponentPaginationLight, | ||
8 | LocalStorageService, | ||
9 | Notifier, | ||
10 | ScreenService, | ||
11 | ServerService, | ||
12 | User, | ||
13 | UserService | ||
14 | } from '@app/core' | ||
15 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | ||
16 | import { GlobalIconName } from '@app/shared/shared-icons' | ||
17 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
18 | import { isLastMonth, isLastWeek, isToday, isYesterday } from '@shared/core-utils/miscs/date' | ||
19 | import { ServerConfig, VideoSortField } from '@shared/models' | ||
20 | import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type' | ||
21 | import { Syndication, Video } from '../shared-main' | ||
22 | import { MiniatureDisplayOptions, OwnerDisplayType } from './video-miniature.component' | ||
23 | |||
24 | enum GroupDate { | ||
25 | UNKNOWN = 0, | ||
26 | TODAY = 1, | ||
27 | YESTERDAY = 2, | ||
28 | LAST_WEEK = 3, | ||
29 | LAST_MONTH = 4, | ||
30 | OLDER = 5 | ||
31 | } | ||
32 | |||
33 | export abstract class AbstractVideoList implements OnInit, OnDestroy, DisableForReuseHook { | ||
34 | pagination: ComponentPaginationLight = { | ||
35 | currentPage: 1, | ||
36 | itemsPerPage: 25 | ||
37 | } | ||
38 | sort: VideoSortField = '-publishedAt' | ||
39 | |||
40 | categoryOneOf?: number[] | ||
41 | languageOneOf?: string[] | ||
42 | nsfwPolicy?: NSFWPolicyType | ||
43 | defaultSort: VideoSortField = '-publishedAt' | ||
44 | |||
45 | syndicationItems: Syndication[] = [] | ||
46 | |||
47 | loadOnInit = true | ||
48 | useUserVideoPreferences = false | ||
49 | ownerDisplayType: OwnerDisplayType = 'account' | ||
50 | displayModerationBlock = false | ||
51 | titleTooltip: string | ||
52 | displayVideoActions = true | ||
53 | groupByDate = false | ||
54 | |||
55 | videos: Video[] = [] | ||
56 | hasDoneFirstQuery = false | ||
57 | disabled = false | ||
58 | |||
59 | displayOptions: MiniatureDisplayOptions = { | ||
60 | date: true, | ||
61 | views: true, | ||
62 | by: true, | ||
63 | avatar: false, | ||
64 | privacyLabel: true, | ||
65 | privacyText: false, | ||
66 | state: false, | ||
67 | blacklistInfo: false | ||
68 | } | ||
69 | |||
70 | actions: { | ||
71 | routerLink: string | ||
72 | iconName: GlobalIconName | ||
73 | label: string | ||
74 | }[] = [] | ||
75 | |||
76 | onDataSubject = new Subject<any[]>() | ||
77 | |||
78 | userMiniature: User | ||
79 | |||
80 | protected serverConfig: ServerConfig | ||
81 | |||
82 | protected abstract notifier: Notifier | ||
83 | protected abstract authService: AuthService | ||
84 | protected abstract userService: UserService | ||
85 | protected abstract route: ActivatedRoute | ||
86 | protected abstract serverService: ServerService | ||
87 | protected abstract screenService: ScreenService | ||
88 | protected abstract storageService: LocalStorageService | ||
89 | protected abstract router: Router | ||
90 | protected abstract i18n: I18n | ||
91 | abstract titlePage: string | ||
92 | |||
93 | private resizeSubscription: Subscription | ||
94 | private angularState: number | ||
95 | |||
96 | private groupedDateLabels: { [id in GroupDate]: string } | ||
97 | private groupedDates: { [id: number]: GroupDate } = {} | ||
98 | |||
99 | private lastQueryLength: number | ||
100 | |||
101 | abstract getVideosObservable (page: number): Observable<{ data: Video[] }> | ||
102 | |||
103 | abstract generateSyndicationList (): void | ||
104 | |||
105 | ngOnInit () { | ||
106 | this.serverConfig = this.serverService.getTmpConfig() | ||
107 | this.serverService.getConfig() | ||
108 | .subscribe(config => this.serverConfig = config) | ||
109 | |||
110 | this.groupedDateLabels = { | ||
111 | [GroupDate.UNKNOWN]: null, | ||
112 | [GroupDate.TODAY]: this.i18n('Today'), | ||
113 | [GroupDate.YESTERDAY]: this.i18n('Yesterday'), | ||
114 | [GroupDate.LAST_WEEK]: this.i18n('Last week'), | ||
115 | [GroupDate.LAST_MONTH]: this.i18n('Last month'), | ||
116 | [GroupDate.OLDER]: this.i18n('Older') | ||
117 | } | ||
118 | |||
119 | // Subscribe to route changes | ||
120 | const routeParams = this.route.snapshot.queryParams | ||
121 | this.loadRouteParams(routeParams) | ||
122 | |||
123 | this.resizeSubscription = fromEvent(window, 'resize') | ||
124 | .pipe(debounceTime(500)) | ||
125 | .subscribe(() => this.calcPageSizes()) | ||
126 | |||
127 | this.calcPageSizes() | ||
128 | |||
129 | const loadUserObservable = this.loadUserAndSettings() | ||
130 | |||
131 | if (this.loadOnInit === true) { | ||
132 | loadUserObservable.subscribe(() => this.loadMoreVideos()) | ||
133 | } | ||
134 | |||
135 | this.userService.listenAnonymousUpdate() | ||
136 | .pipe(switchMap(() => this.loadUserAndSettings())) | ||
137 | .subscribe(() => { | ||
138 | if (this.hasDoneFirstQuery) this.reloadVideos() | ||
139 | }) | ||
140 | |||
141 | // Display avatar in mobile view | ||
142 | if (this.screenService.isInMobileView()) { | ||
143 | this.displayOptions.avatar = true | ||
144 | } | ||
145 | } | ||
146 | |||
147 | ngOnDestroy () { | ||
148 | if (this.resizeSubscription) this.resizeSubscription.unsubscribe() | ||
149 | } | ||
150 | |||
151 | disableForReuse () { | ||
152 | this.disabled = true | ||
153 | } | ||
154 | |||
155 | enabledForReuse () { | ||
156 | this.disabled = false | ||
157 | } | ||
158 | |||
159 | videoById (index: number, video: Video) { | ||
160 | return video.id | ||
161 | } | ||
162 | |||
163 | onNearOfBottom () { | ||
164 | if (this.disabled) return | ||
165 | |||
166 | // No more results | ||
167 | if (this.lastQueryLength !== undefined && this.lastQueryLength < this.pagination.itemsPerPage) return | ||
168 | |||
169 | this.pagination.currentPage += 1 | ||
170 | |||
171 | this.setScrollRouteParams() | ||
172 | |||
173 | this.loadMoreVideos() | ||
174 | } | ||
175 | |||
176 | loadMoreVideos (reset = false) { | ||
177 | this.getVideosObservable(this.pagination.currentPage).subscribe( | ||
178 | ({ data }) => { | ||
179 | this.hasDoneFirstQuery = true | ||
180 | this.lastQueryLength = data.length | ||
181 | |||
182 | if (reset) this.videos = [] | ||
183 | this.videos = this.videos.concat(data) | ||
184 | |||
185 | if (this.groupByDate) this.buildGroupedDateLabels() | ||
186 | |||
187 | this.onMoreVideos() | ||
188 | |||
189 | this.onDataSubject.next(data) | ||
190 | }, | ||
191 | |||
192 | error => { | ||
193 | const message = this.i18n('Cannot load more videos. Try again later.') | ||
194 | |||
195 | console.error(message, { error }) | ||
196 | this.notifier.error(message) | ||
197 | } | ||
198 | ) | ||
199 | } | ||
200 | |||
201 | reloadVideos () { | ||
202 | this.pagination.currentPage = 1 | ||
203 | this.loadMoreVideos(true) | ||
204 | } | ||
205 | |||
206 | toggleModerationDisplay () { | ||
207 | throw new Error('toggleModerationDisplay is not implemented') | ||
208 | } | ||
209 | |||
210 | removeVideoFromArray (video: Video) { | ||
211 | this.videos = this.videos.filter(v => v.id !== video.id) | ||
212 | } | ||
213 | |||
214 | buildGroupedDateLabels () { | ||
215 | let currentGroupedDate: GroupDate = GroupDate.UNKNOWN | ||
216 | |||
217 | for (const video of this.videos) { | ||
218 | const publishedDate = video.publishedAt | ||
219 | |||
220 | if (currentGroupedDate <= GroupDate.TODAY && isToday(publishedDate)) { | ||
221 | if (currentGroupedDate === GroupDate.TODAY) continue | ||
222 | |||
223 | currentGroupedDate = GroupDate.TODAY | ||
224 | this.groupedDates[ video.id ] = currentGroupedDate | ||
225 | continue | ||
226 | } | ||
227 | |||
228 | if (currentGroupedDate <= GroupDate.YESTERDAY && isYesterday(publishedDate)) { | ||
229 | if (currentGroupedDate === GroupDate.YESTERDAY) continue | ||
230 | |||
231 | currentGroupedDate = GroupDate.YESTERDAY | ||
232 | this.groupedDates[ video.id ] = currentGroupedDate | ||
233 | continue | ||
234 | } | ||
235 | |||
236 | if (currentGroupedDate <= GroupDate.LAST_WEEK && isLastWeek(publishedDate)) { | ||
237 | if (currentGroupedDate === GroupDate.LAST_WEEK) continue | ||
238 | |||
239 | currentGroupedDate = GroupDate.LAST_WEEK | ||
240 | this.groupedDates[ video.id ] = currentGroupedDate | ||
241 | continue | ||
242 | } | ||
243 | |||
244 | if (currentGroupedDate <= GroupDate.LAST_MONTH && isLastMonth(publishedDate)) { | ||
245 | if (currentGroupedDate === GroupDate.LAST_MONTH) continue | ||
246 | |||
247 | currentGroupedDate = GroupDate.LAST_MONTH | ||
248 | this.groupedDates[ video.id ] = currentGroupedDate | ||
249 | continue | ||
250 | } | ||
251 | |||
252 | if (currentGroupedDate <= GroupDate.OLDER) { | ||
253 | if (currentGroupedDate === GroupDate.OLDER) continue | ||
254 | |||
255 | currentGroupedDate = GroupDate.OLDER | ||
256 | this.groupedDates[ video.id ] = currentGroupedDate | ||
257 | } | ||
258 | } | ||
259 | } | ||
260 | |||
261 | getCurrentGroupedDateLabel (video: Video) { | ||
262 | if (this.groupByDate === false) return undefined | ||
263 | |||
264 | return this.groupedDateLabels[this.groupedDates[video.id]] | ||
265 | } | ||
266 | |||
267 | // On videos hook for children that want to do something | ||
268 | protected onMoreVideos () { /* empty */ } | ||
269 | |||
270 | protected loadRouteParams (routeParams: { [ key: string ]: any }) { | ||
271 | this.sort = routeParams[ 'sort' ] as VideoSortField || this.defaultSort | ||
272 | this.categoryOneOf = routeParams[ 'categoryOneOf' ] | ||
273 | this.angularState = routeParams[ 'a-state' ] | ||
274 | } | ||
275 | |||
276 | private calcPageSizes () { | ||
277 | if (this.screenService.isInMobileView()) { | ||
278 | this.pagination.itemsPerPage = 5 | ||
279 | } | ||
280 | } | ||
281 | |||
282 | private setScrollRouteParams () { | ||
283 | // Already set | ||
284 | if (this.angularState) return | ||
285 | |||
286 | this.angularState = 42 | ||
287 | |||
288 | const queryParams = { | ||
289 | 'a-state': this.angularState, | ||
290 | categoryOneOf: this.categoryOneOf | ||
291 | } | ||
292 | |||
293 | let path = this.router.url | ||
294 | if (!path || path === '/') path = this.serverConfig.instance.defaultClientRoute | ||
295 | |||
296 | this.router.navigate([ path ], { queryParams, replaceUrl: true, queryParamsHandling: 'merge' }) | ||
297 | } | ||
298 | |||
299 | private loadUserAndSettings () { | ||
300 | return this.userService.getAnonymousOrLoggedUser() | ||
301 | .pipe(tap(user => { | ||
302 | this.userMiniature = user | ||
303 | |||
304 | if (!this.useUserVideoPreferences) return | ||
305 | |||
306 | this.languageOneOf = user.videoLanguages | ||
307 | this.nsfwPolicy = user.nsfwPolicy | ||
308 | })) | ||
309 | } | ||
310 | } | ||