aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--client/src/app/videos/index.ts2
-rw-r--r--client/src/app/videos/shared/video.model.ts38
-rw-r--r--client/src/app/videos/shared/video.service.ts19
-rw-r--r--client/src/app/videos/video-add/index.ts1
-rw-r--r--client/src/app/videos/video-edit/index.ts2
-rw-r--r--client/src/app/videos/video-edit/video-add.component.html (renamed from client/src/app/videos/video-add/video-add.component.html)0
-rw-r--r--client/src/app/videos/video-edit/video-add.component.ts (renamed from client/src/app/videos/video-add/video-add.component.ts)2
-rw-r--r--client/src/app/videos/video-edit/video-edit.component.scss (renamed from client/src/app/videos/video-add/video-add.component.scss)0
-rw-r--r--client/src/app/videos/video-edit/video-update.component.html101
-rw-r--r--client/src/app/videos/video-edit/video-update.component.ts170
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.html6
-rw-r--r--client/src/app/videos/video-watch/video-watch.component.ts5
-rw-r--r--client/src/app/videos/videos-routing.module.ts11
-rw-r--r--client/src/app/videos/videos.module.ts3
14 files changed, 354 insertions, 6 deletions
diff --git a/client/src/app/videos/index.ts b/client/src/app/videos/index.ts
index ca386a51d..5158a23f8 100644
--- a/client/src/app/videos/index.ts
+++ b/client/src/app/videos/index.ts
@@ -1,5 +1,5 @@
1export * from './shared'; 1export * from './shared';
2export * from './video-add'; 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';
diff --git a/client/src/app/videos/shared/video.model.ts b/client/src/app/videos/shared/video.model.ts
index f135ca707..404e3bf45 100644
--- a/client/src/app/videos/shared/video.model.ts
+++ b/client/src/app/videos/shared/video.model.ts
@@ -5,8 +5,11 @@ export class Video {
5 by: string; 5 by: string;
6 createdAt: Date; 6 createdAt: Date;
7 categoryLabel: string; 7 categoryLabel: string;
8 category: string;
8 licenceLabel: string; 9 licenceLabel: string;
10 licence: string;
9 languageLabel: string; 11 languageLabel: string;
12 language: string;
10 description: string; 13 description: string;
11 duration: string; 14 duration: string;
12 id: string; 15 id: string;
@@ -38,8 +41,11 @@ export class Video {
38 author: string, 41 author: string,
39 createdAt: string, 42 createdAt: string,
40 categoryLabel: string, 43 categoryLabel: string,
44 category: string,
41 licenceLabel: string, 45 licenceLabel: string,
46 licence: string,
42 languageLabel: string; 47 languageLabel: string;
48 language: string;
43 description: string, 49 description: string,
44 duration: number; 50 duration: number;
45 id: string, 51 id: string,
@@ -57,8 +63,11 @@ export class Video {
57 this.author = hash.author; 63 this.author = hash.author;
58 this.createdAt = new Date(hash.createdAt); 64 this.createdAt = new Date(hash.createdAt);
59 this.categoryLabel = hash.categoryLabel; 65 this.categoryLabel = hash.categoryLabel;
66 this.category = hash.category;
60 this.licenceLabel = hash.licenceLabel; 67 this.licenceLabel = hash.licenceLabel;
68 this.licence = hash.licence;
61 this.languageLabel = hash.languageLabel; 69 this.languageLabel = hash.languageLabel;
70 this.language = hash.language;
62 this.description = hash.description; 71 this.description = hash.description;
63 this.duration = Video.createDurationString(hash.duration); 72 this.duration = Video.createDurationString(hash.duration);
64 this.id = hash.id; 73 this.id = hash.id;
@@ -84,4 +93,33 @@ export class Video {
84 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos... 93 // If the video is NSFW and the user is not logged in, or the user does not want to display NSFW videos...
85 return (this.nsfw && (!user || user.displayNSFW === false)); 94 return (this.nsfw && (!user || user.displayNSFW === false));
86 } 95 }
96
97 patch(values: Object) {
98 Object.keys(values).forEach((key) => {
99 this[key] = values[key];
100 });
101 }
102
103 toJSON() {
104 return {
105 author: this.author,
106 createdAt: this.createdAt,
107 category: this.category,
108 licence: this.licence,
109 language: this.language,
110 description: this.description,
111 duration: this.duration,
112 id: this.id,
113 isLocal: this.isLocal,
114 magnetUri: this.magnetUri,
115 name: this.name,
116 podHost: this.podHost,
117 tags: this.tags,
118 thumbnailPath: this.thumbnailPath,
119 views: this.views,
120 likes: this.likes,
121 dislikes: this.dislikes,
122 nsfw: this.nsfw
123 };
124 }
87} 125}
diff --git a/client/src/app/videos/shared/video.service.ts b/client/src/app/videos/shared/video.service.ts
index 13d4ca246..ee67bc1ae 100644
--- a/client/src/app/videos/shared/video.service.ts
+++ b/client/src/app/videos/shared/video.service.ts
@@ -1,5 +1,5 @@
1import { Injectable } from '@angular/core'; 1import { Injectable } from '@angular/core';
2import { Http } 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';
@@ -80,6 +80,23 @@ export class VideoService {
80 .catch((res) => this.restExtractor.handleError(res)); 80 .catch((res) => this.restExtractor.handleError(res));
81 } 81 }
82 82
83 updateVideo(video: Video) {
84 const body = {
85 name: video.name,
86 category: video.category,
87 licence: video.licence,
88 language: video.language,
89 description: video.description,
90 tags: video.tags
91 };
92 const headers = new Headers({ 'Content-Type': 'application/json' });
93 const options = new RequestOptions({ headers: headers });
94
95 return this.authHttp.put(`${VideoService.BASE_VIDEO_URL}/${video.id}`, body, options)
96 .map(this.restExtractor.extractDataBool)
97 .catch(this.restExtractor.handleError);
98 }
99
83 getVideos(pagination: RestPagination, sort: SortField) { 100 getVideos(pagination: RestPagination, sort: SortField) {
84 const params = this.restService.buildRestGetParams(pagination, sort); 101 const params = this.restService.buildRestGetParams(pagination, sort);
85 102
diff --git a/client/src/app/videos/video-add/index.ts b/client/src/app/videos/video-add/index.ts
deleted file mode 100644
index 79488e851..000000000
--- a/client/src/app/videos/video-add/index.ts
+++ /dev/null
@@ -1 +0,0 @@
1export * from './video-add.component';
diff --git a/client/src/app/videos/video-edit/index.ts b/client/src/app/videos/video-edit/index.ts
new file mode 100644
index 000000000..5ce4fb9b1
--- /dev/null
+++ b/client/src/app/videos/video-edit/index.ts
@@ -0,0 +1,2 @@
1export * from './video-add.component';
2export * from './video-update.component';
diff --git a/client/src/app/videos/video-add/video-add.component.html b/client/src/app/videos/video-edit/video-add.component.html
index 104747a8c..104747a8c 100644
--- a/client/src/app/videos/video-add/video-add.component.html
+++ b/client/src/app/videos/video-edit/video-add.component.html
diff --git a/client/src/app/videos/video-add/video-add.component.ts b/client/src/app/videos/video-edit/video-add.component.ts
index da556f48d..e3cf0e9d8 100644
--- a/client/src/app/videos/video-add/video-add.component.ts
+++ b/client/src/app/videos/video-edit/video-add.component.ts
@@ -19,7 +19,7 @@ import { VideoService } from '../shared';
19 19
20@Component({ 20@Component({
21 selector: 'my-videos-add', 21 selector: 'my-videos-add',
22 styleUrls: [ './video-add.component.scss' ], 22 styleUrls: [ './video-edit.component.scss' ],
23 templateUrl: './video-add.component.html' 23 templateUrl: './video-add.component.html'
24}) 24})
25 25
diff --git a/client/src/app/videos/video-add/video-add.component.scss b/client/src/app/videos/video-edit/video-edit.component.scss
index 11ee3297e..11ee3297e 100644
--- a/client/src/app/videos/video-add/video-add.component.scss
+++ b/client/src/app/videos/video-edit/video-edit.component.scss
diff --git a/client/src/app/videos/video-edit/video-update.component.html b/client/src/app/videos/video-edit/video-update.component.html
new file mode 100644
index 000000000..665a952d0
--- /dev/null
+++ b/client/src/app/videos/video-edit/video-update.component.html
@@ -0,0 +1,101 @@
1<h3>Update {{ video.name }}</h3>
2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4
5<form novalidate [formGroup]="form">
6 <div class="form-group">
7 <label for="name">Name</label>
8 <input
9 type="text" class="form-control" id="name"
10 formControlName="name"
11 >
12 <div *ngIf="formErrors.name" class="alert alert-danger">
13 {{ formErrors.name }}
14 </div>
15 </div>
16
17 <div class="form-group">
18 <label for="nsfw">NSFW</label>
19 <input
20 type="checkbox" id="nsfw"
21 formControlName="nsfw"
22 >
23 </div>
24
25 <div class="form-group">
26 <label for="category">Category</label>
27 <select class="form-control" id="category" formControlName="category">
28 <option></option>
29 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
30 </select>
31
32 <div *ngIf="formErrors.category" class="alert alert-danger">
33 {{ formErrors.category }}
34 </div>
35 </div>
36
37 <div class="form-group">
38 <label for="licence">Licence</label>
39 <select class="form-control" id="licence" formControlName="licence">
40 <option></option>
41 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
42 </select>
43
44 <div *ngIf="formErrors.licence" class="alert alert-danger">
45 {{ formErrors.licence }}
46 </div>
47 </div>
48
49 <div class="form-group">
50 <label for="language">Language</label>
51 <select class="form-control" id="language" formControlName="language">
52 <option></option>
53 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
54 </select>
55
56 <div *ngIf="formErrors.language" class="alert alert-danger">
57 {{ formErrors.language }}
58 </div>
59 </div>
60
61 <div class="form-group">
62 <label for="tags">Tags</label> <span class="little-information">(press enter to add the tag)</span>
63 <input
64 type="text" class="form-control" id="currentTag"
65 formControlName="currentTag" (keyup)="onTagKeyPress($event)"
66 >
67 <div *ngIf="formErrors.currentTag" class="alert alert-danger">
68 {{ formErrors.currentTag }}
69 </div>
70 </div>
71
72 <div class="tags">
73 <div class="label label-primary tag" *ngFor="let tag of tags">
74 {{ tag }}
75 <span class="remove" (click)="removeTag(tag)">x</span>
76 </div>
77 </div>
78
79 <div *ngIf="tagsError" class="alert alert-danger">
80 {{ tagsError }}
81 </div>
82
83 <div class="form-group">
84 <label for="description">Description</label>
85 <textarea
86 id="description" class="form-control" placeholder="Description..."
87 formControlName="description"
88 >
89 </textarea>
90 <div *ngIf="formErrors.description" class="alert alert-danger">
91 {{ formErrors.description }}
92 </div>
93 </div>
94
95 <div class="form-group">
96 <input
97 type="button" value="Update" class="btn btn-default form-control"
98 (click)="update()"
99 >
100 </div>
101</form>
diff --git a/client/src/app/videos/video-edit/video-update.component.ts b/client/src/app/videos/video-edit/video-update.component.ts
new file mode 100644
index 000000000..b45780a41
--- /dev/null
+++ b/client/src/app/videos/video-edit/video-update.component.ts
@@ -0,0 +1,170 @@
1import { Component, ElementRef, OnInit } from '@angular/core';
2import { FormBuilder, FormGroup } from '@angular/forms';
3import { ActivatedRoute, Router } from '@angular/router';
4
5import { FileUploader } from 'ng2-file-upload/ng2-file-upload';
6import { NotificationsService } from 'angular2-notifications';
7
8import { AuthService } from '../../core';
9import {
10 FormReactive,
11 VIDEO_NAME,
12 VIDEO_CATEGORY,
13 VIDEO_LICENCE,
14 VIDEO_LANGUAGE,
15 VIDEO_DESCRIPTION,
16 VIDEO_TAGS
17} from '../../shared';
18import { Video, VideoService } from '../shared';
19
20@Component({
21 selector: 'my-videos-update',
22 styleUrls: [ './video-edit.component.scss' ],
23 templateUrl: './video-update.component.html'
24})
25
26export class VideoUpdateComponent extends FormReactive implements OnInit {
27 tags: string[] = [];
28 videoCategories = [];
29 videoLicences = [];
30 videoLanguages = [];
31 video: Video;
32
33 error: string = null;
34 form: FormGroup;
35 formErrors = {
36 name: '',
37 category: '',
38 licence: '',
39 language: '',
40 description: '',
41 currentTag: ''
42 };
43 validationMessages = {
44 name: VIDEO_NAME.MESSAGES,
45 category: VIDEO_CATEGORY.MESSAGES,
46 licence: VIDEO_LICENCE.MESSAGES,
47 language: VIDEO_LANGUAGE.MESSAGES,
48 description: VIDEO_DESCRIPTION.MESSAGES,
49 currentTag: VIDEO_TAGS.MESSAGES
50 };
51
52 // Special error messages
53 tagsError = '';
54 fileError = '';
55
56 constructor(
57 private authService: AuthService,
58 private elementRef: ElementRef,
59 private formBuilder: FormBuilder,
60 private route: ActivatedRoute,
61 private router: Router,
62 private notificationsService: NotificationsService,
63 private videoService: VideoService
64 ) {
65 super();
66 }
67
68 buildForm() {
69 this.form = this.formBuilder.group({
70 name: [ '', VIDEO_NAME.VALIDATORS ],
71 nsfw: [ false ],
72 category: [ '', VIDEO_CATEGORY.VALIDATORS ],
73 licence: [ '', VIDEO_LICENCE.VALIDATORS ],
74 language: [ '', VIDEO_LANGUAGE.VALIDATORS ],
75 description: [ '', VIDEO_DESCRIPTION.VALIDATORS ],
76 currentTag: [ '', VIDEO_TAGS.VALIDATORS ]
77 });
78
79 this.form.valueChanges.subscribe(data => this.onValueChanged(data));
80 }
81
82 ngOnInit() {
83 this.buildForm();
84
85 this.videoCategories = this.videoService.videoCategories;
86 this.videoLicences = this.videoService.videoLicences;
87 this.videoLanguages = this.videoService.videoLanguages;
88
89 const id = this.route.snapshot.params['id'];
90 this.videoService.getVideo(id)
91 .subscribe(
92 video => {
93 this.video = video;
94
95 this.hydrateFormFromVideo();
96 },
97
98 err => this.error = 'Cannot fetch video.'
99 );
100 }
101
102 checkForm() {
103 this.forceCheck();
104
105 return this.form.valid === true && this.tagsError === '' && this.fileError === '';
106 }
107
108
109 onTagKeyPress(event: KeyboardEvent) {
110 // Enter press
111 if (event.keyCode === 13) {
112 this.addTagIfPossible();
113 }
114 }
115
116 removeTag(tag: string) {
117 this.tags.splice(this.tags.indexOf(tag), 1);
118 this.form.get('currentTag').enable();
119 }
120
121 update() {
122 // Maybe the user forgot to press "enter" when he filled the field
123 this.addTagIfPossible();
124
125 if (this.checkForm() === false) {
126 return;
127 }
128
129 this.video.patch(this.form.value);
130
131 this.videoService.updateVideo(this.video)
132 .subscribe(
133 () => {
134 this.notificationsService.success('Success', 'Video updated.');
135 this.router.navigate([ '/videos/watch', this.video.id ]);
136 },
137
138 err => {
139 this.error = 'Cannot update the video.';
140 console.error(err);
141 }
142 );
143
144 }
145
146 private addTagIfPossible() {
147 const currentTag = this.form.value['currentTag'];
148 if (currentTag === undefined) return;
149
150 // Check if the tag is valid and does not already exist
151 if (
152 currentTag.length >= 2 &&
153 this.form.controls['currentTag'].valid &&
154 this.tags.indexOf(currentTag) === -1
155 ) {
156 this.tags.push(currentTag);
157 this.form.patchValue({ currentTag: '' });
158
159 if (this.tags.length >= 3) {
160 this.form.get('currentTag').disable();
161 }
162
163 this.tagsError = '';
164 }
165 }
166
167 private hydrateFormFromVideo() {
168 this.form.patchValue(this.video.toJSON());
169 }
170}
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 a6ec7b20f..2a6b15dc9 100644
--- a/client/src/app/videos/video-watch/video-watch.component.html
+++ b/client/src/app/videos/video-watch/video-watch.component.html
@@ -79,6 +79,12 @@
79 </button> 79 </button>
80 80
81 <ul dropdownMenu id="more-menu" role="menu" aria-labelledby="single-button"> 81 <ul dropdownMenu id="more-menu" role="menu" aria-labelledby="single-button">
82 <li *ngIf="canUserUpdateVideo()" role="menuitem">
83 <a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/edit', video.id ]">
84 <span class="glyphicon glyphicon-pencil"></span> Update
85 </a>
86 </li>
87
82 <li role="menuitem"> 88 <li role="menuitem">
83 <a class="dropdown-item" title="Get magnet URI" href="#" (click)="showMagnetUriModal($event)"> 89 <a class="dropdown-item" title="Get magnet URI" href="#" (click)="showMagnetUriModal($event)">
84 <span class="glyphicon glyphicon-magnet"></span> Magnet 90 <span class="glyphicon glyphicon-magnet"></span> Magnet
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 37ed70a99..e04626a67 100644
--- a/client/src/app/videos/video-watch/video-watch.component.ts
+++ b/client/src/app/videos/video-watch/video-watch.component.ts
@@ -187,6 +187,11 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
187 return this.authService.isLoggedIn(); 187 return this.authService.isLoggedIn();
188 } 188 }
189 189
190 canUserUpdateVideo() {
191 return this.authService.getUser() !== null &&
192 this.authService.getUser().username === this.video.author;
193 }
194
190 private checkUserRating() { 195 private checkUserRating() {
191 // Unlogged users do not have ratings 196 // Unlogged users do not have ratings
192 if (this.isUserLoggedIn() === false) return; 197 if (this.isUserLoggedIn() === false) return;
diff --git a/client/src/app/videos/videos-routing.module.ts b/client/src/app/videos/videos-routing.module.ts
index 005e9def6..70968b4d1 100644
--- a/client/src/app/videos/videos-routing.module.ts
+++ b/client/src/app/videos/videos-routing.module.ts
@@ -1,7 +1,7 @@
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 } from './video-add'; 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';
@@ -30,6 +30,15 @@ const videosRoutes: Routes = [
30 } 30 }
31 }, 31 },
32 { 32 {
33 path: 'edit/:id',
34 component: VideoUpdateComponent,
35 data: {
36 meta: {
37 title: 'Edit a video'
38 }
39 }
40 },
41 {
33 path: ':id', 42 path: ':id',
34 redirectTo: 'watch/:id' 43 redirectTo: 'watch/:id'
35 }, 44 },
diff --git a/client/src/app/videos/videos.module.ts b/client/src/app/videos/videos.module.ts
index 03dea17b5..fa37ad966 100644
--- a/client/src/app/videos/videos.module.ts
+++ b/client/src/app/videos/videos.module.ts
@@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
2 2
3import { VideosRoutingModule } from './videos-routing.module'; 3import { VideosRoutingModule } from './videos-routing.module';
4import { VideosComponent } from './videos.component'; 4import { VideosComponent } from './videos.component';
5import { VideoAddComponent } from './video-add'; 5import { VideoAddComponent, VideoUpdateComponent } from './video-edit';
6import { VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list'; 6import { VideoListComponent, VideoMiniatureComponent, VideoSortComponent } from './video-list';
7import { 7import {
8 VideoWatchComponent, 8 VideoWatchComponent,
@@ -24,6 +24,7 @@ import { SharedModule } from '../shared';
24 VideosComponent, 24 VideosComponent,
25 25
26 VideoAddComponent, 26 VideoAddComponent,
27 VideoUpdateComponent,
27 28
28 VideoListComponent, 29 VideoListComponent,
29 VideoMiniatureComponent, 30 VideoMiniatureComponent,