]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blob - client/src/app/shared/shared-video-playlist/video-add-to-playlist.component.ts
Add ability to filter menu links
[github/Chocobozzz/PeerTube.git] / client / src / app / shared / shared-video-playlist / video-add-to-playlist.component.ts
1 import * as debug from 'debug'
2 import { Subject, Subscription } from 'rxjs'
3 import { debounceTime, filter } from 'rxjs/operators'
4 import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'
5 import { AuthService, DisableForReuseHook, Notifier } from '@app/core'
6 import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
7 import {
8 Video,
9 VideoExistInPlaylist,
10 VideoPlaylistCreate,
11 VideoPlaylistElementCreate,
12 VideoPlaylistElementUpdate,
13 VideoPlaylistPrivacy
14 } from '@shared/models'
15 import { secondsToTime } from '../../../assets/player/utils'
16 import { VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR } from '../form-validators/video-playlist-validators'
17 import { CachedPlaylist, VideoPlaylistService } from './video-playlist.service'
18
19 const logger = debug('peertube:playlists:VideoAddToPlaylistComponent')
20
21 type PlaylistElement = {
22 enabled: boolean
23 playlistElementId?: number
24 startTimestamp?: number
25 stopTimestamp?: number
26 }
27
28 type PlaylistSummary = {
29 id: number
30 displayName: string
31 optionalRowDisplayed: boolean
32
33 elements: PlaylistElement[]
34 }
35
36 @Component({
37 selector: 'my-video-add-to-playlist',
38 styleUrls: [ './video-add-to-playlist.component.scss' ],
39 templateUrl: './video-add-to-playlist.component.html',
40 changeDetection: ChangeDetectionStrategy.OnPush
41 })
42 export class VideoAddToPlaylistComponent extends FormReactive implements OnInit, OnChanges, OnDestroy, DisableForReuseHook {
43 @Input() video: Video
44 @Input() currentVideoTimestamp: number
45 @Input() lazyLoad = false
46
47 isNewPlaylistBlockOpened = false
48
49 videoPlaylistSearch: string
50 videoPlaylistSearchChanged = new Subject<string>()
51
52 videoPlaylists: PlaylistSummary[] = []
53
54 private disabled = false
55
56 private listenToPlaylistChangeSub: Subscription
57 private playlistsData: CachedPlaylist[] = []
58
59 constructor (
60 protected formValidatorService: FormValidatorService,
61 private authService: AuthService,
62 private notifier: Notifier,
63 private videoPlaylistService: VideoPlaylistService,
64 private cd: ChangeDetectorRef
65 ) {
66 super()
67 }
68
69 get user () {
70 return this.authService.getUser()
71 }
72
73 ngOnInit () {
74 this.buildForm({
75 displayName: VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR
76 })
77
78 this.videoPlaylistService.listenToMyAccountPlaylistsChange()
79 .subscribe(result => {
80 this.playlistsData = result.data
81
82 this.videoPlaylistService.runPlaylistCheck(this.video.id)
83 })
84
85 this.videoPlaylistSearchChanged
86 .pipe(debounceTime(500))
87 .subscribe(() => this.load())
88
89 if (this.lazyLoad === false) this.load()
90 }
91
92 ngOnChanges (simpleChanges: SimpleChanges) {
93 if (simpleChanges['video']) {
94 this.reload()
95 }
96 }
97
98 ngOnDestroy () {
99 this.unsubscribePlaylistChanges()
100 }
101
102 disableForReuse () {
103 this.disabled = true
104 }
105
106 enabledForReuse () {
107 this.disabled = false
108 }
109
110 reload () {
111 logger('Reloading component')
112
113 this.videoPlaylists = []
114 this.videoPlaylistSearch = undefined
115
116 this.load()
117
118 this.cd.markForCheck()
119 }
120
121 load () {
122 logger('Loading component')
123
124 this.listenToVideoPlaylistChange()
125
126 this.videoPlaylistService.listMyPlaylistWithCache(this.user, this.videoPlaylistSearch)
127 .subscribe(playlistsResult => {
128 this.playlistsData = playlistsResult.data
129
130 this.videoPlaylistService.runPlaylistCheck(this.video.id)
131 })
132 }
133
134 openChange (opened: boolean) {
135 if (opened === false) {
136 this.isNewPlaylistBlockOpened = false
137 }
138 }
139
140 openCreateBlock (event: Event) {
141 event.preventDefault()
142
143 this.isNewPlaylistBlockOpened = true
144 }
145
146 toggleMainPlaylist (e: Event, playlist: PlaylistSummary) {
147 e.preventDefault()
148
149 if (this.isPresentMultipleTimes(playlist) || playlist.optionalRowDisplayed) return
150
151 if (playlist.elements.length === 0) {
152 const element: PlaylistElement = {
153 enabled: true,
154 playlistElementId: undefined,
155 startTimestamp: 0,
156 stopTimestamp: this.video.duration
157 }
158
159 this.addVideoInPlaylist(playlist, element)
160 } else {
161 this.removeVideoFromPlaylist(playlist, playlist.elements[0].playlistElementId)
162 playlist.elements = []
163 }
164
165 this.cd.markForCheck()
166 }
167
168 toggleOptionalPlaylist (e: Event, playlist: PlaylistSummary, element: PlaylistElement, startTimestamp: number, stopTimestamp: number) {
169 e.preventDefault()
170
171 if (element.enabled) {
172 this.removeVideoFromPlaylist(playlist, element.playlistElementId)
173 element.enabled = false
174
175 // Hide optional rows pane when the user unchecked all the playlists
176 if (this.isPrimaryCheckboxChecked(playlist) === false) {
177 playlist.optionalRowDisplayed = false
178 }
179 } else {
180 const element: PlaylistElement = {
181 enabled: true,
182 playlistElementId: undefined,
183 startTimestamp,
184 stopTimestamp
185 }
186
187 this.addVideoInPlaylist(playlist, element)
188 }
189
190 this.cd.markForCheck()
191 }
192
193 createPlaylist () {
194 const displayName = this.form.value[ 'displayName' ]
195
196 const videoPlaylistCreate: VideoPlaylistCreate = {
197 displayName,
198 privacy: VideoPlaylistPrivacy.PRIVATE
199 }
200
201 this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe(
202 () => {
203 this.isNewPlaylistBlockOpened = false
204
205 this.cd.markForCheck()
206 },
207
208 err => this.notifier.error(err.message)
209 )
210 }
211
212 onVideoPlaylistSearchChanged () {
213 this.videoPlaylistSearchChanged.next()
214 }
215
216 isPrimaryCheckboxChecked (playlist: PlaylistSummary) {
217 return playlist.elements.filter(e => e.enabled)
218 .length !== 0
219 }
220
221 toggleOptionalRow (playlist: PlaylistSummary) {
222 playlist.optionalRowDisplayed = !playlist.optionalRowDisplayed
223
224 this.cd.markForCheck()
225 }
226
227 getPrimaryInputName (playlist: PlaylistSummary) {
228 return 'in-playlist-primary-' + playlist.id
229 }
230
231 getOptionalInputName (playlist: PlaylistSummary, element?: PlaylistElement) {
232 const suffix = element
233 ? '-' + element.playlistElementId
234 : ''
235
236 return 'in-playlist-optional-' + playlist.id + suffix
237 }
238
239 buildOptionalRowElements (playlist: PlaylistSummary) {
240 const elements = playlist.elements
241
242 const lastElement = elements.length === 0
243 ? undefined
244 : elements[elements.length - 1]
245
246 // Build an empty last element
247 if (!lastElement || lastElement.enabled === true) {
248 elements.push({
249 enabled: false,
250 startTimestamp: 0,
251 stopTimestamp: this.video.duration
252 })
253 }
254
255 return elements
256 }
257
258 isPresentMultipleTimes (playlist: PlaylistSummary) {
259 return playlist.elements.filter(e => e.enabled === true).length > 1
260 }
261
262 onElementTimestampUpdate (playlist: PlaylistSummary, element: PlaylistElement) {
263 if (!element.playlistElementId || element.enabled === false) return
264
265 const body: VideoPlaylistElementUpdate = {
266 startTimestamp: element.startTimestamp,
267 stopTimestamp: element.stopTimestamp
268 }
269
270 this.videoPlaylistService.updateVideoOfPlaylist(playlist.id, element.playlistElementId, body, this.video.id)
271 .subscribe(
272 () => {
273 this.notifier.success($localize`Timestamps updated`)
274 },
275
276 err => {
277 this.notifier.error(err.message)
278 },
279
280 () => this.cd.markForCheck()
281 )
282 }
283
284 private isOptionalRowDisplayed (playlist: PlaylistSummary) {
285 const elements = playlist.elements.filter(e => e.enabled)
286
287 if (elements.length > 1) return true
288
289 if (elements.length === 1) {
290 const element = elements[0]
291
292 if (
293 (element.startTimestamp && element.startTimestamp !== 0) ||
294 (element.stopTimestamp && element.stopTimestamp !== this.video.duration)
295 ) {
296 return true
297 }
298 }
299
300 return false
301 }
302
303 private removeVideoFromPlaylist (playlist: PlaylistSummary, elementId: number) {
304 this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, elementId, this.video.id)
305 .subscribe(
306 () => {
307 this.notifier.success($localize`Video removed from ${playlist.displayName}`)
308 },
309
310 err => {
311 this.notifier.error(err.message)
312 },
313
314 () => this.cd.markForCheck()
315 )
316 }
317
318 private listenToVideoPlaylistChange () {
319 this.unsubscribePlaylistChanges()
320
321 this.listenToPlaylistChangeSub = this.videoPlaylistService.listenToVideoPlaylistChange(this.video.id)
322 .pipe(filter(() => this.disabled === false))
323 .subscribe(existResult => this.rebuildPlaylists(existResult))
324 }
325
326 private unsubscribePlaylistChanges () {
327 if (this.listenToPlaylistChangeSub) {
328 this.listenToPlaylistChangeSub.unsubscribe()
329 this.listenToPlaylistChangeSub = undefined
330 }
331 }
332
333 private rebuildPlaylists (existResult: VideoExistInPlaylist[]) {
334 logger('Got existing results for %d.', this.video.id, existResult)
335
336 const oldPlaylists = this.videoPlaylists
337
338 this.videoPlaylists = []
339 for (const playlist of this.playlistsData) {
340 const existingPlaylists = existResult.filter(p => p.playlistId === playlist.id)
341
342 const playlistSummary = {
343 id: playlist.id,
344 optionalRowDisplayed: false,
345 displayName: playlist.displayName,
346 elements: existingPlaylists.map(e => ({
347 enabled: true,
348 playlistElementId: e.playlistElementId,
349 startTimestamp: e.startTimestamp || 0,
350 stopTimestamp: e.stopTimestamp || this.video.duration
351 }))
352 }
353
354 const oldPlaylist = oldPlaylists.find(p => p.id === playlist.id)
355 playlistSummary.optionalRowDisplayed = oldPlaylist
356 ? oldPlaylist.optionalRowDisplayed
357 : this.isOptionalRowDisplayed(playlistSummary)
358
359 this.videoPlaylists.push(playlistSummary)
360 }
361
362 logger('Rebuilt playlist state for video %d.', this.video.id, this.videoPlaylists)
363
364 this.cd.markForCheck()
365 }
366
367 private addVideoInPlaylist (playlist: PlaylistSummary, element: PlaylistElement) {
368 const body: VideoPlaylistElementCreate = { videoId: this.video.id }
369
370 if (element.startTimestamp) body.startTimestamp = element.startTimestamp
371 if (element.stopTimestamp && element.stopTimestamp !== this.video.duration) body.stopTimestamp = element.stopTimestamp
372
373 this.videoPlaylistService.addVideoInPlaylist(playlist.id, body)
374 .subscribe(
375 res => {
376 const message = body.startTimestamp || body.stopTimestamp
377 ? $localize`Video added in ${playlist.displayName} at timestamps ${this.formatTimestamp(element)}`
378 : $localize`Video added in ${playlist.displayName}`
379
380 this.notifier.success(message)
381
382 if (element) element.playlistElementId = res.videoPlaylistElement.id
383 },
384
385 err => {
386 this.notifier.error(err.message)
387 },
388
389 () => this.cd.markForCheck()
390 )
391 }
392
393 private formatTimestamp (element: PlaylistElement) {
394 const start = element.startTimestamp ? secondsToTime(element.startTimestamp) : ''
395 const stop = element.stopTimestamp ? secondsToTime(element.stopTimestamp) : ''
396
397 return `(${start}-${stop})`
398 }
399 }