From 29128b2f5ce00093ad81b4b72daae0e3444fd5a8 Mon Sep 17 00:00:00 2001 From: Rigel Kent Date: Thu, 2 Jan 2020 13:07:18 +0100 Subject: Add miniature quick actions to add video to Watch later playlist --- client/src/app/core/auth/auth-user.model.ts | 2 + client/src/app/core/auth/auth.service.ts | 2 +- client/src/app/login/login.component.ts | 2 - .../src/app/shared/images/global-icon.component.ts | 1 + .../video-playlist/video-playlist.service.ts | 1 + .../shared/video/video-thumbnail.component.html | 14 ++++ .../shared/video/video-thumbnail.component.scss | 42 +++++++++-- .../app/shared/video/video-thumbnail.component.ts | 82 +++++++++++++++++++++- client/src/assets/images/global/clock.svg | 11 +++ 9 files changed, 148 insertions(+), 9 deletions(-) create mode 100644 client/src/assets/images/global/clock.svg (limited to 'client') diff --git a/client/src/app/core/auth/auth-user.model.ts b/client/src/app/core/auth/auth-user.model.ts index d371a923f..55a5a6dde 100644 --- a/client/src/app/core/auth/auth-user.model.ts +++ b/client/src/app/core/auth/auth-user.model.ts @@ -5,6 +5,7 @@ import { User as ServerUserModel } from '../../../../../shared/models/users/user 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 @@ -79,6 +80,7 @@ export class AuthUser extends User { } tokens: Tokens + specialPlaylists: Partial[] static load () { const usernameLocalStorage = peertubeLocalStorage.getItem(this.KEYS.USERNAME) diff --git a/client/src/app/core/auth/auth.service.ts b/client/src/app/core/auth/auth.service.ts index d601cadf5..9ae008e39 100644 --- a/client/src/app/core/auth/auth.service.ts +++ b/client/src/app/core/auth/auth.service.ts @@ -4,7 +4,7 @@ import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http' import { Injectable } from '@angular/core' import { Router } from '@angular/router' import { Notifier } from '@app/core/notification/notifier.service' -import { OAuthClientLocal, User as UserServerModel, UserRefreshToken } from '../../../../../shared' +import { OAuthClientLocal, MyUser as UserServerModel, UserRefreshToken } from '../../../../../shared' import { User } from '../../../../../shared/models/users' import { UserLogin } from '../../../../../shared/models/users/user-login.model' import { environment } from '../../../environments/environment' diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index cf923492a..ffadc9aa4 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts @@ -28,13 +28,11 @@ export class LoginComponent extends FormReactive implements OnInit { constructor ( protected formValidatorService: FormValidatorService, - private router: Router, private route: ActivatedRoute, private modalService: NgbModal, private loginValidatorsService: LoginValidatorsService, private authService: AuthService, private userService: UserService, - private serverService: ServerService, private redirectService: RedirectService, private notifier: Notifier, private i18n: I18n diff --git a/client/src/app/shared/images/global-icon.component.ts b/client/src/app/shared/images/global-icon.component.ts index 8a4965926..17186cff4 100644 --- a/client/src/app/shared/images/global-icon.component.ts +++ b/client/src/app/shared/images/global-icon.component.ts @@ -10,6 +10,7 @@ const icons = { 'sparkle': require('!!raw-loader?!../../../assets/images/global/sparkle.svg'), 'alert': require('!!raw-loader?!../../../assets/images/global/alert.svg'), 'cloud-error': require('!!raw-loader?!../../../assets/images/global/cloud-error.svg'), + 'clock': require('!!raw-loader?!../../../assets/images/global/clock.svg'), 'user-add': require('!!raw-loader?!../../../assets/images/global/user-add.svg'), 'no': require('!!raw-loader?!../../../assets/images/global/no.svg'), 'cloud-download': require('!!raw-loader?!../../../assets/images/global/cloud-download.svg'), 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 5f74dcd4c..fc3b77b2a 100644 --- a/client/src/app/shared/video-playlist/video-playlist.service.ts +++ b/client/src/app/shared/video-playlist/video-playlist.service.ts @@ -30,6 +30,7 @@ export class VideoPlaylistService { // Use a replay subject because we "next" a value before subscribing private videoExistsInPlaylistSubject: Subject = new ReplaySubject(1) private readonly videoExistsInPlaylistObservable: Observable + private cachedWatchLaterPlaylists: VideoPlaylist[] constructor ( private authHttp: HttpClient, diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html index b302ebd0f..df15698c0 100644 --- a/client/src/app/shared/video/video-thumbnail.component.html +++ b/client/src/app/shared/video/video-thumbnail.component.html @@ -1,9 +1,23 @@ +
+ +
+ +
+
+ +
+ +
+
+
+
{{ video.durationLabel }}
diff --git a/client/src/app/shared/video/video-thumbnail.component.scss b/client/src/app/shared/video/video-thumbnail.component.scss index e48629778..aac50fd1b 100644 --- a/client/src/app/shared/video/video-thumbnail.component.scss +++ b/client/src/app/shared/video/video-thumbnail.component.scss @@ -18,16 +18,50 @@ } } + .video-thumbnail-watch-later-overlay, .video-thumbnail-duration-overlay { @include static-thumbnail-overlay; - position: absolute; - right: 5px; - bottom: 5px; - padding: 0 5px; border-radius: 3px; font-size: 12px; font-weight: $font-bold; z-index: 1; } + + .video-thumbnail-duration-overlay { + position: absolute; + padding: 0 5px; + right: 5px; + bottom: 5px; + } + + &:hover { + .video-thumbnail-actions-overlay { + opacity: 1; + } + } + + .video-thumbnail-actions-overlay { + position: absolute; + display: flex; + flex-direction: column; + right: 5px; + top: 5px; + opacity: 0; + + div:not(:first-child) { + margin-top: 2px; + } + + .video-thumbnail-watch-later-overlay { + padding: 3px; + + my-global-icon { + width: 22px; + height: 22px; + + @include apply-svg-color(#fff); + } + } + } } diff --git a/client/src/app/shared/video/video-thumbnail.component.ts b/client/src/app/shared/video/video-thumbnail.component.ts index fe65ade94..0f605e425 100644 --- a/client/src/app/shared/video/video-thumbnail.component.ts +++ b/client/src/app/shared/video/video-thumbnail.component.ts @@ -1,6 +1,14 @@ -import { Component, Input } from '@angular/core' +import { Component, Input, OnInit, ChangeDetectorRef } 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 { VideoPlaylistType } from '@shared/models' +import { forkJoin } from 'rxjs' +import { VideoPlaylistElement } from '@app/shared/video-playlist/video-playlist-element.model' +import { VideoPlaylist } from '../video-playlist/video-playlist.model' +import { VideoPlaylistElementCreate } from '../../../../../shared' +import { VideoExistInPlaylist } from '@shared/models/videos/playlist/video-exist-in-playlist.model' @Component({ selector: 'my-video-thumbnail', @@ -13,7 +21,44 @@ export class VideoThumbnailComponent { @Input() routerLink: any[] @Input() queryParams: any[] - constructor (private screenService: ScreenService) { + addToWatchLaterText = 'Add to watch later' + addedToWatchLaterText = 'Added to watch later' + addedToWatchLater: boolean + + watchLaterPlaylist: any + + constructor ( + private screenService: ScreenService, + private authService: AuthService, + private videoPlaylistService: VideoPlaylistService, + private cd: ChangeDetectorRef + ) {} + + load () { + if (this.addedToWatchLater !== undefined) 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() + } + } + ) } getImageUrl () { @@ -39,4 +84,37 @@ export class VideoThumbnailComponent { 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 + + this.videoPlaylistService.removeVideoFromPlaylist( + this.watchLaterPlaylist.playlistId, + this.watchLaterPlaylist.playlistElementId + ).subscribe( + _ => { + this.addedToWatchLater = false + } + ) + } } diff --git a/client/src/assets/images/global/clock.svg b/client/src/assets/images/global/clock.svg new file mode 100644 index 000000000..f2d4f0397 --- /dev/null +++ b/client/src/assets/images/global/clock.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file -- cgit v1.2.3