From 7b992a86b107fc2917b993127df8e95a66ae94db Mon Sep 17 00:00:00 2001 From: Chocobozzz Date: Fri, 17 May 2019 10:45:53 +0200 Subject: Support audio upload in client --- .../my-account-video-playlist-edit.component.html | 10 +- .../my-account-video-playlist-edit.ts | 2 - .../src/app/shared/buttons/button.component.scss | 12 --- client/src/app/shared/buttons/button.component.ts | 2 +- .../app/shared/forms/reactive-file.component.html | 7 +- .../app/shared/forms/reactive-file.component.scss | 10 +- .../app/shared/forms/reactive-file.component.ts | 2 + .../app/shared/images/image-upload.component.html | 9 -- .../app/shared/images/image-upload.component.scss | 18 ---- .../app/shared/images/image-upload.component.ts | 69 -------------- .../shared/images/preview-upload.component.html | 13 +++ .../shared/images/preview-upload.component.scss | 27 ++++++ .../app/shared/images/preview-upload.component.ts | 74 ++++++++++++++ client/src/app/shared/shared.module.ts | 6 +- client/src/app/shared/video/video-edit.model.ts | 5 + .../+video-edit/shared/video-edit.component.html | 14 +-- .../+video-edit/shared/video-edit.component.ts | 1 - .../video-upload.component.html | 21 ++++ .../video-upload.component.scss | 17 +++- .../video-add-components/video-upload.component.ts | 106 +++++++++++++++------ 20 files changed, 255 insertions(+), 170 deletions(-) delete mode 100644 client/src/app/shared/images/image-upload.component.html delete mode 100644 client/src/app/shared/images/image-upload.component.scss delete mode 100644 client/src/app/shared/images/image-upload.component.ts create mode 100644 client/src/app/shared/images/preview-upload.component.html create mode 100644 client/src/app/shared/images/preview-upload.component.scss create mode 100644 client/src/app/shared/images/preview-upload.component.ts diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html index 303fc46f7..82321459f 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.component.html @@ -57,10 +57,12 @@
- + + +
diff --git a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts index fbfb4c8f7..81dd9a75f 100644 --- a/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts +++ b/client/src/app/+my-account/my-account-video-playlists/my-account-video-playlist-edit.ts @@ -1,6 +1,4 @@ import { FormReactive } from '@app/shared' -import { VideoChannel } from '@app/shared/video-channel/video-channel.model' -import { ServerService } from '@app/core' import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model' export abstract class MyAccountVideoPlaylistEdit extends FormReactive { diff --git a/client/src/app/shared/buttons/button.component.scss b/client/src/app/shared/buttons/button.component.scss index 04199a2a9..7ec77f4c9 100644 --- a/client/src/app/shared/buttons/button.component.scss +++ b/client/src/app/shared/buttons/button.component.scss @@ -4,18 +4,6 @@ .action-button { @include peertube-button-link; @include button-with-icon(21px, 0, -2px); - - font-weight: $font-semibold; - color: $grey-foreground-color; - background-color: $grey-background-color; - - &:hover { - background-color: $grey-background-hover-color; - } - - my-global-icon { - @include apply-svg-color($grey-foreground-color); - } } // In a table, try to minimize the space taken by this button diff --git a/client/src/app/shared/buttons/button.component.ts b/client/src/app/shared/buttons/button.component.ts index c2b69d31a..6d34d07f4 100644 --- a/client/src/app/shared/buttons/button.component.ts +++ b/client/src/app/shared/buttons/button.component.ts @@ -9,7 +9,7 @@ import { GlobalIconName } from '@app/shared/images/global-icon.component' export class ButtonComponent { @Input() label = '' - @Input() className: string = undefined + @Input() className: 'orange-button' | 'grey-button' = 'grey-button' @Input() icon: GlobalIconName = undefined @Input() title: string = undefined diff --git a/client/src/app/shared/forms/reactive-file.component.html b/client/src/app/shared/forms/reactive-file.component.html index 7d691059d..f6bf5f9ae 100644 --- a/client/src/app/shared/forms/reactive-file.component.html +++ b/client/src/app/shared/forms/reactive-file.component.html @@ -1,6 +1,9 @@
-
+
+ + {{ inputLabel }} +
-
(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxFileSize | bytes }})
-
{{ filename }}
diff --git a/client/src/app/shared/forms/reactive-file.component.scss b/client/src/app/shared/forms/reactive-file.component.scss index d89844264..84c23c1d6 100644 --- a/client/src/app/shared/forms/reactive-file.component.scss +++ b/client/src/app/shared/forms/reactive-file.component.scss @@ -8,13 +8,11 @@ .button-file { @include peertube-button-file(auto); + @include grey-button; - min-width: 190px; - } - - .file-constraints { - margin-left: 5px; - font-size: 13px; + &.with-icon { + @include button-with-icon; + } } .filename { diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/forms/reactive-file.component.ts index f60c38e8d..b7a821d4f 100644 --- a/client/src/app/shared/forms/reactive-file.component.ts +++ b/client/src/app/shared/forms/reactive-file.component.ts @@ -2,6 +2,7 @@ import { Component, EventEmitter, forwardRef, Input, OnInit, Output } from '@ang import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' import { Notifier } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' +import { GlobalIconName } from '@app/shared/images/global-icon.component' @Component({ selector: 'my-reactive-file', @@ -21,6 +22,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor { @Input() extensions: string[] = [] @Input() maxFileSize: number @Input() displayFilename = false + @Input() icon: GlobalIconName @Output() fileChanged = new EventEmitter() diff --git a/client/src/app/shared/images/image-upload.component.html b/client/src/app/shared/images/image-upload.component.html deleted file mode 100644 index c09c862c4..000000000 --- a/client/src/app/shared/images/image-upload.component.html +++ /dev/null @@ -1,9 +0,0 @@ -
- - - -
-
diff --git a/client/src/app/shared/images/image-upload.component.scss b/client/src/app/shared/images/image-upload.component.scss deleted file mode 100644 index b63963bca..000000000 --- a/client/src/app/shared/images/image-upload.component.scss +++ /dev/null @@ -1,18 +0,0 @@ -@import '_variables'; -@import '_mixins'; - -.root { - height: auto; - display: flex; - align-items: center; - - .preview { - border: 2px solid grey; - border-radius: 4px; - margin-left: 50px; - - &.no-image { - background-color: #ececec; - } - } -} diff --git a/client/src/app/shared/images/image-upload.component.ts b/client/src/app/shared/images/image-upload.component.ts deleted file mode 100644 index 2da1592ff..000000000 --- a/client/src/app/shared/images/image-upload.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { Component, forwardRef, Input } from '@angular/core' -import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' -import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' -import { ServerService } from '@app/core' - -@Component({ - selector: 'my-image-upload', - styleUrls: [ './image-upload.component.scss' ], - templateUrl: './image-upload.component.html', - providers: [ - { - provide: NG_VALUE_ACCESSOR, - useExisting: forwardRef(() => ImageUploadComponent), - multi: true - } - ] -}) -export class ImageUploadComponent implements ControlValueAccessor { - @Input() inputLabel: string - @Input() inputName: string - @Input() previewWidth: string - @Input() previewHeight: string - - imageSrc: SafeResourceUrl - - private file: File - - constructor ( - private sanitizer: DomSanitizer, - private serverService: ServerService - ) {} - - get videoImageExtensions () { - return this.serverService.getConfig().video.image.extensions - } - - get maxVideoImageSize () { - return this.serverService.getConfig().video.image.size.max - } - - onFileChanged (file: File) { - this.file = file - - this.propagateChange(this.file) - this.updatePreview() - } - - propagateChange = (_: any) => { /* empty */ } - - writeValue (file: any) { - this.file = file - this.updatePreview() - } - - registerOnChange (fn: (_: any) => void) { - this.propagateChange = fn - } - - registerOnTouched () { - // Unused - } - - private updatePreview () { - if (this.file) { - const url = URL.createObjectURL(this.file) - this.imageSrc = this.sanitizer.bypassSecurityTrustResourceUrl(url) - } - } -} diff --git a/client/src/app/shared/images/preview-upload.component.html b/client/src/app/shared/images/preview-upload.component.html new file mode 100644 index 000000000..5e1d5211b --- /dev/null +++ b/client/src/app/shared/images/preview-upload.component.html @@ -0,0 +1,13 @@ +
+
+ + + +
+
+ +
(extensions: {{ allowedExtensionsMessage }}, max size: {{ maxVideoImageSize | bytes }})
+
diff --git a/client/src/app/shared/images/preview-upload.component.scss b/client/src/app/shared/images/preview-upload.component.scss new file mode 100644 index 000000000..257060239 --- /dev/null +++ b/client/src/app/shared/images/preview-upload.component.scss @@ -0,0 +1,27 @@ +@import '_variables'; +@import '_mixins'; + +.root { + height: auto; + display: flex; + flex-direction: column; + + .preview-container { + position: relative; + + my-reactive-file { + position: absolute; + bottom: 10px; + left: 10px; + } + + .preview { + border: 2px solid grey; + border-radius: 4px; + + &.no-image { + background-color: #ececec; + } + } + } +} diff --git a/client/src/app/shared/images/preview-upload.component.ts b/client/src/app/shared/images/preview-upload.component.ts new file mode 100644 index 000000000..44b78866e --- /dev/null +++ b/client/src/app/shared/images/preview-upload.component.ts @@ -0,0 +1,74 @@ +import { Component, forwardRef, Input, OnInit } from '@angular/core' +import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' +import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser' +import { ServerService } from '@app/core' + +@Component({ + selector: 'my-preview-upload', + styleUrls: [ './preview-upload.component.scss' ], + templateUrl: './preview-upload.component.html', + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => PreviewUploadComponent), + multi: true + } + ] +}) +export class PreviewUploadComponent implements OnInit, ControlValueAccessor { + @Input() inputLabel: string + @Input() inputName: string + @Input() previewWidth: string + @Input() previewHeight: string + + imageSrc: SafeResourceUrl + allowedExtensionsMessage = '' + + private file: File + + constructor ( + private sanitizer: DomSanitizer, + private serverService: ServerService + ) {} + + get videoImageExtensions () { + return this.serverService.getConfig().video.image.extensions + } + + get maxVideoImageSize () { + return this.serverService.getConfig().video.image.size.max + } + + ngOnInit () { + this.allowedExtensionsMessage = this.videoImageExtensions.join(', ') + } + + onFileChanged (file: File) { + this.file = file + + this.propagateChange(this.file) + this.updatePreview() + } + + propagateChange = (_: any) => { /* empty */ } + + writeValue (file: any) { + this.file = file + this.updatePreview() + } + + registerOnChange (fn: (_: any) => void) { + this.propagateChange = fn + } + + registerOnTouched () { + // Unused + } + + private updatePreview () { + if (this.file) { + const url = URL.createObjectURL(this.file) + this.imageSrc = this.sanitizer.bypassSecurityTrustResourceUrl(url) + } + } +} diff --git a/client/src/app/shared/shared.module.ts b/client/src/app/shared/shared.module.ts index ded65653f..39f1a69e2 100644 --- a/client/src/app/shared/shared.module.ts +++ b/client/src/app/shared/shared.module.ts @@ -69,7 +69,7 @@ import { HtmlRendererService, LinkifierService, MarkdownService } from '@app/sha import { ConfirmComponent } from '@app/shared/confirm/confirm.component' import { SmallLoaderComponent } from '@app/shared/misc/small-loader.component' import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service' -import { ImageUploadComponent } from '@app/shared/images/image-upload.component' +import { PreviewUploadComponent } from '@app/shared/images/preview-upload.component' import { GlobalIconComponent } from '@app/shared/images/global-icon.component' import { VideoPlaylistMiniatureComponent } from '@app/shared/video-playlist/video-playlist-miniature.component' import { VideoAddToPlaylistComponent } from '@app/shared/video-playlist/video-add-to-playlist.component' @@ -154,7 +154,7 @@ import { ClipboardModule } from 'ngx-clipboard' ConfirmComponent, GlobalIconComponent, - ImageUploadComponent + PreviewUploadComponent ], exports: [ @@ -218,7 +218,7 @@ import { ClipboardModule } from 'ngx-clipboard' ConfirmComponent, GlobalIconComponent, - ImageUploadComponent, + PreviewUploadComponent, NumberFormatterPipe, ObjectLengthPipe, diff --git a/client/src/app/shared/video/video-edit.model.ts b/client/src/app/shared/video/video-edit.model.ts index 1f633d427..67d8e7711 100644 --- a/client/src/app/shared/video/video-edit.model.ts +++ b/client/src/app/shared/video/video-edit.model.ts @@ -85,6 +85,11 @@ export class VideoEdit implements VideoUpdate { const originallyPublishedAt = new Date(values['originallyPublishedAt']) this.originallyPublishedAt = originallyPublishedAt.toISOString() } + + // Use the same file than the preview for the thumbnail + if (this.previewfile) { + this.thumbnailfile = this.previewfile + } } toFormPatch () { 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 99695204d..28572d611 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 @@ -187,18 +187,14 @@
-
- -
- Video preview + + + >
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 c80efd802..95d397b52 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 @@ -100,7 +100,6 @@ export class VideoEditComponent implements OnInit, OnDestroy { language: this.videoValidatorsService.VIDEO_LANGUAGE, description: this.videoValidatorsService.VIDEO_DESCRIPTION, tags: null, - thumbnailfile: null, previewfile: null, support: this.videoValidatorsService.VIDEO_SUPPORT, schedulePublicationAt: this.videoValidatorsService.VIDEO_SCHEDULE_PUBLICATION_AT, diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html index 536769d2f..3247a2bd6 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.html @@ -26,6 +26,27 @@
+ + +
+ + +
+ Image that will be merged with your audio file. +
+ The chosen image will be definitive and cannot be modified. +
+ + +
+ +
+ +
+
diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss index 8adf8f169..684342f09 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.scss @@ -1,9 +1,20 @@ @import 'variables'; @import 'mixins'; -.first-step-block .form-group-channel { - margin-bottom: 20px; - margin-top: 35px; +.first-step-block { + + .form-group-channel { + margin-bottom: 20px; + margin-top: 35px; + } + + .audio-image-info { + margin-bottom: 10px; + } + + .audio-preview { + margin: 30px 0; + } } .upload-progress-cancel { diff --git a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts index d6d4bad21..73de25c59 100644 --- a/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts +++ b/client/src/app/videos/+video-edit/video-add-components/video-upload.component.ts @@ -35,8 +35,10 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy userVideoQuotaUsed = 0 userVideoQuotaUsedDaily = 0 + isUploadingAudioFile = false isUploadingVideo = false isUpdatingVideo = false + videoUploaded = false videoUploadObservable: Subscription = null videoUploadPercents = 0 @@ -44,7 +46,9 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy id: 0, uuid: '' } + waitTranscodingEnabled = true + previewfileUpload: File error: string @@ -100,6 +104,17 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } } + getVideoFile () { + return this.videofileInput.nativeElement.files[0] + } + + getAudioUploadLabel () { + const videofile = this.getVideoFile() + if (!videofile) return this.i18n('Upload') + + return this.i18n('Upload {{videofileName}}', { videofileName: videofile.name }) + } + fileChange () { this.uploadFirstStep() } @@ -114,38 +129,15 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } } - uploadFirstStep () { - const videofile = this.videofileInput.nativeElement.files[0] + uploadFirstStep (clickedOnButton = false) { + const videofile = this.getVideoFile() if (!videofile) return - // Check global user quota - const bytePipes = new BytesPipe() - const videoQuota = this.authService.getUser().videoQuota - if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { - const msg = this.i18n( - 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})', - { - videoSize: bytePipes.transform(videofile.size, 0), - videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0), - videoQuota: bytePipes.transform(videoQuota, 0) - } - ) - this.notifier.error(msg) - return - } + if (!this.checkGlobalUserQuota(videofile)) return + if (!this.checkDailyUserQuota(videofile)) return - // Check daily user quota - const videoQuotaDaily = this.authService.getUser().videoQuotaDaily - if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { - const msg = this.i18n( - 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})', - { - videoSize: bytePipes.transform(videofile.size, 0), - quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0), - quotaDaily: bytePipes.transform(videoQuotaDaily, 0) - } - ) - this.notifier.error(msg) + if (clickedOnButton === false && this.isAudioFile(videofile.name)) { + this.isUploadingAudioFile = true return } @@ -180,6 +172,11 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy formData.append('channelId', '' + channelId) formData.append('videofile', videofile) + if (this.previewfileUpload) { + formData.append('previewfile', this.previewfileUpload) + formData.append('thumbnailfile', this.previewfileUpload) + } + this.isUploadingVideo = true this.firstStepDone.emit(name) @@ -187,7 +184,8 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy name, privacy, nsfw, - channelId + channelId, + previewfile: this.previewfileUpload }) this.explainedVideoPrivacies = this.videoService.explainedPrivacyLabels(this.videoPrivacies) @@ -251,4 +249,52 @@ export class VideoUploadComponent extends VideoSend implements OnInit, OnDestroy } ) } + + private checkGlobalUserQuota (videofile: File) { + const bytePipes = new BytesPipe() + + // Check global user quota + const videoQuota = this.authService.getUser().videoQuota + if (videoQuota !== -1 && (this.userVideoQuotaUsed + videofile.size) > videoQuota) { + const msg = this.i18n( + 'Your video quota is exceeded with this video (video size: {{videoSize}}, used: {{videoQuotaUsed}}, quota: {{videoQuota}})', + { + videoSize: bytePipes.transform(videofile.size, 0), + videoQuotaUsed: bytePipes.transform(this.userVideoQuotaUsed, 0), + videoQuota: bytePipes.transform(videoQuota, 0) + } + ) + this.notifier.error(msg) + + return false + } + + return true + } + + private checkDailyUserQuota (videofile: File) { + const bytePipes = new BytesPipe() + + // Check daily user quota + const videoQuotaDaily = this.authService.getUser().videoQuotaDaily + if (videoQuotaDaily !== -1 && (this.userVideoQuotaUsedDaily + videofile.size) > videoQuotaDaily) { + const msg = this.i18n( + 'Your daily video quota is exceeded with this video (video size: {{videoSize}}, used: {{quotaUsedDaily}}, quota: {{quotaDaily}})', + { + videoSize: bytePipes.transform(videofile.size, 0), + quotaUsedDaily: bytePipes.transform(this.userVideoQuotaUsedDaily, 0), + quotaDaily: bytePipes.transform(videoQuotaDaily, 0) + } + ) + this.notifier.error(msg) + + return false + } + + return true + } + + private isAudioFile (filename: string) { + return filename.endsWith('.mp3') || filename.endsWith('.flac') || filename.endsWith('.ogg') + } } -- cgit v1.2.3