Fix progress bar accessibility
This commit is contained in:
parent
51ce03e3cf
commit
d96ec7da71
|
@ -118,27 +118,19 @@
|
|||
</td>
|
||||
|
||||
<td *ngIf="isSelected('quota')">
|
||||
<div class="progress" i18n-title title="Total video quota">
|
||||
<div
|
||||
class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaPercentage(user) + '%' }"
|
||||
[attr.aria-valuenow]="user.rawVideoQuotaUsed" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuota"
|
||||
>
|
||||
</div>
|
||||
<span>{{ user.videoQuotaUsed }}</span>
|
||||
<span>{{ user.videoQuota }}</span>
|
||||
</div>
|
||||
<my-progress-bar
|
||||
i18n-label label="Total video quota" [max]="user.rawVideoQuota" [value]="user.rawVideoQuotaUsed"
|
||||
[valueFormatted]="user.videoQuotaUsed" [maxFormatted]="user.videoQuota" size="small"
|
||||
>
|
||||
</my-progress-bar>
|
||||
</td>
|
||||
|
||||
<td *ngIf="isSelected('quotaDaily')">
|
||||
<div class="progress" i18n-title title="Total daily video quota">
|
||||
<div
|
||||
class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }"
|
||||
[attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily"
|
||||
>
|
||||
</div>
|
||||
<span>{{ user.videoQuotaUsedDaily }}</span>
|
||||
<span>{{ user.videoQuotaDaily }}</span>
|
||||
</div>
|
||||
<my-progress-bar
|
||||
i18n-label label="Total daily video quota" [max]="user.rawVideoQuotaDaily" [value]="user.rawVideoQuotaUsedDaily"
|
||||
[valueFormatted]="user.videoQuotaUsedDaily" [maxFormatted]="user.videoQuotaDaily" size="small"
|
||||
>
|
||||
</my-progress-bar>
|
||||
</td>
|
||||
|
||||
<td *ngIf="isSelected('totalVideoFileSize')">
|
||||
|
|
|
@ -24,18 +24,7 @@ my-global-icon {
|
|||
width: 18px;
|
||||
}
|
||||
|
||||
.progress {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
|
||||
@include progressbar($small: true);
|
||||
}
|
||||
|
||||
@media screen and (max-width: $primeng-breakpoint) {
|
||||
.progress {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-cell {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
} from '../../../../shared/shared-moderation/user-moderation-dropdown.component'
|
||||
import { TableExpanderIconComponent } from '../../../../shared/shared-tables/table-expander-icon.component'
|
||||
import { UserEmailInfoComponent } from '../../../shared/user-email-info.component'
|
||||
import { ProgressBarComponent } from '@app/shared/shared-main/misc/progress-bar.component'
|
||||
|
||||
type UserForList = User & {
|
||||
rawVideoQuota: number
|
||||
|
@ -65,7 +66,8 @@ type UserForList = User & {
|
|||
AutoColspanDirective,
|
||||
UserBanModalComponent,
|
||||
DatePipe,
|
||||
BytesPipe
|
||||
BytesPipe,
|
||||
ProgressBarComponent
|
||||
]
|
||||
})
|
||||
export class UserListComponent extends RestTable <User> implements OnInit {
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<div
|
||||
role="progressbar" class="progress-container" tabindex="0" [ngbTooltip]="label"
|
||||
[attr.aria-valuenow]="value" aria-valuemin="0" [attr.aria-valuemax]="max"
|
||||
[attr.aria-label]="label"
|
||||
[ngClass]="{ red: theme === 'red', green: theme === 'green', small: size === 'small' }"
|
||||
>
|
||||
<div class="progress-bar" [style]="{ width: percentage() + '%' }"></div>
|
||||
|
||||
<span class="value">{{ valueFormatted }}</span>
|
||||
<span class="max" *ngIf="maxFormatted">{{ maxFormatted }}</span>
|
||||
</div>
|
|
@ -0,0 +1,69 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
.progress-container {
|
||||
background-color: pvar(--greyBackgroundColor);
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
height: 2rem;
|
||||
font-size: 0.85rem;
|
||||
font-weight: $font-semibold;
|
||||
border-radius: 0.25rem;
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
color: pvar(--greyForegroundColor);
|
||||
line-height: 2rem;
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
.value {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.max {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
font-weight: $font-semibold;
|
||||
color: pvar(--mainBackgroundColor);
|
||||
background-color: pvar(--mainColor);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
transition: width 0.6s ease;
|
||||
}
|
||||
|
||||
&.red,
|
||||
&.green {
|
||||
span {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&.red .progress-bar {
|
||||
background-color: $red;
|
||||
}
|
||||
|
||||
&.green .progress-bar {
|
||||
background-color: $green;
|
||||
}
|
||||
|
||||
&.small {
|
||||
height: 1rem;
|
||||
font-weight: normal;
|
||||
font-size: 0.75rem;
|
||||
|
||||
span {
|
||||
margin: 0 5px;
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
@include disable-outline;
|
||||
@include button-focus(pvar(--mainColorLightest));
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import { Component, Input, numberAttribute } from '@angular/core'
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
|
||||
|
||||
@Component({
|
||||
selector: 'my-progress-bar',
|
||||
styleUrls: [ './progress-bar.component.scss' ],
|
||||
templateUrl: './progress-bar.component.html',
|
||||
standalone: true,
|
||||
imports: [
|
||||
CommonModule,
|
||||
NgbTooltip
|
||||
]
|
||||
})
|
||||
|
||||
export class ProgressBarComponent {
|
||||
@Input({ required: true, transform: numberAttribute }) value: number
|
||||
@Input({ required: true }) label: string
|
||||
|
||||
@Input({ required: true }) valueFormatted: string | number
|
||||
|
||||
@Input() maxFormatted: string
|
||||
|
||||
@Input() size: 'normal' | 'small' = 'normal'
|
||||
|
||||
@Input({ transform: numberAttribute }) max = 100
|
||||
@Input({ transform: numberAttribute }) min = 0
|
||||
|
||||
@Input() theme: 'green' | 'red' | 'main' = 'main'
|
||||
|
||||
percentage () {
|
||||
return this.value * 100 / this.max
|
||||
}
|
||||
}
|
|
@ -2,22 +2,21 @@
|
|||
<div>
|
||||
<div class="mb-2 fw-bold" i18n>Total video quota</div>
|
||||
|
||||
<div class="progress" tabindex="0" [ngbTooltip]="titleVideoQuota()">
|
||||
<div class="progress-bar" tabindex="0" role="progressbar" [style]="{ width: userVideoQuotaPercentage + '%' }"
|
||||
[attr.aria-valuenow]="userVideoQuotaUsed" aria-valuemin="0" [attr.aria-valuemax]="user?.videoQuota"></div>
|
||||
<span>{{ userVideoQuotaUsed | bytes: 1 }}</span>
|
||||
<span>{{ userVideoQuota }}</span>
|
||||
</div>
|
||||
<my-progress-bar
|
||||
[label]="labelQuota()" [max]="user?.videoQuota || 0" [value]="userVideoQuotaUsed"
|
||||
[valueFormatted]="userVideoQuotaUsed | bytes: 1" [maxFormatted]="userVideoQuota"
|
||||
height="2rem"
|
||||
>
|
||||
</my-progress-bar>
|
||||
</div>
|
||||
|
||||
<div *ngIf="hasDailyQuota()" class="mt-3">
|
||||
<div class="mb-2 fw-bold" i18n>Daily video quota</div>
|
||||
|
||||
<div class="progress" tabindex="0" [ngbTooltip]="titleVideoQuotaDaily()">
|
||||
<div class="progress-bar" role="progressbar" [style]="{ width: userVideoQuotaDailyPercentage + '%' }"
|
||||
[attr.aria-valuenow]="userVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user?.videoQuotaDaily"></div>
|
||||
<span>{{ userVideoQuotaUsedDaily | bytes: 1 }}</span>
|
||||
<span>{{ userVideoQuotaDaily }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<my-progress-bar
|
||||
[label]="labelQuotaDaily()" [max]="user?.videoQuotaDaily || 0" [value]="userVideoQuotaUsedDaily"
|
||||
[valueFormatted]="userVideoQuotaUsedDaily | bytes: 1" [maxFormatted]="userVideoQuotaDaily"
|
||||
height="2rem"
|
||||
>
|
||||
</my-progress-bar>
|
||||
</div>
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
.user-quota {
|
||||
label {
|
||||
@include margin-right(5px);
|
||||
}
|
||||
|
||||
&,
|
||||
.progress {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.progress {
|
||||
@include disable-outline;
|
||||
@include button-focus(pvar(--mainColorLightest));
|
||||
@include progressbar($height: 2rem);
|
||||
|
||||
span {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,15 @@
|
|||
import { NgIf } from '@angular/common'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { AuthService, UserService } from '@app/core'
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { first } from 'rxjs'
|
||||
import { BytesPipe } from '../angular/bytes.pipe'
|
||||
import { ProgressBarComponent } from '../misc/progress-bar.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-user-quota',
|
||||
templateUrl: './user-quota.component.html',
|
||||
styleUrls: [ './user-quota.component.scss' ],
|
||||
standalone: true,
|
||||
imports: [ NgbTooltip, NgIf, BytesPipe ]
|
||||
imports: [ NgIf, BytesPipe, ProgressBarComponent ]
|
||||
})
|
||||
|
||||
export class UserQuotaComponent implements OnInit {
|
||||
|
@ -62,11 +61,11 @@ export class UserQuotaComponent implements OnInit {
|
|||
return this.user.videoQuotaDaily !== -1
|
||||
}
|
||||
|
||||
titleVideoQuota () {
|
||||
return `${new BytesPipe().transform(this.userVideoQuotaUsed, 0).toString()} / ${this.userVideoQuota}`
|
||||
labelQuota () {
|
||||
return `Total video quota: ${new BytesPipe().transform(this.userVideoQuotaUsed, 0).toString()} / ${this.userVideoQuota}`
|
||||
}
|
||||
|
||||
titleVideoQuotaDaily () {
|
||||
return `${new BytesPipe().transform(this.userVideoQuotaUsedDaily, 0).toString()} / ${this.userVideoQuotaDaily}`
|
||||
labelQuotaDaily () {
|
||||
return `Total daily video quota: ${new BytesPipe().transform(this.userVideoQuotaUsedDaily, 0).toString()} / ${this.userVideoQuotaDaily}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
<!-- Upload progress/cancel/error/success header -->
|
||||
<div *ngIf="isUploading && !error" class="upload-progress-cancel">
|
||||
<div class="progress" i18n-title title="Total uploaded">
|
||||
<div
|
||||
class="progress-bar" role="progressbar"
|
||||
[style]="{ width: uploadPercents + '%' }" [attr.aria-valuenow]="uploadPercents" aria-valuemin="0" [attr.aria-valuemax]="100"
|
||||
>
|
||||
<span *ngIf="uploadPercents === 100 && uploaded === false" i18n>Processing…</span>
|
||||
<span *ngIf="uploadPercents !== 100 || uploaded">{{ uploadPercents }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<my-progress-bar
|
||||
i18n-label label="Total uploaded" theme="green"
|
||||
[value]="uploadPercents" [valueFormatted]="getUploadingLabel()"
|
||||
>
|
||||
</my-progress-bar>
|
||||
|
||||
<input
|
||||
*ngIf="uploaded === false"
|
||||
|
@ -17,11 +13,11 @@
|
|||
</div>
|
||||
|
||||
<div *ngIf="error && enableRetryAfterError" class="upload-progress-retry">
|
||||
<div class="progress">
|
||||
<div class="progress-bar red" role="progressbar" [style]="{ width: '100%' }" [attr.aria-valuenow]="100" aria-valuemin="0" [attr.aria-valuemax]="100">
|
||||
<span>{{ error }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<my-progress-bar
|
||||
[label]="error" value="100" [valueFormatted]="error"
|
||||
theme="red"
|
||||
>
|
||||
</my-progress-bar>
|
||||
|
||||
<input type="button" class="peertube-button grey-button ms-1" i18n-value="Retry failed upload" value="Retry" (click)="retry.emit()" />
|
||||
<input type="button" class="peertube-button grey-button ms-1" i18n-value="Cancel ongoing upload" value="Cancel" (click)="cancel.emit()" />
|
||||
|
|
|
@ -5,23 +5,8 @@
|
|||
.upload-progress-cancel {
|
||||
display: flex;
|
||||
margin-bottom: 40px;
|
||||
|
||||
.progress {
|
||||
flex-grow: 1;
|
||||
|
||||
@include progressbar($height: 30px, $font-size: 14px, $background-color: rgba(11, 204, 41, 0.16));
|
||||
|
||||
.progress-bar {
|
||||
background-color: pvar(--green);
|
||||
line-height: 30px;
|
||||
text-align: start;
|
||||
font-weight: $font-semibold;
|
||||
|
||||
span {
|
||||
color: pvar(--mainBackgroundColor);
|
||||
|
||||
@include margin-left(13px);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my-progress-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import { Component, EventEmitter, Input, Output } from '@angular/core'
|
||||
import { ProgressBarComponent } from '../shared-main/misc/progress-bar.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-upload-progress',
|
||||
templateUrl: './upload-progress.component.html',
|
||||
styleUrls: [ './upload-progress.component.scss' ],
|
||||
imports: [ CommonModule ],
|
||||
imports: [ CommonModule, ProgressBarComponent ],
|
||||
standalone: true
|
||||
})
|
||||
export class UploadProgressComponent {
|
||||
|
@ -17,4 +18,12 @@ export class UploadProgressComponent {
|
|||
|
||||
@Output() cancel = new EventEmitter()
|
||||
@Output() retry = new EventEmitter()
|
||||
|
||||
getUploadingLabel () {
|
||||
if (this.uploadPercents === 100 && this.uploaded === false) {
|
||||
return $localize`Processing…`
|
||||
}
|
||||
|
||||
return $localize`${this.uploadPercents}%`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ body {
|
|||
// for css custom properties #{$var}
|
||||
--red: #{$red};
|
||||
--green: #{$green};
|
||||
--white: #{$white};
|
||||
|
||||
--mainColor: #{$main-color};
|
||||
--mainColorLighter: #{$main-color-lighter};
|
||||
|
|
|
@ -628,47 +628,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin progressbar($small: false, $height: 1rem, $font-size: 0.75rem, $background-color: pvar(--greyBackgroundColor)) {
|
||||
background-color: $background-color;
|
||||
display: flex;
|
||||
height: $height;
|
||||
overflow: hidden;
|
||||
font-size: $font-size;
|
||||
border-radius: 0.25rem;
|
||||
position: relative;
|
||||
|
||||
span {
|
||||
position: absolute;
|
||||
color: pvar(--greyForegroundColor);
|
||||
|
||||
@if $small {
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
&:nth-of-type(1) {
|
||||
left: .2rem;
|
||||
}
|
||||
&:nth-of-type(2) {
|
||||
right: .2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
color: pvar(--mainBackgroundColor);
|
||||
background-color: pvar(--mainColor);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
transition: width 0.6s ease;
|
||||
|
||||
&.red {
|
||||
background-color: color.adjust($color: #c54130, $lightness: 10%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin divider($color: pvar(--submenuBackgroundColor), $background: pvar(--mainBackgroundColor)) {
|
||||
width: 95%;
|
||||
border-top: .05rem solid $color;
|
||||
|
|
|
@ -32,7 +32,8 @@ $bg-color: #fff;
|
|||
$fg-color: #212529;
|
||||
|
||||
$red: #EE0700;
|
||||
$green: #338809;
|
||||
$green: #278904;
|
||||
$white: #fff;
|
||||
|
||||
$expanded-horizontal-margins: 150px;
|
||||
$not-expanded-horizontal-margins: 30px;
|
||||
|
@ -108,6 +109,7 @@ $primeng-breakpoint: 960px;
|
|||
$variables: (
|
||||
--red: var(--red),
|
||||
--green: var(--green),
|
||||
--white: var(--white),
|
||||
|
||||
--mainColor: var(--mainColor),
|
||||
--mainColorLighter: var(--mainColorLighter),
|
||||
|
|
|
@ -100,7 +100,12 @@ export const videosAddResumableValidator = [
|
|||
async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
||||
const user = res.locals.oauth.token.User
|
||||
const file = buildUploadXFile(req.body as express.CustomUploadXFile<express.UploadNewVideoXFileMetadata>)
|
||||
const cleanup = () => safeUploadXCleanup(file)
|
||||
const cleanup = () => {
|
||||
safeUploadXCleanup(file)
|
||||
|
||||
Redis.Instance.deleteUploadSession(req.query.upload_id)
|
||||
.catch(err => logger.error('Cannot delete upload session', { err }))
|
||||
}
|
||||
|
||||
const uploadId = req.query.upload_id
|
||||
const sessionExists = await Redis.Instance.doesUploadSessionExist(uploadId)
|
||||
|
|
Loading…
Reference in New Issue