Improve frontend accessibility
authorChocobozzz <me@florianbigard.com>
Tue, 17 Jul 2018 12:44:19 +0000 (14:44 +0200)
committerChocobozzz <me@florianbigard.com>
Tue, 17 Jul 2018 12:56:15 +0000 (14:56 +0200)
In particular checkboxes, likes/dislikes, share button, video thumbnails
and help buttons

22 files changed:
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.html
client/src/app/+my-account/my-account-settings/my-account-video-settings/my-account-video-settings.component.scss
client/src/app/+my-account/my-account-videos/my-account-videos.component.html
client/src/app/+my-account/my-account-videos/my-account-videos.component.scss
client/src/app/shared/forms/peertube-checkbox.component.html [new file with mode: 0644]
client/src/app/shared/forms/peertube-checkbox.component.scss [new file with mode: 0644]
client/src/app/shared/forms/peertube-checkbox.component.ts [new file with mode: 0644]
client/src/app/shared/misc/help.component.html
client/src/app/shared/misc/help.component.ts
client/src/app/shared/shared.module.ts
client/src/app/shared/video-caption/video-caption.service.ts
client/src/app/shared/video/video-miniature.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.html
client/src/app/videos/+video-edit/shared/video-edit.component.scss
client/src/app/videos/+video-edit/shared/video-edit.component.ts
client/src/app/videos/+video-edit/video-update.component.ts
client/src/app/videos/+video-watch/video-watch.component.html
client/src/app/videos/+video-watch/video-watch.component.ts
client/src/assets/player/peertube-videojs-plugin.ts
client/src/sass/include/_mixins.scss
client/src/standalone/videos/embed.ts

index 97900e5238441456064fd43ce675b87384b813a8..6e3f83ccf6fef3490a4afb2f54f931d7f27ddcbf 100644 (file)
 
       <div i18n class="inner-form-title">Signup</div>
 
-      <div class="form-group">
-        <input type="checkbox" id="signupEnabled" formControlName="signupEnabled">
-
-        <label for="signupEnabled"></label>
-        <label i18n for="signupEnabled">Signup enabled</label>
-      </div>
+      <my-peertube-checkbox
+        inputName="signupEnabled" formControlName="signupEnabled"
+        i18n-labelText labelText="Signup enabled"
+      ></my-peertube-checkbox>
 
       <div *ngIf="isSignupEnabled()" class="form-group">
         <label i18n for="signupLimit">Signup limit</label>
         </div>
       </div>
 
-      <div class="form-group">
-        <input type="checkbox" id="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted">
-
-        <label for="servicesTwitterWhitelisted"></label>
-        <label i18n for="servicesTwitterWhitelisted">Instance whitelisted by Twitter</label>
-        <my-help
-          helpType="custom" i18n-customHtml
-          customHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
+      <my-peertube-checkbox
+        inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted"
+        i18n-labelText labelText="Instance whitelisted by Twitter"
+        i18n-helpHtml helpHtml="If your instance is whitelisted by Twitter, a video player will be embedded in the Twitter feed on PeerTube video share.<br />
 If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><br />
 Check this checkbox, save the configuration and test with a video URL of your instance (https://example.com/videos/watch/blabla) on <a target='_blank' rel='noopener noreferrer' href='https://cards-dev.twitter.com/validator'>https://cards-dev.twitter.com/validator</a> to see if you instance is whitelisted."
-        ></my-help>
-
-      </div>
+      ></my-peertube-checkbox>
     </tab>
 
     <tab i18n-heading heading="Advanced configuration">
 
       <div i18n class="inner-form-title">Transcoding</div>
 
-      <div class="form-group">
-        <input type="checkbox" id="transcodingEnabled" formControlName="transcodingEnabled">
-
-        <label for="transcodingEnabled"></label>
-        <label i18n for="transcodingEnabled">Transcoding enabled</label>
-
-        <my-help helpType="custom" i18n-customHtml customHtml="If you disable transcoding, many videos from your users will not work!"></my-help>
-      </div>
+      <my-peertube-checkbox
+        inputName="transcodingEnabled" formControlName="transcodingEnabled"
+        i18n-labelText labelText="Transcoding enabled"
+        i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!"
+      ></my-peertube-checkbox>
 
       <ng-template [ngIf]="isTranscodingEnabled()">
 
@@ -197,12 +186,11 @@ Check this checkbox, save the configuration and test with a video URL of your in
         </div>
 
         <div class="form-group" *ngFor="let resolution of resolutions">
-          <input
-              type="checkbox" [id]="getResolutionKey(resolution)"
-              [formControlName]="getResolutionKey(resolution)"
-          >
-          <label [for]="getResolutionKey(resolution)"></label>
-          <label i18n [for]="getResolutionKey(resolution)">Resolution {{ resolution }} enabled</label>
+          <my-peertube-checkbox
+            [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)"
+            i18n-labelText labelText="Resolution {{resolution}} enabled"
+          ></my-peertube-checkbox>
+
         </div>
       </ng-template>
 
index 98587eb18505a26917f8d624776e7ecc9e5111f7..96629940f9cd4f907b4a9814f93bcfe98ed86a7d 100644 (file)
     </div>
   </div>
 
-  <div class="form-group">
-    <input
-      type="checkbox" id="autoPlayVideo"
-      formControlName="autoPlayVideo"
-    >
-    <label for="autoPlayVideo"></label>
-    <label i18n for="autoPlayVideo">Automatically plays video</label>
-  </div>
+  <my-peertube-checkbox
+    inputName="autoPlayVideo" formControlName="autoPlayVideo"
+    i18n-labelText labelText="Automatically plays video"
+  ></my-peertube-checkbox>
 
   <input type="submit" i18n-value value="Save" [disabled]="!form.valid">
 </form>
index ed59e4689b7338e2394ebde65cd232b8a0afa5af..1881be762605734c80434a9ad1060ce3aa6329cd 100644 (file)
@@ -1,10 +1,6 @@
 @import '_variables';
 @import '_mixins';
 
-input[type=checkbox] {
-  @include peertube-checkbox(1px);
-}
-
 input[type=submit] {
   @include peertube-button;
   @include orange-button;
index 7ac6371dbd7a2b3d61dca7438d797b21886fcd78..4823e2db97c24cf753af7ad320daf16d6839f0d4 100644 (file)
@@ -9,8 +9,7 @@
   <div *ngFor="let videos of videoPages; let i = index" class="videos-page">
     <div class="video" *ngFor="let video of videos; let j = index">
       <div class="checkbox-container">
-        <input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" />
-        <label [for]="'video-check-' + video.id"></label>
+        <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
       </div>
 
       <my-video-thumbnail [video]="video"></my-video-thumbnail>
index 65c0c8bb2fb7192aae89478f0335682e5cdda882..9cd985273f062f4e71d0ac25425fc0d4b3396a64 100644 (file)
     margin-top: 47px;
   }
 
-  .checkbox-container {
-    display: flex;
-    align-items: center;
-    margin-right: 20px;
-    margin-left: 12px;
-
-    input[type=checkbox] {
-      @include peertube-checkbox(2px);
-    }
-  }
-
   my-video-thumbnail {
     margin-right: 10px;
   }
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.html b/client/src/app/shared/forms/peertube-checkbox.component.html
new file mode 100644 (file)
index 0000000..820e7a6
--- /dev/null
@@ -0,0 +1,9 @@
+<div class="form-group">
+  <label class="form-group-checkbox">
+    <input type="checkbox" [(ngModel)]="checked" (ngModelChange)="onModelChange()" [id]="inputName" [disabled]="isDisabled" />
+    <span role="checkbox" [attr.aria-checked]="checked"></span>
+    <span *ngIf="labelText">{{ labelText }}</span>
+  </label>
+
+  <my-help *ngIf="helpHtml" tooltipPlacement="top" helpType="custom" i18n-customHtml [customHtml]="helpHtml"></my-help>
+</div>
\ No newline at end of file
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.scss b/client/src/app/shared/forms/peertube-checkbox.component.scss
new file mode 100644 (file)
index 0000000..cbc50dc
--- /dev/null
@@ -0,0 +1,23 @@
+@import '_variables';
+@import '_mixins';
+
+.form-group {
+  display: flex;
+  align-items: center;
+
+  .form-group-checkbox {
+    display: flex;
+
+    span {
+      font-weight: $font-regular;
+      margin: 0;
+    }
+
+    input {
+      @include peertube-checkbox(1px);
+
+      width: 10px;
+      margin-right: 10px;
+    }
+  }
+}
\ No newline at end of file
diff --git a/client/src/app/shared/forms/peertube-checkbox.component.ts b/client/src/app/shared/forms/peertube-checkbox.component.ts
new file mode 100644 (file)
index 0000000..c626c4c
--- /dev/null
@@ -0,0 +1,45 @@
+import { Component, forwardRef, Input } from '@angular/core'
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
+
+@Component({
+  selector: 'my-peertube-checkbox',
+  styleUrls: [ './peertube-checkbox.component.scss' ],
+  templateUrl: './peertube-checkbox.component.html',
+  providers: [
+    {
+      provide: NG_VALUE_ACCESSOR,
+      useExisting: forwardRef(() => PeertubeCheckboxComponent),
+      multi: true
+    }
+  ]
+})
+export class PeertubeCheckboxComponent implements ControlValueAccessor {
+  @Input() checked = false
+  @Input() inputName: string
+  @Input() labelText: string
+  @Input() helpHtml: string
+
+  isDisabled = false
+
+  propagateChange = (_: any) => { /* empty */ }
+
+  writeValue (checked: boolean) {
+    this.checked = checked
+  }
+
+  registerOnChange (fn: (_: any) => void) {
+    this.propagateChange = fn
+  }
+
+  registerOnTouched () {
+    // Unused
+  }
+
+  onModelChange () {
+    this.propagateChange(this.checked)
+  }
+
+  setDisabledState (isDisabled: boolean) {
+    this.isDisabled = isDisabled
+  }
+}
index f2b6eca330c0d46113e1165f11cd10ce626dd87d..1c3863e5236544f3f540a76af9732bdf1f80fbff 100644 (file)
 </ng-template>
 
 <span
+  role="button"
   class="help-tooltip-button"
   title="Get help"
   i18n-title
+  [attr.aria-pressed]="isPopoverOpened"
   [popover]="tooltipTemplate"
   [placement]="tooltipPlacement"
   [outsideClick]="true"
+  (onHidden)="onPopoverHidden()"
+  (onShown)="onPopoverShown()"
 ></span>
index e7af61b4a8e36103d98d1f357aae8d5576e95473..ba0452e778393dd2ffdb5047a42660c5f8403c2d 100644 (file)
@@ -15,6 +15,7 @@ export class HelpComponent implements OnInit, OnChanges {
   @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom'
   @Input() tooltipPlacement = 'right'
 
+  isPopoverOpened = false
   mainHtml = ''
 
   constructor (private i18n: I18n) { }
@@ -27,6 +28,14 @@ export class HelpComponent implements OnInit, OnChanges {
     this.init()
   }
 
+  onPopoverHidden () {
+    this.isPopoverOpened = false
+  }
+
+  onPopoverShown () {
+    this.isPopoverOpened = true
+  }
+
   private init () {
     if (this.helpType === 'custom') {
       this.mainHtml = this.customHtml
index c3f4bf88b3e7e5cc4adba45981c679c8a9cb5470..fdfb90600b2f63fb13b48efc620ab25073524c73 100644 (file)
@@ -45,6 +45,7 @@ import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calend
 import { ScreenService } from '@app/shared/misc/screen.service'
 import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
 import { VideoCaptionService } from '@app/shared/video-caption'
+import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
 
 @NgModule({
   imports: [
@@ -77,7 +78,8 @@ import { VideoCaptionService } from '@app/shared/video-caption'
     MarkdownTextareaComponent,
     InfiniteScrollerDirective,
     HelpComponent,
-    ReactiveFileComponent
+    ReactiveFileComponent,
+    PeertubeCheckboxComponent
   ],
 
   exports: [
@@ -106,6 +108,7 @@ import { VideoCaptionService } from '@app/shared/video-caption'
     InfiniteScrollerDirective,
     HelpComponent,
     ReactiveFileComponent,
+    PeertubeCheckboxComponent,
 
     NumberFormatterPipe,
     ObjectLengthPipe,
index d1444902d1dd6e5dfe1a3c0d4149fbb62f38212b..e835981dd2d912c3d66f5a52c5dfc8ce0586fdaf 100644 (file)
@@ -42,8 +42,6 @@ export class VideoCaptionService {
   }
 
   updateCaptions (videoId: number | string, videoCaptions: VideoCaptionEdit[]) {
-    if (videoCaptions.length === 0) return of(true)
-
     const observables: Observable<any>[] = []
 
     for (const videoCaption of videoCaptions) {
@@ -58,6 +56,8 @@ export class VideoCaptionService {
       }
     }
 
+    if (observables.length === 0) return of(true)
+
     return forkJoin(observables)
   }
 }
index 3010e5ccc2864047c1122d56e3c1f2bd4788ca44..20020e2a82d4626b362f78524a5cdd86e22820c5 100644 (file)
@@ -3,7 +3,7 @@
 
   <div class="video-miniature-information">
     <a
-      class="video-miniature-name"
+      class="video-miniature-name" alt=""
       [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }"
     >
       {{ video.name }}
index 4675cb8277703159a758624e7078a967c3e3b7d2..16dbf5cfcd0ce244096ea89faf6f1e156f5c4d3a 100644 (file)
           </div>
         </div>
 
-        <div class="form-group form-group-checkbox">
-          <input type="checkbox" id="nsfw" formControlName="nsfw" />
-          <label for="nsfw"></label>
-          <label i18n for="nsfw">This video contains mature or explicit content</label>
-          <my-help
-            tooltipPlacement="top" helpType="custom" i18n-customHtml
-            customHtml="Some instances do not list videos containing mature or explicit content by default."
-          ></my-help>
-        </div>
-
-        <div class="form-group form-group-checkbox">
-          <input type="checkbox" id="commentsEnabled" formControlName="commentsEnabled" />
-          <label for="commentsEnabled"></label>
-          <label i18n for="commentsEnabled">Enable video comments</label>
-        </div>
-
-        <div class="form-group form-group-checkbox">
-          <input type="checkbox" id="waitTranscoding" formControlName="waitTranscoding" />
-          <label for="waitTranscoding"></label>
-          <label i18n for="waitTranscoding">Wait transcoding before publishing the video</label>
-          <my-help
-            tooltipPlacement="top" helpType="custom" i18n-customHtml
-            customHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
-          ></my-help>
-        </div>
+        <my-peertube-checkbox
+          inputName="nsfw" formControlName="nsfw"
+          i18n-labelText labelText="This video contains mature or explicit content"
+          i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default."
+        ></my-peertube-checkbox>
+
+        <my-peertube-checkbox
+          inputName="commentsEnabled" formControlName="commentsEnabled"
+          i18n-labelText labelText="Enable video comments"
+        ></my-peertube-checkbox>
+
+        <my-peertube-checkbox
+          inputName="waitTranscoding" formControlName="waitTranscoding"
+          i18n-labelText labelText="Wait transcoding before publishing the video"
+          i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
+        ></my-peertube-checkbox>
 
       </div>
     </tab>
index 03b8359dedbbfe3207b9016edc65c1e22f6f5dc8..058ccba363fe89b60c9ae5b720d5ba36620c8faf 100644 (file)
   input {
     @include peertube-input-text(100%);
     display: block;
-
-    &[type=checkbox] {
-      @include peertube-checkbox(1px);
-    }
   }
 
   input, select {
     font-size: 15px
   }
 
-  .form-group-checkbox {
-    display: flex;
-    align-items: center;
-
-    label {
-      font-weight: $font-regular;
-      margin: 0;
-    }
-
-    input {
-      width: 10px;
-      margin-right: 10px;
-    }
-  }
-
   .label-tags + span {
     font-size: 15px;
   }
index 743c015cbac23759391217b0e34c38a1eea06ba9..00c7bc41dd0cf058b53d4350156251f9e0e5ff58 100644 (file)
@@ -49,6 +49,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
   calendarDateFormat: string
 
   private schedulerInterval
+  private firstPatchDone = false
 
   constructor (
     private formValidatorService: FormValidatorService,
@@ -167,6 +168,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
       .pipe(map(res => parseInt(res.toString(), 10)))
       .subscribe(
         newPrivacyId => {
+
           this.schedulePublicationEnabled = newPrivacyId === this.SPECIAL_SCHEDULED_PRIVACY
 
           // Value changed
@@ -182,11 +184,18 @@ export class VideoEditComponent implements OnInit, OnDestroy {
             scheduleControl.clearValidators()
 
             waitTranscodingControl.enable()
-            waitTranscodingControl.setValue(true)
+
+            // Do not update the control value on first patch (values come from the server)
+            if (this.firstPatchDone === true) {
+              waitTranscodingControl.setValue(true)
+            }
           }
 
           scheduleControl.updateValueAndValidity()
           waitTranscodingControl.updateValueAndValidity()
+
+          this.firstPatchDone = true
+
         }
       )
   }
index 774772e148085c6fe5521e6145eef041ff1b067b..952fe02933516d43fafaddd09d77b85e9daa5e40 100644 (file)
@@ -107,6 +107,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
           },
 
           err => {
+            this.loadingBar.complete()
             this.isUpdatingVideo = false
             this.notificationsService.error(this.i18n('Error'), err.message)
             console.error(err)
index 21f8f55344910d70f51090a00d9c2316e284f0c6..e7e9f367c8bb28cbd687df2f664328492c59fd7a 100644 (file)
         <div class="video-actions-rates">
           <div class="video-actions">
             <div
-                *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
-                class="action-button action-button-like"
+              *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
+              class="action-button action-button-like" role="button" [attr.aria-pressed]="userRating === 'like'"
             >
               <span class="icon icon-like" i18n-title title="Like this video" ></span>
             </div>
 
             <div
-                *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
-                class="action-button action-button-dislike"
+              *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
+              class="action-button action-button-dislike" role="button" [attr.aria-pressed]="userRating === 'dislike'"
             >
               <span class="icon icon-dislike" i18n-title title="Dislike this video"></span>
             </div>
               <span class="icon-text" i18n>Support</span>
             </div>
 
-            <div (click)="showShareModal()" class="action-button action-button-share">
+            <div (click)="showShareModal()" class="action-button action-button-share" role="button">
               <span class="icon icon-share"></span>
               <span class="icon-text" i18n>Share</span>
             </div>
 
-            <div class="action-more" dropdown dropup="true" placement="right">
+            <div class="action-more" dropdown dropup="true" placement="right" role="button">
               <div class="action-button" dropdownToggle>
                 <span class="icon icon-more"></span>
               </div>
index 43afbae1a19261df7b159a24d7710eb12e06f3b3..4f8549e8f07e6418e9b65b25983382dcf4eeaed2 100644 (file)
@@ -314,7 +314,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
     if (!errorMessage) return
 
     // Display a message in the video player instead of a notification
-    if (errorMessage.indexOf('http error') !== -1) {
+    if (errorMessage.indexOf('from xs param') !== -1) {
       this.flushPlayer()
       this.remoteServerDown = true
       return
index 6d96e1c0b2121cda9307104a5a69619078f5dd66..b02f4373a0df47ab6fbf299393c64646a4123b5f 100644 (file)
@@ -269,7 +269,7 @@ class PeerTubePlugin extends Plugin {
       }
 
       // Remote instance is down
-      if (err.message.indexOf('http error from xs param') !== -1) {
+      if (err.message.indexOf('from xs param') !== -1) {
         this.handleError(err)
       }
 
index 9f8346950181dbb3bae0c4d7073bcdc3d1e12656..3d518394aaf6f53ca067b922cbfb0b43e3f6f377 100644 (file)
 @mixin peertube-checkbox ($border-width) {
   display: none;
 
-  & + label {
+  & + span {
     position: relative;
     width: 18px;
     height: 18px;
     }
   }
 
-  &:checked + label {
+  &:checked + span {
     border-color: transparent;
     background: $orange-color;
     animation: jelly 0.6s ease;
     }
   }
 
-  & + label + label {
+  & + span + span {
     font-size: 15px;
     font-weight: $font-regular;
     margin-left: 5px;
     display: inline;
   }
 
-  &[disabled] + label,
-  &[disabled] + label + label{
+  &[disabled] + span,
+  &[disabled] + span + span{
     opacity: 0.5;
     cursor: default;
   }
index cb05ec2b56d72e0b100393e1eb20e158ef67423a..98ce732579a4f01ba83cf0957aa5939ff3175809 100644 (file)
@@ -321,7 +321,7 @@ class PeerTubeEmbed {
   }
 
   private handleError (err: Error) {
-    if (err.message.indexOf('http error') !== -1) {
+    if (err.message.indexOf('from xs param') !== -1) {
       this.player.dispose()
       this.videoElement = null
       this.displayError('This video is not available because the remote instance is not responding.')