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