diff options
author | Chocobozzz <me@florianbigard.com> | 2018-02-16 16:35:32 +0100 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-02-16 16:35:32 +0100 |
commit | 6de36768980ef6063b8fcd730b59fa685dd2b99c (patch) | |
tree | a8e5a87b14013b33bf2d306552a380c407a1551d | |
parent | b6a4fd6b099b3363ac59c06cfd81b54e1356d8bc (diff) | |
download | PeerTube-6de36768980ef6063b8fcd730b59fa685dd2b99c.tar.gz PeerTube-6de36768980ef6063b8fcd730b59fa685dd2b99c.tar.zst PeerTube-6de36768980ef6063b8fcd730b59fa685dd2b99c.zip |
Add ability to update thumbnail and preview on client
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 | ||
34 | export const VIDEO_IMAGE = { | ||
35 | VALIDATORS: [ ], | ||
36 | MESSAGES: {} | ||
37 | } | ||
38 | |||
34 | export const VIDEO_CHANNEL = { | 39 | export 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 | ||
71 | function 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 | |||
70 | export { | 91 | export { |
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" |
3 | class="video-thumbnail" | 3 | class="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' | |||
18 | import { VideoDetails } from './video-details.model' | 18 | import { VideoDetails } from './video-details.model' |
19 | import { VideoEdit } from './video-edit.model' | 19 | import { VideoEdit } from './video-edit.model' |
20 | import { Video } from './video.model' | 20 | import { Video } from './video.model' |
21 | import { objectToFormData } from '@app/shared/misc/utils' | ||
21 | 22 | ||
22 | @Injectable() | 23 | @Injectable() |
23 | export class VideoService { | 24 | export 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 @@ | |||
1 | import { Component, Input, OnInit } from '@angular/core' | 1 | import { Component, Input, OnInit } from '@angular/core' |
2 | import { FormBuilder, FormControl, FormGroup } from '@angular/forms' | 2 | import { FormBuilder, FormControl, FormGroup } from '@angular/forms' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { VIDEO_IMAGE } from '@app/shared' | ||
4 | import { NotificationsService } from 'angular2-notifications' | 5 | import { NotificationsService } from 'angular2-notifications' |
5 | import 'rxjs/add/observable/forkJoin' | 6 | import 'rxjs/add/observable/forkJoin' |
6 | import { ServerService } from '../../../core/server' | 7 | import { 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 @@ | |||
1 | import { NgModule } from '@angular/core' | 1 | import { NgModule } from '@angular/core' |
2 | import { VideoImageComponent } from '@app/videos/+video-edit/shared/video-image.component' | ||
2 | import { TabsModule } from 'ngx-bootstrap/tabs' | 3 | import { TabsModule } from 'ngx-bootstrap/tabs' |
3 | import { TagInputModule } from 'ngx-chips' | 4 | import { TagInputModule } from 'ngx-chips' |
4 | import { SharedModule } from '../../../shared' | 5 | import { 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 { | 302 | tabset: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 { | 336 | tabset.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 | } |