Set scroll position at top of the textarea when opening the subtitle editor.

## Description

This set the position of the scrollbar at the top of the textarea when opening the __subtitle editor__.
Previously the textarea scroll position was at the bottom of the textarea which doesn't make much sense when you want to edit a subtitle : you most likely want to edit the beginning of the subtitle first.

This also set the caret position on the first character.

## Design decision

I had to use a *component approach* instead of an `<ng-template>` for the edition modal because the `@viewChild` directive doesn't work for elements __inside__ an `<ng-template>`.
I needed the `viewChild` directive to get an `ElementRef` of the `textarea`.

> See the following issue and its workaround :
> - https://github.com/valor-software/ngx-bootstrap/issues/3825
> - https://stackblitz.com/edit/angular-t5dfp7
> - https://medium.com/@izzatnadiri/how-to-pass-data-to-and-receive-from-ng-bootstrap-modals-916f2ad5d66e

## Related issues
Closes [peertube-plugin-transcription/#39](https://gitlab.com/apps_education/peertube/plugin-transcription/-/issues/39)
This commit is contained in:
lutangar 2022-08-30 17:13:26 +02:00 committed by Chocobozzz
parent 5f016383a4
commit 2873a53efd
7 changed files with 65 additions and 58 deletions

View File

@ -0,0 +1,34 @@
<ng-container [formGroup]="form">
<div class="modal-header">
<h4 i18n class="modal-title">Edit caption</h4>
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
</div>
<div class="modal-body">
<label i18n for="captionFileContent">Caption</label>
<textarea
id="captionFileContent"
formControlName="captionFileContent"
class="form-control caption-textarea"
[ngClass]="{ 'input-error': formErrors['captionFileContent'] }"
#textarea
>
</textarea>
<div *ngIf="formErrors.captionFileContent" class="form-error">
{{ formErrors.captionFileContent }}
</div>
</div>
<div class="modal-footer inputs">
<input
type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
(click)="cancel()" (key.enter)="cancel()"
>
<input
type="submit" i18n-value value="Edit this caption" class="peertube-button orange-button"
[disabled]="!form.valid" (click)="updateCaption()"
>
</div>
</ng-container>

View File

@ -2,28 +2,33 @@ import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild }
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 { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'
import { HTMLServerConfig, VideoConstant } from '@shared/models'
import { ServerService } from '../../../../core'
/**
* https://github.com/valor-software/ngx-bootstrap/issues/3825
* https://stackblitz.com/edit/angular-t5dfp7
* https://medium.com/@izzatnadiri/how-to-pass-data-to-and-receive-from-ng-bootstrap-modals-916f2ad5d66e
*/
@Component({
selector: 'my-video-caption-edit-modal',
styleUrls: [ './video-caption-edit-modal.component.scss' ],
templateUrl: './video-caption-edit-modal.component.html'
selector: 'my-video-caption-edit-modal-content',
styleUrls: [ './video-caption-edit-modal-content.component.scss' ],
templateUrl: './video-caption-edit-modal-content.component.html'
})
export class VideoCaptionEditModalComponent extends FormReactive implements OnInit {
export class VideoCaptionEditModalContentComponent extends FormReactive implements OnInit {
@Input() videoCaption: VideoCaptionWithPathEdit
@Input() serverConfig: HTMLServerConfig
@Output() captionEdited = new EventEmitter<VideoCaptionEdit>()
@ViewChild('modal', { static: true }) modal: ElementRef
@ViewChild('textarea', { static: true }) textarea!: ElementRef
videoCaptionLanguages: VideoConstant<string>[] = []
private openedModal: NgbModalRef
constructor (
protected openedModal: NgbActiveModal,
protected formValidatorService: FormValidatorService,
private modalService: NgbModal,
private videoCaptionService: VideoCaptionService,
@ -49,11 +54,14 @@ export class VideoCaptionEditModalComponent extends FormReactive implements OnIn
this.form.patchValue({
captionFileContent: res
})
this.resetTextarea()
})
}
show () {
this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
resetTextarea () {
this.textarea.nativeElement.scrollTop = 0
this.textarea.nativeElement.selectionStart = 0
this.textarea.nativeElement.selectionEnd = 0
}
hide () {

View File

@ -1,36 +0,0 @@
<ng-template #modal>
<ng-container [formGroup]="form">
<div class="modal-header">
<h4 i18n class="modal-title">Edit caption</h4>
<my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon>
</div>
<div class="modal-body">
<label i18n for="captionFileContent">Caption</label>
<textarea
id="captionFileContent"
formControlName="captionFileContent"
class="form-control caption-textarea"
[ngClass]="{ 'input-error': formErrors['captionFileContent'] }"
>
</textarea>
<div *ngIf="formErrors.captionFileContent" class="form-error">
{{ formErrors.captionFileContent }}
</div>
</div>
<div class="modal-footer inputs">
<input
type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button"
(click)="cancel()" (key.enter)="cancel()"
>
<input
type="submit" i18n-value value="Edit this caption" class="peertube-button orange-button"
[disabled]="!form.valid" (click)="updateCaption()"
>
</div>
</ng-container>
</ng-template>

View File

@ -185,7 +185,7 @@
<div i18n class="caption-entry-state">Already uploaded on {{ videoCaption.updatedAt | date }} &#10004;</div>
<span i18n class="caption-entry-edit" (click)="videoCaptionEditModal.show()">Edit</span>
<span i18n class="caption-entry-edit" (click)="openEditCaptionModal(videoCaption)">Edit</span>
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
</ng-container>
@ -212,13 +212,6 @@
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
</ng-container>
<my-video-caption-edit-modal
#videoCaptionEditModal
[videoCaption]="videoCaption"
[serverConfig]="serverConfig"
(captionEdited)="onCaptionEdited($event)"
></my-video-caption-edit-modal>
</div>
</div>

View File

@ -35,10 +35,11 @@ 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 { VideoCaptionEditModalContentComponent } from './video-caption-edit-modal-content/video-caption-edit-modal-content.component'
import { VideoEditType } from './video-edit.type'
import { VideoSource } from '@shared/models/videos/video-source'
import { logger } from '@root-helpers/logger'
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
type VideoLanguages = VideoConstant<string> & { group?: string }
type PluginField = {
@ -70,7 +71,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
@Input() liveVideo: LiveVideo
@ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent
@ViewChild('videoCaptionEditModal', { static: true }) editCaptionModal: VideoCaptionEditModalComponent
@Output() formBuilt = new EventEmitter<void>()
@Output() pluginFieldsAdded = new EventEmitter<void>()
@ -128,7 +128,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
private i18nPrimengCalendarService: I18nPrimengCalendarService,
private ngZone: NgZone,
private hooks: HooksService,
private cd: ChangeDetectorRef
private cd: ChangeDetectorRef,
private modalService: NgbModal
) {
this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone()
this.calendarDateFormat = this.i18nPrimengCalendarService.getDateFormat()
@ -286,6 +287,13 @@ export class VideoEditComponent implements OnInit, OnDestroy {
this.videoCaptionAddModal.show()
}
openEditCaptionModal (videoCaption: VideoCaptionWithPathEdit) {
const modalRef = this.modalService.open(VideoCaptionEditModalContentComponent, { centered: true, keyboard: false })
modalRef.componentInstance.videoCaption = videoCaption
modalRef.componentInstance.serverConfig = this.serverConfig
modalRef.componentInstance.captionEdited.subscribe(this.onCaptionEdited.bind(this))
}
isSaveReplayEnabled () {
return this.serverConfig.live.allowReplay
}

View File

@ -6,7 +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 { VideoCaptionEditModalContentComponent } from './video-caption-edit-modal-content/video-caption-edit-modal-content.component'
import { VideoEditComponent } from './video-edit.component'
@NgModule({
@ -22,7 +22,7 @@ import { VideoEditComponent } from './video-edit.component'
declarations: [
VideoEditComponent,
VideoCaptionAddModalComponent,
VideoCaptionEditModalComponent
VideoCaptionEditModalContentComponent
],
exports: [