From 15e9d5ca39e0b792f61453fbf3885a0fc446afa7 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Tue, 12 Mar 2019 11:40:42 +0100 Subject: Playlist reorder support --- ...-account-video-playlist-elements.component.html | 7 +- ...-account-video-playlist-elements.component.scss | 154 ++++++++++++--------- ...my-account-video-playlist-elements.component.ts | 69 ++++++++- client/src/app/+my-account/my-account.module.ts | 4 +- client/src/app/shared/shared.module.ts | 3 - .../video-playlist/video-playlist.service.ts | 14 ++ 6 files changed, 180 insertions(+), 71 deletions(-) (limited to 'client/src/app') 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 @@
No videos in this playlist.
-
-
+
+
{{ video.playlistElement.position }}
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 @@ @import '_mixins'; @import '_miniature'; -.videos { - .video { +.video, .cdk-drag-preview { + display: flex; + align-items: center; + background-color: var(--mainBackgroundColor); + cursor: pointer; + padding: 10px; + border-bottom: 1px solid $separator-border-color; + + &:hover { + background-color: rgba(0, 0, 0, 0.05); + + .more { + display: block; + } + } + + .position { + font-weight: $font-semibold; + margin-right: 10px; + color: $grey-foreground-color; + min-width: 20px; + } + + my-video-thumbnail { + display: flex; // Avoids an issue with line-height that adds space below the element + margin-right: 10px; + + /deep/ .video-thumbnail { + @include miniature-thumbnail(130px, 72px); + } + } + + .video-info { display: flex; - align-items: center; - padding: 10px; - border-bottom: 1px solid $separator-border-color; + flex-direction: column; - &:hover { - background-color: rgba(0, 0, 0, 0.05); + a { + @include disable-default-a-behaviour; - .more { - display: block; - } + color: var(--mainForegroundColor); } - .position { + .video-info-name { + font-size: 18px; font-weight: $font-semibold; - margin-right: 10px; - color: $grey-foreground-color; } - my-video-thumbnail { - display: flex; // Avoids an issue with line-height that adds space below the element - margin-right: 10px; - - /deep/ .video-thumbnail { - @include miniature-thumbnail(130px, 72px); - } + .video-info-account, .video-info-timestamp { + color: $grey-foreground-color; } + } - .video-info { - display: flex; - flex-direction: column; + .more { + justify-self: flex-end; + margin-left: auto; + cursor: pointer; + display: none; - a { - @include disable-default-a-behaviour; + &.show { + display: block; + } - color: var(--mainForegroundColor); - } + .icon-more { + @include apply-svg-color($grey-foreground-color); - .video-info-name { - font-size: 18px; - font-weight: $font-semibold; + &::after { + border: none; } + } - .video-info-account, .video-info-timestamp { - color: $grey-foreground-color; - } + .dropdown-item { + @include dropdown-with-icon-item; } - .more { - justify-self: flex-end; - margin-left: auto; - cursor: pointer; - display: none; + .timestamp-options { + padding-top: 0; + padding-left: 35px; + margin-bottom: 15px; - &.show { - display: block; + > div { + display: flex; + align-items: center; } - .icon-more { - @include apply-svg-color($grey-foreground-color); + input { + @include peertube-button; + @include orange-button; - &::after { - border: none; - } + margin-top: 10px; } + } + } +} - .dropdown-item { - @include dropdown-with-icon-item; - } +// Thanks Angular CDK <3 https://material.angular.io/cdk/drag-drop/examples +.cdk-drag-preview { + box-sizing: border-box; + border-radius: 4px; + box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), + 0 8px 10px 1px rgba(0, 0, 0, 0.14), + 0 3px 14px 2px rgba(0, 0, 0, 0.12); +} - .timestamp-options { - padding-top: 0; - padding-left: 35px; - margin-bottom: 15px; +.cdk-drag-placeholder { + opacity: 0; +} - > div { - display: flex; - align-items: center; - } +.cdk-drag-animating { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); +} - input { - @include peertube-button; - @include orange-button; +.video:last-child { + border: none; +} - margin-top: 10px; - } - } - } - } +.videos.cdk-drop-list-dragging .video:not(.cdk-drag-placeholder) { + transition: transform 250ms cubic-bezier(0, 0, 0.2, 1); } 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' import { ConfirmService } from '../../core/confirm' import { ComponentPagination } from '@app/shared/rest/component-pagination.model' import { Video } from '@app/shared/video/video.model' -import { Subscription } from 'rxjs' +import { Subject, Subscription } from 'rxjs' import { ActivatedRoute } from '@angular/router' import { VideoService } from '@app/shared/video/video.service' import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' @@ -13,6 +13,8 @@ import { I18n } from '@ngx-translate/i18n-polyfill' import { secondsToTime } from '../../../assets/player/utils' import { VideoPlaylistElementUpdate } from '@shared/models' import { NgbDropdown } from '@ng-bootstrap/ng-bootstrap' +import { CdkDragDrop, CdkDragMove } from '@angular/cdk/drag-drop' +import { throttleTime } from 'rxjs/operators' @Component({ selector: 'my-account-video-playlist-elements', @@ -42,6 +44,7 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro private videoPlaylistId: string | number private paramsSub: Subscription + private dragMoveSubject = new Subject() constructor ( private authService: AuthService, @@ -61,12 +64,66 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro this.loadPlaylistInfo() }) + + this.dragMoveSubject.asObservable() + .pipe(throttleTime(200)) + .subscribe(y => this.checkScroll(y)) } ngOnDestroy () { if (this.paramsSub) this.paramsSub.unsubscribe() } + drop (event: CdkDragDrop) { + const previousIndex = event.previousIndex + const newIndex = event.currentIndex + + if (previousIndex === newIndex) return + + const oldPosition = this.videos[previousIndex].playlistElement.position + const insertAfter = newIndex === 0 ? 0 : this.videos[newIndex].playlistElement.position + + this.videoPlaylistService.reorderPlaylist(this.playlist.id, oldPosition, insertAfter) + .subscribe( + () => { /* nothing to do */ }, + + err => this.notifier.error(err.message) + ) + + const video = this.videos[previousIndex] + + this.videos.splice(previousIndex, 1) + this.videos.splice(newIndex, 0, video) + + this.reorderClientPositions() + } + + onDragMove (event: CdkDragMove) { + this.dragMoveSubject.next(event.pointerPosition.y) + } + + checkScroll (pointerY: number) { + // FIXME: Uncomment when https://github.com/angular/material2/issues/14098 is fixed + // FIXME: Remove when https://github.com/angular/material2/issues/13588 is implemented + // if (pointerY < 150) { + // window.scrollBy({ + // left: 0, + // top: -20, + // behavior: 'smooth' + // }) + // + // return + // } + // + // if (window.innerHeight - pointerY <= 50) { + // window.scrollBy({ + // left: 0, + // top: 20, + // behavior: 'smooth' + // }) + // } + } + isVideoBlur (video: Video) { return video.isVideoNSFWForUser(this.authService.getUser(), this.serverService.getConfig()) } @@ -78,6 +135,7 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro this.notifier.success(this.i18n('Video removed from {{name}}', { name: this.playlist.displayName })) this.videos = this.videos.filter(v => v.id !== video.id) + this.reorderClientPositions() }, err => this.notifier.error(err.message) @@ -173,4 +231,13 @@ export class MyAccountVideoPlaylistElementsComponent implements OnInit, OnDestro this.playlist = playlist }) } + + private reorderClientPositions () { + let i = 1 + + for (const video of this.videos) { + video.playlistElement.position = i + i++ + } + } } 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 import { MyAccountVideoPlaylistElementsComponent } from '@app/+my-account/my-account-video-playlists/my-account-video-playlist-elements.component' +import { DragDropModule } from '@angular/cdk/drag-drop' @NgModule({ imports: [ @@ -43,7 +44,8 @@ import { AutoCompleteModule, SharedModule, TableModule, - InputSwitchModule + InputSwitchModule, + DragDropModule ], 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 import { BytesPipe, KeysPipe, NgPipesModule } from 'ngx-pipes' import { SharedModule as PrimeSharedModule } from 'primeng/components/common/shared' -import { KeyFilterModule } from 'primeng/keyfilter' import { AUTH_INTERCEPTOR_PROVIDER } from './auth' import { ButtonComponent } from './buttons/button.component' @@ -95,7 +94,6 @@ import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.compo PrimeSharedModule, InputMaskModule, - KeyFilterModule, NgPipesModule ], @@ -155,7 +153,6 @@ import { TimestampInputComponent } from '@app/shared/forms/timestamp-input.compo PrimeSharedModule, InputMaskModule, - KeyFilterModule, BytesPipe, KeysPipe, 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' import { Account } from '@app/shared/account/account.model' import { RestService } from '@app/shared/rest' import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' +import { VideoPlaylistReorder } from '@shared/models/videos/playlist/video-playlist-reorder.model' @Injectable() export class VideoPlaylistService { @@ -125,6 +126,19 @@ export class VideoPlaylistService { ) } + reorderPlaylist (playlistId: number, oldPosition: number, newPosition: number) { + const body: VideoPlaylistReorder = { + startPosition: oldPosition, + insertAfterPosition: newPosition + } + + return this.authHttp.post(VideoPlaylistService.BASE_VIDEO_PLAYLIST_URL + playlistId + '/videos/reorder', body) + .pipe( + map(this.restExtractor.extractDataBool), + catchError(err => this.restExtractor.handleError(err)) + ) + } + doesVideoExistInPlaylist (videoId: number) { this.videoExistsInPlaylistSubject.next(videoId) -- cgit v1.2.3