diff options
author | Chocobozzz <me@florianbigard.com> | 2019-03-06 15:36:44 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2019-03-18 11:17:59 +0100 |
commit | 830b4faff15fb9c81d88e8e69fcdf94aad32bef8 (patch) | |
tree | 53de6c9e30ce88734b4bdda62016e0498fe78491 /client/src/app/shared/video-playlist | |
parent | d4c9f45b31eda0b7a391ddc83eb290ca5cba311f (diff) | |
download | PeerTube-830b4faff15fb9c81d88e8e69fcdf94aad32bef8.tar.gz PeerTube-830b4faff15fb9c81d88e8e69fcdf94aad32bef8.tar.zst PeerTube-830b4faff15fb9c81d88e8e69fcdf94aad32bef8.zip |
Add/update/delete/list my playlists
Diffstat (limited to 'client/src/app/shared/video-playlist')
5 files changed, 249 insertions, 0 deletions
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 new file mode 100644 index 000000000..1a39f5fe5 --- /dev/null +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.html | |||
@@ -0,0 +1,22 @@ | |||
1 | <div class="miniature"> | ||
2 | <a | ||
3 | [routerLink]="[ '/videos/watch' ]" [attr.title]="playlist.displayName" | ||
4 | class="miniature-thumbnail" | ||
5 | > | ||
6 | <img alt="" [attr.aria-labelledby]="playlist.displayName" [attr.src]="playlist.thumbnailUrl" /> | ||
7 | |||
8 | <div class="miniature-playlist-info-overlay"> | ||
9 | <ng-container i18n>{playlist.videosLength, plural, =0 {No videos} =1 {1 video} other {{{playlist.videosLength}} videos}}</ng-container> | ||
10 | </div> | ||
11 | |||
12 | <div class="play-overlay"> | ||
13 | <div class="icon"></div> | ||
14 | </div> | ||
15 | </a> | ||
16 | |||
17 | <div class="miniature-bottom"> | ||
18 | <a tabindex="-1" class="miniature-name" [routerLink]="[ '/videos/watch' ]" [attr.title]="playlist.displayName"> | ||
19 | {{ playlist.displayName }} | ||
20 | </a> | ||
21 | </div> | ||
22 | </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 new file mode 100644 index 000000000..a47206577 --- /dev/null +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.scss | |||
@@ -0,0 +1,34 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | @import '_miniature'; | ||
4 | |||
5 | .miniature { | ||
6 | display: inline-block; | ||
7 | |||
8 | .miniature-thumbnail { | ||
9 | @include miniature-thumbnail; | ||
10 | |||
11 | .miniature-playlist-info-overlay { | ||
12 | @include static-thumbnail-overlay; | ||
13 | |||
14 | position: absolute; | ||
15 | right: 0; | ||
16 | bottom: 0; | ||
17 | height: $video-thumbnail-height; | ||
18 | padding: 0 10px; | ||
19 | display: flex; | ||
20 | align-items: center; | ||
21 | font-size: 15px; | ||
22 | } | ||
23 | } | ||
24 | |||
25 | .miniature-bottom { | ||
26 | width: 200px; | ||
27 | margin-top: 2px; | ||
28 | line-height: normal; | ||
29 | |||
30 | .miniature-name { | ||
31 | @include miniature-name; | ||
32 | } | ||
33 | } | ||
34 | } | ||
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 new file mode 100644 index 000000000..b3bba7c87 --- /dev/null +++ b/client/src/app/shared/video-playlist/video-playlist-miniature.component.ts | |||
@@ -0,0 +1,11 @@ | |||
1 | import { Component, Input } from '@angular/core' | ||
2 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-video-playlist-miniature', | ||
6 | styleUrls: [ './video-playlist-miniature.component.scss' ], | ||
7 | templateUrl: './video-playlist-miniature.component.html' | ||
8 | }) | ||
9 | export class VideoPlaylistMiniatureComponent { | ||
10 | @Input() playlist: VideoPlaylist | ||
11 | } | ||
diff --git a/client/src/app/shared/video-playlist/video-playlist.model.ts b/client/src/app/shared/video-playlist/video-playlist.model.ts new file mode 100644 index 000000000..9d0b02789 --- /dev/null +++ b/client/src/app/shared/video-playlist/video-playlist.model.ts | |||
@@ -0,0 +1,74 @@ | |||
1 | import { | ||
2 | VideoChannelSummary, | ||
3 | VideoConstant, | ||
4 | VideoPlaylist as ServerVideoPlaylist, | ||
5 | VideoPlaylistPrivacy, | ||
6 | VideoPlaylistType | ||
7 | } from '../../../../../shared/models/videos' | ||
8 | import { AccountSummary, peertubeTranslate } from '@shared/models' | ||
9 | import { Actor } from '@app/shared/actor/actor.model' | ||
10 | import { getAbsoluteAPIUrl } from '@app/shared/misc/utils' | ||
11 | |||
12 | export class VideoPlaylist implements ServerVideoPlaylist { | ||
13 | id: number | ||
14 | uuid: string | ||
15 | isLocal: boolean | ||
16 | |||
17 | displayName: string | ||
18 | description: string | ||
19 | privacy: VideoConstant<VideoPlaylistPrivacy> | ||
20 | |||
21 | thumbnailPath: string | ||
22 | |||
23 | videosLength: number | ||
24 | |||
25 | type: VideoConstant<VideoPlaylistType> | ||
26 | |||
27 | createdAt: Date | string | ||
28 | updatedAt: Date | string | ||
29 | |||
30 | ownerAccount: AccountSummary | ||
31 | videoChannel?: VideoChannelSummary | ||
32 | |||
33 | thumbnailUrl: string | ||
34 | |||
35 | ownerBy: string | ||
36 | ownerAvatarUrl: string | ||
37 | |||
38 | videoChannelBy?: string | ||
39 | videoChannelAvatarUrl?: string | ||
40 | |||
41 | constructor (hash: ServerVideoPlaylist, translations: {}) { | ||
42 | const absoluteAPIUrl = getAbsoluteAPIUrl() | ||
43 | |||
44 | this.id = hash.id | ||
45 | this.uuid = hash.uuid | ||
46 | this.isLocal = hash.isLocal | ||
47 | |||
48 | this.displayName = hash.displayName | ||
49 | this.description = hash.description | ||
50 | this.privacy = hash.privacy | ||
51 | |||
52 | this.thumbnailPath = hash.thumbnailPath | ||
53 | this.thumbnailUrl = absoluteAPIUrl + hash.thumbnailPath | ||
54 | |||
55 | this.videosLength = hash.videosLength | ||
56 | |||
57 | this.type = hash.type | ||
58 | |||
59 | this.createdAt = new Date(hash.createdAt) | ||
60 | this.updatedAt = new Date(hash.updatedAt) | ||
61 | |||
62 | this.ownerAccount = hash.ownerAccount | ||
63 | this.ownerBy = Actor.CREATE_BY_STRING(hash.ownerAccount.name, hash.ownerAccount.host) | ||
64 | this.ownerAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.ownerAccount) | ||
65 | |||
66 | if (hash.videoChannel) { | ||
67 | this.videoChannel = hash.videoChannel | ||
68 | this.videoChannelBy = Actor.CREATE_BY_STRING(hash.videoChannel.name, hash.videoChannel.host) | ||
69 | this.videoChannelAvatarUrl = Actor.GET_ACTOR_AVATAR_URL(this.videoChannel) | ||
70 | } | ||
71 | |||
72 | this.privacy.label = peertubeTranslate(this.privacy.label, translations) | ||
73 | } | ||
74 | } | ||
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts new file mode 100644 index 000000000..8b66e122c --- /dev/null +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts | |||
@@ -0,0 +1,108 @@ | |||
1 | import { catchError, map, switchMap } from 'rxjs/operators' | ||
2 | import { Injectable } from '@angular/core' | ||
3 | import { Observable } from 'rxjs' | ||
4 | import { RestExtractor } from '../rest/rest-extractor.service' | ||
5 | import { HttpClient } from '@angular/common/http' | ||
6 | import { ResultList } from '../../../../../shared' | ||
7 | import { environment } from '../../../environments/environment' | ||
8 | import { VideoPlaylist as VideoPlaylistServerModel } from '@shared/models/videos/playlist/video-playlist.model' | ||
9 | import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' | ||
10 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
11 | import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' | ||
12 | import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' | ||
13 | import { objectToFormData } from '@app/shared/misc/utils' | ||
14 | import { ServerService } from '@app/core' | ||
15 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
16 | import { AccountService } from '@app/shared/account/account.service' | ||
17 | import { Account } from '@app/shared/account/account.model' | ||
18 | |||
19 | @Injectable() | ||
20 | export class VideoPlaylistService { | ||
21 | static BASE_VIDEO_PLAYLIST_URL = environment.apiUrl + '/api/v1/video-playlists/' | ||
22 | |||
23 | constructor ( | ||
24 | private authHttp: HttpClient, | ||
25 | private serverService: ServerService, | ||
26 | private restExtractor: RestExtractor | ||
27 | ) { } | ||
28 | |||
29 | listChannelPlaylists (videoChannel: VideoChannel): Observable<ResultList<VideoPlaylist>> { | ||
30 | const url = VideoChannelService.BASE_VIDEO_CHANNEL_URL + videoChannel.nameWithHost + '/video-playlists' | ||
31 | |||
32 | return this.authHttp.get<ResultList<VideoPlaylist>>(url) | ||
33 | .pipe( | ||
34 | switchMap(res => this.extractPlaylists(res)), | ||
35 | catchError(err => this.restExtractor.handleError(err)) | ||
36 | ) | ||
37 | } | ||
38 | |||
39 | listAccountPlaylists (account: Account): Observable<ResultList<VideoPlaylist>> { | ||
40 | const url = AccountService.BASE_ACCOUNT_URL + account.nameWithHost + '/video-playlists' | ||
41 | |||
42 | return this.authHttp.get<ResultList<VideoPlaylist>>(url) | ||
43 | .pipe( | ||
44 | switchMap(res => this.extractPlaylists(res)), | ||
45 | catchError(err => this.restExtractor.handleError(err)) | ||
46 | ) | ||
47 | } | ||
48 | |||
49 | getVideoPlaylist (id: string | number) { | ||
50 | const url = VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + id | ||
51 | |||
52 | return this.authHttp.get<VideoPlaylist>(url) | ||
53 | .pipe( | ||
54 | switchMap(res => this.extractPlaylist(res)), | ||
55 | catchError(err => this.restExtractor.handleError(err)) | ||
56 | ) | ||
57 | } | ||
58 | |||
59 | createVideoPlaylist (body: VideoPlaylistCreate) { | ||
60 | const data = objectToFormData(body) | ||
61 | |||
62 | return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL, data) | ||
63 | .pipe( | ||
64 | map(this.restExtractor.extractDataBool), | ||
65 | catchError(err => this.restExtractor.handleError(err)) | ||
66 | ) | ||
67 | } | ||
68 | |||
69 | updateVideoPlaylist (videoPlaylist: VideoPlaylist, body: VideoPlaylistUpdate) { | ||
70 | const data = objectToFormData(body) | ||
71 | |||
72 | return this.authHttp.put(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id, data) | ||
73 | .pipe( | ||
74 | map(this.restExtractor.extractDataBool), | ||
75 | catchError(err => this.restExtractor.handleError(err)) | ||
76 | ) | ||
77 | } | ||
78 | |||
79 | removeVideoPlaylist (videoPlaylist: VideoPlaylist) { | ||
80 | return this.authHttp.delete(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + videoPlaylist.id) | ||
81 | .pipe( | ||
82 | map(this.restExtractor.extractDataBool), | ||
83 | catchError(err => this.restExtractor.handleError(err)) | ||
84 | ) | ||
85 | } | ||
86 | |||
87 | extractPlaylists (result: ResultList<VideoPlaylistServerModel>) { | ||
88 | return this.serverService.localeObservable | ||
89 | .pipe( | ||
90 | map(translations => { | ||
91 | const playlistsJSON = result.data | ||
92 | const total = result.total | ||
93 | const playlists: VideoPlaylist[] = [] | ||
94 | |||
95 | for (const playlistJSON of playlistsJSON) { | ||
96 | playlists.push(new VideoPlaylist(playlistJSON, translations)) | ||
97 | } | ||
98 | |||
99 | return { data: playlists, total } | ||
100 | }) | ||
101 | ) | ||
102 | } | ||
103 | |||
104 | extractPlaylist (playlist: VideoPlaylistServerModel) { | ||
105 | return this.serverService.localeObservable | ||
106 | .pipe(map(translations => new VideoPlaylist(playlist, translations))) | ||
107 | } | ||
108 | } | ||