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
}
}
-export class AuthUser extends User {
+export class AuthUser extends User implements ServerMyUserModel {
private static KEYS = {
ID: 'id',
ROLE: 'role',
}
tokens: Tokens
- specialPlaylists: Partial<VideoPlaylist>[]
+ specialPlaylists: MyUserSpecialPlaylist[]
static load () {
const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME)
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 () {
return hasUserRight(this.role, right)
}
- canManage (user: ServerUserModel) {
+ canManage (user: ServerMyUserModel) {
const myRole = this.role
if (myRole === UserRole.ADMINISTRATOR) return true
-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'
// 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,
private restService: RestService
) {
this.videoExistsInPlaylistObservable = this.videoExistsInPlaylistSubject.pipe(
+ distinctUntilChanged(),
bufferTime(500),
filter(videoIds => videoIds.length !== 0),
switchMap(videoIds => this.doVideosExistInPlaylist(videoIds)),
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)))
}
}
<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">
-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 = {
@Output() videoUnblacklisted = new EventEmitter()
@Output() videoRemoved = new EventEmitter()
- @ViewChild('thumbnail', { static: true }) thumbnail: VideoThumbnailComponent
-
videoActionsDisplayOptions: VideoActionsDisplayType = {
playlist: true,
download: false,
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)
loadActions () {
if (this.displayVideoActions) this.showActions = true
- this.thumbnail.load()
+
+ this.loadWatchLater()
}
onVideoBlacklisted () {
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
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()
+ })
+ }
}
<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>
bottom: 5px;
}
- &:focus,
- &:hover {
- .video-thumbnail-actions-overlay {
- opacity: 1;
- }
- }
-
.video-thumbnail-actions-overlay {
position: absolute;
display: flex;
-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',
@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 () {
return [ '/videos/watch', this.video.uuid ]
}
- isUserLoggedIn () {
- return this.authService.isLoggedIn()
- }
-
- 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
+ onWatchLaterClick (event: Event) {
+ this.watchLaterClick.emit(this.inWatchLaterPlaylist)
- this.videoPlaylistService.removeVideoFromPlaylist(
- this.watchLaterPlaylist.playlistId,
- this.watchLaterPlaylist.playlistElementId
- ).subscribe(
- _ => {
- this.addedToWatchLater = false
- }
- )
+ event.stopPropagation()
+ return false
}
}
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',