aboutsummaryrefslogtreecommitdiffhomepage
path: root/client
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2019-03-12 11:40:42 +0100
committerChocobozzz <chocobozzz@cpy.re>2019-03-18 11:17:59 +0100
commit15e9d5ca39e0b792f61453fbf3885a0fc446afa7 (patch)
tree015628bc7497f45477d287e8bb482e39d5d491e2 /client
parentc5a1ae500e68b759f76851552be6dd10631d34f4 (diff)
downloadPeerTube-15e9d5ca39e0b792f61453fbf3885a0fc446afa7.tar.gz
PeerTube-15e9d5ca39e0b792f61453fbf3885a0fc446afa7.tar.zst
PeerTube-15e9d5ca39e0b792f61453fbf3885a0fc446afa7.zip
Playlist reorder support
Diffstat (limited to 'client')
-rw-r--r--client/package.json1
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.html7
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.scss154
-rw-r--r--client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component.ts69
-rw-r--r--client/src/app/+my-account/my-account.module.ts4
-rw-r--r--client/src/app/shared/shared.module.ts3
-rw-r--r--client/src/app/shared/video-playlist/video-playlist.service.ts14
-rw-r--r--client/yarn.lock14
8 files changed, 195 insertions, 71 deletions
diff --git a/client/package.json b/client/package.json
index 08d309b07..72708bd76 100644
--- a/client/package.json
+++ b/client/package.json
@@ -66,6 +66,7 @@
66 "devDependencies": { 66 "devDependencies": {
67 "@angular-devkit/build-angular": "~0.13.1", 67 "@angular-devkit/build-angular": "~0.13.1",
68 "@angular/animations": "~7.2.4", 68 "@angular/animations": "~7.2.4",
69 "@angular/cdk": "^7.3.4",
69 "@angular/cli": "~7.3.1", 70 "@angular/cli": "~7.3.1",
70 "@angular/common": "~7.2.4", 71 "@angular/common": "~7.2.4",
71 "@angular/compiler": "~7.2.4", 72 "@angular/compiler": "~7.2.4",
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
index e2d09a36d..67a8b1a91 100644
--- 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
@@ -1,7 +1,10 @@
1<div i18n class="no-results" *ngIf="pagination.totalItems === 0">No videos in this playlist.</div> 1<div i18n class="no-results" *ngIf="pagination.totalItems === 0">No videos in this playlist.</div>
2 2
3<div class="videos" myInfiniteScroller (nearOfBottom)="onNearOfBottom()"> 3<div
4 <div *ngFor="let video of videos" class="video"> 4 class="videos" myInfiniteScroller [autoInit]="true" (nearOfBottom)="onNearOfBottom()"
5 cdkDropList (cdkDropListDropped)="drop($event)"
6>
7 <div class="video" *ngFor="let video of videos" cdkDrag (cdkDragMoved)="onDragMove($event)">
5 <div class="position">{{ video.playlistElement.position }}</div> 8 <div class="position">{{ video.playlistElement.position }}</div>
6 9
7 <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur(video)"></my-video-thumbnail> 10 <my-video-thumbnail [video]="video" [nsfw]="isVideoBlur(video)"></my-video-thumbnail>
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
index 3be10078e..4ac89d08f 100644
--- 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
@@ -2,95 +2,121 @@
2@import '_mixins'; 2@import '_mixins';
3@import '_miniature'; 3@import '_miniature';
4 4
5.videos { 5.video, .cdk-drag-preview {
6 .video { 6 display: flex;
7 align-items: center;
8 background-color: var(--mainBackgroundColor);
9 cursor: pointer;
10 padding: 10px;
11 border-bottom: 1px solid $separator-border-color;
12
13 &:hover {
14 background-color: rgba(0, 0, 0, 0.05);
15
16 .more {
17 display: block;
18 }
19 }
20
21 .position {
22 font-weight: $font-semibold;
23 margin-right: 10px;
24 color: $grey-foreground-color;
25 min-width: 20px;
26 }
27
28 my-video-thumbnail {
29 display: flex; // Avoids an issue with line-height that adds space below the element
30 margin-right: 10px;
31
32 /deep/ .video-thumbnail {
33 @include miniature-thumbnail(130px, 72px);
34 }
35 }
36
37 .video-info {
7 display: flex; 38 display: flex;
8 align-items: center; 39 flex-direction: column;
9 padding: 10px;
10 border-bottom: 1px solid $separator-border-color;
11 40
12 &:hover { 41 a {
13 background-color: rgba(0, 0, 0, 0.05); 42 @include disable-default-a-behaviour;
14 43
15 .more { 44 color: var(--mainForegroundColor);
16 display: block;
17 }
18 } 45 }
19 46
20 .position { 47 .video-info-name {
48 font-size: 18px;
21 font-weight: $font-semibold; 49 font-weight: $font-semibold;
22 margin-right: 10px;
23 color: $grey-foreground-color;
24 } 50 }
25 51
26 my-video-thumbnail { 52 .video-info-account, .video-info-timestamp {
27 display: flex; // Avoids an issue with line-height that adds space below the element 53 color: $grey-foreground-color;
28 margin-right: 10px;
29
30 /deep/ .video-thumbnail {
31 @include miniature-thumbnail(130px, 72px);
32 }
33 } 54 }
55 }
34 56
35 .video-info { 57 .more {
36 display: flex; 58 justify-self: flex-end;
37 flex-direction: column; 59 margin-left: auto;
60 cursor: pointer;
61 display: none;
38 62
39 a { 63 &.show {
40 @include disable-default-a-behaviour; 64 display: block;
65 }
41 66
42 color: var(--mainForegroundColor); 67 .icon-more {
43 } 68 @include apply-svg-color($grey-foreground-color);
44 69
45 .video-info-name { 70 &::after {
46 font-size: 18px; 71 border: none;
47 font-weight: $font-semibold;
48 } 72 }
73 }
49 74
50 .video-info-account, .video-info-timestamp { 75 .dropdown-item {
51 color: $grey-foreground-color; 76 @include dropdown-with-icon-item;
52 }
53 } 77 }
54 78
55 .more { 79 .timestamp-options {
56 justify-self: flex-end; 80 padding-top: 0;
57 margin-left: auto; 81 padding-left: 35px;
58 cursor: pointer; 82 margin-bottom: 15px;
59 display: none;
60 83
61 &.show { 84 > div {
62 display: block; 85 display: flex;
86 align-items: center;
63 } 87 }
64 88
65 .icon-more { 89 input {
66 @include apply-svg-color($grey-foreground-color); 90 @include peertube-button;
91 @include orange-button;
67 92
68 &::after { 93 margin-top: 10px;
69 border: none;
70 }
71 } 94 }
95 }
96 }
97}
72 98
73 .dropdown-item { 99// Thanks Angular CDK <3 https://material.angular.io/cdk/drag-drop/examples
74 @include dropdown-with-icon-item; 100.cdk-drag-preview {
75 } 101 box-sizing: border-box;
102 border-radius: 4px;
103 box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
104 0 8px 10px 1px rgba(0, 0, 0, 0.14),
105 0 3px 14px 2px rgba(0, 0, 0, 0.12);
106}
76 107
77 .timestamp-options { 108.cdk-drag-placeholder {
78 padding-top: 0; 109 opacity: 0;
79 padding-left: 35px; 110}
80 margin-bottom: 15px;
81 111
82 > div { 112.cdk-drag-animating {
83 display: flex; 113 transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
84 align-items: center; 114}
85 }
86 115
87 input { 116.video:last-child {
88 @include peertube-button; 117 border: none;
89 @include orange-button; 118}
90 119
91 margin-top: 10px; 120.videos.cdk-drop-list-dragging .video:not(.cdk-drag-placeholder) {
92 } 121 transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);
93 }
94 }
95 }
96} 122}
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
index 76aff3d4f..4076a3721 100644
--- 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
@@ -4,7 +4,7 @@ import { AuthService } from '../../core/auth'
4import { ConfirmService } from '../../core/confirm' 4import { ConfirmService } from '../../core/confirm'
5import { ComponentPagination } from '@app/shared/rest/component-pagination.model' 5import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
6import { Video } from '@app/shared/video/video.model' 6import { Video } from '@app/shared/video/video.model'
7import { Subscription } from 'rxjs' 7import { Subject, Subscription } from 'rxjs'
8import { ActivatedRoute } from '@angular/router' 8import { ActivatedRoute } from '@angular/router'
9import { VideoService } from '@app/shared/video/video.service' 9import { VideoService } from '@app/shared/video/video.service'
10import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' 10import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
@@ -13,6 +13,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill'
13import { secondsToTime } from '../../../assets/player/utils' 13import { secondsToTime } from '../../../assets/player/utils'
14import { VideoPlaylistElementUpdate } from '@shared/models' 14import { VideoPlaylistElementUpdate } from '@shared/models'
15import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' 15import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap'
16import { CdkDragDrop, CdkDragMove } from '@angular/cdk/drag-drop'
17import { throttleTime } from 'rxjs/operators'
16 18
17@Component({ 19@Component({
18 selector: 'my-account-video-playlist-elements', 20 selector: 'my-account-video-playlist-elements',
@@ -42,6 +44,7 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
42 44
43 private videoPlaylistId: string | number 45 private videoPlaylistId: string | number
44 private paramsSub: Subscription 46 private paramsSub: Subscription
47 private dragMoveSubject = new Subject<number>()
45 48
46 constructor ( 49 constructor (
47 private authService: AuthService, 50 private authService: AuthService,
@@ -61,12 +64,66 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
61 64
62 this.loadPlaylistInfo() 65 this.loadPlaylistInfo()
63 }) 66 })
67
68 this.dragMoveSubject.asObservable()
69 .pipe(throttleTime(200))
70 .subscribe(y => this.checkScroll(y))
64 } 71 }
65 72
66 ngOnDestroy () { 73 ngOnDestroy () {
67 if (this.paramsSub) this.paramsSub.unsubscribe() 74 if (this.paramsSub) this.paramsSub.unsubscribe()
68 } 75 }
69 76
77 drop (event: CdkDragDrop<any>) {
78 const previousIndex = event.previousIndex
79 const newIndex = event.currentIndex
80
81 if (previousIndex === newIndex) return
82
83 const oldPosition = this.videos[previousIndex].playlistElement.position
84 const insertAfter = newIndex === 0 ? 0 : this.videos[newIndex].playlistElement.position
85
86 this.videoPlaylistService.reorderPlaylist(this.playlist.id, oldPosition, insertAfter)
87 .subscribe(
88 () => { /* nothing to do */ },
89
90 err => this.notifier.error(err.message)
91 )
92
93 const video = this.videos[previousIndex]
94
95 this.videos.splice(previousIndex, 1)
96 this.videos.splice(newIndex, 0, video)
97
98 this.reorderClientPositions()
99 }
100
101 onDragMove (event: CdkDragMove<any>) {
102 this.dragMoveSubject.next(event.pointerPosition.y)
103 }
104
105 checkScroll (pointerY: number) {
106 // FIXME: Uncomment when https://github.com/angular/material2/issues/14098 is fixed
107 // FIXME: Remove when https://github.com/angular/material2/issues/13588 is implemented
108 // if (pointerY < 150) {
109 // window.scrollBy({
110 // left: 0,
111 // top: -20,
112 // behavior: 'smooth'
113 // })
114 //
115 // return
116 // }
117 //
118 // if (window.innerHeight - pointerY <= 50) {
119 // window.scrollBy({
120 // left: 0,
121 // top: 20,
122 // behavior: 'smooth'
123 // })
124 // }
125 }
126
70 isVideoBlur (video: Video) { 127 isVideoBlur (video: Video) {
71 return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig()) 128 return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig())
72 } 129 }
@@ -78,6 +135,7 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
78 this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName })) 135 this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName }))
79 136
80 this.videos = this.videos.filter(v => v.id !== video.id) 137 this.videos = this.videos.filter(v => v.id !== video.id)
138 this.reorderClientPositions()
81 }, 139 },
82 140
83 err => this.notifier.error(err.message) 141 err => this.notifier.error(err.message)
@@ -173,4 +231,13 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro
173 this.playlist = playlist 231 this.playlist = playlist
174 }) 232 })
175 } 233 }
234
235 private reorderClientPositions () {
236 let i = 1
237
238 for (const video of this.videos) {
239 video.playlistElement.position = i
240 i++
241 }
242 }
176} 243}
diff --git a/client/src/app/+my-account/my-account.module.ts b/client/src/app/+my-account/my-account.module.ts
index ba8300111..4a18a9968 100644
--- a/client/src/app/+my-account/my-account.module.ts
+++ b/client/src/app/+my-account/my-account.module.ts
@@ -35,6 +35,7 @@ import { MyAccountVideoPlaylistsComponent } from '@app/+my-account/my-account-vi
35import { 35import {
36 MyAccountVideoPlaylistElementsComponent 36 MyAccountVideoPlaylistElementsComponent
37} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' 37} from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component'
38import { DragDropModule } from '@angular/cdk/drag-drop'
38 39
39@NgModule({ 40@NgModule({
40 imports: [ 41 imports: [
@@ -43,7 +44,8 @@ import {
43 AutoCompleteModule, 44 AutoCompleteModule,
44 SharedModule, 45 SharedModule,
45 TableModule, 46 TableModule,
46 InputSwitchModule 47 InputSwitchModule,
48 DragDropModule
47 ], 49 ],
48 50
49 declarations: [ 51 declarations: [
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 1f9eee0b7..05da0d829 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -9,7 +9,6 @@ import { InfiniteScrollerDirective } from '@app/shared/video/infinite-scroller.d
9 9
10import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' 10import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes'
11import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' 11import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared'
12import { KeyFilterModule } from 'primeng/keyfilter'
13 12
14import { AUTH_INTERCEPTOR_PROVIDER } from './auth' 13import { AUTH_INTERCEPTOR_PROVIDER } from './auth'
15import { ButtonComponent } from './buttons/button.component' 14import { ButtonComponent } from './buttons/button.component'
@@ -95,7 +94,6 @@ import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.compo
95 94
96 PrimeSharedModule, 95 PrimeSharedModule,
97 InputMaskModule, 96 InputMaskModule,
98 KeyFilterModule,
99 NgPipesModule 97 NgPipesModule
100 ], 98 ],
101 99
@@ -155,7 +153,6 @@ import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.compo
155 153
156 PrimeSharedModule, 154 PrimeSharedModule,
157 InputMaskModule, 155 InputMaskModule,
158 KeyFilterModule,
159 BytesPipe, 156 BytesPipe,
160 KeysPipe, 157 KeysPipe,
161 158
diff --git a/client/src/app/shared/video-playlist/video-playlist.service.ts b/client/src/app/shared/video-playlist/video-playlist.service.ts
index f7b37f83a..da7437507 100644
--- a/client/src/app/shared/video-playlist/video-playlist.service.ts
+++ b/client/src/app/shared/video-playlist/video-playlist.service.ts
@@ -17,6 +17,7 @@ import { AccountService } from '@app/shared/account/account.service'
17import { Account } from '@app/shared/account/account.model' 17import { Account } from '@app/shared/account/account.model'
18import { RestService } from '@app/shared/rest' 18import { RestService } from '@app/shared/rest'
19import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' 19import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model'
20import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model'
20 21
21@Injectable() 22@Injectable()
22export class VideoPlaylistService { 23export class VideoPlaylistService {
@@ -125,6 +126,19 @@ export class VideoPlaylistService {
125 ) 126 )
126 } 127 }
127 128
129 reorderPlaylist (playlistId: number, oldPosition: number, newPosition: number) {
130 const body: VideoPlaylistReorder = {
131 startPosition: oldPosition,
132 insertAfterPosition: newPosition
133 }
134
135 return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/reorder', body)
136 .pipe(
137 map(this.restExtractor.extractDataBool),
138 catchError(err => this.restExtractor.handleError(err))
139 )
140 }
141
128 doesVideoExistInPlaylist (videoId: number) { 142 doesVideoExistInPlaylist (videoId: number) {
129 this.videoExistsInPlaylistSubject.next(videoId) 143 this.videoExistsInPlaylistSubject.next(videoId)
130 144
diff --git a/client/yarn.lock b/client/yarn.lock
index 2d3ade3dd..8f643aad4 100644
--- a/client/yarn.lock
+++ b/client/yarn.lock
@@ -107,6 +107,15 @@
107 dependencies: 107 dependencies:
108 tslib "^1.9.0" 108 tslib "^1.9.0"
109 109
110"@angular/cdk@^7.3.4":
111 version "7.3.4"
112 resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-7.3.4.tgz#b9d8c62cdd24fa6517dd3ae68c78632b1525d35f"
113 integrity sha512-cHl1o7obogCO3Nxf9n8MrXpfHa7AH1QNX2BY+bftYBTHW++YJe+qAwkwWLVqnJD9TQE2OpiR058zoJU20khM/g==
114 dependencies:
115 tslib "^1.7.1"
116 optionalDependencies:
117 parse5 "^5.0.0"
118
110"@angular/cli@~7.3.1": 119"@angular/cli@~7.3.1":
111 version "7.3.1" 120 version "7.3.1"
112 resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-7.3.1.tgz#a18acdec84deb03a1fae79cae415bbc8f9c87ffa" 121 resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-7.3.1.tgz#a18acdec84deb03a1fae79cae415bbc8f9c87ffa"
@@ -7469,6 +7478,11 @@ parse5@4.0.0:
7469 resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608" 7478 resolved "https://registry.yarnpkg.com/parse5/-/parse5-4.0.0.tgz#6d78656e3da8d78b4ec0b906f7c08ef1dfe3f608"
7470 integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA== 7479 integrity sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==
7471 7480
7481parse5@^5.0.0:
7482 version "5.1.0"
7483 resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
7484 integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
7485
7472parseqs@0.0.5: 7486parseqs@0.0.5:
7473 version "0.0.5" 7487 version "0.0.5"
7474 resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d" 7488 resolved "https://registry.yarnpkg.com/parseqs/-/parseqs-0.0.5.tgz#d5208a3738e46766e291ba2ea173684921a8b89d"