diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts index 5c4152884..6deadfcbe 100644 --- a/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts +++ b/client/src/app/+videos/+video-edit/shared/video-caption-add-modal.component.ts @@ -77,7 +77,8 @@ export class VideoCaptionAddModalComponent extends FormReactive implements OnIni this.captionAdded.emit({ language: languageObject, - captionfile: this.form.value['captionfile'] + captionfile: this.form.value['captionfile'], + action: 'CREATE' }) this.hide() diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.html b/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.html new file mode 100644 index 000000000..4543b93d8 --- /dev/null +++ b/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.html @@ -0,0 +1,36 @@ + + + + + Edit caption + + + + + Caption + + + + + {{ formErrors.description }} + + + + + + diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.scss b/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.scss new file mode 100644 index 000000000..bd96f2b7a --- /dev/null +++ b/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.scss @@ -0,0 +1,4 @@ +.caption-textarea { + min-height: 600px; +} + diff --git a/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.ts b/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.ts new file mode 100644 index 000000000..d2232a38e --- /dev/null +++ b/client/src/app/+videos/+video-edit/shared/video-caption-edit-modal/video-caption-edit-modal.component.ts @@ -0,0 +1,92 @@ +import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' + +import { VIDEO_CAPTION_FILE_CONTENT_VALIDATOR } from '@app/shared/form-validators/video-captions-validators' +import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' +import { VideoCaptionEdit, VideoCaptionService, VideoCaptionWithPathEdit } from '@app/shared/shared-main' +import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' +import { HTMLServerConfig, VideoConstant } from '@shared/models' +import { ServerService } from '../../../../core' + +@Component({ + selector: 'my-video-caption-edit-modal', + styleUrls: [ './video-caption-edit-modal.component.scss' ], + templateUrl: './video-caption-edit-modal.component.html' +}) + +export class VideoCaptionEditModalComponent extends FormReactive implements OnInit { + @Input() videoCaption: VideoCaptionWithPathEdit + @Input() serverConfig: HTMLServerConfig + + @Output() captionEdited = new EventEmitter() + + @ViewChild('modal', { static: true }) modal: ElementRef + + videoCaptionLanguages: VideoConstant[] = [] + private openedModal: NgbModalRef + private closingModal = false + + constructor ( + protected formValidatorService: FormValidatorService, + private modalService: NgbModal, + private videoCaptionService: VideoCaptionService, + private serverService: ServerService + ) { + super() + } + + ngOnInit () { + this.serverService.getVideoLanguages().subscribe(languages => this.videoCaptionLanguages = languages) + + this.buildForm({ captionFileContent: VIDEO_CAPTION_FILE_CONTENT_VALIDATOR }) + + this.loadCaptionContent() + } + + loadCaptionContent () { + const { captionPath } = this.videoCaption + if (captionPath) { + this.videoCaptionService.getCaptionContent({ + captionPath + }).subscribe((res) => { + this.form.patchValue({ + captionFileContent: res + }) + }) + } + } + + show () { + this.closingModal = false + + this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) + } + + hide () { + this.closingModal = true + this.openedModal.close() + } + + cancel () { + this.hide() + } + + isReplacingExistingCaption () { + return true + } + + updateCaption () { + const format = 'vtt' + const languageId = this.videoCaption.language.id + const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) + this.captionEdited.emit({ + language: languageObject, + captionfile: new File([ this.form.value['captionFileContent'] ], `${languageId}.${format}`, { + type: 'text/vtt', + lastModified: Date.now() + }), + action: 'UPDATE' + }) + + this.hide() + } +} 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 9bb13ba88..2281f8631 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 @@ -186,6 +186,7 @@ Already uploaded ✔ + Edit Delete @@ -197,6 +198,14 @@ Cancel create + + {{ videoCaption.language.label }} + + Will be edited on update + + Cancel edition + + {{ videoCaption.language.label }} @@ -204,6 +213,13 @@ Cancel deletion + + @@ -373,5 +389,5 @@ 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 4b1dec89a..5344e5431 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 @@ -96,6 +96,10 @@ my-peertube-checkbox { } } + .caption-entry-edit { + @include peertube-button; + } + .caption-entry-delete { @include peertube-button; @include grey-button; 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 31dbe43e6..0f4d0619b 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 @@ -21,7 +21,7 @@ import { } from '@app/shared/form-validators/video-validators' import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms' import { InstanceService } from '@app/shared/shared-instance' -import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' +import { VideoCaptionEdit, VideoCaptionWithPathEdit, VideoEdit, VideoService } from '@app/shared/shared-main' import { PluginInfo } from '@root-helpers/plugins-manager' import { HTMLServerConfig, @@ -34,6 +34,7 @@ import { } from '@shared/models' import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' +import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' import { VideoEditType } from './video-edit.type' type VideoLanguages = VideoConstant & { group?: string } @@ -58,13 +59,14 @@ export class VideoEditComponent implements OnInit, OnDestroy { @Input() userVideoChannels: SelectChannelItem[] = [] @Input() forbidScheduledPublication = true - @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] + @Input() videoCaptions: (VideoCaptionWithPathEdit)[] = [] @Input() waitTranscodingEnabled = true @Input() type: VideoEditType @Input() liveVideo: LiveVideo @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent + @ViewChild('videoCaptionEditModal', { static: true }) editCaptionModal: VideoCaptionEditModalComponent @Output() formBuilt = new EventEmitter() @Output() pluginFieldsAdded = new EventEmitter() @@ -228,12 +230,12 @@ export class VideoEditComponent implements OnInit, OnDestroy { .map(c => c.language.id) } - onCaptionAdded (caption: VideoCaptionEdit) { + onCaptionEdited (caption: VideoCaptionEdit) { const existingCaption = this.videoCaptions.find(c => c.language.id === caption.language.id) // Replace existing caption? if (existingCaption) { - Object.assign(existingCaption, caption, { action: 'CREATE' as 'CREATE' }) + Object.assign(existingCaption, caption) } else { this.videoCaptions.push( Object.assign(caption, { action: 'CREATE' as 'CREATE' }) @@ -251,7 +253,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { } // This caption is not on the server, just remove it from our array - if (caption.action === 'CREATE') { + if (caption.action === 'CREATE' || caption.action === 'UPDATE') { removeElementFromArray(this.videoCaptions, caption) return } 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 7a3854065..4e8767364 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 @@ -6,6 +6,7 @@ import { SharedMainModule } from '@app/shared/shared-main' import { SharedVideoLiveModule } from '@app/shared/shared-video-live' import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' +import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' import { VideoEditComponent } from './video-edit.component' @NgModule({ @@ -20,7 +21,8 @@ import { VideoEditComponent } from './video-edit.component' declarations: [ VideoEditComponent, - VideoCaptionAddModalComponent + VideoCaptionAddModalComponent, + VideoCaptionEditModalComponent ], exports: [ diff --git a/client/src/app/shared/form-validators/video-captions-validators.ts b/client/src/app/shared/form-validators/video-captions-validators.ts index a16216422..e589fe934 100644 --- a/client/src/app/shared/form-validators/video-captions-validators.ts +++ b/client/src/app/shared/form-validators/video-captions-validators.ts @@ -14,3 +14,10 @@ export const VIDEO_CAPTION_FILE_VALIDATOR: BuildFormValidator = { required: $localize`Video caption file is required.` } } + +export const VIDEO_CAPTION_FILE_CONTENT_VALIDATOR: BuildFormValidator = { + VALIDATORS: [ Validators.required ], + MESSAGES: { + required: $localize`Caption content is required.` + } +} diff --git a/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts b/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts index 732f20158..129e80bc0 100644 --- a/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts +++ b/client/src/app/shared/shared-main/video-caption/video-caption-edit.model.ts @@ -4,6 +4,8 @@ export interface VideoCaptionEdit { label?: string } - action?: 'CREATE' | 'REMOVE' + action?: 'CREATE' | 'REMOVE' | 'UPDATE' captionfile?: any } + +export type VideoCaptionWithPathEdit = VideoCaptionEdit & { captionPath?: string } diff --git a/client/src/app/shared/shared-main/video-caption/video-caption.service.ts b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts index 97b79d842..67eb09e4d 100644 --- a/client/src/app/shared/shared-main/video-caption/video-caption.service.ts +++ b/client/src/app/shared/shared-main/video-caption/video-caption.service.ts @@ -8,6 +8,7 @@ import { VideoService } from '@app/shared/shared-main/video' import { peertubeTranslate } from '@shared/core-utils/i18n' import { ResultList, VideoCaption } from '@shared/models' import { VideoCaptionEdit } from './video-caption-edit.model' +import { environment } from '../../../../environments/environment' @Injectable() export class VideoCaptionService { @@ -57,7 +58,7 @@ export class VideoCaptionService { let obs: Observable = of(undefined) for (const videoCaption of videoCaptions) { - if (videoCaption.action === 'CREATE') { + if (videoCaption.action === 'CREATE' || videoCaption.action === 'UPDATE') { obs = obs.pipe(switchMap(() => this.addCaption(videoId, videoCaption.language.id, videoCaption.captionfile))) } else if (videoCaption.action === 'REMOVE') { obs = obs.pipe(switchMap(() => this.removeCaption(videoId, videoCaption.language.id))) @@ -66,4 +67,8 @@ export class VideoCaptionService { return obs } + + getCaptionContent ({ captionPath }: Pick) { + return this.authHttp.get(`${environment.originServerUrl}${captionPath}`, { responseType: 'text' }) + } }