From 830b4faff15fb9c81d88e8e69fcdf94aad32bef8 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 6 Mar 2019 15:36:44 +0100 Subject: Add/update/delete/list my playlists --- .../app/+my-account/my-account-routing.module.ts | 37 ++++++ .../my-account-subscriptions.component.ts | 4 +- .../my-account-video-channels.component.html | 2 +- .../my-account-video-playlist-create.component.ts | 89 ++++++++++++++ .../my-account-video-playlist-edit.component.html | 64 ++++++++++ .../my-account-video-playlist-edit.component.scss | 27 +++++ .../my-account-video-playlist-edit.ts | 13 ++ .../my-account-video-playlist-update.component.ts | 132 +++++++++++++++++++++ .../my-account-video-playlists.component.html | 20 ++++ .../my-account-video-playlists.component.scss | 50 ++++++++ .../my-account-video-playlists.component.ts | 85 +++++++++++++ client/src/app/+my-account/my-account.component.ts | 4 + client/src/app/+my-account/my-account.module.ts | 13 +- 13 files changed, 535 insertions(+), 5 deletions(-) create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss create mode 100644 client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts (limited to 'client/src/app/+my-account') 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 import { MyAccountServerBlocklistComponent } from '@app/+my-account/my-account-blocklist/my-account-server-blocklist.component' import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' +import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component' +import { + MyAccountVideoPlaylistCreateComponent +} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component' +import { + MyAccountVideoPlaylistUpdateComponent +} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component' const myAccountRoutes: Routes = [ { @@ -36,6 +43,7 @@ const myAccountRoutes: Routes = [ } } }, + { path: 'video-channels', component: MyAccountVideoChannelsComponent, @@ -63,6 +71,35 @@ const myAccountRoutes: Routes = [ } } }, + + { + path: 'video-playlists', + component: MyAccountVideoPlaylistsComponent, + data: { + meta: { + title: 'Account playlists' + } + } + }, + { + path: 'video-playlists/create', + component: MyAccountVideoPlaylistCreateComponent, + data: { + meta: { + title: 'Create new playlist' + } + } + }, + { + path: 'video-playlists/update/:videoPlaylistId', + component: MyAccountVideoPlaylistUpdateComponent, + data: { + meta: { + title: 'Update playlist' + } + } + }, + { path: 'videos', 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 @@ import { Component, OnInit } from '@angular/core' import { Notifier } from '@app/core' import { VideoChannel } from '@app/shared/video-channel/video-channel.model' -import { I18n } from '@ngx-translate/i18n-polyfill' import { UserSubscriptionService } from '@app/shared/user-subscription' import { ComponentPagination } from '@app/shared/rest/component-pagination.model' @@ -21,8 +20,7 @@ export class MyAccountSubscriptionsComponent implements OnInit { constructor ( private userSubscriptionService: UserSubscriptionService, - private notifier: Notifier, - private i18n: I18n + private notifier: Notifier ) {} 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 @@
- Create another video channel + Create a new video channel
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 @@ +import { Component, OnInit } from '@angular/core' +import { Router } from '@angular/router' +import { AuthService, Notifier, ServerService } from '@app/core' +import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { VideoPlaylistValidatorsService } from '@app/shared' +import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' +import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' +import { VideoConstant } from '@shared/models' +import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' +import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils' + +@Component({ + selector: 'my-account-video-playlist-create', + templateUrl: './my-account-video-playlist-edit.component.html', + styleUrls: [ './my-account-video-playlist-edit.component.scss' ] +}) +export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylistEdit implements OnInit { + error: string + videoPlaylistPrivacies: VideoConstant[] = [] + + constructor ( + protected formValidatorService: FormValidatorService, + private authService: AuthService, + private videoPlaylistValidatorsService: VideoPlaylistValidatorsService, + private notifier: Notifier, + private router: Router, + private videoPlaylistService: VideoPlaylistService, + private serverService: ServerService, + private i18n: I18n + ) { + super() + } + + ngOnInit () { + this.buildForm({ + 'display-name': this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME, + privacy: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_PRIVACY, + description: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DESCRIPTION, + videoChannelId: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_CHANNEL_ID, + thumbnailfile: null + }) + + populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) + + this.serverService.videoPlaylistPrivaciesLoaded.subscribe( + () => { + this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies() + + this.form.patchValue({ + privacy: VideoPlaylistPrivacy.PRIVATE + }) + } + ) + } + + formValidated () { + this.error = undefined + + const body = this.form.value + const videoPlaylistCreate: VideoPlaylistCreate = { + displayName: body['display-name'], + privacy: body.privacy, + description: body.description || null, + videoChannelId: body.videoChannelId || null, + thumbnailfile: body.thumbnailfile || null + } + + this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe( + () => { + this.notifier.success( + this.i18n('Playlist {{playlistName}} created.', { playlistName: videoPlaylistCreate.displayName }) + ) + this.router.navigate([ '/my-account', 'video-playlists' ]) + }, + + err => this.error = err.message + ) + } + + isCreation () { + return true + } + + getFormButtonTitle () { + return this.i18n('Create') + } +} 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 @@ +
Create a new playlist
+ +
{{ error }}
+ +
+
+
+
+ + +
+ {{ formErrors['display-name'] }} +
+
+ +
+ + +
+ {{ formErrors.description }} +
+
+
+ +
+
+ +
+ +
+ +
+ {{ formErrors.privacy }} +
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
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 @@ +@import '_variables'; +@import '_mixins'; + +.form-sub-title { + margin-bottom: 20px; +} + +input[type=text] { + @include peertube-input-text(340px); + + display: block; +} + +textarea { + @include peertube-textarea(500px, 150px); + + display: block; +} + +.peertube-select-container { + @include peertube-select-container(340px); +} + +input[type=submit] { + @include peertube-button; + @include orange-button; +} 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 @@ +import { FormReactive } from '@app/shared' +import { VideoChannel } from '@app/shared/video-channel/video-channel.model' +import { ServerService } from '@app/core' +import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model' + +export abstract class MyAccountVideoPlaylistEdit extends FormReactive { + // Declare it here to avoid errors in create template + videoPlaylistToUpdate: VideoPlaylist + userVideoChannels: { id: number, label: string }[] = [] + + abstract isCreation (): boolean + abstract getFormButtonTitle (): string +} 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 @@ +import { Component, OnDestroy, OnInit } from '@angular/core' +import { ActivatedRoute, Router } from '@angular/router' +import { AuthService, Notifier, ServerService } from '@app/core' +import { Subscription } from 'rxjs' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { FormValidatorService } from '@app/shared/forms/form-validators/form-validator.service' +import { MyAccountVideoPlaylistEdit } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-edit' +import { populateAsyncUserVideoChannels } from '@app/shared/misc/utils' +import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' +import { VideoPlaylistValidatorsService } from '@app/shared' +import { VideoPlaylistUpdate } from '@shared/models/videos/playlist/video-playlist-update.model' +import { VideoConstant } from '@shared/models' +import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' +import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' + +@Component({ + selector: 'my-account-video-playlist-update', + templateUrl: './my-account-video-playlist-edit.component.html', + styleUrls: [ './my-account-video-playlist-edit.component.scss' ] +}) +export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylistEdit implements OnInit, OnDestroy { + error: string + videoPlaylistToUpdate: VideoPlaylist + videoPlaylistPrivacies: VideoConstant[] = [] + + private paramsSub: Subscription + + constructor ( + protected formValidatorService: FormValidatorService, + private authService: AuthService, + private videoPlaylistValidatorsService: VideoPlaylistValidatorsService, + private notifier: Notifier, + private router: Router, + private route: ActivatedRoute, + private videoPlaylistService: VideoPlaylistService, + private i18n: I18n, + private serverService: ServerService + ) { + super() + } + + ngOnInit () { + this.buildForm({ + 'display-name': this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DISPLAY_NAME, + privacy: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_PRIVACY, + description: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_DESCRIPTION, + videoChannelId: this.videoPlaylistValidatorsService.VIDEO_PLAYLIST_CHANNEL_ID, + thumbnailfile: null + }) + + populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) + + this.paramsSub = this.route.params.subscribe(routeParams => { + const videoPlaylistId = routeParams['videoPlaylistId'] + + this.videoPlaylistService.getVideoPlaylist(videoPlaylistId).subscribe( + videoPlaylistToUpdate => { + this.videoPlaylistToUpdate = videoPlaylistToUpdate + + this.hydrateFormFromPlaylist() + + this.serverService.videoPlaylistPrivaciesLoaded.subscribe( + () => { + this.videoPlaylistPrivacies = this.serverService.getVideoPlaylistPrivacies() + .filter(p => { + // If the playlist is not private, we cannot put it in private anymore + return this.videoPlaylistToUpdate.privacy.id === VideoPlaylistPrivacy.PRIVATE || + p.id !== VideoPlaylistPrivacy.PRIVATE + }) + } + ) + }, + + err => this.error = err.message + ) + }) + } + + ngOnDestroy () { + if (this.paramsSub) this.paramsSub.unsubscribe() + } + + formValidated () { + this.error = undefined + + const body = this.form.value + const videoPlaylistUpdate: VideoPlaylistUpdate = { + displayName: body['display-name'], + privacy: body['privacy'], + description: body.description || null, + videoChannelId: body.videoChannelId || null, + thumbnailfile: body.thumbnailfile || undefined + } + + this.videoPlaylistService.updateVideoPlaylist(this.videoPlaylistToUpdate, videoPlaylistUpdate).subscribe( + () => { + this.notifier.success( + this.i18n('Playlist {{videoPlaylistName}} updated.', { videoPlaylistName: videoPlaylistUpdate.displayName }) + ) + + this.router.navigate([ '/my-account', 'video-playlists' ]) + }, + + err => this.error = err.message + ) + } + + isCreation () { + return false + } + + getFormButtonTitle () { + return this.i18n('Update') + } + + private hydrateFormFromPlaylist () { + this.form.patchValue({ + 'display-name': this.videoPlaylistToUpdate.displayName, + privacy: this.videoPlaylistToUpdate.privacy.id, + description: this.videoPlaylistToUpdate.description, + videoChannelId: this.videoPlaylistToUpdate.videoChannel ? this.videoPlaylistToUpdate.videoChannel.id : null + }) + + fetch(this.videoPlaylistToUpdate.thumbnailUrl) + .then(response => response.blob()) + .then(data => { + this.form.patchValue({ + thumbnailfile: data + }) + }) + } +} 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 @@ + + +
+
+
+ +
+ +
+ + + +
+
+
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 @@ +@import '_variables'; +@import '_mixins'; + +.create-button { + @include create-button; +} + +/deep/ .action-button { + &.action-button-delete { + margin-right: 10px; + } +} + +.video-playlist { + @include row-blocks; + + .miniature-wrapper { + flex-grow: 1; + + /deep/ .miniature { + display: flex; + + .miniature-bottom { + margin-left: 10px; + } + } + } + + .video-playlist-buttons { + min-width: 190px; + } +} + +.video-playlists-header { + text-align: right; + margin: 20px 0 50px; +} + +@media screen and (max-width: 800px) { + .video-playlists-header { + text-align: center; + } + + .video-playlist { + + .video-playlist-buttons { + margin-top: 10px; + } + } +} 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 @@ +import { Component, OnInit } from '@angular/core' +import { Notifier } from '@app/core' +import { AuthService } from '../../core/auth' +import { ConfirmService } from '../../core/confirm' +import { User } from '@app/shared' +import { flatMap } from 'rxjs/operators' +import { I18n } from '@ngx-translate/i18n-polyfill' +import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model' +import { ComponentPagination } from '@app/shared/rest/component-pagination.model' +import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' +import { VideoPlaylistType } from '@shared/models' + +@Component({ + selector: 'my-account-video-playlists', + templateUrl: './my-account-video-playlists.component.html', + styleUrls: [ './my-account-video-playlists.component.scss' ] +}) +export class MyAccountVideoPlaylistsComponent implements OnInit { + videoPlaylists: VideoPlaylist[] = [] + + pagination: ComponentPagination = { + currentPage: 1, + itemsPerPage: 10, + totalItems: null + } + + private user: User + + constructor ( + private authService: AuthService, + private notifier: Notifier, + private confirmService: ConfirmService, + private videoPlaylistService: VideoPlaylistService, + private i18n: I18n + ) {} + + ngOnInit () { + this.user = this.authService.getUser() + + this.loadVideoPlaylists() + } + + async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) { + const res = await this.confirmService.confirm( + this.i18n( + 'Do you really want to delete {{playlistDisplayName}}?', + { playlistDisplayName: videoPlaylist.displayName } + ), + this.i18n('Delete') + ) + if (res === false) return + + this.videoPlaylistService.removeVideoPlaylist(videoPlaylist) + .subscribe( + () => { + this.videoPlaylists = this.videoPlaylists + .filter(p => p.id !== videoPlaylist.id) + + this.notifier.success( + this.i18n('Playlist {{playlistDisplayName}} deleted.', { playlistDisplayName: videoPlaylist.displayName }) + ) + }, + + error => this.notifier.error(error.message) + ) + } + + isRegularPlaylist (playlist: VideoPlaylist) { + return playlist.type.id === VideoPlaylistType.REGULAR + } + + private loadVideoPlaylists () { + this.authService.userInformationLoaded + .pipe(flatMap(() => this.videoPlaylistService.listAccountPlaylists(this.user.account))) + .subscribe(res => this.videoPlaylists = res.data) + } + + private ofNearOfBottom () { + // Last page + if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return + + this.pagination.currentPage += 1 + this.loadVideoPlaylists() + } +} 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 @@ -27,6 +27,10 @@ export class MyAccountComponent { label: this.i18n('My videos'), routerLink: '/my-account/videos' }, + { + label: this.i18n('My playlists'), + routerLink: '/my-account/video-playlists' + }, { label: this.i18n('My subscriptions'), routerLink: '/my-account/subscriptions' 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 import { MyAccountHistoryComponent } from '@app/+my-account/my-account-history/my-account-history.component' import { MyAccountNotificationsComponent } from '@app/+my-account/my-account-notifications/my-account-notifications.component' import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-account-settings/my-account-notification-preferences' +import { + MyAccountVideoPlaylistCreateComponent +} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component' +import { + MyAccountVideoPlaylistUpdateComponent +} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component' +import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlists.component' @NgModule({ imports: [ @@ -57,7 +64,11 @@ import { MyAccountNotificationPreferencesComponent } from '@app/+my-account/my-a MyAccountServerBlocklistComponent, MyAccountHistoryComponent, MyAccountNotificationsComponent, - MyAccountNotificationPreferencesComponent + MyAccountNotificationPreferencesComponent, + + MyAccountVideoPlaylistCreateComponent, + MyAccountVideoPlaylistUpdateComponent, + MyAccountVideoPlaylistsComponent ], exports: [ -- cgit v1.2.3