From 6de36768980ef6063b8fcd730b59fa685dd2b99c Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 16 Feb 2018 16:35:32 +0100 Subject: [PATCH] Add ability to update thumbnail and preview on client --- .../app/+admin/follows/follows.component.html | 2 - client/src/app/core/server/server.service.ts | 4 + .../app/shared/forms/form-validators/video.ts | 5 + .../forms/markdown-textarea.component.html | 2 +- client/src/app/shared/misc/utils.ts | 24 +- client/src/app/shared/shared.module.ts | 5 +- .../src/app/shared/video/video-edit.model.ts | 6 + .../video/video-thumbnail.component.html | 2 +- client/src/app/shared/video/video.service.ts | 17 +- .../shared/video-edit.component.html | 224 ++++++++++-------- .../shared/video-edit.component.scss | 12 + .../shared/video-edit.component.ts | 7 + .../+video-edit/shared/video-edit.module.ts | 4 +- .../+video-edit/video-update.component.ts | 26 +- .../+video-watch/video-watch.component.html | 2 +- .../+video-watch/video-watch.component.ts | 6 + client/src/sass/application.scss | 55 +++-- server/controllers/api/config.ts | 6 + shared/models/config/server-config.model.ts | 6 + shared/models/videos/video-update.model.ts | 2 + 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 @@ - - 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 { } }, video: { + image: { + size: { max: 0 }, + extensions: [] + }, file: { extensions: [] } 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 = { MESSAGES: {} } +export const VIDEO_IMAGE = { + VALIDATORS: [ ], + MESSAGES: {} +} + export const VIDEO_CHANNEL = { VALIDATORS: [ Validators.required ], 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 @@ id="description" name="description"> - + 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 () { return window.innerWidth < 500 } +// Thanks: https://gist.github.com/ghinda/8442a57f22099bdb2e34 +function objectToFormData (obj: any, form?: FormData, namespace?: string) { + let fd = form || new FormData() + let formKey + + for (let key of Object.keys(obj)) { + if (namespace) formKey = `${namespace}[${key}]` + else formKey = key + + if (obj[key] === undefined) continue + + if (typeof obj[ key ] === 'object' && !(obj[ key ] instanceof File)) { + objectToFormData(obj[ key ], fd, key) + } else { + fd.append(formKey, obj[ key ]) + } + } + + return fd +} + export { viewportHeight, getParameterByName, @@ -75,5 +96,6 @@ export { dateToHuman, isInSmallView, isInMobileView, - immutableAssign + immutableAssign, + objectToFormData } 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' BsDropdownModule.forRoot(), ModalModule.forRoot(), + TabsModule.forRoot(), PrimeSharedModule, - NgPipesModule, - TabsModule.forRoot() + NgPipesModule ], declarations: [ @@ -69,6 +69,7 @@ import { VideoService } from './video/video.service' BsDropdownModule, ModalModule, + TabsModule, PrimeSharedModule, BytesPipe, 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 { commentsEnabled: boolean channel: number privacy: VideoPrivacy + thumbnailfile?: any + previewfile?: any + thumbnailUrl: string + previewUrl: string uuid?: string id?: number @@ -29,6 +33,8 @@ export class VideoEdit { this.commentsEnabled = videoDetails.commentsEnabled this.channel = videoDetails.channel.id this.privacy = videoDetails.privacy + this.thumbnailUrl = videoDetails.thumbnailUrl + this.previewUrl = videoDetails.previewUrl } } 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 @@ [routerLink]="['/videos/watch', video.uuid]" [attr.title]="video.name" class="video-thumbnail" > -video thumbnail +
{{ 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' import { VideoDetails } from './video-details.model' import { VideoEdit } from './video-edit.model' import { Video } from './video.model' +import { objectToFormData } from '@app/shared/misc/utils' @Injectable() export class VideoService { @@ -46,10 +47,10 @@ export class VideoService { } updateVideo (video: VideoEdit) { - const language = video.language || null - const licence = video.licence || null - const category = video.category || null - const description = video.description || null + const language = video.language || undefined + const licence = video.licence || undefined + const category = video.category || undefined + const description = video.description || undefined const body: VideoUpdate = { name: video.name, @@ -60,10 +61,14 @@ export class VideoService { privacy: video.privacy, tags: video.tags, nsfw: video.nsfw, - commentsEnabled: video.commentsEnabled + commentsEnabled: video.commentsEnabled, + thumbnailfile: video.thumbnailfile, + previewfile: video.previewfile } - return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, body) + const data = objectToFormData(body) + + return this.authHttp.put(VideoService.BASE_VIDEO_URL + video.id, data) .map(this.restExtractor.extractDataBool) .catch(this.restExtractor.handleError) } 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 @@
- -
-
- - -
- {{ formErrors.name }} -
-
- -
- (press Enter to add) - -
- -
- - - -
- {{ formErrors.description }} -
-
-
- -
-
- -
- -
-
- -
- -
- -
- -
- {{ formErrors.category }} -
-
- -
- -
- + + + +
+
+ + +
+ {{ formErrors.name }} +
+
+ +
+ (press Enter to add) + +
+ +
+ + + +
+ {{ formErrors.description }} +
+
-
- {{ formErrors.licence }} -
-
- -
- -
- -
+
+
+ +
+ +
+
+ +
+ +
+ +
+ +
+ {{ formErrors.category }} +
+
+ +
+ +
+ +
+ +
+ {{ formErrors.licence }} +
+
+ +
+ +
+ +
+ +
+ {{ formErrors.language }} +
+
+ +
+ +
+ +
+ +
+ {{ formErrors.privacy }} +
+
+ +
+ + + +
+ +
+ + + +
-
- {{ formErrors.language }}
-
- -
- -
- + + + +
+
+ +
+ +
+ +
+
-
- {{ formErrors.privacy }} -
-
- -
- - - -
- -
- - - -
+ -
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 @@ .label-tags + span { font-size: 15px; } + + .root-tabset /deep/ > .nav { + margin-left: 15px; + margin-bottom: 15px; + + .nav-link { + display: flex !important; + align-items: center; + height: 30px !important; + padding: 0 15px !important; + } + } } .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 @@ import { Component, Input, OnInit } from '@angular/core' import { FormBuilder, FormControl, FormGroup } from '@angular/forms' import { ActivatedRoute, Router } from '@angular/router' +import { VIDEO_IMAGE } from '@app/shared' import { NotificationsService } from 'angular2-notifications' import 'rxjs/add/observable/forkJoin' import { ServerService } from '../../../core/server' @@ -57,6 +58,8 @@ export class VideoEditComponent implements OnInit { this.formErrors['licence'] = '' this.formErrors['language'] = '' this.formErrors['description'] = '' + this.formErrors['thumbnailfile'] = '' + this.formErrors['previewfile'] = '' this.validationMessages['name'] = VIDEO_NAME.MESSAGES this.validationMessages['privacy'] = VIDEO_PRIVACY.MESSAGES @@ -65,6 +68,8 @@ export class VideoEditComponent implements OnInit { this.validationMessages['licence'] = VIDEO_LICENCE.MESSAGES this.validationMessages['language'] = VIDEO_LANGUAGE.MESSAGES this.validationMessages['description'] = VIDEO_DESCRIPTION.MESSAGES + this.validationMessages['thumbnailfile'] = VIDEO_IMAGE.MESSAGES + this.validationMessages['previewfile'] = VIDEO_IMAGE.MESSAGES this.form.addControl('name', new FormControl('', VIDEO_NAME.VALIDATORS)) this.form.addControl('privacy', new FormControl('', VIDEO_PRIVACY.VALIDATORS)) @@ -76,6 +81,8 @@ export class VideoEditComponent implements OnInit { this.form.addControl('language', new FormControl('', VIDEO_LANGUAGE.VALIDATORS)) this.form.addControl('description', new FormControl('', VIDEO_DESCRIPTION.VALIDATORS)) this.form.addControl('tags', new FormControl('')) + this.form.addControl('thumbnailfile', new FormControl('')) + this.form.addControl('previewfile', new FormControl('')) } 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 @@ import { NgModule } from '@angular/core' +import { VideoImageComponent } from '@app/videos/+video-edit/shared/video-image.component' import { TabsModule } from 'ngx-bootstrap/tabs' import { TagInputModule } from 'ngx-chips' import { SharedModule } from '../../../shared' @@ -12,7 +13,8 @@ import { VideoEditComponent } from './video-edit.component' ], declarations: [ - VideoEditComponent + VideoEditComponent, + VideoImageComponent ], 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 { this.buildForm() this.serverService.videoPrivaciesLoaded - .subscribe( - () => this.videoPrivacies = this.serverService.getVideoPrivacies() - ) + .subscribe(() => this.videoPrivacies = this.serverService.getVideoPrivacies()) populateAsyncUserVideoChannels(this.authService, this.userVideoChannels) + .catch(err => console.error('Cannot populate async user video channels.', err)) const uuid: string = this.route.snapshot.params['uuid'] this.videoService.getVideo(uuid) @@ -116,5 +115,26 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { private hydrateFormFromVideo () { this.form.patchValue(this.video.toJSON()) + + const objects = [ + { + url: 'thumbnailUrl', + name: 'thumbnailfile' + }, + { + url: 'previewUrl', + name: 'previewfile' + } + ] + + for (const obj of objects) { + fetch(this.video[obj.url]) + .then(response => response.blob()) + .then(data => { + this.form.patchValue({ + [ obj.name ]: data + }) + }) + } } } 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 @@
- +
Video not found :'(
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 { return Account.GET_ACCOUNT_AVATAR_URL(this.video.account) } + getVideoPoster () { + if (!this.video) return '' + + return this.video.previewUrl + } + getVideoTags () { if (!this.video || Array.isArray(this.video.tags) === false) return [] 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 { } } -.nav { - font-size: 16px !important; - border: none !important; - - .nav-item .nav-link { - margin-right: 30px; - padding: 0; - border-radius: 3px; +tabset:not(.bootstrap) { + .nav { + font-size: 16px !important; border: none !important; - .tab-link { - display: flex !important; - align-items: center; - min-height: 30px !important; - padding: 0 15px; - } + .nav-item .nav-link { + margin-right: 30px; + padding: 0; + border-radius: 3px; + border: none !important; - &, & a { - color: #000 !important; - @include disable-default-a-behaviour; - } + .tab-link { + display: flex !important; + align-items: center; + min-height: 30px !important; + padding: 0 15px; + } - &.active, &:hover { - background-color: #F0F0F0; + &, & a { + color: #000 !important; + @include disable-default-a-behaviour; + } + + &.active, &:hover { + background-color: #F0F0F0; + } + + &.active { + font-weight: $font-semibold !important; + } } + } +} - &.active { - font-weight: $font-semibold !important; +tabset.bootstrap { + .nav-item .nav-link { + &, & a { + color: #000; + @include disable-default-a-behaviour; } } } 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 } }, video: { + image: { + extensions: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.EXTNAME, + size: { + max: CONSTRAINTS_FIELDS.VIDEOS.IMAGE.FILE_SIZE.max + } + }, file: { extensions: CONSTRAINTS_FIELDS.VIDEOS.EXTNAME } 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 { } video: { + image: { + size: { + max: number + } + extensions: string[] + }, file: { extensions: string[] } 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 { tags?: string[] commentsEnabled?: boolean nsfw?: boolean + thumbnailfile?: Blob + previewfile?: Blob } -- 2.41.0