Move watch later logic in miniature
This commit is contained in:
parent
ac0868bcc0
commit
b7819090de
8 changed files with 127 additions and 102 deletions
|
@ -1,11 +1,10 @@
|
|||
import { peertubeLocalStorage } from '@app/shared/misc/peertube-web-storage'
|
||||
import { UserRight } from '../../../../../shared/models/users/user-right.enum'
|
||||
import { User as ServerUserModel } from '../../../../../shared/models/users/user.model'
|
||||
import { MyUser as ServerMyUserModel, MyUserSpecialPlaylist } from '../../../../../shared/models/users/user.model'
|
||||
// Do not use the barrel (dependency loop)
|
||||
import { hasUserRight, UserRole } from '../../../../../shared/models/users/user-role'
|
||||
import { User } from '../../shared/users/user.model'
|
||||
import { NSFWPolicyType } from '../../../../../shared/models/videos/nsfw-policy.type'
|
||||
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
|
||||
|
||||
export type TokenOptions = {
|
||||
accessToken: string
|
||||
|
@ -67,7 +66,7 @@ class Tokens {
|
|||
}
|
||||
}
|
||||
|
||||
export class AuthUser extends User {
|
||||
export class AuthUser extends User implements ServerMyUserModel {
|
||||
private static KEYS = {
|
||||
ID: 'id',
|
||||
ROLE: 'role',
|
||||
|
@ -80,7 +79,7 @@ export class AuthUser extends User {
|
|||
}
|
||||
|
||||
tokens: Tokens
|
||||
specialPlaylists: Partial<VideoPlaylist>[]
|
||||
specialPlaylists: MyUserSpecialPlaylist[]
|
||||
|
||||
static load () {
|
||||
const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME)
|
||||
|
@ -115,9 +114,11 @@ export class AuthUser extends User {
|
|||
Tokens.flush()
|
||||
}
|
||||
|
||||
constructor (userHash: Partial<ServerUserModel>, hashTokens: TokenOptions) {
|
||||
constructor (userHash: Partial<ServerMyUserModel>, hashTokens: TokenOptions) {
|
||||
super(userHash)
|
||||
|
||||
this.tokens = new Tokens(hashTokens)
|
||||
this.specialPlaylists = userHash.specialPlaylists
|
||||
}
|
||||
|
||||
getAccessToken () {
|
||||
|
@ -141,7 +142,7 @@ export class AuthUser extends User {
|
|||
return hasUserRight(this.role, right)
|
||||
}
|
||||
|
||||
canManage (user: ServerUserModel) {
|
||||
canManage (user: ServerMyUserModel) {
|
||||
const myRole = this.role
|
||||
|
||||
if (myRole === UserRole.ADMINISTRATOR) return true
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { bufferTime, catchError, filter, first, map, share, switchMap } from 'rxjs/operators'
|
||||
import { bufferTime, catchError, distinctUntilChanged, filter, first, map, share, switchMap } from 'rxjs/operators'
|
||||
import { Injectable } from '@angular/core'
|
||||
import { Observable, ReplaySubject, Subject } from 'rxjs'
|
||||
import { RestExtractor } from '../rest/rest-extractor.service'
|
||||
|
@ -30,7 +30,6 @@ export class VideoPlaylistService {
|
|||
// Use a replay subject because we "next" a value before subscribing
|
||||
private videoExistsInPlaylistSubject: Subject<number> = new ReplaySubject(1)
|
||||
private readonly videoExistsInPlaylistObservable: Observable<VideoExistInPlaylist>
|
||||
private cachedWatchLaterPlaylists: VideoPlaylist[]
|
||||
|
||||
constructor (
|
||||
private authHttp: HttpClient,
|
||||
|
@ -39,6 +38,7 @@ export class VideoPlaylistService {
|
|||
private restService: RestService
|
||||
) {
|
||||
this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe(
|
||||
distinctUntilChanged(),
|
||||
bufferTime(500),
|
||||
filter(videoIds => videoIds.length !== 0),
|
||||
switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
|
||||
|
@ -224,7 +224,7 @@ export class VideoPlaylistService {
|
|||
let params = new HttpParams()
|
||||
params = this.restService.addObjectParams(params, { videoIds })
|
||||
|
||||
return this.authHttp.get<VideoExistInPlaylist>(url, { params })
|
||||
return this.authHttp.get<VideoExistInPlaylist>(url, { params, headers: { ignoreLoadingBar: '' } })
|
||||
.pipe(catchError(err => this.restExtractor.handleError(err)))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<div class="video-miniature" [ngClass]="{ 'display-as-row': displayAsRow }" (mouseenter)="loadActions()">
|
||||
<my-video-thumbnail #thumbnail [video]="video" [nsfw]="isVideoBlur"></my-video-thumbnail>
|
||||
<my-video-thumbnail
|
||||
[video]="video" [nsfw]="isVideoBlur"
|
||||
[displayWatchLaterPlaylist]="isWatchLaterPlaylistDisplayed()" [inWatchLaterPlaylist]="inWatchLaterPlaylist" (watchLaterClick)="onWatchLaterClick($event)"
|
||||
></my-video-thumbnail>
|
||||
|
||||
<div class="video-bottom">
|
||||
<div class="video-miniature-information">
|
||||
|
|
|
@ -1,12 +1,24 @@
|
|||
import { ChangeDetectionStrategy, Component, EventEmitter, Inject, Input, LOCALE_ID, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
Input,
|
||||
LOCALE_ID,
|
||||
OnInit,
|
||||
Output
|
||||
} from '@angular/core'
|
||||
import { User } from '../users'
|
||||
import { Video } from './video.model'
|
||||
import { ServerService } from '@app/core'
|
||||
import { ServerConfig, VideoPrivacy, VideoState } from '../../../../../shared'
|
||||
import { AuthService, ServerService } from '@app/core'
|
||||
import { ServerConfig, VideoPlaylistType, VideoPrivacy, VideoState } from '../../../../../shared'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
import { VideoActionsDisplayType } from '@app/shared/video/video-actions-dropdown.component'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { VideoThumbnailComponent } from './video-thumbnail.component'
|
||||
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
|
||||
import { forkJoin } from 'rxjs'
|
||||
import { first } from 'rxjs/operators'
|
||||
|
||||
export type OwnerDisplayType = 'account' | 'videoChannel' | 'auto'
|
||||
export type MiniatureDisplayOptions = {
|
||||
|
@ -47,8 +59,6 @@ export class VideoMiniatureComponent implements OnInit {
|
|||
@Output() videoUnblacklisted = new EventEmitter()
|
||||
@Output() videoRemoved = new EventEmitter()
|
||||
|
||||
@ViewChild('thumbnail', { static: true }) thumbnail: VideoThumbnailComponent
|
||||
|
||||
videoActionsDisplayOptions: VideoActionsDisplayType = {
|
||||
playlist: true,
|
||||
download: false,
|
||||
|
@ -60,14 +70,28 @@ export class VideoMiniatureComponent implements OnInit {
|
|||
showActions = false
|
||||
serverConfig: ServerConfig
|
||||
|
||||
addToWatchLaterText: string
|
||||
addedToWatchLaterText: string
|
||||
inWatchLaterPlaylist: boolean
|
||||
|
||||
watchLaterPlaylist: {
|
||||
id: number
|
||||
playlistElementId?: number
|
||||
}
|
||||
|
||||
private ownerDisplayTypeChosen: 'account' | 'videoChannel'
|
||||
|
||||
constructor (
|
||||
private screenService: ScreenService,
|
||||
private serverService: ServerService,
|
||||
private i18n: I18n,
|
||||
private authService: AuthService,
|
||||
private videoPlaylistService: VideoPlaylistService,
|
||||
private cd: ChangeDetectorRef,
|
||||
@Inject(LOCALE_ID) private localeId: string
|
||||
) { }
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
get isVideoBlur () {
|
||||
return this.video.isVideoNSFWForUser(this.user, this.serverConfig)
|
||||
|
@ -131,7 +155,8 @@ export class VideoMiniatureComponent implements OnInit {
|
|||
|
||||
loadActions () {
|
||||
if (this.displayVideoActions) this.showActions = true
|
||||
this.thumbnail.load()
|
||||
|
||||
this.loadWatchLater()
|
||||
}
|
||||
|
||||
onVideoBlacklisted () {
|
||||
|
@ -146,6 +171,38 @@ export class VideoMiniatureComponent implements OnInit {
|
|||
this.videoRemoved.emit()
|
||||
}
|
||||
|
||||
isUserLoggedIn () {
|
||||
return this.authService.isLoggedIn()
|
||||
}
|
||||
|
||||
onWatchLaterClick (currentState: boolean) {
|
||||
if (currentState === true) this.removeFromWatchLater()
|
||||
else this.addToWatchLater()
|
||||
|
||||
this.inWatchLaterPlaylist = !currentState
|
||||
}
|
||||
|
||||
addToWatchLater () {
|
||||
const body = { videoId: this.video.id }
|
||||
|
||||
this.videoPlaylistService.addVideoInPlaylist(this.watchLaterPlaylist.id, body).subscribe(
|
||||
res => {
|
||||
this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
removeFromWatchLater () {
|
||||
this.videoPlaylistService.removeVideoFromPlaylist(this.watchLaterPlaylist.id, this.watchLaterPlaylist.playlistElementId)
|
||||
.subscribe(
|
||||
_ => { /* empty */ }
|
||||
)
|
||||
}
|
||||
|
||||
isWatchLaterPlaylistDisplayed () {
|
||||
return this.inWatchLaterPlaylist !== undefined
|
||||
}
|
||||
|
||||
private setUpBy () {
|
||||
if (this.ownerDisplayType === 'account' || this.ownerDisplayType === 'videoChannel') {
|
||||
this.ownerDisplayTypeChosen = this.ownerDisplayType
|
||||
|
@ -163,4 +220,29 @@ export class VideoMiniatureComponent implements OnInit {
|
|||
this.ownerDisplayTypeChosen = 'videoChannel'
|
||||
}
|
||||
}
|
||||
|
||||
private loadWatchLater () {
|
||||
if (!this.isUserLoggedIn()) return
|
||||
|
||||
forkJoin([
|
||||
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id),
|
||||
this.authService.userInformationLoaded.pipe(first())
|
||||
]).subscribe(
|
||||
([ existResult ]) => {
|
||||
const watchLaterPlaylist = this.authService.getUser().specialPlaylists.find(p => p.type === VideoPlaylistType.WATCH_LATER)
|
||||
const existsInWatchLater = existResult[ this.video.id ].find(r => r.playlistId === watchLaterPlaylist.id)
|
||||
this.inWatchLaterPlaylist = false
|
||||
|
||||
this.watchLaterPlaylist = {
|
||||
id: watchLaterPlaylist.id
|
||||
}
|
||||
|
||||
if (existsInWatchLater) {
|
||||
this.inWatchLaterPlaylist = true
|
||||
this.watchLaterPlaylist.playlistElementId = existsInWatchLater.playlistElementId
|
||||
}
|
||||
|
||||
this.cd.markForCheck()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
<a
|
||||
[routerLink]="getVideoRouterLink()" [queryParams]="queryParams" [attr.title]="video.name"
|
||||
class="video-thumbnail"
|
||||
(mouseenter)="load()" (focus)="load()"
|
||||
>
|
||||
<img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" />
|
||||
<img alt="" [attr.aria-labelledby]="video.name" [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" loading="lazy" />
|
||||
|
||||
<div *ngIf="isUserLoggedIn()" class="video-thumbnail-actions-overlay">
|
||||
<ng-container *ngIf="addedToWatchLater !== true">
|
||||
<div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="addToWatchLater();$event.stopPropagation();false">
|
||||
<div *ngIf="displayWatchLaterPlaylist" class="video-thumbnail-actions-overlay">
|
||||
<ng-container *ngIf="inWatchLaterPlaylist !== true">
|
||||
<div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addToWatchLaterText" container="body" (click)="onWatchLaterClick($event)">
|
||||
<my-global-icon iconName="clock" [attr.aria-label]="addToWatchLaterText" role="button"></my-global-icon>
|
||||
</div>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="addedToWatchLater === true">
|
||||
<div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="removeFromWatchLater();$event.stopPropagation();false">
|
||||
|
||||
<ng-container *ngIf="inWatchLaterPlaylist === true">
|
||||
<div class="video-thumbnail-watch-later-overlay" placement="left" [ngbTooltip]="addedToWatchLaterText" container="body" (click)="onWatchLaterClick($event)">
|
||||
<my-global-icon iconName="tick" [attr.aria-label]="addedToWatchLaterText" role="button"></my-global-icon>
|
||||
</div>
|
||||
</ng-container>
|
||||
|
|
|
@ -35,13 +35,6 @@
|
|||
bottom: 5px;
|
||||
}
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
.video-thumbnail-actions-overlay {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.video-thumbnail-actions-overlay {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core'
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { Video } from './video.model'
|
||||
import { ScreenService } from '@app/shared/misc/screen.service'
|
||||
import { AuthService, ThemeService } from '@app/core'
|
||||
import { VideoPlaylistService } from '../video-playlist/video-playlist.service'
|
||||
import { VideoPlaylistElementCreate } from '../../../../../shared'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-thumbnail',
|
||||
|
@ -16,45 +14,20 @@ export class VideoThumbnailComponent {
|
|||
@Input() routerLink: any[]
|
||||
@Input() queryParams: any[]
|
||||
|
||||
addToWatchLaterText = 'Add to watch later'
|
||||
addedToWatchLaterText = 'Added to watch later'
|
||||
addedToWatchLater: boolean
|
||||
@Input() displayWatchLaterPlaylist: boolean
|
||||
@Input() inWatchLaterPlaylist: boolean
|
||||
|
||||
watchLaterPlaylist: any
|
||||
@Output() watchLaterClick = new EventEmitter<boolean>()
|
||||
|
||||
addToWatchLaterText: string
|
||||
addedToWatchLaterText: string
|
||||
|
||||
constructor (
|
||||
private screenService: ScreenService,
|
||||
private authService: AuthService,
|
||||
private videoPlaylistService: VideoPlaylistService,
|
||||
private cd: ChangeDetectorRef
|
||||
) {}
|
||||
|
||||
load () {
|
||||
if (this.addedToWatchLater !== undefined) return
|
||||
if (!this.isUserLoggedIn()) return
|
||||
|
||||
this.videoPlaylistService.doesVideoExistInPlaylist(this.video.id)
|
||||
.subscribe(
|
||||
existResult => {
|
||||
for (const playlist of this.authService.getUser().specialPlaylists) {
|
||||
const existingPlaylist = existResult[ this.video.id ].find(p => p.playlistId === playlist.id)
|
||||
this.addedToWatchLater = !!existingPlaylist
|
||||
|
||||
if (existingPlaylist) {
|
||||
this.watchLaterPlaylist = {
|
||||
playlistId: existingPlaylist.playlistId,
|
||||
playlistElementId: existingPlaylist.playlistElementId
|
||||
}
|
||||
} else {
|
||||
this.watchLaterPlaylist = {
|
||||
playlistId: playlist.id
|
||||
}
|
||||
}
|
||||
|
||||
this.cd.markForCheck()
|
||||
}
|
||||
}
|
||||
)
|
||||
private i18n: I18n
|
||||
) {
|
||||
this.addToWatchLaterText = this.i18n('Add to watch later')
|
||||
this.addedToWatchLaterText = this.i18n('Remove from watch later')
|
||||
}
|
||||
|
||||
getImageUrl () {
|
||||
|
@ -81,36 +54,10 @@ export class VideoThumbnailComponent {
|
|||
return [ '/videos/watch', this.video.uuid ]
|
||||
}
|
||||
|
||||
isUserLoggedIn () {
|
||||
return this.authService.isLoggedIn()
|
||||
}
|
||||
onWatchLaterClick (event: Event) {
|
||||
this.watchLaterClick.emit(this.inWatchLaterPlaylist)
|
||||
|
||||
addToWatchLater () {
|
||||
if (this.addedToWatchLater === undefined) return
|
||||
this.addedToWatchLater = true
|
||||
|
||||
this.videoPlaylistService.addVideoInPlaylist(
|
||||
this.watchLaterPlaylist.playlistId,
|
||||
{ videoId: this.video.id } as VideoPlaylistElementCreate
|
||||
).subscribe(
|
||||
res => {
|
||||
this.addedToWatchLater = true
|
||||
this.watchLaterPlaylist.playlistElementId = res.videoPlaylistElement.id
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
removeFromWatchLater () {
|
||||
if (this.addedToWatchLater === undefined) return
|
||||
this.addedToWatchLater = false
|
||||
|
||||
this.videoPlaylistService.removeVideoFromPlaylist(
|
||||
this.watchLaterPlaylist.playlistId,
|
||||
this.watchLaterPlaylist.playlistElementId
|
||||
).subscribe(
|
||||
_ => {
|
||||
this.addedToWatchLater = false
|
||||
}
|
||||
)
|
||||
event.stopPropagation()
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@ import { CanComponentDeactivate } from '@app/shared/guards/can-deactivate-guard.
|
|||
import { FormValidatorService, UserService } from '@app/shared'
|
||||
import { VideoCaptionService } from '@app/shared/video-caption'
|
||||
import { scrollToTop } from '@app/shared/misc/utils'
|
||||
import { ServerConfig } from '@shared/models'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-upload',
|
||||
|
|
Loading…
Reference in a new issue