aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/+my-account/my-account-video-playlists
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2020-11-12 15:28:54 +0100
committerChocobozzz <chocobozzz@cpy.re>2020-11-13 12:02:21 +0100
commit17119e4a546522468878cf115558b17949ab50d0 (patch)
tree3f130cfd7fdccf5aeeac9beee941750590239047 /client/src/app/+my-account/my-account-video-playlists
parentb4bc269e5517849b5b89052f0c1a2c01b6f65089 (diff)
downloadPeerTube-17119e4a546522468878cf115558b17949ab50d0.tar.gz
PeerTube-17119e4a546522468878cf115558b17949ab50d0.tar.zst
PeerTube-17119e4a546522468878cf115558b17949ab50d0.zip
Reorganize left menu and account menu
Add my-settings and my-library in left menu Move administration below my-library Split account menu: my-setting and my library
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.ts92
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html100
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss36
-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-elements.component.html51
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss83
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts198
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts130
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html35
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss78
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts102
11 files changed, 0 insertions, 918 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
deleted file mode 100644
index 7a80aaa92..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-create.component.ts
+++ /dev/null
@@ -1,92 +0,0 @@
1import { Component, OnInit } from '@angular/core'
2import { Router } from '@angular/router'
3import { AuthService, Notifier, ServerService } from '@app/core'
4import { populateAsyncUserVideoChannels } from '@app/helpers'
5import {
6 setPlaylistChannelValidator,
7 VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR,
8 VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR,
9 VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR,
10 VIDEO_PLAYLIST_PRIVACY_VALIDATOR
11} from '@app/shared/form-validators/video-playlist-validators'
12import { FormValidatorService } from '@app/shared/shared-forms'
13import { VideoPlaylistService } from '@app/shared/shared-video-playlist'
14import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model'
15import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model'
16import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit'
17
18@Component({
19 selector: 'my-account-video-playlist-create',
20 templateUrl: './my-account-video-playlist-edit.component.html',
21 styleUrls: [ './my-account-video-playlist-edit.component.scss' ]
22})
23export class MyAccountVideoPlaylistCreateComponent extends MyAccountVideoPlaylistEdit implements OnInit {
24 error: string
25
26 constructor (
27 protected formValidatorService: FormValidatorService,
28 private authService: AuthService,
29 private notifier: Notifier,
30 private router: Router,
31 private videoPlaylistService: VideoPlaylistService,
32 private serverService: ServerService
33 ) {
34 super()
35 }
36
37 ngOnInit () {
38 this.buildForm({
39 displayName: VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR,
40 privacy: VIDEO_PLAYLIST_PRIVACY_VALIDATOR,
41 description: VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR,
42 videoChannelId: VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR,
43 thumbnailfile: null
44 })
45
46 this.form.get('privacy').valueChanges.subscribe(privacy => {
47 setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy)
48 })
49
50 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
51 .catch(err => console.error('Cannot populate user video channels.', err))
52
53 this.serverService.getVideoPlaylistPrivacies()
54 .subscribe(videoPlaylistPrivacies => {
55 this.videoPlaylistPrivacies = videoPlaylistPrivacies
56
57 this.form.patchValue({
58 privacy: VideoPlaylistPrivacy.PRIVATE
59 })
60 })
61 }
62
63 formValidated () {
64 this.error = undefined
65
66 const body = this.form.value
67 const videoPlaylistCreate: VideoPlaylistCreate = {
68 displayName: body.displayName,
69 privacy: body.privacy,
70 description: body.description || null,
71 videoChannelId: body.videoChannelId || null,
72 thumbnailfile: body.thumbnailfile || null
73 }
74
75 this.videoPlaylistService.createVideoPlaylist(videoPlaylistCreate).subscribe(
76 () => {
77 this.notifier.success($localize`Playlist ${videoPlaylistCreate.displayName} created.`)
78 this.router.navigate([ '/my-account', 'video-playlists' ])
79 },
80
81 err => this.error = err.message
82 )
83 }
84
85 isCreation () {
86 return true
87 }
88
89 getFormButtonTitle () {
90 return $localize`Create`
91 }
92}
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
deleted file mode 100644
index 56060359a..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html
+++ /dev/null
@@ -1,100 +0,0 @@
1<nav aria-label="breadcrumb">
2 <ol class="breadcrumb">
3 <li class="breadcrumb-item">
4 <a routerLink="/my-account/video-playlists" i18n>My Playlists</a>
5 </li>
6
7 <ng-container *ngIf="isCreation()">
8 <li class="breadcrumb-item active" i18n>Create</li>
9 </ng-container>
10 <ng-container *ngIf="!isCreation()">
11 <li class="breadcrumb-item active" i18n>Edit</li>
12 <li class="breadcrumb-item active" aria-current="page">
13 <a *ngIf="videoPlaylistToUpdate" [routerLink]="[ '/my-account/video-playlists/update', videoPlaylistToUpdate?.uuid ]">{{ videoPlaylistToUpdate?.displayName }}</a>
14 </li>
15 </ng-container>
16 </ol>
17</nav>
18
19<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
20
21<form role="form" (ngSubmit)="formValidated()" [formGroup]="form">
22
23 <div class="form-row"> <!-- playlist grid -->
24 <div class="form-group col-12 col-lg-4 col-xl-3">
25 <div *ngIf="isCreation()" class="video-playlist-title" i18n>NEW PLAYLIST</div>
26 <div *ngIf="!isCreation() && videoPlaylistToUpdate" class="video-playlist-title" i18n>PLAYLIST</div>
27 </div>
28
29 <div class="form-group form-group-right col-12 col-lg-8 col-xl-9">
30
31 <div class="col-md-12 col-xl-6">
32 <div class="form-group">
33 <label i18n for="displayName">Display name</label>
34 <input
35 type="text" id="displayName" class="form-control"
36 formControlName="displayName" [ngClass]="{ 'input-error': formErrors['displayName'] }"
37 >
38 <div *ngIf="formErrors['displayName']" class="form-error">
39 {{ formErrors['displayName'] }}
40 </div>
41 </div>
42
43 <div class="form-group">
44 <label i18n for="description">Description</label>
45 <textarea
46 id="description" formControlName="description"
47 class="form-control" [ngClass]="{ 'input-error': formErrors['description'] }"
48 ></textarea>
49 <div *ngIf="formErrors.description" class="form-error">
50 {{ formErrors.description }}
51 </div>
52 </div>
53 </div>
54
55 <div class="col-md-12 col-xl-6">
56 <div class="form-group">
57 <label i18n for="privacy">Privacy</label>
58 <div class="peertube-select-container">
59 <select id="privacy" formControlName="privacy" class="form-control">
60 <option *ngFor="let privacy of videoPlaylistPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
61 </select>
62 </div>
63
64 <div *ngIf="formErrors.privacy" class="form-error">
65 {{ formErrors.privacy }}
66 </div>
67 </div>
68
69 <div class="form-group">
70 <label i18n>Channel</label>
71
72 <my-select-channel
73 labelForId="videoChannelIdl" [items]="userVideoChannels" formControlName="videoChannelId"
74 ></my-select-channel>
75
76 <div *ngIf="formErrors['videoChannelId']" class="form-error">
77 {{ formErrors['videoChannelId'] }}
78 </div>
79 </div>
80
81 <div class="form-group">
82 <label i18n>Playlist thumbnail</label>
83
84 <my-preview-upload
85 i18n-inputLabel inputLabel="Edit" inputName="thumbnailfile" formControlName="thumbnailfile"
86 previewWidth="223px" previewHeight="122px"
87 ></my-preview-upload>
88 </div>
89 </div>
90
91 <div class="form-row"> <!-- submit placement block -->
92 <div class="col-md-7 col-xl-5"></div>
93 <div class="col-md-5 col-xl-5 d-inline-flex">
94 <input type="submit" value="{{ getFormButtonTitle() }}" [disabled]="!form.valid">
95 </div>
96 </div>
97 </div>
98 </div>
99
100</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
deleted file mode 100644
index 08fab1101..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.scss
+++ /dev/null
@@ -1,36 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4label {
5 font-weight: $font-regular;
6 font-size: 100%;
7}
8
9.video-playlist-title {
10 @include settings-big-title;
11}
12
13input[type=text] {
14 @include peertube-input-text(340px);
15
16 display: block;
17}
18
19textarea {
20 @include peertube-textarea(500px, 150px);
21
22 display: block;
23}
24
25.peertube-select-container {
26 @include peertube-select-container(340px);
27}
28
29input[type=submit] {
30 @include peertube-button;
31 @include orange-button;
32}
33
34.breadcrumb {
35 @include breadcrumb;
36}
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
deleted file mode 100644
index 774d58c90..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts
+++ /dev/null
@@ -1,13 +0,0 @@
1import { FormReactive, SelectChannelItem } from '@app/shared/shared-forms'
2import { VideoConstant, VideoPlaylistPrivacy } from '@shared/models'
3import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model'
4
5export abstract class MyAccountVideoPlaylistEdit extends FormReactive {
6 // Declare it here to avoid errors in create template
7 videoPlaylistToUpdate: VideoPlaylist
8 userVideoChannels: SelectChannelItem[] = []
9 videoPlaylistPrivacies: VideoConstant<VideoPlaylistPrivacy>[] = []
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-elements.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html
deleted file mode 100644
index 09b4c8a1b..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html
+++ /dev/null
@@ -1,51 +0,0 @@
1<div class="row">
2
3 <div class="playlist-info col-xs-12 col-md-5 col-xl-3">
4 <my-video-playlist-miniature
5 *ngIf="playlist" [playlist]="playlist" [toManage]="false" [displayChannel]="true"
6 [displayDescription]="true" [displayPrivacy]="true"
7 ></my-video-playlist-miniature>
8
9 <div class="playlist-buttons">
10 <button (click)="showShareModal()" class="action-button share-button">
11 <my-global-icon iconName="share" aria-hidden="true"></my-global-icon>
12 <span class="icon-text" i18n>Share</span>
13 </button>
14
15 <my-action-dropdown
16 *ngIf="isRegularPlaylist(playlist)"
17 [entry]="playlist" [actions]="playlistActions" label="More"
18 ></my-action-dropdown>
19 </div>
20
21 </div>
22
23 <div class="playlist-elements col-xs-12 col-md-7 col-xl-9">
24 <div class="no-results" *ngIf="pagination.totalItems === 0">
25 <div i18n>No videos in this playlist.</div>
26
27 <div i18n>
28 Browse videos on PeerTube to add them in your playlist.
29 </div>
30
31 <div i18n>
32 See the <a target="_blank" href="https://docs.joinpeertube.org/#/use-library?id=playlist">documentation</a> for more information.
33 </div>
34 </div>
35
36 <div
37 class="videos" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()"
38 cdkDropList (cdkDropListDropped)="drop($event)" [dataObservable]="onDataSubject.asObservable()"
39 >
40 <div class="video" *ngFor="let playlistElement of playlistElements; trackBy: trackByFn" cdkDrag [cdkDragStartDelay]="getDragStartDelay()">
41 <my-video-playlist-element-miniature
42 [playlistElement]="playlistElement" [playlist]="playlist" [owned]="true" (elementRemoved)="onElementRemoved($event)"
43 [position]="playlistElement.position"
44 >
45 </my-video-playlist-element-miniature>
46 </div>
47 </div>
48 </div>
49</div>
50
51<my-video-share #videoShareModal [playlist]="playlist"></my-video-share>
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss
deleted file mode 100644
index de7e1993f..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss
+++ /dev/null
@@ -1,83 +0,0 @@
1@import '_variables';
2@import '_mixins';
3@import '_miniature';
4
5.playlist-info {
6 background-color: pvar(--submenuColor);
7 margin-left: -$not-expanded-horizontal-margins;
8 margin-top: -$sub-menu-margin-bottom;
9
10 padding: 10px;
11
12 display: flex;
13 flex-direction: column;
14 justify-content: flex-start;
15 align-items: center;
16
17 /* fix ellipsis dots background color */
18 ::ng-deep .miniature-name::after {
19 background-color: pvar(--submenuColor) !important;
20 }
21}
22
23.playlist-buttons {
24 display:flex;
25 margin: 30px 0 10px 0;
26
27 .share-button {
28 @include peertube-button;
29 @include button-with-icon(17px, 3px, -1px);
30 @include grey-button;
31 @include apply-svg-color(pvar(--actionButtonColor));
32
33 margin-right: 10px;
34 }
35}
36
37// Thanks Angular CDK <3 https://material.angular.io/cdk/drag-drop/examples
38.cdk-drag-preview {
39 box-sizing: border-box;
40 border-radius: 4px;
41 box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
42 0 8px 10px 1px rgba(0, 0, 0, 0.14),
43 0 3px 14px 2px rgba(0, 0, 0, 0.12);
44}
45
46.cdk-drag-placeholder {
47 opacity: 0;
48}
49
50.cdk-drag-animating {
51 transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
52}
53
54.video:last-child {
55 border: none;
56}
57
58.videos.cdk-drop-list-dragging .video:not(.cdk-drag-placeholder) {
59 transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
60}
61
62@media screen and (max-width: $small-view) {
63 .playlist-info {
64 width: 100vw;
65 padding-top: 20px;
66 margin-left: calc(#{var(--expanded-horizontal-margin-content)} * -1);
67 }
68
69 .playlist-elements {
70 padding: 0 !important;
71 }
72
73 ::ng-deep my-video-playlist-element-miniature {
74
75 .video {
76 padding: 5px !important;
77 }
78
79 .position {
80 margin-right: 5px !important;
81 }
82 }
83}
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts
deleted file mode 100644
index f6cdf1067..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts
+++ /dev/null
@@ -1,198 +0,0 @@
1import { Subject, Subscription } from 'rxjs'
2import { CdkDragDrop } from '@angular/cdk/drag-drop'
3import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router'
5import { ComponentPagination, ConfirmService, Notifier, ScreenService } from '@app/core'
6import { DropdownAction } from '@app/shared/shared-main'
7import { VideoShareComponent } from '@app/shared/shared-share-modal'
8import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist'
9import { VideoPlaylistType } from '@shared/models'
10
11@Component({
12 selector: 'my-account-video-playlist-elements',
13 templateUrl: './my-account-video-playlist-elements.component.html',
14 styleUrls: [ './my-account-video-playlist-elements.component.scss' ]
15})
16export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestroy {
17 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
18
19 playlistElements: VideoPlaylistElement[] = []
20 playlist: VideoPlaylist
21
22 playlistActions: DropdownAction<VideoPlaylist>[][] = []
23
24 pagination: ComponentPagination = {
25 currentPage: 1,
26 itemsPerPage: 10,
27 totalItems: null
28 }
29
30 onDataSubject = new Subject<any[]>()
31
32 private videoPlaylistId: string | number
33 private paramsSub: Subscription
34
35 constructor (
36 private notifier: Notifier,
37 private router: Router,
38 private confirmService: ConfirmService,
39 private route: ActivatedRoute,
40 private screenService: ScreenService,
41 private videoPlaylistService: VideoPlaylistService
42 ) {}
43
44 ngOnInit () {
45 this.playlistActions = [
46 [
47 {
48 label: $localize`Update playlist`,
49 iconName: 'edit',
50 linkBuilder: playlist => [ '/my-account', 'video-playlists', 'update', playlist.uuid ]
51 },
52 {
53 label: $localize`Delete playlist`,
54 iconName: 'delete',
55 handler: playlist => this.deleteVideoPlaylist(playlist)
56 }
57 ]
58 ]
59
60 this.paramsSub = this.route.params.subscribe(routeParams => {
61 this.videoPlaylistId = routeParams[ 'videoPlaylistId' ]
62 this.loadElements()
63
64 this.loadPlaylistInfo()
65 })
66 }
67
68 ngOnDestroy () {
69 if (this.paramsSub) this.paramsSub.unsubscribe()
70 }
71
72 drop (event: CdkDragDrop<any>) {
73 const previousIndex = event.previousIndex
74 const newIndex = event.currentIndex
75
76 if (previousIndex === newIndex) return
77
78 const oldPosition = this.playlistElements[previousIndex].position
79 let insertAfter = this.playlistElements[newIndex].position
80
81 if (oldPosition > insertAfter) insertAfter--
82
83 const element = this.playlistElements[previousIndex]
84
85 this.playlistElements.splice(previousIndex, 1)
86 this.playlistElements.splice(newIndex, 0, element)
87
88 this.videoPlaylistService.reorderPlaylist(this.playlist.id, oldPosition, insertAfter)
89 .subscribe(
90 () => {
91 this.reorderClientPositions()
92 },
93
94 err => this.notifier.error(err.message)
95 )
96 }
97
98 onElementRemoved (element: VideoPlaylistElement) {
99 const oldFirst = this.findFirst()
100
101 this.playlistElements = this.playlistElements.filter(v => v.id !== element.id)
102 this.reorderClientPositions(oldFirst)
103 }
104
105 onNearOfBottom () {
106 // Last page
107 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
108
109 this.pagination.currentPage += 1
110 this.loadElements()
111 }
112
113 trackByFn (index: number, elem: VideoPlaylistElement) {
114 return elem.id
115 }
116
117 isRegularPlaylist (playlist: VideoPlaylist) {
118 return playlist?.type.id === VideoPlaylistType.REGULAR
119 }
120
121 showShareModal () {
122 this.videoShareModal.show()
123 }
124
125 async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) {
126 const res = await this.confirmService.confirm(
127 $localize`Do you really want to delete ${videoPlaylist.displayName}?`,
128 $localize`Delete`
129 )
130 if (res === false) return
131
132 this.videoPlaylistService.removeVideoPlaylist(videoPlaylist)
133 .subscribe(
134 () => {
135 this.router.navigate([ '/my-account', 'video-playlists' ])
136 this.notifier.success($localize`Playlist ${videoPlaylist.displayName} deleted.`)
137 },
138
139 error => this.notifier.error(error.message)
140 )
141 }
142
143 /**
144 * Returns null to not have drag and drop delay.
145 * In small views, where elements are about 100% wide,
146 * we add a delay to prevent unwanted drag&drop.
147 *
148 * @see {@link https://github.com/Chocobozzz/PeerTube/issues/2078}
149 *
150 * @returns {null|number} Null for no delay, or a number in milliseconds.
151 */
152 getDragStartDelay (): null | number {
153 if (this.screenService.isInTouchScreen()) {
154 return 500
155 }
156
157 return null
158 }
159
160 private loadElements () {
161 this.videoPlaylistService.getPlaylistVideos(this.videoPlaylistId, this.pagination)
162 .subscribe(({ total, data }) => {
163 this.playlistElements = this.playlistElements.concat(data)
164 this.pagination.totalItems = total
165
166 this.onDataSubject.next(data)
167 })
168 }
169
170 private loadPlaylistInfo () {
171 this.videoPlaylistService.getVideoPlaylist(this.videoPlaylistId)
172 .subscribe(playlist => {
173 this.playlist = playlist
174 })
175 }
176
177 private reorderClientPositions (first?: VideoPlaylistElement) {
178 if (this.playlistElements.length === 0) return
179
180 const oldFirst = first || this.findFirst()
181 let i = 1
182
183 for (const element of this.playlistElements) {
184 element.position = i
185 i++
186 }
187
188 // Reload playlist thumbnail if the first element changed
189 const newFirst = this.findFirst()
190 if (oldFirst && newFirst && oldFirst.id !== newFirst.id) {
191 this.playlist.refreshThumbnail()
192 }
193 }
194
195 private findFirst () {
196 return this.playlistElements.find(e => e.position === 1)
197 }
198}
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
deleted file mode 100644
index fefc6d607..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-update.component.ts
+++ /dev/null
@@ -1,130 +0,0 @@
1import { forkJoin, Subscription } from 'rxjs'
2import { map, switchMap } from 'rxjs/operators'
3import { Component, OnDestroy, OnInit } from '@angular/core'
4import { ActivatedRoute, Router } from '@angular/router'
5import { AuthService, Notifier, ServerService } from '@app/core'
6import { populateAsyncUserVideoChannels } from '@app/helpers'
7import {
8 setPlaylistChannelValidator,
9 VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR,
10 VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR,
11 VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR,
12 VIDEO_PLAYLIST_PRIVACY_VALIDATOR
13} from '@app/shared/form-validators/video-playlist-validators'
14import { FormValidatorService } from '@app/shared/shared-forms'
15import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
16import { VideoPlaylistUpdate } from '@shared/models'
17import { MyAccountVideoPlaylistEdit } from './my-account-video-playlist-edit'
18
19@Component({
20 selector: 'my-account-video-playlist-update',
21 templateUrl: './my-account-video-playlist-edit.component.html',
22 styleUrls: [ './my-account-video-playlist-edit.component.scss' ]
23})
24export class MyAccountVideoPlaylistUpdateComponent extends MyAccountVideoPlaylistEdit implements OnInit, OnDestroy {
25 error: string
26 videoPlaylistToUpdate: VideoPlaylist
27
28 private paramsSub: Subscription
29
30 constructor (
31 protected formValidatorService: FormValidatorService,
32 private authService: AuthService,
33 private notifier: Notifier,
34 private router: Router,
35 private route: ActivatedRoute,
36 private videoPlaylistService: VideoPlaylistService,
37 private serverService: ServerService
38 ) {
39 super()
40 }
41
42 ngOnInit () {
43 this.buildForm({
44 displayName: VIDEO_PLAYLIST_DISPLAY_NAME_VALIDATOR,
45 privacy: VIDEO_PLAYLIST_PRIVACY_VALIDATOR,
46 description: VIDEO_PLAYLIST_DESCRIPTION_VALIDATOR,
47 videoChannelId: VIDEO_PLAYLIST_CHANNEL_ID_VALIDATOR,
48 thumbnailfile: null
49 })
50
51 this.form.get('privacy').valueChanges.subscribe(privacy => {
52 setPlaylistChannelValidator(this.form.get('videoChannelId'), privacy)
53 })
54
55 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
56 .catch(err => console.error('Cannot populate user video channels.', err))
57
58 this.paramsSub = this.route.params
59 .pipe(
60 map(routeParams => routeParams['videoPlaylistId']),
61 switchMap(videoPlaylistId => {
62 return forkJoin([
63 this.videoPlaylistService.getVideoPlaylist(videoPlaylistId),
64 this.serverService.getVideoPlaylistPrivacies()
65 ])
66 })
67 )
68 .subscribe(
69 ([ videoPlaylistToUpdate, videoPlaylistPrivacies]) => {
70 this.videoPlaylistToUpdate = videoPlaylistToUpdate
71 this.videoPlaylistPrivacies = videoPlaylistPrivacies
72
73 this.hydrateFormFromPlaylist()
74 },
75
76 err => this.error = err.message
77 )
78 }
79
80 ngOnDestroy () {
81 if (this.paramsSub) this.paramsSub.unsubscribe()
82 }
83
84 formValidated () {
85 this.error = undefined
86
87 const body = this.form.value
88 const videoPlaylistUpdate: VideoPlaylistUpdate = {
89 displayName: body.displayName,
90 privacy: body.privacy,
91 description: body.description || null,
92 videoChannelId: body.videoChannelId || null,
93 thumbnailfile: body.thumbnailfile || undefined
94 }
95
96 this.videoPlaylistService.updateVideoPlaylist(this.videoPlaylistToUpdate, videoPlaylistUpdate).subscribe(
97 () => {
98 this.notifier.success($localize`Playlist ${videoPlaylistUpdate.displayName} updated.`)
99 this.router.navigate([ '/my-account', 'video-playlists' ])
100 },
101
102 err => this.error = err.message
103 )
104 }
105
106 isCreation () {
107 return false
108 }
109
110 getFormButtonTitle () {
111 return $localize`Update`
112 }
113
114 private hydrateFormFromPlaylist () {
115 this.form.patchValue({
116 displayName: this.videoPlaylistToUpdate.displayName,
117 privacy: this.videoPlaylistToUpdate.privacy.id,
118 description: this.videoPlaylistToUpdate.description,
119 videoChannelId: this.videoPlaylistToUpdate.videoChannel ? this.videoPlaylistToUpdate.videoChannel.id : null
120 })
121
122 fetch(this.videoPlaylistToUpdate.thumbnailUrl)
123 .then(response => response.blob())
124 .then(data => {
125 this.form.patchValue({
126 thumbnailfile: data
127 })
128 })
129 }
130}
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
deleted file mode 100644
index afcf6a084..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.html
+++ /dev/null
@@ -1,35 +0,0 @@
1<h1>
2 <span>
3 <my-global-icon iconName="playlists" aria-hidden="true"></my-global-icon>
4 <ng-container i18n>My playlists</ng-container> <span class="badge badge-secondary">{{ pagination.totalItems }}</span>
5 </span>
6</h1>
7
8<div class="video-playlists-header d-flex justify-content-between">
9 <div class="has-feedback has-clear">
10 <input type="text" placeholder="Search your playlists" i18n-placeholder [(ngModel)]="videoPlaylistsSearch"
11 (ngModelChange)="onVideoPlaylistSearchChanged()" />
12 <a class="glyphicon glyphicon-remove-sign form-control-feedback form-control-clear" (click)="resetSearch()"></a>
13 <span class="sr-only" i18n>Clear filters</span>
14 </div>
15
16 <a class="create-button" routerLink="create">
17 <my-global-icon iconName="add" aria-hidden="true"></my-global-icon>
18 <ng-container i18n>Create playlist</ng-container>
19 </a>
20</div>
21
22<div class="video-playlists" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()" [dataObservable]="onDataSubject.asObservable()">
23 <div *ngFor="let playlist of videoPlaylists" class="video-playlist">
24 <div class="miniature-wrapper">
25 <my-video-playlist-miniature [playlist]="playlist" [toManage]="true" [displayChannel]="true" [displayDescription]="true" [displayPrivacy]="true"
26 ></my-video-playlist-miniature>
27 </div>
28
29 <div *ngIf="isRegularPlaylist(playlist)" class="video-playlist-buttons">
30 <my-delete-button label (click)="deleteVideoPlaylist(playlist)"></my-delete-button>
31
32 <my-edit-button label [routerLink]="[ 'update', playlist.uuid ]"></my-edit-button>
33 </div>
34 </div>
35</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
deleted file mode 100644
index 2b7c88246..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.scss
+++ /dev/null
@@ -1,78 +0,0 @@
1@import '_variables';
2@import '_mixins';
3
4.create-button {
5 @include create-button;
6}
7
8input[type=text] {
9 @include peertube-input-text(300px);
10}
11
12::ng-deep .action-button {
13 &.action-button-delete {
14 margin-right: 10px;
15 }
16}
17
18.video-playlist {
19 @include row-blocks;
20
21 .miniature-wrapper {
22 flex-grow: 1;
23
24 ::ng-deep .miniature {
25 display: flex;
26
27 .miniature-info {
28 margin-left: 10px;
29 width: auto;
30 }
31 }
32 }
33
34 .video-playlist-buttons {
35 min-width: 190px;
36 height: max-content;
37 }
38}
39
40.video-playlists-header {
41 margin-bottom: 30px;
42}
43
44@media screen and (max-width: $small-view) {
45 .video-playlists-header {
46 text-align: center;
47 }
48
49 .video-playlist {
50
51 .video-playlist-buttons {
52 margin-top: 10px;
53 }
54 }
55
56 my-video-playlist-miniature ::ng-deep .miniature {
57 flex-direction: column;
58
59 .miniature-info {
60 margin-left: 0 !important;
61 }
62
63 .miniature-name {
64 max-width: $video-thumbnail-width;
65 }
66 }
67}
68
69@media screen and (max-width: $mobile-view) {
70 .video-playlists-header {
71 flex-direction: column;
72
73 input[type=text] {
74 width: 100% !important;
75 margin-bottom: 12px;
76 }
77 }
78}
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
deleted file mode 100644
index 1e569c0b6..000000000
--- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlists.component.ts
+++ /dev/null
@@ -1,102 +0,0 @@
1import { Subject } from 'rxjs'
2import { debounceTime, mergeMap } from 'rxjs/operators'
3import { Component, OnInit } from '@angular/core'
4import { AuthService, ComponentPagination, ConfirmService, Notifier, User } from '@app/core'
5import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist'
6import { VideoPlaylistType } from '@shared/models'
7
8@Component({
9 selector: 'my-account-video-playlists',
10 templateUrl: './my-account-video-playlists.component.html',
11 styleUrls: [ './my-account-video-playlists.component.scss' ]
12})
13export class MyAccountVideoPlaylistsComponent implements OnInit {
14 videoPlaylistsSearch: string
15 videoPlaylists: VideoPlaylist[] = []
16 videoPlaylistSearchChanged = new Subject<string>()
17
18 pagination: ComponentPagination = {
19 currentPage: 1,
20 itemsPerPage: 5,
21 totalItems: null
22 }
23
24 onDataSubject = new Subject<any[]>()
25
26 private user: User
27
28 constructor (
29 private authService: AuthService,
30 private notifier: Notifier,
31 private confirmService: ConfirmService,
32 private videoPlaylistService: VideoPlaylistService
33 ) {}
34
35 ngOnInit () {
36 this.user = this.authService.getUser()
37
38 this.loadVideoPlaylists()
39
40 this.videoPlaylistSearchChanged
41 .pipe(
42 debounceTime(500))
43 .subscribe(() => {
44 this.loadVideoPlaylists(true)
45 })
46 }
47
48 async deleteVideoPlaylist (videoPlaylist: VideoPlaylist) {
49 const res = await this.confirmService.confirm(
50 $localize`Do you really want to delete ${videoPlaylist.displayName}?`,
51 $localize`Delete`
52 )
53 if (res === false) return
54
55 this.videoPlaylistService.removeVideoPlaylist(videoPlaylist)
56 .subscribe(
57 () => {
58 this.videoPlaylists = this.videoPlaylists
59 .filter(p => p.id !== videoPlaylist.id)
60
61 this.notifier.success($localize`Playlist ${videoPlaylist.displayName}} deleted.`)
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 onNearOfBottom () {
73 // Last page
74 if (this.pagination.totalItems <= (this.pagination.currentPage * this.pagination.itemsPerPage)) return
75
76 this.pagination.currentPage += 1
77 this.loadVideoPlaylists()
78 }
79
80 resetSearch () {
81 this.videoPlaylistsSearch = ''
82 this.onVideoPlaylistSearchChanged()
83 }
84
85 onVideoPlaylistSearchChanged () {
86 this.videoPlaylistSearchChanged.next()
87 }
88
89 private loadVideoPlaylists (reset = false) {
90 this.authService.userInformationLoaded
91 .pipe(mergeMap(() => {
92 return this.videoPlaylistService.listAccountPlaylists(this.user.account, this.pagination, '-updatedAt', this.videoPlaylistsSearch)
93 }))
94 .subscribe(res => {
95 if (reset) this.videoPlaylists = []
96 this.videoPlaylists = this.videoPlaylists.concat(res.data)
97 this.pagination.totalItems = res.total
98
99 this.onDataSubject.next(res.data)
100 })
101 }
102}