aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+my-account/my-account-video-playlists
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/+my-account/my-account-video-playlists')
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts89
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html64
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss27
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts13
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts132
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html20
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss50
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts85
8 files changed, 480 insertions, 0 deletions
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
new file mode 100644
index 000000000..61b61e221
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
@@ -0,0 +1,89 @@
1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router'
3import { AuthService, Notifier, ServerService } from '@app/core'
4import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
7import { VideoPlaylistValidatorsService } from '@app/shared'
8import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
10import { VideoConstant } from '@shared/models'
11import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model'
12import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
13
14@Component({
15 selector: 'my-account-video-playlist-create',
16 templateUrl: './my-account-video-playlist-edit.component.html',
17 styleUrls: [ './my-account-video-playlist-edit.component.scss' ]
18})
19export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylistEdit implements OnInit {
20 error: string
21 videoPlaylistPrivacies: VideoConstant<VideoPlaylistPrivacy>[] = []
22
23 constructor (
24 protected formValidatorService: FormValidatorService,
25 private authService: AuthService,
26 private videoPlaylistValidatorsService: VideoPlaylistValidatorsService,
27 private notifier: Notifier,
28 private router: Router,
29 private videoPlaylistService: VideoPlaylistService,
30 private serverService: ServerService,
31 private i18n: I18n
32 ) {
33 super()
34 }
35
36 ngOnInit () {
37 this.buildForm({
38 'display-name': this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME,
39 privacy: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_PRIVACY,
40 description: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DESCRIPTION,
41 videoChannelId: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_CHANNEL_ID,
42 thumbnailfile: null
43 })
44
45 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
46
47 this.serverService.videoPlaylistPrivaciesLoaded.subscribe(
48 () => {
49 this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies()
50
51 this.form.patchValue({
52 privacy: VideoPlaylistPrivacy.PRIVATE
53 })
54 }
55 )
56 }
57
58 formValidated () {
59 this.error = undefined
60
61 const body = this.form.value
62 const videoPlaylistCreate: VideoPlaylistCreate = {
63 displayName: body['display-name'],
64 privacy: body.privacy,
65 description: body.description || null,
66 videoChannelId: body.videoChannelId || null,
67 thumbnailfile: body.thumbnailfile || null
68 }
69
70 this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe(
71 () => {
72 this.notifier.success(
73 this.i18n('Playlist {{playlistName}} created.', { playlistName: videoPlaylistCreate.displayName })
74 )
75 this.router.navigate([ '/my-account', 'video-playlists' ])
76 },
77
78 err => this.error = err.message
79 )
80 }
81
82 isCreation () {
83 return true
84 }
85
86 getFormButtonTitle () {
87 return this.i18n('Create')
88 }
89}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
new file mode 100644
index 000000000..b76488c78
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
@@ -0,0 +1,64 @@
1<div i18n class="form-sub-title" *ngIf="isCreation() === true">Create a new playlist</div>
2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4
5<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
6 <div class="row">
7 <div class="col-md-12 col-xl-6">
8 <div class="form-group">
9 <label i18n for="display-name">Display name</label>
10 <input
11 type="text" id="display-name"
12 formControlName="display-name" [ngClass]="{ 'input-error': formErrors['display-name'] }"
13 >
14 <div *ngIf="formErrors['display-name']" class="form-error">
15 {{ formErrors['display-name'] }}
16 </div>
17 </div>
18
19 <div class="form-group">
20 <label i18n for="description">Description</label>
21 <textarea
22 id="description" formControlName="description"
23 [ngClass]="{ 'input-error': formErrors['description'] }"
24 ></textarea>
25 <div *ngIf="formErrors.description" class="form-error">
26 {{ formErrors.description }}
27 </div>
28 </div>
29 </div>
30
31 <div class="col-md-12 col-xl-6">
32 <div class="form-group">
33 <label i18n for="privacy">Privacy</label>
34 <div class="peertube-select-container">
35 <select id="privacy" formControlName="privacy">
36 <option *ngFor="let privacy of videoPlaylistPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
37 </select>
38 </div>
39
40 <div *ngIf="formErrors.privacy" class="form-error">
41 {{ formErrors.privacy }}
42 </div>
43 </div>
44
45 <div class="form-group">
46 <label i18n>Channel</label>
47 <div class="peertube-select-container">
48 <select formControlName="videoChannelId">
49 <option></option>
50 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
51 </select>
52 </div>
53 </div>
54
55 <div class="form-group">
56 <my-image-upload
57 i18n-inputLabel inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
58 previewWidth="200px" previewHeight="110px"
59 ></my-image-upload>
60 </div>
61 </div>
62 </div>
63 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
64</form>
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss
new file mode 100644
index 000000000..5af846d8e
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss
@@ -0,0 +1,27 @@
1@import '_variables';
2@import '_mixins';
3
4.form-sub-title {
5 margin-bottom: 20px;
6}
7
8input[type=text] {
9 @include peertube-input-text(340px);
10
11 display: block;
12}
13
14textarea {
15 @include peertube-textarea(500px, 150px);
16
17 display: block;
18}
19
20.peertube-select-container {
21 @include peertube-select-container(340px);
22}
23
24input[type=submit] {
25 @include peertube-button;
26 @include orange-button;
27}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
new file mode 100644
index 000000000..fbfb4c8f7
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
@@ -0,0 +1,13 @@
1import { FormReactive } from '@app/shared'
2import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
3import { ServerService } from '@app/core'
4import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model'
5
6export abstract class MyAccountVideoPlaylistEdit extends FormReactive {
7 // Declare it here to avoid errors in create template
8 videoPlaylistToUpdate: VideoPlaylist
9 userVideoChannels: { id: number, label: string }[] = []
10
11 abstract isCreation (): boolean
12 abstract getFormButtonTitle (): string
13}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
new file mode 100644
index 000000000..167d7dd09
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
@@ -0,0 +1,132 @@
1import { Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'
3import { AuthService, Notifier, ServerService } from '@app/core'
4import { Subscription } from 'rxjs'
5import { I18n } from '@ngx-translate/i18n-polyfill'
6import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service'
7import { MyAccountVideoPlaylistEdit } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-edit'
8import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils'
9import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
10import { VideoPlaylistValidatorsService } from '@app/shared'
11import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model'
12import { VideoConstant } from '@shared/models'
13import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model'
14import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
15
16@Component({
17 selector: 'my-account-video-playlist-update',
18 templateUrl: './my-account-video-playlist-edit.component.html',
19 styleUrls: [ './my-account-video-playlist-edit.component.scss' ]
20})
21export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylistEdit implements OnInit, OnDestroy {
22 error: string
23 videoPlaylistToUpdate: VideoPlaylist
24 videoPlaylistPrivacies: VideoConstant<VideoPlaylistPrivacy>[] = []
25
26 private paramsSub: Subscription
27
28 constructor (
29 protected formValidatorService: FormValidatorService,
30 private authService: AuthService,
31 private videoPlaylistValidatorsService: VideoPlaylistValidatorsService,
32 private notifier: Notifier,
33 private router: Router,
34 private route: ActivatedRoute,
35 private videoPlaylistService: VideoPlaylistService,
36 private i18n: I18n,
37 private serverService: ServerService
38 ) {
39 super()
40 }
41
42 ngOnInit () {
43 this.buildForm({
44 'display-name': this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME,
45 privacy: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_PRIVACY,
46 description: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DESCRIPTION,
47 videoChannelId: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_CHANNEL_ID,
48 thumbnailfile: null
49 })
50
51 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
52
53 this.paramsSub = this.route.params.subscribe(routeParams => {
54 const videoPlaylistId = routeParams['videoPlaylistId']
55
56 this.videoPlaylistService.getVideoPlaylist(videoPlaylistId).subscribe(
57 videoPlaylistToUpdate => {
58 this.videoPlaylistToUpdate = videoPlaylistToUpdate
59
60 this.hydrateFormFromPlaylist()
61
62 this.serverService.videoPlaylistPrivaciesLoaded.subscribe(
63 () => {
64 this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies()
65 .filter(p => {
66 // If the playlist is not private, we cannot put it in private anymore
67 return this.videoPlaylistToUpdate.privacy.id === VideoPlaylistPrivacy.PRIVATE ||
68 p.id !== VideoPlaylistPrivacy.PRIVATE
69 })
70 }
71 )
72 },
73
74 err => this.error = err.message
75 )
76 })
77 }
78
79 ngOnDestroy () {
80 if (this.paramsSub) this.paramsSub.unsubscribe()
81 }
82
83 formValidated () {
84 this.error = undefined
85
86 const body = this.form.value
87 const videoPlaylistUpdate: VideoPlaylistUpdate = {
88 displayName: body['display-name'],
89 privacy: body['privacy'],
90 description: body.description || null,
91 videoChannelId: body.videoChannelId || null,
92 thumbnailfile: body.thumbnailfile || undefined
93 }
94
95 this.videoPlaylistService.updateVideoPlaylist(this.videoPlaylistToUpdate, videoPlaylistUpdate).subscribe(
96 () => {
97 this.notifier.success(
98 this.i18n('Playlist {{videoPlaylistName}} updated.', { videoPlaylistName: videoPlaylistUpdate.displayName })
99 )
100
101 this.router.navigate([ '/my-account', 'video-playlists' ])
102 },
103
104 err => this.error = err.message
105 )
106 }
107
108 isCreation () {
109 return false
110 }
111
112 getFormButtonTitle () {
113 return this.i18n('Update')
114 }
115
116 private hydrateFormFromPlaylist () {
117 this.form.patchValue({
118 'display-name': this.videoPlaylistToUpdate.displayName,
119 privacy: this.videoPlaylistToUpdate.privacy.id,
120 description: this.videoPlaylistToUpdate.description,
121 videoChannelId: this.videoPlaylistToUpdate.videoChannel ? this.videoPlaylistToUpdate.videoChannel.id : null
122 })
123
124 fetch(this.videoPlaylistToUpdate.thumbnailUrl)
125 .then(response => response.blob())
126 .then(data => {
127 this.form.patchValue({
128 thumbnailfile: data
129 })
130 })
131 }
132}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html
new file mode 100644
index 000000000..ab5d9cc5a
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html
@@ -0,0 +1,20 @@
1<div class="video-playlists-header">
2 <a class="create-button" routerLink="create">
3 <my-global-icon iconName="add"></my-global-icon>
4 <ng-container i18n>Create a new playlist</ng-container>
5 </a>
6</div>
7
8<div class="video-playlists">
9 <div *ngFor="let playlist of videoPlaylists" class="video-playlist">
10 <div class="miniature-wrapper">
11 <my-video-playlist-miniature [playlist]="playlist"></my-video-playlist-miniature>
12 </div>
13
14 <div *ngIf="isRegularPlaylist(playlist)" class="video-playlist-buttons">
15 <my-delete-button (click)="deleteVideoPlaylist(playlist)"></my-delete-button>
16
17 <my-edit-button [routerLink]="[ 'update', playlist.uuid ]"></my-edit-button>
18 </div>
19 </div>
20</div>
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss
new file mode 100644
index 000000000..88fba5b05
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss
@@ -0,0 +1,50 @@
1@import '_variables';
2@import '_mixins';
3
4.create-button {
5 @include create-button;
6}
7
8/deep/ .action-button {
9 &.action-button-delete {
10 margin-right: 10px;
11 }
12}
13
14.video-playlist {
15 @include row-blocks;
16
17 .miniature-wrapper {
18 flex-grow: 1;
19
20 /deep/ .miniature {
21 display: flex;
22
23 .miniature-bottom {
24 margin-left: 10px;
25 }
26 }
27 }
28
29 .video-playlist-buttons {
30 min-width: 190px;
31 }
32}
33
34.video-playlists-header {
35 text-align: right;
36 margin: 20px 0 50px;
37}
38
39@media screen and (max-width: 800px) {
40 .video-playlists-header {
41 text-align: center;
42 }
43
44 .video-playlist {
45
46 .video-playlist-buttons {
47 margin-top: 10px;
48 }
49 }
50}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts
new file mode 100644
index 000000000..761ce90e8
--- /dev/null
+++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts
@@ -0,0 +1,85 @@
1import { Component, OnInit } from '@angular/core'
2import { Notifier } from '@app/core'
3import { AuthService } from '../../core/auth'
4import { ConfirmService } from '../../core/confirm'
5import { User } from '@app/shared'
6import { flatMap } from 'rxjs/operators'
7import { I18n } from '@ngx-translate/i18n-polyfill'
8import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
9import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
10import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
11import { VideoPlaylistType } from '@shared/models'
12
13@Component({
14 selector: 'my-account-video-playlists',
15 templateUrl: './my-account-video-playlists.component.html',
16 styleUrls: [ './my-account-video-playlists.component.scss' ]
17})
18export class MyAccountVideoPlaylistsComponent implements OnInit {
19 videoPlaylists: VideoPlaylist[] = []
20
21 pagination: ComponentPagination = {
22 currentPage: 1,
23 itemsPerPage: 10,
24 totalItems: null
25 }
26
27 private user: User
28
29 constructor (
30 private authService: AuthService,
31 private notifier: Notifier,
32 private confirmService: ConfirmService,
33 private videoPlaylistService: VideoPlaylistService,
34 private i18n: I18n
35 ) {}
36
37 ngOnInit () {
38 this.user = this.authService.getUser()
39
40 this.loadVideoPlaylists()
41 }
42
43 async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) {
44 const res = await this.confirmService.confirm(
45 this.i18n(
46 'Do you really want to delete {{playlistDisplayName}}?',
47 { playlistDisplayName: videoPlaylist.displayName }
48 ),
49 this.i18n('Delete')
50 )
51 if (res === false) return
52
53 this.videoPlaylistService.removeVideoPlaylist(videoPlaylist)
54 .subscribe(
55 () => {
56 this.videoPlaylists = this.videoPlaylists
57 .filter(p => p.id !== videoPlaylist.id)
58
59 this.notifier.success(
60 this.i18n('Playlist {{playlistDisplayName}} deleted.', { playlistDisplayName: videoPlaylist.displayName })
61 )
62 },
63
64 error => this.notifier.error(error.message)
65 )
66 }
67
68 isRegularPlaylist (playlist: VideoPlaylist) {
69 return playlist.type.id === VideoPlaylistType.REGULAR
70 }
71
72 private loadVideoPlaylists () {
73 this.authService.userInformationLoaded
74 .pipe(flatMap(() => this.videoPlaylistService.listAccountPlaylists(this.user.account)))
75 .subscribe(res => this.videoPlaylists = res.data)
76 }
77
78 private ofNearOfBottom () {
79 // Last page
80 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
81
82 this.pagination.currentPage += 1
83 this.loadVideoPlaylists()
84 }
85}