1
0
Fork 0

Improve frontend accessibility

In particular checkboxes, likes/dislikes, share button, video thumbnails
and help buttons
This commit is contained in:
Chocobozzz 2018-07-17 14:44:19 +02:00
parent 4bdd9473fd
commit 0f7fedc398
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
22 changed files with 160 additions and 117 deletions

View File

@ -85,12 +85,10 @@
<div i18n class="inner-form-title">Signup</div> <div i18n class="inner-form-title">Signup</div>
<div class="form-group"> <my-peertube-checkbox
<input type="checkbox" id="signupEnabled" formControlName="signupEnabled"> inputName="signupEnabled" formControlName="signupEnabled"
i18n-labelText labelText="Signup enabled"
<label for="signupEnabled"></label> ></my-peertube-checkbox>
<label i18n for="signupEnabled">Signup enabled</label>
</div>
<div *ngIf="isSignupEnabled()" class="form-group"> <div *ngIf="isSignupEnabled()" class="form-group">
<label i18n for="signupLimit">Signup limit</label> <label i18n for="signupLimit">Signup limit</label>
@ -152,33 +150,24 @@
</div> </div>
</div> </div>
<div class="form-group"> <my-peertube-checkbox
<input type="checkbox" id="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted"> inputName="servicesTwitterWhitelisted" formControlName="servicesTwitterWhitelisted"
i18n-labelText labelText="Instance whitelisted by Twitter"
<label for="servicesTwitterWhitelisted"></label> 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 />
<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 />
If the instance is not whitelisted, we use an image link card that will redirect on your PeerTube instance.<br /><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." 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> ></my-peertube-checkbox>
</div>
</tab> </tab>
<tab i18n-heading heading="Advanced configuration"> <tab i18n-heading heading="Advanced configuration">
<div i18n class="inner-form-title">Transcoding</div> <div i18n class="inner-form-title">Transcoding</div>
<div class="form-group"> <my-peertube-checkbox
<input type="checkbox" id="transcodingEnabled" formControlName="transcodingEnabled"> inputName="transcodingEnabled" formControlName="transcodingEnabled"
i18n-labelText labelText="Transcoding enabled"
<label for="transcodingEnabled"></label> i18n-helpHtml helpHtml="If you disable transcoding, many videos from your users will not work!"
<label i18n for="transcodingEnabled">Transcoding enabled</label> ></my-peertube-checkbox>
<my-help helpType="custom" i18n-customHtml customHtml="If you disable transcoding, many videos from your users will not work!"></my-help>
</div>
<ng-template [ngIf]="isTranscodingEnabled()"> <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>
<div class="form-group" *ngFor="let resolution of resolutions"> <div class="form-group" *ngFor="let resolution of resolutions">
<input <my-peertube-checkbox
type="checkbox" [id]="getResolutionKey(resolution)" [inputName]="getResolutionKey(resolution)" [formControlName]="getResolutionKey(resolution)"
[formControlName]="getResolutionKey(resolution)" i18n-labelText labelText="Resolution {{resolution}} enabled"
> ></my-peertube-checkbox>
<label [for]="getResolutionKey(resolution)"></label>
<label i18n [for]="getResolutionKey(resolution)">Resolution {{ resolution }} enabled</label>
</div> </div>
</ng-template> </ng-template>

View File

@ -15,14 +15,10 @@
</div> </div>
</div> </div>
<div class="form-group"> <my-peertube-checkbox
<input inputName="autoPlayVideo" formControlName="autoPlayVideo"
type="checkbox" id="autoPlayVideo" i18n-labelText labelText="Automatically plays video"
formControlName="autoPlayVideo" ></my-peertube-checkbox>
>
<label for="autoPlayVideo"></label>
<label i18n for="autoPlayVideo">Automatically plays video</label>
</div>
<input type="submit" i18n-value value="Save" [disabled]="!form.valid"> <input type="submit" i18n-value value="Save" [disabled]="!form.valid">
</form> </form>

View File

@ -1,10 +1,6 @@
@import '_variables'; @import '_variables';
@import '_mixins'; @import '_mixins';
input[type=checkbox] {
@include peertube-checkbox(1px);
}
input[type=submit] { input[type=submit] {
@include peertube-button; @include peertube-button;
@include orange-button; @include orange-button;

View File

@ -9,8 +9,7 @@
<div *ngFor="let videos of videoPages; let i = index" class="videos-page"> <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="video" *ngFor="let video of videos; let j = index">
<div class="checkbox-container"> <div class="checkbox-container">
<input [id]="'video-check-' + video.id" type="checkbox" [(ngModel)]="checkedVideos[video.id]" /> <my-peertube-checkbox [inputName]="'video-check-' + video.id" [(ngModel)]="checkedVideos[video.id]"></my-peertube-checkbox>
<label [for]="'video-check-' + video.id"></label>
</div> </div>
<my-video-thumbnail [video]="video"></my-video-thumbnail> <my-video-thumbnail [video]="video"></my-video-thumbnail>

View File

@ -52,17 +52,6 @@
margin-top: 47px; 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 { my-video-thumbnail {
margin-right: 10px; margin-right: 10px;
} }

View File

@ -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>

View File

@ -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;
}
}
}

View File

@ -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
}
}

View File

@ -13,10 +13,14 @@
</ng-template> </ng-template>
<span <span
role="button"
class="help-tooltip-button" class="help-tooltip-button"
title="Get help" title="Get help"
i18n-title i18n-title
[attr.aria-pressed]="isPopoverOpened"
[popover]="tooltipTemplate" [popover]="tooltipTemplate"
[placement]="tooltipPlacement" [placement]="tooltipPlacement"
[outsideClick]="true" [outsideClick]="true"
(onHidden)="onPopoverHidden()"
(onShown)="onPopoverShown()"
></span> ></span>

View File

@ -15,6 +15,7 @@ export class HelpComponent implements OnInit, OnChanges {
@Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom' @Input() helpType: 'custom' | 'markdownText' | 'markdownEnhanced' = 'custom'
@Input() tooltipPlacement = 'right' @Input() tooltipPlacement = 'right'
isPopoverOpened = false
mainHtml = '' mainHtml = ''
constructor (private i18n: I18n) { } constructor (private i18n: I18n) { }
@ -27,6 +28,14 @@ export class HelpComponent implements OnInit, OnChanges {
this.init() this.init()
} }
onPopoverHidden () {
this.isPopoverOpened = false
}
onPopoverShown () {
this.isPopoverOpened = true
}
private init () { private init () {
if (this.helpType === 'custom') { if (this.helpType === 'custom') {
this.mainHtml = this.customHtml this.mainHtml = this.customHtml

View File

@ -45,6 +45,7 @@ import { I18nPrimengCalendarService } from '@app/shared/i18n/i18n-primeng-calend
import { ScreenService } from '@app/shared/misc/screen.service' import { ScreenService } from '@app/shared/misc/screen.service'
import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service' import { VideoCaptionsValidatorsService } from '@app/shared/forms/form-validators/video-captions-validators.service'
import { VideoCaptionService } from '@app/shared/video-caption' import { VideoCaptionService } from '@app/shared/video-caption'
import { PeertubeCheckboxComponent } from '@app/shared/forms/peertube-checkbox.component'
@NgModule({ @NgModule({
imports: [ imports: [
@ -77,7 +78,8 @@ import { VideoCaptionService } from '@app/shared/video-caption'
MarkdownTextareaComponent, MarkdownTextareaComponent,
InfiniteScrollerDirective, InfiniteScrollerDirective,
HelpComponent, HelpComponent,
ReactiveFileComponent ReactiveFileComponent,
PeertubeCheckboxComponent
], ],
exports: [ exports: [
@ -106,6 +108,7 @@ import { VideoCaptionService } from '@app/shared/video-caption'
InfiniteScrollerDirective, InfiniteScrollerDirective,
HelpComponent, HelpComponent,
ReactiveFileComponent, ReactiveFileComponent,
PeertubeCheckboxComponent,
NumberFormatterPipe, NumberFormatterPipe,
ObjectLengthPipe, ObjectLengthPipe,

View File

@ -42,8 +42,6 @@ export class VideoCaptionService {
} }
updateCaptions (videoId: number | string, videoCaptions: VideoCaptionEdit[]) { updateCaptions (videoId: number | string, videoCaptions: VideoCaptionEdit[]) {
if (videoCaptions.length === 0) return of(true)
const observables: Observable<any>[] = [] const observables: Observable<any>[] = []
for (const videoCaption of videoCaptions) { for (const videoCaption of videoCaptions) {
@ -58,6 +56,8 @@ export class VideoCaptionService {
} }
} }
if (observables.length === 0) return of(true)
return forkJoin(observables) return forkJoin(observables)
} }
} }

View File

@ -3,7 +3,7 @@
<div class="video-miniature-information"> <div class="video-miniature-information">
<a <a
class="video-miniature-name" class="video-miniature-name" alt=""
[routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }" [routerLink]="[ '/videos/watch', video.uuid ]" [attr.title]="video.name" [ngClass]="{ 'blur-filter': isVideoBlur() }"
> >
{{ video.name }} {{ video.name }}

View File

@ -110,31 +110,22 @@
</div> </div>
</div> </div>
<div class="form-group form-group-checkbox"> <my-peertube-checkbox
<input type="checkbox" id="nsfw" formControlName="nsfw" /> inputName="nsfw" formControlName="nsfw"
<label for="nsfw"></label> i18n-labelText labelText="This video contains mature or explicit content"
<label i18n for="nsfw">This video contains mature or explicit content</label> i18n-helpHtml helpHtml="Some instances do not list videos containing mature or explicit content by default."
<my-help ></my-peertube-checkbox>
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"> <my-peertube-checkbox
<input type="checkbox" id="commentsEnabled" formControlName="commentsEnabled" /> inputName="commentsEnabled" formControlName="commentsEnabled"
<label for="commentsEnabled"></label> i18n-labelText labelText="Enable video comments"
<label i18n for="commentsEnabled">Enable video comments</label> ></my-peertube-checkbox>
</div>
<div class="form-group form-group-checkbox"> <my-peertube-checkbox
<input type="checkbox" id="waitTranscoding" formControlName="waitTranscoding" /> inputName="waitTranscoding" formControlName="waitTranscoding"
<label for="waitTranscoding"></label> i18n-labelText labelText="Wait transcoding before publishing the video"
<label i18n for="waitTranscoding">Wait transcoding before publishing the video</label> i18n-helpHtml helpHtml="If you decide not to wait for transcoding before publishing the video, it could be unplayable until transcoding ends."
<my-help ></my-peertube-checkbox>
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>
</div> </div>
</tab> </tab>

View File

@ -16,31 +16,12 @@
input { input {
@include peertube-input-text(100%); @include peertube-input-text(100%);
display: block; display: block;
&[type=checkbox] {
@include peertube-checkbox(1px);
}
} }
input, select { input, select {
font-size: 15px 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 { .label-tags + span {
font-size: 15px; font-size: 15px;
} }

View File

@ -49,6 +49,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
calendarDateFormat: string calendarDateFormat: string
private schedulerInterval private schedulerInterval
private firstPatchDone = false
constructor ( constructor (
private formValidatorService: FormValidatorService, private formValidatorService: FormValidatorService,
@ -167,6 +168,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
.pipe(map(res => parseInt(res.toString(), 10))) .pipe(map(res => parseInt(res.toString(), 10)))
.subscribe( .subscribe(
newPrivacyId => { newPrivacyId => {
this.schedulePublicationEnabled = newPrivacyId === this.SPECIAL_SCHEDULED_PRIVACY this.schedulePublicationEnabled = newPrivacyId === this.SPECIAL_SCHEDULED_PRIVACY
// Value changed // Value changed
@ -182,11 +184,18 @@ export class VideoEditComponent implements OnInit, OnDestroy {
scheduleControl.clearValidators() scheduleControl.clearValidators()
waitTranscodingControl.enable() waitTranscodingControl.enable()
// Do not update the control value on first patch (values come from the server)
if (this.firstPatchDone === true) {
waitTranscodingControl.setValue(true) waitTranscodingControl.setValue(true)
} }
}
scheduleControl.updateValueAndValidity() scheduleControl.updateValueAndValidity()
waitTranscodingControl.updateValueAndValidity() waitTranscodingControl.updateValueAndValidity()
this.firstPatchDone = true
} }
) )
} }

View File

@ -107,6 +107,7 @@ export class VideoUpdateComponent extends FormReactive implements OnInit {
}, },
err => { err => {
this.loadingBar.complete()
this.isUpdatingVideo = false this.isUpdatingVideo = false
this.notificationsService.error(this.i18n('Error'), err.message) this.notificationsService.error(this.i18n('Error'), err.message)
console.error(err) console.error(err)

View File

@ -49,14 +49,14 @@
<div class="video-actions"> <div class="video-actions">
<div <div
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
class="action-button action-button-like" 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> <span class="icon icon-like" i18n-title title="Like this video" ></span>
</div> </div>
<div <div
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
class="action-button action-button-dislike" 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> <span class="icon icon-dislike" i18n-title title="Dislike this video"></span>
</div> </div>
@ -66,12 +66,12 @@
<span class="icon-text" i18n>Support</span> <span class="icon-text" i18n>Support</span>
</div> </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 icon-share"></span>
<span class="icon-text" i18n>Share</span> <span class="icon-text" i18n>Share</span>
</div> </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> <div class="action-button" dropdownToggle>
<span class="icon icon-more"></span> <span class="icon icon-more"></span>
</div> </div>

View File

@ -314,7 +314,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
if (!errorMessage) return if (!errorMessage) return
// Display a message in the video player instead of a notification // 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.flushPlayer()
this.remoteServerDown = true this.remoteServerDown = true
return return

View File

@ -269,7 +269,7 @@ class PeerTubePlugin extends Plugin {
} }
// Remote instance is down // 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) this.handleError(err)
} }

View File

@ -240,7 +240,7 @@
@mixin peertube-checkbox ($border-width) { @mixin peertube-checkbox ($border-width) {
display: none; display: none;
& + label { & + span {
position: relative; position: relative;
width: 18px; width: 18px;
height: 18px; height: 18px;
@ -263,7 +263,7 @@
} }
} }
&:checked + label { &:checked + span {
border-color: transparent; border-color: transparent;
background: $orange-color; background: $orange-color;
animation: jelly 0.6s ease; animation: jelly 0.6s ease;
@ -274,7 +274,7 @@
} }
} }
& + label + label { & + span + span {
font-size: 15px; font-size: 15px;
font-weight: $font-regular; font-weight: $font-regular;
margin-left: 5px; margin-left: 5px;
@ -282,8 +282,8 @@
display: inline; display: inline;
} }
&[disabled] + label, &[disabled] + span,
&[disabled] + label + label{ &[disabled] + span + span{
opacity: 0.5; opacity: 0.5;
cursor: default; cursor: default;
} }

View File

@ -321,7 +321,7 @@ class PeerTubeEmbed {
} }
private handleError (err: Error) { private handleError (err: Error) {
if (err.message.indexOf('http error') !== -1) { if (err.message.indexOf('from xs param') !== -1) {
this.player.dispose() this.player.dispose()
this.videoElement = null this.videoElement = null
this.displayError('This video is not available because the remote instance is not responding.') this.displayError('This video is not available because the remote instance is not responding.')