diff options
Diffstat (limited to 'client/src/app/videos/+video-edit')
13 files changed, 623 insertions, 427 deletions
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.html b/client/src/app/videos/+video-edit/shared/video-description.component.html new file mode 100644 index 000000000..5d05467be --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-description.component.html | |||
@@ -0,0 +1,9 @@ | |||
1 | <textarea | ||
2 | [(ngModel)]="description" (ngModelChange)="onModelChange()" | ||
3 | id="description" name="description"> | ||
4 | </textarea> | ||
5 | |||
6 | <tabset #staticTabs class="previews"> | ||
7 | <tab heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab> | ||
8 | <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab> | ||
9 | </tabset> | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.scss b/client/src/app/videos/+video-edit/shared/video-description.component.scss new file mode 100644 index 000000000..2a4c8d189 --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-description.component.scss | |||
@@ -0,0 +1,24 @@ | |||
1 | textarea { | ||
2 | @include peertube-input-text(100%); | ||
3 | |||
4 | padding: 5px 15px; | ||
5 | font-size: 15px; | ||
6 | height: 150px; | ||
7 | margin-bottom: 15px; | ||
8 | } | ||
9 | |||
10 | /deep/ { | ||
11 | .nav-link { | ||
12 | display: flex !important; | ||
13 | align-items: center; | ||
14 | height: 30px !important; | ||
15 | padding: 0 15px !important; | ||
16 | } | ||
17 | |||
18 | .tab-content { | ||
19 | min-height: 75px; | ||
20 | padding: 15px; | ||
21 | font-size: 15px; | ||
22 | } | ||
23 | } | ||
24 | |||
diff --git a/client/src/app/videos/+video-edit/shared/video-description.component.ts b/client/src/app/videos/+video-edit/shared/video-description.component.ts new file mode 100644 index 000000000..9b77a27e6 --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-description.component.ts | |||
@@ -0,0 +1,68 @@ | |||
1 | import { Component, forwardRef, Input, OnInit } from '@angular/core' | ||
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | ||
3 | import { truncate } from 'lodash' | ||
4 | import 'rxjs/add/operator/debounceTime' | ||
5 | import 'rxjs/add/operator/distinctUntilChanged' | ||
6 | import { Subject } from 'rxjs/Subject' | ||
7 | import { MarkdownService } from '../../shared' | ||
8 | |||
9 | @Component({ | ||
10 | selector: 'my-video-description', | ||
11 | templateUrl: './video-description.component.html', | ||
12 | styleUrls: [ './video-description.component.scss' ], | ||
13 | providers: [ | ||
14 | { | ||
15 | provide: NG_VALUE_ACCESSOR, | ||
16 | useExisting: forwardRef(() => VideoDescriptionComponent), | ||
17 | multi: true | ||
18 | } | ||
19 | ] | ||
20 | }) | ||
21 | |||
22 | export class VideoDescriptionComponent implements ControlValueAccessor, OnInit { | ||
23 | @Input() description = '' | ||
24 | truncatedDescriptionHTML = '' | ||
25 | descriptionHTML = '' | ||
26 | |||
27 | private descriptionChanged = new Subject<string>() | ||
28 | |||
29 | constructor (private markdownService: MarkdownService) {} | ||
30 | |||
31 | ngOnInit () { | ||
32 | this.descriptionChanged | ||
33 | .debounceTime(150) | ||
34 | .distinctUntilChanged() | ||
35 | .subscribe(() => this.updateDescriptionPreviews()) | ||
36 | |||
37 | this.descriptionChanged.next(this.description) | ||
38 | } | ||
39 | |||
40 | propagateChange = (_: any) => { /* empty */ } | ||
41 | |||
42 | writeValue (description: string) { | ||
43 | this.description = description | ||
44 | |||
45 | this.descriptionChanged.next(this.description) | ||
46 | } | ||
47 | |||
48 | registerOnChange (fn: (_: any) => void) { | ||
49 | this.propagateChange = fn | ||
50 | } | ||
51 | |||
52 | registerOnTouched () { | ||
53 | // Unused | ||
54 | } | ||
55 | |||
56 | onModelChange () { | ||
57 | this.propagateChange(this.description) | ||
58 | |||
59 | this.descriptionChanged.next(this.description) | ||
60 | } | ||
61 | |||
62 | private updateDescriptionPreviews () { | ||
63 | if (!this.description) return | ||
64 | |||
65 | this.truncatedDescriptionHTML = this.markdownService.markdownToHTML(truncate(this.description, { length: 250 })) | ||
66 | this.descriptionHTML = this.markdownService.markdownToHTML(this.description) | ||
67 | } | ||
68 | } | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.html b/client/src/app/videos/+video-edit/shared/video-edit.component.html new file mode 100644 index 000000000..8c071ce12 --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html | |||
@@ -0,0 +1,86 @@ | |||
1 | <div class="video-edit row" [formGroup]="form"> | ||
2 | |||
3 | <div class="col-md-8"> | ||
4 | <div class="form-group"> | ||
5 | <label for="name">Title</label> | ||
6 | <input type="text" id="name" formControlName="name" /> | ||
7 | <div *ngIf="formErrors.name" class="form-error"> | ||
8 | {{ formErrors.name }} | ||
9 | </div> | ||
10 | </div> | ||
11 | |||
12 | <div class="form-group"> | ||
13 | <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span> | ||
14 | <tag-input | ||
15 | [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | ||
16 | formControlName="tags" maxItems="5" modelAsStrings="true" | ||
17 | ></tag-input> | ||
18 | </div> | ||
19 | |||
20 | <div class="form-group"> | ||
21 | <label for="description">Description</label> | ||
22 | <my-video-description formControlName="description"></my-video-description> | ||
23 | |||
24 | <div *ngIf="formErrors.description" class="form-error"> | ||
25 | {{ formErrors.description }} | ||
26 | </div> | ||
27 | </div> | ||
28 | </div> | ||
29 | |||
30 | <div class="col-md-4"> | ||
31 | <div class="form-group"> | ||
32 | <label for="category">Category</label> | ||
33 | <select id="category" formControlName="category"> | ||
34 | <option></option> | ||
35 | <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> | ||
36 | </select> | ||
37 | |||
38 | <div *ngIf="formErrors.category" class="form-error"> | ||
39 | {{ formErrors.category }} | ||
40 | </div> | ||
41 | </div> | ||
42 | |||
43 | <div class="form-group"> | ||
44 | <label for="licence">Licence</label> | ||
45 | <select id="licence" formControlName="licence"> | ||
46 | <option></option> | ||
47 | <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> | ||
48 | </select> | ||
49 | |||
50 | <div *ngIf="formErrors.licence" class="form-error"> | ||
51 | {{ formErrors.licence }} | ||
52 | </div> | ||
53 | </div> | ||
54 | |||
55 | <div class="form-group"> | ||
56 | <label for="language">Language</label> | ||
57 | <select id="language" formControlName="language"> | ||
58 | <option></option> | ||
59 | <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> | ||
60 | </select> | ||
61 | |||
62 | <div *ngIf="formErrors.language" class="form-error"> | ||
63 | {{ formErrors.language }} | ||
64 | </div> | ||
65 | </div> | ||
66 | |||
67 | <div class="form-group"> | ||
68 | <label for="privacy">Privacy</label> | ||
69 | <select id="privacy" formControlName="privacy"> | ||
70 | |||
71 | <option></option> | ||
72 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | ||
73 | </select> | ||
74 | |||
75 | <div *ngIf="formErrors.privacy" class="form-error"> | ||
76 | {{ formErrors.privacy }} | ||
77 | </div> | ||
78 | </div> | ||
79 | |||
80 | <div class="form-group form-group-checkbox"> | ||
81 | <input type="checkbox" id="nsfw" formControlName="nsfw" /> | ||
82 | <label for="nsfw">This video contains mature or explicit content</label> | ||
83 | </div> | ||
84 | |||
85 | </div> | ||
86 | </div> | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.scss b/client/src/app/videos/+video-edit/shared/video-edit.component.scss index 9ee0c520c..d363499ce 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.component.scss +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.scss | |||
@@ -1,48 +1,126 @@ | |||
1 | .btn-file { | 1 | .video-edit { |
2 | position: relative; | 2 | height: 100%; |
3 | overflow: hidden; | 3 | |
4 | display: block; | 4 | .form-group { |
5 | margin-bottom: 25px; | ||
6 | } | ||
7 | |||
8 | input { | ||
9 | @include peertube-input-text(100%); | ||
10 | display: block; | ||
11 | |||
12 | &[type=checkbox] { | ||
13 | outline: 0; | ||
14 | } | ||
15 | } | ||
16 | |||
17 | select { | ||
18 | @include peertube-select(100%); | ||
19 | } | ||
20 | |||
21 | input, select { | ||
22 | font-size: 15px | ||
23 | } | ||
24 | |||
25 | .form-group-checkbox { | ||
26 | display: flex; | ||
27 | align-items: center; | ||
28 | |||
29 | label { | ||
30 | font-weight: $font-regular; | ||
31 | margin: 0; | ||
32 | } | ||
33 | |||
34 | input { | ||
35 | width: 10px; | ||
36 | margin-right: 10px; | ||
37 | } | ||
38 | } | ||
5 | } | 39 | } |
6 | 40 | ||
7 | .btn-file input[type=file] { | 41 | .submit-container { |
8 | position: absolute; | ||
9 | top: 0; | ||
10 | right: 0; | ||
11 | min-width: 100%; | ||
12 | min-height: 100%; | ||
13 | font-size: 100px; | ||
14 | text-align: right; | 42 | text-align: right; |
15 | filter: alpha(opacity=0); | 43 | position: relative; |
16 | opacity: 0; | 44 | bottom: $button-height; |
17 | outline: none; | ||
18 | background: white; | ||
19 | cursor: inherit; | ||
20 | display: block; | ||
21 | } | ||
22 | 45 | ||
23 | .form-group { | 46 | .message-submit { |
24 | margin-bottom: 10px; | 47 | display: inline-block; |
25 | } | 48 | margin-right: 25px; |
49 | |||
50 | color: #585858; | ||
51 | font-size: 15px; | ||
52 | } | ||
53 | |||
54 | .submit-button { | ||
55 | @include peertube-button; | ||
56 | @include orange-button; | ||
57 | |||
58 | display: inline-block; | ||
26 | 59 | ||
27 | div.tags { | 60 | input { |
28 | height: 40px; | 61 | cursor: inherit; |
29 | font-size: 20px; | 62 | background-color: inherit; |
30 | margin-top: 20px; | 63 | border: none; |
64 | padding: 0; | ||
65 | outline: 0; | ||
66 | } | ||
31 | 67 | ||
32 | .tag { | 68 | .icon.icon-validate { |
33 | margin-right: 10px; | 69 | @include icon(20px); |
34 | 70 | ||
35 | .remove { | 71 | cursor: inherit; |
36 | cursor: pointer; | 72 | position: relative; |
73 | top: -1px; | ||
74 | margin-right: 4px; | ||
75 | background-image: url('../../../../assets/images/global/validate.svg'); | ||
37 | } | 76 | } |
38 | } | 77 | } |
39 | } | 78 | } |
40 | 79 | ||
41 | div.file-to-upload { | 80 | /deep/ { |
42 | height: 40px; | 81 | .ng2-tag-input { |
82 | border: none !important; | ||
83 | } | ||
43 | 84 | ||
44 | .glyphicon-remove { | 85 | .ng2-tags-container { |
45 | cursor: pointer; | 86 | display: flex; |
87 | align-items: center; | ||
88 | border: 1px solid #C6C6C6; | ||
89 | border-radius: 3px; | ||
90 | padding: 5px !important; | ||
91 | } | ||
92 | |||
93 | tag { | ||
94 | background-color: #E5E5E5 !important; | ||
95 | border-radius: 3px !important; | ||
96 | font-size: 15px !important; | ||
97 | color: #000 !important; | ||
98 | height: 30px !important; | ||
99 | line-height: 30px !important; | ||
100 | margin: 0 5px 0 0 !important; | ||
101 | cursor: default !important; | ||
102 | padding: 0 8px 0 10px !important; | ||
103 | |||
104 | div { | ||
105 | height: 100% !important; | ||
106 | } | ||
107 | } | ||
108 | |||
109 | delete-icon { | ||
110 | cursor: pointer !important; | ||
111 | height: auto !important; | ||
112 | vertical-align: middle !important; | ||
113 | padding-left: 6px !important; | ||
114 | |||
115 | svg { | ||
116 | height: auto !important; | ||
117 | vertical-align: middle !important; | ||
118 | fill: #585858 !important; | ||
119 | } | ||
120 | |||
121 | &:hover { | ||
122 | transform: none !important; | ||
123 | } | ||
46 | } | 124 | } |
47 | } | 125 | } |
48 | 126 | ||
@@ -50,7 +128,3 @@ div.file-to-upload { | |||
50 | font-size: 0.8em; | 128 | font-size: 0.8em; |
51 | font-style: italic; | 129 | font-style: italic; |
52 | } | 130 | } |
53 | |||
54 | .label-tags { | ||
55 | margin-bottom: 0; | ||
56 | } | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.component.ts b/client/src/app/videos/+video-edit/shared/video-edit.component.ts new file mode 100644 index 000000000..5b1cc3f9c --- /dev/null +++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts | |||
@@ -0,0 +1,83 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | ||
2 | import { FormBuilder, FormControl, FormGroup } from '@angular/forms' | ||
3 | import { ActivatedRoute, Router } from '@angular/router' | ||
4 | import { NotificationsService } from 'angular2-notifications' | ||
5 | import { ServerService } from 'app/core' | ||
6 | import { VideoEdit } from 'app/shared/video/video-edit.model' | ||
7 | import 'rxjs/add/observable/forkJoin' | ||
8 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | ||
9 | import { | ||
10 | ValidatorMessage, | ||
11 | VIDEO_CATEGORY, | ||
12 | VIDEO_DESCRIPTION, | ||
13 | VIDEO_LANGUAGE, | ||
14 | VIDEO_LICENCE, | ||
15 | VIDEO_NAME, | ||
16 | VIDEO_PRIVACY, | ||
17 | VIDEO_TAGS | ||
18 | } from '../../../shared/forms/form-validators' | ||
19 | |||
20 | @Component({ | ||
21 | selector: 'my-video-edit', | ||
22 | styleUrls: [ './video-edit.component.scss' ], | ||
23 | templateUrl: './video-edit.component.html' | ||
24 | }) | ||
25 | |||
26 | export class VideoEditComponent implements OnInit { | ||
27 | @Input() form: FormGroup | ||
28 | @Input() formErrors: { [ id: string ]: string } = {} | ||
29 | @Input() validationMessages: ValidatorMessage = {} | ||
30 | @Input() videoPrivacies = [] | ||
31 | |||
32 | tags: string[] = [] | ||
33 | videoCategories = [] | ||
34 | videoLicences = [] | ||
35 | videoLanguages = [] | ||
36 | video: VideoEdit | ||
37 | |||
38 | tagValidators = VIDEO_TAGS.VALIDATORS | ||
39 | tagValidatorsMessages = VIDEO_TAGS.MESSAGES | ||
40 | |||
41 | error: string = null | ||
42 | |||
43 | constructor ( | ||
44 | private formBuilder: FormBuilder, | ||
45 | private route: ActivatedRoute, | ||
46 | private router: Router, | ||
47 | private notificationsService: NotificationsService, | ||
48 | private serverService: ServerService | ||
49 | ) { } | ||
50 | |||
51 | updateForm () { | ||
52 | this.formErrors['name'] = '' | ||
53 | this.formErrors['privacy'] = '' | ||
54 | this.formErrors['category'] = '' | ||
55 | this.formErrors['licence'] = '' | ||
56 | this.formErrors['language'] = '' | ||
57 | this.formErrors['description'] = '' | ||
58 | |||
59 | this.validationMessages['name'] = VIDEO_NAME.MESSAGES | ||
60 | this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES | ||
61 | this.validationMessages['category'] = VIDEO_CATEGORY.MESSAGES | ||
62 | this.validationMessages['licence'] = VIDEO_LICENCE.MESSAGES | ||
63 | this.validationMessages['language'] = VIDEO_LANGUAGE.MESSAGES | ||
64 | this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES | ||
65 | |||
66 | this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS)) | ||
67 | this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS)) | ||
68 | this.form.addControl('nsfw', new FormControl(false)) | ||
69 | this.form.addControl('category', new FormControl('', VIDEO_CATEGORY.VALIDATORS)) | ||
70 | this.form.addControl('licence', new FormControl('', VIDEO_LICENCE.VALIDATORS)) | ||
71 | this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS)) | ||
72 | this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS)) | ||
73 | this.form.addControl('tags', new FormControl('')) | ||
74 | } | ||
75 | |||
76 | ngOnInit () { | ||
77 | this.updateForm() | ||
78 | |||
79 | this.videoCategories = this.serverService.getVideoCategories() | ||
80 | this.videoLicences = this.serverService.getVideoLicences() | ||
81 | this.videoLanguages = this.serverService.getVideoLanguages() | ||
82 | } | ||
83 | } | ||
diff --git a/client/src/app/videos/+video-edit/shared/video-edit.module.ts b/client/src/app/videos/+video-edit/shared/video-edit.module.ts index c64cea920..ce106d82f 100644 --- a/client/src/app/videos/+video-edit/shared/video-edit.module.ts +++ b/client/src/app/videos/+video-edit/shared/video-edit.module.ts | |||
@@ -3,8 +3,10 @@ import { NgModule } from '@angular/core' | |||
3 | import { TagInputModule } from 'ngx-chips' | 3 | import { TagInputModule } from 'ngx-chips' |
4 | import { TabsModule } from 'ngx-bootstrap/tabs' | 4 | import { TabsModule } from 'ngx-bootstrap/tabs' |
5 | 5 | ||
6 | import { VideoService, MarkdownService, VideoDescriptionComponent } from '../../shared' | 6 | import { MarkdownService } from '../../shared' |
7 | import { SharedModule } from '../../../shared' | 7 | import { SharedModule } from '../../../shared' |
8 | import { VideoDescriptionComponent } from './video-description.component' | ||
9 | import { VideoEditComponent } from './video-edit.component' | ||
8 | 10 | ||
9 | @NgModule({ | 11 | @NgModule({ |
10 | imports: [ | 12 | imports: [ |
@@ -15,18 +17,19 @@ import { SharedModule } from '../../../shared' | |||
15 | ], | 17 | ], |
16 | 18 | ||
17 | declarations: [ | 19 | declarations: [ |
18 | VideoDescriptionComponent | 20 | VideoDescriptionComponent, |
21 | VideoEditComponent | ||
19 | ], | 22 | ], |
20 | 23 | ||
21 | exports: [ | 24 | exports: [ |
22 | TagInputModule, | 25 | TagInputModule, |
23 | TabsModule, | 26 | TabsModule, |
24 | 27 | ||
25 | VideoDescriptionComponent | 28 | VideoDescriptionComponent, |
29 | VideoEditComponent | ||
26 | ], | 30 | ], |
27 | 31 | ||
28 | providers: [ | 32 | providers: [ |
29 | VideoService, | ||
30 | MarkdownService | 33 | MarkdownService |
31 | ] | 34 | ] |
32 | }) | 35 | }) |
diff --git a/client/src/app/videos/+video-edit/video-add.component.html b/client/src/app/videos/+video-edit/video-add.component.html index b4e0f9f7c..a6f2bf6f2 100644 --- a/client/src/app/videos/+video-edit/video-add.component.html +++ b/client/src/app/videos/+video-edit/video-add.component.html | |||
@@ -1,141 +1,53 @@ | |||
1 | <div class="row"> | 1 | <div class="margin-content"> |
2 | <div class="content-padding"> | 2 | <div class="title-page title-page-single"> |
3 | Upload your video | ||
4 | </div> | ||
3 | 5 | ||
4 | <h3>Upload a video</h3> | 6 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> |
5 | 7 | ||
6 | <div *ngIf="error !== undefined" class="alert alert-danger">{{ error }}</div> | 8 | <div *ngIf="!isUploadingVideo" class="upload-video-container"> |
9 | <div class="upload-video"> | ||
10 | <div class="icon icon-upload"></div> | ||
7 | 11 | ||
8 | <form novalidate [formGroup]="form"> | 12 | <div class="button-file"> |
9 | <div class="form-group"> | 13 | <span>Select the file to upload</span> |
10 | <label for="name">Name</label> | 14 | <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange()" /> |
11 | <input | ||
12 | type="text" class="form-control" id="name" | ||
13 | formControlName="name" | ||
14 | > | ||
15 | <div *ngIf="formErrors.name" class="alert alert-danger"> | ||
16 | {{ formErrors.name }} | ||
17 | </div> | ||
18 | </div> | 15 | </div> |
19 | 16 | ||
20 | <div class="form-group"> | 17 | <div class="form-group"> |
21 | <label for="privacy">Privacy</label> | 18 | <select [(ngModel)]="firstStepPrivacyId"> |
22 | <select class="form-control" id="privacy" formControlName="privacy"> | ||
23 | <option></option> | ||
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | 19 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> |
25 | </select> | 20 | </select> |
26 | |||
27 | <div *ngIf="formErrors.privacy" class="alert alert-danger"> | ||
28 | {{ formErrors.privacy }} | ||
29 | </div> | ||
30 | </div> | ||
31 | |||
32 | <div class="form-group"> | ||
33 | <input | ||
34 | type="checkbox" id="nsfw" | ||
35 | formControlName="nsfw" | ||
36 | > | ||
37 | <label for="nsfw">This video contains mature or explicit content</label> | ||
38 | </div> | 21 | </div> |
39 | 22 | ||
40 | <div class="form-group"> | 23 | <div class="form-group"> |
41 | <label for="category">Channel</label> | 24 | <select [(ngModel)]="firstStepChannelId"> |
42 | <select class="form-control" id="channelId" formControlName="channelId"> | ||
43 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> | 25 | <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option> |
44 | </select> | 26 | </select> |
45 | |||
46 | <div *ngIf="formErrors.channelId" class="alert alert-danger"> | ||
47 | {{ formErrors.channelId }} | ||
48 | </div> | ||
49 | </div> | ||
50 | |||
51 | <div class="form-group"> | ||
52 | <label for="category">Category</label> | ||
53 | <select class="form-control" id="category" formControlName="category"> | ||
54 | <option></option> | ||
55 | <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> | ||
56 | </select> | ||
57 | |||
58 | <div *ngIf="formErrors.category" class="alert alert-danger"> | ||
59 | {{ formErrors.category }} | ||
60 | </div> | ||
61 | </div> | ||
62 | |||
63 | <div class="form-group"> | ||
64 | <label for="licence">Licence</label> | ||
65 | <select class="form-control" id="licence" formControlName="licence"> | ||
66 | <option></option> | ||
67 | <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> | ||
68 | </select> | ||
69 | |||
70 | <div *ngIf="formErrors.licence" class="alert alert-danger"> | ||
71 | {{ formErrors.licence }} | ||
72 | </div> | ||
73 | </div> | ||
74 | |||
75 | <div class="form-group"> | ||
76 | <label for="language">Language</label> | ||
77 | <select class="form-control" id="language" formControlName="language"> | ||
78 | <option></option> | ||
79 | <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> | ||
80 | </select> | ||
81 | |||
82 | <div *ngIf="formErrors.language" class="alert alert-danger"> | ||
83 | {{ formErrors.language }} | ||
84 | </div> | ||
85 | </div> | ||
86 | |||
87 | <div class="form-group"> | ||
88 | <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span> | ||
89 | <tag-input | ||
90 | [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | ||
91 | formControlName="tags" maxItems="5" modelAsStrings="true" | ||
92 | ></tag-input> | ||
93 | </div> | ||
94 | |||
95 | <div class="form-group"> | ||
96 | <label for="videofile">File</label> | ||
97 | <div class="btn btn-default btn-file"> | ||
98 | <span>Select the video...</span> | ||
99 | <input #videofileInput type="file" name="videofile" id="videofile" (change)="fileChange($event)" /> | ||
100 | <input type="hidden" name="videofileHidden" formControlName="videofile"/> | ||
101 | </div> | ||
102 | </div> | 27 | </div> |
28 | </div> | ||
29 | </div> | ||
103 | 30 | ||
104 | <div class="file-to-upload"> | 31 | <p-progressBar |
105 | <div class="file" *ngIf="filename"> | 32 | *ngIf="isUploadingVideo" [value]="videoUploadPercents" |
106 | <span class="filename">{{ filename }}</span> | 33 | [ngClass]="{ processing: videoUploadPercents === 100 && videoUploaded === false }" |
107 | <span class="glyphicon glyphicon-remove" (click)="removeFile()"></span> | 34 | ></p-progressBar> |
108 | </div> | ||
109 | </div> | ||
110 | 35 | ||
111 | <div *ngIf="formErrors.videofile" class="alert alert-danger"> | 36 | <!-- Hidden because we need to load the component --> |
112 | {{ formErrors.videofile }} | 37 | <form [hidden]="!isUploadingVideo" novalidate [formGroup]="form"> |
113 | </div> | 38 | <my-video-edit |
39 | [form]="form" [formErrors]="formErrors" | ||
40 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" | ||
41 | ></my-video-edit> | ||
114 | 42 | ||
115 | <div class="form-group"> | ||
116 | <label for="description">Description</label> | ||
117 | <my-video-description formControlName="description"></my-video-description> | ||
118 | 43 | ||
119 | <div *ngIf="formErrors.description" class="alert alert-danger"> | 44 | <div class="submit-container"> |
120 | {{ formErrors.description }} | 45 | <div *ngIf="videoUploaded === false" class="message-submit">Publish will be available when upload is finished</div> |
121 | </div> | ||
122 | </div> | ||
123 | 46 | ||
124 | <div class="progress"> | 47 | <div class="submit-button" (click)="updateSecondStep()" [ngClass]="{ disabled: !form.valid || videoUploaded !== true }"> |
125 | <progressbar [value]="progressPercent" max="100"> | 48 | <span class="icon icon-validate"></span> |
126 | <ng-template [ngIf]="progressPercent === 100"> | 49 | <input type="button" value="Publish" /> |
127 | <span class="glyphicon glyphicon-refresh glyphicon-refresh-animate"></span> | ||
128 | Server is processing the video | ||
129 | </ng-template> | ||
130 | </progressbar> | ||
131 | </div> | 50 | </div> |
132 | 51 | </div> | |
133 | <div class="form-group"> | 52 | </form> |
134 | <input | ||
135 | type="button" value="Upload" class="btn btn-default form-control" | ||
136 | (click)="upload()" | ||
137 | > | ||
138 | </div> | ||
139 | </form> | ||
140 | </div> | ||
141 | </div> | 53 | </div> |
diff --git a/client/src/app/videos/+video-edit/video-add.component.scss b/client/src/app/videos/+video-edit/video-add.component.scss new file mode 100644 index 000000000..39673b4b7 --- /dev/null +++ b/client/src/app/videos/+video-edit/video-add.component.scss | |||
@@ -0,0 +1,96 @@ | |||
1 | .upload-video-container { | ||
2 | border-radius: 3px; | ||
3 | background-color: #F7F7F7; | ||
4 | border: 3px solid #EAEAEA; | ||
5 | width: 100%; | ||
6 | height: 440px; | ||
7 | text-align: center; | ||
8 | margin-top: 40px; | ||
9 | display: flex; | ||
10 | justify-content: center; | ||
11 | align-items: center; | ||
12 | |||
13 | .upload-video { | ||
14 | display: flex; | ||
15 | flex-direction: column; | ||
16 | align-items: center; | ||
17 | |||
18 | .icon.icon-upload { | ||
19 | @include icon(90px); | ||
20 | margin-bottom: 25px; | ||
21 | cursor: default; | ||
22 | |||
23 | background-image: url('../../../assets/images/video/upload.svg'); | ||
24 | } | ||
25 | |||
26 | .button-file { | ||
27 | position: relative; | ||
28 | overflow: hidden; | ||
29 | display: inline-block; | ||
30 | margin-bottom: 70px; | ||
31 | |||
32 | @include peertube-button; | ||
33 | @include orange-button; | ||
34 | |||
35 | input[type=file] { | ||
36 | position: absolute; | ||
37 | top: 0; | ||
38 | right: 0; | ||
39 | min-width: 100%; | ||
40 | min-height: 100%; | ||
41 | font-size: 100px; | ||
42 | text-align: right; | ||
43 | filter: alpha(opacity=0); | ||
44 | opacity: 0; | ||
45 | outline: none; | ||
46 | background: white; | ||
47 | cursor: inherit; | ||
48 | display: block; | ||
49 | } | ||
50 | } | ||
51 | |||
52 | select { | ||
53 | @include peertube-select(auto); | ||
54 | |||
55 | display: inline-block; | ||
56 | font-size: 15px | ||
57 | } | ||
58 | } | ||
59 | } | ||
60 | |||
61 | p-progressBar { | ||
62 | /deep/ .ui-progressbar { | ||
63 | margin-top: 25px !important; | ||
64 | margin-bottom: 40px !important; | ||
65 | font-size: 15px !important; | ||
66 | color: #fff !important; | ||
67 | height: 30px !important; | ||
68 | line-height: 30px !important; | ||
69 | border-radius: 3px !important; | ||
70 | background-color: rgba(11, 204, 41, 0.16) !important; | ||
71 | |||
72 | .ui-progressbar-value { | ||
73 | background-color: #0BCC29 !important; | ||
74 | } | ||
75 | |||
76 | .ui-progressbar-label { | ||
77 | text-align: left; | ||
78 | padding-left: 18px; | ||
79 | margin-top: 0 !important; | ||
80 | } | ||
81 | } | ||
82 | |||
83 | &.processing { | ||
84 | /deep/ .ui-progressbar-label { | ||
85 | // Same color as background to hide "100%" | ||
86 | color: rgba(11, 204, 41, 0.16) !important; | ||
87 | |||
88 | &::before { | ||
89 | content: 'Processing...'; | ||
90 | color: #fff; | ||
91 | } | ||
92 | } | ||
93 | } | ||
94 | } | ||
95 | |||
96 | |||
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 1704cf486..2bbc3de17 100644 --- a/client/src/app/videos/+video-edit/video-add.component.ts +++ b/client/src/app/videos/+video-edit/video-add.component.ts | |||
@@ -1,68 +1,42 @@ | |||
1 | import { HttpEventType, HttpResponse } from '@angular/common/http' | ||
1 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 3 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | import { Router } from '@angular/router' | 4 | import { Router } from '@angular/router' |
4 | |||
5 | import { NotificationsService } from 'angular2-notifications' | 5 | import { NotificationsService } from 'angular2-notifications' |
6 | 6 | import { VideoService } from 'app/shared/video/video.service' | |
7 | import { | ||
8 | FormReactive, | ||
9 | VIDEO_NAME, | ||
10 | VIDEO_CATEGORY, | ||
11 | VIDEO_LICENCE, | ||
12 | VIDEO_LANGUAGE, | ||
13 | VIDEO_DESCRIPTION, | ||
14 | VIDEO_TAGS, | ||
15 | VIDEO_CHANNEL, | ||
16 | VIDEO_FILE, | ||
17 | VIDEO_PRIVACY | ||
18 | } from '../../shared' | ||
19 | import { AuthService, ServerService } from '../../core' | ||
20 | import { VideoService } from '../shared' | ||
21 | import { VideoCreate } from '../../../../../shared' | 7 | import { VideoCreate } from '../../../../../shared' |
22 | import { HttpEventType, HttpResponse } from '@angular/common/http' | 8 | import { VideoPrivacy } from '../../../../../shared/models/videos' |
9 | import { AuthService, ServerService } from '../../core' | ||
10 | import { FormReactive } from '../../shared' | ||
11 | import { ValidatorMessage } from '../../shared/forms/form-validators' | ||
12 | import { VideoEdit } from '../../shared/video/video-edit.model' | ||
23 | 13 | ||
24 | @Component({ | 14 | @Component({ |
25 | selector: 'my-videos-add', | 15 | selector: 'my-videos-add', |
26 | styleUrls: [ './shared/video-edit.component.scss' ], | 16 | templateUrl: './video-add.component.html', |
27 | templateUrl: './video-add.component.html' | 17 | styleUrls: [ |
18 | './shared/video-edit.component.scss', | ||
19 | './video-add.component.scss' | ||
20 | ] | ||
28 | }) | 21 | }) |
29 | 22 | ||
30 | export class VideoAddComponent extends FormReactive implements OnInit { | 23 | export class VideoAddComponent extends FormReactive implements OnInit { |
31 | @ViewChild('videofileInput') videofileInput | 24 | @ViewChild('videofileInput') videofileInput |
32 | 25 | ||
33 | progressPercent = 0 | 26 | isUploadingVideo = false |
34 | tags: string[] = [] | 27 | videoUploaded = false |
35 | videoCategories = [] | 28 | videoUploadPercents = 0 |
36 | videoLicences = [] | 29 | videoUploadedId = 0 |
37 | videoLanguages = [] | ||
38 | videoPrivacies = [] | ||
39 | userVideoChannels = [] | ||
40 | |||
41 | tagValidators = VIDEO_TAGS.VALIDATORS | ||
42 | tagValidatorsMessages = VIDEO_TAGS.MESSAGES | ||
43 | 30 | ||
44 | error: string | 31 | error: string = null |
45 | form: FormGroup | 32 | form: FormGroup |
46 | formErrors = { | 33 | formErrors: { [ id: string ]: string } = {} |
47 | name: '', | 34 | validationMessages: ValidatorMessage = {} |
48 | privacy: '', | 35 | |
49 | category: '', | 36 | userVideoChannels = [] |
50 | licence: '', | 37 | videoPrivacies = [] |
51 | language: '', | 38 | firstStepPrivacyId = 0 |
52 | channelId: '', | 39 | firstStepChannelId = 0 |
53 | description: '', | ||
54 | videofile: '' | ||
55 | } | ||
56 | validationMessages = { | ||
57 | name: VIDEO_NAME.MESSAGES, | ||
58 | privacy: VIDEO_PRIVACY.MESSAGES, | ||
59 | category: VIDEO_CATEGORY.MESSAGES, | ||
60 | licence: VIDEO_LICENCE.MESSAGES, | ||
61 | language: VIDEO_LANGUAGE.MESSAGES, | ||
62 | channelId: VIDEO_CHANNEL.MESSAGES, | ||
63 | description: VIDEO_DESCRIPTION.MESSAGES, | ||
64 | videofile: VIDEO_FILE.MESSAGES | ||
65 | } | ||
66 | 40 | ||
67 | constructor ( | 41 | constructor ( |
68 | private formBuilder: FormBuilder, | 42 | private formBuilder: FormBuilder, |
@@ -75,35 +49,23 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
75 | super() | 49 | super() |
76 | } | 50 | } |
77 | 51 | ||
78 | get filename () { | ||
79 | return this.form.value['videofile'] | ||
80 | } | ||
81 | |||
82 | buildForm () { | 52 | buildForm () { |
83 | this.form = this.formBuilder.group({ | 53 | this.form = this.formBuilder.group({}) |
84 | name: [ '', VIDEO_NAME.VALIDATORS ], | ||
85 | nsfw: [ false ], | ||
86 | privacy: [ '', VIDEO_PRIVACY.VALIDATORS ], | ||
87 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], | ||
88 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], | ||
89 | language: [ '', VIDEO_LANGUAGE.VALIDATORS ], | ||
90 | channelId: [ '', VIDEO_CHANNEL.VALIDATORS ], | ||
91 | description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], | ||
92 | videofile: [ '', VIDEO_FILE.VALIDATORS ], | ||
93 | tags: [ '' ] | ||
94 | }) | ||
95 | |||
96 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) | 54 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) |
97 | } | 55 | } |
98 | 56 | ||
99 | ngOnInit () { | 57 | ngOnInit () { |
100 | this.videoCategories = this.serverService.getVideoCategories() | ||
101 | this.videoLicences = this.serverService.getVideoLicences() | ||
102 | this.videoLanguages = this.serverService.getVideoLanguages() | ||
103 | this.videoPrivacies = this.serverService.getVideoPrivacies() | ||
104 | |||
105 | this.buildForm() | 58 | this.buildForm() |
106 | 59 | ||
60 | this.serverService.videoPrivaciesLoaded | ||
61 | .subscribe( | ||
62 | () => { | ||
63 | this.videoPrivacies = this.serverService.getVideoPrivacies() | ||
64 | |||
65 | // Public by default | ||
66 | this.firstStepPrivacyId = VideoPrivacy.PUBLIC | ||
67 | }) | ||
68 | |||
107 | this.authService.userInformationLoaded | 69 | this.authService.userInformationLoaded |
108 | .subscribe( | 70 | .subscribe( |
109 | () => { | 71 | () => { |
@@ -114,21 +76,13 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
114 | if (Array.isArray(videoChannels) === false) return | 76 | if (Array.isArray(videoChannels) === false) return |
115 | 77 | ||
116 | this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name })) | 78 | this.userVideoChannels = videoChannels.map(v => ({ id: v.id, label: v.name })) |
117 | 79 | this.firstStepChannelId = this.userVideoChannels[0].id | |
118 | this.form.patchValue({ channelId: this.userVideoChannels[0].id }) | ||
119 | } | 80 | } |
120 | ) | 81 | ) |
121 | } | 82 | } |
122 | 83 | ||
123 | // The goal is to keep reactive form validation (required field) | 84 | fileChange () { |
124 | // https://stackoverflow.com/a/44238894 | 85 | this.uploadFirstStep() |
125 | fileChange ($event) { | ||
126 | this.form.controls['videofile'].setValue($event.target.files[0].name) | ||
127 | } | ||
128 | |||
129 | removeFile () { | ||
130 | this.videofileInput.nativeElement.value = '' | ||
131 | this.form.controls['videofile'].setValue('') | ||
132 | } | 86 | } |
133 | 87 | ||
134 | checkForm () { | 88 | checkForm () { |
@@ -137,62 +91,72 @@ export class VideoAddComponent extends FormReactive implements OnInit { | |||
137 | return this.form.valid | 91 | return this.form.valid |
138 | } | 92 | } |
139 | 93 | ||
140 | upload () { | 94 | uploadFirstStep () { |
141 | if (this.checkForm() === false) { | ||
142 | return | ||
143 | } | ||
144 | |||
145 | const formValue: VideoCreate = this.form.value | ||
146 | |||
147 | const name = formValue.name | ||
148 | const privacy = formValue.privacy | ||
149 | const nsfw = formValue.nsfw | ||
150 | const category = formValue.category | ||
151 | const licence = formValue.licence | ||
152 | const language = formValue.language | ||
153 | const channelId = formValue.channelId | ||
154 | const description = formValue.description | ||
155 | const tags = formValue.tags | ||
156 | const videofile = this.videofileInput.nativeElement.files[0] | 95 | const videofile = this.videofileInput.nativeElement.files[0] |
96 | const name = videofile.name.replace(/\.[^/.]+$/, '') | ||
97 | const privacy = this.firstStepPrivacyId.toString() | ||
98 | const nsfw = false | ||
99 | const channelId = this.firstStepChannelId.toString() | ||
157 | 100 | ||
158 | const formData = new FormData() | 101 | const formData = new FormData() |
159 | formData.append('name', name) | 102 | formData.append('name', name) |
160 | formData.append('privacy', privacy.toString()) | 103 | // Put the video "private" -> we wait he validates the second step |
161 | formData.append('category', '' + category) | 104 | formData.append('privacy', VideoPrivacy.PRIVATE.toString()) |
162 | formData.append('nsfw', '' + nsfw) | 105 | formData.append('nsfw', '' + nsfw) |
163 | formData.append('licence', '' + licence) | ||
164 | formData.append('channelId', '' + channelId) | 106 | formData.append('channelId', '' + channelId) |
165 | formData.append('videofile', videofile) | 107 | formData.append('videofile', videofile) |
166 | 108 | ||
167 | // Language is optional | 109 | this.isUploadingVideo = true |
168 | if (language) { | 110 | this.form.patchValue({ |
169 | formData.append('language', '' + language) | 111 | name, |
170 | } | 112 | privacy, |
171 | 113 | nsfw, | |
172 | formData.append('description', description) | 114 | channelId |
173 | 115 | }) | |
174 | for (let i = 0; i < tags.length; i++) { | ||
175 | formData.append(`tags[${i}]`, tags[i]) | ||
176 | } | ||
177 | 116 | ||
178 | this.videoService.uploadVideo(formData).subscribe( | 117 | this.videoService.uploadVideo(formData).subscribe( |
179 | event => { | 118 | event => { |
180 | if (event.type === HttpEventType.UploadProgress) { | 119 | if (event.type === HttpEventType.UploadProgress) { |
181 | this.progressPercent = Math.round(100 * event.loaded / event.total) | 120 | this.videoUploadPercents = Math.round(100 * event.loaded / event.total) |
182 | } else if (event instanceof HttpResponse) { | 121 | } else if (event instanceof HttpResponse) { |
183 | console.log('Video uploaded.') | 122 | console.log('Video uploaded.') |
184 | this.notificationsService.success('Success', 'Video uploaded.') | ||
185 | 123 | ||
186 | // Display all the videos once it's finished | 124 | this.videoUploaded = true |
187 | this.router.navigate([ '/videos/list' ]) | 125 | |
126 | this.videoUploadedId = event.body.video.id | ||
188 | } | 127 | } |
189 | }, | 128 | }, |
190 | 129 | ||
191 | err => { | 130 | err => { |
192 | // Reset progress | 131 | // Reset progress |
193 | this.progressPercent = 0 | 132 | this.videoUploadPercents = 0 |
194 | this.error = err.message | 133 | this.error = err.message |
195 | } | 134 | } |
196 | ) | 135 | ) |
197 | } | 136 | } |
137 | |||
138 | updateSecondStep () { | ||
139 | if (this.checkForm() === false) { | ||
140 | return | ||
141 | } | ||
142 | |||
143 | const video = new VideoEdit() | ||
144 | video.patch(this.form.value) | ||
145 | video.channel = this.firstStepChannelId | ||
146 | video.id = this.videoUploadedId | ||
147 | |||
148 | this.videoService.updateVideo(video) | ||
149 | .subscribe( | ||
150 | () => { | ||
151 | this.notificationsService.success('Success', 'Video published.') | ||
152 | this.router.navigate([ '/videos/watch', video.id ]) | ||
153 | }, | ||
154 | |||
155 | err => { | ||
156 | this.error = 'Cannot update the video.' | ||
157 | console.error(err) | ||
158 | } | ||
159 | ) | ||
160 | |||
161 | } | ||
198 | } | 162 | } |
diff --git a/client/src/app/videos/+video-edit/video-add.module.ts b/client/src/app/videos/+video-edit/video-add.module.ts index f58d12dac..1efecdf4d 100644 --- a/client/src/app/videos/+video-edit/video-add.module.ts +++ b/client/src/app/videos/+video-edit/video-add.module.ts | |||
@@ -1,4 +1,5 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { ProgressBarModule } from 'primeng/primeng' | ||
2 | import { SharedModule } from '../../shared' | 3 | import { SharedModule } from '../../shared' |
3 | import { VideoEditModule } from './shared/video-edit.module' | 4 | import { VideoEditModule } from './shared/video-edit.module' |
4 | import { VideoAddRoutingModule } from './video-add-routing.module' | 5 | import { VideoAddRoutingModule } from './video-add-routing.module' |
@@ -8,7 +9,8 @@ import { VideoAddComponent } from './video-add.component' | |||
8 | imports: [ | 9 | imports: [ |
9 | VideoAddRoutingModule, | 10 | VideoAddRoutingModule, |
10 | VideoEditModule, | 11 | VideoEditModule, |
11 | SharedModule | 12 | SharedModule, |
13 | ProgressBarModule | ||
12 | ], | 14 | ], |
13 | 15 | ||
14 | declarations: [ | 16 | declarations: [ |
diff --git a/client/src/app/videos/+video-edit/video-update.component.html b/client/src/app/videos/+video-edit/video-update.component.html index b9c6139b2..261b8a130 100644 --- a/client/src/app/videos/+video-edit/video-update.component.html +++ b/client/src/app/videos/+video-edit/video-update.component.html | |||
@@ -1,101 +1,20 @@ | |||
1 | <div class="row"> | 1 | <div class="margin-content"> |
2 | <div class="content-padding"> | 2 | <div class="title-page title-page-single"> |
3 | 3 | Update {{ video?.name }} | |
4 | <h3>Update {{ video?.name }}</h3> | 4 | </div> |
5 | |||
6 | <div *ngIf="error" class="alert alert-danger">{{ error }}</div> | ||
7 | 5 | ||
8 | <form novalidate [formGroup]="form"> | 6 | <form novalidate [formGroup]="form"> |
9 | <div class="form-group"> | ||
10 | <label for="name">Name</label> | ||
11 | <input | ||
12 | type="text" class="form-control" id="name" | ||
13 | formControlName="name" | ||
14 | > | ||
15 | <div *ngIf="formErrors.name" class="alert alert-danger"> | ||
16 | {{ formErrors.name }} | ||
17 | </div> | ||
18 | </div> | ||
19 | |||
20 | <div class="form-group"> | ||
21 | <label for="privacy">Privacy</label> | ||
22 | <select class="form-control" id="privacy" formControlName="privacy"> | ||
23 | <option></option> | ||
24 | <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> | ||
25 | </select> | ||
26 | |||
27 | <div *ngIf="formErrors.privacy" class="alert alert-danger"> | ||
28 | {{ formErrors.privacy }} | ||
29 | </div> | ||
30 | </div> | ||
31 | |||
32 | <div class="form-group"> | ||
33 | <input | ||
34 | type="checkbox" id="nsfw" | ||
35 | formControlName="nsfw" | ||
36 | > | ||
37 | <label for="nsfw">This video contains mature or explicit content</label> | ||
38 | </div> | ||
39 | |||
40 | <div class="form-group"> | ||
41 | <label for="category">Category</label> | ||
42 | <select class="form-control" id="category" formControlName="category"> | ||
43 | <option></option> | ||
44 | <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option> | ||
45 | </select> | ||
46 | |||
47 | <div *ngIf="formErrors.category" class="alert alert-danger"> | ||
48 | {{ formErrors.category }} | ||
49 | </div> | ||
50 | </div> | ||
51 | |||
52 | <div class="form-group"> | ||
53 | <label for="licence">Licence</label> | ||
54 | <select class="form-control" id="licence" formControlName="licence"> | ||
55 | <option></option> | ||
56 | <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option> | ||
57 | </select> | ||
58 | |||
59 | <div *ngIf="formErrors.licence" class="alert alert-danger"> | ||
60 | {{ formErrors.licence }} | ||
61 | </div> | ||
62 | </div> | ||
63 | |||
64 | <div class="form-group"> | ||
65 | <label for="language">Language</label> | ||
66 | <select class="form-control" id="language" formControlName="language"> | ||
67 | <option></option> | ||
68 | <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> | ||
69 | </select> | ||
70 | |||
71 | <div *ngIf="formErrors.language" class="alert alert-danger"> | ||
72 | {{ formErrors.language }} | ||
73 | </div> | ||
74 | </div> | ||
75 | |||
76 | <div class="form-group"> | ||
77 | <label class="label-tags">Tags</label> <span class="little-information">(press enter to add the tag)</span> | ||
78 | <tag-input | ||
79 | [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" | ||
80 | formControlName="tags" maxItems="5" modelAsStrings="true" | ||
81 | ></tag-input> | ||
82 | </div> | ||
83 | 7 | ||
84 | <div class="form-group"> | 8 | <my-video-edit |
85 | <label for="description">Description</label> | 9 | [form]="form" [formErrors]="formErrors" |
86 | <my-video-description formControlName="description"></my-video-description> | 10 | [validationMessages]="validationMessages" [videoPrivacies]="videoPrivacies" |
11 | ></my-video-edit> | ||
87 | 12 | ||
88 | <div *ngIf="formErrors.description" class="alert alert-danger"> | 13 | <div class="submit-container"> |
89 | {{ formErrors.description }} | 14 | <div class="submit-button" (click)="update()" [ngClass]="{ disabled: !form.valid }"> |
15 | <span class="icon icon-validate"></span> | ||
16 | <input type="button" value="Update" /> | ||
90 | </div> | 17 | </div> |
91 | </div> | 18 | </div> |
92 | |||
93 | <div class="form-group"> | ||
94 | <input | ||
95 | type="button" value="Update" class="btn btn-default form-control" | ||
96 | (click)="update()" | ||
97 | > | ||
98 | </div> | ||
99 | </form> | 19 | </form> |
100 | </div> | ||
101 | </div> | 20 | </div> |
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 0e966cb50..d1da8b6d8 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -1,23 +1,14 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { FormBuilder, FormGroup } from '@angular/forms' | 2 | import { FormBuilder, FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import 'rxjs/add/observable/forkJoin' | ||
5 | |||
6 | import { NotificationsService } from 'angular2-notifications' | 4 | import { NotificationsService } from 'angular2-notifications' |
7 | 5 | import 'rxjs/add/observable/forkJoin' | |
8 | import { ServerService } from '../../core' | ||
9 | import { | ||
10 | FormReactive, | ||
11 | VIDEO_NAME, | ||
12 | VIDEO_CATEGORY, | ||
13 | VIDEO_LICENCE, | ||
14 | VIDEO_LANGUAGE, | ||
15 | VIDEO_DESCRIPTION, | ||
16 | VIDEO_TAGS, | ||
17 | VIDEO_PRIVACY | ||
18 | } from '../../shared' | ||
19 | import { VideoEdit, VideoService } from '../shared' | ||
20 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' | 6 | import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy.enum' |
7 | import { ServerService } from '../../core' | ||
8 | import { FormReactive } from '../../shared' | ||
9 | import { ValidatorMessage } from '../../shared/forms/form-validators' | ||
10 | import { VideoEdit } from '../../shared/video/video-edit.model' | ||
11 | import { VideoService } from '../../shared/video/video.service' | ||
21 | 12 | ||
22 | @Component({ | 13 | @Component({ |
23 | selector: 'my-videos-update', | 14 | selector: 'my-videos-update', |
@@ -26,34 +17,13 @@ import { VideoPrivacy } from '../../../../../shared/models/videos/video-privacy. | |||
26 | }) | 17 | }) |
27 | 18 | ||
28 | export class VideoUpdateComponent extends FormReactive implements OnInit { | 19 | export class VideoUpdateComponent extends FormReactive implements OnInit { |
29 | tags: string[] = [] | ||
30 | videoCategories = [] | ||
31 | videoLicences = [] | ||
32 | videoLanguages = [] | ||
33 | videoPrivacies = [] | ||
34 | video: VideoEdit | 20 | video: VideoEdit |
35 | 21 | ||
36 | tagValidators = VIDEO_TAGS.VALIDATORS | ||
37 | tagValidatorsMessages = VIDEO_TAGS.MESSAGES | ||
38 | |||
39 | error: string = null | 22 | error: string = null |
40 | form: FormGroup | 23 | form: FormGroup |
41 | formErrors = { | 24 | formErrors: { [ id: string ]: string } = {} |
42 | name: '', | 25 | validationMessages: ValidatorMessage = {} |
43 | privacy: '', | 26 | videoPrivacies = [] |
44 | category: '', | ||
45 | licence: '', | ||
46 | language: '', | ||
47 | description: '' | ||
48 | } | ||
49 | validationMessages = { | ||
50 | name: VIDEO_NAME.MESSAGES, | ||
51 | privacy: VIDEO_PRIVACY.MESSAGES, | ||
52 | category: VIDEO_CATEGORY.MESSAGES, | ||
53 | licence: VIDEO_LICENCE.MESSAGES, | ||
54 | language: VIDEO_LANGUAGE.MESSAGES, | ||
55 | description: VIDEO_DESCRIPTION.MESSAGES | ||
56 | } | ||
57 | 27 | ||
58 | fileError = '' | 28 | fileError = '' |
59 | 29 | ||
@@ -69,30 +39,16 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
69 | } | 39 | } |
70 | 40 | ||
71 | buildForm () { | 41 | buildForm () { |
72 | this.form = this.formBuilder.group({ | 42 | this.form = this.formBuilder.group({}) |
73 | name: [ '', VIDEO_NAME.VALIDATORS ], | ||
74 | privacy: [ '', VIDEO_PRIVACY.VALIDATORS ], | ||
75 | nsfw: [ false ], | ||
76 | category: [ '', VIDEO_CATEGORY.VALIDATORS ], | ||
77 | licence: [ '', VIDEO_LICENCE.VALIDATORS ], | ||
78 | language: [ '', VIDEO_LANGUAGE.VALIDATORS ], | ||
79 | description: [ '', VIDEO_DESCRIPTION.VALIDATORS ], | ||
80 | tags: [ '' ] | ||
81 | }) | ||
82 | |||
83 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) | 43 | this.form.valueChanges.subscribe(data => this.onValueChanged(data)) |
84 | } | 44 | } |
85 | 45 | ||
86 | ngOnInit () { | 46 | ngOnInit () { |
87 | this.buildForm() | 47 | this.buildForm() |
88 | 48 | ||
89 | this.videoCategories = this.serverService.getVideoCategories() | ||
90 | this.videoLicences = this.serverService.getVideoLicences() | ||
91 | this.videoLanguages = this.serverService.getVideoLanguages() | ||
92 | this.videoPrivacies = this.serverService.getVideoPrivacies() | 49 | this.videoPrivacies = this.serverService.getVideoPrivacies() |
93 | 50 | ||
94 | const uuid: string = this.route.snapshot.params['uuid'] | 51 | const uuid: string = this.route.snapshot.params['uuid'] |
95 | |||
96 | this.videoService.getVideo(uuid) | 52 | this.videoService.getVideo(uuid) |
97 | .switchMap(video => { | 53 | .switchMap(video => { |
98 | return this.videoService | 54 | return this.videoService |
@@ -104,7 +60,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
104 | video => { | 60 | video => { |
105 | this.video = new VideoEdit(video) | 61 | this.video = new VideoEdit(video) |
106 | 62 | ||
107 | // We cannot set private a video that was not private anymore | 63 | // We cannot set private a video that was not private |
108 | if (video.privacy !== VideoPrivacy.PRIVATE) { | 64 | if (video.privacy !== VideoPrivacy.PRIVATE) { |
109 | const newVideoPrivacies = [] | 65 | const newVideoPrivacies = [] |
110 | for (const p of this.videoPrivacies) { | 66 | for (const p of this.videoPrivacies) { |