diff options
author | Chocobozzz <florian.bigard@gmail.com> | 2017-03-08 21:35:43 +0100 |
---|---|---|
committer | Chocobozzz <florian.bigard@gmail.com> | 2017-03-08 21:35:43 +0100 |
commit | d38b82810638b9f664c9016fac2684454c273a77 (patch) | |
tree | 9465c367e5033675309efca4d66790c6fdd5230d /client/src/app/videos | |
parent | 8f9064432122cba0f518a24ac4378357dadec589 (diff) | |
download | PeerTube-d38b82810638b9f664c9016fac2684454c273a77.tar.gz PeerTube-d38b82810638b9f664c9016fac2684454c273a77.tar.zst PeerTube-d38b82810638b9f664c9016fac2684454c273a77.zip |
Add like/dislike system for videos
Diffstat (limited to 'client/src/app/videos')
-rw-r--r-- | client/src/app/videos/shared/index.ts | 1 | ||||
-rw-r--r-- | client/src/app/videos/shared/rate-type.type.ts | 1 | ||||
-rw-r--r-- | client/src/app/videos/shared/sort-field.type.ts | 6 | ||||
-rw-r--r-- | client/src/app/videos/shared/video.model.ts | 8 | ||||
-rw-r--r-- | client/src/app/videos/shared/video.service.ts | 43 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/video-watch.component.html | 20 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/video-watch.component.scss | 28 | ||||
-rw-r--r-- | client/src/app/videos/video-watch/video-watch.component.ts | 70 |
8 files changed, 166 insertions, 11 deletions
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 @@ | |||
1 | export * from './loader'; | 1 | export * from './loader'; |
2 | export * from './sort-field.type'; | 2 | export * from './sort-field.type'; |
3 | export * from './rate-type.type'; | ||
3 | export * from './video.model'; | 4 | export * from './video.model'; |
4 | export * from './video.service'; | 5 | 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 @@ | |||
1 | export type SortField = "name" | "-name" | 1 | export type SortField = 'name' | '-name' |
2 | | "duration" | "-duration" | 2 | | 'duration' | '-duration' |
3 | | "createdAt" | "-createdAt"; | 3 | | '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 { | |||
12 | tags: string[]; | 12 | tags: string[]; |
13 | thumbnailPath: string; | 13 | thumbnailPath: string; |
14 | views: number; | 14 | views: number; |
15 | likes: number; | ||
16 | dislikes: number; | ||
15 | 17 | ||
16 | private static createByString(author: string, podHost: string) { | 18 | private static createByString(author: string, podHost: string) { |
17 | return author + '@' + podHost; | 19 | return author + '@' + podHost; |
@@ -38,7 +40,9 @@ export class Video { | |||
38 | podHost: string, | 40 | podHost: string, |
39 | tags: string[], | 41 | tags: string[], |
40 | thumbnailPath: string, | 42 | thumbnailPath: string, |
41 | views: number | 43 | views: number, |
44 | likes: number, | ||
45 | dislikes: number, | ||
42 | }) { | 46 | }) { |
43 | this.author = hash.author; | 47 | this.author = hash.author; |
44 | this.createdAt = new Date(hash.createdAt); | 48 | this.createdAt = new Date(hash.createdAt); |
@@ -52,6 +56,8 @@ export class Video { | |||
52 | this.tags = hash.tags; | 56 | this.tags = hash.tags; |
53 | this.thumbnailPath = hash.thumbnailPath; | 57 | this.thumbnailPath = hash.thumbnailPath; |
54 | this.views = hash.views; | 58 | this.views = hash.views; |
59 | this.likes = hash.likes; | ||
60 | this.dislikes = hash.dislikes; | ||
55 | 61 | ||
56 | this.by = Video.createByString(hash.author, hash.podHost); | 62 | this.by = Video.createByString(hash.author, hash.podHost); |
57 | } | 63 | } |
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'; | |||
6 | 6 | ||
7 | import { Search } from '../../shared'; | 7 | import { Search } from '../../shared'; |
8 | import { SortField } from './sort-field.type'; | 8 | import { SortField } from './sort-field.type'; |
9 | import { RateType } from './rate-type.type'; | ||
9 | import { AuthService } from '../../core'; | 10 | import { AuthService } from '../../core'; |
10 | import { AuthHttp, RestExtractor, RestPagination, RestService, ResultList } from '../../shared'; | 11 | import { |
12 | AuthHttp, | ||
13 | RestExtractor, | ||
14 | RestPagination, | ||
15 | RestService, | ||
16 | ResultList, | ||
17 | UserService | ||
18 | } from '../../shared'; | ||
11 | import { Video } from './video.model'; | 19 | import { Video } from './video.model'; |
12 | 20 | ||
13 | @Injectable() | 21 | @Injectable() |
@@ -56,14 +64,41 @@ export class VideoService { | |||
56 | } | 64 | } |
57 | 65 | ||
58 | reportVideo(id: string, reason: string) { | 66 | reportVideo(id: string, reason: string) { |
67 | const url = VideoService.BASE_VIDEO_URL + id + '/abuse'; | ||
59 | const body = { | 68 | const body = { |
60 | reason | 69 | reason |
61 | }; | 70 | }; |
62 | const url = VideoService.BASE_VIDEO_URL + id + '/abuse'; | ||
63 | 71 | ||
64 | return this.authHttp.post(url, body) | 72 | return this.authHttp.post(url, body) |
65 | .map(this.restExtractor.extractDataBool) | 73 | .map(this.restExtractor.extractDataBool) |
66 | .catch((res) => this.restExtractor.handleError(res)); | 74 | .catch((res) => this.restExtractor.handleError(res)); |
75 | } | ||
76 | |||
77 | setVideoLike(id: string) { | ||
78 | return this.setVideoRate(id, 'like'); | ||
79 | } | ||
80 | |||
81 | setVideoDislike(id: string) { | ||
82 | return this.setVideoRate(id, 'dislike'); | ||
83 | } | ||
84 | |||
85 | getUserVideoRating(id: string) { | ||
86 | const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'; | ||
87 | |||
88 | return this.authHttp.get(url) | ||
89 | .map(this.restExtractor.extractDataGet) | ||
90 | .catch((res) => this.restExtractor.handleError(res)); | ||
91 | } | ||
92 | |||
93 | private setVideoRate(id: string, rateType: RateType) { | ||
94 | const url = VideoService.BASE_VIDEO_URL + id + '/rate'; | ||
95 | const body = { | ||
96 | rating: rateType | ||
97 | }; | ||
98 | |||
99 | return this.authHttp.put(url, body) | ||
100 | .map(this.restExtractor.extractDataBool) | ||
101 | .catch((res) => this.restExtractor.handleError(res)); | ||
67 | } | 102 | } |
68 | 103 | ||
69 | private extractVideos(result: ResultList) { | 104 | 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 @@ | |||
32 | 32 | ||
33 | <div *ngIf="video !== null" id="video-info"> | 33 | <div *ngIf="video !== null" id="video-info"> |
34 | <div class="row" id="video-name-actions"> | 34 | <div class="row" id="video-name-actions"> |
35 | <div class="col-md-8"> | 35 | <div class="col-md-6"> |
36 | <div class="row"> | 36 | <div class="row"> |
37 | <div id="video-name" class="col-md-12"> | 37 | <div id="video-name" class="col-md-12"> |
38 | {{ video.name }} | 38 | {{ video.name }} |
@@ -52,7 +52,23 @@ | |||
52 | </div> | 52 | </div> |
53 | </div> | 53 | </div> |
54 | 54 | ||
55 | <div id="video-actions" class="col-md-4 text-right"> | 55 | <div id="video-actions" class="col-md-6 text-right"> |
56 | <div id="rates"> | ||
57 | <button | ||
58 | id="likes" class="btn btn-default" | ||
59 | [ngClass]="{ 'not-interactive-btn': !isUserLoggedIn(), 'activated-btn': userRating === 'like' }" (click)="setLike()" | ||
60 | > | ||
61 | <span class="glyphicon glyphicon-thumbs-up"></span> {{ video.likes }} | ||
62 | </button> | ||
63 | |||
64 | <button | ||
65 | id="dislikes" class="btn btn-default" | ||
66 | [ngClass]="{ 'not-interactive-btn': !isUserLoggedIn(), 'activated-btn': userRating === 'dislike' }" (click)="setDislike()" | ||
67 | > | ||
68 | <span class=" glyphicon glyphicon-thumbs-down"></span> {{ video.dislikes }} | ||
69 | </button> | ||
70 | </div> | ||
71 | |||
56 | <button id="share" class="btn btn-default" (click)="showShareModal()"> | 72 | <button id="share" class="btn btn-default" (click)="showShareModal()"> |
57 | <span class="glyphicon glyphicon-share"></span> Share | 73 | <span class="glyphicon glyphicon-share"></span> Share |
58 | </button> | 74 | </button> |
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 @@ | |||
47 | top: 2px; | 47 | top: 2px; |
48 | } | 48 | } |
49 | 49 | ||
50 | #rates { | ||
51 | display: inline-block; | ||
52 | margin-right: 20px; | ||
53 | |||
54 | // Remove focus style | ||
55 | .btn:focus { | ||
56 | outline: 0; | ||
57 | } | ||
58 | |||
59 | .activated-btn { | ||
60 | color: #333; | ||
61 | background-color: #e6e6e6; | ||
62 | border-color: #8c8c8c; | ||
63 | } | ||
64 | |||
65 | .not-interactive-btn { | ||
66 | cursor: default; | ||
67 | |||
68 | &:hover, &:focus, &:active { | ||
69 | color: #333; | ||
70 | background-color: #fff; | ||
71 | border-color: #ccc; | ||
72 | box-shadow: none; | ||
73 | outline: 0; | ||
74 | } | ||
75 | } | ||
76 | } | ||
77 | |||
50 | #share, #more { | 78 | #share, #more { |
51 | font-weight: bold; | 79 | font-weight: bold; |
52 | opacity: 0.85; | 80 | 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'; | |||
10 | import { VideoMagnetComponent } from './video-magnet.component'; | 10 | import { VideoMagnetComponent } from './video-magnet.component'; |
11 | import { VideoShareComponent } from './video-share.component'; | 11 | import { VideoShareComponent } from './video-share.component'; |
12 | import { VideoReportComponent } from './video-report.component'; | 12 | import { VideoReportComponent } from './video-report.component'; |
13 | import { Video, VideoService } from '../shared'; | 13 | import { RateType, Video, VideoService } from '../shared'; |
14 | import { WebTorrentService } from './webtorrent.service'; | 14 | import { WebTorrentService } from './webtorrent.service'; |
15 | 15 | ||
16 | @Component({ | 16 | @Component({ |
@@ -33,6 +33,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
33 | player: VideoJSPlayer; | 33 | player: VideoJSPlayer; |
34 | playerElement: Element; | 34 | playerElement: Element; |
35 | uploadSpeed: number; | 35 | uploadSpeed: number; |
36 | userRating: RateType = null; | ||
36 | video: Video = null; | 37 | video: Video = null; |
37 | videoNotFound = false; | 38 | videoNotFound = false; |
38 | 39 | ||
@@ -61,6 +62,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
61 | this.video = video; | 62 | this.video = video; |
62 | this.setOpenGraphTags(); | 63 | this.setOpenGraphTags(); |
63 | this.loadVideo(); | 64 | this.loadVideo(); |
65 | this.checkUserRating(); | ||
64 | }, | 66 | }, |
65 | error => { | 67 | error => { |
66 | this.videoNotFound = true; | 68 | this.videoNotFound = true; |
@@ -136,6 +138,40 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
136 | }); | 138 | }); |
137 | } | 139 | } |
138 | 140 | ||
141 | setLike() { | ||
142 | if (this.isUserLoggedIn() === false) return; | ||
143 | // Already liked this video | ||
144 | if (this.userRating === 'like') return; | ||
145 | |||
146 | this.videoService.setVideoLike(this.video.id) | ||
147 | .subscribe( | ||
148 | () => { | ||
149 | // Update the video like attribute | ||
150 | this.updateVideoRating(this.userRating, 'like'); | ||
151 | this.userRating = 'like'; | ||
152 | }, | ||
153 | |||
154 | err => this.notificationsService.error('Error', err.text) | ||
155 | ); | ||
156 | } | ||
157 | |||
158 | setDislike() { | ||
159 | if (this.isUserLoggedIn() === false) return; | ||
160 | // Already disliked this video | ||
161 | if (this.userRating === 'dislike') return; | ||
162 | |||
163 | this.videoService.setVideoDislike(this.video.id) | ||
164 | .subscribe( | ||
165 | () => { | ||
166 | // Update the video dislike attribute | ||
167 | this.updateVideoRating(this.userRating, 'dislike'); | ||
168 | this.userRating = 'dislike'; | ||
169 | }, | ||
170 | |||
171 | err => this.notificationsService.error('Error', err.text) | ||
172 | ); | ||
173 | } | ||
174 | |||
139 | showReportModal(event: Event) { | 175 | showReportModal(event: Event) { |
140 | event.preventDefault(); | 176 | event.preventDefault(); |
141 | this.videoReportModal.show(); | 177 | this.videoReportModal.show(); |
@@ -154,6 +190,38 @@ export class VideoWatchComponent implements OnInit, OnDestroy { | |||
154 | return this.authService.isLoggedIn(); | 190 | return this.authService.isLoggedIn(); |
155 | } | 191 | } |
156 | 192 | ||
193 | private checkUserRating() { | ||
194 | // Unlogged users do not have ratings | ||
195 | if (this.isUserLoggedIn() === false) return; | ||
196 | |||
197 | this.videoService.getUserVideoRating(this.video.id) | ||
198 | .subscribe( | ||
199 | ratingObject => { | ||
200 | if (ratingObject) { | ||
201 | this.userRating = ratingObject.rating; | ||
202 | } | ||
203 | }, | ||
204 | |||
205 | err => this.notificationsService.error('Error', err.text) | ||
206 | ); | ||
207 | } | ||
208 | |||
209 | private updateVideoRating(oldRating: RateType, newRating: RateType) { | ||
210 | let likesToIncrement = 0; | ||
211 | let dislikesToIncrement = 0; | ||
212 | |||
213 | if (oldRating) { | ||
214 | if (oldRating === 'like') likesToIncrement--; | ||
215 | if (oldRating === 'dislike') dislikesToIncrement--; | ||
216 | } | ||
217 | |||
218 | if (newRating === 'like') likesToIncrement++; | ||
219 | if (newRating === 'dislike') dislikesToIncrement++; | ||
220 | |||
221 | this.video.likes += likesToIncrement; | ||
222 | this.video.dislikes += dislikesToIncrement; | ||
223 | } | ||
224 | |||
157 | private loadTooLong() { | 225 | private loadTooLong() { |
158 | this.error = true; | 226 | this.error = true; |
159 | console.error('The video load seems to be abnormally long.'); | 227 | console.error('The video load seems to be abnormally long.'); |