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:
parent
5f016383a4
commit
2873a53efd
7 changed files with 65 additions and 58 deletions
|
@ -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>
|
|
@ -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 { VIDEO_CAPTION_FILE_CONTENT_VALIDATOR } from '@app/shared/form-validators/video-captions-validators'
|
||||||
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
|
||||||
import { VideoCaptionEdit, VideoCaptionService, VideoCaptionWithPathEdit } from '@app/shared/shared-main'
|
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 { HTMLServerConfig, VideoConstant } from '@shared/models'
|
||||||
import { ServerService } from '../../../../core'
|
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({
|
@Component({
|
||||||
selector: 'my-video-caption-edit-modal',
|
selector: 'my-video-caption-edit-modal-content',
|
||||||
styleUrls: [ './video-caption-edit-modal.component.scss' ],
|
styleUrls: [ './video-caption-edit-modal-content.component.scss' ],
|
||||||
templateUrl: './video-caption-edit-modal.component.html'
|
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() videoCaption: VideoCaptionWithPathEdit
|
||||||
@Input() serverConfig: HTMLServerConfig
|
@Input() serverConfig: HTMLServerConfig
|
||||||
|
|
||||||
@Output() captionEdited = new EventEmitter<VideoCaptionEdit>()
|
@Output() captionEdited = new EventEmitter<VideoCaptionEdit>()
|
||||||
|
|
||||||
@ViewChild('modal', { static: true }) modal: ElementRef
|
@ViewChild('textarea', { static: true }) textarea!: ElementRef
|
||||||
|
|
||||||
videoCaptionLanguages: VideoConstant<string>[] = []
|
videoCaptionLanguages: VideoConstant<string>[] = []
|
||||||
private openedModal: NgbModalRef
|
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
|
protected openedModal: NgbActiveModal,
|
||||||
protected formValidatorService: FormValidatorService,
|
protected formValidatorService: FormValidatorService,
|
||||||
private modalService: NgbModal,
|
private modalService: NgbModal,
|
||||||
private videoCaptionService: VideoCaptionService,
|
private videoCaptionService: VideoCaptionService,
|
||||||
|
@ -49,11 +54,14 @@ export class VideoCaptionEditModalComponent extends FormReactive implements OnIn
|
||||||
this.form.patchValue({
|
this.form.patchValue({
|
||||||
captionFileContent: res
|
captionFileContent: res
|
||||||
})
|
})
|
||||||
|
this.resetTextarea()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
show () {
|
resetTextarea () {
|
||||||
this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false })
|
this.textarea.nativeElement.scrollTop = 0
|
||||||
|
this.textarea.nativeElement.selectionStart = 0
|
||||||
|
this.textarea.nativeElement.selectionEnd = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
hide () {
|
hide () {
|
|
@ -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>
|
|
|
@ -185,7 +185,7 @@
|
||||||
|
|
||||||
<div i18n class="caption-entry-state">Already uploaded on {{ videoCaption.updatedAt | date }} ✔</div>
|
<div i18n class="caption-entry-state">Already uploaded on {{ videoCaption.updatedAt | date }} ✔</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>
|
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
@ -212,13 +212,6 @@
|
||||||
|
|
||||||
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
|
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<my-video-caption-edit-modal
|
|
||||||
#videoCaptionEditModal
|
|
||||||
[videoCaption]="videoCaption"
|
|
||||||
[serverConfig]="serverConfig"
|
|
||||||
(captionEdited)="onCaptionEdited($event)"
|
|
||||||
></my-video-caption-edit-modal>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -35,10 +35,11 @@ import {
|
||||||
} from '@shared/models'
|
} from '@shared/models'
|
||||||
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
|
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
|
||||||
import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
|
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 { VideoEditType } from './video-edit.type'
|
||||||
import { VideoSource } from '@shared/models/videos/video-source'
|
import { VideoSource } from '@shared/models/videos/video-source'
|
||||||
import { logger } from '@root-helpers/logger'
|
import { logger } from '@root-helpers/logger'
|
||||||
|
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||||
|
|
||||||
type VideoLanguages = VideoConstant<string> & { group?: string }
|
type VideoLanguages = VideoConstant<string> & { group?: string }
|
||||||
type PluginField = {
|
type PluginField = {
|
||||||
|
@ -70,7 +71,6 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
@Input() liveVideo: LiveVideo
|
@Input() liveVideo: LiveVideo
|
||||||
|
|
||||||
@ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent
|
@ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent
|
||||||
@ViewChild('videoCaptionEditModal', { static: true }) editCaptionModal: VideoCaptionEditModalComponent
|
|
||||||
|
|
||||||
@Output() formBuilt = new EventEmitter<void>()
|
@Output() formBuilt = new EventEmitter<void>()
|
||||||
@Output() pluginFieldsAdded = new EventEmitter<void>()
|
@Output() pluginFieldsAdded = new EventEmitter<void>()
|
||||||
|
@ -128,7 +128,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
private i18nPrimengCalendarService: I18nPrimengCalendarService,
|
private i18nPrimengCalendarService: I18nPrimengCalendarService,
|
||||||
private ngZone: NgZone,
|
private ngZone: NgZone,
|
||||||
private hooks: HooksService,
|
private hooks: HooksService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef,
|
||||||
|
private modalService: NgbModal
|
||||||
) {
|
) {
|
||||||
this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone()
|
this.calendarTimezone = this.i18nPrimengCalendarService.getTimezone()
|
||||||
this.calendarDateFormat = this.i18nPrimengCalendarService.getDateFormat()
|
this.calendarDateFormat = this.i18nPrimengCalendarService.getDateFormat()
|
||||||
|
@ -286,6 +287,13 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
this.videoCaptionAddModal.show()
|
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 () {
|
isSaveReplayEnabled () {
|
||||||
return this.serverConfig.live.allowReplay
|
return this.serverConfig.live.allowReplay
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { SharedMainModule } from '@app/shared/shared-main'
|
||||||
import { SharedVideoLiveModule } from '@app/shared/shared-video-live'
|
import { SharedVideoLiveModule } from '@app/shared/shared-video-live'
|
||||||
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
|
import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
|
||||||
import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component'
|
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'
|
import { VideoEditComponent } from './video-edit.component'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -22,7 +22,7 @@ import { VideoEditComponent } from './video-edit.component'
|
||||||
declarations: [
|
declarations: [
|
||||||
VideoEditComponent,
|
VideoEditComponent,
|
||||||
VideoCaptionAddModalComponent,
|
VideoCaptionAddModalComponent,
|
||||||
VideoCaptionEditModalComponent
|
VideoCaptionEditModalContentComponent
|
||||||
],
|
],
|
||||||
|
|
||||||
exports: [
|
exports: [
|
||||||
|
|
Loading…
Reference in a new issue