diff options
Diffstat (limited to 'client/src')
19 files changed, 282 insertions, 128 deletions
diff --git a/client/src/app/account/account.component.html b/client/src/app/account/account.component.html index 4797fa914..5a8847acd 100644 --- a/client/src/app/account/account.component.html +++ b/client/src/app/account/account.component.html | |||
@@ -3,25 +3,25 @@ | |||
3 | <div *ngIf="information" class="alert alert-success">{{ information }}</div> | 3 | <div *ngIf="information" class="alert alert-success">{{ information }}</div> |
4 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | 4 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
5 | 5 | ||
6 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="changePasswordForm"> | 6 | <form role="form" (ngSubmit)="changePassword()" [formGroup]="form"> |
7 | <div class="form-group"> | 7 | <div class="form-group"> |
8 | <label for="new-password">New password</label> | 8 | <label for="new-password">New password</label> |
9 | <input | 9 | <input |
10 | type="password" class="form-control" name="new-password" id="new-password" | 10 | type="password" class="form-control" id="new-password" |
11 | [(ngModel)]="newPassword" #newPasswordInput="ngModel" | 11 | formControlName="new-password" |
12 | > | 12 | > |
13 | <div [hidden]="changePasswordForm.controls['new-password'].valid || changePasswordForm.controls['new-password'].pristine" class="alert alert-warning"> | 13 | <div *ngIf="formErrors['new-password']" class="alert alert-danger"> |
14 | The password should have more than 5 characters | 14 | {{ formErrors['new-password'] }} |
15 | </div> | 15 | </div> |
16 | </div> | 16 | </div> |
17 | 17 | ||
18 | <div class="form-group"> | 18 | <div class="form-group"> |
19 | <label for="name">Confirm new password</label> | 19 | <label for="name">Confirm new password</label> |
20 | <input | 20 | <input |
21 | type="password" class="form-control" name="new-confirmed-password" id="new-confirmed-password" | 21 | type="password" class="form-control" id="new-confirmed-password" |
22 | [(ngModel)]="newConfirmedPassword" #newConfirmedPasswordInput="ngModel" | 22 | formControlName="new-confirmed-password" |
23 | > | 23 | > |
24 | </div> | 24 | </div> |
25 | 25 | ||
26 | <input type="submit" value="Change password" class="btn btn-default" [disabled]="!changePasswordForm.valid"> | 26 | <input type="submit" value="Change password" class="btn btn-default" [disabled]="!form.valid"> |
27 | </form> | 27 | </form> |
diff --git a/client/src/app/account/account.component.ts b/client/src/app/account/account.component.ts index a22738d3f..b503406c9 100644 --- a/client/src/app/account/account.component.ts +++ b/client/src/app/account/account.component.ts | |||
@@ -1,44 +1,64 @@ | |||
1 | import { } from '@angular/common'; | 1 | import { } from '@angular/common'; |
2 | import { Component, OnInit } from '@angular/core'; | 2 | import { Component, OnInit } from '@angular/core'; |
3 | import { FormControl, FormGroup, Validators } from '@angular/forms'; | 3 | import { FormBuilder, FormGroup } from '@angular/forms'; |
4 | import { Router } from '@angular/router'; | 4 | import { Router } from '@angular/router'; |
5 | 5 | ||
6 | import { AccountService } from './account.service'; | 6 | import { AccountService } from './account.service'; |
7 | import { FormReactive, USER_PASSWORD } from '../shared'; | ||
7 | 8 | ||
8 | @Component({ | 9 | @Component({ |
9 | selector: 'my-account', | 10 | selector: 'my-account', |
10 | template: require('./account.component.html') | 11 | template: require('./account.component.html') |
11 | }) | 12 | }) |
12 | 13 | ||
13 | export class AccountComponent implements OnInit { | 14 | export class AccountComponent extends FormReactive implements OnInit { |
14 | newPassword = ''; | ||
15 | newConfirmedPassword = ''; | ||
16 | changePasswordForm: FormGroup; | ||
17 | information: string = null; | 15 | information: string = null; |
18 | error: string = null; | 16 | error: string = null; |
19 | 17 | ||
18 | form: FormGroup; | ||
19 | formErrors = { | ||
20 | 'new-password': '', | ||
21 | 'new-confirmed-password': '' | ||
22 | }; | ||
23 | validationMessages = { | ||
24 | 'new-password': USER_PASSWORD.MESSAGES, | ||
25 | 'new-confirmed-password': USER_PASSWORD.MESSAGES | ||
26 | }; | ||
27 | |||
20 | constructor( | 28 | constructor( |
21 | private accountService: AccountService, | 29 | private accountService: AccountService, |
30 | private formBuilder: FormBuilder, | ||
22 | private router: Router | 31 | private router: Router |
23 | ) {} | 32 | ) { |
33 | super(); | ||
34 | } | ||
24 | 35 | ||
25 | ngOnInit() { | 36 | buildForm() { |
26 | this.changePasswordForm = new FormGroup({ | 37 | this.form = this.formBuilder.group({ |
27 | 'new-password': new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]), | 38 | 'new-password': [ '', USER_PASSWORD.VALIDATORS ], |
28 | 'new-confirmed-password': new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]), | 39 | 'new-confirmed-password': [ '', USER_PASSWORD.VALIDATORS ], |
29 | }); | 40 | }); |
41 | |||
42 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
43 | } | ||
44 | |||
45 | ngOnInit() { | ||
46 | this.buildForm(); | ||
30 | } | 47 | } |
31 | 48 | ||
32 | changePassword() { | 49 | changePassword() { |
50 | const newPassword = this.form.value['new-password']; | ||
51 | const newConfirmedPassword = this.form.value['new-confirmed-password']; | ||
52 | |||
33 | this.information = null; | 53 | this.information = null; |
34 | this.error = null; | 54 | this.error = null; |
35 | 55 | ||
36 | if (this.newPassword !== this.newConfirmedPassword) { | 56 | if (newPassword !== newConfirmedPassword) { |
37 | this.error = 'The new password and the confirmed password do not correspond.'; | 57 | this.error = 'The new password and the confirmed password do not correspond.'; |
38 | return; | 58 | return; |
39 | } | 59 | } |
40 | 60 | ||
41 | this.accountService.changePassword(this.newPassword).subscribe( | 61 | this.accountService.changePassword(newPassword).subscribe( |
42 | ok => this.information = 'Password updated.', | 62 | ok => this.information = 'Password updated.', |
43 | 63 | ||
44 | err => this.error = err | 64 | err => this.error = err |
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.html b/client/src/app/admin/friends/friend-add/friend-add.component.html index 5b8dc8d87..788f3b44d 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.html +++ b/client/src/app/admin/friends/friend-add/friend-add.component.html | |||
@@ -2,14 +2,14 @@ | |||
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)="makeFriends()" [formGroup]="friendAddForm"> | 5 | <form (ngSubmit)="makeFriends()" [formGroup]="form"> |
6 | <div class="form-group" *ngFor="let url of urls; let id = index; trackBy:customTrackBy"> | 6 | <div class="form-group" *ngFor="let url of urls; let id = index; trackBy:customTrackBy"> |
7 | <label for="username">Url</label> | 7 | <label for="username">Url</label> |
8 | 8 | ||
9 | <div class="input-group"> | 9 | <div class="input-group"> |
10 | <input | 10 | <input |
11 | type="text" class="form-control" placeholder="http://domain.com" | 11 | type="text" class="form-control" placeholder="http://domain.com" |
12 | [name]="'url-' + id" [id]="'url-' + id" [formControlName]="'url-' + id" [(ngModel)]="urls[id]" | 12 | [id]="'url-' + id" [formControlName]="'url-' + id" |
13 | /> | 13 | /> |
14 | <span class="input-group-btn"> | 14 | <span class="input-group-btn"> |
15 | <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button> | 15 | <button *ngIf="displayAddField(id)" (click)="addField()" class="btn btn-default" type="button">+</button> |
@@ -17,7 +17,7 @@ | |||
17 | </span> | 17 | </span> |
18 | </div> | 18 | </div> |
19 | 19 | ||
20 | <div [hidden]="friendAddForm.controls['url-' + id].valid || friendAddForm.controls['url-' + id].pristine" class="alert alert-warning"> | 20 | <div [hidden]="form.controls['url-' + id].valid || form.controls['url-' + id].pristine" class="alert alert-warning"> |
21 | It should be a valid url. | 21 | It should be a valid url. |
22 | </div> | 22 | </div> |
23 | </div> | 23 | </div> |
diff --git a/client/src/app/admin/friends/friend-add/friend-add.component.ts b/client/src/app/admin/friends/friend-add/friend-add.component.ts index 55aed9156..68363b482 100644 --- a/client/src/app/admin/friends/friend-add/friend-add.component.ts +++ b/client/src/app/admin/friends/friend-add/friend-add.component.ts | |||
@@ -11,19 +11,19 @@ import { FriendService } from '../shared'; | |||
11 | styles: [ require('./friend-add.component.scss') ] | 11 | styles: [ require('./friend-add.component.scss') ] |
12 | }) | 12 | }) |
13 | export class FriendAddComponent implements OnInit { | 13 | export class FriendAddComponent implements OnInit { |
14 | friendAddForm: FormGroup; | 14 | form: FormGroup; |
15 | urls = [ ]; | 15 | urls = [ ]; |
16 | error: string = null; | 16 | error: string = null; |
17 | 17 | ||
18 | constructor(private router: Router, private friendService: FriendService) {} | 18 | constructor(private router: Router, private friendService: FriendService) {} |
19 | 19 | ||
20 | ngOnInit() { | 20 | ngOnInit() { |
21 | this.friendAddForm = new FormGroup({}); | 21 | this.form = new FormGroup({}); |
22 | this.addField(); | 22 | this.addField(); |
23 | } | 23 | } |
24 | 24 | ||
25 | addField() { | 25 | addField() { |
26 | this.friendAddForm.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); | 26 | this.form.addControl(`url-${this.urls.length}`, new FormControl('', [ validateUrl ])); |
27 | this.urls.push(''); | 27 | this.urls.push(''); |
28 | } | 28 | } |
29 | 29 | ||
@@ -42,7 +42,7 @@ export class FriendAddComponent implements OnInit { | |||
42 | isFormValid() { | 42 | isFormValid() { |
43 | // Do not check the last input | 43 | // Do not check the last input |
44 | for (let i = 0; i < this.urls.length - 1; i++) { | 44 | for (let i = 0; i < this.urls.length - 1; i++) { |
45 | if (!this.friendAddForm.controls[`url-${i}`].valid) return false; | 45 | if (!this.form.controls[`url-${i}`].valid) return false; |
46 | } | 46 | } |
47 | 47 | ||
48 | const lastIndex = this.urls.length - 1; | 48 | const lastIndex = this.urls.length - 1; |
@@ -50,13 +50,13 @@ export class FriendAddComponent implements OnInit { | |||
50 | if (this.urls[lastIndex] === '' && lastIndex !== 0) { | 50 | if (this.urls[lastIndex] === '' && lastIndex !== 0) { |
51 | return true; | 51 | return true; |
52 | } else { | 52 | } else { |
53 | return this.friendAddForm.controls[`url-${lastIndex}`].valid; | 53 | return this.form.controls[`url-${lastIndex}`].valid; |
54 | } | 54 | } |
55 | } | 55 | } |
56 | 56 | ||
57 | removeField(index: number) { | 57 | removeField(index: number) { |
58 | // Remove the last control | 58 | // Remove the last control |
59 | this.friendAddForm.removeControl(`url-${this.urls.length - 1}`); | 59 | this.form.removeControl(`url-${this.urls.length - 1}`); |
60 | this.urls.splice(index, 1); | 60 | this.urls.splice(index, 1); |
61 | } | 61 | } |
62 | 62 | ||
@@ -94,7 +94,8 @@ export class FriendAddComponent implements OnInit { | |||
94 | private getNotEmptyUrls() { | 94 | private getNotEmptyUrls() { |
95 | const notEmptyUrls = []; | 95 | const notEmptyUrls = []; |
96 | 96 | ||
97 | this.urls.forEach((url) => { | 97 | Object.keys(this.form.value).forEach((urlKey) => { |
98 | const url = this.form.value[urlKey]; | ||
98 | if (url !== '') notEmptyUrls.push(url); | 99 | if (url !== '') notEmptyUrls.push(url); |
99 | }); | 100 | }); |
100 | 101 | ||
diff --git a/client/src/app/admin/users/user-add/user-add.component.html b/client/src/app/admin/users/user-add/user-add.component.html index 09219893b..9b76c7c1b 100644 --- a/client/src/app/admin/users/user-add/user-add.component.html +++ b/client/src/app/admin/users/user-add/user-add.component.html | |||
@@ -2,28 +2,28 @@ | |||
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 role="form" (ngSubmit)="addUser()" [formGroup]="userAddForm"> | 5 | <form role="form" (ngSubmit)="addUser()" [formGroup]="form"> |
6 | <div class="form-group"> | 6 | <div class="form-group"> |
7 | <label for="username">Username</label> | 7 | <label for="username">Username</label> |
8 | <input | 8 | <input |
9 | type="text" class="form-control" name="username" id="username" placeholder="Username" | 9 | type="text" class="form-control" id="username" placeholder="Username" |
10 | [(ngModel)]="username" | 10 | formControlName="username" |
11 | > | 11 | > |
12 | <div [hidden]="userAddForm.controls.username.valid || userAddForm.controls.username.pristine" class="alert alert-danger"> | 12 | <div *ngIf="formErrors.username" class="alert alert-danger"> |
13 | Username is required with a length >= 3 and <= 20 | 13 | {{ formErrors.username }} |
14 | </div> | 14 | </div> |
15 | </div> | 15 | </div> |
16 | 16 | ||
17 | <div class="form-group"> | 17 | <div class="form-group"> |
18 | <label for="password">Password</label> | 18 | <label for="password">Password</label> |
19 | <input | 19 | <input |
20 | type="password" class="form-control" name="password" id="password" placeholder="Password" | 20 | type="password" class="form-control" id="password" placeholder="Password" |
21 | [(ngModel)]="password" | 21 | formControlName="password" |
22 | > | 22 | > |
23 | <div [hidden]="userAddForm.controls.password.valid || userAddForm.controls.password.pristine" class="alert alert-danger"> | 23 | <div *ngIf="formErrors.password" class="alert alert-danger"> |
24 | Password is required with a length >= 6 | 24 | {{ formErrors.password }} |
25 | </div> | 25 | </div> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <input type="submit" value="Add user" class="btn btn-default" [disabled]="!userAddForm.valid"> | 28 | <input type="submit" value="Add user" class="btn btn-default" [disabled]="!form.valid"> |
29 | </form> | 29 | </form> |
diff --git a/client/src/app/admin/users/user-add/user-add.component.ts b/client/src/app/admin/users/user-add/user-add.component.ts index e3f4b2e1a..b79437795 100644 --- a/client/src/app/admin/users/user-add/user-add.component.ts +++ b/client/src/app/admin/users/user-add/user-add.component.ts | |||
@@ -1,32 +1,54 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | 1 | import { Component, OnInit } from '@angular/core'; |
2 | import { FormGroup, FormControl, Validators } from '@angular/forms'; | 2 | import { FormBuilder, FormGroup } from '@angular/forms'; |
3 | import { Router } from '@angular/router'; | 3 | import { Router } from '@angular/router'; |
4 | 4 | ||
5 | import { UserService } from '../shared'; | 5 | import { UserService } from '../shared'; |
6 | import { FormReactive, USER_USERNAME, USER_PASSWORD } from '../../../shared'; | ||
6 | 7 | ||
7 | @Component({ | 8 | @Component({ |
8 | selector: 'my-user-add', | 9 | selector: 'my-user-add', |
9 | template: require('./user-add.component.html') | 10 | template: require('./user-add.component.html') |
10 | }) | 11 | }) |
11 | export class UserAddComponent implements OnInit { | 12 | export class UserAddComponent extends FormReactive implements OnInit { |
12 | userAddForm: FormGroup; | ||
13 | error: string = null; | 13 | error: string = null; |
14 | username = ''; | ||
15 | password = ''; | ||
16 | 14 | ||
17 | constructor(private router: Router, private userService: UserService) {} | 15 | form: FormGroup; |
16 | formErrors = { | ||
17 | 'username': '', | ||
18 | 'password': '' | ||
19 | }; | ||
20 | validationMessages = { | ||
21 | 'username': USER_USERNAME.MESSAGES, | ||
22 | 'password': USER_PASSWORD.MESSAGES, | ||
23 | }; | ||
18 | 24 | ||
19 | ngOnInit() { | 25 | constructor( |
20 | this.userAddForm = new FormGroup({ | 26 | private formBuilder: FormBuilder, |
21 | username: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(20) ]), | 27 | private router: Router, |
22 | password: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(6) ]), | 28 | private userService: UserService |
29 | ) { | ||
30 | super(); | ||
31 | } | ||
32 | |||
33 | buildForm() { | ||
34 | this.form = this.formBuilder.group({ | ||
35 | username: [ '', USER_USERNAME.VALIDATORS ], | ||
36 | password: [ '', USER_PASSWORD.VALIDATORS ], | ||
23 | }); | 37 | }); |
38 | |||
39 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
40 | } | ||
41 | |||
42 | ngOnInit() { | ||
43 | this.buildForm(); | ||
24 | } | 44 | } |
25 | 45 | ||
26 | addUser() { | 46 | addUser() { |
27 | this.error = null; | 47 | this.error = null; |
28 | 48 | ||
29 | this.userService.addUser(this.username, this.password).subscribe( | 49 | const { username, password } = this.form.value; |
50 | |||
51 | this.userService.addUser(username, password).subscribe( | ||
30 | ok => this.router.navigate([ '/admin/users/list' ]), | 52 | ok => this.router.navigate([ '/admin/users/list' ]), |
31 | 53 | ||
32 | err => this.error = err.text | 54 | err => this.error = err.text |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 950b3c48e..f071224c5 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -28,7 +28,8 @@ import { | |||
28 | VideoMiniatureComponent, | 28 | VideoMiniatureComponent, |
29 | VideoSortComponent, | 29 | VideoSortComponent, |
30 | VideoWatchComponent, | 30 | VideoWatchComponent, |
31 | VideoService | 31 | VideoService, |
32 | WebTorrentService | ||
32 | } from './videos'; | 33 | } from './videos'; |
33 | import { | 34 | import { |
34 | FriendsComponent, | 35 | FriendsComponent, |
@@ -59,7 +60,7 @@ const APP_PROVIDERS = [ | |||
59 | 60 | ||
60 | AuthService, | 61 | AuthService, |
61 | RestExtractor, | 62 | RestExtractor, |
62 | RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService | 63 | RestExtractor, RestService, VideoService, SearchService, FriendService, UserService, AccountService, WebTorrentService |
63 | ]; | 64 | ]; |
64 | /** | 65 | /** |
65 | * `AppModule` is the main entry point into Angular2's bootstraping process | 66 | * `AppModule` is the main entry point into Angular2's bootstraping process |
diff --git a/client/src/app/login/login.component.html b/client/src/app/login/login.component.html index 636872942..94a405405 100644 --- a/client/src/app/login/login.component.html +++ b/client/src/app/login/login.component.html | |||
@@ -2,28 +2,28 @@ | |||
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 role="form" (ngSubmit)="login()" [formGroup]="loginForm"> | 5 | <form role="form" (ngSubmit)="login()" [formGroup]="form"> |
6 | <div class="form-group"> | 6 | <div class="form-group"> |
7 | <label for="username">Username</label> | 7 | <label for="username">Username</label> |
8 | <input | 8 | <input |
9 | type="text" class="form-control" name="username" id="username" placeholder="Username" | 9 | type="text" class="form-control" id="username" placeholder="Username" required |
10 | [(ngModel)]="username" | 10 | formControlName="username" |
11 | > | 11 | > |
12 | <div [hidden]="loginForm.controls.username.valid || loginForm.controls.username.pristine" class="alert alert-danger"> | 12 | <div *ngIf="formErrors.username" class="alert alert-danger"> |
13 | Username is required | 13 | {{ formErrors.username }} |
14 | </div> | 14 | </div> |
15 | </div> | 15 | </div> |
16 | 16 | ||
17 | <div class="form-group"> | 17 | <div class="form-group"> |
18 | <label for="password">Password</label> | 18 | <label for="password">Password</label> |
19 | <input | 19 | <input |
20 | type="password" class="form-control" name="password" id="password" placeholder="Password" | 20 | type="password" class="form-control" name="password" id="password" placeholder="Password" required |
21 | [(ngModel)]="password" | 21 | formControlName="password" |
22 | > | 22 | > |
23 | <div [hidden]="loginForm.controls.password.valid || loginForm.controls.password.pristine" class="alert alert-danger"> | 23 | <div *ngIf="formErrors.password" class="alert alert-danger"> |
24 | Password is required | 24 | {{ formErrors.password }} |
25 | </div> | 25 | </div> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <input type="submit" value="Login" class="btn btn-default" [disabled]="!loginForm.valid"> | 28 | <input type="submit" value="Login" class="btn btn-default" [disabled]="!form.valid"> |
29 | </form> | 29 | </form> |
diff --git a/client/src/app/login/login.component.ts b/client/src/app/login/login.component.ts index 7a4e15c2c..378714ca1 100644 --- a/client/src/app/login/login.component.ts +++ b/client/src/app/login/login.component.ts | |||
@@ -1,39 +1,60 @@ | |||
1 | import { Component, OnInit } from '@angular/core'; | 1 | import { Component, OnInit } from '@angular/core'; |
2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; | 2 | import { FormBuilder, FormGroup, Validators } from '@angular/forms'; |
3 | import { Router } from '@angular/router'; | 3 | import { Router } from '@angular/router'; |
4 | 4 | ||
5 | import { AuthService } from '../shared'; | 5 | import { AuthService, FormReactive } from '../shared'; |
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-login', | 8 | selector: 'my-login', |
9 | template: require('./login.component.html') | 9 | template: require('./login.component.html') |
10 | }) | 10 | }) |
11 | 11 | ||
12 | export class LoginComponent implements OnInit { | 12 | export class LoginComponent extends FormReactive implements OnInit { |
13 | error: string = null; | 13 | error: string = null; |
14 | username = ''; | 14 | |
15 | password: ''; | 15 | form: FormGroup; |
16 | loginForm: FormGroup; | 16 | formErrors = { |
17 | 'username': '', | ||
18 | 'password': '' | ||
19 | }; | ||
20 | validationMessages = { | ||
21 | 'username': { | ||
22 | 'required': 'Username is required.', | ||
23 | }, | ||
24 | 'password': { | ||
25 | 'required': 'Password is required.' | ||
26 | } | ||
27 | }; | ||
17 | 28 | ||
18 | constructor( | 29 | constructor( |
19 | private authService: AuthService, | 30 | private authService: AuthService, |
31 | private formBuilder: FormBuilder, | ||
20 | private router: Router | 32 | private router: Router |
21 | ) {} | 33 | ) { |
34 | super(); | ||
35 | } | ||
22 | 36 | ||
23 | ngOnInit() { | 37 | buildForm() { |
24 | this.loginForm = new FormGroup({ | 38 | this.form = this.formBuilder.group({ |
25 | username: new FormControl('', [ <any>Validators.required ]), | 39 | username: [ '', Validators.required ], |
26 | password: new FormControl('', [ <any>Validators.required ]), | 40 | password: [ '', Validators.required ], |
27 | }); | 41 | }); |
42 | |||
43 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
44 | } | ||
45 | |||
46 | ngOnInit() { | ||
47 | this.buildForm(); | ||
28 | } | 48 | } |
29 | 49 | ||
30 | login() { | 50 | login() { |
31 | this.authService.login(this.username, this.password).subscribe( | 51 | this.error = null; |
32 | result => { | 52 | |
33 | this.error = null; | 53 | const { username, password } = this.form.value; |
54 | |||
55 | this.authService.login(username, password).subscribe( | ||
56 | result => this.router.navigate(['/videos/list']), | ||
34 | 57 | ||
35 | this.router.navigate(['/videos/list']); | ||
36 | }, | ||
37 | error => { | 58 | error => { |
38 | console.error(error.json); | 59 | console.error(error.json); |
39 | 60 | ||
diff --git a/client/src/app/shared/form-validators/index.ts b/client/src/app/shared/form-validators/index.ts deleted file mode 100644 index f9e9a6191..000000000 --- a/client/src/app/shared/form-validators/index.ts +++ /dev/null | |||
@@ -1 +0,0 @@ | |||
1 | export * from './url.validator'; | ||
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts new file mode 100644 index 000000000..1e8a69771 --- /dev/null +++ b/client/src/app/shared/forms/form-reactive.ts | |||
@@ -0,0 +1,24 @@ | |||
1 | import { FormGroup } from '@angular/forms'; | ||
2 | |||
3 | export abstract class FormReactive { | ||
4 | abstract form: FormGroup; | ||
5 | abstract formErrors: Object; | ||
6 | abstract validationMessages: Object; | ||
7 | |||
8 | abstract buildForm(): void; | ||
9 | |||
10 | protected onValueChanged(data?: any) { | ||
11 | for (const field in this.formErrors) { | ||
12 | // clear previous error message (if any) | ||
13 | this.formErrors[field] = ''; | ||
14 | const control = this.form.get(field); | ||
15 | |||
16 | if (control && control.dirty && !control.valid) { | ||
17 | const messages = this.validationMessages[field]; | ||
18 | for (const key in control.errors) { | ||
19 | this.formErrors[field] += messages[key] + ' '; | ||
20 | } | ||
21 | } | ||
22 | } | ||
23 | } | ||
24 | } | ||
diff --git a/client/src/app/shared/forms/form-validators/index.ts b/client/src/app/shared/forms/form-validators/index.ts new file mode 100644 index 000000000..1d2ae6f68 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/index.ts | |||
@@ -0,0 +1,3 @@ | |||
1 | export * from './url.validator'; | ||
2 | export * from './user'; | ||
3 | export * from './video'; | ||
diff --git a/client/src/app/shared/form-validators/url.validator.ts b/client/src/app/shared/forms/form-validators/url.validator.ts index 67163b4e9..67163b4e9 100644 --- a/client/src/app/shared/form-validators/url.validator.ts +++ b/client/src/app/shared/forms/form-validators/url.validator.ts | |||
diff --git a/client/src/app/shared/forms/form-validators/user.ts b/client/src/app/shared/forms/form-validators/user.ts new file mode 100644 index 000000000..5b11ff265 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/user.ts | |||
@@ -0,0 +1,17 @@ | |||
1 | import { Validators } from '@angular/forms'; | ||
2 | |||
3 | export const USER_USERNAME = { | ||
4 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(20) ], | ||
5 | MESSAGES: { | ||
6 | 'required': 'Username is required.', | ||
7 | 'minlength': 'Username must be at least 3 characters long.', | ||
8 | 'maxlength': 'Username cannot be more than 20 characters long.' | ||
9 | } | ||
10 | }; | ||
11 | export const USER_PASSWORD = { | ||
12 | VALIDATORS: [ Validators.required, Validators.minLength(6) ], | ||
13 | MESSAGES: { | ||
14 | 'required': 'Password is required.', | ||
15 | 'minlength': 'Password must be at least 6 characters long.', | ||
16 | } | ||
17 | }; | ||
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts new file mode 100644 index 000000000..3766d4018 --- /dev/null +++ b/client/src/app/shared/forms/form-validators/video.ts | |||
@@ -0,0 +1,25 @@ | |||
1 | import { Validators } from '@angular/forms'; | ||
2 | |||
3 | export const VIDEO_NAME = { | ||
4 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(50) ], | ||
5 | MESSAGES: { | ||
6 | 'required': 'Video name is required.', | ||
7 | 'minlength': 'Video name must be at least 3 characters long.', | ||
8 | 'maxlength': 'Video name cannot be more than 50 characters long.' | ||
9 | } | ||
10 | }; | ||
11 | export const VIDEO_DESCRIPTION = { | ||
12 | VALIDATORS: [ Validators.required, Validators.minLength(3), Validators.maxLength(250) ], | ||
13 | MESSAGES: { | ||
14 | 'required': 'Video description is required.', | ||
15 | 'minlength': 'Video description must be at least 3 characters long.', | ||
16 | 'maxlength': 'Video description cannot be more than 250 characters long.' | ||
17 | } | ||
18 | }; | ||
19 | |||
20 | export const VIDEO_TAGS = { | ||
21 | VALIDATORS: [ Validators.pattern('^[a-zA-Z0-9]{2,10}$') ], | ||
22 | MESSAGES: { | ||
23 | 'pattern': 'A tag should be between 2 and 10 alphanumeric characters long.' | ||
24 | } | ||
25 | }; | ||
diff --git a/client/src/app/shared/forms/index.ts b/client/src/app/shared/forms/index.ts new file mode 100644 index 000000000..588ebb4be --- /dev/null +++ b/client/src/app/shared/forms/index.ts | |||
@@ -0,0 +1,2 @@ | |||
1 | export * from './form-validators'; | ||
2 | export * from './form-reactive'; | ||
diff --git a/client/src/app/shared/index.ts b/client/src/app/shared/index.ts index c362a0e4a..af34b4b64 100644 --- a/client/src/app/shared/index.ts +++ b/client/src/app/shared/index.ts | |||
@@ -1,5 +1,5 @@ | |||
1 | export * from './auth'; | 1 | export * from './auth'; |
2 | export * from './form-validators'; | 2 | export * from './forms'; |
3 | export * from './rest'; | 3 | export * from './rest'; |
4 | export * from './search'; | 4 | export * from './search'; |
5 | export * from './users'; | 5 | export * from './users'; |
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 76bb61f7d..64320cae7 100644 --- a/client/src/app/videos/video-add/video-add.component.html +++ b/client/src/app/videos/video-add/video-add.component.html | |||
@@ -2,31 +2,31 @@ | |||
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 novalidate (ngSubmit)="upload()" [formGroup]="videoForm"> | 5 | <form novalidate (ngSubmit)="upload()" [formGroup]="form"> |
6 | <div class="form-group"> | 6 | <div class="form-group"> |
7 | <label for="name">Name</label> | 7 | <label for="name">Name</label> |
8 | <input | 8 | <input |
9 | type="text" class="form-control" name="name" id="name" | 9 | type="text" class="form-control" id="name" |
10 | [(ngModel)]="video.name" | 10 | formControlName="name" |
11 | > | 11 | > |
12 | <div [hidden]="videoForm.controls.name.valid || videoForm.controls.name.pristine" class="alert alert-warning"> | 12 | <div *ngIf="formErrors.name" class="alert alert-danger"> |
13 | A name is required and should be between 3 and 50 characters long | 13 | {{ formErrors.name }} |
14 | </div> | 14 | </div> |
15 | </div> | 15 | </div> |
16 | 16 | ||
17 | <div class="form-group"> | 17 | <div class="form-group"> |
18 | <label for="tags">Tags</label> | 18 | <label for="tags">Tags</label> |
19 | <input | 19 | <input |
20 | type="text" class="form-control" name="tags" id="tags" | 20 | type="text" class="form-control" id="currentTag" |
21 | [disabled]="isTagsInputDisabled" (keyup)="onTagKeyPress($event)" [(ngModel)]="currentTag" | 21 | formControlName="currentTag" (keyup)="onTagKeyPress($event)" |
22 | > | 22 | > |
23 | <div [hidden]="videoForm.controls.tags.valid || videoForm.controls.tags.pristine" class="alert alert-warning"> | 23 | <div *ngIf="formErrors.currentTag" class="alert alert-danger"> |
24 | A tag should be between 2 and 10 characters (alphanumeric) long | 24 | {{ formErrors.currentTag }} |
25 | </div> | 25 | </div> |
26 | </div> | 26 | </div> |
27 | 27 | ||
28 | <div class="tags"> | 28 | <div class="tags"> |
29 | <div class="label label-primary tag" *ngFor="let tag of video.tags"> | 29 | <div class="label label-primary tag" *ngFor="let tag of tags"> |
30 | {{ tag }} | 30 | {{ tag }} |
31 | <span class="remove" (click)="removeTag(tag)">x</span> | 31 | <span class="remove" (click)="removeTag(tag)">x</span> |
32 | </div> | 32 | </div> |
@@ -53,12 +53,12 @@ | |||
53 | <div class="form-group"> | 53 | <div class="form-group"> |
54 | <label for="description">Description</label> | 54 | <label for="description">Description</label> |
55 | <textarea | 55 | <textarea |
56 | name="description" id="description" class="form-control" placeholder="Description..." | 56 | id="description" class="form-control" placeholder="Description..." |
57 | [(ngModel)]="video.description" | 57 | formControlName="description" |
58 | > | 58 | > |
59 | </textarea> | 59 | </textarea> |
60 | <div [hidden]="videoForm.controls.description.valid || videoForm.controls.description.pristine" class="alert alert-warning"> | 60 | <div *ngIf="formErrors.description" class="alert alert-danger"> |
61 | A description is required and should be between 3 and 250 characters long | 61 | {{ formErrors.description }} |
62 | </div> | 62 | </div> |
63 | </div> | 63 | </div> |
64 | 64 | ||
@@ -69,7 +69,7 @@ | |||
69 | <div class="form-group"> | 69 | <div class="form-group"> |
70 | <input | 70 | <input |
71 | type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()" | 71 | type="submit" value="Upload" class="btn btn-default form-control" [title]="getInvalidFieldsTitle()" |
72 | [disabled]="!videoForm.valid || video.tags.length === 0 || filename === null" | 72 | [disabled]="!form.valid || tags.length === 0 || filename === null" |
73 | > | 73 | > |
74 | </div> | 74 | </div> |
75 | </form> | 75 | </form> |
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 f0695d768..16a8409be 100644 --- a/client/src/app/videos/video-add/video-add.component.ts +++ b/client/src/app/videos/video-add/video-add.component.ts | |||
@@ -1,10 +1,10 @@ | |||
1 | import { Component, ElementRef, OnInit } from '@angular/core'; | 1 | import { Component, ElementRef, OnInit } from '@angular/core'; |
2 | import { FormControl, FormGroup, Validators } from '@angular/forms'; | 2 | import { FormBuilder, FormGroup } from '@angular/forms'; |
3 | import { Router } from '@angular/router'; | 3 | import { Router } from '@angular/router'; |
4 | 4 | ||
5 | import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; | 5 | import { FileUploader } from 'ng2-file-upload/ng2-file-upload'; |
6 | 6 | ||
7 | import { AuthService } from '../../shared'; | 7 | import { AuthService, FormReactive, VIDEO_NAME, VIDEO_DESCRIPTION, VIDEO_TAGS } from '../../shared'; |
8 | 8 | ||
9 | @Component({ | 9 | @Component({ |
10 | selector: 'my-videos-add', | 10 | selector: 'my-videos-add', |
@@ -12,22 +12,31 @@ import { AuthService } from '../../shared'; | |||
12 | template: require('./video-add.component.html') | 12 | template: require('./video-add.component.html') |
13 | }) | 13 | }) |
14 | 14 | ||
15 | export class VideoAddComponent implements OnInit { | 15 | export class VideoAddComponent extends FormReactive implements OnInit { |
16 | currentTag: string; // Tag the user is writing in the input | 16 | tags: string[] = []; |
17 | error: string = null; | ||
18 | videoForm: FormGroup; | ||
19 | uploader: FileUploader; | 17 | uploader: FileUploader; |
20 | video = { | 18 | |
19 | error: string = null; | ||
20 | form: FormGroup; | ||
21 | formErrors = { | ||
21 | name: '', | 22 | name: '', |
22 | tags: [], | 23 | description: '', |
23 | description: '' | 24 | currentTag: '' |
25 | }; | ||
26 | validationMessages = { | ||
27 | name: VIDEO_NAME.MESSAGES, | ||
28 | description: VIDEO_DESCRIPTION.MESSAGES, | ||
29 | currentTag: VIDEO_TAGS.MESSAGES | ||
24 | }; | 30 | }; |
25 | 31 | ||
26 | constructor( | 32 | constructor( |
27 | private authService: AuthService, | 33 | private authService: AuthService, |
28 | private elementRef: ElementRef, | 34 | private elementRef: ElementRef, |
35 | private formBuilder: FormBuilder, | ||
29 | private router: Router | 36 | private router: Router |
30 | ) {} | 37 | ) { |
38 | super(); | ||
39 | } | ||
31 | 40 | ||
32 | get filename() { | 41 | get filename() { |
33 | if (this.uploader.queue.length === 0) { | 42 | if (this.uploader.queue.length === 0) { |
@@ -37,20 +46,26 @@ export class VideoAddComponent implements OnInit { | |||
37 | return this.uploader.queue[0].file.name; | 46 | return this.uploader.queue[0].file.name; |
38 | } | 47 | } |
39 | 48 | ||
40 | get isTagsInputDisabled () { | 49 | buildForm() { |
41 | return this.video.tags.length >= 3; | 50 | this.form = this.formBuilder.group({ |
51 | name: [ '', VIDEO_NAME.VALIDATORS ], | ||
52 | description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], | ||
53 | currentTag: [ '', VIDEO_TAGS.VALIDATORS ] | ||
54 | }); | ||
55 | |||
56 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)); | ||
42 | } | 57 | } |
43 | 58 | ||
44 | getInvalidFieldsTitle() { | 59 | getInvalidFieldsTitle() { |
45 | let title = ''; | 60 | let title = ''; |
46 | const nameControl = this.videoForm.controls['name']; | 61 | const nameControl = this.form.controls['name']; |
47 | const descriptionControl = this.videoForm.controls['description']; | 62 | const descriptionControl = this.form.controls['description']; |
48 | 63 | ||
49 | if (!nameControl.valid) { | 64 | if (!nameControl.valid) { |
50 | title += 'A name is required\n'; | 65 | title += 'A name is required\n'; |
51 | } | 66 | } |
52 | 67 | ||
53 | if (this.video.tags.length === 0) { | 68 | if (this.tags.length === 0) { |
54 | title += 'At least one tag is required\n'; | 69 | title += 'At least one tag is required\n'; |
55 | } | 70 | } |
56 | 71 | ||
@@ -66,13 +81,6 @@ export class VideoAddComponent implements OnInit { | |||
66 | } | 81 | } |
67 | 82 | ||
68 | ngOnInit() { | 83 | ngOnInit() { |
69 | this.videoForm = new FormGroup({ | ||
70 | name: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(50) ]), | ||
71 | description: new FormControl('', [ <any>Validators.required, <any>Validators.minLength(3), <any>Validators.maxLength(250) ]), | ||
72 | tags: new FormControl('', <any>Validators.pattern('^[a-zA-Z0-9]{2,10}$')) | ||
73 | }); | ||
74 | |||
75 | |||
76 | this.uploader = new FileUploader({ | 84 | this.uploader = new FileUploader({ |
77 | authToken: this.authService.getRequestHeaderValue(), | 85 | authToken: this.authService.getRequestHeaderValue(), |
78 | queueLimit: 1, | 86 | queueLimit: 1, |
@@ -81,26 +89,37 @@ export class VideoAddComponent implements OnInit { | |||
81 | }); | 89 | }); |
82 | 90 | ||
83 | this.uploader.onBuildItemForm = (item, form) => { | 91 | this.uploader.onBuildItemForm = (item, form) => { |
84 | form.append('name', this.video.name); | 92 | const name = this.form.value['name']; |
85 | form.append('description', this.video.description); | 93 | const description = this.form.value['description']; |
94 | |||
95 | form.append('name', name); | ||
96 | form.append('description', description); | ||
86 | 97 | ||
87 | for (let i = 0; i < this.video.tags.length; i++) { | 98 | for (let i = 0; i < this.tags.length; i++) { |
88 | form.append(`tags[${i}]`, this.video.tags[i]); | 99 | form.append(`tags[${i}]`, this.tags[i]); |
89 | } | 100 | } |
90 | }; | 101 | }; |
102 | |||
103 | this.buildForm(); | ||
91 | } | 104 | } |
92 | 105 | ||
93 | onTagKeyPress(event: KeyboardEvent) { | 106 | onTagKeyPress(event: KeyboardEvent) { |
107 | const currentTag = this.form.value['currentTag']; | ||
108 | |||
94 | // Enter press | 109 | // Enter press |
95 | if (event.keyCode === 13) { | 110 | if (event.keyCode === 13) { |
96 | // Check if the tag is valid and does not already exist | 111 | // Check if the tag is valid and does not already exist |
97 | if ( | 112 | if ( |
98 | this.currentTag !== '' && | 113 | currentTag !== '' && |
99 | this.videoForm.controls['tags'].valid && | 114 | this.form.controls['currentTag'].valid && |
100 | this.video.tags.indexOf(this.currentTag) === -1 | 115 | this.tags.indexOf(currentTag) === -1 |
101 | ) { | 116 | ) { |
102 | this.video.tags.push(this.currentTag); | 117 | this.tags.push(currentTag); |
103 | this.currentTag = ''; | 118 | this.form.patchValue({ currentTag: '' }); |
119 | |||
120 | if (this.tags.length >= 3) { | ||
121 | this.form.get('currentTag').disable(); | ||
122 | } | ||
104 | } | 123 | } |
105 | } | 124 | } |
106 | } | 125 | } |
@@ -110,7 +129,7 @@ export class VideoAddComponent implements OnInit { | |||
110 | } | 129 | } |
111 | 130 | ||
112 | removeTag(tag: string) { | 131 | removeTag(tag: string) { |
113 | this.video.tags.splice(this.video.tags.indexOf(tag), 1); | 132 | this.tags.splice(this.tags.indexOf(tag), 1); |
114 | } | 133 | } |
115 | 134 | ||
116 | upload() { | 135 | upload() { |