aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2018-02-16 16:35:32 +0100
committerChocobozzz <me@florianbigard.com>2018-02-16 16:35:32 +0100
commit6de36768980ef6063b8fcd730b59fa685dd2b99c (patch)
treea8e5a87b14013b33bf2d306552a380c407a1551d
parentb6a4fd6b099b3363ac59c06cfd81b54e1356d8bc (diff)
downloadPeerTube-6de36768980ef6063b8fcd730b59fa685dd2b99c.tar.gz
PeerTube-6de36768980ef6063b8fcd730b59fa685dd2b99c.tar.zst
PeerTube-6de36768980ef6063b8fcd730b59fa685dd2b99c.zip
Add ability to update thumbnail and preview on client
-rw-r--r--client/src/app/+admin/follows/follows.component.html2
-rw-r--r--client/src/app/core/server/server.service.ts4
-rw-r--r--client/src/app/shared/forms/form-validators/video.ts5
-rw-r--r--client/src/app/shared/forms/markdown-textarea.component.html2
-rw-r--r--client/src/app/shared/misc/utils.ts24
-rw-r--r--client/src/app/shared/shared.module.ts5
-rw-r--r--client/src/app/shared/video/video-edit.model.ts6
-rw-r--r--client/src/app/shared/video/video-thumbnail.component.html2
-rw-r--r--client/src/app/shared/video/video.service.ts17
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.html224
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.scss12
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.component.ts7
-rw-r--r--client/src/app/videos/+video-edit/shared/video-edit.module.ts4
-rw-r--r--client/src/app/videos/+video-edit/video-update.component.ts26
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.html2
-rw-r--r--client/src/app/videos/+video-watch/video-watch.component.ts6
-rw-r--r--client/src/sass/application.scss55
-rw-r--r--server/controllers/api/config.ts6
-rw-r--r--shared/models/config/server-config.model.ts6
-rw-r--r--shared/models/videos/video-update.model.ts2
20 files changed, 277 insertions, 140 deletions
diff --git a/client/src/app/+admin/follows/follows.component.html b/client/src/app/+admin/follows/follows.component.html
index 1baba5a4d..d3d748622 100644
--- a/client/src/app/+admin/follows/follows.component.html
+++ b/client/src/app/+admin/follows/follows.component.html
@@ -10,6 +10,4 @@
10 </tabset> 10 </tabset>
11</div> 11</div>
12 12
13
14
15<router-outlet></router-outlet> 13<router-outlet></router-outlet>
diff --git a/client/src/app/core/server/server.service.ts b/client/src/app/core/server/server.service.ts
index 65714fd05..553ad8af6 100644
--- a/client/src/app/core/server/server.service.ts
+++ b/client/src/app/core/server/server.service.ts
@@ -35,6 +35,10 @@ export class ServerService {
35 } 35 }
36 }, 36 },
37 video: { 37 video: {
38 image: {
39 size: { max: 0 },
40 extensions: []
41 },
38 file: { 42 file: {
39 extensions: [] 43 extensions: []
40 } 44 }
diff --git a/client/src/app/shared/forms/form-validators/video.ts b/client/src/app/shared/forms/form-validators/video.ts
index 500b5bc5f..34a237a12 100644
--- a/client/src/app/shared/forms/form-validators/video.ts
+++ b/client/src/app/shared/forms/form-validators/video.ts
@@ -31,6 +31,11 @@ export const VIDEO_LANGUAGE = {
31 MESSAGES: {} 31 MESSAGES: {}
32} 32}
33 33
34export const VIDEO_IMAGE = {
35 VALIDATORS: [ ],
36 MESSAGES: {}
37}
38
34export const VIDEO_CHANNEL = { 39export const VIDEO_CHANNEL = {
35 VALIDATORS: [ Validators.required ], 40 VALIDATORS: [ Validators.required ],
36 MESSAGES: { 41 MESSAGES: {
diff --git a/client/src/app/shared/forms/markdown-textarea.component.html b/client/src/app/shared/forms/markdown-textarea.component.html
index d2d4cf95c..e8c5ded5b 100644
--- a/client/src/app/shared/forms/markdown-textarea.component.html
+++ b/client/src/app/shared/forms/markdown-textarea.component.html
@@ -5,7 +5,7 @@
5 id="description" name="description"> 5 id="description" name="description">
6 </textarea> 6 </textarea>
7 7
8 <tabset *ngIf="arePreviewsDisplayed()" #staticTabs class="previews"> 8 <tabset *ngIf="arePreviewsDisplayed()" class="previews">
9 <tab *ngIf="truncate !== undefined" heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab> 9 <tab *ngIf="truncate !== undefined" heading="Truncated description preview" [innerHTML]="truncatedDescriptionHTML"></tab>
10 <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab> 10 <tab heading="Complete description preview" [innerHTML]="descriptionHTML"></tab>
11 </tabset> 11 </tabset>
diff --git a/client/src/app/shared/misc/utils.ts b/client/src/app/shared/misc/utils.ts
index e6a697098..e2e4c5b36 100644
--- a/client/src/app/shared/misc/utils.ts
+++ b/client/src/app/shared/misc/utils.ts
@@ -67,6 +67,27 @@ function isInMobileView () {
67 return window.innerWidth < 500 67 return window.innerWidth < 500
68} 68}
69 69
70// Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34
71function objectToFormData (obj: any, form?: FormData, namespace?: string) {
72 let fd = form || new FormData()
73 let formKey
74
75 for (let key of Object.keys(obj)) {
76 if (namespace) formKey = `${namespace}[${key}]`
77 else formKey = key
78
79 if (obj[key] === undefined) continue
80
81 if (typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) {
82 objectToFormData(obj[ key ], fd, key)
83 } else {
84 fd.append(formKey, obj[ key ])
85 }
86 }
87
88 return fd
89}
90
70export { 91export {
71 viewportHeight, 92 viewportHeight,
72 getParameterByName, 93 getParameterByName,
@@ -75,5 +96,6 @@ export {
75 dateToHuman, 96 dateToHuman,
76 isInSmallView, 97 isInSmallView,
77 isInMobileView, 98 isInMobileView,
78 immutableAssign 99 immutableAssign,
100 objectToFormData
79} 101}
diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts
index 330a0ba84..2a9426479 100644
--- a/client/src/app/shared/shared.module.ts
+++ b/client/src/app/shared/shared.module.ts
@@ -40,10 +40,10 @@ import { VideoService } from './video/video.service'
40 40
41 BsDropdownModule.forRoot(), 41 BsDropdownModule.forRoot(),
42 ModalModule.forRoot(), 42 ModalModule.forRoot(),
43 TabsModule.forRoot(),
43 44
44 PrimeSharedModule, 45 PrimeSharedModule,
45 NgPipesModule, 46 NgPipesModule
46 TabsModule.forRoot()
47 ], 47 ],
48 48
49 declarations: [ 49 declarations: [
@@ -69,6 +69,7 @@ import { VideoService } from './video/video.service'
69 69
70 BsDropdownModule, 70 BsDropdownModule,
71 ModalModule, 71 ModalModule,
72 TabsModule,
72 PrimeSharedModule, 73 PrimeSharedModule,
73 BytesPipe, 74 BytesPipe,
74 KeysPipe, 75 KeysPipe,
diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts
index b1c772217..c39252f46 100644
--- a/client/src/app/shared/video/video-edit.model.ts
+++ b/client/src/app/shared/video/video-edit.model.ts
@@ -12,6 +12,10 @@ export class VideoEdit {
12 commentsEnabled: boolean 12 commentsEnabled: boolean
13 channel: number 13 channel: number
14 privacy: VideoPrivacy 14 privacy: VideoPrivacy
15 thumbnailfile?: any
16 previewfile?: any
17 thumbnailUrl: string
18 previewUrl: string
15 uuid?: string 19 uuid?: string
16 id?: number 20 id?: number
17 21
@@ -29,6 +33,8 @@ export class VideoEdit {
29 this.commentsEnabled = videoDetails.commentsEnabled 33 this.commentsEnabled = videoDetails.commentsEnabled
30 this.channel = videoDetails.channel.id 34 this.channel = videoDetails.channel.id
31 this.privacy = videoDetails.privacy 35 this.privacy = videoDetails.privacy
36 this.thumbnailUrl = videoDetails.thumbnailUrl
37 this.previewUrl = videoDetails.previewUrl
32 } 38 }
33 } 39 }
34 40
diff --git a/client/src/app/shared/video/video-thumbnail.component.html b/client/src/app/shared/video/video-thumbnail.component.html
index 8acfb3c41..4604d10e2 100644
--- a/client/src/app/shared/video/video-thumbnail.component.html
+++ b/client/src/app/shared/video/video-thumbnail.component.html
@@ -2,7 +2,7 @@
2 [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" 2 [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name"
3class="video-thumbnail" 3class="video-thumbnail"
4> 4>
5<img [attr.src]="getImageUrl()" alt="video thumbnail" [ngClass]="{ 'blur-filter': nsfw }" /> 5<img [attr.src]="getImageUrl()" [ngClass]="{ 'blur-filter': nsfw }" />
6 6
7<div class="video-thumbnail-overlay"> 7<div class="video-thumbnail-overlay">
8 {{ video.durationLabel }} 8 {{ video.durationLabel }}
diff --git a/client/src/app/shared/video/video.service.ts b/client/src/app/shared/video/video.service.ts
index 01d32176b..2e7138cd1 100644
--- a/client/src/app/shared/video/video.service.ts
+++ b/client/src/app/shared/video/video.service.ts
@@ -18,6 +18,7 @@ import { SortField } from './sort-field.type'
18import { VideoDetails } from './video-details.model' 18import { VideoDetails } from './video-details.model'
19import { VideoEdit } from './video-edit.model' 19import { VideoEdit } from './video-edit.model'
20import { Video } from './video.model' 20import { Video } from './video.model'
21import { objectToFormData } from '@app/shared/misc/utils'
21 22
22@Injectable() 23@Injectable()
23export class VideoService { 24export class VideoService {
@@ -46,10 +47,10 @@ export class VideoService {
46 } 47 }
47 48
48 updateVideo (video: VideoEdit) { 49 updateVideo (video: VideoEdit) {
49 const language = video.language || null 50 const language = video.language || undefined
50 const licence = video.licence || null 51 const licence = video.licence || undefined
51 const category = video.category || null 52 const category = video.category || undefined
52 const description = video.description || null 53 const description = video.description || undefined
53 54
54 const body: VideoUpdate = { 55 const body: VideoUpdate = {
55 name: video.name, 56 name: video.name,
@@ -60,10 +61,14 @@ export class VideoService {
60 privacy: video.privacy, 61 privacy: video.privacy,
61 tags: video.tags, 62 tags: video.tags,
62 nsfw: video.nsfw, 63 nsfw: video.nsfw,
63 commentsEnabled: video.commentsEnabled 64 commentsEnabled: video.commentsEnabled,
65 thumbnailfile: video.thumbnailfile,
66 previewfile: video.previewfile
64 } 67 }
65 68
66 return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body) 69 const data = objectToFormData(body)
70
71 return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, data)
67 .map(this.restExtractor.extractDataBool) 72 .map(this.restExtractor.extractDataBool)
68 .catch(this.restExtractor.handleError) 73 .catch(this.restExtractor.handleError)
69 } 74 }
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
index d031825bd..899249778 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.html
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.html
@@ -1,109 +1,133 @@
1<div class="video-edit row" [formGroup]="form"> 1<div class="video-edit row" [formGroup]="form">
2 2 <tabset class="root-tabset bootstrap">
3 <div class="col-md-8"> 3
4 <div class="form-group"> 4 <tab heading="Basic info">
5 <label for="name">Title</label> 5 <div class="col-md-8">
6 <input type="text" id="name" formControlName="name" /> 6 <div class="form-group">
7 <div *ngIf="formErrors.name" class="form-error"> 7 <label for="name">Title</label>
8 {{ formErrors.name }} 8 <input type="text" id="name" formControlName="name" />
9 </div> 9 <div *ngIf="formErrors.name" class="form-error">
10 </div> 10 {{ formErrors.name }}
11 11 </div>
12 <div class="form-group"> 12 </div>
13 <label class="label-tags">Tags</label> <span>(press Enter to add)</span> 13
14 <tag-input 14 <div class="form-group">
15 [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages" 15 <label class="label-tags">Tags</label> <span>(press Enter to add)</span>
16 formControlName="tags" maxItems="5" modelAsStrings="true" 16 <tag-input
17 ></tag-input> 17 [ngModel]="tags" [validators]="tagValidators" [errorMessages]="tagValidatorsMessages"
18 </div> 18 formControlName="tags" maxItems="5" modelAsStrings="true"
19 19 ></tag-input>
20 <div class="form-group"> 20 </div>
21 <label for="description">Description</label> 21
22 <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea> 22 <div class="form-group">
23 23 <label for="description">Description</label>
24 <div *ngIf="formErrors.description" class="form-error"> 24 <my-markdown-textarea truncate="250" formControlName="description"></my-markdown-textarea>
25 {{ formErrors.description }} 25
26 </div> 26 <div *ngIf="formErrors.description" class="form-error">
27 </div> 27 {{ formErrors.description }}
28 </div> 28 </div>
29 29 </div>
30 <div class="col-md-4">
31 <div class="form-group">
32 <label>Channel</label>
33 <div class="peertube-select-disabled-container">
34 <select formControlName="channelId">
35 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
36 </select>
37 </div>
38 </div>
39
40 <div class="form-group">
41 <label for="category">Category</label>
42 <div class="peertube-select-container">
43 <select id="category" formControlName="category">
44 <option></option>
45 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
46 </select>
47 </div>
48
49 <div *ngIf="formErrors.category" class="form-error">
50 {{ formErrors.category }}
51 </div>
52 </div>
53
54 <div class="form-group">
55 <label for="licence">Licence</label>
56 <div class="peertube-select-container">
57 <select id="licence" formControlName="licence">
58 <option></option>
59 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
60 </select>
61 </div> 30 </div>
62 31
63 <div *ngIf="formErrors.licence" class="form-error"> 32 <div class="col-md-4">
64 {{ formErrors.licence }} 33 <div class="form-group">
65 </div> 34 <label>Channel</label>
66 </div> 35 <div class="peertube-select-disabled-container">
67 36 <select formControlName="channelId">
68 <div class="form-group"> 37 <option *ngFor="let channel of userVideoChannels" [value]="channel.id">{{ channel.label }}</option>
69 <label for="language">Language</label> 38 </select>
70 <div class="peertube-select-container"> 39 </div>
71 <select id="language" formControlName="language"> 40 </div>
72 <option></option> 41
73 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option> 42 <div class="form-group">
74 </select> 43 <label for="category">Category</label>
75 </div> 44 <div class="peertube-select-container">
45 <select id="category" formControlName="category">
46 <option></option>
47 <option *ngFor="let category of videoCategories" [value]="category.id">{{ category.label }}</option>
48 </select>
49 </div>
50
51 <div *ngIf="formErrors.category" class="form-error">
52 {{ formErrors.category }}
53 </div>
54 </div>
55
56 <div class="form-group">
57 <label for="licence">Licence</label>
58 <div class="peertube-select-container">
59 <select id="licence" formControlName="licence">
60 <option></option>
61 <option *ngFor="let licence of videoLicences" [value]="licence.id">{{ licence.label }}</option>
62 </select>
63 </div>
64
65 <div *ngIf="formErrors.licence" class="form-error">
66 {{ formErrors.licence }}
67 </div>
68 </div>
69
70 <div class="form-group">
71 <label for="language">Language</label>
72 <div class="peertube-select-container">
73 <select id="language" formControlName="language">
74 <option></option>
75 <option *ngFor="let language of videoLanguages" [value]="language.id">{{ language.label }}</option>
76 </select>
77 </div>
78
79 <div *ngIf="formErrors.language" class="form-error">
80 {{ formErrors.language }}
81 </div>
82 </div>
83
84 <div class="form-group">
85 <label for="privacy">Privacy</label>
86 <div class="peertube-select-container">
87 <select id="privacy" formControlName="privacy">
88 <option></option>
89 <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option>
90 </select>
91 </div>
92
93 <div *ngIf="formErrors.privacy" class="form-error">
94 {{ formErrors.privacy }}
95 </div>
96 </div>
97
98 <div class="form-group form-group-checkbox">
99 <input type="checkbox" id="nsfw" formControlName="nsfw" />
100 <label for="nsfw"></label>
101 <label for="nsfw">This video contains mature or explicit content</label>
102 </div>
103
104 <div class="form-group form-group-checkbox">
105 <input type="checkbox" id="commentsEnabled" formControlName="commentsEnabled" />
106 <label for="commentsEnabled"></label>
107 <label for="commentsEnabled">Enable video comments</label>
108 </div>
76 109
77 <div *ngIf="formErrors.language" class="form-error">
78 {{ formErrors.language }}
79 </div> 110 </div>
80 </div> 111 </tab>
81 112
82 <div class="form-group"> 113 <tab heading="Advanced settings">
83 <label for="privacy">Privacy</label> 114 <div class="col-md-12">
84 <div class="peertube-select-container"> 115 <div class="form-group">
85 <select id="privacy" formControlName="privacy"> 116 <my-video-image
86 <option></option> 117 inputLabel="Upload thumbnail" inputName="thumbnailfile" formControlName="thumbnailfile"
87 <option *ngFor="let privacy of videoPrivacies" [value]="privacy.id">{{ privacy.label }}</option> 118 previewWidth="200px" previewHeight="110px"
88 </select> 119 ></my-video-image>
120 </div>
121
122 <div class="form-group">
123 <my-video-image
124 inputLabel="Upload preview" inputName="previewfile" formControlName="previewfile"
125 previewWidth="360px" previewHeight="200px"
126 ></my-video-image>
127 </div>
89 </div> 128 </div>
129 </tab>
90 130
91 <div *ngIf="formErrors.privacy" class="form-error"> 131 </tabset>
92 {{ formErrors.privacy }}
93 </div>
94 </div>
95
96 <div class="form-group form-group-checkbox">
97 <input type="checkbox" id="nsfw" formControlName="nsfw" />
98 <label for="nsfw"></label>
99 <label for="nsfw">This video contains mature or explicit content</label>
100 </div>
101
102 <div class="form-group form-group-checkbox">
103 <input type="checkbox" id="commentsEnabled" formControlName="commentsEnabled" />
104 <label for="commentsEnabled"></label>
105 <label for="commentsEnabled">Enable video comments</label>
106 </div>
107 132
108 </div>
109</div> 133</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 1df9d4006..f78336fa8 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
@@ -47,6 +47,18 @@
47 .label-tags + span { 47 .label-tags + span {
48 font-size: 15px; 48 font-size: 15px;
49 } 49 }
50
51 .root-tabset /deep/ > .nav {
52 margin-left: 15px;
53 margin-bottom: 15px;
54
55 .nav-link {
56 display: flex !important;
57 align-items: center;
58 height: 30px !important;
59 padding: 0 15px !important;
60 }
61 }
50} 62}
51 63
52.submit-container { 64.submit-container {
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
index 2b307d5fa..85e5cc3f5 100644
--- a/client/src/app/videos/+video-edit/shared/video-edit.component.ts
+++ b/client/src/app/videos/+video-edit/shared/video-edit.component.ts
@@ -1,6 +1,7 @@
1import { Component, Input, OnInit } from '@angular/core' 1import { Component, Input, OnInit } from '@angular/core'
2import { FormBuilder, FormControl, FormGroup } from '@angular/forms' 2import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
3import { ActivatedRoute, Router } from '@angular/router' 3import { ActivatedRoute, Router } from '@angular/router'
4import { VIDEO_IMAGE } from '@app/shared'
4import { NotificationsService } from 'angular2-notifications' 5import { NotificationsService } from 'angular2-notifications'
5import 'rxjs/add/observable/forkJoin' 6import 'rxjs/add/observable/forkJoin'
6import { ServerService } from '../../../core/server' 7import { ServerService } from '../../../core/server'
@@ -57,6 +58,8 @@ export class VideoEditComponent implements OnInit {
57 this.formErrors['licence'] = '' 58 this.formErrors['licence'] = ''
58 this.formErrors['language'] = '' 59 this.formErrors['language'] = ''
59 this.formErrors['description'] = '' 60 this.formErrors['description'] = ''
61 this.formErrors['thumbnailfile'] = ''
62 this.formErrors['previewfile'] = ''
60 63
61 this.validationMessages['name'] = VIDEO_NAME.MESSAGES 64 this.validationMessages['name'] = VIDEO_NAME.MESSAGES
62 this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES 65 this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES
@@ -65,6 +68,8 @@ export class VideoEditComponent implements OnInit {
65 this.validationMessages['licence'] = VIDEO_LICENCE.MESSAGES 68 this.validationMessages['licence'] = VIDEO_LICENCE.MESSAGES
66 this.validationMessages['language'] = VIDEO_LANGUAGE.MESSAGES 69 this.validationMessages['language'] = VIDEO_LANGUAGE.MESSAGES
67 this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES 70 this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES
71 this.validationMessages['thumbnailfile'] = VIDEO_IMAGE.MESSAGES
72 this.validationMessages['previewfile'] = VIDEO_IMAGE.MESSAGES
68 73
69 this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS)) 74 this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS))
70 this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS)) 75 this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS))
@@ -76,6 +81,8 @@ export class VideoEditComponent implements OnInit {
76 this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS)) 81 this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS))
77 this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS)) 82 this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS))
78 this.form.addControl('tags', new FormControl('')) 83 this.form.addControl('tags', new FormControl(''))
84 this.form.addControl('thumbnailfile', new FormControl(''))
85 this.form.addControl('previewfile', new FormControl(''))
79 } 86 }
80 87
81 ngOnInit () { 88 ngOnInit () {
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 098a71ae6..1b82281bf 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
@@ -1,4 +1,5 @@
1import { NgModule } from '@angular/core' 1import { NgModule } from '@angular/core'
2import { VideoImageComponent } from '@app/videos/+video-edit/shared/video-image.component'
2import { TabsModule } from 'ngx-bootstrap/tabs' 3import { TabsModule } from 'ngx-bootstrap/tabs'
3import { TagInputModule } from 'ngx-chips' 4import { TagInputModule } from 'ngx-chips'
4import { SharedModule } from '../../../shared' 5import { SharedModule } from '../../../shared'
@@ -12,7 +13,8 @@ import { VideoEditComponent } from './video-edit.component'
12 ], 13 ],
13 14
14 declarations: [ 15 declarations: [
15 VideoEditComponent 16 VideoEditComponent,
17 VideoImageComponent
16 ], 18 ],
17 19
18 exports: [ 20 exports: [
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 7f41b56d8..ad6452835 100644
--- a/client/src/app/videos/+video-edit/video-update.component.ts
+++ b/client/src/app/videos/+video-edit/video-update.component.ts
@@ -48,11 +48,10 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
48 this.buildForm() 48 this.buildForm()
49 49
50 this.serverService.videoPrivaciesLoaded 50 this.serverService.videoPrivaciesLoaded
51 .subscribe( 51 .subscribe(() => this.videoPrivacies = this.serverService.getVideoPrivacies())
52 () => this.videoPrivacies = this.serverService.getVideoPrivacies()
53 )
54 52
55 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) 53 populateAsyncUserVideoChannels(this.authService, this.userVideoChannels)
54 .catch(err => console.error('Cannot populate async user video channels.', err))
56 55
57 const uuid: string = this.route.snapshot.params['uuid'] 56 const uuid: string = this.route.snapshot.params['uuid']
58 this.videoService.getVideo(uuid) 57 this.videoService.getVideo(uuid)
@@ -116,5 +115,26 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
116 115
117 private hydrateFormFromVideo () { 116 private hydrateFormFromVideo () {
118 this.form.patchValue(this.video.toJSON()) 117 this.form.patchValue(this.video.toJSON())
118
119 const objects = [
120 {
121 url: 'thumbnailUrl',
122 name: 'thumbnailfile'
123 },
124 {
125 url: 'previewUrl',
126 name: 'previewfile'
127 }
128 ]
129
130 for (const obj of objects) {
131 fetch(this.video[obj.url])
132 .then(response => response.blob())
133 .then(data => {
134 this.form.patchValue({
135 [ obj.name ]: data
136 })
137 })
138 }
119 } 139 }
120} 140}
diff --git a/client/src/app/videos/+video-watch/video-watch.component.html b/client/src/app/videos/+video-watch/video-watch.component.html
index af90e22a1..8c173d6b3 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.html
+++ b/client/src/app/videos/+video-watch/video-watch.component.html
@@ -1,7 +1,7 @@
1<div class="row"> 1<div class="row">
2 <!-- We need the video container for videojs so we just hide it --> 2 <!-- We need the video container for videojs so we just hide it -->
3 <div [hidden]="videoNotFound" id="video-container"> 3 <div [hidden]="videoNotFound" id="video-container">
4 <video id="video-element" class="video-js vjs-peertube-skin"></video> 4 <video [poster]="getVideoPoster()" id="video-element" class="video-js vjs-peertube-skin"></video>
5 </div> 5 </div>
6 6
7 <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div> 7 <div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts
index 7a64406e6..7c97f0964 100644
--- a/client/src/app/videos/+video-watch/video-watch.component.ts
+++ b/client/src/app/videos/+video-watch/video-watch.component.ts
@@ -211,6 +211,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
211 return Account.GET_ACCOUNT_AVATAR_URL(this.video.account) 211 return Account.GET_ACCOUNT_AVATAR_URL(this.video.account)
212 } 212 }
213 213
214 getVideoPoster () {
215 if (!this.video) return ''
216
217 return this.video.previewUrl
218 }
219
214 getVideoTags () { 220 getVideoTags () {
215 if (!this.video || Array.isArray(this.video.tags) === false) return [] 221 if (!this.video || Array.isArray(this.video.tags) === false) return []
216 222
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss
index 340221002..80dd3408f 100644
--- a/client/src/sass/application.scss
+++ b/client/src/sass/application.scss
@@ -299,34 +299,45 @@ p-datatable {
299 } 299 }
300} 300}
301 301
302.nav { 302tabset:not(.bootstrap) {
303 font-size: 16px !important; 303 .nav {
304 border: none !important; 304 font-size: 16px !important;
305
306 .nav-item .nav-link {
307 margin-right: 30px;
308 padding: 0;
309 border-radius: 3px;
310 border: none !important; 305 border: none !important;
311 306
312 .tab-link { 307 .nav-item .nav-link {
313 display: flex !important; 308 margin-right: 30px;
314 align-items: center; 309 padding: 0;
315 min-height: 30px !important; 310 border-radius: 3px;
316 padding: 0 15px; 311 border: none !important;
317 }
318 312
319 &, & a { 313 .tab-link {
320 color: #000 !important; 314 display: flex !important;
321 @include disable-default-a-behaviour; 315 align-items: center;
322 } 316 min-height: 30px !important;
317 padding: 0 15px;
318 }
323 319
324 &.active, &:hover { 320 &, & a {
325 background-color: #F0F0F0; 321 color: #000 !important;
322 @include disable-default-a-behaviour;
323 }
324
325 &.active, &:hover {
326 background-color: #F0F0F0;
327 }
328
329 &.active {
330 font-weight: $font-semibold !important;
331 }
326 } 332 }
333 }
334}
327 335
328 &.active { 336tabset.bootstrap {
329 font-weight: $font-semibold !important; 337 .nav-item .nav-link {
338 &, & a {
339 color: #000;
340 @include disable-default-a-behaviour;
330 } 341 }
331 } 342 }
332} 343}
diff --git a/server/controllers/api/config.ts b/server/controllers/api/config.ts
index 89163edb3..532afb8c0 100644
--- a/server/controllers/api/config.ts
+++ b/server/controllers/api/config.ts
@@ -61,6 +61,12 @@ async function getConfig (req: express.Request, res: express.Response, next: exp
61 } 61 }
62 }, 62 },
63 video: { 63 video: {
64 image: {
65 extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME,
66 size: {
67 max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max
68 }
69 },
64 file: { 70 file: {
65 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME 71 extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME
66 } 72 }
diff --git a/shared/models/config/server-config.model.ts b/shared/models/config/server-config.model.ts
index fdc36bcc1..988dd71e3 100644
--- a/shared/models/config/server-config.model.ts
+++ b/shared/models/config/server-config.model.ts
@@ -23,6 +23,12 @@ export interface ServerConfig {
23 } 23 }
24 24
25 video: { 25 video: {
26 image: {
27 size: {
28 max: number
29 }
30 extensions: string[]
31 },
26 file: { 32 file: {
27 extensions: string[] 33 extensions: string[]
28 } 34 }
diff --git a/shared/models/videos/video-update.model.ts b/shared/models/videos/video-update.model.ts
index 0b26484d7..b281ace9a 100644
--- a/shared/models/videos/video-update.model.ts
+++ b/shared/models/videos/video-update.model.ts
@@ -11,4 +11,6 @@ export interface VideoUpdate {
11 tags?: string[] 11 tags?: string[]
12 commentsEnabled?: boolean 12 commentsEnabled?: boolean
13 nsfw?: boolean 13 nsfw?: boolean
14 thumbnailfile?: Blob
15 previewfile?: Blob
14} 16}