From d38b82810638b9f664c9016fac2684454c273a77 Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Wed, 8 Mar 2017 21:35:43 +0100 Subject: Add like/dislike system for videos --- client/src/app/shared/users/user.service.ts | 2 +- client/src/app/videos/shared/index.ts | 1 + client/src/app/videos/shared/rate-type.type.ts | 1 + client/src/app/videos/shared/sort-field.type.ts | 6 +- client/src/app/videos/shared/video.model.ts | 8 ++- client/src/app/videos/shared/video.service.ts | 43 +++++++++++-- .../videos/video-watch/video-watch.component.html | 20 ++++++- .../videos/video-watch/video-watch.component.scss | 28 +++++++++ .../videos/video-watch/video-watch.component.ts | 70 +++++++++++++++++++++- 9 files changed, 167 insertions(+), 12 deletions(-) create mode 100644 client/src/app/videos/shared/rate-type.type.ts (limited to 'client') diff --git a/client/src/app/shared/users/user.service.ts b/client/src/app/shared/users/user.service.ts index 4cf100f0d..865e04d48 100644 --- a/client/src/app/shared/users/user.service.ts +++ b/client/src/app/shared/users/user.service.ts @@ -8,7 +8,7 @@ import { RestExtractor } from '../rest'; @Injectable() export class UserService { - private static BASE_USERS_URL = '/api/v1/users/'; + static BASE_USERS_URL = '/api/v1/users/'; constructor( private authHttp: AuthHttp, diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts index 67d16ead1..beaa528c0 100644 --- a/client/src/app/videos/shared/index.ts +++ b/client/src/app/videos/shared/index.ts @@ -1,4 +1,5 @@ export * from './loader'; export * from './sort-field.type'; +export * from './rate-type.type'; export * from './video.model'; export * from './video.service'; diff --git a/client/src/app/videos/shared/rate-type.type.ts b/client/src/app/videos/shared/rate-type.type.ts new file mode 100644 index 000000000..88034d1ff --- /dev/null +++ b/client/src/app/videos/shared/rate-type.type.ts @@ -0,0 +1 @@ +export type RateType = 'like' | 'dislike'; diff --git a/client/src/app/videos/shared/sort-field.type.ts b/client/src/app/videos/shared/sort-field.type.ts index 74908e344..7bda3112a 100644 --- a/client/src/app/videos/shared/sort-field.type.ts +++ b/client/src/app/videos/shared/sort-field.type.ts @@ -1,3 +1,3 @@ -export type SortField = "name" | "-name" - | "duration" | "-duration" - | "createdAt" | "-createdAt"; +export type SortField = 'name' | '-name' + | 'duration' | '-duration' + | 'createdAt' | '-createdAt'; diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts index 8e676708b..3eef936eb 100644 --- a/client/src/app/videos/shared/video.model.ts +++ b/client/src/app/videos/shared/video.model.ts @@ -12,6 +12,8 @@ export class Video { tags: string[]; thumbnailPath: string; views: number; + likes: number; + dislikes: number; private static createByString(author: string, podHost: string) { return author + '@' + podHost; @@ -38,7 +40,9 @@ export class Video { podHost: string, tags: string[], thumbnailPath: string, - views: number + views: number, + likes: number, + dislikes: number, }) { this.author = hash.author; this.createdAt = new Date(hash.createdAt); @@ -52,6 +56,8 @@ export class Video { this.tags = hash.tags; this.thumbnailPath = hash.thumbnailPath; this.views = hash.views; + this.likes = hash.likes; + this.dislikes = hash.dislikes; this.by = Video.createByString(hash.author, hash.podHost); } diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts index 7094d9a34..8bb5a2933 100644 --- a/client/src/app/videos/shared/video.service.ts +++ b/client/src/app/videos/shared/video.service.ts @@ -6,8 +6,16 @@ import 'rxjs/add/operator/map'; import { Search } from '../../shared'; import { SortField } from './sort-field.type'; +import { RateType } from './rate-type.type'; import { AuthService } from '../../core'; -import { AuthHttp, RestExtractor, RestPagination, RestService, ResultList } from '../../shared'; +import { + AuthHttp, + RestExtractor, + RestPagination, + RestService, + ResultList, + UserService +} from '../../shared'; import { Video } from './video.model'; @Injectable() @@ -56,14 +64,41 @@ export class VideoService { } reportVideo(id: string, reason: string) { + const url = VideoService.BASE_VIDEO_URL + id + '/abuse'; const body = { reason }; - const url = VideoService.BASE_VIDEO_URL + id + '/abuse'; return this.authHttp.post(url, body) - .map(this.restExtractor.extractDataBool) - .catch((res) => this.restExtractor.handleError(res)); + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); + } + + setVideoLike(id: string) { + return this.setVideoRate(id, 'like'); + } + + setVideoDislike(id: string) { + return this.setVideoRate(id, 'dislike'); + } + + getUserVideoRating(id: string) { + const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'; + + return this.authHttp.get(url) + .map(this.restExtractor.extractDataGet) + .catch((res) => this.restExtractor.handleError(res)); + } + + private setVideoRate(id: string, rateType: RateType) { + const url = VideoService.BASE_VIDEO_URL + id + '/rate'; + const body = { + rating: rateType + }; + + return this.authHttp.put(url, body) + .map(this.restExtractor.extractDataBool) + .catch((res) => this.restExtractor.handleError(res)); } private extractVideos(result: ResultList) { diff --git a/client/src/app/videos/video-watch/video-watch.component.html b/client/src/app/videos/video-watch/video-watch.component.html index 24d741ff9..67094359e 100644 --- a/client/src/app/videos/video-watch/video-watch.component.html +++ b/client/src/app/videos/video-watch/video-watch.component.html @@ -32,7 +32,7 @@
-
+
{{ video.name }} @@ -52,7 +52,23 @@
-
+
+
+ + + +
+ diff --git a/client/src/app/videos/video-watch/video-watch.component.scss b/client/src/app/videos/video-watch/video-watch.component.scss index 0b8af52ce..5f322a194 100644 --- a/client/src/app/videos/video-watch/video-watch.component.scss +++ b/client/src/app/videos/video-watch/video-watch.component.scss @@ -47,6 +47,34 @@ top: 2px; } + #rates { + display: inline-block; + margin-right: 20px; + + // Remove focus style + .btn:focus { + outline: 0; + } + + .activated-btn { + color: #333; + background-color: #e6e6e6; + border-color: #8c8c8c; + } + + .not-interactive-btn { + cursor: default; + + &:hover, &:focus, &:active { + color: #333; + background-color: #fff; + border-color: #ccc; + box-shadow: none; + outline: 0; + } + } + } + #share, #more { font-weight: bold; opacity: 0.85; diff --git a/client/src/app/videos/video-watch/video-watch.component.ts b/client/src/app/videos/video-watch/video-watch.component.ts index d1abc81bc..ed6b30102 100644 --- a/client/src/app/videos/video-watch/video-watch.component.ts +++ b/client/src/app/videos/video-watch/video-watch.component.ts @@ -10,7 +10,7 @@ import { AuthService } from '../../core'; import { VideoMagnetComponent } from './video-magnet.component'; import { VideoShareComponent } from './video-share.component'; import { VideoReportComponent } from './video-report.component'; -import { Video, VideoService } from '../shared'; +import { RateType, Video, VideoService } from '../shared'; import { WebTorrentService } from './webtorrent.service'; @Component({ @@ -33,6 +33,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { player: VideoJSPlayer; playerElement: Element; uploadSpeed: number; + userRating: RateType = null; video: Video = null; videoNotFound = false; @@ -61,6 +62,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { this.video = video; this.setOpenGraphTags(); this.loadVideo(); + this.checkUserRating(); }, error => { this.videoNotFound = true; @@ -136,6 +138,40 @@ export class VideoWatchComponent implements OnInit, OnDestroy { }); } + setLike() { + if (this.isUserLoggedIn() === false) return; + // Already liked this video + if (this.userRating === 'like') return; + + this.videoService.setVideoLike(this.video.id) + .subscribe( + () => { + // Update the video like attribute + this.updateVideoRating(this.userRating, 'like'); + this.userRating = 'like'; + }, + + err => this.notificationsService.error('Error', err.text) + ); + } + + setDislike() { + if (this.isUserLoggedIn() === false) return; + // Already disliked this video + if (this.userRating === 'dislike') return; + + this.videoService.setVideoDislike(this.video.id) + .subscribe( + () => { + // Update the video dislike attribute + this.updateVideoRating(this.userRating, 'dislike'); + this.userRating = 'dislike'; + }, + + err => this.notificationsService.error('Error', err.text) + ); + } + showReportModal(event: Event) { event.preventDefault(); this.videoReportModal.show(); @@ -154,6 +190,38 @@ export class VideoWatchComponent implements OnInit, OnDestroy { return this.authService.isLoggedIn(); } + 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.notificationsService.error('Error', err.text) + ); + } + + private updateVideoRating(oldRating: RateType, newRating: RateType) { + 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; + } + private loadTooLong() { this.error = true; console.error('The video load seems to be abnormally long.'); -- cgit v1.2.3