mirror of https://github.com/Chocobozzz/PeerTube
Improve captions UX (at least I've tried)
parent
d73c98884e
commit
772d5642ba
|
@ -10,6 +10,7 @@ export type FormReactiveValidationMessages = {
|
||||||
|
|
||||||
export abstract class FormReactive {
|
export abstract class FormReactive {
|
||||||
protected abstract formValidatorService: FormValidatorService
|
protected abstract formValidatorService: FormValidatorService
|
||||||
|
protected formChanged = false
|
||||||
|
|
||||||
form: FormGroup
|
form: FormGroup
|
||||||
formErrors: FormReactiveErrors
|
formErrors: FormReactiveErrors
|
||||||
|
@ -31,6 +32,8 @@ export abstract class FormReactive {
|
||||||
this.formErrors[ field ] = ''
|
this.formErrors[ field ] = ''
|
||||||
const control = this.form.get(field)
|
const control = this.form.get(field)
|
||||||
|
|
||||||
|
if (control.dirty) this.formChanged = true
|
||||||
|
|
||||||
// Don't care if dirty on force check
|
// Don't care if dirty on force check
|
||||||
const isDirty = control.dirty || forceCheck === true
|
const isDirty = control.dirty || forceCheck === true
|
||||||
if (control && isDirty && !control.valid) {
|
if (control && isDirty && !control.valid) {
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
[name]="inputName" [id]="inputName" [accept]="extensions"
|
[name]="inputName" [id]="inputName" [accept]="extensions"
|
||||||
(change)="fileChange($event)"
|
(change)="fileChange($event)" [(ngModel)]="fileInputValue"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
|
||||||
@Output() fileChanged = new EventEmitter<Blob>()
|
@Output() fileChanged = new EventEmitter<Blob>()
|
||||||
|
|
||||||
allowedExtensionsMessage = ''
|
allowedExtensionsMessage = ''
|
||||||
|
fileInputValue: any
|
||||||
|
|
||||||
private file: File
|
private file: File
|
||||||
|
|
||||||
|
@ -63,6 +64,8 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor {
|
||||||
|
|
||||||
writeValue (file: any) {
|
writeValue (file: any) {
|
||||||
this.file = file
|
this.file = file
|
||||||
|
|
||||||
|
if (!this.file) this.fileInputValue = null
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOnChange (fn: (_: any) => void) {
|
registerOnChange (fn: (_: any) => void) {
|
||||||
|
|
|
@ -142,10 +142,33 @@
|
||||||
|
|
||||||
<div class="form-group" *ngFor="let videoCaption of videoCaptions">
|
<div class="form-group" *ngFor="let videoCaption of videoCaptions">
|
||||||
|
|
||||||
<div *ngIf="videoCaption.action !== 'REMOVE'" class="caption-entry">
|
<div class="caption-entry">
|
||||||
<div class="caption-entry-label">{{ videoCaption.language.label }}</div>
|
<ng-container *ngIf="!videoCaption.action">
|
||||||
|
<a
|
||||||
|
i18n-title title="See the subtitle file" class="caption-entry-label" target="_blank" rel="noopener noreferrer"
|
||||||
|
[href]="videoCaption.captionPath"
|
||||||
|
>{{ videoCaption.language.label }}</a>
|
||||||
|
|
||||||
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
|
<div class="caption-entry-state">Already uploaded ✔</div>
|
||||||
|
|
||||||
|
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="videoCaption.action === 'CREATE'">
|
||||||
|
<span class="caption-entry-label">{{ videoCaption.language.label }}</span>
|
||||||
|
|
||||||
|
<div class="caption-entry-state caption-entry-state-create">Will be created on update</div>
|
||||||
|
|
||||||
|
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="videoCaption.action === 'REMOVE'">
|
||||||
|
<span class="caption-entry-label">{{ videoCaption.language.label }}</span>
|
||||||
|
|
||||||
|
<div class="caption-entry-state caption-entry-state-delete">Will be deleted on update</div>
|
||||||
|
|
||||||
|
<span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -46,11 +46,34 @@
|
||||||
height: 40px;
|
height: 40px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
a.caption-entry-label {
|
||||||
|
@include disable-default-a-behaviour;
|
||||||
|
|
||||||
|
color: #000;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.caption-entry-label {
|
.caption-entry-label {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
width: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption-entry-state {
|
||||||
|
width: 200px;
|
||||||
|
|
||||||
|
&.caption-entry-state-create {
|
||||||
|
color: #39CC0B;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.caption-entry-state-delete {
|
||||||
|
color: #FF0000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.caption-entry-delete {
|
.caption-entry-delete {
|
||||||
|
|
|
@ -50,6 +50,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
private schedulerInterval
|
private schedulerInterval
|
||||||
private firstPatchDone = false
|
private firstPatchDone = false
|
||||||
|
private initialVideoCaptions: string[] = []
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
private formValidatorService: FormValidatorService,
|
private formValidatorService: FormValidatorService,
|
||||||
|
@ -127,6 +128,8 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
this.videoLanguages = this.serverService.getVideoLanguages()
|
this.videoLanguages = this.serverService.getVideoLanguages()
|
||||||
|
|
||||||
this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute
|
this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute
|
||||||
|
|
||||||
|
this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy () {
|
ngOnDestroy () {
|
||||||
|
@ -147,7 +150,13 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCaption (caption: VideoCaptionEdit) {
|
async deleteCaption (caption: VideoCaptionEdit) {
|
||||||
|
// Caption recovers his former state
|
||||||
|
if (caption.action && this.initialVideoCaptions.indexOf(caption.language.id) !== -1) {
|
||||||
|
caption.action = undefined
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// This caption is not on the server, just remove it from our array
|
// This caption is not on the server, just remove it from our array
|
||||||
if (caption.action === 'CREATE') {
|
if (caption.action === 'CREATE') {
|
||||||
removeElementFromArray(this.videoCaptions, caption)
|
removeElementFromArray(this.videoCaptions, caption)
|
||||||
|
|
|
@ -6,12 +6,14 @@ import { MetaGuard } from '@ngx-meta/core'
|
||||||
import { LoginGuard } from '../../core'
|
import { LoginGuard } from '../../core'
|
||||||
import { VideoUpdateComponent } from './video-update.component'
|
import { VideoUpdateComponent } from './video-update.component'
|
||||||
import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver'
|
import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver'
|
||||||
|
import { CanDeactivateGuard } from '@app/shared/guards/can-deactivate-guard.service'
|
||||||
|
|
||||||
const videoUpdateRoutes: Routes = [
|
const videoUpdateRoutes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: VideoUpdateComponent,
|
component: VideoUpdateComponent,
|
||||||
canActivate: [ MetaGuard, LoginGuard ],
|
canActivate: [ MetaGuard, LoginGuard ],
|
||||||
|
canDeactivate: [ CanDeactivateGuard ],
|
||||||
resolve: {
|
resolve: {
|
||||||
videoData: VideoUpdateResolver
|
videoData: VideoUpdateResolver
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,8 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
schedulePublicationPossible = false
|
schedulePublicationPossible = false
|
||||||
videoCaptions: VideoCaptionEdit[] = []
|
videoCaptions: VideoCaptionEdit[] = []
|
||||||
|
|
||||||
|
private updateDone = false
|
||||||
|
|
||||||
constructor (
|
constructor (
|
||||||
protected formValidatorService: FormValidatorService,
|
protected formValidatorService: FormValidatorService,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
|
@ -65,7 +67,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE
|
this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: Angular does not detec
|
// FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout
|
||||||
setTimeout(() => this.hydrateFormFromVideo())
|
setTimeout(() => this.hydrateFormFromVideo())
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -76,6 +78,16 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canDeactivate () {
|
||||||
|
if (this.updateDone === true) return { canDeactivate: true }
|
||||||
|
|
||||||
|
for (const caption of this.videoCaptions) {
|
||||||
|
if (caption.action) return { canDeactivate: false }
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canDeactivate: this.formChanged === false }
|
||||||
|
}
|
||||||
|
|
||||||
checkForm () {
|
checkForm () {
|
||||||
this.forceCheck()
|
this.forceCheck()
|
||||||
|
|
||||||
|
@ -100,6 +112,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
|
||||||
)
|
)
|
||||||
.subscribe(
|
.subscribe(
|
||||||
() => {
|
() => {
|
||||||
|
this.updateDone = true
|
||||||
this.isUpdatingVideo = false
|
this.isUpdatingVideo = false
|
||||||
this.loadingBar.complete()
|
this.loadingBar.complete()
|
||||||
this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.'))
|
this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.'))
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { VideoEditModule } from './shared/video-edit.module'
|
||||||
import { VideoUpdateRoutingModule } from './video-update-routing.module'
|
import { VideoUpdateRoutingModule } from './video-update-routing.module'
|
||||||
import { VideoUpdateComponent } from './video-update.component'
|
import { VideoUpdateComponent } from './video-update.component'
|
||||||
import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver'
|
import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver'
|
||||||
|
import { CanDeactivateGuard } from '@app/shared/guards/can-deactivate-guard.service'
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -21,7 +22,8 @@ import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolv
|
||||||
],
|
],
|
||||||
|
|
||||||
providers: [
|
providers: [
|
||||||
VideoUpdateResolver
|
VideoUpdateResolver,
|
||||||
|
CanDeactivateGuard
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class VideoUpdateModule { }
|
export class VideoUpdateModule { }
|
||||||
|
|
Loading…
Reference in New Issue