diff options
author | lutangar <johan.dufour@gmail.com> | 2021-12-22 18:36:56 +0100 |
---|---|---|
committer | Chocobozzz <chocobozzz@cpy.re> | 2022-02-28 14:29:01 +0100 |
commit | 57d74ec83d64daaf2efc6a3dad8adbcfe1f59415 (patch) | |
tree | 3d56b8c45e6f7c88149c34e9c93b994c94587107 /client/src/app/+videos | |
parent | e66d0892b12c5b3b3e8a6b7a4129103a912486a9 (diff) | |
download | PeerTube-57d74ec83d64daaf2efc6a3dad8adbcfe1f59415.tar.gz PeerTube-57d74ec83d64daaf2efc6a3dad8adbcfe1f59415.tar.zst PeerTube-57d74ec83d64daaf2efc6a3dad8adbcfe1f59415.zip |
Add simple subtitle edition from video captions tab
Introduce a new __Edit__ button on a subtitle.
It opens a modal with simple textarea allowing the user to do quick corrections on a subtitle.
Diffstat (limited to 'client/src/app/+videos')
8 files changed, 165 insertions, 8 deletions
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 | |||
77 | 77 | ||
78 | this.captionAdded.emit({ | 78 | this.captionAdded.emit({ |
79 | language: languageObject, | 79 | language: languageObject, |
80 | captionfile: this.form.value['captionfile'] | 80 | captionfile: this.form.value['captionfile'], |
81 | action: 'CREATE' | ||
81 | }) | 82 | }) |
82 | 83 | ||
83 | this.hide() | 84 | 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 @@ | |||
1 | <ng-template #modal> | ||
2 | <ng-container [formGroup]="form"> | ||
3 | |||
4 | <div class="modal-header"> | ||
5 | <h4 i18n class="modal-title">Edit caption</h4> | ||
6 | <my-global-icon iconName="cross" aria-label="Close" role="button" (click)="hide()"></my-global-icon> | ||
7 | </div> | ||
8 | |||
9 | <div class="modal-body"> | ||
10 | <label i18n for="captionFileContent">Caption</label> | ||
11 | <textarea | ||
12 | id="captionFileContent" | ||
13 | formControlName="captionFileContent" | ||
14 | class="form-control caption-textarea" | ||
15 | [ngClass]="{ 'input-error': formErrors['captionFileContent'] }" | ||
16 | > | ||
17 | </textarea> | ||
18 | |||
19 | <div *ngIf="formErrors.captionFileContent" class="form-error"> | ||
20 | {{ formErrors.description }} | ||
21 | </div> | ||
22 | </div> | ||
23 | |||
24 | <div class="modal-footer inputs"> | ||
25 | <input | ||
26 | type="button" role="button" i18n-value value="Cancel" class="peertube-button grey-button" | ||
27 | (click)="cancel()" (key.enter)="cancel()" | ||
28 | > | ||
29 | |||
30 | <input | ||
31 | type="submit" i18n-value value="Edit this caption" class="peertube-button orange-button" | ||
32 | [disabled]="!form.valid" (click)="updateCaption()" | ||
33 | > | ||
34 | </div> | ||
35 | </ng-container> | ||
36 | </ng-template> | ||
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 @@ | |||
1 | .caption-textarea { | ||
2 | min-height: 600px; | ||
3 | } | ||
4 | |||
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 @@ | |||
1 | import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core' | ||
2 | |||
3 | import { VIDEO_CAPTION_FILE_CONTENT_VALIDATOR } from '@app/shared/form-validators/video-captions-validators' | ||
4 | import { FormReactive, FormValidatorService } from '@app/shared/shared-forms' | ||
5 | import { VideoCaptionEdit, VideoCaptionService, VideoCaptionWithPathEdit } from '@app/shared/shared-main' | ||
6 | import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap' | ||
7 | import { HTMLServerConfig, VideoConstant } from '@shared/models' | ||
8 | import { ServerService } from '../../../../core' | ||
9 | |||
10 | @Component({ | ||
11 | selector: 'my-video-caption-edit-modal', | ||
12 | styleUrls: [ './video-caption-edit-modal.component.scss' ], | ||
13 | templateUrl: './video-caption-edit-modal.component.html' | ||
14 | }) | ||
15 | |||
16 | export class VideoCaptionEditModalComponent extends FormReactive implements OnInit { | ||
17 | @Input() videoCaption: VideoCaptionWithPathEdit | ||
18 | @Input() serverConfig: HTMLServerConfig | ||
19 | |||
20 | @Output() captionEdited = new EventEmitter<VideoCaptionEdit>() | ||
21 | |||
22 | @ViewChild('modal', { static: true }) modal: ElementRef | ||
23 | |||
24 | videoCaptionLanguages: VideoConstant<string>[] = [] | ||
25 | private openedModal: NgbModalRef | ||
26 | private closingModal = false | ||
27 | |||
28 | constructor ( | ||
29 | protected formValidatorService: FormValidatorService, | ||
30 | private modalService: NgbModal, | ||
31 | private videoCaptionService: VideoCaptionService, | ||
32 | private serverService: ServerService | ||
33 | ) { | ||
34 | super() | ||
35 | } | ||
36 | |||
37 | ngOnInit () { | ||
38 | this.serverService.getVideoLanguages().subscribe(languages => this.videoCaptionLanguages = languages) | ||
39 | |||
40 | this.buildForm({ captionFileContent: VIDEO_CAPTION_FILE_CONTENT_VALIDATOR }) | ||
41 | |||
42 | this.loadCaptionContent() | ||
43 | } | ||
44 | |||
45 | loadCaptionContent () { | ||
46 | const { captionPath } = this.videoCaption | ||
47 | if (captionPath) { | ||
48 | this.videoCaptionService.getCaptionContent({ | ||
49 | captionPath | ||
50 | }).subscribe((res) => { | ||
51 | this.form.patchValue({ | ||
52 | captionFileContent: res | ||
53 | }) | ||
54 | }) | ||
55 | } | ||
56 | } | ||
57 | |||
58 | show () { | ||
59 | this.closingModal = false | ||
60 | |||
61 | this.openedModal = this.modalService.open(this.modal, { centered: true, keyboard: false }) | ||
62 | } | ||
63 | |||
64 | hide () { | ||
65 | this.closingModal = true | ||
66 | this.openedModal.close() | ||
67 | } | ||
68 | |||
69 | cancel () { | ||
70 | this.hide() | ||
71 | } | ||
72 | |||
73 | isReplacingExistingCaption () { | ||
74 | return true | ||
75 | } | ||
76 | |||
77 | updateCaption () { | ||
78 | const format = 'vtt' | ||
79 | const languageId = this.videoCaption.language.id | ||
80 | const languageObject = this.videoCaptionLanguages.find(l => l.id === languageId) | ||
81 | this.captionEdited.emit({ | ||
82 | language: languageObject, | ||
83 | captionfile: new File([ this.form.value['captionFileContent'] ], `${languageId}.${format}`, { | ||
84 | type: 'text/vtt', | ||
85 | lastModified: Date.now() | ||
86 | }), | ||
87 | action: 'UPDATE' | ||
88 | }) | ||
89 | |||
90 | this.hide() | ||
91 | } | ||
92 | } | ||
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 @@ | |||
186 | 186 | ||
187 | <div i18n class="caption-entry-state">Already uploaded ✔</div> | 187 | <div i18n class="caption-entry-state">Already uploaded ✔</div> |
188 | 188 | ||
189 | <span i18n class="caption-entry-edit" (click)="videoCaptionEditModal.show()">Edit</span> | ||
189 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span> | 190 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span> |
190 | </ng-container> | 191 | </ng-container> |
191 | 192 | ||
@@ -197,6 +198,14 @@ | |||
197 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span> | 198 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span> |
198 | </ng-container> | 199 | </ng-container> |
199 | 200 | ||
201 | <ng-container *ngIf="videoCaption.action === 'UPDATE'"> | ||
202 | <span class="caption-entry-label">{{ videoCaption.language.label }}</span> | ||
203 | |||
204 | <div i18n class="caption-entry-state caption-entry-state-create">Will be edited on update</div> | ||
205 | |||
206 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel edition</span> | ||
207 | </ng-container> | ||
208 | |||
200 | <ng-container *ngIf="videoCaption.action === 'REMOVE'"> | 209 | <ng-container *ngIf="videoCaption.action === 'REMOVE'"> |
201 | <span class="caption-entry-label">{{ videoCaption.language.label }}</span> | 210 | <span class="caption-entry-label">{{ videoCaption.language.label }}</span> |
202 | 211 | ||
@@ -204,6 +213,13 @@ | |||
204 | 213 | ||
205 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span> | 214 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span> |
206 | </ng-container> | 215 | </ng-container> |
216 | |||
217 | <my-video-caption-edit-modal | ||
218 | #videoCaptionEditModal | ||
219 | [videoCaption]="videoCaption" | ||
220 | [serverConfig]="serverConfig" | ||
221 | (captionEdited)="onCaptionEdited($event)" | ||
222 | ></my-video-caption-edit-modal> | ||
207 | </div> | 223 | </div> |
208 | </div> | 224 | </div> |
209 | 225 | ||
@@ -373,5 +389,5 @@ | |||
373 | </div> | 389 | </div> |
374 | 390 | ||
375 | <my-video-caption-add-modal | 391 | <my-video-caption-add-modal |
376 | #videoCaptionAddModal [existingCaptions]="getExistingCaptions()" [serverConfig]="serverConfig" (captionAdded)="onCaptionAdded($event)" | 392 | #videoCaptionAddModal [existingCaptions]="getExistingCaptions()" [serverConfig]="serverConfig" (captionAdded)="onCaptionEdited($event)" |
377 | ></my-video-caption-add-modal> | 393 | ></my-video-caption-add-modal> |
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 { | |||
96 | } | 96 | } |
97 | } | 97 | } |
98 | 98 | ||
99 | .caption-entry-edit { | ||
100 | @include peertube-button; | ||
101 | } | ||
102 | |||
99 | .caption-entry-delete { | 103 | .caption-entry-delete { |
100 | @include peertube-button; | 104 | @include peertube-button; |
101 | @include grey-button; | 105 | @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 { | |||
21 | } from '@app/shared/form-validators/video-validators' | 21 | } from '@app/shared/form-validators/video-validators' |
22 | import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms' | 22 | import { FormReactiveValidationMessages, FormValidatorService } from '@app/shared/shared-forms' |
23 | import { InstanceService } from '@app/shared/shared-instance' | 23 | import { InstanceService } from '@app/shared/shared-instance' |
24 | import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main' | 24 | import { VideoCaptionEdit, VideoCaptionWithPathEdit, VideoEdit, VideoService } from '@app/shared/shared-main' |
25 | import { PluginInfo } from '@root-helpers/plugins-manager' | 25 | import { PluginInfo } from '@root-helpers/plugins-manager' |
26 | import { | 26 | import { |
27 | HTMLServerConfig, | 27 | HTMLServerConfig, |
@@ -34,6 +34,7 @@ import { | |||
34 | } from '@shared/models' | 34 | } from '@shared/models' |
35 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' | 35 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' |
36 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 36 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
37 | import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' | ||
37 | import { VideoEditType } from './video-edit.type' | 38 | import { VideoEditType } from './video-edit.type' |
38 | 39 | ||
39 | type VideoLanguages = VideoConstant<string> & { group?: string } | 40 | type VideoLanguages = VideoConstant<string> & { group?: string } |
@@ -58,13 +59,14 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
58 | @Input() userVideoChannels: SelectChannelItem[] = [] | 59 | @Input() userVideoChannels: SelectChannelItem[] = [] |
59 | @Input() forbidScheduledPublication = true | 60 | @Input() forbidScheduledPublication = true |
60 | 61 | ||
61 | @Input() videoCaptions: (VideoCaptionEdit & { captionPath?: string })[] = [] | 62 | @Input() videoCaptions: (VideoCaptionWithPathEdit)[] = [] |
62 | 63 | ||
63 | @Input() waitTranscodingEnabled = true | 64 | @Input() waitTranscodingEnabled = true |
64 | @Input() type: VideoEditType | 65 | @Input() type: VideoEditType |
65 | @Input() liveVideo: LiveVideo | 66 | @Input() liveVideo: LiveVideo |
66 | 67 | ||
67 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent | 68 | @ViewChild('videoCaptionAddModal', { static: true }) videoCaptionAddModal: VideoCaptionAddModalComponent |
69 | @ViewChild('videoCaptionEditModal', { static: true }) editCaptionModal: VideoCaptionEditModalComponent | ||
68 | 70 | ||
69 | @Output() formBuilt = new EventEmitter<void>() | 71 | @Output() formBuilt = new EventEmitter<void>() |
70 | @Output() pluginFieldsAdded = new EventEmitter<void>() | 72 | @Output() pluginFieldsAdded = new EventEmitter<void>() |
@@ -228,12 +230,12 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
228 | .map(c => c.language.id) | 230 | .map(c => c.language.id) |
229 | } | 231 | } |
230 | 232 | ||
231 | onCaptionAdded (caption: VideoCaptionEdit) { | 233 | onCaptionEdited (caption: VideoCaptionEdit) { |
232 | const existingCaption = this.videoCaptions.find(c => c.language.id === caption.language.id) | 234 | const existingCaption = this.videoCaptions.find(c => c.language.id === caption.language.id) |
233 | 235 | ||
234 | // Replace existing caption? | 236 | // Replace existing caption? |
235 | if (existingCaption) { | 237 | if (existingCaption) { |
236 | Object.assign(existingCaption, caption, { action: 'CREATE' as 'CREATE' }) | 238 | Object.assign(existingCaption, caption) |
237 | } else { | 239 | } else { |
238 | this.videoCaptions.push( | 240 | this.videoCaptions.push( |
239 | Object.assign(caption, { action: 'CREATE' as 'CREATE' }) | 241 | Object.assign(caption, { action: 'CREATE' as 'CREATE' }) |
@@ -251,7 +253,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
251 | } | 253 | } |
252 | 254 | ||
253 | // This caption is not on the server, just remove it from our array | 255 | // This caption is not on the server, just remove it from our array |
254 | if (caption.action === 'CREATE') { | 256 | if (caption.action === 'CREATE' || caption.action === 'UPDATE') { |
255 | removeElementFromArray(this.videoCaptions, caption) | 257 | removeElementFromArray(this.videoCaptions, caption) |
256 | return | 258 | return |
257 | } | 259 | } |
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' | |||
6 | import { SharedVideoLiveModule } from '@app/shared/shared-video-live' | 6 | import { SharedVideoLiveModule } from '@app/shared/shared-video-live' |
7 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' | 7 | import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service' |
8 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' | 8 | import { VideoCaptionAddModalComponent } from './video-caption-add-modal.component' |
9 | import { VideoCaptionEditModalComponent } from './video-caption-edit-modal/video-caption-edit-modal.component' | ||
9 | import { VideoEditComponent } from './video-edit.component' | 10 | import { VideoEditComponent } from './video-edit.component' |
10 | 11 | ||
11 | @NgModule({ | 12 | @NgModule({ |
@@ -20,7 +21,8 @@ import { VideoEditComponent } from './video-edit.component' | |||
20 | 21 | ||
21 | declarations: [ | 22 | declarations: [ |
22 | VideoEditComponent, | 23 | VideoEditComponent, |
23 | VideoCaptionAddModalComponent | 24 | VideoCaptionAddModalComponent, |
25 | VideoCaptionEditModalComponent | ||
24 | ], | 26 | ], |
25 | 27 | ||
26 | exports: [ | 28 | exports: [ |