--- /dev/null
+<ng-template #ratePopoverText>
+ <span [innerHTML]="getRatePopoverText()"></span>
+</ng-template>
+
+<button
+ [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" (keyup.enter)="setLike()"
+ class="action-button action-button-like" [attr.aria-pressed]="userRating === 'like'" [attr.aria-label]="tooltipLike"
+ [ngbTooltip]="tooltipLike"
+ placement="bottom auto"
+>
+ <my-global-icon iconName="like"></my-global-icon>
+ <span *ngIf="video.likes" class="count">{{ video.likes }}</span>
+</button>
+
+<button
+ [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" (keyup.enter)="setDislike()"
+ class="action-button action-button-dislike" [attr.aria-pressed]="userRating === 'dislike'" [attr.aria-label]="tooltipDislike"
+ [ngbTooltip]="tooltipDislike"
+ placement="bottom auto"
+>
+ <my-global-icon iconName="dislike"></my-global-icon>
+ <span *ngIf="video.dislikes" class="count">{{ video.dislikes }}</span>
+</button>
--- /dev/null
+@use '_variables' as *;
+@use '_mixins' as *;
+
+.action-button-like,
+.action-button-dislike {
+ filter: brightness(120%);
+
+ .count {
+ margin: 0 5px;
+ }
+}
+
+.activated {
+ color: pvar(--activatedActionButtonColor) !important;
+}
--- /dev/null
+import { Hotkey, HotkeysService } from 'angular2-hotkeys'
+import { Observable } from 'rxjs'
+import { Component, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output } from '@angular/core'
+import { Notifier, ScreenService } from '@app/core'
+import { VideoDetails, VideoService } from '@app/shared/shared-main'
+import { UserVideoRateType } from '@shared/models'
+
+@Component({
+ selector: 'my-video-rate',
+ templateUrl: './video-rate.component.html',
+ styleUrls: [ './video-rate.component.scss' ]
+})
+export class VideoRateComponent implements OnInit, OnChanges, OnDestroy {
+ @Input() video: VideoDetails
+ @Input() isUserLoggedIn: boolean
+
+ @Output() userRatingLoaded = new EventEmitter<UserVideoRateType>()
+ @Output() rateUpdated = new EventEmitter<UserVideoRateType>()
+
+ userRating: UserVideoRateType
+
+ tooltipLike = ''
+ tooltipDislike = ''
+
+ private hotkeys: Hotkey[]
+
+ constructor (
+ private videoService: VideoService,
+ private notifier: Notifier,
+ private hotkeysService: HotkeysService,
+ private screenService: ScreenService
+ ) { }
+
+ async ngOnInit () {
+ // Hide the tooltips for unlogged users in mobile view, this adds confusion with the popover
+ if (this.isUserLoggedIn || !this.screenService.isInMobileView()) {
+ this.tooltipLike = $localize`Like this video`
+ this.tooltipDislike = $localize`Dislike this video`
+ }
+
+ if (this.isUserLoggedIn) {
+ this.hotkeys = [
+ new Hotkey('shift+l', () => {
+ this.setLike()
+ return false
+ }, undefined, $localize`Like the video`),
+
+ new Hotkey('shift+d', () => {
+ this.setDislike()
+ return false
+ }, undefined, $localize`Dislike the video`)
+ ]
+
+ this.hotkeysService.add(this.hotkeys)
+ }
+ }
+
+ ngOnChanges () {
+ this.checkUserRating()
+ }
+
+ ngOnDestroy () {
+ this.hotkeysService.remove(this.hotkeys)
+ }
+
+ setLike () {
+ if (this.isUserLoggedIn === false) return
+
+ // Already liked this video
+ if (this.userRating === 'like') this.setRating('none')
+ else this.setRating('like')
+ }
+
+ setDislike () {
+ if (this.isUserLoggedIn === false) return
+
+ // Already disliked this video
+ if (this.userRating === 'dislike') this.setRating('none')
+ else this.setRating('dislike')
+ }
+
+ getRatePopoverText () {
+ if (this.isUserLoggedIn) return undefined
+
+ return $localize`You need to be <a href="/login">logged in</a> to rate this video.`
+ }
+
+ private checkUserRating () {
+ // Unlogged users do not have ratings
+ if (this.isUserLoggedIn === false) return
+
+ this.videoService.getUserVideoRating(this.video.id)
+ .subscribe(
+ ratingObject => {
+ if (!ratingObject) return
+
+ this.userRating = ratingObject.rating
+ this.userRatingLoaded.emit(this.userRating)
+ },
+
+ err => this.notifier.error(err.message)
+ )
+ }
+
+ private setRating (nextRating: UserVideoRateType) {
+ const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = {
+ like: this.videoService.setVideoLike,
+ dislike: this.videoService.setVideoDislike,
+ none: this.videoService.unsetVideoLike
+ }
+
+ ratingMethods[nextRating].call(this.videoService, this.video.id)
+ .subscribe(
+ () => {
+ // Update the video like attribute
+ this.updateVideoRating(this.userRating, nextRating)
+ this.userRating = nextRating
+ this.rateUpdated.emit(this.userRating)
+ },
+
+ (err: { message: string }) => this.notifier.error(err.message)
+ )
+ }
+
+ private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) {
+ let likesToIncrement = 0
+ let dislikesToIncrement = 0
+
+ if (oldRating) {
+ if (oldRating === 'like') likesToIncrement--
+ if (oldRating === 'dislike') dislikesToIncrement--
+ }
+
+ if (newRating === 'like') likesToIncrement++
+ if (newRating === 'dislike') dislikesToIncrement++
+
+ this.video.likes += likesToIncrement
+ this.video.dislikes += dislikesToIncrement
+
+ this.video.buildLikeAndDislikePercents()
+ }
+}
</div>
<div class="video-actions-rates">
- <ng-template #ratePopoverText>
- <span [innerHTML]="getRatePopoverText()"></span>
- </ng-template>
-
<div class="video-actions full-width justify-content-end">
- <button
- [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" (keyup.enter)="setLike()"
- class="action-button action-button-like" [attr.aria-pressed]="userRating === 'like'" [attr.aria-label]="tooltipLike"
- [ngbTooltip]="tooltipLike"
- placement="bottom auto"
- >
- <my-global-icon iconName="like"></my-global-icon>
- <span *ngIf="video.likes" class="count">{{ video.likes }}</span>
- </button>
-
- <button
- [ngbPopover]="getRatePopoverText() && ratePopoverText" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" (keyup.enter)="setDislike()"
- class="action-button action-button-dislike" [attr.aria-pressed]="userRating === 'dislike'" [attr.aria-label]="tooltipDislike"
- [ngbTooltip]="tooltipDislike"
- placement="bottom auto"
- >
- <my-global-icon iconName="dislike"></my-global-icon>
- <span *ngIf="video.dislikes" class="count">{{ video.dislikes }}</span>
- </button>
+ <my-video-rate
+ [video]="video" [isUserLoggedIn]="isUserLoggedIn()"
+ (rateUpdated)="onRateUpdated($event)" (userRatingLoaded)="onRateUpdated($event)"
+ ></my-video-rate>
<button *ngIf="video.support" (click)="showSupportModal()" (keyup.enter)="showSupportModal()" class="action-button action-button-support" [attr.aria-label]="tooltipSupport"
[ngbTooltip]="tooltipSupport"
::ng-deep.action-button {
@include peertube-button;
@include button-with-icon(21px, 0, -1px);
- @include apply-svg-color(pvar(--actionButtonColor));
font-size: 100%;
font-weight: $font-semibold;
opacity: 0.9;
}
- &.action-button-like,
- &.action-button-dislike {
- filter: brightness(120%);
-
- .count {
- margin: 0 5px;
- }
- }
-
- &.action-button-like.activated {
- .count {
- color: pvar(--activatedActionButtonColor);
- }
-
- my-global-icon {
- @include apply-svg-color(pvar(--activatedActionButtonColor));
- }
- }
-
- &.action-button-dislike.activated {
- .count {
- color: pvar(--activatedActionButtonColor);
- }
-
- my-global-icon {
- @include apply-svg-color(pvar(--activatedActionButtonColor));
- }
- }
-
&.action-button-support {
color: pvar(--supportButtonColor);
import { Hotkey, HotkeysService } from 'angular2-hotkeys'
-import { forkJoin, Observable, Subscription } from 'rxjs'
+import { forkJoin, Subscription } from 'rxjs'
import { catchError } from 'rxjs/operators'
import { PlatformLocation } from '@angular/common'
import { ChangeDetectorRef, Component, ElementRef, Inject, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
theaterEnabled = false
- userRating: UserVideoRateType = null
-
playerPlaceholderImgSrc: string
video: VideoDetails = null
hasAlreadyAcceptedPrivacyConcern = false
remoteServerDown = false
- hotkeys: Hotkey[] = []
-
- tooltipLike = ''
- tooltipDislike = ''
tooltipSupport = ''
tooltipSaveToPlaylist = ''
liveInfo: true
}
+ userRating: UserVideoRateType
+
private nextVideoUuid = ''
private nextVideoTitle = ''
private currentTime: number
private serverConfig: HTMLServerConfig
+ private hotkeys: Hotkey[] = []
+
constructor (
private elementRef: ElementRef,
private changeDetector: ChangeDetectorRef,
async ngOnInit () {
// Hide the tooltips for unlogged users in mobile view, this adds confusion with the popover
if (this.user || !this.screenService.isInMobileView()) {
- this.tooltipLike = $localize`Like this video`
- this.tooltipDislike = $localize`Dislike this video`
this.tooltipSupport = $localize`Support options for this video`
this.tooltipSaveToPlaylist = $localize`Save to playlist`
}
this.hotkeysService.remove(this.hotkeys)
}
- setLike () {
- if (this.isUserLoggedIn() === false) return
-
- // Already liked this video
- if (this.userRating === 'like') this.setRating('none')
- else this.setRating('like')
- }
-
- setDislike () {
- if (this.isUserLoggedIn() === false) return
-
- // Already disliked this video
- if (this.userRating === 'dislike') this.setRating('none')
- else this.setRating('dislike')
- }
-
- getRatePopoverText () {
- if (this.isUserLoggedIn()) return undefined
-
- return $localize`You need to be <a href="/login">logged in</a> to rate this video.`
- }
-
showMoreDescription () {
if (this.completeVideoDescription === undefined) {
return this.loadCompleteDescription()
this.loadVideo(videoId)
}
+ onRateUpdated (userRating: UserVideoRateType) {
+ this.userRating = userRating
+ this.setVideoLikesBarTooltipText()
+ }
+
displayOtherVideosAsRow () {
// Use the same value as in the SASS file
return this.screenService.getWindowInnerWidth() <= 1100
this.notifier.error(errorMessage)
}
- private checkUserRating () {
- // Unlogged users do not have ratings
- if (this.isUserLoggedIn() === false) return
-
- this.videoService.getUserVideoRating(this.video.id)
- .subscribe(
- ratingObject => {
- if (ratingObject) {
- this.userRating = ratingObject.rating
- }
- },
-
- err => this.notifier.error(err.message)
- )
- }
-
private async onVideoFetched (
video: VideoDetails,
videoCaptions: VideoCaption[],
this.setVideoLikesBarTooltipText()
this.setOpenGraphTags()
- this.checkUserRating()
const hookOptions = {
videojs,
}
}
- private setRating (nextRating: UserVideoRateType) {
- const ratingMethods: { [id in UserVideoRateType]: (id: number) => Observable<any> } = {
- like: this.videoService.setVideoLike,
- dislike: this.videoService.setVideoDislike,
- none: this.videoService.unsetVideoLike
- }
-
- ratingMethods[nextRating].call(this.videoService, this.video.id)
- .subscribe(
- () => {
- // Update the video like attribute
- this.updateVideoRating(this.userRating, nextRating)
- this.userRating = nextRating
- },
-
- (err: { message: string }) => this.notifier.error(err.message)
- )
- }
-
- private updateVideoRating (oldRating: UserVideoRateType, newRating: UserVideoRateType) {
- let likesToIncrement = 0
- let dislikesToIncrement = 0
-
- if (oldRating) {
- if (oldRating === 'like') likesToIncrement--
- if (oldRating === 'dislike') dislikesToIncrement--
- }
-
- if (newRating === 'like') likesToIncrement++
- if (newRating === 'dislike') dislikesToIncrement++
-
- this.video.likes += likesToIncrement
- this.video.dislikes += dislikesToIncrement
-
- this.video.buildLikeAndDislikePercents()
- this.setVideoLikesBarTooltipText()
- }
-
private setOpenGraphTags () {
this.metaService.setTitle(this.video.name)
if (this.isUserLoggedIn()) {
this.hotkeys = this.hotkeys.concat([
- new Hotkey('shift+l', () => {
- this.setLike()
- return false
- }, undefined, $localize`Like the video`),
-
- new Hotkey('shift+d', () => {
- this.setDislike()
- return false
- }, undefined, $localize`Dislike the video`),
-
new Hotkey('shift+s', () => {
this.subscribeButton.subscribed ? this.subscribeButton.unsubscribe() : this.subscribeButton.subscribe()
return false
import { SharedVideoCommentModule } from '@app/shared/shared-video-comment'
import { SharedVideoMiniatureModule } from '@app/shared/shared-video-miniature'
import { SharedVideoPlaylistModule } from '@app/shared/shared-video-playlist'
+import { SharedActorImageModule } from '../../shared/shared-actor-image/shared-actor-image.module'
import { VideoCommentService } from '../../shared/shared-video-comment/video-comment.service'
import { VideoCommentAddComponent } from './comment/video-comment-add.component'
import { VideoCommentComponent } from './comment/video-comment.component'
import { PlayerStylesComponent } from './player-styles.component'
import { RecommendationsModule } from './recommendations/recommendations.module'
import { TimestampRouteTransformerDirective } from './timestamp-route-transformer.directive'
+import { VideoAvatarChannelComponent } from './video-avatar-channel.component'
+import { VideoRateComponent } from './video-rate.component'
import { VideoWatchPlaylistComponent } from './video-watch-playlist.component'
import { VideoWatchRoutingModule } from './video-watch-routing.module'
import { VideoWatchComponent } from './video-watch.component'
-import { SharedActorImageModule } from '../../shared/shared-actor-image/shared-actor-image.module'
-import { VideoAvatarChannelComponent } from './video-avatar-channel.component'
@NgModule({
imports: [
declarations: [
VideoWatchComponent,
VideoWatchPlaylistComponent,
+ VideoRateComponent,
VideoCommentsComponent,
VideoCommentAddComponent,