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/+my-account | |
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/+my-account')
13 files changed, 535 insertions, 5 deletions
diff --git a/client/src/app/+my-account/my-account-routing.module.ts b/client/src/app/+my-account/my-account-routing.module.ts index 9996218ca..0193afff7 100644 --- a/client/src/app/+my-account/my-account-routing.module.ts +++ b/client/src/app/+my-account/my-account-routing.module.ts | |||
@@ -15,6 +15,13 @@ import { MyAccountBlocklistComponent } from '@app/+my-account/my-account-blockli | |||
15 | import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' | 15 | import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' |
16 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' | 16 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' |
17 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' | 17 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' |
18 | import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component' | ||
19 | import { | ||
20 | MyAccountVideoPlaylistCreateComponent | ||
21 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component' | ||
22 | import { | ||
23 | MyAccountVideoPlaylistUpdateComponent | ||
24 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component' | ||
18 | 25 | ||
19 | const myAccountRoutes: Routes = [ | 26 | const myAccountRoutes: Routes = [ |
20 | { | 27 | { |
@@ -36,6 +43,7 @@ const myAccountRoutes: Routes = [ | |||
36 | } | 43 | } |
37 | } | 44 | } |
38 | }, | 45 | }, |
46 | |||
39 | { | 47 | { |
40 | path: 'video-channels', | 48 | path: 'video-channels', |
41 | component: MyAccountVideoChannelsComponent, | 49 | component: MyAccountVideoChannelsComponent, |
@@ -63,6 +71,35 @@ const myAccountRoutes: Routes = [ | |||
63 | } | 71 | } |
64 | } | 72 | } |
65 | }, | 73 | }, |
74 | |||
75 | { | ||
76 | path: 'video-playlists', | ||
77 | component: MyAccountVideoPlaylistsComponent, | ||
78 | data: { | ||
79 | meta: { | ||
80 | title: 'Account playlists' | ||
81 | } | ||
82 | } | ||
83 | }, | ||
84 | { | ||
85 | path: 'video-playlists/create', | ||
86 | component: MyAccountVideoPlaylistCreateComponent, | ||
87 | data: { | ||
88 | meta: { | ||
89 | title: 'Create new playlist' | ||
90 | } | ||
91 | } | ||
92 | }, | ||
93 | { | ||
94 | path: 'video-playlists/update/:videoPlaylistId', | ||
95 | component: MyAccountVideoPlaylistUpdateComponent, | ||
96 | data: { | ||
97 | meta: { | ||
98 | title: 'Update playlist' | ||
99 | } | ||
100 | } | ||
101 | }, | ||
102 | |||
66 | { | 103 | { |
67 | path: 'videos', | 104 | path: 'videos', |
68 | component: MyAccountVideosComponent, | 105 | component: MyAccountVideosComponent, |
diff --git a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts index 9d2dccdf0..6ce22989b 100644 --- a/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts +++ b/client/src/app/+my-account/my-account-subscriptions/my-account-subscriptions.component.ts | |||
@@ -1,7 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | 3 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' |
4 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
5 | import { UserSubscriptionService } from '@app/shared/user-subscription' | 4 | import { UserSubscriptionService } from '@app/shared/user-subscription' |
6 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | 5 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' |
7 | 6 | ||
@@ -21,8 +20,7 @@ export class MyAccountSubscriptionsComponent implements OnInit { | |||
21 | 20 | ||
22 | constructor ( | 21 | constructor ( |
23 | private userSubscriptionService: UserSubscriptionService, | 22 | private userSubscriptionService: UserSubscriptionService, |
24 | private notifier: Notifier, | 23 | private notifier: Notifier |
25 | private i18n: I18n | ||
26 | ) {} | 24 | ) {} |
27 | 25 | ||
28 | ngOnInit () { | 26 | ngOnInit () { |
diff --git a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html index 51db2e75d..11e87ba79 100644 --- a/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html +++ b/client/src/app/+my-account/my-account-video-channels/my-account-video-channels.component.html | |||
@@ -1,7 +1,7 @@ | |||
1 | <div class="video-channels-header"> | 1 | <div class="video-channels-header"> |
2 | <a class="create-button" routerLink="create"> | 2 | <a class="create-button" routerLink="create"> |
3 | <my-global-icon iconName="add"></my-global-icon> | 3 | <my-global-icon iconName="add"></my-global-icon> |
4 | <ng-container i18n>Create another video channel</ng-container> | 4 | <ng-container i18n>Create a new video channel</ng-container> |
5 | </a> | 5 | </a> |
6 | </div> | 6 | </div> |
7 | 7 | ||
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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { Router } from '@angular/router' | ||
3 | import { AuthService, Notifier, ServerService } from '@app/core' | ||
4 | import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
7 | import { VideoPlaylistValidatorsService } from '@app/shared' | ||
8 | import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' | ||
9 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
10 | import { VideoConstant } from '@shared/models' | ||
11 | import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' | ||
12 | import { 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 | }) | ||
19 | export 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 | |||
8 | input[type=text] { | ||
9 | @include peertube-input-text(340px); | ||
10 | |||
11 | display: block; | ||
12 | } | ||
13 | |||
14 | textarea { | ||
15 | @include peertube-textarea(500px, 150px); | ||
16 | |||
17 | display: block; | ||
18 | } | ||
19 | |||
20 | .peertube-select-container { | ||
21 | @include peertube-select-container(340px); | ||
22 | } | ||
23 | |||
24 | input[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 @@ | |||
1 | import { FormReactive } from '@app/shared' | ||
2 | import { VideoChannel } from '@app/shared/video-channel/video-channel.model' | ||
3 | import { ServerService } from '@app/core' | ||
4 | import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model' | ||
5 | |||
6 | export 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 @@ | |||
1 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
2 | import { ActivatedRoute, Router } from '@angular/router' | ||
3 | import { AuthService, Notifier, ServerService } from '@app/core' | ||
4 | import { Subscription } from 'rxjs' | ||
5 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
6 | import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' | ||
7 | import { MyAccountVideoPlaylistEdit } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-edit' | ||
8 | import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils' | ||
9 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
10 | import { VideoPlaylistValidatorsService } from '@app/shared' | ||
11 | import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' | ||
12 | import { VideoConstant } from '@shared/models' | ||
13 | import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' | ||
14 | import { 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 | }) | ||
21 | export 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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { Notifier } from '@app/core' | ||
3 | import { AuthService } from '../../core/auth' | ||
4 | import { ConfirmService } from '../../core/confirm' | ||
5 | import { User } from '@app/shared' | ||
6 | import { flatMap } from 'rxjs/operators' | ||
7 | import { I18n } from '@ngx-translate/i18n-polyfill' | ||
8 | import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' | ||
9 | import { ComponentPagination } from '@app/shared/rest/component-pagination.model' | ||
10 | import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' | ||
11 | import { 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 | }) | ||
18 | export 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 | } | ||
diff --git a/client/src/app/+my-account/my-account.component.ts b/client/src/app/+my-account/my-account.component.ts index 8a4102d80..f624ff505 100644 --- a/client/src/app/+my-account/my-account.component.ts +++ b/client/src/app/+my-account/my-account.component.ts | |||
@@ -28,6 +28,10 @@ export class MyAccountComponent { | |||
28 | routerLink: '/my-account/videos' | 28 | routerLink: '/my-account/videos' |
29 | }, | 29 | }, |
30 | { | 30 | { |
31 | label: this.i18n('My playlists'), | ||
32 | routerLink: '/my-account/video-playlists' | ||
33 | }, | ||
34 | { | ||
31 | label: this.i18n('My subscriptions'), | 35 | label: this.i18n('My subscriptions'), |
32 | routerLink: '/my-account/subscriptions' | 36 | routerLink: '/my-account/subscriptions' |
33 | }, | 37 | }, |
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts index 18f51f171..3dbce2b92 100644 --- a/client/src/app/+my-account/my-account.module.ts +++ b/client/src/app/+my-account/my-account.module.ts | |||
@@ -25,6 +25,13 @@ import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-b | |||
25 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' | 25 | import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' |
26 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' | 26 | import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' |
27 | import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences' | 27 | import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences' |
28 | import { | ||
29 | MyAccountVideoPlaylistCreateComponent | ||
30 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component' | ||
31 | import { | ||
32 | MyAccountVideoPlaylistUpdateComponent | ||
33 | } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component' | ||
34 | import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component' | ||
28 | 35 | ||
29 | @NgModule({ | 36 | @NgModule({ |
30 | imports: [ | 37 | imports: [ |
@@ -57,7 +64,11 @@ import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-a | |||
57 | MyAccountServerBlocklistComponent, | 64 | MyAccountServerBlocklistComponent, |
58 | MyAccountHistoryComponent, | 65 | MyAccountHistoryComponent, |
59 | MyAccountNotificationsComponent, | 66 | MyAccountNotificationsComponent, |
60 | MyAccountNotificationPreferencesComponent | 67 | MyAccountNotificationPreferencesComponent, |
68 | |||
69 | MyAccountVideoPlaylistCreateComponent, | ||
70 | MyAccountVideoPlaylistUpdateComponent, | ||
71 | MyAccountVideoPlaylistsComponent | ||
61 | ], | 72 | ], |
62 | 73 | ||
63 | exports: [ | 74 | exports: [ |