1
0
Fork 0

Move video watch playlist in its own component

This commit is contained in:
Chocobozzz 2019-05-13 11:18:24 +02:00
parent 722bca907b
commit 72675ebe01
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
9 changed files with 227 additions and 193 deletions

View 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;
@ -44,13 +51,6 @@
}
}
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;

View 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'
},
{

View File

@ -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>

View File

@ -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);
}
}
}
}

View File

@ -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 } })
}
}
}

View 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">

View 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;
}
}

View 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 } })
}
}
}

View 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,