diff options
author | Chocobozzz <me@florianbigard.com> | 2019-03-07 17:06:00 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-03-18 11:17:59 +0100 |
commit | f0a3988066f72a28bb44520af072f18d91d77dde (patch) | |
tree | dfa3a92102557b567530c5dd014c90866621140a /client/src/app/shared/video-playlist | |
parent | 830b4faff15fb9c81d88e8e69fcdf94aad32bef8 (diff) | |
download | PeerTube-f0a3988066f72a28bb44520af072f18d91d77dde.tar.gz PeerTube-f0a3988066f72a28bb44520af072f18d91d77dde.tar.zst PeerTube-f0a3988066f72a28bb44520af072f18d91d77dde.zip |
Add to playlist dropdown
Diffstat (limited to 'client/src/app/shared/video-playlist')
8 files changed, 461 insertions, 13 deletions
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.html b/client/src/app/shared/video-playlist/video-add-to-playlist.component.html new file mode 100644 index 000000000..ed3cd8dc5 --- /dev/null +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.html | |||
@@ -0,0 +1,74 @@ | |||
1 | <div class="header"> | ||
2 | <div class="first-row"> | ||
3 | <div i18n class="title">Save to</div> | ||
4 | |||
5 | <div i18n class="options" (click)="displayOptions = !displayOptions"> | ||
6 | <my-global-icon iconName="cog"></my-global-icon> | ||
7 | |||
8 | Options | ||
9 | </div> | ||
10 | </div> | ||
11 | |||
12 | <div class="options-row" *ngIf="displayOptions"> | ||
13 | <div> | ||
14 | <my-peertube-checkbox | ||
15 | inputName="startAt" [(ngModel)]="timestampOptions.startTimestampEnabled" | ||
16 | i18n-labelText labelText="Start at" | ||
17 | ></my-peertube-checkbox> | ||
18 | |||
19 | <my-timestamp-input | ||
20 | [timestamp]="timestampOptions.startTimestamp" | ||
21 | [maxTimestamp]="video.duration" | ||
22 | [disabled]="!timestampOptions.startTimestampEnabled" | ||
23 | [(ngModel)]="timestampOptions.startTimestamp" | ||
24 | ></my-timestamp-input> | ||
25 | </div> | ||
26 | |||
27 | <div> | ||
28 | <my-peertube-checkbox | ||
29 | inputName="stopAt" [(ngModel)]="timestampOptions.stopTimestampEnabled" | ||
30 | i18n-labelText labelText="Stop at" | ||
31 | ></my-peertube-checkbox> | ||
32 | |||
33 | <my-timestamp-input | ||
34 | [timestamp]="timestampOptions.stopTimestamp" | ||
35 | [maxTimestamp]="video.duration" | ||
36 | [disabled]="!timestampOptions.stopTimestampEnabled" | ||
37 | [(ngModel)]="timestampOptions.stopTimestamp" | ||
38 | ></my-timestamp-input> | ||
39 | </div> | ||
40 | </div> | ||
41 | </div> | ||
42 | |||
43 | <div class="playlist dropdown-item" *ngFor="let playlist of videoPlaylists" (click)="togglePlaylist($event, playlist)"> | ||
44 | <my-peertube-checkbox [inputName]="'in-playlist-' + playlist.id" [(ngModel)]="playlist.inPlaylist"></my-peertube-checkbox> | ||
45 | |||
46 | <div class="display-name"> | ||
47 | {{ playlist.displayName }} | ||
48 | |||
49 | <div *ngIf="playlist.inPlaylist && (playlist.startTimestamp || playlist.stopTimestamp)" class="timestamp-info"> | ||
50 | {{ formatTimestamp(playlist) }} | ||
51 | </div> | ||
52 | </div> | ||
53 | </div> | ||
54 | |||
55 | <div class="new-playlist-button dropdown-item" (click)="openCreateBlock($event)" [hidden]="isNewPlaylistBlockOpened"> | ||
56 | <my-global-icon iconName="add"></my-global-icon> | ||
57 | |||
58 | Create a new playlist | ||
59 | </div> | ||
60 | |||
61 | <form class="new-playlist-block dropdown-item" *ngIf="isNewPlaylistBlockOpened" (ngSubmit)="createPlaylist()" [formGroup]="form"> | ||
62 | <div class="form-group"> | ||
63 | <label i18n for="display-name">Display name</label> | ||
64 | <input | ||
65 | type="text" id="display-name" | ||
66 | formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }" | ||
67 | > | ||
68 | <div *ngIf="formErrors['display-name']" class="form-error"> | ||
69 | {{ formErrors['display-name'] }} | ||
70 | </div> | ||
71 | </div> | ||
72 | |||
73 | <input type="submit" i18n-value value="Create" [disabled]="!form.valid"> | ||
74 | </form> | ||
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss b/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss new file mode 100644 index 000000000..68dcda1eb --- /dev/null +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.scss | |||
@@ -0,0 +1,98 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .header { | ||
5 | min-width: 240px; | ||
6 | padding: 6px 24px 10px 24px; | ||
7 | |||
8 | margin-bottom: 10px; | ||
9 | border-bottom: 1px solid $separator-border-color; | ||
10 | |||
11 | .first-row { | ||
12 | display: flex; | ||
13 | align-items: center; | ||
14 | |||
15 | .title { | ||
16 | font-size: 18px; | ||
17 | flex-grow: 1; | ||
18 | } | ||
19 | |||
20 | .options { | ||
21 | font-size: 14px; | ||
22 | cursor: pointer; | ||
23 | |||
24 | my-global-icon { | ||
25 | @include apply-svg-color(#333); | ||
26 | |||
27 | width: 16px; | ||
28 | height: 16px; | ||
29 | } | ||
30 | } | ||
31 | } | ||
32 | |||
33 | .options-row { | ||
34 | margin-top: 10px; | ||
35 | |||
36 | > div { | ||
37 | display: flex; | ||
38 | align-items: center; | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | |||
43 | .dropdown-item { | ||
44 | padding: 6px 24px; | ||
45 | } | ||
46 | |||
47 | .playlist { | ||
48 | display: flex; | ||
49 | cursor: pointer; | ||
50 | |||
51 | my-peertube-checkbox { | ||
52 | margin-right: 10px; | ||
53 | } | ||
54 | |||
55 | .display-name { | ||
56 | display: flex; | ||
57 | align-items: flex-end; | ||
58 | |||
59 | .timestamp-info { | ||
60 | font-size: 0.9em; | ||
61 | color: $grey-foreground-color; | ||
62 | margin-left: 5px; | ||
63 | } | ||
64 | } | ||
65 | } | ||
66 | |||
67 | .new-playlist-button, | ||
68 | .new-playlist-block { | ||
69 | padding-top: 10px; | ||
70 | margin-top: 10px; | ||
71 | border-top: 1px solid $separator-border-color; | ||
72 | } | ||
73 | |||
74 | .new-playlist-button { | ||
75 | cursor: pointer; | ||
76 | |||
77 | my-global-icon { | ||
78 | @include apply-svg-color(#333); | ||
79 | |||
80 | position: relative; | ||
81 | left: -1px; | ||
82 | top: -1px; | ||
83 | margin-right: 4px; | ||
84 | width: 21px; | ||
85 | height: 21px; | ||
86 | } | ||
87 | } | ||
88 | |||
89 | input[type=text] { | ||
90 | @include peertube-input-text(200px); | ||
91 | |||
92 | display: block; | ||
93 | } | ||
94 | |||
95 | input[type=submit] { | ||
96 | @include peertube-button; | ||
97 | @include orange-button; | ||
98 | } | ||
diff --git a/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts b/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts new file mode 100644 index 000000000..c6fb6dbed --- /dev/null +++ b/client/src/app/shared/video-playlist/video-add-to-playlist.component.ts | |||
@@ -0,0 +1,195 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
3 | import { AuthService, Notifier } from '@app/core' | ||
4 | import { forkJoin } from 'rxjs' | ||
5 | import { Video, VideoPlaylistCreate, VideoPlaylistElementCreate, VideoPlaylistPrivacy } from '@shared/models' | ||
6 | import { FormReactive, FormValidatorService, VideoPlaylistValidatorsService } from '@app/shared/forms' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
8 | import { secondsToTime, timeToInt } from '../../../assets/player/utils' | ||
9 | |||
10 | type PlaylistSummary = { | ||
11 | id: number | ||
12 | inPlaylist: boolean | ||
13 | displayName: string | ||
14 | |||
15 | startTimestamp?: number | ||
16 | stopTimestamp?: number | ||
17 | } | ||
18 | |||
19 | @Component({ | ||
20 | selector: 'my-video-add-to-playlist', | ||
21 | styleUrls: [ './video-add-to-playlist.component.scss' ], | ||
22 | templateUrl: './video-add-to-playlist.component.html' | ||
23 | }) | ||
24 | export class VideoAddToPlaylistComponent extends FormReactive implements OnInit { | ||
25 | @Input() video: Video | ||
26 | @Input() currentVideoTimestamp: number | ||
27 | |||
28 | isNewPlaylistBlockOpened = false | ||
29 | videoPlaylists: PlaylistSummary[] = [] | ||
30 | timestampOptions: { | ||
31 | startTimestampEnabled: boolean | ||
32 | startTimestamp: number | ||
33 | stopTimestampEnabled: boolean | ||
34 | stopTimestamp: number | ||
35 | } | ||
36 | displayOptions = false | ||
37 | |||
38 | constructor ( | ||
39 | protected formValidatorService: FormValidatorService, | ||
40 | private authService: AuthService, | ||
41 | private notifier: Notifier, | ||
42 | private i18n: I18n, | ||
43 | private videoPlaylistService: VideoPlaylistService, | ||
44 | private videoPlaylistValidatorsService: VideoPlaylistValidatorsService | ||
45 | ) { | ||
46 | super() | ||
47 | } | ||
48 | |||
49 | get user () { | ||
50 | return this.authService.getUser() | ||
51 | } | ||
52 | |||
53 | ngOnInit () { | ||
54 | this.resetOptions(true) | ||
55 | |||
56 | this.buildForm({ | ||
57 | 'display-name': this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME | ||
58 | }) | ||
59 | |||
60 | forkJoin([ | ||
61 | this.videoPlaylistService.listAccountPlaylists(this.user.account, '-updatedAt'), | ||
62 | this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id) | ||
63 | ]) | ||
64 | .subscribe( | ||
65 | ([ playlistsResult, existResult ]) => { | ||
66 | for (const playlist of playlistsResult.data) { | ||
67 | const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id) | ||
68 | |||
69 | this.videoPlaylists.push({ | ||
70 | id: playlist.id, | ||
71 | displayName: playlist.displayName, | ||
72 | inPlaylist: !!existingPlaylist, | ||
73 | startTimestamp: existingPlaylist ? existingPlaylist.startTimestamp : undefined, | ||
74 | stopTimestamp: existingPlaylist ? existingPlaylist.stopTimestamp : undefined | ||
75 | }) | ||
76 | } | ||
77 | } | ||
78 | ) | ||
79 | } | ||
80 | |||
81 | openChange (opened: boolean) { | ||
82 | if (opened === false) { | ||
83 | this.isNewPlaylistBlockOpened = false | ||
84 | this.displayOptions = false | ||
85 | } | ||
86 | } | ||
87 | |||
88 | openCreateBlock (event: Event) { | ||
89 | event.preventDefault() | ||
90 | |||
91 | this.isNewPlaylistBlockOpened = true | ||
92 | } | ||
93 | |||
94 | togglePlaylist (event: Event, playlist: PlaylistSummary) { | ||
95 | event.preventDefault() | ||
96 | |||
97 | if (playlist.inPlaylist === true) { | ||
98 | this.removeVideoFromPlaylist(playlist) | ||
99 | } else { | ||
100 | this.addVideoInPlaylist(playlist) | ||
101 | } | ||
102 | |||
103 | playlist.inPlaylist = !playlist.inPlaylist | ||
104 | this.resetOptions() | ||
105 | } | ||
106 | |||
107 | createPlaylist () { | ||
108 | const displayName = this.form.value[ 'display-name' ] | ||
109 | |||
110 | const videoPlaylistCreate: VideoPlaylistCreate = { | ||
111 | displayName, | ||
112 | privacy: VideoPlaylistPrivacy.PRIVATE | ||
113 | } | ||
114 | |||
115 | this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe( | ||
116 | res => { | ||
117 | this.videoPlaylists.push({ | ||
118 | id: res.videoPlaylist.id, | ||
119 | displayName, | ||
120 | inPlaylist: false | ||
121 | }) | ||
122 | |||
123 | this.isNewPlaylistBlockOpened = false | ||
124 | }, | ||
125 | |||
126 | err => this.notifier.error(err.message) | ||
127 | ) | ||
128 | } | ||
129 | |||
130 | resetOptions (resetTimestamp = false) { | ||
131 | this.displayOptions = false | ||
132 | |||
133 | this.timestampOptions = {} as any | ||
134 | this.timestampOptions.startTimestampEnabled = false | ||
135 | this.timestampOptions.stopTimestampEnabled = false | ||
136 | |||
137 | if (resetTimestamp) { | ||
138 | this.timestampOptions.startTimestamp = 0 | ||
139 | this.timestampOptions.stopTimestamp = this.video.duration | ||
140 | } | ||
141 | } | ||
142 | |||
143 | formatTimestamp (playlist: PlaylistSummary) { | ||
144 | const start = playlist.startTimestamp ? secondsToTime(playlist.startTimestamp) : '' | ||
145 | const stop = playlist.stopTimestamp ? secondsToTime(playlist.stopTimestamp) : '' | ||
146 | |||
147 | return `(${start}-${stop})` | ||
148 | } | ||
149 | |||
150 | private removeVideoFromPlaylist (playlist: PlaylistSummary) { | ||
151 | this.videoPlaylistService.removeVideoFromPlaylist(playlist.id, this.video.id) | ||
152 | .subscribe( | ||
153 | () => { | ||
154 | this.notifier.success(this.i18n('Video removed from {{name}}', { name: playlist.displayName })) | ||
155 | |||
156 | playlist.inPlaylist = false | ||
157 | }, | ||
158 | |||
159 | err => { | ||
160 | this.notifier.error(err.message) | ||
161 | |||
162 | playlist.inPlaylist = true | ||
163 | } | ||
164 | ) | ||
165 | } | ||
166 | |||
167 | private addVideoInPlaylist (playlist: PlaylistSummary) { | ||
168 | const body: VideoPlaylistElementCreate = { videoId: this.video.id } | ||
169 | |||
170 | if (this.timestampOptions.startTimestampEnabled) body.startTimestamp = this.timestampOptions.startTimestamp | ||
171 | if (this.timestampOptions.stopTimestampEnabled) body.stopTimestamp = this.timestampOptions.stopTimestamp | ||
172 | |||
173 | this.videoPlaylistService.addVideoInPlaylist(playlist.id, body) | ||
174 | .subscribe( | ||
175 | () => { | ||
176 | playlist.inPlaylist = true | ||
177 | |||
178 | playlist.startTimestamp = body.startTimestamp | ||
179 | playlist.stopTimestamp = body.stopTimestamp | ||
180 | |||
181 | const message = body.startTimestamp || body.stopTimestamp | ||
182 | ? this.i18n('Video added in {{n}} at timestamps {{t}}', { n: playlist.displayName, t: this.formatTimestamp(playlist) }) | ||
183 | : this.i18n('Video added in {{n}}', { n: playlist.displayName }) | ||
184 | |||
185 | this.notifier.success(message) | ||
186 | }, | ||
187 | |||
188 | err => { | ||
189 | this.notifier.error(err.message) | ||
190 | |||
191 | playlist.inPlaylist = false | ||
192 | } | ||
193 | ) | ||
194 | } | ||
195 | } | ||
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html b/client/src/app/shared/video-playlist/video-playlist-miniature.component.html index 1a39f5fe5..a136f9233 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.html +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.html | |||
@@ -1,6 +1,6 @@ | |||
1 | <div class="miniature"> | 1 | <div class="miniature" [ngClass]="{ 'no-videos': playlist.videosLength === 0, 'to-manage': toManage }"> |
2 | <a | 2 | <a |
3 | [routerLink]="[ '/videos/watch' ]" [attr.title]="playlist.displayName" | 3 | [routerLink]="getPlaylistUrl()" [attr.title]="playlist.displayName" |
4 | class="miniature-thumbnail" | 4 | class="miniature-thumbnail" |
5 | > | 5 | > |
6 | <img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" /> | 6 | <img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" /> |
@@ -15,7 +15,7 @@ | |||
15 | </a> | 15 | </a> |
16 | 16 | ||
17 | <div class="miniature-bottom"> | 17 | <div class="miniature-bottom"> |
18 | <a tabindex="-1" class="miniature-name" [routerLink]="[ '/videos/watch' ]" [attr.title]="playlist.displayName"> | 18 | <a tabindex="-1" class="miniature-name" [routerLink]="getPlaylistUrl()" [attr.title]="playlist.displayName"> |
19 | {{ playlist.displayName }} | 19 | {{ playlist.displayName }} |
20 | </a> | 20 | </a> |
21 | </div> | 21 | </div> |
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss index a47206577..f8cd47f73 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss | |||
@@ -5,6 +5,17 @@ | |||
5 | .miniature { | 5 | .miniature { |
6 | display: inline-block; | 6 | display: inline-block; |
7 | 7 | ||
8 | &.no-videos:not(.to-manage){ | ||
9 | a { | ||
10 | cursor: default !important; | ||
11 | } | ||
12 | } | ||
13 | |||
14 | &.to-manage .play-overlay, | ||
15 | &.no-videos { | ||
16 | display: none; | ||
17 | } | ||
18 | |||
8 | .miniature-thumbnail { | 19 | .miniature-thumbnail { |
9 | @include miniature-thumbnail; | 20 | @include miniature-thumbnail; |
10 | 21 | ||
diff --git a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts b/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts index b3bba7c87..cb5803400 100644 --- a/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts | |||
@@ -8,4 +8,12 @@ import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | |||
8 | }) | 8 | }) |
9 | export class VideoPlaylistMiniatureComponent { | 9 | export class VideoPlaylistMiniatureComponent { |
10 | @Input() playlist: VideoPlaylist | 10 | @Input() playlist: VideoPlaylist |
11 | @Input() toManage = false | ||
12 | |||
13 | getPlaylistUrl () { | ||
14 | if (this.toManage) return [ '/my-account/video-playlists', this.playlist.uuid ] | ||
15 | if (this.playlist.videosLength === 0) return null | ||
16 | |||
17 | return [ '/videos/watch/playlist', this.playlist.uuid ] | ||
18 | } | ||
11 | } | 19 | } |
diff --git a/client/src/app/shared/video-playlist/video-playlist.model.ts b/client/src/app/shared/video-playlist/video-playlist.model.ts index 9d0b02789..ec8013e89 100644 --- a/client/src/app/shared/video-playlist/video-playlist.model.ts +++ b/client/src/app/shared/video-playlist/video-playlist.model.ts | |||
@@ -46,6 +46,7 @@ export class VideoPlaylist implements ServerVideoPlaylist { | |||
46 | this.isLocal = hash.isLocal | 46 | this.isLocal = hash.isLocal |
47 | 47 | ||
48 | this.displayName = hash.displayName | 48 | this.displayName = hash.displayName |
49 | |||
49 | this.description = hash.description | 50 | this.description = hash.description |
50 | this.privacy = hash.privacy | 51 | this.privacy = hash.privacy |
51 | 52 | ||
@@ -70,5 +71,9 @@ export class VideoPlaylist implements ServerVideoPlaylist { | |||
70 | } | 71 | } |
71 | 72 | ||
72 | this.privacy.label = peertubeTranslate(this.privacy.label, translations) | 73 | this.privacy.label = peertubeTranslate(this.privacy.label, translations) |
74 | |||
75 | if (this.type.id === VideoPlaylistType.WATCH_LATER) { | ||
76 | this.displayName = peertubeTranslate(this.displayName, translations) | ||
77 | } | ||
73 | } | 78 | } |
74 | } | 79 | } |
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts index 8b66e122c..f7b37f83a 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts | |||
@@ -1,9 +1,9 @@ | |||
1 | import { catchError, map, switchMap } from 'rxjs/operators' | 1 | import { bufferTime, catchError, filter, first, map, share, switchMap } from 'rxjs/operators' |
2 | import { Injectable } from '@angular/core' | 2 | import { Injectable } from '@angular/core' |
3 | import { Observable } from 'rxjs' | 3 | import { Observable, ReplaySubject, Subject } from 'rxjs' |
4 | import { RestExtractor } from '../rest/rest-extractor.service' | 4 | import { RestExtractor } from '../rest/rest-extractor.service' |
5 | import { HttpClient } from '@angular/common/http' | 5 | import { HttpClient, HttpParams } from '@angular/common/http' |
6 | import { ResultList } from '../../../../../shared' | 6 | import { ResultList, VideoPlaylistElementCreate, VideoPlaylistElementUpdate } from '../../../../../shared' |
7 | import { environment } from '../../../environments/environment' | 7 | import { environment } from '../../../environments/environment' |
8 | import { VideoPlaylist as VideoPlaylistServerModel } from '@shared/models/videos/playlist/video-playlist.model' | 8 | import { VideoPlaylist as VideoPlaylistServerModel } from '@shared/models/videos/playlist/video-playlist.model' |
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | 9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' |
@@ -15,16 +15,31 @@ import { ServerService } from '@app/core' | |||
15 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | 15 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' |
16 | import { AccountService } from '@app/shared/account/account.service' | 16 | import { AccountService } from '@app/shared/account/account.service' |
17 | import { Account } from '@app/shared/account/account.model' | 17 | import { Account } from '@app/shared/account/account.model' |
18 | import { RestService } from '@app/shared/rest' | ||
19 | import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' | ||
18 | 20 | ||
19 | @Injectable() | 21 | @Injectable() |
20 | export class VideoPlaylistService { | 22 | export class VideoPlaylistService { |
21 | static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/' | 23 | static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/' |
24 | static MY_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/users/me/video-playlists/' | ||
25 | |||
26 | // Use a replay subject because we "next" a value before subscribing | ||
27 | private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1) | ||
28 | private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist> | ||
22 | 29 | ||
23 | constructor ( | 30 | constructor ( |
24 | private authHttp: HttpClient, | 31 | private authHttp: HttpClient, |
25 | private serverService: ServerService, | 32 | private serverService: ServerService, |
26 | private restExtractor: RestExtractor | 33 | private restExtractor: RestExtractor, |
27 | ) { } | 34 | private restService: RestService |
35 | ) { | ||
36 | this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe( | ||
37 | bufferTime(500), | ||
38 | filter(videoIds => videoIds.length !== 0), | ||
39 | switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)), | ||
40 | share() | ||
41 | ) | ||
42 | } | ||
28 | 43 | ||
29 | listChannelPlaylists (videoChannel: VideoChannel): Observable<ResultList<VideoPlaylist>> { | 44 | listChannelPlaylists (videoChannel: VideoChannel): Observable<ResultList<VideoPlaylist>> { |
30 | const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists' | 45 | const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists' |
@@ -36,10 +51,13 @@ export class VideoPlaylistService { | |||
36 | ) | 51 | ) |
37 | } | 52 | } |
38 | 53 | ||
39 | listAccountPlaylists (account: Account): Observable<ResultList<VideoPlaylist>> { | 54 | listAccountPlaylists (account: Account, sort: string): Observable<ResultList<VideoPlaylist>> { |
40 | const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists' | 55 | const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists' |
41 | 56 | ||
42 | return this.authHttp.get<ResultList<VideoPlaylist>>(url) | 57 | let params = new HttpParams() |
58 | params = this.restService.addRestGetParams(params, undefined, sort) | ||
59 | |||
60 | return this.authHttp.get<ResultList<VideoPlaylist>>(url, { params }) | ||
43 | .pipe( | 61 | .pipe( |
44 | switchMap(res => this.extractPlaylists(res)), | 62 | switchMap(res => this.extractPlaylists(res)), |
45 | catchError(err => this.restExtractor.handleError(err)) | 63 | catchError(err => this.restExtractor.handleError(err)) |
@@ -59,9 +77,8 @@ export class VideoPlaylistService { | |||
59 | createVideoPlaylist (body: VideoPlaylistCreate) { | 77 | createVideoPlaylist (body: VideoPlaylistCreate) { |
60 | const data = objectToFormData(body) | 78 | const data = objectToFormData(body) |
61 | 79 | ||
62 | return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) | 80 | return this.authHttp.post<{ videoPlaylist: { id: number } }>(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) |
63 | .pipe( | 81 | .pipe( |
64 | map(this.restExtractor.extractDataBool), | ||
65 | catchError(err => this.restExtractor.handleError(err)) | 82 | catchError(err => this.restExtractor.handleError(err)) |
66 | ) | 83 | ) |
67 | } | 84 | } |
@@ -84,6 +101,36 @@ export class VideoPlaylistService { | |||
84 | ) | 101 | ) |
85 | } | 102 | } |
86 | 103 | ||
104 | addVideoInPlaylist (playlistId: number, body: VideoPlaylistElementCreate) { | ||
105 | return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos', body) | ||
106 | .pipe( | ||
107 | map(this.restExtractor.extractDataBool), | ||
108 | catchError(err => this.restExtractor.handleError(err)) | ||
109 | ) | ||
110 | } | ||
111 | |||
112 | updateVideoOfPlaylist (playlistId: number, videoId: number, body: VideoPlaylistElementUpdate) { | ||
113 | return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + videoId, body) | ||
114 | .pipe( | ||
115 | map(this.restExtractor.extractDataBool), | ||
116 | catchError(err => this.restExtractor.handleError(err)) | ||
117 | ) | ||
118 | } | ||
119 | |||
120 | removeVideoFromPlaylist (playlistId: number, videoId: number) { | ||
121 | return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/' + videoId) | ||
122 | .pipe( | ||
123 | map(this.restExtractor.extractDataBool), | ||
124 | catchError(err => this.restExtractor.handleError(err)) | ||
125 | ) | ||
126 | } | ||
127 | |||
128 | doesVideoExistInPlaylist (videoId: number) { | ||
129 | this.videoExistsInPlaylistSubject.next(videoId) | ||
130 | |||
131 | return this.videoExistsInPlaylistObservable.pipe(first()) | ||
132 | } | ||
133 | |||
87 | extractPlaylists (result: ResultList<VideoPlaylistServerModel>) { | 134 | extractPlaylists (result: ResultList<VideoPlaylistServerModel>) { |
88 | return this.serverService.localeObservable | 135 | return this.serverService.localeObservable |
89 | .pipe( | 136 | .pipe( |
@@ -105,4 +152,14 @@ export class VideoPlaylistService { | |||
105 | return this.serverService.localeObservable | 152 | return this.serverService.localeObservable |
106 | .pipe(map(translations => new VideoPlaylist(playlist, translations))) | 153 | .pipe(map(translations => new VideoPlaylist(playlist, translations))) |
107 | } | 154 | } |
155 | |||
156 | private doVideosExistInPlaylist (videoIds: number[]): Observable<VideoExistInPlaylist> { | ||
157 | const url = VideoPlaylistService.MY_VIDEO_PLAYLIST_URL + 'videos-exist' | ||
158 | let params = new HttpParams() | ||
159 | |||
160 | params = this.restService.addObjectParams(params, { videoIds }) | ||
161 | |||
162 | return this.authHttp.get<VideoExistInPlaylist>(url, { params }) | ||
163 | .pipe(catchError(err => this.restExtractor.handleError(err))) | ||
164 | } | ||
108 | } | 165 | } |