-
{{ progressBar.value | bytes }} / {{ progressBar.max | bytes }}
+
-
+
+
+
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 @@
.btn-file {
position: relative;
overflow: hidden;
+ display: block;
}
.btn-file input[type=file] {
@@ -28,6 +29,28 @@
margin-bottom: 10px;
}
-#progress {
- margin-bottom: 10px;
+div.tags {
+ height: 40px;
+ font-size: 20px;
+ margin-top: 20px;
+
+ .tag {
+ margin-right: 10px;
+
+ .remove {
+ cursor: pointer;
+ }
+ }
+}
+
+div.file-to-upload {
+ height: 40px;
+
+ .glyphicon-remove {
+ cursor: pointer;
+ }
+}
+
+div.progress {
+ // height: 40px;
}
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 @@
-///
-///
-
+import { Control, ControlGroup, Validators } from '@angular/common';
import { Component, ElementRef, OnInit } from '@angular/core';
import { Router } from '@angular/router-deprecated';
import { BytesPipe } from 'angular-pipes/src/math/bytes.pipe';
import { PROGRESSBAR_DIRECTIVES } from 'ng2-bootstrap/components/progressbar';
+import { FileSelectDirective, FileUploader } from 'ng2-file-upload/ng2-file-upload';
-import { AuthService, User } from '../../shared';
+import { AuthService } from '../../shared';
@Component({
selector: 'my-videos-add',
styles: [ require('./video-add.component.scss') ],
template: require('./video-add.component.html'),
- directives: [ PROGRESSBAR_DIRECTIVES ],
+ directives: [ FileSelectDirective, PROGRESSBAR_DIRECTIVES ],
pipes: [ BytesPipe ]
})
export class VideoAddComponent implements OnInit {
+ currentTag: string; // Tag the user is writing in the input
error: string = null;
- fileToUpload: any;
- progressBar: { value: number; max: number; } = { value: 0, max: 0 };
- user: User;
-
- private form: any;
+ videoForm: ControlGroup;
+ uploader: FileUploader;
+ video = {
+ name: '',
+ tags: [],
+ description: ''
+ };
constructor(
private authService: AuthService,
@@ -31,52 +33,108 @@ export class VideoAddComponent implements OnInit {
private router: Router
) {}
- ngOnInit() {
- this.user = User.load();
- jQuery(this.elementRef.nativeElement).find('#videofile').fileupload({
- url: '/api/v1/videos',
- dataType: 'json',
- singleFileUploads: true,
- multipart: true,
- autoUpload: false,
+ get filename() {
+ if (this.uploader.queue.length === 0) {
+ return null;
+ }
- add: (e, data) => {
- this.form = data;
- this.fileToUpload = data['files'][0];
- },
-
- progressall: (e, data) => {
- this.progressBar.value = data.loaded;
- // The server is a little bit slow to answer (has to seed the video)
- // So we add more time to the progress bar (+10%)
- this.progressBar.max = data.total + (0.1 * data.total);
- },
-
- done: (e, data) => {
- this.progressBar.value = this.progressBar.max;
- console.log('Video uploaded.');
-
- // Print all the videos once it's finished
- this.router.navigate(['VideosList']);
- },
-
- fail: (e, data) => {
- const xhr = data.jqXHR;
- if (xhr.status === 400) {
- this.error = xhr.responseText;
- } else {
- this.error = 'Unknow error';
- }
-
- console.error(data);
- }
- });
+ return this.uploader.queue[0].file.name;
}
- uploadFile() {
- this.error = null;
- this.form.formData = jQuery(this.elementRef.nativeElement).find('form').serializeArray();
- this.form.headers = this.authService.getRequestHeader().toJSON();
- this.form.submit();
+ get isTagsInputDisabled () {
+ return this.video.tags.length >= 3;
+ }
+
+ getInvalidFieldsTitle() {
+ let title = '';
+ const nameControl = this.videoForm.controls['name'];
+ const descriptionControl = this.videoForm.controls['description'];
+
+ if (!nameControl.valid) {
+ title += 'A name is required\n';
+ }
+
+ if (this.video.tags.length === 0) {
+ title += 'At least one tag is required\n';
+ }
+
+ if (this.filename === null) {
+ title += 'A file is required\n';
+ }
+
+ if (!descriptionControl.valid) {
+ title += 'A description is required\n';
+ }
+
+ return title;
+ }
+
+ ngOnInit() {
+ this.videoForm = new ControlGroup({
+ name: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(50) ])),
+ description: new Control('', Validators.compose([ Validators.required, Validators.minLength(3), Validators.maxLength(250) ])),
+ tags: new Control('', Validators.pattern('^[a-zA-Z0-9]{2,10}$'))
+ });
+
+
+ this.uploader = new FileUploader({
+ authToken: this.authService.getRequestHeaderValue(),
+ queueLimit: 1,
+ url: '/api/v1/videos',
+ removeAfterUpload: true
+ });
+
+ this.uploader.onBuildItemForm = (item, form) => {
+ form.append('name', this.video.name);
+ form.append('description', this.video.description);
+
+ for (let i = 0; i < this.video.tags.length; i++) {
+ form.append(`tags[${i}]`, this.video.tags[i]);
+ }
+ };
+ }
+
+ onTagKeyPress(event: KeyboardEvent) {
+ // Enter press
+ if (event.keyCode === 13) {
+ // Check if the tag is valid and does not already exist
+ if (
+ this.currentTag !== '' &&
+ this.videoForm.controls['tags'].valid &&
+ this.video.tags.indexOf(this.currentTag) === -1
+ ) {
+ this.video.tags.push(this.currentTag);
+ this.currentTag = '';
+ }
+ }
+ }
+
+ removeFile() {
+ this.uploader.clearQueue();
+ }
+
+ removeTag(tag: string) {
+ this.video.tags.splice(this.video.tags.indexOf(tag), 1);
+ }
+
+ upload() {
+ const item = this.uploader.queue[0];
+ // TODO: wait for https://github.com/valor-software/ng2-file-upload/pull/242
+ item.alias = 'videofile';
+
+ item.onSuccess = () => {
+ console.log('Video uploaded.');
+
+ // Print all the videos once it's finished
+ this.router.navigate(['VideosList']);
+ };
+
+ item.onError = (response: string, status: number) => {
+ this.error = (status === 400) ? response : 'Unknow error';
+ console.error(this.error);
+ };
+
+
+ this.uploader.uploadAll();
}
}
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';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/mergeMap';
-import 'jquery';
import 'bootstrap-loader';
-import 'jquery.ui.widget/jquery.ui.widget';
-import 'blueimp-file-upload';
+import 'ng2-file-upload';
diff --git a/client/tsconfig.json b/client/tsconfig.json
index 3b903f8c8..fdcf742ea 100644
--- a/client/tsconfig.json
+++ b/client/tsconfig.json
@@ -65,8 +65,6 @@
"src/vendor.ts",
"typings/globals/es6-shim/index.d.ts",
"typings/globals/jasmine/index.d.ts",
- "typings/globals/jquery.fileupload/index.d.ts",
- "typings/globals/jquery/index.d.ts",
"typings/globals/node/index.d.ts",
"typings/index.d.ts"
]
diff --git a/client/typings.json b/client/typings.json
index ff8b56a48..9a8891f25 100644
--- a/client/typings.json
+++ b/client/typings.json
@@ -2,8 +2,6 @@
"globalDependencies": {
"es6-shim": "registry:dt/es6-shim#0.31.2+20160317120654",
"jasmine": "registry:dt/jasmine#2.2.0+20160412134438",
- "jquery": "registry:dt/jquery#1.10.0+20160417213236",
- "jquery.fileupload": "registry:dt/jquery.fileupload#5.40.1+20160316155526",
"node": "registry:dt/node#4.0.0+20160509154515"
}
}
diff --git a/server/initializers/constants.js b/server/initializers/constants.js
index 6fa322010..22cbb1361 100644
--- a/server/initializers/constants.js
+++ b/server/initializers/constants.js
@@ -41,8 +41,8 @@ const THUMBNAILS_SIZE = '200x110'
const THUMBNAILS_STATIC_PATH = '/static/thumbnails'
const VIDEOS_CONSTRAINTS_FIELDS = {
- NAME: { min: 1, max: 50 }, // Length
- DESCRIPTION: { min: 1, max: 250 }, // Length
+ NAME: { min: 3, max: 50 }, // Length
+ DESCRIPTION: { min: 3, max: 250 }, // Length
MAGNET_URI: { min: 10 }, // Length
DURATION: { min: 1, max: 7200 }, // Number
AUTHOR: { min: 3, max: 20 }, // Length
diff --git a/server/middlewares/reqValidators/videos.js b/server/middlewares/reqValidators/videos.js
index 3618e4716..f31fd93a2 100644
--- a/server/middlewares/reqValidators/videos.js
+++ b/server/middlewares/reqValidators/videos.js
@@ -32,7 +32,7 @@ function videosAdd (req, res, next) {
}
if (!customValidators.isVideoDurationValid(duration)) {
- return res.status(400).send('Duration of the video file is too big (max: ' + constants.MAXIMUM_VIDEO_DURATION + 's).')
+ return res.status(400).send('Duration of the video file is too big (max: ' + constants.VIDEOS_CONSTRAINTS_FIELDS.DURATION.max + 's).')
}
videoFile.duration = duration
diff --git a/server/models/videos.js b/server/models/videos.js
index d6b743c7c..c177b414c 100644
--- a/server/models/videos.js
+++ b/server/models/videos.js
@@ -12,6 +12,7 @@ const port = config.get('webserver.port')
// ---------------------------------------------------------------------------
+// TODO: add indexes on searchable columns
const videosSchema = mongoose.Schema({
name: String,
namePath: String,