diff options
author | Chocobozzz <me@florianbigard.com> | 2020-11-12 15:28:54 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2020-11-13 12:02:21 +0100 |
commit | 17119e4a546522468878cf115558b17949ab50d0 (patch) | |
tree | 3f130cfd7fdccf5aeeac9beee941750590239047 /client/src/app/+my-account/my-account-video-playlists | |
parent | b4bc269e5517849b5b89052f0c1a2c01b6f65089 (diff) | |
download | PeerTube-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')
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 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | ||
2 | import { Router } from '@angular/router' | ||
3 | import { AuthService, Notifier, ServerService } from '@app/core' | ||
4 | import { populateAsyncUserVideoChannels } from '@app/helpers' | ||
5 | import { | ||
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' | ||
12 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
13 | import { VideoPlaylistService } from '@app/shared/shared-video-playlist' | ||
14 | import { VideoPlaylistCreate } from '@shared/models/videos/playlist/video-playlist-create.model' | ||
15 | import { VideoPlaylistPrivacy } from '@shared/models/videos/playlist/video-playlist-privacy.model' | ||
16 | import { 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 | }) | ||
23 | export 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 | |||
4 | label { | ||
5 | font-weight: $font-regular; | ||
6 | font-size: 100%; | ||
7 | } | ||
8 | |||
9 | .video-playlist-title { | ||
10 | @include settings-big-title; | ||
11 | } | ||
12 | |||
13 | input[type=text] { | ||
14 | @include peertube-input-text(340px); | ||
15 | |||
16 | display: block; | ||
17 | } | ||
18 | |||
19 | textarea { | ||
20 | @include peertube-textarea(500px, 150px); | ||
21 | |||
22 | display: block; | ||
23 | } | ||
24 | |||
25 | .peertube-select-container { | ||
26 | @include peertube-select-container(340px); | ||
27 | } | ||
28 | |||
29 | input[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 @@ | |||
1 | import { FormReactive, SelectChannelItem } from '@app/shared/shared-forms' | ||
2 | import { VideoConstant, VideoPlaylistPrivacy } from '@shared/models' | ||
3 | import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model' | ||
4 | |||
5 | export 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 @@ | |||
1 | import { Subject, Subscription } from 'rxjs' | ||
2 | import { CdkDragDrop } from '@angular/cdk/drag-drop' | ||
3 | import { Component, OnDestroy, OnInit, ViewChild } from '@angular/core' | ||
4 | import { ActivatedRoute, Router } from '@angular/router' | ||
5 | import { ComponentPagination, ConfirmService, Notifier, ScreenService } from '@app/core' | ||
6 | import { DropdownAction } from '@app/shared/shared-main' | ||
7 | import { VideoShareComponent } from '@app/shared/shared-share-modal' | ||
8 | import { VideoPlaylist, VideoPlaylistElement, VideoPlaylistService } from '@app/shared/shared-video-playlist' | ||
9 | import { 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 | }) | ||
16 | export 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 @@ | |||
1 | import { forkJoin, Subscription } from 'rxjs' | ||
2 | import { map, switchMap } from 'rxjs/operators' | ||
3 | import { Component, OnDestroy, OnInit } from '@angular/core' | ||
4 | import { ActivatedRoute, Router } from '@angular/router' | ||
5 | import { AuthService, Notifier, ServerService } from '@app/core' | ||
6 | import { populateAsyncUserVideoChannels } from '@app/helpers' | ||
7 | import { | ||
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' | ||
14 | import { FormValidatorService } from '@app/shared/shared-forms' | ||
15 | import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' | ||
16 | import { VideoPlaylistUpdate } from '@shared/models' | ||
17 | import { 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 | }) | ||
24 | export 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 | |||
8 | input[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 @@ | |||
1 | import { Subject } from 'rxjs' | ||
2 | import { debounceTime, mergeMap } from 'rxjs/operators' | ||
3 | import { Component, OnInit } from '@angular/core' | ||
4 | import { AuthService, ComponentPagination, ConfirmService, Notifier, User } from '@app/core' | ||
5 | import { VideoPlaylist, VideoPlaylistService } from '@app/shared/shared-video-playlist' | ||
6 | import { 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 | }) | ||
13 | export 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 | } | ||