diff options
author | Chocobozzz <me@florianbigard.com> | 2018-07-25 10:28:43 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2018-07-25 10:28:43 +0200 |
commit | 772d5642ba617865519ca5e590061adf174866d4 (patch) | |
tree | 27e2f3c9a78a4de67128f005d2bd73a5eecca804 | |
parent | d73c98884ec7f970ed95a01fb2d445d10c53c817 (diff) | |
download | PeerTube-772d5642ba617865519ca5e590061adf174866d4.tar.gz PeerTube-772d5642ba617865519ca5e590061adf174866d4.tar.zst PeerTube-772d5642ba617865519ca5e590061adf174866d4.zip |
Improve captions UX (at least I've tried)
9 files changed, 85 insertions, 7 deletions
diff --git a/client/src/app/shared/forms/form-reactive.ts b/client/src/app/shared/forms/form-reactive.ts index 441ec8203..e4f7481b5 100644 --- a/client/src/app/shared/forms/form-reactive.ts +++ b/client/src/app/shared/forms/form-reactive.ts | |||
@@ -10,6 +10,7 @@ export type FormReactiveValidationMessages = { | |||
10 | 10 | ||
11 | export abstract class FormReactive { | 11 | export abstract class FormReactive { |
12 | protected abstract formValidatorService: FormValidatorService | 12 | protected abstract formValidatorService: FormValidatorService |
13 | protected formChanged = false | ||
13 | 14 | ||
14 | form: FormGroup | 15 | form: FormGroup |
15 | formErrors: FormReactiveErrors | 16 | formErrors: FormReactiveErrors |
@@ -31,6 +32,8 @@ export abstract class FormReactive { | |||
31 | this.formErrors[ field ] = '' | 32 | this.formErrors[ field ] = '' |
32 | const control = this.form.get(field) | 33 | const control = this.form.get(field) |
33 | 34 | ||
35 | if (control.dirty) this.formChanged = true | ||
36 | |||
34 | // Don't care if dirty on force check | 37 | // Don't care if dirty on force check |
35 | const isDirty = control.dirty || forceCheck === true | 38 | const isDirty = control.dirty || forceCheck === true |
36 | if (control && isDirty && !control.valid) { | 39 | if (control && isDirty && !control.valid) { |
diff --git a/client/src/app/shared/forms/reactive-file.component.html b/client/src/app/shared/forms/reactive-file.component.html index 9fb1c9e3e..7d691059d 100644 --- a/client/src/app/shared/forms/reactive-file.component.html +++ b/client/src/app/shared/forms/reactive-file.component.html | |||
@@ -4,7 +4,7 @@ | |||
4 | <input | 4 | <input |
5 | type="file" | 5 | type="file" |
6 | [name]="inputName" [id]="inputName" [accept]="extensions" | 6 | [name]="inputName" [id]="inputName" [accept]="extensions" |
7 | (change)="fileChange($event)" | 7 | (change)="fileChange($event)" [(ngModel)]="fileInputValue" |
8 | /> | 8 | /> |
9 | </div> | 9 | </div> |
10 | 10 | ||
diff --git a/client/src/app/shared/forms/reactive-file.component.ts b/client/src/app/shared/forms/reactive-file.component.ts index f5758b643..8d22aa56c 100644 --- a/client/src/app/shared/forms/reactive-file.component.ts +++ b/client/src/app/shared/forms/reactive-file.component.ts | |||
@@ -25,6 +25,7 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor { | |||
25 | @Output() fileChanged = new EventEmitter<Blob>() | 25 | @Output() fileChanged = new EventEmitter<Blob>() |
26 | 26 | ||
27 | allowedExtensionsMessage = '' | 27 | allowedExtensionsMessage = '' |
28 | fileInputValue: any | ||
28 | 29 | ||
29 | private file: File | 30 | private file: File |
30 | 31 | ||
@@ -63,6 +64,8 @@ export class ReactiveFileComponent implements OnInit, ControlValueAccessor { | |||
63 | 64 | ||
64 | writeValue (file: any) { | 65 | writeValue (file: any) { |
65 | this.file = file | 66 | this.file = file |
67 | |||
68 | if (!this.file) this.fileInputValue = null | ||
66 | } | 69 | } |
67 | 70 | ||
68 | registerOnChange (fn: (_: any) => void) { | 71 | registerOnChange (fn: (_: any) => void) { |
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 16dbf5cfc..2c40747ba 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 | |||
@@ -142,10 +142,33 @@ | |||
142 | 142 | ||
143 | <div class="form-group" *ngFor="let videoCaption of videoCaptions"> | 143 | <div class="form-group" *ngFor="let videoCaption of videoCaptions"> |
144 | 144 | ||
145 | <div *ngIf="videoCaption.action !== 'REMOVE'" class="caption-entry"> | 145 | <div class="caption-entry"> |
146 | <div class="caption-entry-label">{{ videoCaption.language.label }}</div> | 146 | <ng-container *ngIf="!videoCaption.action"> |
147 | <a | ||
148 | i18n-title title="See the subtitle file" class="caption-entry-label" target="_blank" rel="noopener noreferrer" | ||
149 | [href]="videoCaption.captionPath" | ||
150 | >{{ videoCaption.language.label }}</a> | ||
147 | 151 | ||
148 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span> | 152 | <div class="caption-entry-state">Already uploaded ✔</div> |
153 | |||
154 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Delete</span> | ||
155 | </ng-container> | ||
156 | |||
157 | <ng-container *ngIf="videoCaption.action === 'CREATE'"> | ||
158 | <span class="caption-entry-label">{{ videoCaption.language.label }}</span> | ||
159 | |||
160 | <div class="caption-entry-state caption-entry-state-create">Will be created on update</div> | ||
161 | |||
162 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel create</span> | ||
163 | </ng-container> | ||
164 | |||
165 | <ng-container *ngIf="videoCaption.action === 'REMOVE'"> | ||
166 | <span class="caption-entry-label">{{ videoCaption.language.label }}</span> | ||
167 | |||
168 | <div class="caption-entry-state caption-entry-state-delete">Will be deleted on update</div> | ||
169 | |||
170 | <span i18n class="caption-entry-delete" (click)="deleteCaption(videoCaption)">Cancel deletion</span> | ||
171 | </ng-container> | ||
149 | </div> | 172 | </div> |
150 | </div> | 173 | </div> |
151 | 174 | ||
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 058ccba36..f3d9ee44a 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 | |||
@@ -46,11 +46,34 @@ | |||
46 | height: 40px; | 46 | height: 40px; |
47 | align-items: center; | 47 | align-items: center; |
48 | 48 | ||
49 | a.caption-entry-label { | ||
50 | @include disable-default-a-behaviour; | ||
51 | |||
52 | color: #000; | ||
53 | |||
54 | &:hover { | ||
55 | opacity: 0.8; | ||
56 | } | ||
57 | } | ||
58 | |||
49 | .caption-entry-label { | 59 | .caption-entry-label { |
50 | font-size: 15px; | 60 | font-size: 15px; |
51 | font-weight: bold; | 61 | font-weight: bold; |
52 | 62 | ||
53 | margin-right: 20px; | 63 | margin-right: 20px; |
64 | width: 150px; | ||
65 | } | ||
66 | |||
67 | .caption-entry-state { | ||
68 | width: 200px; | ||
69 | |||
70 | &.caption-entry-state-create { | ||
71 | color: #39CC0B; | ||
72 | } | ||
73 | |||
74 | &.caption-entry-state-delete { | ||
75 | color: #FF0000; | ||
76 | } | ||
54 | } | 77 | } |
55 | 78 | ||
56 | .caption-entry-delete { | 79 | .caption-entry-delete { |
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 00c7bc41d..b8aef99dd 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 | |||
@@ -50,6 +50,7 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
50 | 50 | ||
51 | private schedulerInterval | 51 | private schedulerInterval |
52 | private firstPatchDone = false | 52 | private firstPatchDone = false |
53 | private initialVideoCaptions: string[] = [] | ||
53 | 54 | ||
54 | constructor ( | 55 | constructor ( |
55 | private formValidatorService: FormValidatorService, | 56 | private formValidatorService: FormValidatorService, |
@@ -127,6 +128,8 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
127 | this.videoLanguages = this.serverService.getVideoLanguages() | 128 | this.videoLanguages = this.serverService.getVideoLanguages() |
128 | 129 | ||
129 | this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute | 130 | this.schedulerInterval = setInterval(() => this.minScheduledDate = new Date(), 1000 * 60) // Update every minute |
131 | |||
132 | this.initialVideoCaptions = this.videoCaptions.map(c => c.language.id) | ||
130 | } | 133 | } |
131 | 134 | ||
132 | ngOnDestroy () { | 135 | ngOnDestroy () { |
@@ -147,7 +150,13 @@ export class VideoEditComponent implements OnInit, OnDestroy { | |||
147 | ) | 150 | ) |
148 | } | 151 | } |
149 | 152 | ||
150 | deleteCaption (caption: VideoCaptionEdit) { | 153 | async deleteCaption (caption: VideoCaptionEdit) { |
154 | // Caption recovers his former state | ||
155 | if (caption.action && this.initialVideoCaptions.indexOf(caption.language.id) !== -1) { | ||
156 | caption.action = undefined | ||
157 | return | ||
158 | } | ||
159 | |||
151 | // This caption is not on the server, just remove it from our array | 160 | // This caption is not on the server, just remove it from our array |
152 | if (caption.action === 'CREATE') { | 161 | if (caption.action === 'CREATE') { |
153 | removeElementFromArray(this.videoCaptions, caption) | 162 | removeElementFromArray(this.videoCaptions, caption) |
diff --git a/client/src/app/videos/+video-edit/video-update-routing.module.ts b/client/src/app/videos/+video-edit/video-update-routing.module.ts index dfeeb8a90..564b8fb45 100644 --- a/client/src/app/videos/+video-edit/video-update-routing.module.ts +++ b/client/src/app/videos/+video-edit/video-update-routing.module.ts | |||
@@ -6,12 +6,14 @@ import { MetaGuard } from '@ngx-meta/core' | |||
6 | import { LoginGuard } from '../../core' | 6 | import { LoginGuard } from '../../core' |
7 | import { VideoUpdateComponent } from './video-update.component' | 7 | import { VideoUpdateComponent } from './video-update.component' |
8 | import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver' | 8 | import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver' |
9 | import { CanDeactivateGuard } from '@app/shared/guards/can-deactivate-guard.service' | ||
9 | 10 | ||
10 | const videoUpdateRoutes: Routes = [ | 11 | const videoUpdateRoutes: Routes = [ |
11 | { | 12 | { |
12 | path: '', | 13 | path: '', |
13 | component: VideoUpdateComponent, | 14 | component: VideoUpdateComponent, |
14 | canActivate: [ MetaGuard, LoginGuard ], | 15 | canActivate: [ MetaGuard, LoginGuard ], |
16 | canDeactivate: [ CanDeactivateGuard ], | ||
15 | resolve: { | 17 | resolve: { |
16 | videoData: VideoUpdateResolver | 18 | videoData: VideoUpdateResolver |
17 | } | 19 | } |
diff --git a/client/src/app/videos/+video-edit/video-update.component.ts b/client/src/app/videos/+video-edit/video-update.component.ts index 952fe0293..798c48f3c 100644 --- a/client/src/app/videos/+video-edit/video-update.component.ts +++ b/client/src/app/videos/+video-edit/video-update.component.ts | |||
@@ -29,6 +29,8 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
29 | schedulePublicationPossible = false | 29 | schedulePublicationPossible = false |
30 | videoCaptions: VideoCaptionEdit[] = [] | 30 | videoCaptions: VideoCaptionEdit[] = [] |
31 | 31 | ||
32 | private updateDone = false | ||
33 | |||
32 | constructor ( | 34 | constructor ( |
33 | protected formValidatorService: FormValidatorService, | 35 | protected formValidatorService: FormValidatorService, |
34 | private route: ActivatedRoute, | 36 | private route: ActivatedRoute, |
@@ -65,7 +67,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
65 | this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE | 67 | this.schedulePublicationPossible = this.video.privacy === VideoPrivacy.PRIVATE |
66 | } | 68 | } |
67 | 69 | ||
68 | // FIXME: Angular does not detec | 70 | // FIXME: Angular does not detect the change inside this subscription, so use the patched setTimeout |
69 | setTimeout(() => this.hydrateFormFromVideo()) | 71 | setTimeout(() => this.hydrateFormFromVideo()) |
70 | }, | 72 | }, |
71 | 73 | ||
@@ -76,6 +78,16 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
76 | ) | 78 | ) |
77 | } | 79 | } |
78 | 80 | ||
81 | canDeactivate () { | ||
82 | if (this.updateDone === true) return { canDeactivate: true } | ||
83 | |||
84 | for (const caption of this.videoCaptions) { | ||
85 | if (caption.action) return { canDeactivate: false } | ||
86 | } | ||
87 | |||
88 | return { canDeactivate: this.formChanged === false } | ||
89 | } | ||
90 | |||
79 | checkForm () { | 91 | checkForm () { |
80 | this.forceCheck() | 92 | this.forceCheck() |
81 | 93 | ||
@@ -100,6 +112,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit { | |||
100 | ) | 112 | ) |
101 | .subscribe( | 113 | .subscribe( |
102 | () => { | 114 | () => { |
115 | this.updateDone = true | ||
103 | this.isUpdatingVideo = false | 116 | this.isUpdatingVideo = false |
104 | this.loadingBar.complete() | 117 | this.loadingBar.complete() |
105 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.')) | 118 | this.notificationsService.success(this.i18n('Success'), this.i18n('Video updated.')) |
diff --git a/client/src/app/videos/+video-edit/video-update.module.ts b/client/src/app/videos/+video-edit/video-update.module.ts index 4f5d72cec..d60aa699f 100644 --- a/client/src/app/videos/+video-edit/video-update.module.ts +++ b/client/src/app/videos/+video-edit/video-update.module.ts | |||
@@ -4,6 +4,7 @@ import { VideoEditModule } from './shared/video-edit.module' | |||
4 | import { VideoUpdateRoutingModule } from './video-update-routing.module' | 4 | import { VideoUpdateRoutingModule } from './video-update-routing.module' |
5 | import { VideoUpdateComponent } from './video-update.component' | 5 | import { VideoUpdateComponent } from './video-update.component' |
6 | import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver' | 6 | import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolver' |
7 | import { CanDeactivateGuard } from '@app/shared/guards/can-deactivate-guard.service' | ||
7 | 8 | ||
8 | @NgModule({ | 9 | @NgModule({ |
9 | imports: [ | 10 | imports: [ |
@@ -21,7 +22,8 @@ import { VideoUpdateResolver } from '@app/videos/+video-edit/video-update.resolv | |||
21 | ], | 22 | ], |
22 | 23 | ||
23 | providers: [ | 24 | providers: [ |
24 | VideoUpdateResolver | 25 | VideoUpdateResolver, |
26 | CanDeactivateGuard | ||
25 | ] | 27 | ] |
26 | }) | 28 | }) |
27 | export class VideoUpdateModule { } | 29 | export class VideoUpdateModule { } |