Move video watch playlist in its own component
authorChocobozzz <me@florianbigard.com>
Mon, 13 May 2019 09:18:24 +0000 (11:18 +0200)
committerChocobozzz <me@florianbigard.com>
Mon, 13 May 2019 09:19:29 +0000 (11:19 +0200)
client/src/app/shared/video-playlist/video-playlist-element-miniature.component.scss
client/src/app/shared/video/video-actions-dropdown.component.ts
client/src/app/videos/+video-watch/video-watch-playlist.component.html [new file with mode: 0644]
client/src/app/videos/+video-watch/video-watch-playlist.component.scss [new file with mode: 0644]
client/src/app/videos/+video-watch/video-watch-playlist.component.ts [new file with mode: 0644]
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.scss
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/app/videos/+video-watch/video-watch.module.ts

index f8a068cbc4a5b076306c345f92064f95a6f99751..cb7072d7f3fe4559ab3d19bef58bdb6023ef4155 100644 (file)
@@ -2,6 +2,13 @@
 @import '_mixins';
 @import '_miniature';
 
+my-video-thumbnail {
+  @include thumbnail-size-component(130px, 72px);
+
+  display: flex; // Avoids an issue with line-height that adds space below the element
+  margin-right: 10px;
+}
+
 .video {
   display: flex;
   align-items: center;
       }
     }
 
-    my-video-thumbnail {
-      @include thumbnail-size-component(130px, 72px);
-
-      display: flex; // Avoids an issue with line-height that adds space below the element
-      margin-right: 10px;
-    }
-
     .video-info {
       display: flex;
       flex-direction: column;
index b2d77a9e675fab722c507ef7c4b455c8b85465e0..c1da0eba6e37920459d71c7ca77f22585d70dee2 100644 (file)
@@ -1,4 +1,4 @@
-import { AfterContentInit, AfterViewInit, Component, EventEmitter, Input, OnChanges, OnInit, Output, ViewChild } from '@angular/core'
+import { AfterViewInit, Component, EventEmitter, Input, OnChanges, Output, ViewChild } from '@angular/core'
 import { I18n } from '@ngx-translate/i18n-polyfill'
 import { DropdownAction, DropdownButtonSize, DropdownDirection } from '@app/shared/buttons/action-dropdown.component'
 import { AuthService, ConfirmService, Notifier, ServerService } from '@app/core'
@@ -133,6 +133,10 @@ export class VideoActionsDropdownComponent implements AfterViewInit, OnChanges {
     return this.video.isUnblacklistableBy(this.user)
   }
 
+  isVideoDownloadable () {
+    return this.video && this.video instanceof VideoDetails && this.video.downloadEnabled
+  }
+
   /* Action handlers */
 
   async unblacklistVideo () {
@@ -202,7 +206,7 @@ export class VideoActionsDropdownComponent implements AfterViewInit, OnChanges {
         {
           label: this.i18n('Download'),
           handler: () => this.showDownloadModal(),
-          isDisplayed: () => this.displayOptions.download,
+          isDisplayed: () => this.displayOptions.download && this.isVideoDownloadable(),
           iconName: 'download'
         },
         {
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.html b/client/src/app/videos/+video-watch/video-watch-playlist.component.html
new file mode 100644 (file)
index 0000000..c168a31
--- /dev/null
@@ -0,0 +1,25 @@
+<div *ngIf="playlist && video" class="playlist" myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()">
+  <div class="playlist-info">
+    <div class="playlist-display-name">
+      {{ playlist.displayName }}
+
+      <span *ngIf="isUnlistedPlaylist()" class="badge badge-warning" i18n>Unlisted</span>
+      <span *ngIf="isPrivatePlaylist()" class="badge badge-danger" i18n>Private</span>
+      <span *ngIf="isPublicPlaylist()" class="badge badge-info" i18n>Public</span>
+    </div>
+
+    <div class="playlist-by-index">
+      <div class="playlist-by">{{ playlist.ownerBy }}</div>
+      <div class="playlist-index">
+        <span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
+      </div>
+    </div>
+  </div>
+
+  <div *ngFor="let playlistVideo of playlistVideos">
+    <my-video-playlist-element-miniature
+      [video]="playlistVideo" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)"
+      [playing]="currentPlaylistPosition === playlistVideo.playlistElement.position" [accountLink]="false" [position]="playlistVideo.playlistElement.position"
+    ></my-video-playlist-element-miniature>
+  </div>
+</div>
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.scss b/client/src/app/videos/+video-watch/video-watch-playlist.component.scss
new file mode 100644 (file)
index 0000000..5da55c2
--- /dev/null
@@ -0,0 +1,59 @@
+@import '_variables';
+@import '_mixins';
+@import '_bootstrap-variables';
+@import '_miniature';
+
+.playlist {
+  min-width: 200px;
+  max-width: 470px;
+  height: 66vh;
+  background-color: var(--mainBackgroundColor);
+  overflow-y: auto;
+  border-bottom: 1px solid $separator-border-color;
+
+  .playlist-info {
+    padding: 5px 30px;
+    background-color: #e4e4e4;
+
+    .playlist-display-name {
+      font-size: 18px;
+      font-weight: $font-semibold;
+      margin-bottom: 5px;
+    }
+
+    .playlist-by-index {
+      color: $grey-foreground-color;
+      display: flex;
+
+      .playlist-by {
+        margin-right: 5px;
+      }
+
+      .playlist-index span:first-child::after {
+        content: '/';
+        margin: 0 3px;
+      }
+    }
+  }
+
+  my-video-playlist-element-miniature {
+    /deep/ {
+      .video {
+        .position {
+          margin-right: 0;
+        }
+
+        .video-info {
+          .video-info-name {
+            font-size: 15px;
+          }
+        }
+      }
+
+      my-video-thumbnail {
+        @include thumbnail-size-component(90px, 50px);
+      }
+    }
+  }
+}
+
diff --git a/client/src/app/videos/+video-watch/video-watch-playlist.component.ts b/client/src/app/videos/+video-watch/video-watch-playlist.component.ts
new file mode 100644 (file)
index 0000000..3ac06c0
--- /dev/null
@@ -0,0 +1,111 @@
+import { Component, Input } from '@angular/core'
+import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
+import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
+import { Video } from '@app/shared/video/video.model'
+import { VideoDetails, VideoPlaylistPrivacy } from '@shared/models'
+import { VideoService } from '@app/shared/video/video.service'
+import { Router } from '@angular/router'
+import { AuthService } from '@app/core'
+
+@Component({
+  selector: 'my-video-watch-playlist',
+  templateUrl: './video-watch-playlist.component.html',
+  styleUrls: [ './video-watch-playlist.component.scss' ]
+})
+export class VideoWatchPlaylistComponent {
+  @Input() video: VideoDetails
+  @Input() playlist: VideoPlaylist
+
+  playlistVideos: Video[] = []
+  playlistPagination: ComponentPagination = {
+    currentPage: 1,
+    itemsPerPage: 30,
+    totalItems: null
+  }
+
+  noPlaylistVideos = false
+  currentPlaylistPosition = 1
+
+  constructor (
+    private auth: AuthService,
+    private videoService: VideoService,
+    private router: Router
+  ) {}
+
+  onPlaylistVideosNearOfBottom () {
+    // Last page
+    if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return
+
+    this.playlistPagination.currentPage += 1
+    this.loadPlaylistElements(this.playlist,false)
+  }
+
+  onElementRemoved (video: Video) {
+    this.playlistVideos = this.playlistVideos.filter(v => v.id !== video.id)
+
+    this.playlistPagination.totalItems--
+  }
+
+  isPlaylistOwned () {
+    return this.playlist.isLocal === true && this.playlist.ownerAccount.name === this.auth.getUser().username
+  }
+
+  isUnlistedPlaylist () {
+    return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED
+  }
+
+  isPrivatePlaylist () {
+    return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE
+  }
+
+  isPublicPlaylist () {
+    return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC
+  }
+
+  loadPlaylistElements (playlist: VideoPlaylist, redirectToFirst = false) {
+    this.videoService.getPlaylistVideos(playlist.uuid, this.playlistPagination)
+        .subscribe(({ totalVideos, videos }) => {
+          this.playlistVideos = this.playlistVideos.concat(videos)
+          this.playlistPagination.totalItems = totalVideos
+
+          if (totalVideos === 0) {
+            this.noPlaylistVideos = true
+            return
+          }
+
+          this.updatePlaylistIndex(this.video)
+
+          if (redirectToFirst) {
+            const extras = {
+              queryParams: { videoId: this.playlistVideos[ 0 ].uuid },
+              replaceUrl: true
+            }
+            this.router.navigate([], extras)
+          }
+        })
+  }
+
+  updatePlaylistIndex (video: VideoDetails) {
+    if (this.playlistVideos.length === 0 || !video) return
+
+    for (const playlistVideo of this.playlistVideos) {
+      if (playlistVideo.id === video.id) {
+        this.currentPlaylistPosition = playlistVideo.playlistElement.position
+        return
+      }
+    }
+
+    // Load more videos to find our video
+    this.onPlaylistVideosNearOfBottom()
+  }
+
+  navigateToNextPlaylistVideo () {
+    if (this.currentPlaylistPosition < this.playlistPagination.totalItems) {
+      const next = this.playlistVideos.find(v => v.playlistElement.position === this.currentPlaylistPosition + 1)
+
+      const start = next.playlistElement.startTimestamp
+      const stop = next.playlistElement.stopTimestamp
+      this.router.navigate([],{ queryParams: { videoId: next.uuid, start, stop } })
+    }
+  }
+}
index 7e9b89dd0d88b073a152c6891105a47e50840a11..7da74b57ef4b3bd69acbb356576defe9ae1c6353 100644 (file)
@@ -9,31 +9,10 @@
 
     <div id="videojs-wrapper"></div>
 
-    <div *ngIf="playlist && video" class="playlist"  myInfiniteScroller [autoInit]="true" [onItself]="true" (nearOfBottom)="onPlaylistVideosNearOfBottom()">
-      <div class="playlist-info">
-        <div class="playlist-display-name">
-          {{ playlist.displayName }}
-
-          <span *ngIf="isUnlistedPlaylist()" class="badge badge-warning" i18n>Unlisted</span>
-          <span *ngIf="isPrivatePlaylist()" class="badge badge-danger" i18n>Private</span>
-          <span *ngIf="isPublicPlaylist()" class="badge badge-info" i18n>Public</span>
-        </div>
-
-        <div class="playlist-by-index">
-          <div class="playlist-by">{{ playlist.ownerBy }}</div>
-          <div class="playlist-index">
-            <span>{{ currentPlaylistPosition }}</span><span>{{ playlistPagination.totalItems }}</span>
-          </div>
-        </div>
-      </div>
-
-      <div *ngFor="let playlistVideo of playlistVideos">
-        <my-video-playlist-element-miniature
-          [video]="playlistVideo" [playlist]="playlist" [owned]="isPlaylistOwned()" (elementRemoved)="onElementRemoved($event)"
-          [playing]="currentPlaylistPosition === playlistVideo.playlistElement.position" [accountLink]="false" [position]="playlistVideo.playlistElement.position"
-        ></my-video-playlist-element-miniature>
-      </div>
-    </div>
+    <my-video-watch-playlist
+      #videoWatchPlaylist
+      [video]="video" [playlist]="playlist" class="playlist"
+    ></my-video-watch-playlist>
   </div>
 
   <div class="row">
index d8113b666de6abd2eeb5504309863bc1270c2eb1..8ca5c4118e7d53ab48a39465a565ddfab07fc96c 100644 (file)
@@ -15,10 +15,10 @@ $player-factor: 1.7; // 16/9
 }
 
 @mixin playlist-below-player {
-  width: 100%;
-  height: auto;
-  max-height: 300px;
-  border-bottom: 1px solid $separator-border-color;
+  width: 100% !important;
+  height: auto !important;
+  max-height: 300px !important;
+  border-bottom: 1px solid $separator-border-color !important;
 }
 
 .root {
@@ -37,7 +37,7 @@ $player-factor: 1.7; // 16/9
       width: 100%;
     }
 
-    .playlist {
+    my-video-watch-playlist /deep/ .playlist {
       @include playlist-below-player;
     }
   }
@@ -80,60 +80,6 @@ $player-factor: 1.7; // 16/9
     }
   }
 
-  .playlist {
-    min-width: 200px;
-    max-width: 470px;
-    height: 66vh;
-    background-color: var(--mainBackgroundColor);
-    overflow-y: auto;
-    border-bottom: 1px solid $separator-border-color;
-
-    .playlist-info {
-      padding: 5px 30px;
-      background-color: #e4e4e4;
-
-      .playlist-display-name {
-        font-size: 18px;
-        font-weight: $font-semibold;
-        margin-bottom: 5px;
-      }
-
-      .playlist-by-index {
-        color: $grey-foreground-color;
-        display: flex;
-
-        .playlist-by {
-          margin-right: 5px;
-        }
-
-        .playlist-index span:first-child::after {
-          content: '/';
-          margin: 0 3px;
-        }
-      }
-    }
-
-    my-video-playlist-element-miniature {
-      /deep/ {
-        .video {
-          .position {
-            margin-right: 0;
-          }
-
-          .video-info {
-            .video-info-name {
-              font-size: 15px;
-            }
-          }
-        }
-
-        my-video-thumbnail {
-          @include thumbnail-size-component(90px, 50px);
-        }
-      }
-    }
-  }
-
   /deep/ .video-js {
     width: getPlayerWidth(66vh);
     height: 66vh;
@@ -508,7 +454,7 @@ my-video-comments {
     flex-direction: column;
     justify-content: center;
 
-    .playlist {
+    my-video-watch-playlist /deep/ .playlist {
       @include playlist-below-player;
     }
   }
index bce65221085f2908ee1f2032aa4d235fdd936abf..0532e7de7f2f02408ae18a447472382558ecff07 100644 (file)
@@ -8,7 +8,7 @@ import { MetaService } from '@ngx-meta/core'
 import { Notifier, ServerService } from '@app/core'
 import { forkJoin, Subscription } from 'rxjs'
 import { Hotkey, HotkeysService } from 'angular2-hotkeys'
-import { UserVideoRateType, VideoCaption, VideoPlaylistPrivacy, VideoPrivacy, VideoState } from '../../../../../shared'
+import { UserVideoRateType, VideoCaption, VideoPrivacy, VideoState } from '../../../../../shared'
 import { AuthService, ConfirmService } from '../../core'
 import { RestExtractor, VideoBlacklistService } from '../../shared'
 import { VideoDetails } from '../../shared/video/video-details.model'
@@ -27,9 +27,9 @@ import {
 } from '../../../assets/player/peertube-player-manager'
 import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
-import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
 import { Video } from '@app/shared/video/video.model'
 import { isWebRTCDisabled } from '../../../assets/player/utils'
+import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
 
 @Component({
   selector: 'my-video-watch',
@@ -39,6 +39,7 @@ import { isWebRTCDisabled } from '../../../assets/player/utils'
 export class VideoWatchComponent implements OnInit, OnDestroy {
   private static LOCAL_STORAGE_PRIVACY_CONCERN_KEY = 'video-watch-privacy-concern'
 
+  @ViewChild('videoWatchPlaylist') videoWatchPlaylist: VideoWatchPlaylistComponent
   @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
   @ViewChild('videoSupportModal') videoSupportModal: VideoSupportComponent
   @ViewChild('subscribeButton') subscribeButton: SubscribeButtonComponent
@@ -51,14 +52,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
   descriptionLoading = false
 
   playlist: VideoPlaylist = null
-  playlistVideos: Video[] = []
-  playlistPagination: ComponentPagination = {
-    currentPage: 1,
-    itemsPerPage: 30,
-    totalItems: null
-  }
-  noPlaylistVideos = false
-  currentPlaylistPosition = 1
 
   completeDescriptionShown = false
   completeVideoDescription: string
@@ -230,10 +223,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return this.video.tags
   }
 
-  isVideoRemovable () {
-    return this.video.isRemovableBy(this.authService.getUser())
-  }
-
   onVideoRemoved () {
     this.redirectService.redirectToHomepage()
   }
@@ -247,10 +236,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return this.video && this.video.state.id === VideoState.TO_TRANSCODE
   }
 
-  isVideoDownloadable () {
-    return this.video && this.video.downloadEnabled
-  }
-
   isVideoToImport () {
     return this.video && this.video.state.id === VideoState.TO_IMPORT
   }
@@ -263,36 +248,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     return video.isVideoNSFWForUser(this.user, this.serverService.getConfig())
   }
 
-  isPlaylistOwned () {
-    return this.playlist.isLocal === true && this.playlist.ownerAccount.name === this.user.username
-  }
-
-  isUnlistedPlaylist () {
-    return this.playlist.privacy.id === VideoPlaylistPrivacy.UNLISTED
-  }
-
-  isPrivatePlaylist () {
-    return this.playlist.privacy.id === VideoPlaylistPrivacy.PRIVATE
-  }
-
-  isPublicPlaylist () {
-    return this.playlist.privacy.id === VideoPlaylistPrivacy.PUBLIC
-  }
-
-  onPlaylistVideosNearOfBottom () {
-    // Last page
-    if (this.playlistPagination.totalItems <= (this.playlistPagination.currentPage * this.playlistPagination.itemsPerPage)) return
-
-    this.playlistPagination.currentPage += 1
-    this.loadPlaylistElements(false)
-  }
-
-  onElementRemoved (video: Video) {
-    this.playlistVideos = this.playlistVideos.filter(v => v.id !== video.id)
-
-    this.playlistPagination.totalItems--
-  }
-
   private loadVideo (videoId: string) {
     // Video did not change
     if (this.video && this.video.uuid === videoId) return
@@ -333,33 +288,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
         this.playlist = playlist
 
         const videoId = this.route.snapshot.queryParams['videoId']
-        this.loadPlaylistElements(!videoId)
+        this.videoWatchPlaylist.loadPlaylistElements(playlist, !videoId)
       })
   }
 
-  private loadPlaylistElements (redirectToFirst = false) {
-    this.videoService.getPlaylistVideos(this.playlist.uuid, this.playlistPagination)
-        .subscribe(({ totalVideos, videos }) => {
-          this.playlistVideos = this.playlistVideos.concat(videos)
-          this.playlistPagination.totalItems = totalVideos
-
-          if (totalVideos === 0) {
-            this.noPlaylistVideos = true
-            return
-          }
-
-          this.updatePlaylistIndex()
-
-          if (redirectToFirst) {
-            const extras = {
-              queryParams: { videoId: this.playlistVideos[ 0 ].uuid },
-              replaceUrl: true
-            }
-            this.router.navigate([], extras)
-          }
-        })
-  }
-
   private updateVideoDescription (description: string) {
     this.video.description = description
     this.setVideoDescriptionHTML()
@@ -421,7 +353,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.remoteServerDown = false
     this.currentTime = undefined
 
-    this.updatePlaylistIndex()
+    this.videoWatchPlaylist.updatePlaylistIndex(video)
 
     let startTime = urlOptions.startTime || (this.video.userHistory ? this.video.userHistory.currentTime : 0)
     // If we are at the end of the video, reset the timer
@@ -519,13 +451,13 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
 
       this.player.one('ended', () => {
         if (this.playlist) {
-          this.zone.run(() => this.navigateToNextPlaylistVideo())
+          this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
         }
       })
 
       this.player.one('stopped', () => {
         if (this.playlist) {
-          this.zone.run(() => this.navigateToNextPlaylistVideo())
+          this.zone.run(() => this.videoWatchPlaylist.navigateToNextPlaylistVideo())
         }
       })
 
@@ -586,20 +518,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     this.setVideoLikesBarTooltipText()
   }
 
-  private updatePlaylistIndex () {
-    if (this.playlistVideos.length === 0 || !this.video) return
-
-    for (const video of this.playlistVideos) {
-      if (video.id === this.video.id) {
-        this.currentPlaylistPosition = video.playlistElement.position
-        return
-      }
-    }
-
-    // Load more videos to find our video
-    this.onPlaylistVideosNearOfBottom()
-  }
-
   private setOpenGraphTags () {
     this.metaService.setTitle(this.video.name)
 
@@ -639,14 +557,4 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
       this.player = undefined
     }
   }
-
-  private navigateToNextPlaylistVideo () {
-    if (this.currentPlaylistPosition < this.playlistPagination.totalItems) {
-      const next = this.playlistVideos.find(v => v.playlistElement.position === this.currentPlaylistPosition + 1)
-
-      const start = next.playlistElement.startTimestamp
-      const stop = next.playlistElement.stopTimestamp
-      this.router.navigate([],{ queryParams: { videoId: next.uuid, start, stop } })
-    }
-  }
 }
index 983350f52c2c7bf73a39f0b094876d584981ad22..67596a3da41794c7b0075cdb6a5005a4ef12ab6f 100644 (file)
@@ -11,6 +11,7 @@ import { VideoWatchComponent } from './video-watch.component'
 import { NgxQRCodeModule } from 'ngx-qrcode2'
 import { NgbTooltipModule } from '@ng-bootstrap/ng-bootstrap'
 import { RecommendationsModule } from '@app/videos/recommendations/recommendations.module'
+import { VideoWatchPlaylistComponent } from '@app/videos/+video-watch/video-watch-playlist.component'
 
 @NgModule({
   imports: [
@@ -23,6 +24,7 @@ import { RecommendationsModule } from '@app/videos/recommendations/recommendatio
 
   declarations: [
     VideoWatchComponent,
+    VideoWatchPlaylistComponent,
 
     VideoShareComponent,
     VideoSupportComponent,