aboutsummaryrefslogtreecommitdiffhomepage
path: root/client/src
diff options
context:
space:
mode:
Diffstat (limited to 'client/src')
-rw-r--r--client/src/app/shared/search/search-field.type.ts2
-rw-r--r--client/src/app/shared/search/search.component.ts3
-rw-r--r--client/src/app/shared/users/auth.service.ts6
-rw-r--r--client/src/app/videos/video-add/video-add.component.html64
-rw-r--r--client/src/app/videos/video-add/video-add.component.scss27
-rw-r--r--client/src/app/videos/video-add/video-add.component.ts162
-rw-r--r--client/src/vendor.ts4
7 files changed, 192 insertions, 76 deletions
diff --git a/client/src/app/shared/search/search-field.type.ts b/client/src/app/shared/search/search-field.type.ts
index 846236290..5228ee68a 100644
--- a/client/src/app/shared/search/search-field.type.ts
+++ b/client/src/app/shared/search/search-field.type.ts
@@ -1 +1 @@
export type SearchField = "name" | "author" | "podUrl" | "magnetUri"; export type SearchField = "name" | "author" | "podUrl" | "magnetUri" | "tags";
diff --git a/client/src/app/shared/search/search.component.ts b/client/src/app/shared/search/search.component.ts
index 31f8b1535..c14c2d99c 100644
--- a/client/src/app/shared/search/search.component.ts
+++ b/client/src/app/shared/search/search.component.ts
@@ -18,7 +18,8 @@ export class SearchComponent {
18 name: 'Name', 18 name: 'Name',
19 author: 'Author', 19 author: 'Author',
20 podUrl: 'Pod Url', 20 podUrl: 'Pod Url',
21 magnetUri: 'Magnet Uri' 21 magnetUri: 'Magnet Uri',
22 tags: 'Tags'
22 }; 23 };
23 searchCriterias: Search = { 24 searchCriterias: Search = {
24 field: 'name', 25 field: 'name',
diff --git a/client/src/app/shared/users/auth.service.ts b/client/src/app/shared/users/auth.service.ts
index 720037563..1c822c1e1 100644
--- a/client/src/app/shared/users/auth.service.ts
+++ b/client/src/app/shared/users/auth.service.ts
@@ -43,7 +43,11 @@ export class AuthService {
43 } 43 }
44 44
45 getRequestHeader() { 45 getRequestHeader() {
46 return new Headers({ 'Authorization': `${this.getTokenType()} ${this.getToken()}` }); 46 return new Headers({ 'Authorization': this.getRequestHeaderValue() });
47 }
48
49 getRequestHeaderValue() {
50 return `${this.getTokenType()} ${this.getToken()}`;
47 } 51 }
48 52
49 getToken() { 53 getToken() {
diff --git a/client/src/app/videos/video-add/video-add.component.html b/client/src/app/videos/video-add/video-add.component.html
index cbe274e8a..6b2eb9377 100644
--- a/client/src/app/videos/video-add/video-add.component.html
+++ b/client/src/app/videos/video-add/video-add.component.html
@@ -2,42 +2,74 @@
2 2
3<div *ngIf="error" class="alert alert-danger">{{ error }}</div> 3<div *ngIf="error" class="alert alert-danger">{{ error }}</div>
4 4
5<form (ngSubmit)="uploadFile()" #videoForm="ngForm"> 5<form novalidate (ngSubmit)="upload()" [ngFormModel]="videoForm">
6 <div class="form-group"> 6 <div class="form-group">
7 <label for="name">Video name</label> 7 <label for="name">Name</label>
8 <input 8 <input
9 type="text" class="form-control" name="name" id="name" required 9 type="text" class="form-control" name="name" id="name"
10 ngControl="name" #name="ngForm" 10 ngControl="name" #name="ngForm" [(ngModel)]="video.name"
11 > 11 >
12 <div [hidden]="name.valid || name.pristine" class="alert alert-danger"> 12 <div [hidden]="name.valid || name.pristine" class="alert alert-warning">
13 Name is required 13 A name is required and should be between 3 and 50 characters long
14 </div> 14 </div>
15 </div> 15 </div>
16 16
17 <div class="form-group"> 17 <div class="form-group">
18 <div class="btn btn-default btn-file"> 18 <label for="tags">Tags</label>
19 <input
20 type="text" class="form-control" name="tags" id="tags"
21 ngControl="tags" #tags="ngForm" [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag"
22 >
23 <div [hidden]="tags.valid || tags.pristine" class="alert alert-warning">
24 A tag should be between 2 and 10 characters long
25 </div>
26 </div>
27
28 <div class="tags">
29 <div class="label label-info tag" *ngFor="let tag of video.tags">
30 {{ tag }}
31 <span class="remove" (click)="removeTag(tag)">x</span>
32 </div>
33 </div>
34
35 <div class="form-group">
36 <label for="videofile">File</label>
37 <div class="btn btn-default btn-file" [ngClass]="{ 'disabled': filename !== null }" >
19 <span>Select the video...</span> 38 <span>Select the video...</span>
20 <input type="file" name="videofile" id="videofile"> 39 <input
40 type="file" name="videofile" id="videofile"
41 ng2FileSelect [uploader]="uploader" [disabled]="filename !== null"
42 >
21 </div> 43 </div>
44 </div>
22 45
23 <span *ngIf="fileToUpload">{{ fileToUpload.name }}</span> 46 <div class="file-to-upload">
47 <div class="file" *ngIf="uploader.queue.length > 0">
48 <span class="filename">{{ filename }}</span>
49 <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span>
50 </div>
24 </div> 51 </div>
25 52
26 <div class="form-group"> 53 <div class="form-group">
27 <label for="description">Description</label> 54 <label for="description">Description</label>
28 <textarea 55 <textarea
29 name="description" id="description" class="form-control" placeholder="Description..." required 56 name="description" id="description" class="form-control" placeholder="Description..."
30 ngControl="description" #description="ngForm" 57 ngControl="description" #description="ngForm" [(ngModel)]="video.description"
31 > 58 >
32 </textarea> 59 </textarea>
33 <div [hidden]="description.valid || description.pristine" class="alert alert-danger"> 60 <div [hidden]="description.valid || description.pristine" class="alert alert-warning">
34 A description is required 61 A description is required and should be between 3 and 250 characters long
35 </div> 62 </div>
36 </div> 63 </div>
37 64
38 <div id="progress" *ngIf="progressBar.max !== 0"> 65 <div class="progress">
39 <progressbar [value]="progressBar.value" [max]="progressBar.max">{{ progressBar.value | bytes }} / {{ progressBar.max | bytes }}</progressbar> 66 <progressbar [value]="uploader.progress" max="100"></progressbar>
40 </div> 67 </div>
41 68
42 <input type="submit" value="Upload" class="btn btn-default" [disabled]="!videoForm.form.valid || !fileToUpload"> 69 <div class="form-group">
70 <input
71 type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()"
72 [disabled]="!videoForm.valid || video.tags.length === 0 || filename === null"
73 >
74 </div>
43</form> 75</form>
diff --git a/client/src/app/videos/video-add/video-add.component.scss b/client/src/app/videos/video-add/video-add.component.scss
index 01195f017..d66df2fd4 100644
--- a/client/src/app/videos/video-add/video-add.component.scss
+++ b/client/src/app/videos/video-add/video-add.component.scss
@@ -1,6 +1,7 @@
1.btn-file { 1.btn-file {
2 position: relative; 2 position: relative;
3 overflow: hidden; 3 overflow: hidden;
4 display: block;
4} 5}
5 6
6.btn-file input[type=file] { 7.btn-file input[type=file] {
@@ -28,6 +29,28 @@
28 margin-bottom: 10px; 29 margin-bottom: 10px;
29} 30}
30 31
31#progress { 32div.tags {
32 margin-bottom: 10px; 33 height: 40px;
34 font-size: 20px;
35 margin-top: 20px;
36
37 .tag {
38 margin-right: 10px;
39
40 .remove {
41 cursor: pointer;
42 }
43 }
44}
45
46div.file-to-upload {
47 height: 40px;
48
49 .glyphicon-remove {
50 cursor: pointer;
51 }
52}
53
54div.progress {
55 // height: 40px;
33} 56}
diff --git a/client/src/app/videos/video-add/video-add.component.ts b/client/src/app/videos/video-add/video-add.component.ts
index 144879a54..2b45ea125 100644
--- a/client/src/app/videos/video-add/video-add.component.ts
+++ b/client/src/app/videos/video-add/video-add.component.ts
@@ -1,29 +1,31 @@
1/// <reference path="../../../../typings/globals/jquery/index.d.ts" /> 1import { Control, ControlGroup, Validators } from '@angular/common';
2/// <reference path="../../../../typings/globals/jquery.fileupload/index.d.ts" />
3
4import { Component, ElementRef, OnInit } from '@angular/core'; 2import { Component, ElementRef, OnInit } from '@angular/core';
5import { Router } from '@angular/router-deprecated'; 3import { Router } from '@angular/router-deprecated';
6 4
7import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe'; 5import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
8import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar'; 6import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar';
7import { FileSelectDirective, FileUploader } from 'ng2-file-upload/ng2-file-upload';
9 8
10import { AuthService, User } from '../../shared'; 9import { AuthService } from '../../shared';
11 10
12@Component({ 11@Component({
13 selector: 'my-videos-add', 12 selector: 'my-videos-add',
14 styles: [ require('./video-add.component.scss') ], 13 styles: [ require('./video-add.component.scss') ],
15 template: require('./video-add.component.html'), 14 template: require('./video-add.component.html'),
16 directives: [ PROGRESSBAR_DIRECTIVES ], 15 directives: [ FileSelectDirective, PROGRESSBAR_DIRECTIVES ],
17 pipes: [ BytesPipe ] 16 pipes: [ BytesPipe ]
18}) 17})
19 18
20export class VideoAddComponent implements OnInit { 19export class VideoAddComponent implements OnInit {
20 currentTag: string; // Tag the user is writing in the input
21 error: string = null; 21 error: string = null;
22 fileToUpload: any; 22 videoForm: ControlGroup;
23 progressBar: { value: number; max: number; } = { value: 0, max: 0 }; 23 uploader: FileUploader;
24 user: User; 24 video = {
25 25 name: '',
26 private form: any; 26 tags: [],
27 description: ''
28 };
27 29
28 constructor( 30 constructor(
29 private authService: AuthService, 31 private authService: AuthService,
@@ -31,52 +33,108 @@ export class VideoAddComponent implements OnInit {
31 private router: Router 33 private router: Router
32 ) {} 34 ) {}
33 35
36 get filename() {
37 if (this.uploader.queue.length === 0) {
38 return null;
39 }
40
41 return this.uploader.queue[0].file.name;
42 }
43
44 get isTagsInputDisabled () {
45 return this.video.tags.length >= 3;
46 }
47
48 getInvalidFieldsTitle() {
49 let title = '';
50 const nameControl = this.videoForm.controls['name'];
51 const descriptionControl = this.videoForm.controls['description'];
52
53 if (!nameControl.valid) {
54 title += 'A name is required\n';
55 }
56
57 if (this.video.tags.length === 0) {
58 title += 'At least one tag is required\n';
59 }
60
61 if (this.filename === null) {
62 title += 'A file is required\n';
63 }
64
65 if (!descriptionControl.valid) {
66 title += 'A description is required\n';
67 }
68
69 return title;
70 }
71
34 ngOnInit() { 72 ngOnInit() {
35 this.user = User.load(); 73 this.videoForm = new ControlGroup({
36 jQuery(this.elementRef.nativeElement).find('#videofile').fileupload({ 74 name: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(50) ])),
75 description: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(250) ])),
76 tags: new Control('', Validators.pattern('^[a-zA-Z0-9]{2,10}$'))
77 });
78
79
80 this.uploader = new FileUploader({
81 authToken: this.authService.getRequestHeaderValue(),
82 queueLimit: 1,
37 url: '/api/v1/videos', 83 url: '/api/v1/videos',
38 dataType: 'json', 84 removeAfterUpload: true
39 singleFileUploads: true,
40 multipart: true,
41 autoUpload: false,
42
43 add: (e, data) => {
44 this.form = data;
45 this.fileToUpload = data['files'][0];
46 },
47
48 progressall: (e, data) => {
49 this.progressBar.value = data.loaded;
50 // The server is a little bit slow to answer (has to seed the video)
51 // So we add more time to the progress bar (+10%)
52 this.progressBar.max = data.total + (0.1 * data.total);
53 },
54
55 done: (e, data) => {
56 this.progressBar.value = this.progressBar.max;
57 console.log('Video uploaded.');
58
59 // Print all the videos once it's finished
60 this.router.navigate(['VideosList']);
61 },
62
63 fail: (e, data) => {
64 const xhr = data.jqXHR;
65 if (xhr.status === 400) {
66 this.error = xhr.responseText;
67 } else {
68 this.error = 'Unknow error';
69 }
70
71 console.error(data);
72 }
73 }); 85 });
86
87 this.uploader.onBuildItemForm = (item, form) => {
88 form.append('name', this.video.name);
89 form.append('description', this.video.description);
90
91 for (let i = 0; i < this.video.tags.length; i++) {
92 form.append(`tags[${i}]`, this.video.tags[i]);
93 }
94 };
95 }
96
97 onTagKeyPress(event: KeyboardEvent) {
98 // Enter press
99 if (event.keyCode === 13) {
100 // Check if the tag is valid and does not already exist
101 if (
102 this.currentTag !== '' &&
103 this.videoForm.controls['tags'].valid &&
104 this.video.tags.indexOf(this.currentTag) === -1
105 ) {
106 this.video.tags.push(this.currentTag);
107 this.currentTag = '';
108 }
109 }
110 }
111
112 removeFile() {
113 this.uploader.clearQueue();
114 }
115
116 removeTag(tag: string) {
117 this.video.tags.splice(this.video.tags.indexOf(tag), 1);
74 } 118 }
75 119
76 uploadFile() { 120 upload() {
77 this.error = null; 121 const item = this.uploader.queue[0];
78 this.form.formData = jQuery(this.elementRef.nativeElement).find('form').serializeArray(); 122 // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242
79 this.form.headers = this.authService.getRequestHeader().toJSON(); 123 item.alias = 'videofile';
80 this.form.submit(); 124
125 item.onSuccess = () => {
126 console.log('Video uploaded.');
127
128 // Print all the videos once it's finished
129 this.router.navigate(['VideosList']);
130 };
131
132 item.onError = (response: string, status: number) => {
133 this.error = (status === 400) ? response : 'Unknow error';
134 console.error(this.error);
135 };
136
137
138 this.uploader.uploadAll();
81 } 139 }
82} 140}
diff --git a/client/src/vendor.ts b/client/src/vendor.ts
index 496f44cf6..437d05822 100644
--- a/client/src/vendor.ts
+++ b/client/src/vendor.ts
@@ -18,7 +18,5 @@ import 'rxjs/add/operator/catch';
18import 'rxjs/add/operator/map'; 18import 'rxjs/add/operator/map';
19import 'rxjs/add/operator/mergeMap'; 19import 'rxjs/add/operator/mergeMap';
20 20
21import 'jquery';
22import 'bootstrap-loader'; 21import 'bootstrap-loader';
23import 'jquery.ui.widget/jquery.ui.widget'; 22import 'ng2-file-upload';
24import 'blueimp-file-upload';