aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src/app/videos
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/app/videos')
-rw-r--r--client/src/app/videos/index.ts14
-rw-r--r--client/src/app/videos/shared/index.ts8
-rw-r--r--client/src/app/videos/shared/rate-type.type.ts2
-rw-r--r--client/src/app/videos/shared/sort-field.type.ts3
-rw-r--r--client/src/app/videos/shared/video.model.ts150
-rw-r--r--client/src/app/videos/shared/video.service.ts146
-rw-r--r--client/src/app/videos/video-edit/index.ts4
-rw-r--r--client/src/app/videos/video-edit/video-add.component.ts159
-rw-r--r--client/src/app/videos/video-edit/video-update.component.ts99
-rw-r--r--client/src/app/videos/video-list/index.ts8
-rw-r--r--client/src/app/videos/video-list/loader.component.ts4
-rw-r--r--client/src/app/videos/video-list/video-list.component.html2
-rw-r--r--client/src/app/videos/video-list/video-list.component.ts126
-rw-r--r--client/src/app/videos/video-list/video-miniature.component.ts31
-rw-r--r--client/src/app/videos/video-list/video-sort.component.ts22
-rw-r--r--client/src/app/videos/video-watch/index.ts10
-rw-r--r--client/src/app/videos/video-watch/video-magnet.component.ts20
-rw-r--r--client/src/app/videos/video-watch/video-report.component.ts56
-rw-r--r--client/src/app/videos/video-watch/video-share.component.ts32
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.ts323
-rw-r--r--client/src/app/videos/video-watch/webtorrent.service.ts34
-rw-r--r--client/src/app/videos/videos-routing.module.ts14
-rw-r--r--client/src/app/videos/videos.component.ts2
-rw-r--r--client/src/app/videos/videos.module.ts18
24 files changed, 642 insertions, 645 deletions
diff --git a/client/src/app/videos/index.ts b/client/src/app/videos/index.ts
index 5158a23f8..83edcc758 100644
--- a/client/src/app/videos/index.ts
+++ b/client/src/app/videos/index.ts
@@ -1,7 +1,7 @@
1export * from './shared'; 1export * from './shared'
2export * from './video-edit'; 2export * from './video-edit'
3export * from './video-list'; 3export * from './video-list'
4export * from './video-watch'; 4export * from './video-watch'
5export * from './videos-routing.module'; 5export * from './videos-routing.module'
6export * from './videos.component'; 6export * from './videos.component'
7export * from './videos.module'; 7export * from './videos.module'
diff --git a/client/src/app/videos/shared/index.ts b/client/src/app/videos/shared/index.ts
index a68491022..0fa14f641 100644
--- a/client/src/app/videos/shared/index.ts
+++ b/client/src/app/videos/shared/index.ts
@@ -1,4 +1,4 @@
1export * from './sort-field.type'; 1export * from './sort-field.type'
2export * from './rate-type.type'; 2export * from './rate-type.type'
3export * from './video.model'; 3export * from './video.model'
4export * from './video.service'; 4export * 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
index 88034d1ff..20eea3ae5 100644
--- a/client/src/app/videos/shared/rate-type.type.ts
+++ b/client/src/app/videos/shared/rate-type.type.ts
@@ -1 +1 @@
export type RateType = 'like' | 'dislike'; 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 6cc598d8b..776f360f8 100644
--- a/client/src/app/videos/shared/sort-field.type.ts
+++ b/client/src/app/videos/shared/sort-field.type.ts
@@ -2,5 +2,4 @@ export type SortField = 'name' | '-name'
2 | 'duration' | '-duration' 2 | 'duration' | '-duration'
3 | 'createdAt' | '-createdAt' 3 | 'createdAt' | '-createdAt'
4 | 'views' | '-views' 4 | 'views' | '-views'
5 | 'likes' | '-likes'; 5 | 'likes' | '-likes'
6
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts
index e897eb175..f5e16fc13 100644
--- a/client/src/app/videos/shared/video.model.ts
+++ b/client/src/app/videos/shared/video.model.ts
@@ -1,56 +1,56 @@
1import { Video as VideoServerModel } from '../../../../../shared'; 1import { Video as VideoServerModel } from '../../../../../shared'
2import { User } from '../../shared'; 2import { User } from '../../shared'
3 3
4export class Video implements VideoServerModel { 4export class Video implements VideoServerModel {
5 author: string; 5 author: string
6 by: string; 6 by: string
7 createdAt: Date; 7 createdAt: Date
8 categoryLabel: string; 8 categoryLabel: string
9 category: number; 9 category: number
10 licenceLabel: string; 10 licenceLabel: string
11 licence: number; 11 licence: number
12 languageLabel: string; 12 languageLabel: string
13 language: number; 13 language: number
14 description: string; 14 description: string
15 duration: number; 15 duration: number
16 durationLabel: string; 16 durationLabel: string
17 id: string; 17 id: string
18 isLocal: boolean; 18 isLocal: boolean
19 magnetUri: string; 19 magnetUri: string
20 name: string; 20 name: string
21 podHost: string; 21 podHost: string
22 tags: string[]; 22 tags: string[]
23 thumbnailPath: string; 23 thumbnailPath: string
24 thumbnailUrl: string; 24 thumbnailUrl: string
25 views: number; 25 views: number
26 likes: number; 26 likes: number
27 dislikes: number; 27 dislikes: number
28 nsfw: boolean; 28 nsfw: boolean
29 29
30 private static createByString(author: string, podHost: string) { 30 private static createByString (author: string, podHost: string) {
31 return author + '@' + podHost; 31 return author + '@' + podHost
32 } 32 }
33 33
34 private static createDurationString(duration: number) { 34 private static createDurationString (duration: number) {
35 const minutes = Math.floor(duration / 60); 35 const minutes = Math.floor(duration / 60)
36 const seconds = duration % 60; 36 const seconds = duration % 60
37 const minutes_padding = minutes >= 10 ? '' : '0'; 37 const minutesPadding = minutes >= 10 ? '' : '0'
38 const seconds_padding = seconds >= 10 ? '' : '0'; 38 const secondsPadding = seconds >= 10 ? '' : '0'
39 39
40 return minutes_padding + minutes.toString() + ':' + seconds_padding + seconds.toString(); 40 return minutesPadding + minutes.toString() + ':' + secondsPadding + seconds.toString()
41 } 41 }
42 42
43 constructor(hash: { 43 constructor (hash: {
44 author: string, 44 author: string,
45 createdAt: string, 45 createdAt: string,
46 categoryLabel: string, 46 categoryLabel: string,
47 category: number, 47 category: number,
48 licenceLabel: string, 48 licenceLabel: string,
49 licence: number, 49 licence: number,
50 languageLabel: string; 50 languageLabel: string
51 language: number; 51 language: number
52 description: string, 52 description: string,
53 duration: number; 53 duration: number
54 id: string, 54 id: string,
55 isLocal: boolean, 55 isLocal: boolean,
56 magnetUri: string, 56 magnetUri: string,
@@ -63,57 +63,57 @@ export class Video implements VideoServerModel {
63 dislikes: number, 63 dislikes: number,
64 nsfw: boolean 64 nsfw: boolean
65 }) { 65 }) {
66 this.author = hash.author; 66 this.author = hash.author
67 this.createdAt = new Date(hash.createdAt); 67 this.createdAt = new Date(hash.createdAt)
68 this.categoryLabel = hash.categoryLabel; 68 this.categoryLabel = hash.categoryLabel
69 this.category = hash.category; 69 this.category = hash.category
70 this.licenceLabel = hash.licenceLabel; 70 this.licenceLabel = hash.licenceLabel
71 this.licence = hash.licence; 71 this.licence = hash.licence
72 this.languageLabel = hash.languageLabel; 72 this.languageLabel = hash.languageLabel
73 this.language = hash.language; 73 this.language = hash.language
74 this.description = hash.description; 74 this.description = hash.description
75 this.duration = hash.duration; 75 this.duration = hash.duration
76 this.durationLabel = Video.createDurationString(hash.duration); 76 this.durationLabel = Video.createDurationString(hash.duration)
77 this.id = hash.id; 77 this.id = hash.id
78 this.isLocal = hash.isLocal; 78 this.isLocal = hash.isLocal
79 this.magnetUri = hash.magnetUri; 79 this.magnetUri = hash.magnetUri
80 this.name = hash.name; 80 this.name = hash.name
81 this.podHost = hash.podHost; 81 this.podHost = hash.podHost
82 this.tags = hash.tags; 82 this.tags = hash.tags
83 this.thumbnailPath = hash.thumbnailPath; 83 this.thumbnailPath = hash.thumbnailPath
84 this.thumbnailUrl = API_URL + hash.thumbnailPath; 84 this.thumbnailUrl = API_URL + hash.thumbnailPath
85 this.views = hash.views; 85 this.views = hash.views
86 this.likes = hash.likes; 86 this.likes = hash.likes
87 this.dislikes = hash.dislikes; 87 this.dislikes = hash.dislikes
88 this.nsfw = hash.nsfw; 88 this.nsfw = hash.nsfw
89 89
90 this.by = Video.createByString(hash.author, hash.podHost); 90 this.by = Video.createByString(hash.author, hash.podHost)
91 } 91 }
92 92
93 isRemovableBy(user) { 93 isRemovableBy (user) {
94 return user && this.isLocal === true && (this.author === user.username || user.isAdmin() === true); 94 return user && this.isLocal === true && (this.author === user.username || user.isAdmin() === true)
95 } 95 }
96 96
97 isBlackistableBy(user) { 97 isBlackistableBy (user) {
98 return user && user.isAdmin() === true && this.isLocal === false; 98 return user && user.isAdmin() === true && this.isLocal === false
99 } 99 }
100 100
101 isUpdatableBy(user) { 101 isUpdatableBy (user) {
102 return user && this.isLocal === true && user.username === this.author; 102 return user && this.isLocal === true && user.username === this.author
103 } 103 }
104 104
105 isVideoNSFWForUser(user: User) { 105 isVideoNSFWForUser (user: User) {
106 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos... 106 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
107 return (this.nsfw && (!user || user.displayNSFW === false)); 107 return (this.nsfw && (!user || user.displayNSFW === false))
108 } 108 }
109 109
110 patch(values: Object) { 110 patch (values: Object) {
111 Object.keys(values).forEach((key) => { 111 Object.keys(values).forEach((key) => {
112 this[key] = values[key]; 112 this[key] = values[key]
113 }); 113 })
114 } 114 }
115 115
116 toJSON() { 116 toJSON () {
117 return { 117 return {
118 author: this.author, 118 author: this.author,
119 createdAt: this.createdAt, 119 createdAt: this.createdAt,
@@ -133,6 +133,6 @@ export class Video implements VideoServerModel {
133 likes: this.likes, 133 likes: this.likes,
134 dislikes: this.dislikes, 134 dislikes: this.dislikes,
135 nsfw: this.nsfw 135 nsfw: this.nsfw
136 }; 136 }
137 } 137 }
138} 138}
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
index a53ea1064..a4e3d16df 100644
--- a/client/src/app/videos/shared/video.service.ts
+++ b/client/src/app/videos/shared/video.service.ts
@@ -1,13 +1,13 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Http, Headers, RequestOptions } from '@angular/http'; 2import { Http, Headers, RequestOptions } from '@angular/http'
3import { Observable } from 'rxjs/Observable'; 3import { Observable } from 'rxjs/Observable'
4import 'rxjs/add/operator/catch'; 4import 'rxjs/add/operator/catch'
5import 'rxjs/add/operator/map'; 5import 'rxjs/add/operator/map'
6 6
7import { Search } from '../../shared'; 7import { Search } from '../../shared'
8import { SortField } from './sort-field.type'; 8import { SortField } from './sort-field.type'
9import { RateType } from './rate-type.type'; 9import { RateType } from './rate-type.type'
10import { AuthService } from '../../core'; 10import { AuthService } from '../../core'
11import { 11import {
12 AuthHttp, 12 AuthHttp,
13 RestExtractor, 13 RestExtractor,
@@ -15,18 +15,18 @@ import {
15 RestService, 15 RestService,
16 ResultList, 16 ResultList,
17 UserService 17 UserService
18} from '../../shared'; 18} from '../../shared'
19import { Video } from './video.model'; 19import { Video } from './video.model'
20 20
21@Injectable() 21@Injectable()
22export class VideoService { 22export class VideoService {
23 private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'; 23 private static BASE_VIDEO_URL = API_URL + '/api/v1/videos/'
24 24
25 videoCategories: Array<{ id: number, label: string }> = []; 25 videoCategories: Array<{ id: number, label: string }> = []
26 videoLicences: Array<{ id: number, label: string }> = []; 26 videoLicences: Array<{ id: number, label: string }> = []
27 videoLanguages: Array<{ id: number, label: string }> = []; 27 videoLanguages: Array<{ id: number, label: string }> = []
28 28
29 constructor( 29 constructor (
30 private authService: AuthService, 30 private authService: AuthService,
31 private authHttp: AuthHttp, 31 private authHttp: AuthHttp,
32 private http: Http, 32 private http: Http,
@@ -34,54 +34,54 @@ export class VideoService {
34 private restService: RestService 34 private restService: RestService
35 ) {} 35 ) {}
36 36
37 loadVideoCategories() { 37 loadVideoCategories () {
38 return this.http.get(VideoService.BASE_VIDEO_URL + 'categories') 38 return this.http.get(VideoService.BASE_VIDEO_URL + 'categories')
39 .map(this.restExtractor.extractDataGet) 39 .map(this.restExtractor.extractDataGet)
40 .subscribe(data => { 40 .subscribe(data => {
41 Object.keys(data).forEach(categoryKey => { 41 Object.keys(data).forEach(categoryKey => {
42 this.videoCategories.push({ 42 this.videoCategories.push({
43 id: parseInt(categoryKey), 43 id: parseInt(categoryKey, 10),
44 label: data[categoryKey] 44 label: data[categoryKey]
45 }); 45 })
46 }); 46 })
47 }); 47 })
48 } 48 }
49 49
50 loadVideoLicences() { 50 loadVideoLicences () {
51 return this.http.get(VideoService.BASE_VIDEO_URL + 'licences') 51 return this.http.get(VideoService.BASE_VIDEO_URL + 'licences')
52 .map(this.restExtractor.extractDataGet) 52 .map(this.restExtractor.extractDataGet)
53 .subscribe(data => { 53 .subscribe(data => {
54 Object.keys(data).forEach(licenceKey => { 54 Object.keys(data).forEach(licenceKey => {
55 this.videoLicences.push({ 55 this.videoLicences.push({
56 id: parseInt(licenceKey), 56 id: parseInt(licenceKey, 10),
57 label: data[licenceKey] 57 label: data[licenceKey]
58 }); 58 })
59 }); 59 })
60 }); 60 })
61 } 61 }
62 62
63 loadVideoLanguages() { 63 loadVideoLanguages () {
64 return this.http.get(VideoService.BASE_VIDEO_URL + 'languages') 64 return this.http.get(VideoService.BASE_VIDEO_URL + 'languages')
65 .map(this.restExtractor.extractDataGet) 65 .map(this.restExtractor.extractDataGet)
66 .subscribe(data => { 66 .subscribe(data => {
67 Object.keys(data).forEach(languageKey => { 67 Object.keys(data).forEach(languageKey => {
68 this.videoLanguages.push({ 68 this.videoLanguages.push({
69 id: parseInt(languageKey), 69 id: parseInt(languageKey, 10),
70 label: data[languageKey] 70 label: data[languageKey]
71 }); 71 })
72 }); 72 })
73 }); 73 })
74 } 74 }
75 75
76 getVideo(id: string): Observable<Video> { 76 getVideo (id: string): Observable<Video> {
77 return this.http.get(VideoService.BASE_VIDEO_URL + id) 77 return this.http.get(VideoService.BASE_VIDEO_URL + id)
78 .map(this.restExtractor.extractDataGet) 78 .map(this.restExtractor.extractDataGet)
79 .map(video_hash => new Video(video_hash)) 79 .map(videoHash => new Video(videoHash))
80 .catch((res) => this.restExtractor.handleError(res)); 80 .catch((res) => this.restExtractor.handleError(res))
81 } 81 }
82 82
83 updateVideo(video: Video) { 83 updateVideo (video: Video) {
84 const language = video.language ? video.language : null; 84 const language = video.language ? video.language : null
85 85
86 const body = { 86 const body = {
87 name: video.name, 87 name: video.name,
@@ -90,94 +90,94 @@ export class VideoService {
90 language, 90 language,
91 description: video.description, 91 description: video.description,
92 tags: video.tags 92 tags: video.tags
93 }; 93 }
94 94
95 const headers = new Headers({ 'Content-Type': 'application/json' }); 95 const headers = new Headers({ 'Content-Type': 'application/json' })
96 const options = new RequestOptions({ headers: headers }); 96 const options = new RequestOptions({ headers: headers })
97 97
98 return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options) 98 return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options)
99 .map(this.restExtractor.extractDataBool) 99 .map(this.restExtractor.extractDataBool)
100 .catch(this.restExtractor.handleError); 100 .catch(this.restExtractor.handleError)
101 } 101 }
102 102
103 getVideos(pagination: RestPagination, sort: SortField) { 103 getVideos (pagination: RestPagination, sort: SortField) {
104 const params = this.restService.buildRestGetParams(pagination, sort); 104 const params = this.restService.buildRestGetParams(pagination, sort)
105 105
106 return this.http.get(VideoService.BASE_VIDEO_URL, { search: params }) 106 return this.http.get(VideoService.BASE_VIDEO_URL, { search: params })
107 .map(res => res.json()) 107 .map(res => res.json())
108 .map(this.extractVideos) 108 .map(this.extractVideos)
109 .catch((res) => this.restExtractor.handleError(res)); 109 .catch((res) => this.restExtractor.handleError(res))
110 } 110 }
111 111
112 removeVideo(id: string) { 112 removeVideo (id: string) {
113 return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id) 113 return this.authHttp.delete(VideoService.BASE_VIDEO_URL + id)
114 .map(this.restExtractor.extractDataBool) 114 .map(this.restExtractor.extractDataBool)
115 .catch((res) => this.restExtractor.handleError(res)); 115 .catch((res) => this.restExtractor.handleError(res))
116 } 116 }
117 117
118 searchVideos(search: Search, pagination: RestPagination, sort: SortField) { 118 searchVideos (search: Search, pagination: RestPagination, sort: SortField) {
119 const params = this.restService.buildRestGetParams(pagination, sort); 119 const params = this.restService.buildRestGetParams(pagination, sort)
120 120
121 if (search.field) params.set('field', search.field); 121 if (search.field) params.set('field', search.field)
122 122
123 return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params }) 123 return this.http.get(VideoService.BASE_VIDEO_URL + 'search/' + encodeURIComponent(search.value), { search: params })
124 .map(this.restExtractor.extractDataList) 124 .map(this.restExtractor.extractDataList)
125 .map(this.extractVideos) 125 .map(this.extractVideos)
126 .catch((res) => this.restExtractor.handleError(res)); 126 .catch((res) => this.restExtractor.handleError(res))
127 } 127 }
128 128
129 reportVideo(id: string, reason: string) { 129 reportVideo (id: string, reason: string) {
130 const url = VideoService.BASE_VIDEO_URL + id + '/abuse'; 130 const url = VideoService.BASE_VIDEO_URL + id + '/abuse'
131 const body = { 131 const body = {
132 reason 132 reason
133 }; 133 }
134 134
135 return this.authHttp.post(url, body) 135 return this.authHttp.post(url, body)
136 .map(this.restExtractor.extractDataBool) 136 .map(this.restExtractor.extractDataBool)
137 .catch((res) => this.restExtractor.handleError(res)); 137 .catch((res) => this.restExtractor.handleError(res))
138 } 138 }
139 139
140 setVideoLike(id: string) { 140 setVideoLike (id: string) {
141 return this.setVideoRate(id, 'like'); 141 return this.setVideoRate(id, 'like')
142 } 142 }
143 143
144 setVideoDislike(id: string) { 144 setVideoDislike (id: string) {
145 return this.setVideoRate(id, 'dislike'); 145 return this.setVideoRate(id, 'dislike')
146 } 146 }
147 147
148 getUserVideoRating(id: string) { 148 getUserVideoRating (id: string) {
149 const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'; 149 const url = UserService.BASE_USERS_URL + '/me/videos/' + id + '/rating'
150 150
151 return this.authHttp.get(url) 151 return this.authHttp.get(url)
152 .map(this.restExtractor.extractDataGet) 152 .map(this.restExtractor.extractDataGet)
153 .catch((res) => this.restExtractor.handleError(res)); 153 .catch((res) => this.restExtractor.handleError(res))
154 } 154 }
155 155
156 blacklistVideo(id: string) { 156 blacklistVideo (id: string) {
157 return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {}) 157 return this.authHttp.post(VideoService.BASE_VIDEO_URL + id + '/blacklist', {})
158 .map(this.restExtractor.extractDataBool) 158 .map(this.restExtractor.extractDataBool)
159 .catch((res) => this.restExtractor.handleError(res)); 159 .catch((res) => this.restExtractor.handleError(res))
160 } 160 }
161 161
162 private setVideoRate(id: string, rateType: RateType) { 162 private setVideoRate (id: string, rateType: RateType) {
163 const url = VideoService.BASE_VIDEO_URL + id + '/rate'; 163 const url = VideoService.BASE_VIDEO_URL + id + '/rate'
164 const body = { 164 const body = {
165 rating: rateType 165 rating: rateType
166 }; 166 }
167 167
168 return this.authHttp.put(url, body) 168 return this.authHttp.put(url, body)
169 .map(this.restExtractor.extractDataBool) 169 .map(this.restExtractor.extractDataBool)
170 .catch((res) => this.restExtractor.handleError(res)); 170 .catch((res) => this.restExtractor.handleError(res))
171 } 171 }
172 172
173 private extractVideos(result: ResultList) { 173 private extractVideos (result: ResultList) {
174 const videosJson = result.data; 174 const videosJson = result.data
175 const totalVideos = result.total; 175 const totalVideos = result.total
176 const videos = []; 176 const videos = []
177 for (const videoJson of videosJson) { 177 for (const videoJson of videosJson) {
178 videos.push(new Video(videoJson)); 178 videos.push(new Video(videoJson))
179 } 179 }
180 180
181 return { videos, totalVideos }; 181 return { videos, totalVideos }
182 } 182 }
183} 183}
diff --git a/client/src/app/videos/video-edit/index.ts b/client/src/app/videos/video-edit/index.ts
index 5ce4fb9b1..3b4a9cb87 100644
--- a/client/src/app/videos/video-edit/index.ts
+++ b/client/src/app/videos/video-edit/index.ts
@@ -1,2 +1,2 @@
1export * from './video-add.component'; 1export * from './video-add.component'
2export * from './video-update.component'; 2export * from './video-update.component'
diff --git a/client/src/app/videos/video-edit/video-add.component.ts b/client/src/app/videos/video-edit/video-add.component.ts
index e5eb9a9f4..0653f5ac4 100644
--- a/client/src/app/videos/video-edit/video-add.component.ts
+++ b/client/src/app/videos/video-edit/video-add.component.ts
@@ -1,11 +1,11 @@
1import { Component, ElementRef, OnInit } from '@angular/core'; 1import { Component, ElementRef, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { Router } from '@angular/router'; 3import { Router } from '@angular/router'
4 4
5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; 5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'
6import { NotificationsService } from 'angular2-notifications'; 6import { NotificationsService } from 'angular2-notifications'
7 7
8import { AuthService } from '../../core'; 8import { AuthService } from '../../core'
9import { 9import {
10 FormReactive, 10 FormReactive,
11 VIDEO_NAME, 11 VIDEO_NAME,
@@ -14,8 +14,8 @@ import {
14 VIDEO_LANGUAGE, 14 VIDEO_LANGUAGE,
15 VIDEO_DESCRIPTION, 15 VIDEO_DESCRIPTION,
16 VIDEO_TAGS 16 VIDEO_TAGS
17} from '../../shared'; 17} from '../../shared'
18import { VideoService } from '../shared'; 18import { VideoService } from '../shared'
19 19
20@Component({ 20@Component({
21 selector: 'my-videos-add', 21 selector: 'my-videos-add',
@@ -24,36 +24,36 @@ import { VideoService } from '../shared';
24}) 24})
25 25
26export class VideoAddComponent extends FormReactive implements OnInit { 26export class VideoAddComponent extends FormReactive implements OnInit {
27 tags: string[] = []; 27 tags: string[] = []
28 uploader: FileUploader; 28 uploader: FileUploader
29 videoCategories = []; 29 videoCategories = []
30 videoLicences = []; 30 videoLicences = []
31 videoLanguages = []; 31 videoLanguages = []
32 32
33 tagValidators = VIDEO_TAGS.VALIDATORS; 33 tagValidators = VIDEO_TAGS.VALIDATORS
34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES; 34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES
35 35
36 error: string = null; 36 error: string = null
37 form: FormGroup; 37 form: FormGroup
38 formErrors = { 38 formErrors = {
39 name: '', 39 name: '',
40 category: '', 40 category: '',
41 licence: '', 41 licence: '',
42 language: '', 42 language: '',
43 description: '' 43 description: ''
44 }; 44 }
45 validationMessages = { 45 validationMessages = {
46 name: VIDEO_NAME.MESSAGES, 46 name: VIDEO_NAME.MESSAGES,
47 category: VIDEO_CATEGORY.MESSAGES, 47 category: VIDEO_CATEGORY.MESSAGES,
48 licence: VIDEO_LICENCE.MESSAGES, 48 licence: VIDEO_LICENCE.MESSAGES,
49 language: VIDEO_LANGUAGE.MESSAGES, 49 language: VIDEO_LANGUAGE.MESSAGES,
50 description: VIDEO_DESCRIPTION.MESSAGES 50 description: VIDEO_DESCRIPTION.MESSAGES
51 }; 51 }
52 52
53 // Special error messages 53 // Special error messages
54 fileError = ''; 54 fileError = ''
55 55
56 constructor( 56 constructor (
57 private authService: AuthService, 57 private authService: AuthService,
58 private elementRef: ElementRef, 58 private elementRef: ElementRef,
59 private formBuilder: FormBuilder, 59 private formBuilder: FormBuilder,
@@ -61,18 +61,18 @@ export class VideoAddComponent extends FormReactive implements OnInit {
61 private notificationsService: NotificationsService, 61 private notificationsService: NotificationsService,
62 private videoService: VideoService 62 private videoService: VideoService
63 ) { 63 ) {
64 super(); 64 super()
65 } 65 }
66 66
67 get filename() { 67 get filename () {
68 if (this.uploader.queue.length === 0) { 68 if (this.uploader.queue.length === 0) {
69 return null; 69 return null
70 } 70 }
71 71
72 return this.uploader.queue[0].file.name; 72 return this.uploader.queue[0].file.name
73 } 73 }
74 74
75 buildForm() { 75 buildForm () {
76 this.form = this.formBuilder.group({ 76 this.form = this.formBuilder.group({
77 name: [ '', VIDEO_NAME.VALIDATORS ], 77 name: [ '', VIDEO_NAME.VALIDATORS ],
78 nsfw: [ false ], 78 nsfw: [ false ],
@@ -81,115 +81,106 @@ export class VideoAddComponent extends FormReactive implements OnInit {
81 language: [ '', VIDEO_LANGUAGE.VALIDATORS ], 81 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
82 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], 82 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
83 tags: [ ''] 83 tags: [ '']
84 }); 84 })
85 85
86 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 86 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
87 } 87 }
88 88
89 ngOnInit() { 89 ngOnInit () {
90 this.videoCategories = this.videoService.videoCategories; 90 this.videoCategories = this.videoService.videoCategories
91 this.videoLicences = this.videoService.videoLicences; 91 this.videoLicences = this.videoService.videoLicences
92 this.videoLanguages = this.videoService.videoLanguages; 92 this.videoLanguages = this.videoService.videoLanguages
93 93
94 this.uploader = new FileUploader({ 94 this.uploader = new FileUploader({
95 authToken: this.authService.getRequestHeaderValue(), 95 authToken: this.authService.getRequestHeaderValue(),
96 queueLimit: 1, 96 queueLimit: 1,
97 url: API_URL + '/api/v1/videos', 97 url: API_URL + '/api/v1/videos',
98 removeAfterUpload: true 98 removeAfterUpload: true
99 }); 99 })
100 100
101 this.uploader.onBuildItemForm = (item, form) => { 101 this.uploader.onBuildItemForm = (item, form) => {
102 const name = this.form.value['name']; 102 const name = this.form.value['name']
103 const nsfw = this.form.value['nsfw']; 103 const nsfw = this.form.value['nsfw']
104 const category = this.form.value['category']; 104 const category = this.form.value['category']
105 const licence = this.form.value['licence']; 105 const licence = this.form.value['licence']
106 const language = this.form.value['language']; 106 const language = this.form.value['language']
107 const description = this.form.value['description']; 107 const description = this.form.value['description']
108 const tags = this.form.value['tags']; 108 const tags = this.form.value['tags']
109 109
110 form.append('name', name); 110 form.append('name', name)
111 form.append('category', category); 111 form.append('category', category)
112 form.append('nsfw', nsfw); 112 form.append('nsfw', nsfw)
113 form.append('licence', licence); 113 form.append('licence', licence)
114 114
115 // Language is optional 115 // Language is optional
116 if (language) { 116 if (language) {
117 form.append('language', language); 117 form.append('language', language)
118 } 118 }
119 119
120 form.append('description', description); 120 form.append('description', description)
121 121
122 for (let i = 0; i < tags.length; i++) { 122 for (let i = 0; i < tags.length; i++) {
123 form.append(`tags[${i}]`, tags[i]); 123 form.append(`tags[${i}]`, tags[i])
124 } 124 }
125 }; 125 }
126 126
127 this.buildForm(); 127 this.buildForm()
128 } 128 }
129 129
130 checkForm() { 130 checkForm () {
131 this.forceCheck(); 131 this.forceCheck()
132 132
133 if (this.filename === null) { 133 if (this.filename === null) {
134 this.fileError = 'You did not add a file.'; 134 this.fileError = 'You did not add a file.'
135 } 135 }
136 136
137 return this.form.valid === true && this.fileError === ''; 137 return this.form.valid === true && this.fileError === ''
138 } 138 }
139 139
140 fileChanged() { 140 fileChanged () {
141 this.fileError = ''; 141 this.fileError = ''
142 } 142 }
143 143
144 removeFile() { 144 removeFile () {
145 this.uploader.clearQueue(); 145 this.uploader.clearQueue()
146 } 146 }
147 147
148 upload() { 148 upload () {
149 if (this.checkForm() === false) { 149 if (this.checkForm() === false) {
150 return; 150 return
151 } 151 }
152 152
153 const item = this.uploader.queue[0]; 153 const item = this.uploader.queue[0]
154 // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242 154 // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242
155 item.alias = 'videofile'; 155 item.alias = 'videofile'
156
157 // FIXME: remove
158 // Run detection change for progress bar
159 const interval = setInterval(() => { ; }, 250);
160 156
161 item.onSuccess = () => { 157 item.onSuccess = () => {
162 clearInterval(interval); 158 console.log('Video uploaded.')
163 159 this.notificationsService.success('Success', 'Video uploaded.')
164 console.log('Video uploaded.');
165 this.notificationsService.success('Success', 'Video uploaded.');
166
167 160
168 // Print all the videos once it's finished 161 // Print all the videos once it's finished
169 this.router.navigate(['/videos/list']); 162 this.router.navigate(['/videos/list'])
170 }; 163 }
171 164
172 item.onError = (response: string, status: number) => { 165 item.onError = (response: string, status: number) => {
173 clearInterval(interval);
174
175 // We need to handle manually these cases beceause we use the FileUpload component 166 // We need to handle manually these cases beceause we use the FileUpload component
176 if (status === 400) { 167 if (status === 400) {
177 this.error = response; 168 this.error = response
178 } else if (status === 401) { 169 } else if (status === 401) {
179 this.error = 'Access token was expired, refreshing token...'; 170 this.error = 'Access token was expired, refreshing token...'
180 this.authService.refreshAccessToken().subscribe( 171 this.authService.refreshAccessToken().subscribe(
181 () => { 172 () => {
182 // Update the uploader request header 173 // Update the uploader request header
183 this.uploader.authToken = this.authService.getRequestHeaderValue(); 174 this.uploader.authToken = this.authService.getRequestHeaderValue()
184 this.error += ' access token refreshed. Please retry your request.'; 175 this.error += ' access token refreshed. Please retry your request.'
185 } 176 }
186 ); 177 )
187 } else { 178 } else {
188 this.error = 'Unknow error'; 179 this.error = 'Unknow error'
189 console.error(this.error); 180 console.error(this.error)
190 } 181 }
191 }; 182 }
192 183
193 this.uploader.uploadAll(); 184 this.uploader.uploadAll()
194 } 185 }
195} 186}
diff --git a/client/src/app/videos/video-edit/video-update.component.ts b/client/src/app/videos/video-edit/video-update.component.ts
index 933132cc0..9ee7ca6a8 100644
--- a/client/src/app/videos/video-edit/video-update.component.ts
+++ b/client/src/app/videos/video-edit/video-update.component.ts
@@ -1,11 +1,11 @@
1import { Component, ElementRef, OnInit } from '@angular/core'; 1import { Component, ElementRef, OnInit } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router'; 3import { ActivatedRoute, Router } from '@angular/router'
4 4
5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; 5import { FileUploader } from 'ng2-file-upload/ng2-file-upload'
6import { NotificationsService } from 'angular2-notifications'; 6import { NotificationsService } from 'angular2-notifications'
7 7
8import { AuthService } from '../../core'; 8import { AuthService } from '../../core'
9import { 9import {
10 FormReactive, 10 FormReactive,
11 VIDEO_NAME, 11 VIDEO_NAME,
@@ -14,8 +14,8 @@ import {
14 VIDEO_LANGUAGE, 14 VIDEO_LANGUAGE,
15 VIDEO_DESCRIPTION, 15 VIDEO_DESCRIPTION,
16 VIDEO_TAGS 16 VIDEO_TAGS
17} from '../../shared'; 17} from '../../shared'
18import { Video, VideoService } from '../shared'; 18import { Video, VideoService } from '../shared'
19 19
20@Component({ 20@Component({
21 selector: 'my-videos-update', 21 selector: 'my-videos-update',
@@ -24,35 +24,35 @@ import { Video, VideoService } from '../shared';
24}) 24})
25 25
26export class VideoUpdateComponent extends FormReactive implements OnInit { 26export class VideoUpdateComponent extends FormReactive implements OnInit {
27 tags: string[] = []; 27 tags: string[] = []
28 videoCategories = []; 28 videoCategories = []
29 videoLicences = []; 29 videoLicences = []
30 videoLanguages = []; 30 videoLanguages = []
31 video: Video; 31 video: Video
32 32
33 tagValidators = VIDEO_TAGS.VALIDATORS; 33 tagValidators = VIDEO_TAGS.VALIDATORS
34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES; 34 tagValidatorsMessages = VIDEO_TAGS.MESSAGES
35 35
36 error: string = null; 36 error: string = null
37 form: FormGroup; 37 form: FormGroup
38 formErrors = { 38 formErrors = {
39 name: '', 39 name: '',
40 category: '', 40 category: '',
41 licence: '', 41 licence: '',
42 language: '', 42 language: '',
43 description: '' 43 description: ''
44 }; 44 }
45 validationMessages = { 45 validationMessages = {
46 name: VIDEO_NAME.MESSAGES, 46 name: VIDEO_NAME.MESSAGES,
47 category: VIDEO_CATEGORY.MESSAGES, 47 category: VIDEO_CATEGORY.MESSAGES,
48 licence: VIDEO_LICENCE.MESSAGES, 48 licence: VIDEO_LICENCE.MESSAGES,
49 language: VIDEO_LANGUAGE.MESSAGES, 49 language: VIDEO_LANGUAGE.MESSAGES,
50 description: VIDEO_DESCRIPTION.MESSAGES 50 description: VIDEO_DESCRIPTION.MESSAGES
51 }; 51 }
52 52
53 fileError = ''; 53 fileError = ''
54 54
55 constructor( 55 constructor (
56 private authService: AuthService, 56 private authService: AuthService,
57 private elementRef: ElementRef, 57 private elementRef: ElementRef,
58 private formBuilder: FormBuilder, 58 private formBuilder: FormBuilder,
@@ -61,10 +61,10 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
61 private notificationsService: NotificationsService, 61 private notificationsService: NotificationsService,
62 private videoService: VideoService 62 private videoService: VideoService
63 ) { 63 ) {
64 super(); 64 super()
65 } 65 }
66 66
67 buildForm() { 67 buildForm () {
68 this.form = this.formBuilder.group({ 68 this.form = this.formBuilder.group({
69 name: [ '', VIDEO_NAME.VALIDATORS ], 69 name: [ '', VIDEO_NAME.VALIDATORS ],
70 nsfw: [ false ], 70 nsfw: [ false ],
@@ -73,60 +73,63 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
73 language: [ '', VIDEO_LANGUAGE.VALIDATORS ], 73 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
74 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], 74 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
75 tags: [ '' ] 75 tags: [ '' ]
76 }); 76 })
77 77
78 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 78 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
79 } 79 }
80 80
81 ngOnInit() { 81 ngOnInit () {
82 this.buildForm(); 82 this.buildForm()
83 83
84 this.videoCategories = this.videoService.videoCategories; 84 this.videoCategories = this.videoService.videoCategories
85 this.videoLicences = this.videoService.videoLicences; 85 this.videoLicences = this.videoService.videoLicences
86 this.videoLanguages = this.videoService.videoLanguages; 86 this.videoLanguages = this.videoService.videoLanguages
87 87
88 const id = this.route.snapshot.params['id']; 88 const id = this.route.snapshot.params['id']
89 this.videoService.getVideo(id) 89 this.videoService.getVideo(id)
90 .subscribe( 90 .subscribe(
91 video => { 91 video => {
92 this.video = video; 92 this.video = video
93 93
94 this.hydrateFormFromVideo(); 94 this.hydrateFormFromVideo()
95 }, 95 },
96 96
97 err => this.error = 'Cannot fetch video.' 97 err => {
98 ); 98 console.error(err)
99 this.error = 'Cannot fetch video.'
100 }
101 )
99 } 102 }
100 103
101 checkForm() { 104 checkForm () {
102 this.forceCheck(); 105 this.forceCheck()
103 106
104 return this.form.valid; 107 return this.form.valid
105 } 108 }
106 109
107 update() { 110 update () {
108 if (this.checkForm() === false) { 111 if (this.checkForm() === false) {
109 return; 112 return
110 } 113 }
111 114
112 this.video.patch(this.form.value); 115 this.video.patch(this.form.value)
113 116
114 this.videoService.updateVideo(this.video) 117 this.videoService.updateVideo(this.video)
115 .subscribe( 118 .subscribe(
116 () => { 119 () => {
117 this.notificationsService.success('Success', 'Video updated.'); 120 this.notificationsService.success('Success', 'Video updated.')
118 this.router.navigate([ '/videos/watch', this.video.id ]); 121 this.router.navigate([ '/videos/watch', this.video.id ])
119 }, 122 },
120 123
121 err => { 124 err => {
122 this.error = 'Cannot update the video.'; 125 this.error = 'Cannot update the video.'
123 console.error(err); 126 console.error(err)
124 } 127 }
125 ); 128 )
126 129
127 } 130 }
128 131
129 private hydrateFormFromVideo() { 132 private hydrateFormFromVideo () {
130 this.form.patchValue(this.video.toJSON()); 133 this.form.patchValue(this.video.toJSON())
131 } 134 }
132} 135}
diff --git a/client/src/app/videos/video-list/index.ts b/client/src/app/videos/video-list/index.ts
index 71d3b78e6..a490e6bb5 100644
--- a/client/src/app/videos/video-list/index.ts
+++ b/client/src/app/videos/video-list/index.ts
@@ -1,4 +1,4 @@
1export * from './loader.component'; 1export * from './loader.component'
2export * from './video-list.component'; 2export * from './video-list.component'
3export * from './video-miniature.component'; 3export * from './video-miniature.component'
4export * from './video-sort.component'; 4export * from './video-sort.component'
diff --git a/client/src/app/videos/video-list/loader.component.ts b/client/src/app/videos/video-list/loader.component.ts
index e72d2f3f3..e5780e0fa 100644
--- a/client/src/app/videos/video-list/loader.component.ts
+++ b/client/src/app/videos/video-list/loader.component.ts
@@ -1,4 +1,4 @@
1import { Component, Input } from '@angular/core'; 1import { Component, Input } from '@angular/core'
2 2
3@Component({ 3@Component({
4 selector: 'my-loader', 4 selector: 'my-loader',
@@ -7,5 +7,5 @@ import { Component, Input } from '@angular/core';
7}) 7})
8 8
9export class LoaderComponent { 9export class LoaderComponent {
10 @Input() loading: boolean; 10 @Input() loading: boolean
11} 11}
diff --git a/client/src/app/videos/video-list/video-list.component.html b/client/src/app/videos/video-list/video-list.component.html
index f80592279..680fba3f5 100644
--- a/client/src/app/videos/video-list/video-list.component.html
+++ b/client/src/app/videos/video-list/video-list.component.html
@@ -17,7 +17,7 @@
17 17
18 <my-video-miniature 18 <my-video-miniature
19 class="ng-animate" 19 class="ng-animate"
20 *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort" (removed)="onRemoved(video)" 20 *ngFor="let video of videos" [video]="video" [user]="user" [currentSort]="sort"
21 > 21 >
22 </my-video-miniature> 22 </my-video-miniature>
23</div> 23</div>
diff --git a/client/src/app/videos/video-list/video-list.component.ts b/client/src/app/videos/video-list/video-list.component.ts
index 16a40bdc4..0c36e5b08 100644
--- a/client/src/app/videos/video-list/video-list.component.ts
+++ b/client/src/app/videos/video-list/video-list.component.ts
@@ -1,17 +1,17 @@
1import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'; 1import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'; 2import { ActivatedRoute, Router } from '@angular/router'
3import { BehaviorSubject } from 'rxjs/BehaviorSubject'; 3import { BehaviorSubject } from 'rxjs/BehaviorSubject'
4 4
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { 7import {
8 SortField, 8 SortField,
9 Video, 9 Video,
10 VideoService 10 VideoService
11} from '../shared'; 11} from '../shared'
12import { AuthService, AuthUser } from '../../core'; 12import { AuthService, AuthUser } from '../../core'
13import { RestPagination, Search, SearchField } from '../../shared'; 13import { RestPagination, Search, SearchField } from '../../shared'
14import { SearchService } from '../../shared'; 14import { SearchService } from '../../shared'
15 15
16@Component({ 16@Component({
17 selector: 'my-videos-list', 17 selector: 'my-videos-list',
@@ -19,21 +19,21 @@ import { SearchService } from '../../shared';
19 templateUrl: './video-list.component.html' 19 templateUrl: './video-list.component.html'
20}) 20})
21export class VideoListComponent implements OnInit, OnDestroy { 21export class VideoListComponent implements OnInit, OnDestroy {
22 loading: BehaviorSubject<boolean> = new BehaviorSubject(false); 22 loading: BehaviorSubject<boolean> = new BehaviorSubject(false)
23 pagination: RestPagination = { 23 pagination: RestPagination = {
24 currentPage: 1, 24 currentPage: 1,
25 itemsPerPage: 25, 25 itemsPerPage: 25,
26 totalItems: null 26 totalItems: null
27 }; 27 }
28 sort: SortField; 28 sort: SortField
29 user: AuthUser = null; 29 user: AuthUser = null
30 videos: Video[] = []; 30 videos: Video[] = []
31 31
32 private search: Search; 32 private search: Search
33 private subActivatedRoute: any; 33 private subActivatedRoute: any
34 private subSearch: any; 34 private subSearch: any
35 35
36 constructor( 36 constructor (
37 private notificationsService: NotificationsService, 37 private notificationsService: NotificationsService,
38 private authService: AuthService, 38 private authService: AuthService,
39 private changeDetector: ChangeDetectorRef, 39 private changeDetector: ChangeDetectorRef,
@@ -43,114 +43,114 @@ export class VideoListComponent implements OnInit, OnDestroy {
43 private searchService: SearchService 43 private searchService: SearchService
44 ) {} 44 ) {}
45 45
46 ngOnInit() { 46 ngOnInit () {
47 if (this.authService.isLoggedIn()) { 47 if (this.authService.isLoggedIn()) {
48 this.user = AuthUser.load(); 48 this.user = AuthUser.load()
49 } 49 }
50 50
51 // Subscribe to route changes 51 // Subscribe to route changes
52 this.subActivatedRoute = this.route.params.subscribe(routeParams => { 52 this.subActivatedRoute = this.route.params.subscribe(routeParams => {
53 this.loadRouteParams(routeParams); 53 this.loadRouteParams(routeParams)
54 54
55 // Update the search service component 55 // Update the search service component
56 this.searchService.updateSearch.next(this.search); 56 this.searchService.updateSearch.next(this.search)
57 this.getVideos(); 57 this.getVideos()
58 }); 58 })
59 59
60 // Subscribe to search changes 60 // Subscribe to search changes
61 this.subSearch = this.searchService.searchUpdated.subscribe(search => { 61 this.subSearch = this.searchService.searchUpdated.subscribe(search => {
62 this.search = search; 62 this.search = search
63 // Reset pagination 63 // Reset pagination
64 this.pagination.currentPage = 1; 64 this.pagination.currentPage = 1
65 65
66 this.navigateToNewParams(); 66 this.navigateToNewParams()
67 }); 67 })
68 } 68 }
69 69
70 ngOnDestroy() { 70 ngOnDestroy () {
71 this.subActivatedRoute.unsubscribe(); 71 this.subActivatedRoute.unsubscribe()
72 this.subSearch.unsubscribe(); 72 this.subSearch.unsubscribe()
73 } 73 }
74 74
75 getVideos() { 75 getVideos () {
76 this.loading.next(true); 76 this.loading.next(true)
77 this.videos = []; 77 this.videos = []
78 78
79 let observable = null; 79 let observable = null
80 if (this.search.value) { 80 if (this.search.value) {
81 observable = this.videoService.searchVideos(this.search, this.pagination, this.sort); 81 observable = this.videoService.searchVideos(this.search, this.pagination, this.sort)
82 } else { 82 } else {
83 observable = this.videoService.getVideos(this.pagination, this.sort); 83 observable = this.videoService.getVideos(this.pagination, this.sort)
84 } 84 }
85 85
86 observable.subscribe( 86 observable.subscribe(
87 ({ videos, totalVideos }) => { 87 ({ videos, totalVideos }) => {
88 this.videos = videos; 88 this.videos = videos
89 this.pagination.totalItems = totalVideos; 89 this.pagination.totalItems = totalVideos
90 90
91 this.loading.next(false); 91 this.loading.next(false)
92 }, 92 },
93 error => this.notificationsService.error('Error', error.text) 93 error => this.notificationsService.error('Error', error.text)
94 ); 94 )
95 } 95 }
96 96
97 isThereNoVideo() { 97 isThereNoVideo () {
98 return !this.loading.getValue() && this.videos.length === 0; 98 return !this.loading.getValue() && this.videos.length === 0
99 } 99 }
100 100
101 onPageChanged(event: any) { 101 onPageChanged (event: any) {
102 // Be sure the current page is set 102 // Be sure the current page is set
103 this.pagination.currentPage = event.page; 103 this.pagination.currentPage = event.page
104 104
105 this.navigateToNewParams(); 105 this.navigateToNewParams()
106 } 106 }
107 107
108 onSort(sort: SortField) { 108 onSort (sort: SortField) {
109 this.sort = sort; 109 this.sort = sort
110 110
111 this.navigateToNewParams(); 111 this.navigateToNewParams()
112 } 112 }
113 113
114 private buildRouteParams() { 114 private buildRouteParams () {
115 // There is always a sort and a current page 115 // There is always a sort and a current page
116 const params: any = { 116 const params: any = {
117 sort: this.sort, 117 sort: this.sort,
118 page: this.pagination.currentPage 118 page: this.pagination.currentPage
119 }; 119 }
120 120
121 // Maybe there is a search 121 // Maybe there is a search
122 if (this.search.value) { 122 if (this.search.value) {
123 params.field = this.search.field; 123 params.field = this.search.field
124 params.search = this.search.value; 124 params.search = this.search.value
125 } 125 }
126 126
127 return params; 127 return params
128 } 128 }
129 129
130 private loadRouteParams(routeParams) { 130 private loadRouteParams (routeParams) {
131 if (routeParams['search'] !== undefined) { 131 if (routeParams['search'] !== undefined) {
132 this.search = { 132 this.search = {
133 value: routeParams['search'], 133 value: routeParams['search'],
134 field: <SearchField>routeParams['field'] 134 field: routeParams['field'] as SearchField
135 }; 135 }
136 } else { 136 } else {
137 this.search = { 137 this.search = {
138 value: '', 138 value: '',
139 field: 'name' 139 field: 'name'
140 }; 140 }
141 } 141 }
142 142
143 this.sort = <SortField>routeParams['sort'] || '-createdAt'; 143 this.sort = routeParams['sort'] as SortField || '-createdAt'
144 144
145 if (routeParams['page'] !== undefined) { 145 if (routeParams['page'] !== undefined) {
146 this.pagination.currentPage = parseInt(routeParams['page']); 146 this.pagination.currentPage = parseInt(routeParams['page'], 10)
147 } else { 147 } else {
148 this.pagination.currentPage = 1; 148 this.pagination.currentPage = 1
149 } 149 }
150 } 150 }
151 151
152 private navigateToNewParams() { 152 private navigateToNewParams () {
153 const routeParams = this.buildRouteParams(); 153 const routeParams = this.buildRouteParams()
154 this.router.navigate(['/videos/list', routeParams]); 154 this.router.navigate(['/videos/list', routeParams])
155 } 155 }
156} 156}
diff --git a/client/src/app/videos/video-list/video-miniature.component.ts b/client/src/app/videos/video-list/video-miniature.component.ts
index 28601ca7f..1cfeacf36 100644
--- a/client/src/app/videos/video-list/video-miniature.component.ts
+++ b/client/src/app/videos/video-list/video-miniature.component.ts
@@ -1,10 +1,10 @@
1import { Component, Input, Output, EventEmitter } from '@angular/core'; 1import { Component, Input, Output, EventEmitter } from '@angular/core'
2 2
3import { NotificationsService } from 'angular2-notifications'; 3import { NotificationsService } from 'angular2-notifications'
4 4
5import { ConfirmService, ConfigService } from '../../core'; 5import { ConfirmService, ConfigService } from '../../core'
6import { SortField, Video, VideoService } from '../shared'; 6import { SortField, Video, VideoService } from '../shared'
7import { User } from '../../shared'; 7import { User } from '../../shared'
8 8
9@Component({ 9@Component({
10 selector: 'my-video-miniature', 10 selector: 'my-video-miniature',
@@ -13,25 +13,26 @@ import { User } from '../../shared';
13}) 13})
14 14
15export class VideoMiniatureComponent { 15export class VideoMiniatureComponent {
16 @Input() currentSort: SortField; 16 @Input() currentSort: SortField
17 @Input() user: User; 17 @Input() user: User
18 @Input() video: Video; 18 @Input() video: Video
19 19
20 constructor( 20 constructor (
21 private notificationsService: NotificationsService, 21 private notificationsService: NotificationsService,
22 private confirmService: ConfirmService, 22 private confirmService: ConfirmService,
23 private configService: ConfigService, 23 private configService: ConfigService,
24 private videoService: VideoService 24 private videoService: VideoService
25 ) {} 25 ) {}
26 26
27 getVideoName() { 27 getVideoName () {
28 if (this.isVideoNSFWForThisUser()) 28 if (this.isVideoNSFWForThisUser()) {
29 return 'NSFW'; 29 return 'NSFW'
30 }
30 31
31 return this.video.name; 32 return this.video.name
32 } 33 }
33 34
34 isVideoNSFWForThisUser() { 35 isVideoNSFWForThisUser () {
35 return this.video.isVideoNSFWForUser(this.user); 36 return this.video.isVideoNSFWForUser(this.user)
36 } 37 }
37} 38}
diff --git a/client/src/app/videos/video-list/video-sort.component.ts b/client/src/app/videos/video-list/video-sort.component.ts
index 20979a395..64916bf16 100644
--- a/client/src/app/videos/video-list/video-sort.component.ts
+++ b/client/src/app/videos/video-list/video-sort.component.ts
@@ -1,6 +1,6 @@
1import { Component, EventEmitter, Input, Output } from '@angular/core'; 1import { Component, EventEmitter, Input, Output } from '@angular/core'
2 2
3import { SortField } from '../shared'; 3import { SortField } from '../shared'
4 4
5@Component({ 5@Component({
6 selector: 'my-video-sort', 6 selector: 'my-video-sort',
@@ -8,9 +8,9 @@ import { SortField } from '../shared';
8}) 8})
9 9
10export class VideoSortComponent { 10export class VideoSortComponent {
11 @Output() sort = new EventEmitter<any>(); 11 @Output() sort = new EventEmitter<any>()
12 12
13 @Input() currentSort: SortField; 13 @Input() currentSort: SortField
14 14
15 sortChoices: { [ P in SortField ]: string } = { 15 sortChoices: { [ P in SortField ]: string } = {
16 'name': 'Name - Asc', 16 'name': 'Name - Asc',
@@ -23,17 +23,17 @@ export class VideoSortComponent {
23 '-views': 'Views - Desc', 23 '-views': 'Views - Desc',
24 'likes': 'Likes - Asc', 24 'likes': 'Likes - Asc',
25 '-likes': 'Likes - Desc' 25 '-likes': 'Likes - Desc'
26 }; 26 }
27 27
28 get choiceKeys() { 28 get choiceKeys () {
29 return Object.keys(this.sortChoices); 29 return Object.keys(this.sortChoices)
30 } 30 }
31 31
32 getStringChoice(choiceKey: SortField) { 32 getStringChoice (choiceKey: SortField) {
33 return this.sortChoices[choiceKey]; 33 return this.sortChoices[choiceKey]
34 } 34 }
35 35
36 onSortChange() { 36 onSortChange () {
37 this.sort.emit(this.currentSort); 37 this.sort.emit(this.currentSort)
38 } 38 }
39} 39}
diff --git a/client/src/app/videos/video-watch/index.ts b/client/src/app/videos/video-watch/index.ts
index ed0ed2fc0..6e35262d3 100644
--- a/client/src/app/videos/video-watch/index.ts
+++ b/client/src/app/videos/video-watch/index.ts
@@ -1,5 +1,5 @@
1export * from './video-magnet.component'; 1export * from './video-magnet.component'
2export * from './video-share.component'; 2export * from './video-share.component'
3export * from './video-report.component'; 3export * from './video-report.component'
4export * from './video-watch.component'; 4export * from './video-watch.component'
5export * from './webtorrent.service'; 5export * from './webtorrent.service'
diff --git a/client/src/app/videos/video-watch/video-magnet.component.ts b/client/src/app/videos/video-watch/video-magnet.component.ts
index 894fa45fc..f9432e92c 100644
--- a/client/src/app/videos/video-watch/video-magnet.component.ts
+++ b/client/src/app/videos/video-watch/video-magnet.component.ts
@@ -1,27 +1,27 @@
1import { Component, Input, ViewChild } from '@angular/core'; 1import { Component, Input, ViewChild } from '@angular/core'
2 2
3import { ModalDirective } from 'ngx-bootstrap/modal'; 3import { ModalDirective } from 'ngx-bootstrap/modal'
4 4
5import { Video } from '../shared'; 5import { Video } from '../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-magnet', 8 selector: 'my-video-magnet',
9 templateUrl: './video-magnet.component.html' 9 templateUrl: './video-magnet.component.html'
10}) 10})
11export class VideoMagnetComponent { 11export class VideoMagnetComponent {
12 @Input() video: Video = null; 12 @Input() video: Video = null
13 13
14 @ViewChild('modal') modal: ModalDirective; 14 @ViewChild('modal') modal: ModalDirective
15 15
16 constructor() { 16 constructor () {
17 // empty 17 // empty
18 } 18 }
19 19
20 show() { 20 show () {
21 this.modal.show(); 21 this.modal.show()
22 } 22 }
23 23
24 hide() { 24 hide () {
25 this.modal.hide(); 25 this.modal.hide()
26 } 26 }
27} 27}
diff --git a/client/src/app/videos/video-watch/video-report.component.ts b/client/src/app/videos/video-watch/video-report.component.ts
index 528005b84..61213cd68 100644
--- a/client/src/app/videos/video-watch/video-report.component.ts
+++ b/client/src/app/videos/video-watch/video-report.component.ts
@@ -1,69 +1,69 @@
1import { Component, Input, OnInit, ViewChild } from '@angular/core'; 1import { Component, Input, OnInit, ViewChild } from '@angular/core'
2import { FormBuilder, FormGroup } from '@angular/forms'; 2import { FormBuilder, FormGroup } from '@angular/forms'
3 3
4import { ModalDirective } from 'ngx-bootstrap/modal'; 4import { ModalDirective } from 'ngx-bootstrap/modal'
5import { NotificationsService } from 'angular2-notifications'; 5import { NotificationsService } from 'angular2-notifications'
6 6
7import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared'; 7import { FormReactive, VideoAbuseService, VIDEO_ABUSE_REASON } from '../../shared'
8import { Video, VideoService } from '../shared'; 8import { Video, VideoService } from '../shared'
9 9
10@Component({ 10@Component({
11 selector: 'my-video-report', 11 selector: 'my-video-report',
12 templateUrl: './video-report.component.html' 12 templateUrl: './video-report.component.html'
13}) 13})
14export class VideoReportComponent extends FormReactive implements OnInit { 14export class VideoReportComponent extends FormReactive implements OnInit {
15 @Input() video: Video = null; 15 @Input() video: Video = null
16 16
17 @ViewChild('modal') modal: ModalDirective; 17 @ViewChild('modal') modal: ModalDirective
18 18
19 error: string = null; 19 error: string = null
20 form: FormGroup; 20 form: FormGroup
21 formErrors = { 21 formErrors = {
22 reason: '' 22 reason: ''
23 }; 23 }
24 validationMessages = { 24 validationMessages = {
25 reason: VIDEO_ABUSE_REASON.MESSAGES 25 reason: VIDEO_ABUSE_REASON.MESSAGES
26 }; 26 }
27 27
28 constructor( 28 constructor (
29 private formBuilder: FormBuilder, 29 private formBuilder: FormBuilder,
30 private videoAbuseService: VideoAbuseService, 30 private videoAbuseService: VideoAbuseService,
31 private notificationsService: NotificationsService 31 private notificationsService: NotificationsService
32 ) { 32 ) {
33 super(); 33 super()
34 } 34 }
35 35
36 ngOnInit() { 36 ngOnInit () {
37 this.buildForm(); 37 this.buildForm()
38 } 38 }
39 39
40 buildForm() { 40 buildForm () {
41 this.form = this.formBuilder.group({ 41 this.form = this.formBuilder.group({
42 reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ] 42 reason: [ '', VIDEO_ABUSE_REASON.VALIDATORS ]
43 }); 43 })
44 44
45 this.form.valueChanges.subscribe(data => this.onValueChanged(data)); 45 this.form.valueChanges.subscribe(data => this.onValueChanged(data))
46 } 46 }
47 47
48 show() { 48 show () {
49 this.modal.show(); 49 this.modal.show()
50 } 50 }
51 51
52 hide() { 52 hide () {
53 this.modal.hide(); 53 this.modal.hide()
54 } 54 }
55 55
56 report() { 56 report () {
57 const reason = this.form.value['reason']; 57 const reason = this.form.value['reason']
58 58
59 this.videoAbuseService.reportVideo(this.video.id, reason) 59 this.videoAbuseService.reportVideo(this.video.id, reason)
60 .subscribe( 60 .subscribe(
61 () => { 61 () => {
62 this.notificationsService.success('Success', 'Video reported.'); 62 this.notificationsService.success('Success', 'Video reported.')
63 this.hide(); 63 this.hide()
64 }, 64 },
65 65
66 err => this.notificationsService.error('Error', err.text) 66 err => this.notificationsService.error('Error', err.text)
67 ); 67 )
68 } 68 }
69} 69}
diff --git a/client/src/app/videos/video-watch/video-share.component.ts b/client/src/app/videos/video-watch/video-share.component.ts
index aa921afc2..bbd25f5ef 100644
--- a/client/src/app/videos/video-watch/video-share.component.ts
+++ b/client/src/app/videos/video-watch/video-share.component.ts
@@ -1,42 +1,42 @@
1import { Component, Input, ViewChild } from '@angular/core'; 1import { Component, Input, ViewChild } from '@angular/core'
2 2
3import { ModalDirective } from 'ngx-bootstrap/modal'; 3import { ModalDirective } from 'ngx-bootstrap/modal'
4 4
5import { Video } from '../shared'; 5import { Video } from '../shared'
6 6
7@Component({ 7@Component({
8 selector: 'my-video-share', 8 selector: 'my-video-share',
9 templateUrl: './video-share.component.html' 9 templateUrl: './video-share.component.html'
10}) 10})
11export class VideoShareComponent { 11export class VideoShareComponent {
12 @Input() video: Video = null; 12 @Input() video: Video = null
13 13
14 @ViewChild('modal') modal: ModalDirective; 14 @ViewChild('modal') modal: ModalDirective
15 15
16 constructor() { 16 constructor () {
17 // empty 17 // empty
18 } 18 }
19 19
20 show() { 20 show () {
21 this.modal.show(); 21 this.modal.show()
22 } 22 }
23 23
24 hide() { 24 hide () {
25 this.modal.hide(); 25 this.modal.hide()
26 } 26 }
27 27
28 getVideoIframeCode() { 28 getVideoIframeCode () {
29 return '<iframe width="560" height="315" ' + 29 return '<iframe width="560" height="315" ' +
30 'src="' + window.location.origin + '/videos/embed/' + this.video.id + '" ' + 30 'src="' + window.location.origin + '/videos/embed/' + this.video.id + '" ' +
31 'frameborder="0" allowfullscreen>' + 31 'frameborder="0" allowfullscreen>' +
32 '</iframe>'; 32 '</iframe>'
33 } 33 }
34 34
35 getVideoUrl() { 35 getVideoUrl () {
36 return window.location.href; 36 return window.location.href
37 } 37 }
38 38
39 notSecure() { 39 notSecure () {
40 return window.location.protocol === 'http:'; 40 return window.location.protocol === 'http:'
41 } 41 }
42} 42}
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 bcfebf2fd..4a547f7e4 100644
--- a/client/src/app/videos/video-watch/video-watch.component.ts
+++ b/client/src/app/videos/video-watch/video-watch.component.ts
@@ -1,18 +1,18 @@
1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'; 1import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core'
2import { ActivatedRoute, Router } from '@angular/router'; 2import { ActivatedRoute, Router } from '@angular/router'
3import { Observable } from 'rxjs/Observable'; 3import { Observable } from 'rxjs/Observable'
4import { Subscription } from 'rxjs/Subscription'; 4import { Subscription } from 'rxjs/Subscription'
5 5
6import * as videojs from 'video.js'; 6import * as videojs from 'video.js'
7import { MetaService } from '@nglibs/meta'; 7import { MetaService } from '@nglibs/meta'
8import { NotificationsService } from 'angular2-notifications'; 8import { NotificationsService } from 'angular2-notifications'
9 9
10import { AuthService, ConfirmService } from '../../core'; 10import { AuthService, ConfirmService } from '../../core'
11import { VideoMagnetComponent } from './video-magnet.component'; 11import { VideoMagnetComponent } from './video-magnet.component'
12import { VideoShareComponent } from './video-share.component'; 12import { VideoShareComponent } from './video-share.component'
13import { VideoReportComponent } from './video-report.component'; 13import { VideoReportComponent } from './video-report.component'
14import { RateType, Video, VideoService } from '../shared'; 14import { RateType, Video, VideoService } from '../shared'
15import { WebTorrentService } from './webtorrent.service'; 15import { WebTorrentService } from './webtorrent.service'
16 16
17@Component({ 17@Component({
18 selector: 'my-video-watch', 18 selector: 'my-video-watch',
@@ -21,30 +21,30 @@ import { WebTorrentService } from './webtorrent.service';
21}) 21})
22 22
23export class VideoWatchComponent implements OnInit, OnDestroy { 23export class VideoWatchComponent implements OnInit, OnDestroy {
24 private static LOADTIME_TOO_LONG = 20000; 24 private static LOADTIME_TOO_LONG = 20000
25 25
26 @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent; 26 @ViewChild('videoMagnetModal') videoMagnetModal: VideoMagnetComponent
27 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent; 27 @ViewChild('videoShareModal') videoShareModal: VideoShareComponent
28 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent; 28 @ViewChild('videoReportModal') videoReportModal: VideoReportComponent
29 29
30 downloadSpeed: number; 30 downloadSpeed: number
31 error = false; 31 error = false
32 loading = false; 32 loading = false
33 numPeers: number; 33 numPeers: number
34 player: videojs.Player; 34 player: videojs.Player
35 playerElement: Element; 35 playerElement: Element
36 uploadSpeed: number; 36 uploadSpeed: number
37 userRating: RateType = null; 37 userRating: RateType = null
38 video: Video = null; 38 video: Video = null
39 videoNotFound = false; 39 videoNotFound = false
40 40
41 private errorTimer: number; 41 private errorTimer: number
42 private paramsSub: Subscription; 42 private paramsSub: Subscription
43 private errorsSub: Subscription; 43 private errorsSub: Subscription
44 private warningsSub: Subscription; 44 private warningsSub: Subscription
45 private torrentInfosInterval: number; 45 private torrentInfosInterval: number
46 46
47 constructor( 47 constructor (
48 private elementRef: ElementRef, 48 private elementRef: ElementRef,
49 private ngZone: NgZone, 49 private ngZone: NgZone,
50 private route: ActivatedRoute, 50 private route: ActivatedRoute,
@@ -57,278 +57,281 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
57 private notificationsService: NotificationsService 57 private notificationsService: NotificationsService
58 ) {} 58 ) {}
59 59
60 ngOnInit() { 60 ngOnInit () {
61 this.paramsSub = this.route.params.subscribe(routeParams => { 61 this.paramsSub = this.route.params.subscribe(routeParams => {
62 let id = routeParams['id']; 62 let id = routeParams['id']
63 this.videoService.getVideo(id).subscribe( 63 this.videoService.getVideo(id).subscribe(
64 video => this.onVideoFetched(video), 64 video => this.onVideoFetched(video),
65 65
66 error => this.videoNotFound = true 66 error => {
67 ); 67 console.error(error)
68 }); 68 this.videoNotFound = true
69 }
70 )
71 })
69 72
70 this.playerElement = this.elementRef.nativeElement.querySelector('#video-container'); 73 this.playerElement = this.elementRef.nativeElement.querySelector('#video-container')
71 74
72 const videojsOptions = { 75 const videojsOptions = {
73 controls: true, 76 controls: true,
74 autoplay: false 77 autoplay: false
75 }; 78 }
76 79
77 const self = this; 80 const self = this
78 videojs(this.playerElement, videojsOptions, function () { 81 videojs(this.playerElement, videojsOptions, function () {
79 self.player = this; 82 self.player = this
80 }); 83 })
81 84
82 this.errorsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.error('Error', err.message)); 85 this.errorsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.error('Error', err.message))
83 this.warningsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.alert('Warning', err.message)); 86 this.warningsSub = this.webTorrentService.errors.subscribe(err => this.notificationsService.alert('Warning', err.message))
84 } 87 }
85 88
86 ngOnDestroy() { 89 ngOnDestroy () {
87 // Remove WebTorrent stuff 90 // Remove WebTorrent stuff
88 console.log('Removing video from webtorrent.'); 91 console.log('Removing video from webtorrent.')
89 window.clearInterval(this.torrentInfosInterval); 92 window.clearInterval(this.torrentInfosInterval)
90 window.clearTimeout(this.errorTimer); 93 window.clearTimeout(this.errorTimer)
91 94
92 if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) { 95 if (this.video !== null && this.webTorrentService.has(this.video.magnetUri)) {
93 this.webTorrentService.remove(this.video.magnetUri); 96 this.webTorrentService.remove(this.video.magnetUri)
94 } 97 }
95 98
96 // Remove player 99 // Remove player
97 videojs(this.playerElement).dispose(); 100 videojs(this.playerElement).dispose()
98 101
99 // Unsubscribe subscriptions 102 // Unsubscribe subscriptions
100 this.paramsSub.unsubscribe(); 103 this.paramsSub.unsubscribe()
101 this.errorsSub.unsubscribe(); 104 this.errorsSub.unsubscribe()
102 this.warningsSub.unsubscribe(); 105 this.warningsSub.unsubscribe()
103 } 106 }
104 107
105 loadVideo() { 108 loadVideo () {
106 // Reset the error 109 // Reset the error
107 this.error = false; 110 this.error = false
108 // We are loading the video 111 // We are loading the video
109 this.loading = true; 112 this.loading = true
110 113
111 console.log('Adding ' + this.video.magnetUri + '.'); 114 console.log('Adding ' + this.video.magnetUri + '.')
112 115
113 // The callback might never return if there are network issues 116 // The callback might never return if there are network issues
114 // So we create a timer to inform the user the load is abnormally long 117 // So we create a timer to inform the user the load is abnormally long
115 this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG); 118 this.errorTimer = window.setTimeout(() => this.loadTooLong(), VideoWatchComponent.LOADTIME_TOO_LONG)
116 119
117 this.webTorrentService.add(this.video.magnetUri, (torrent) => { 120 this.webTorrentService.add(this.video.magnetUri, (torrent) => {
118 // Clear the error timer 121 // Clear the error timer
119 window.clearTimeout(this.errorTimer); 122 window.clearTimeout(this.errorTimer)
120 // Maybe the error was fired by the timer, so reset it 123 // Maybe the error was fired by the timer, so reset it
121 this.error = false; 124 this.error = false
122 125
123 // We are not loading the video anymore 126 // We are not loading the video anymore
124 this.loading = false; 127 this.loading = false
125 128
126 console.log('Added ' + this.video.magnetUri + '.'); 129 console.log('Added ' + this.video.magnetUri + '.')
127 torrent.files[0].renderTo(this.playerElement, { autoplay: true }, (err) => { 130 torrent.files[0].renderTo(this.playerElement, { autoplay: true }, (err) => {
128 if (err) { 131 if (err) {
129 this.notificationsService.error('Error', 'Cannot append the file in the video element.'); 132 this.notificationsService.error('Error', 'Cannot append the file in the video element.')
130 console.error(err); 133 console.error(err)
131 } 134 }
132 }); 135 })
133 136
134 this.runInProgress(torrent); 137 this.runInProgress(torrent)
135 }); 138 })
136 } 139 }
137 140
138 setLike() { 141 setLike () {
139 if (this.isUserLoggedIn() === false) return; 142 if (this.isUserLoggedIn() === false) return
140 // Already liked this video 143 // Already liked this video
141 if (this.userRating === 'like') return; 144 if (this.userRating === 'like') return
142 145
143 this.videoService.setVideoLike(this.video.id) 146 this.videoService.setVideoLike(this.video.id)
144 .subscribe( 147 .subscribe(
145 () => { 148 () => {
146 // Update the video like attribute 149 // Update the video like attribute
147 this.updateVideoRating(this.userRating, 'like'); 150 this.updateVideoRating(this.userRating, 'like')
148 this.userRating = 'like'; 151 this.userRating = 'like'
149 }, 152 },
150 153
151 err => this.notificationsService.error('Error', err.text) 154 err => this.notificationsService.error('Error', err.text)
152 ); 155 )
153 } 156 }
154 157
155 setDislike() { 158 setDislike () {
156 if (this.isUserLoggedIn() === false) return; 159 if (this.isUserLoggedIn() === false) return
157 // Already disliked this video 160 // Already disliked this video
158 if (this.userRating === 'dislike') return; 161 if (this.userRating === 'dislike') return
159 162
160 this.videoService.setVideoDislike(this.video.id) 163 this.videoService.setVideoDislike(this.video.id)
161 .subscribe( 164 .subscribe(
162 () => { 165 () => {
163 // Update the video dislike attribute 166 // Update the video dislike attribute
164 this.updateVideoRating(this.userRating, 'dislike'); 167 this.updateVideoRating(this.userRating, 'dislike')
165 this.userRating = 'dislike'; 168 this.userRating = 'dislike'
166 }, 169 },
167 170
168 err => this.notificationsService.error('Error', err.text) 171 err => this.notificationsService.error('Error', err.text)
169 ); 172 )
170 } 173 }
171 174
172 removeVideo(event: Event) { 175 removeVideo (event: Event) {
173 event.preventDefault(); 176 event.preventDefault()
174 177
175 this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe( 178 this.confirmService.confirm('Do you really want to delete this video?', 'Delete').subscribe(
176 res => { 179 res => {
177 if (res === false) return; 180 if (res === false) return
178 181
179 this.videoService.removeVideo(this.video.id) 182 this.videoService.removeVideo(this.video.id)
180 .subscribe( 183 .subscribe(
181 status => { 184 status => {
182 this.notificationsService.success('Success', `Video ${this.video.name} deleted.`); 185 this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
183 // Go back to the video-list. 186 // Go back to the video-list.
184 this.router.navigate(['/videos/list']); 187 this.router.navigate(['/videos/list'])
185 }, 188 },
186 189
187 error => this.notificationsService.error('Error', error.text) 190 error => this.notificationsService.error('Error', error.text)
188 ); 191 )
189 } 192 }
190 ); 193 )
191 } 194 }
192 195
193 blacklistVideo(event: Event) { 196 blacklistVideo (event: Event) {
194 event.preventDefault(); 197 event.preventDefault()
195 198
196 this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe( 199 this.confirmService.confirm('Do you really want to blacklist this video ?', 'Blacklist').subscribe(
197 res => { 200 res => {
198 if (res === false) return; 201 if (res === false) return
199 202
200 this.videoService.blacklistVideo(this.video.id) 203 this.videoService.blacklistVideo(this.video.id)
201 .subscribe( 204 .subscribe(
202 status => { 205 status => {
203 this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`); 206 this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
204 this.router.navigate(['/videos/list']); 207 this.router.navigate(['/videos/list'])
205 }, 208 },
206 209
207 error => this.notificationsService.error('Error', error.text) 210 error => this.notificationsService.error('Error', error.text)
208 ); 211 )
209 } 212 }
210 ); 213 )
211 } 214 }
212 215
213 showReportModal(event: Event) { 216 showReportModal (event: Event) {
214 event.preventDefault(); 217 event.preventDefault()
215 this.videoReportModal.show(); 218 this.videoReportModal.show()
216 } 219 }
217 220
218 showShareModal() { 221 showShareModal () {
219 this.videoShareModal.show(); 222 this.videoShareModal.show()
220 } 223 }
221 224
222 showMagnetUriModal(event: Event) { 225 showMagnetUriModal (event: Event) {
223 event.preventDefault(); 226 event.preventDefault()
224 this.videoMagnetModal.show(); 227 this.videoMagnetModal.show()
225 } 228 }
226 229
227 isUserLoggedIn() { 230 isUserLoggedIn () {
228 return this.authService.isLoggedIn(); 231 return this.authService.isLoggedIn()
229 } 232 }
230 233
231 canUserUpdateVideo() { 234 canUserUpdateVideo () {
232 return this.video.isUpdatableBy(this.authService.getUser()); 235 return this.video.isUpdatableBy(this.authService.getUser())
233 } 236 }
234 237
235 isVideoRemovable() { 238 isVideoRemovable () {
236 return this.video.isRemovableBy(this.authService.getUser()); 239 return this.video.isRemovableBy(this.authService.getUser())
237 } 240 }
238 241
239 isVideoBlacklistable() { 242 isVideoBlacklistable () {
240 return this.video.isBlackistableBy(this.authService.getUser()); 243 return this.video.isBlackistableBy(this.authService.getUser())
241 } 244 }
242 245
243 private checkUserRating() { 246 private checkUserRating () {
244 // Unlogged users do not have ratings 247 // Unlogged users do not have ratings
245 if (this.isUserLoggedIn() === false) return; 248 if (this.isUserLoggedIn() === false) return
246 249
247 this.videoService.getUserVideoRating(this.video.id) 250 this.videoService.getUserVideoRating(this.video.id)
248 .subscribe( 251 .subscribe(
249 ratingObject => { 252 ratingObject => {
250 if (ratingObject) { 253 if (ratingObject) {
251 this.userRating = ratingObject.rating; 254 this.userRating = ratingObject.rating
252 } 255 }
253 }, 256 },
254 257
255 err => this.notificationsService.error('Error', err.text) 258 err => this.notificationsService.error('Error', err.text)
256 ); 259 )
257 } 260 }
258 261
259 private onVideoFetched(video: Video) { 262 private onVideoFetched (video: Video) {
260 this.video = video; 263 this.video = video
261 264
262 let observable; 265 let observable
263 if (this.video.isVideoNSFWForUser(this.authService.getUser())) { 266 if (this.video.isVideoNSFWForUser(this.authService.getUser())) {
264 observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW'); 267 observable = this.confirmService.confirm('This video is not safe for work. Are you sure you want to watch it?', 'NSFW')
265 } else { 268 } else {
266 observable = Observable.of(true); 269 observable = Observable.of(true)
267 } 270 }
268 271
269 observable.subscribe( 272 observable.subscribe(
270 res => { 273 res => {
271 if (res === false) { 274 if (res === false) {
272 return this.router.navigate([ '/videos/list' ]); 275 return this.router.navigate([ '/videos/list' ])
273 } 276 }
274 277
275 this.setOpenGraphTags(); 278 this.setOpenGraphTags()
276 this.loadVideo(); 279 this.loadVideo()
277 this.checkUserRating(); 280 this.checkUserRating()
278 } 281 }
279 ); 282 )
280 } 283 }
281 284
282 private updateVideoRating(oldRating: RateType, newRating: RateType) { 285 private updateVideoRating (oldRating: RateType, newRating: RateType) {
283 let likesToIncrement = 0; 286 let likesToIncrement = 0
284 let dislikesToIncrement = 0; 287 let dislikesToIncrement = 0
285 288
286 if (oldRating) { 289 if (oldRating) {
287 if (oldRating === 'like') likesToIncrement--; 290 if (oldRating === 'like') likesToIncrement--
288 if (oldRating === 'dislike') dislikesToIncrement--; 291 if (oldRating === 'dislike') dislikesToIncrement--
289 } 292 }
290 293
291 if (newRating === 'like') likesToIncrement++; 294 if (newRating === 'like') likesToIncrement++
292 if (newRating === 'dislike') dislikesToIncrement++; 295 if (newRating === 'dislike') dislikesToIncrement++
293 296
294 this.video.likes += likesToIncrement; 297 this.video.likes += likesToIncrement
295 this.video.dislikes += dislikesToIncrement; 298 this.video.dislikes += dislikesToIncrement
296 } 299 }
297 300
298 private loadTooLong() { 301 private loadTooLong () {
299 this.error = true; 302 this.error = true
300 console.error('The video load seems to be abnormally long.'); 303 console.error('The video load seems to be abnormally long.')
301 } 304 }
302 305
303 private setOpenGraphTags() { 306 private setOpenGraphTags () {
304 this.metaService.setTitle(this.video.name); 307 this.metaService.setTitle(this.video.name)
305 308
306 this.metaService.setTag('og:type', 'video'); 309 this.metaService.setTag('og:type', 'video')
307 310
308 this.metaService.setTag('og:title', this.video.name); 311 this.metaService.setTag('og:title', this.video.name)
309 this.metaService.setTag('name', this.video.name); 312 this.metaService.setTag('name', this.video.name)
310 313
311 this.metaService.setTag('og:description', this.video.description); 314 this.metaService.setTag('og:description', this.video.description)
312 this.metaService.setTag('description', this.video.description); 315 this.metaService.setTag('description', this.video.description)
313 316
314 this.metaService.setTag('og:image', this.video.thumbnailPath); 317 this.metaService.setTag('og:image', this.video.thumbnailPath)
315 318
316 this.metaService.setTag('og:duration', this.video.duration.toString()); 319 this.metaService.setTag('og:duration', this.video.duration.toString())
317 320
318 this.metaService.setTag('og:site_name', 'PeerTube'); 321 this.metaService.setTag('og:site_name', 'PeerTube')
319 322
320 this.metaService.setTag('og:url', window.location.href); 323 this.metaService.setTag('og:url', window.location.href)
321 this.metaService.setTag('url', window.location.href); 324 this.metaService.setTag('url', window.location.href)
322 } 325 }
323 326
324 private runInProgress(torrent: any) { 327 private runInProgress (torrent: any) {
325 // Refresh each second 328 // Refresh each second
326 this.torrentInfosInterval = window.setInterval(() => { 329 this.torrentInfosInterval = window.setInterval(() => {
327 this.ngZone.run(() => { 330 this.ngZone.run(() => {
328 this.downloadSpeed = torrent.downloadSpeed; 331 this.downloadSpeed = torrent.downloadSpeed
329 this.numPeers = torrent.numPeers; 332 this.numPeers = torrent.numPeers
330 this.uploadSpeed = torrent.uploadSpeed; 333 this.uploadSpeed = torrent.uploadSpeed
331 }); 334 })
332 }, 1000); 335 }, 1000)
333 } 336 }
334} 337}
diff --git a/client/src/app/videos/video-watch/webtorrent.service.ts b/client/src/app/videos/video-watch/webtorrent.service.ts
index 8936e7992..211894bfd 100644
--- a/client/src/app/videos/video-watch/webtorrent.service.ts
+++ b/client/src/app/videos/video-watch/webtorrent.service.ts
@@ -1,33 +1,33 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core'
2import { Subject } from 'rxjs/Subject'; 2import { Subject } from 'rxjs/Subject'
3 3
4import * as WebTorrent from 'webtorrent'; 4import * as WebTorrent from 'webtorrent'
5 5
6@Injectable() 6@Injectable()
7export class WebTorrentService { 7export class WebTorrentService {
8 errors = new Subject<Error>(); 8 errors = new Subject<Error>()
9 warnings = new Subject<Error>(); 9 warnings = new Subject<Error>()
10 10
11 // TODO: use WebTorrent @type 11 // TODO: use WebTorrent @type
12 // private client: WebTorrent.Client; 12 // private client: WebTorrent.Client
13 private client: any; 13 private client: any
14 14
15 constructor() { 15 constructor () {
16 this.client = new WebTorrent({ dht: false }); 16 this.client = new WebTorrent({ dht: false })
17 17
18 this.client.on('error', (err) => this.errors.next(err)); 18 this.client.on('error', (err) => this.errors.next(err))
19 this.client.on('warning', (err) => this.warnings.next(err)); 19 this.client.on('warning', (err) => this.warnings.next(err))
20 } 20 }
21 21
22 add(magnetUri: string, callback: Function) { 22 add (magnetUri: string, callback: Function) {
23 return this.client.add(magnetUri, callback); 23 return this.client.add(magnetUri, callback)
24 } 24 }
25 25
26 remove(magnetUri: string) { 26 remove (magnetUri: string) {
27 return this.client.remove(magnetUri); 27 return this.client.remove(magnetUri)
28 } 28 }
29 29
30 has(magnetUri: string) { 30 has (magnetUri: string) {
31 return this.client.get(magnetUri) !== null; 31 return this.client.get(magnetUri) !== null
32 } 32 }
33} 33}
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 70968b4d1..7d002abde 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,10 +1,10 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2import { RouterModule, Routes } from '@angular/router'; 2import { RouterModule, Routes } from '@angular/router'
3 3
4import { VideoAddComponent, VideoUpdateComponent } from './video-edit'; 4import { VideoAddComponent, VideoUpdateComponent } from './video-edit'
5import { VideoListComponent } from './video-list'; 5import { VideoListComponent } from './video-list'
6import { VideosComponent } from './videos.component'; 6import { VideosComponent } from './videos.component'
7import { VideoWatchComponent } from './video-watch'; 7import { VideoWatchComponent } from './video-watch'
8 8
9const videosRoutes: Routes = [ 9const videosRoutes: Routes = [
10 { 10 {
@@ -48,7 +48,7 @@ const videosRoutes: Routes = [
48 } 48 }
49 ] 49 ]
50 } 50 }
51]; 51]
52 52
53@NgModule({ 53@NgModule({
54 imports: [ RouterModule.forChild(videosRoutes) ], 54 imports: [ RouterModule.forChild(videosRoutes) ],
diff --git a/client/src/app/videos/videos.component.ts b/client/src/app/videos/videos.component.ts
index 591e7523d..972c2221f 100644
--- a/client/src/app/videos/videos.component.ts
+++ b/client/src/app/videos/videos.component.ts
@@ -1,4 +1,4 @@
1import { Component } from '@angular/core'; 1import { Component } from '@angular/core'
2 2
3@Component({ 3@Component({
4 template: '<router-outlet></router-outlet>' 4 template: '<router-outlet></router-outlet>'
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index adfbe7031..75a8dd24f 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -1,20 +1,20 @@
1import { NgModule } from '@angular/core'; 1import { NgModule } from '@angular/core'
2 2
3import { TagInputModule } from 'ng2-tag-input'; 3import { TagInputModule } from 'ng2-tag-input'
4 4
5import { VideosRoutingModule } from './videos-routing.module'; 5import { VideosRoutingModule } from './videos-routing.module'
6import { VideosComponent } from './videos.component'; 6import { VideosComponent } from './videos.component'
7import { VideoAddComponent, VideoUpdateComponent } from './video-edit'; 7import { VideoAddComponent, VideoUpdateComponent } from './video-edit'
8import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'; 8import { LoaderComponent, VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'
9import { 9import {
10 VideoWatchComponent, 10 VideoWatchComponent,
11 VideoMagnetComponent, 11 VideoMagnetComponent,
12 VideoReportComponent, 12 VideoReportComponent,
13 VideoShareComponent, 13 VideoShareComponent,
14 WebTorrentService 14 WebTorrentService
15} from './video-watch'; 15} from './video-watch'
16import { VideoService } from './shared'; 16import { VideoService } from './shared'
17import { SharedModule } from '../shared'; 17import { SharedModule } from '../shared'
18 18
19@NgModule({ 19@NgModule({
20 imports: [ 20 imports: [