Remove ng-select
Component is too complex and causes accessibility issues
This commit is contained in:
parent
9fbad291af
commit
d54e5178bc
|
@ -56,7 +56,6 @@
|
|||
"@formatjs/intl-locale": "^4.0.0",
|
||||
"@formatjs/intl-pluralrules": "^5.2.2",
|
||||
"@ng-bootstrap/ng-bootstrap": "^17.0.0",
|
||||
"@ng-select/ng-select": "^13.8.1",
|
||||
"@ngx-loading-bar/core": "^6.0.0",
|
||||
"@ngx-loading-bar/http-client": "^6.0.0",
|
||||
"@ngx-loading-bar/router": "^6.0.0",
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="instanceDefaultClientRouteLabel"
|
||||
labelForId="instanceDefaultClientRoute"
|
||||
inputId="instanceDefaultClientRoute"
|
||||
[items]="defaultLandingPageOptions"
|
||||
formControlName="defaultClientRoute"
|
||||
inputType="text"
|
||||
|
@ -224,7 +224,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="userVideoQuotaLabel"
|
||||
labelForId="userVideoQuota"
|
||||
inputId="userVideoQuota"
|
||||
[items]="getVideoQuotaOptions()"
|
||||
formControlName="videoQuota"
|
||||
i18n-inputSuffix inputSuffix="bytes" inputType="number"
|
||||
|
@ -241,7 +241,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="userVideoQuotaDailyLabel"
|
||||
labelForId="userVideoQuotaDaily"
|
||||
inputId="userVideoQuotaDaily"
|
||||
[items]="getVideoQuotaDailyOptions()"
|
||||
formControlName="videoQuotaDaily"
|
||||
i18n-inputSuffix inputSuffix="bytes" inputType="number"
|
||||
|
@ -561,7 +561,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="exportUsersMaxUserVideoQuota"
|
||||
labelForId="exportUsersMaxUserVideoQuota"
|
||||
inputId="exportUsersMaxUserVideoQuota"
|
||||
[items]="exportMaxUserVideoQuotaOptions"
|
||||
formControlName="maxUserVideoQuota"
|
||||
i18n-inputSuffix inputSuffix="bytes" inputType="number"
|
||||
|
@ -574,10 +574,7 @@
|
|||
<div class="form-group" [ngClass]="getDisabledExportUsersClass()">
|
||||
<label i18n for="exportUsersExportExpiration">User export expiration</label>
|
||||
|
||||
<my-select-options
|
||||
labelForId="exportUsersExportExpiration" [items]="exportExpirationOptions" formControlName="exportExpiration"
|
||||
bindLabel="label" bindValue="value" [clearable]="false" [searchable]="false"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="exportUsersExportExpiration" [items]="exportExpirationOptions" formControlName="exportExpiration"></my-select-options>
|
||||
|
||||
<div i18n class="mt-1 small muted">The archive file is deleted after this period.</div>
|
||||
|
||||
|
|
|
@ -56,8 +56,7 @@ input[type=checkbox] {
|
|||
}
|
||||
|
||||
my-select-options,
|
||||
my-select-custom-value,
|
||||
my-select-checkbox {
|
||||
my-select-custom-value {
|
||||
display: block;
|
||||
|
||||
@include responsive-width($form-base-input-width);
|
||||
|
|
|
@ -76,7 +76,7 @@
|
|||
|
||||
<div>
|
||||
<my-select-checkbox
|
||||
labelForId="instanceCategories"
|
||||
inputId="instanceCategories"
|
||||
formControlName="categories" [availableItems]="categoryItems"
|
||||
[selectableGroup]="false"
|
||||
i18n-placeholder placeholder="Add a new category"
|
||||
|
@ -90,7 +90,7 @@
|
|||
|
||||
<div>
|
||||
<my-select-checkbox
|
||||
labelForId="instanceLanguages"
|
||||
inputId="instanceLanguages"
|
||||
formControlName="languages" [availableItems]="languageItems"
|
||||
[selectableGroup]="false"
|
||||
i18n-placeholder placeholder="Add a new language"
|
||||
|
|
|
@ -73,10 +73,7 @@
|
|||
<div class="form-group" [ngClass]="getDisabledLiveClass()">
|
||||
<label i18n for="liveMaxDuration">Max live duration</label>
|
||||
|
||||
<my-select-options
|
||||
labelForId="liveMaxDuration" [items]="liveMaxDurationOptions" formControlName="maxDuration"
|
||||
bindLabel="label" bindValue="value" [clearable]="false" [searchable]="true"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="liveMaxDuration" [items]="liveMaxDurationOptions" formControlName="maxDuration"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.live.maxDuration" class="form-error" role="alert">{{ formErrors.live.maxDuration }}</div>
|
||||
</div>
|
||||
|
@ -178,7 +175,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="liveTranscodingThreadsLabel"
|
||||
labelForId="liveTranscodingThreads"
|
||||
inputId="liveTranscodingThreads"
|
||||
[items]="transcodingThreadOptions"
|
||||
formControlName="threads"
|
||||
[clearable]="false"
|
||||
|
@ -190,13 +187,7 @@
|
|||
<label i18n for="liveTranscodingProfile">Live transcoding profile</label>
|
||||
<span class="small muted ms-1" i18n>new live transcoding profiles can be added by PeerTube plugins</span>
|
||||
|
||||
<my-select-options
|
||||
id="liveTranscodingProfile"
|
||||
formControlName="profile"
|
||||
[items]="transcodingProfiles"
|
||||
[clearable]="false"
|
||||
>
|
||||
</my-select-options>
|
||||
<my-select-options inputId="liveTranscodingProfile" formControlName="profile" [items]="transcodingProfiles"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.live.transcoding.profile" class="form-error" role="alert">{{ formErrors.live.transcoding.profile }}</div>
|
||||
</div>
|
||||
|
|
|
@ -201,7 +201,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="transcodingThreadsLabel"
|
||||
labelForId="transcodingThreads"
|
||||
inputId="transcodingThreads"
|
||||
[items]="transcodingThreadOptions"
|
||||
formControlName="threads"
|
||||
[clearable]="false"
|
||||
|
@ -226,12 +226,7 @@
|
|||
<label i18n for="transcodingProfile">Transcoding profile</label>
|
||||
<span class="small muted ms-1" i18n>new transcoding profiles can be added by PeerTube plugins</span>
|
||||
|
||||
<my-select-options
|
||||
id="transcodingProfile"
|
||||
formControlName="profile"
|
||||
[items]="transcodingProfiles"
|
||||
[clearable]="false"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="transcodingProfile" formControlName="profile" [items]="transcodingProfiles"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.transcoding.profile" class="form-error" role="alert">{{ formErrors.transcoding.profile }}</div>
|
||||
</div>
|
||||
|
|
|
@ -154,7 +154,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="videoQuotaLabel"
|
||||
labelForId="videoQuota"
|
||||
inputId="videoQuota"
|
||||
[items]="videoQuotaOptions"
|
||||
formControlName="videoQuota"
|
||||
i18n-inputSuffix inputSuffix="bytes" inputType="number"
|
||||
|
@ -173,7 +173,7 @@
|
|||
|
||||
<my-select-custom-value
|
||||
labelId="videoQuotaDailyLabel"
|
||||
labelForId="videoQuotaDaily"
|
||||
inputId="videoQuotaDaily"
|
||||
[items]="videoQuotaDailyOptions"
|
||||
formControlName="videoQuotaDaily"
|
||||
i18n-inputSuffix inputSuffix="bytes" inputType="number"
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
<label i18n for="select-columns">Select the columns to display</label>
|
||||
|
||||
<my-select-checkbox
|
||||
labelForId="select-columns"
|
||||
inputId="select-columns"
|
||||
[availableItems]="columns"
|
||||
[selectableGroup]="false" [(ngModel)]="selectedColumns"
|
||||
i18n-placeholder placeholder="Select the columns to display"
|
||||
|
|
|
@ -7,34 +7,19 @@
|
|||
<div class="select-filter-block">
|
||||
<label for="jobType" i18n>Job type</label>
|
||||
|
||||
<ng-select
|
||||
class="select-job-type"
|
||||
labelForId="jobType"
|
||||
[(ngModel)]="jobType" (ngModelChange)="onJobStateOrTypeChanged()"
|
||||
[searchable]="true"
|
||||
[clearable]="false"
|
||||
>
|
||||
<ng-option *ngFor="let jobType of jobTypes" [value]="jobType">{{ jobType }}</ng-option>
|
||||
</ng-select>
|
||||
<my-select-options
|
||||
class="select-job-type" inputId="jobType" filter="true"
|
||||
[items]="jobTypeItems" [(ngModel)]="jobType" (ngModelChange)="onJobStateOrTypeChanged()"
|
||||
></my-select-options>
|
||||
</div>
|
||||
|
||||
<div class="select-filter-block">
|
||||
<label for="jobState" i18n>Job state</label>
|
||||
<ng-select
|
||||
class="select-job-state"
|
||||
labelForId="jobState"
|
||||
[(ngModel)]="jobState"
|
||||
(ngModelChange)="onJobStateOrTypeChanged()"
|
||||
[clearable]="false"
|
||||
[searchable]="false"
|
||||
>
|
||||
<ng-option value="all">
|
||||
<span i18n="Selector for the list displaying jobs, filtering by their state">any</span>
|
||||
</ng-option>
|
||||
<ng-option *ngFor="let state of jobStates" [value]="state">
|
||||
<span class="pt-badge" [ngClass]="getJobStateClass(state)">{{ state }}</span>
|
||||
</ng-option>
|
||||
</ng-select>
|
||||
|
||||
<my-select-options
|
||||
class="select-job-state" inputId="jobState"
|
||||
[items]="jobStateItems" [(ngModel)]="jobState" (ngModelChange)="onJobStateOrTypeChanged()"
|
||||
></my-select-options>
|
||||
</div>
|
||||
|
||||
<div class="button-filter-block">
|
||||
|
@ -78,7 +63,7 @@
|
|||
<td class="job-priority c-hand" [pRowToggler]="job">{{ job.priority }}</td>
|
||||
|
||||
<td class="job-state c-hand" [pRowToggler]="job" *ngIf="jobState === 'all'">
|
||||
<span class="pt-badge ellipsis" [ngClass]="getJobStateClass(job.state)">{{ job.state }}</span>
|
||||
<span class="ellipsis" [ngClass]="getJobStateClasses(job.state)">{{ job.state }}</span>
|
||||
</td>
|
||||
|
||||
<td *ngIf="hasGlobalProgress()" class="job-progress c-hand" [pRowToggler]="job">
|
||||
|
@ -96,7 +81,7 @@
|
|||
|
||||
<ng-template pTemplate="rowexpansion" let-job>
|
||||
<tr>
|
||||
<td [attr.colspan]="getColspan()">
|
||||
<td myAutoColspan>
|
||||
<pre>{{ [
|
||||
'Job: ' + job.id,
|
||||
'Type: ' + job.type,
|
||||
|
@ -105,13 +90,15 @@
|
|||
].join('\n') }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td [attr.colspan]="getColspan()">
|
||||
<td myAutoColspan>
|
||||
<pre>{{ job.data }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="job-error" *ngIf="job.error">
|
||||
<td [attr.colspan]="getColspan()">
|
||||
<td myAutoColspan>
|
||||
<pre>{{ job.error }}</pre>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -119,18 +106,22 @@
|
|||
|
||||
<ng-template pTemplate="emptymessage">
|
||||
<tr>
|
||||
<td [attr.colspan]="getColspan()">
|
||||
<td myAutoColspan>
|
||||
<div class="no-results">
|
||||
<div class="d-block">
|
||||
<ng-container *ngIf="jobState === 'all'">
|
||||
<ng-container *ngIf="jobType === 'all'" i18n>No jobs found.</ng-container>
|
||||
<ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found.</ng-container>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngIf="jobState !== 'all'">
|
||||
<ng-container *ngIf="jobType === 'all'" i18n>No <span class="pt-badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span> jobs found.</ng-container>
|
||||
<ng-container *ngIf="jobType !== 'all'" i18n>No <code>{{ jobType }}</code> jobs found that are <span class="pt-badge" [ngClass]="getJobStateClass(jobState)">{{ jobState }}</span>.</ng-container>
|
||||
</ng-container>
|
||||
@if (jobState === 'all') {
|
||||
@if (jobType === 'all') {
|
||||
<ng-container i18n>No jobs found.</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>No <code>{{ jobType }}</code> jobs found.</ng-container>
|
||||
}
|
||||
} @else {
|
||||
@if (jobType === 'all') {
|
||||
<ng-container i18n>No <span [ngClass]="getJobStateClasses(jobState)">{{ jobState }}</span> jobs found.</ng-container>
|
||||
} @else {
|
||||
<ng-container i18n>No <code>{{ jobType }}</code> jobs found that are <span [ngClass]="getJobStateClasses(jobState)">{{ jobState }}</span>.</ng-container>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
@use '_mixins' as *;
|
||||
|
||||
.select-job-state {
|
||||
display: block;
|
||||
min-width: 120px;
|
||||
|
||||
::ng-deep .pt-badge {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
|
||||
.select-job-type {
|
||||
display: block;
|
||||
min-width: 240px;
|
||||
|
||||
::ng-deep .ng-dropdown-panel .ng-dropdown-panel-items {
|
||||
@media screen and (min-height: 500px) {
|
||||
max-height: calc(90vh - 250px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (min-width: $primeng-breakpoint) {
|
||||
|
|
|
@ -1,20 +1,21 @@
|
|||
import { SortMeta, SharedModule } from 'primeng/api'
|
||||
import { NgClass, NgFor, NgIf } from '@angular/common'
|
||||
import { Component, OnInit } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { Notifier, RestPagination, RestTable } from '@app/core'
|
||||
import { escapeHTML } from '@peertube/peertube-core-utils'
|
||||
import { SelectOptionsComponent } from '@app/shared/shared-forms/select/select-options.component'
|
||||
import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component'
|
||||
import { AutoColspanDirective } from '@app/shared/shared-main/common/auto-colspan.directive'
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Job, JobState, JobType } from '@peertube/peertube-models'
|
||||
import { peertubeLocalStorage } from '@root-helpers/peertube-web-storage'
|
||||
import { SharedModule, SortMeta } from 'primeng/api'
|
||||
import { TableModule } from 'primeng/table'
|
||||
import { SelectOptionsItem } from 'src/types'
|
||||
import { JobStateClient } from '../../../../types/job-state-client.type'
|
||||
import { JobTypeClient } from '../../../../types/job-type-client.type'
|
||||
import { JobService } from './job.service'
|
||||
import { TableExpanderIconComponent } from '../../../shared/shared-tables/table-expander-icon.component'
|
||||
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { TableModule } from 'primeng/table'
|
||||
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { NgFor, NgClass, NgIf } from '@angular/common'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component'
|
||||
import { TableExpanderIconComponent } from '../../../shared/shared-tables/table-expander-icon.component'
|
||||
import { JobService } from './job.service'
|
||||
|
||||
@Component({
|
||||
selector: 'my-jobs',
|
||||
|
@ -24,7 +25,6 @@ import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.compon
|
|||
imports: [
|
||||
FormsModule,
|
||||
NgFor,
|
||||
NgSelectModule,
|
||||
NgClass,
|
||||
ButtonComponent,
|
||||
TableModule,
|
||||
|
@ -32,15 +32,22 @@ import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.compon
|
|||
NgIf,
|
||||
NgbTooltip,
|
||||
TableExpanderIconComponent,
|
||||
GlobalIconComponent
|
||||
GlobalIconComponent,
|
||||
SelectOptionsComponent,
|
||||
AutoColspanDirective
|
||||
]
|
||||
})
|
||||
export class JobsComponent extends RestTable implements OnInit {
|
||||
private static LOCAL_STORAGE_STATE = 'jobs-list-state'
|
||||
private static LOCAL_STORAGE_TYPE = 'jobs-list-type'
|
||||
|
||||
jobState?: JobStateClient | 'all'
|
||||
jobStates: JobStateClient[] = [ 'active', 'completed', 'failed', 'waiting', 'delayed' ]
|
||||
jobState?: JobStateClient
|
||||
jobStates: JobStateClient[] = [ 'all', 'active', 'completed', 'failed', 'waiting', 'delayed' ]
|
||||
jobStateItems: SelectOptionsItem[] = this.jobStates.map(s => ({
|
||||
id: s,
|
||||
label: s,
|
||||
classes: this.getJobStateClasses(s)
|
||||
}))
|
||||
|
||||
jobType: JobTypeClient = 'all'
|
||||
jobTypes: JobTypeClient[] = [
|
||||
|
@ -74,6 +81,7 @@ export class JobsComponent extends RestTable implements OnInit {
|
|||
'video-transcription',
|
||||
'videos-views-stats'
|
||||
]
|
||||
jobTypeItems: SelectOptionsItem[] = this.jobTypes.map(i => ({ id: i, label: i }))
|
||||
|
||||
jobs: Job[] = []
|
||||
totalRecords: number
|
||||
|
@ -96,27 +104,21 @@ export class JobsComponent extends RestTable implements OnInit {
|
|||
return 'JobsComponent'
|
||||
}
|
||||
|
||||
getJobStateClass (state: JobStateClient) {
|
||||
getJobStateClasses (state: JobStateClient) {
|
||||
switch (state) {
|
||||
case 'active':
|
||||
return 'badge-blue'
|
||||
return [ 'pt-badge', 'badge-blue' ]
|
||||
case 'completed':
|
||||
return 'badge-green'
|
||||
return [ 'pt-badge', 'badge-green' ]
|
||||
case 'delayed':
|
||||
return 'badge-brown'
|
||||
return [ 'pt-badge', 'badge-brown' ]
|
||||
case 'failed':
|
||||
return 'badge-red'
|
||||
return [ 'pt-badge', 'badge-red' ]
|
||||
case 'waiting':
|
||||
return 'badge-yellow'
|
||||
return [ 'pt-badge', 'badge-yellow' ]
|
||||
}
|
||||
}
|
||||
|
||||
getColspan () {
|
||||
if (this.jobState === 'all' && this.hasGlobalProgress()) return 7
|
||||
|
||||
if (this.jobState === 'all' || this.hasGlobalProgress()) return 6
|
||||
|
||||
return 5
|
||||
return []
|
||||
}
|
||||
|
||||
onJobStateOrTypeChanged () {
|
||||
|
@ -174,13 +176,10 @@ export class JobsComponent extends RestTable implements OnInit {
|
|||
|
||||
private loadJobStateAndType () {
|
||||
const state = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_STATE)
|
||||
if (state) this.jobState = state as JobState
|
||||
|
||||
// FIXME: We use <ng-option> that doesn't escape HTML
|
||||
// https://github.com/ng-select/ng-select/issues/1363
|
||||
if (state) this.jobState = escapeHTML(state) as JobState
|
||||
|
||||
const type = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE)
|
||||
if (type) this.jobType = type as JobType
|
||||
const jobType = peertubeLocalStorage.getItem(JobsComponent.LOCAL_STORAGE_TYPE)
|
||||
if (jobType) this.jobType = jobType as JobType
|
||||
}
|
||||
|
||||
private saveJobStateAndType () {
|
||||
|
|
|
@ -19,33 +19,21 @@
|
|||
<div>
|
||||
<label i18n for="log-start-date">Start date</label>
|
||||
|
||||
<ng-select
|
||||
[(ngModel)]="startDate"
|
||||
(ngModelChange)="refresh()"
|
||||
[clearable]="false"
|
||||
[searchable]="false"
|
||||
labelForId="log-start-date"
|
||||
>
|
||||
<ng-option *ngFor="let time of timeChoices" [value]="time.id">
|
||||
{{ time.label }} ({{ time.id | date: time.dateFormat }} - <span i18n>now</span>)
|
||||
</ng-option>
|
||||
</ng-select>
|
||||
<my-select-options inputId="log-start-date" [items]="timeChoices" [(ngModel)]="startDate" (ngModelChange)="refresh()">
|
||||
<ng-template ptTemplate="item" let-item>
|
||||
{{ item.label }} ({{ item.id | date: item.dateFormat }} - <span i18n>now</span>)
|
||||
</ng-template>
|
||||
</my-select-options>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label i18n for="log-level">Log level</label>
|
||||
|
||||
<ng-select
|
||||
[(ngModel)]="level"
|
||||
(ngModelChange)="refresh()"
|
||||
[clearable]="false"
|
||||
[searchable]="false"
|
||||
labelForId="log-level"
|
||||
>
|
||||
<ng-option *ngFor="let levelChoice of levelChoices" [value]="levelChoice.id">
|
||||
<span class="level-choice me-1" [ngClass]="levelChoice.id">⬤</span> {{ levelChoice.label }}
|
||||
</ng-option>
|
||||
</ng-select>
|
||||
<my-select-options inputId="log-level" [items]="levelChoices" [(ngModel)]="level" (ngModelChange)="refresh()">
|
||||
<ng-template ptTemplate="item" let-item>
|
||||
<span class="level-choice me-1" [ngClass]="item.id">⬤</span> {{ item.label }}
|
||||
</ng-template>
|
||||
</my-select-options>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
font-size: 13px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
color: #000;
|
||||
background: rgb(250, 250, 250);
|
||||
padding: 20px;
|
||||
|
||||
|
@ -67,8 +68,9 @@
|
|||
}
|
||||
|
||||
.level-choice {
|
||||
font-size: 80%;
|
||||
vertical-align: text-top;
|
||||
font-size: 14px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
|
||||
&.debug {
|
||||
color: rgb(197, 197, 197);
|
||||
|
@ -83,7 +85,7 @@
|
|||
}
|
||||
|
||||
&.error {
|
||||
color: #DC262B;
|
||||
color: pvar(--red);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,9 +99,7 @@ my-copy-button {
|
|||
.header {
|
||||
flex-direction: column;
|
||||
|
||||
.peertube-select-container,
|
||||
ng-select,
|
||||
my-button {
|
||||
> * {
|
||||
width: 100% !important;
|
||||
margin-bottom: 10px !important;
|
||||
|
||||
|
|
|
@ -2,8 +2,9 @@ import { DatePipe, NgClass, NgFor, NgIf } from '@angular/common'
|
|||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core'
|
||||
import { FormsModule } from '@angular/forms'
|
||||
import { LocalStorageService, Notifier } from '@app/core'
|
||||
import { SelectOptionsComponent } from '@app/shared/shared-forms/select/select-options.component'
|
||||
import { GlobalIconComponent } from '@app/shared/shared-icons/global-icon.component'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { PeerTubeTemplateDirective } from '@app/shared/shared-main/common/peertube-template.directive'
|
||||
import { ServerLogLevel } from '@peertube/peertube-models'
|
||||
import { SelectTagsComponent } from '../../../shared/shared-forms/select/select-tags.component'
|
||||
import { ButtonComponent } from '../../../shared/shared-main/buttons/button.component'
|
||||
|
@ -18,14 +19,15 @@ import { LogsService } from './logs.service'
|
|||
imports: [
|
||||
FormsModule,
|
||||
NgFor,
|
||||
NgSelectModule,
|
||||
NgIf,
|
||||
NgClass,
|
||||
SelectTagsComponent,
|
||||
ButtonComponent,
|
||||
DatePipe,
|
||||
CopyButtonComponent,
|
||||
GlobalIconComponent
|
||||
GlobalIconComponent,
|
||||
SelectOptionsComponent,
|
||||
PeerTubeTemplateDirective
|
||||
]
|
||||
})
|
||||
export class LogsComponent implements OnInit {
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<div class="modal-body" [formGroup]="form">
|
||||
<div class="form-group">
|
||||
<label i18n for="channel">Select a channel to receive the video</label>
|
||||
<my-select-channel labelForId="channel" formControlName="channel" [items]="videoChannels"></my-select-channel>
|
||||
<my-select-channel class="d-block" inputId="channel" formControlName="channel" [items]="videoChannels"></my-select-channel>
|
||||
|
||||
<div *ngIf="formErrors.channel" class="form-error" role="alert">{{ formErrors.channel }}</div>
|
||||
</div>
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="videoChannel">Video channel</label>
|
||||
<my-select-channel labelForId="videoChannel" [items]="userVideoChannels" formControlName="videoChannel"></my-select-channel>
|
||||
<my-select-channel inputId="videoChannel" [items]="userVideoChannels" formControlName="videoChannel"></my-select-channel>
|
||||
|
||||
<div *ngIf="formErrors['videoChannel']" class="form-error" role="alert">
|
||||
{{ formErrors['videoChannel'] }}
|
||||
|
|
|
@ -59,11 +59,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="privacy">Privacy</label>
|
||||
<div class="peertube-select-container">
|
||||
<my-select-options
|
||||
labelForId="privacy" [items]="videoPlaylistPrivacies" formControlName="privacy" [clearable]="false"
|
||||
></my-select-options>
|
||||
</div>
|
||||
<my-select-options inputId="privacy" [items]="videoPlaylistPrivacies" formControlName="privacy"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.privacy" class="form-error" role="alert">{{ formErrors.privacy }}</div>
|
||||
</div>
|
||||
|
@ -71,7 +67,7 @@
|
|||
<div class="form-group">
|
||||
<label for="videoChannelId" i18n>Channel</label>
|
||||
|
||||
<my-select-channel labelForId="videoChannelId" [items]="userVideoChannels" formControlName="videoChannelId"></my-select-channel>
|
||||
<my-select-channel inputId="videoChannelId" [items]="userVideoChannels" formControlName="videoChannelId"></my-select-channel>
|
||||
|
||||
<div *ngIf="formErrors['videoChannelId']" class="form-error" role="alert">{{ formErrors['videoChannelId'] }}</div>
|
||||
</div>
|
||||
|
|
|
@ -9,7 +9,8 @@ input[type=text] {
|
|||
@include peertube-select-container(340px);
|
||||
}
|
||||
|
||||
my-select-channel {
|
||||
my-select-channel,
|
||||
my-select-options {
|
||||
display: block;
|
||||
max-width: 340px;
|
||||
}
|
||||
|
|
|
@ -20,11 +20,13 @@
|
|||
</div>
|
||||
|
||||
<div class="stats-with-date">
|
||||
<div class="overall-stats">
|
||||
<div class="date-filter-wrapper">
|
||||
<h2>{{ getViewersStatsTitle() }}</h2>
|
||||
|
||||
<my-select-options [(ngModel)]="currentDateFilter" (ngModelChange)="onDateFilterChange()" [items]="dateFilters"></my-select-options>
|
||||
<div class="overall-stats">
|
||||
<h2>{{ getViewersStatsTitle() }}</h2>
|
||||
|
||||
<div class="date-filter-wrapper">
|
||||
<label class="visually-hidden" for="date-filter">Filter viewers stats by date</label>
|
||||
<my-select-options inputId="date-filter" [(ngModel)]="currentDateFilter" (ngModelChange)="onDateFilterChange()" [items]="dateFilters"></my-select-options>
|
||||
</div>
|
||||
|
||||
<div class="cards">
|
||||
|
|
|
@ -11,12 +11,11 @@
|
|||
|
||||
<div class="modal-body">
|
||||
<label i18n for="language">Language</label>
|
||||
<div class="peertube-ng-select-container">
|
||||
<ng-select
|
||||
labelForId="language" [items]="videoCaptionLanguages" formControlName="language"
|
||||
bindLabel="label" bindValue="id"
|
||||
></ng-select>
|
||||
</div>
|
||||
|
||||
<my-select-options
|
||||
inputId="language" [items]="videoCaptionLanguages" formControlName="language"
|
||||
clearable="true" filter="true" virtualScroll="true"
|
||||
></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.language" class="form-error" role="alert">
|
||||
{{ formErrors.language }}
|
||||
|
|
|
@ -1,23 +1,23 @@
|
|||
import { NgIf } from '@angular/common'
|
||||
import { Component, ElementRef, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { ServerService } from '@app/core'
|
||||
import { VIDEO_CAPTION_FILE_VALIDATOR, VIDEO_CAPTION_LANGUAGE_VALIDATOR } from '@app/shared/form-validators/video-captions-validators'
|
||||
import { FormReactive } from '@app/shared/shared-forms/form-reactive'
|
||||
import { FormReactiveService } from '@app/shared/shared-forms/form-reactive.service'
|
||||
import { SelectOptionsComponent } from '@app/shared/shared-forms/select/select-options.component'
|
||||
import { VideoCaptionEdit } from '@app/shared/shared-main/video-caption/video-caption-edit.model'
|
||||
import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { HTMLServerConfig, VideoConstant } from '@peertube/peertube-models'
|
||||
import { ReactiveFileComponent } from '../../../../shared/shared-forms/reactive-file.component'
|
||||
import { NgIf } from '@angular/common'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { GlobalIconComponent } from '../../../../shared/shared-icons/global-icon.component'
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||
import { VideoCaptionEdit } from '@app/shared/shared-main/video-caption/video-caption-edit.model'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-caption-add-modal',
|
||||
styleUrls: [ './video-caption-add-modal.component.scss' ],
|
||||
templateUrl: './video-caption-add-modal.component.html',
|
||||
standalone: true,
|
||||
imports: [ FormsModule, ReactiveFormsModule, GlobalIconComponent, NgSelectModule, NgIf, ReactiveFileComponent ]
|
||||
imports: [ FormsModule, ReactiveFormsModule, GlobalIconComponent, NgIf, ReactiveFileComponent, SelectOptionsComponent ]
|
||||
})
|
||||
|
||||
export class VideoCaptionAddModalComponent extends FormReactive implements OnInit {
|
||||
|
|
|
@ -64,14 +64,12 @@
|
|||
<div class="col-video-edit">
|
||||
<div class="form-group">
|
||||
<label i18n for="channel">Channel</label>
|
||||
<my-select-channel labelForId="channel" [items]="userVideoChannels" formControlName="channelId"></my-select-channel>
|
||||
<my-select-channel class="d-block" inputId="channel" [items]="userVideoChannels" formControlName="channelId"></my-select-channel>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="category">Category</label>
|
||||
<my-select-options
|
||||
labelForId="category" [items]="videoCategories" formControlName="category" [clearable]="true"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="category" [items]="videoCategories" formControlName="category" clearable="true"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.category" class="form-error" role="alert">
|
||||
{{ formErrors.category }}
|
||||
|
@ -89,9 +87,7 @@
|
|||
</ng-template>
|
||||
</my-help>
|
||||
|
||||
<my-select-options
|
||||
labelForId="licence" [items]="videoLicences" formControlName="licence" [clearable]="true"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="licence" [items]="videoLicences" formControlName="licence" clearable="true"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.licence" class="form-error" role="alert">
|
||||
{{ formErrors.licence }}
|
||||
|
@ -101,8 +97,8 @@
|
|||
<div class="form-group">
|
||||
<label i18n for="language">Language</label>
|
||||
<my-select-options
|
||||
labelForId="language" [items]="videoLanguages" formControlName="language"
|
||||
[clearable]="true" [searchable]="true" [groupBy]="'group'"
|
||||
inputId="language" [items]="videoLanguages" formControlName="language"
|
||||
clearable="true" filter="true" virtualScroll="true"
|
||||
></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.language" class="form-error" role="alert">
|
||||
|
@ -112,9 +108,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="privacy">Privacy</label>
|
||||
<my-select-options
|
||||
labelForId="privacy" [items]="videoPrivacies" formControlName="privacy" [clearable]="false"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="privacy" [items]="videoPrivacies" formControlName="privacy"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.privacy" class="form-error" role="alert">
|
||||
{{ formErrors.privacy }}
|
||||
|
@ -358,16 +352,12 @@
|
|||
|
||||
<div class="form-group mx-4" *ngIf="isSaveReplayEnabled()">
|
||||
<label i18n for="replayPrivacy">Privacy of the new replay</label>
|
||||
<my-select-options
|
||||
labelForId="replayPrivacy" [items]="replayPrivacies" [clearable]="false" formControlName="replayPrivacy"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="replayPrivacy" [items]="replayPrivacies" formControlName="replayPrivacy"></my-select-options>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="isLatencyModeEnabled()">
|
||||
<label i18n for="latencyMode">Latency mode</label>
|
||||
<my-select-options
|
||||
labelForId="latencyMode" [items]="latencyModes" formControlName="latencyMode" [clearable]="true"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="latencyMode" [items]="latencyModes" formControlName="latencyMode" clearable="true"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.latencyMode" class="form-error" role="alert">
|
||||
{{ formErrors.latencyMode }}
|
||||
|
@ -454,7 +444,7 @@
|
|||
|
||||
<div class="form-group mb-4">
|
||||
<label i18n for="commentsPolicy">Comments policy</label>
|
||||
<my-select-options labelForId="commentsPolicy" [items]="commentPolicies" formControlName="commentsPolicy" [clearable]="false"></my-select-options>
|
||||
<my-select-options inputId="commentsPolicy" [items]="commentPolicies" formControlName="commentsPolicy"></my-select-options>
|
||||
|
||||
<div *ngIf="formErrors.commentsPolicy" class="form-error" role="alert">
|
||||
{{ formErrors.commentsPolicy }}
|
||||
|
|
|
@ -82,7 +82,6 @@ import { I18nPrimengCalendarService } from './i18n-primeng-calendar.service'
|
|||
import { ThumbnailManagerComponent } from './thumbnail-manager/thumbnail-manager.component'
|
||||
import { VideoEditType } from './video-edit.type'
|
||||
|
||||
type VideoLanguages = VideoConstant<string> & { group?: string }
|
||||
type PluginField = {
|
||||
pluginInfo: PluginInfo
|
||||
commonOptions: RegisterClientFormFieldOptions
|
||||
|
@ -167,7 +166,7 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
videoCategories: VideoConstant<number>[] = []
|
||||
videoLicences: VideoConstant<number>[] = []
|
||||
commentPolicies: VideoConstant<VideoCommentPolicyType>[] = []
|
||||
videoLanguages: VideoLanguages[] = []
|
||||
videoLanguages: VideoConstant<string>[] = []
|
||||
latencyModes: SelectOptionsItem[] = [
|
||||
{
|
||||
id: LiveVideoLatencyMode.SMALL_LATENCY,
|
||||
|
@ -303,16 +302,14 @@ export class VideoEditComponent implements OnInit, OnDestroy {
|
|||
this.instanceService.getAbout(),
|
||||
this.serverService.getVideoLanguages()
|
||||
]).pipe(map(([ about, languages ]) => ({ about, languages })))
|
||||
.subscribe(res => {
|
||||
this.videoLanguages = res.languages
|
||||
.map(l => {
|
||||
if (l.id === 'zxx') return { ...l, group: $localize`Other`, groupOrder: 1 }
|
||||
.subscribe(({ about, languages }) => {
|
||||
this.videoLanguages = [
|
||||
...languages.filter(l => about.instance.languages.includes(l.id)),
|
||||
|
||||
return res.about.instance.languages.includes(l.id)
|
||||
? { ...l, group: $localize`Instance languages`, groupOrder: 0 }
|
||||
: { ...l, group: $localize`All languages`, groupOrder: 2 }
|
||||
})
|
||||
.sort((a, b) => a.groupOrder - b.groupOrder)
|
||||
languages.find(l => l.id === 'zxx'),
|
||||
|
||||
...languages.filter(l => !about.instance.languages.includes(l.id) && l.id !== 'zxx')
|
||||
]
|
||||
})
|
||||
|
||||
this.serverService.getVideoPrivacies()
|
||||
|
|
|
@ -4,14 +4,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-channel">Channel</label>
|
||||
<my-select-channel labelForId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
<my-select-channel inputId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-privacy">Privacy</label>
|
||||
<my-select-options
|
||||
labelForId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"></my-select-options>
|
||||
</div>
|
||||
|
||||
<div class="form-group live-type">
|
||||
|
|
|
@ -28,14 +28,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-channel">Channel</label>
|
||||
<my-select-channel labelForId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
<my-select-channel inputId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-privacy">Privacy</label>
|
||||
<my-select-options
|
||||
labelForId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"></my-select-options>
|
||||
</div>
|
||||
|
||||
<input
|
||||
|
|
|
@ -24,14 +24,12 @@
|
|||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-channel">Channel</label>
|
||||
<my-select-channel labelForId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
<my-select-channel inputId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-privacy">Privacy</label>
|
||||
<my-select-options
|
||||
labelForId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"></my-select-options>
|
||||
</div>
|
||||
|
||||
<input
|
||||
|
|
|
@ -26,10 +26,12 @@ $width-size: 275px;
|
|||
.peertube-select-container {
|
||||
@include peertube-select-container($width-size);
|
||||
}
|
||||
my-select-options ::ng-deep ng-select,
|
||||
my-select-channel ::ng-deep ng-select,
|
||||
|
||||
my-select-channel,
|
||||
my-select-options,
|
||||
.peertube-radio-container,
|
||||
.form-group-description {
|
||||
display: block;
|
||||
width: $width-size;
|
||||
|
||||
@media screen and (max-width: $width-size) {
|
||||
|
|
|
@ -17,14 +17,12 @@
|
|||
|
||||
<div class="form-group form-group-channel">
|
||||
<label i18n for="first-step-channel">Channel</label>
|
||||
<my-select-channel labelForId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
<my-select-channel inputId="first-step-channel" [items]="userVideoChannels" [(ngModel)]="firstStepChannelId"></my-select-channel>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label i18n for="first-step-privacy">Privacy</label>
|
||||
<my-select-options
|
||||
labelForId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"
|
||||
></my-select-options>
|
||||
<my-select-options inputId="first-step-privacy" [items]="videoPrivacies" [(ngModel)]="firstStepPrivacyId"></my-select-options>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="isUploadingAudioFile">
|
||||
|
|
|
@ -30,8 +30,8 @@
|
|||
<label i18n for="transcription-language">Language</label>
|
||||
|
||||
<my-select-options
|
||||
labelForId="transcription-language" [items]="languagesOptions"
|
||||
[(ngModel)]="currentLanguage" (ngModelChange)="updateCurrentCaption()" clearable="false"
|
||||
inputId="transcription-language" [items]="languagesOptions"
|
||||
[(ngModel)]="currentLanguage" (ngModelChange)="updateCurrentCaption()"
|
||||
></my-select-options>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -13,7 +13,8 @@ export class HotkeysService {
|
|||
cheatSheetToggle = new Subject<boolean>()
|
||||
|
||||
private hotkeys: Hotkey[] = []
|
||||
private readonly preventIn = new Set([ 'INPUT', 'SELECT', 'TEXTAREA' ])
|
||||
private readonly preventInNode = new Set([ 'INPUT', 'SELECT', 'TEXTAREA' ])
|
||||
private readonly preventInRole = new Set([ 'combobox' ])
|
||||
|
||||
private disabled = false
|
||||
|
||||
|
@ -62,7 +63,7 @@ export class HotkeysService {
|
|||
const target = event.target as HTMLElement
|
||||
const nodeName: string = target.nodeName.toUpperCase()
|
||||
|
||||
if (target.isContentEditable || this.preventIn.has(nodeName)) {
|
||||
if (target.isContentEditable || this.preventInNode.has(nodeName) || this.preventInRole.has(target.getAttribute('role'))) {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
<my-select-checkbox-all
|
||||
[labelForId]="labelForId"
|
||||
[(ngModel)]="selectedCategories"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[availableItems]="availableCategories"
|
||||
i18n-placeholder placeholder="Add a new category"
|
||||
[allGroupLabel]="allCategoriesGroup"
|
||||
>
|
||||
</my-select-checkbox-all>
|
|
@ -1,14 +1,25 @@
|
|||
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { ServerService } from '@app/core'
|
||||
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { ItemSelectCheckboxValue } from './select-checkbox.component'
|
||||
import { SelectCheckboxAllComponent } from './select-checkbox-all.component'
|
||||
import { SelectCheckboxDefaultAllComponent } from './select-checkbox-default-all.component'
|
||||
import { NgIf } from '@angular/common'
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-categories',
|
||||
styleUrls: [ './select-shared.component.scss' ],
|
||||
templateUrl: './select-categories.component.html',
|
||||
template: `
|
||||
<my-select-checkbox-default-all
|
||||
*ngIf="availableCategories"
|
||||
[inputId]="inputId"
|
||||
[(ngModel)]="selectedCategories"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[availableItems]="availableCategories"
|
||||
i18n-placeholder placeholder="Add a new category"
|
||||
i18n-allSelectedLabel allSelectedLabel="All categories"
|
||||
i18n-selectedLabel selectedLabel="{1} categories selected"
|
||||
>
|
||||
</my-select-checkbox-default-all>
|
||||
`,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
|
@ -17,19 +28,13 @@ import { SelectCheckboxAllComponent } from './select-checkbox-all.component'
|
|||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ SelectCheckboxAllComponent, FormsModule ]
|
||||
imports: [ SelectCheckboxDefaultAllComponent, FormsModule, NgIf ]
|
||||
})
|
||||
export class SelectCategoriesComponent implements ControlValueAccessor, OnInit {
|
||||
@Input({ required: true }) labelForId: string
|
||||
@Input({ required: true }) inputId: string
|
||||
|
||||
selectedCategories: ItemSelectCheckboxValue[] = []
|
||||
availableCategories: SelectOptionsItem[] = []
|
||||
|
||||
allCategoriesGroup = $localize`All categories`
|
||||
|
||||
// Fix a bug on ng-select when we update items after we selected items
|
||||
private toWrite: any
|
||||
private loaded = false
|
||||
selectedCategories: string[]
|
||||
availableCategories: SelectOptionsItem[]
|
||||
|
||||
constructor (
|
||||
private server: ServerService
|
||||
|
@ -41,9 +46,7 @@ export class SelectCategoriesComponent implements ControlValueAccessor, OnInit {
|
|||
this.server.getVideoCategories()
|
||||
.subscribe(
|
||||
categories => {
|
||||
this.availableCategories = categories.map(c => ({ label: c.label, id: c.id + '', group: this.allCategoriesGroup }))
|
||||
this.loaded = true
|
||||
this.writeValue(this.toWrite)
|
||||
this.availableCategories = categories.map(c => ({ label: c.label, id: c.id + '' }))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -51,14 +54,9 @@ export class SelectCategoriesComponent implements ControlValueAccessor, OnInit {
|
|||
propagateChange = (_: any) => { /* empty */ }
|
||||
|
||||
writeValue (categories: string[] | number[]) {
|
||||
if (!this.loaded) {
|
||||
this.toWrite = categories
|
||||
return
|
||||
}
|
||||
|
||||
this.selectedCategories = categories
|
||||
? categories.map(c => c + '')
|
||||
: categories as string[]
|
||||
: null
|
||||
}
|
||||
|
||||
registerOnChange (fn: (_: any) => void) {
|
||||
|
@ -70,6 +68,10 @@ export class SelectCategoriesComponent implements ControlValueAccessor, OnInit {
|
|||
}
|
||||
|
||||
onModelChange () {
|
||||
this.propagateChange(this.selectedCategories)
|
||||
this.propagateChange(
|
||||
this.selectedCategories
|
||||
? this.selectedCategories.map(c => c + '')
|
||||
: null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<ng-select
|
||||
[(ngModel)]="selectedId"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[bindLabel]="bindLabel"
|
||||
[bindValue]="bindValue"
|
||||
[clearable]="clearable"
|
||||
[searchable]="searchable"
|
||||
[labelForId]="labelForId"
|
||||
>
|
||||
<ng-option *ngFor="let channel of channels" [value]="channel.id">
|
||||
<img alt="" class="avatar me-1" [src]="channel.avatarPath" />
|
||||
{{ channel.label }}
|
||||
</ng-option>
|
||||
</ng-select>
|
|
@ -1,14 +1,25 @@
|
|||
import { CommonModule } from '@angular/common'
|
||||
import { Component, forwardRef, Input, OnChanges } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'
|
||||
import { SelectChannelItem } from '../../../../types/select-options-item.model'
|
||||
import { NgFor } from '@angular/common'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { VideoChannel } from '@app/shared/shared-main/channel/video-channel.model'
|
||||
import { DropdownModule } from 'primeng/dropdown'
|
||||
import { SelectChannelItem, SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { SelectOptionsComponent } from './select-options.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-channel',
|
||||
styleUrls: [ './select-shared.component.scss' ],
|
||||
templateUrl: './select-channel.component.html',
|
||||
template: `
|
||||
<my-select-options
|
||||
[inputId]="inputId"
|
||||
|
||||
[items]="channels"
|
||||
|
||||
[(ngModel)]="selectedId"
|
||||
(ngModelChange)="onModelChange()"
|
||||
|
||||
[filter]="channels && channels.length > 5"
|
||||
></my-select-options>
|
||||
`,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
|
@ -17,28 +28,22 @@ import { VideoChannel } from '@app/shared/shared-main/channel/video-channel.mode
|
|||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ NgSelectModule, FormsModule, NgFor ]
|
||||
imports: [ DropdownModule, FormsModule, CommonModule, SelectOptionsComponent ]
|
||||
})
|
||||
export class SelectChannelComponent implements ControlValueAccessor, OnChanges {
|
||||
@Input({ required: true }) labelForId: string
|
||||
@Input({ required: true }) inputId: string
|
||||
@Input() items: SelectChannelItem[] = []
|
||||
|
||||
channels: SelectChannelItem[] = []
|
||||
channels: SelectOptionsItem[]
|
||||
selectedId: number
|
||||
|
||||
// ng-select options
|
||||
bindLabel = 'label'
|
||||
bindValue = 'id'
|
||||
clearable = false
|
||||
searchable = false
|
||||
|
||||
ngOnChanges () {
|
||||
this.channels = this.items.map(c => {
|
||||
const avatarPath = c.avatarPath
|
||||
? c.avatarPath
|
||||
: VideoChannel.GET_DEFAULT_AVATAR_URL(20)
|
||||
: VideoChannel.GET_DEFAULT_AVATAR_URL(21)
|
||||
|
||||
return Object.assign({}, c, { avatarPath })
|
||||
return Object.assign({}, c, { imageUrl: avatarPath })
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -61,4 +66,8 @@ export class SelectChannelComponent implements ControlValueAccessor, OnChanges {
|
|||
onModelChange () {
|
||||
this.propagateChange(this.selectedId)
|
||||
}
|
||||
|
||||
getSelectedChannel () {
|
||||
return (this.channels || []).find(c => c.id + '' === this.selectedId + '')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,123 +0,0 @@
|
|||
import { Component, forwardRef, Input } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'
|
||||
import { Notifier } from '@app/core'
|
||||
import { formatICU } from '@app/helpers'
|
||||
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { ItemSelectCheckboxValue, SelectCheckboxComponent } from './select-checkbox.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-checkbox-all',
|
||||
styleUrls: [ './select-shared.component.scss' ],
|
||||
template: `
|
||||
<my-select-checkbox
|
||||
[(ngModel)]="selectedItems"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[availableItems]="availableItems"
|
||||
[selectableGroup]="true" [selectableGroupAsModel]="true"
|
||||
[placeholder]="placeholder"
|
||||
[labelForId]="labelForId"
|
||||
(focusout)="onBlur()"
|
||||
>
|
||||
</my-select-checkbox>`,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectCheckboxAllComponent),
|
||||
multi: true
|
||||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ SelectCheckboxComponent, FormsModule ]
|
||||
})
|
||||
export class SelectCheckboxAllComponent implements ControlValueAccessor {
|
||||
@Input({ required: true }) labelForId: string
|
||||
@Input() availableItems: SelectOptionsItem[] = []
|
||||
@Input() allGroupLabel: string
|
||||
|
||||
@Input() placeholder: string
|
||||
@Input() maxItems: number
|
||||
|
||||
selectedItems: ItemSelectCheckboxValue[]
|
||||
|
||||
constructor (
|
||||
private notifier: Notifier
|
||||
) {
|
||||
|
||||
}
|
||||
|
||||
propagateChange = (_: any) => { /* empty */ }
|
||||
|
||||
writeValue (items: string[]) {
|
||||
this.selectedItems = items
|
||||
? items.map(l => ({ id: l }))
|
||||
: [ { group: this.allGroupLabel } ]
|
||||
}
|
||||
|
||||
registerOnChange (fn: (_: any) => void) {
|
||||
this.propagateChange = fn
|
||||
}
|
||||
|
||||
registerOnTouched () {
|
||||
// Unused
|
||||
}
|
||||
|
||||
onModelChange () {
|
||||
if (!this.isMaxConstraintValid()) return
|
||||
|
||||
this.propagateChange(this.buildOutputItems())
|
||||
}
|
||||
|
||||
onBlur () {
|
||||
// Automatically use "All languages" if the user did not select any language
|
||||
if (Array.isArray(this.selectedItems) && this.selectedItems.length === 0) {
|
||||
this.selectedItems = [ { group: this.allGroupLabel } ]
|
||||
}
|
||||
}
|
||||
|
||||
private isMaxConstraintValid () {
|
||||
if (!this.maxItems) return true
|
||||
|
||||
const outputItems = this.buildOutputItems()
|
||||
if (!outputItems) return true
|
||||
|
||||
if (outputItems.length >= this.maxItems) {
|
||||
this.notifier.error(
|
||||
formatICU(
|
||||
$localize`You can't select more than {maxItems, plural, =1 {1 item} other {{maxItems} items}}`,
|
||||
{ maxItems: this.maxItems }
|
||||
)
|
||||
)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private buildOutputItems () {
|
||||
if (!Array.isArray(this.selectedItems)) return undefined
|
||||
|
||||
// null means "All"
|
||||
if (this.selectedItems.length === 0 || this.selectedItems.length === this.availableItems.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (this.selectedItems.length === 1) {
|
||||
const item = this.selectedItems[0]
|
||||
|
||||
const itemGroup = typeof item === 'string' || typeof item === 'number'
|
||||
? item
|
||||
: item.group
|
||||
|
||||
if (itemGroup === this.allGroupLabel) return null
|
||||
}
|
||||
|
||||
return this.selectedItems.map(l => {
|
||||
if (typeof l === 'string' || typeof l === 'number') return l
|
||||
|
||||
if (l.group) return l.group
|
||||
|
||||
return l.id + ''
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
import { booleanAttribute, Component, forwardRef, Input } from '@angular/core'
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { Notifier } from '@app/core'
|
||||
import { formatICU } from '@app/helpers'
|
||||
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { SelectCheckboxComponent } from './select-checkbox.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-checkbox-default-all',
|
||||
template: `
|
||||
<my-select-checkbox
|
||||
[(ngModel)]="selectedItems"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[availableItems]="availableItems"
|
||||
[placeholder]="placeholder"
|
||||
[inputId]="inputId"
|
||||
|
||||
[selectedItemsLabel]="selectedItemsLabel"
|
||||
|
||||
showClear="false"
|
||||
|
||||
[virtualScroll]="virtualScroll"
|
||||
|
||||
(panelHide)="onPanelHide()"
|
||||
>
|
||||
</my-select-checkbox>`,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
useExisting: forwardRef(() => SelectCheckboxDefaultAllComponent),
|
||||
multi: true
|
||||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ SelectCheckboxComponent, FormsModule ]
|
||||
})
|
||||
export class SelectCheckboxDefaultAllComponent implements ControlValueAccessor {
|
||||
@Input({ required: true }) inputId: string
|
||||
@Input() availableItems: SelectOptionsItem[] = []
|
||||
|
||||
@Input() placeholder: string
|
||||
@Input() maxIndividualItems: number
|
||||
|
||||
@Input() allSelectedLabel: string
|
||||
@Input() selectedLabel: string
|
||||
|
||||
@Input({ transform: booleanAttribute }) virtualScroll = false
|
||||
|
||||
selectedItemsLabel: string
|
||||
selectedItems: string[]
|
||||
|
||||
constructor (private notifier: Notifier) {
|
||||
|
||||
}
|
||||
|
||||
propagateChange = (_: any) => { /* empty */ }
|
||||
|
||||
writeValue (items: string[]) {
|
||||
if (items) this.selectedItems = items
|
||||
else this.selectAll()
|
||||
}
|
||||
|
||||
registerOnChange (fn: (_: any) => void) {
|
||||
this.propagateChange = fn
|
||||
}
|
||||
|
||||
registerOnTouched () {
|
||||
// Unused
|
||||
}
|
||||
|
||||
onModelChange () {
|
||||
this.updateLabel()
|
||||
|
||||
if (!this.isMaxItemsValid()) return
|
||||
|
||||
this.propagateChange(this.buildOutputItems())
|
||||
}
|
||||
|
||||
onPanelHide () {
|
||||
// Automatically use "All languages" if the user did not select any language
|
||||
if (Array.isArray(this.selectedItems) && this.selectedItems.length === 0) {
|
||||
this.selectAll()
|
||||
}
|
||||
|
||||
this.checkMaxItems()
|
||||
}
|
||||
|
||||
private isMaxItemsValid () {
|
||||
if (!this.maxIndividualItems) return true
|
||||
|
||||
const outputItems = this.buildOutputItems()
|
||||
if (!outputItems) return true
|
||||
|
||||
if (outputItems.length >= this.maxIndividualItems) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private checkMaxItems () {
|
||||
if (!this.isMaxItemsValid()) {
|
||||
this.notifier.error(
|
||||
formatICU(
|
||||
$localize`You can't select more than {maxItems, plural, =1 {1 item} other {{maxItems} items}}`,
|
||||
{ maxItems: this.maxIndividualItems }
|
||||
)
|
||||
)
|
||||
|
||||
this.selectAll()
|
||||
}
|
||||
}
|
||||
|
||||
private selectAll () {
|
||||
this.selectedItems = this.availableItems.map(i => i.id + '')
|
||||
|
||||
this.updateLabel()
|
||||
}
|
||||
|
||||
private updateLabel () {
|
||||
if (this.selectedItems && this.availableItems && this.selectedItems.length === this.availableItems.length) {
|
||||
this.selectedItemsLabel = this.allSelectedLabel
|
||||
} else {
|
||||
this.selectedItemsLabel = this.selectedLabel
|
||||
}
|
||||
}
|
||||
|
||||
private buildOutputItems () {
|
||||
if (!Array.isArray(this.selectedItems)) return undefined
|
||||
|
||||
// null means "All"
|
||||
if (this.selectedItems.length === 0 || this.selectedItems.length === this.availableItems.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.selectedItems
|
||||
}
|
||||
}
|
|
@ -1,41 +1,22 @@
|
|||
<ng-select
|
||||
[items]="availableItems"
|
||||
<p-multiSelect
|
||||
[inputId]="inputId"
|
||||
|
||||
[options]="availableItems"
|
||||
[(ngModel)]="selectedItems"
|
||||
(ngModelChange)="onModelChange()"
|
||||
|
||||
[placeholder]="placeholder"
|
||||
[clearable]="true"
|
||||
[multiple]="true"
|
||||
[searchable]="true"
|
||||
[closeOnSelect]="false"
|
||||
[disabled]="disabled"
|
||||
[labelForId]="labelForId"
|
||||
|
||||
bindValue="id"
|
||||
bindLabel="label"
|
||||
[showClear]="showClear"
|
||||
|
||||
notFoundText="No items found" i18n-notFoundText
|
||||
optionValue="id"
|
||||
|
||||
[selectableGroup]="selectableGroup"
|
||||
[selectableGroupAsModel]="selectableGroupAsModel"
|
||||
[selectedItemsLabel]="selectedItemsLabel"
|
||||
[selectionLimit]="selectionLimit"
|
||||
|
||||
groupBy="group"
|
||||
[compareWith]="compareFn"
|
||||
[virtualScroll]="virtualScroll"
|
||||
[virtualScrollItemSize]="virtualScrollItemSize"
|
||||
|
||||
(onPanelHide)="panelHide.emit()"
|
||||
>
|
||||
|
||||
<ng-template ng-optgroup-tmp let-item="item" let-item$="item$" let-index="index">
|
||||
<div class="checkbox-wrapper">
|
||||
<input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/>
|
||||
<span role="checkbox" [attr.aria-checked]="item$.selected"></span>
|
||||
<span>{{ item.group }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
<ng-template ng-option-tmp let-item="item" let-item$="item$" let-index="index">
|
||||
<div class="checkbox-wrapper">
|
||||
<input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/>
|
||||
<span role="checkbox" [attr.aria-checked]="item$.selected"></span>
|
||||
<span>{{ item.label }}</span>
|
||||
</div>
|
||||
</ng-template>
|
||||
|
||||
</ng-select>
|
||||
</p-multiSelect>
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
ng-select ::ng-deep {
|
||||
.ng-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
input {
|
||||
@include peertube-checkbox(1px);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,13 +1,11 @@
|
|||
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { booleanAttribute, Component, EventEmitter, forwardRef, Input, numberAttribute, Output } from '@angular/core'
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { MultiSelectModule } from 'primeng/multiselect'
|
||||
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
|
||||
export type ItemSelectCheckboxValue = { id?: string, group?: string } | string
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-checkbox',
|
||||
styleUrls: [ './select-shared.component.scss', 'select-checkbox.component.scss' ],
|
||||
templateUrl: './select-checkbox.component.html',
|
||||
providers: [
|
||||
{
|
||||
|
@ -17,40 +15,35 @@ export type ItemSelectCheckboxValue = { id?: string, group?: string } | string
|
|||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ NgSelectModule, FormsModule ]
|
||||
imports: [ MultiSelectModule, FormsModule, CommonModule ]
|
||||
})
|
||||
export class SelectCheckboxComponent implements OnInit, ControlValueAccessor {
|
||||
@Input({ required: true }) labelForId: string
|
||||
export class SelectCheckboxComponent implements ControlValueAccessor {
|
||||
@Input({ required: true }) inputId: string
|
||||
|
||||
@Input() availableItems: SelectOptionsItem[] = []
|
||||
@Input() selectedItems: ItemSelectCheckboxValue[] = []
|
||||
@Input() selectedItems: string[] = []
|
||||
|
||||
@Input() selectableGroup: boolean
|
||||
@Input() selectableGroupAsModel: boolean
|
||||
@Input() placeholder: string
|
||||
|
||||
disabled = false
|
||||
@Input() selectionLimit: number
|
||||
|
||||
ngOnInit () {
|
||||
if (!this.placeholder) this.placeholder = $localize`Add a new option`
|
||||
}
|
||||
@Input() selectedItemsLabel: string
|
||||
|
||||
@Input({ transform: booleanAttribute }) virtualScroll = false
|
||||
@Input({ transform: numberAttribute }) virtualScrollItemSize = 33
|
||||
|
||||
@Input({ transform: booleanAttribute }) showClear: boolean
|
||||
|
||||
@Output() panelHide = new EventEmitter()
|
||||
|
||||
disabled = false
|
||||
|
||||
propagateChange = (_: any) => { /* empty */ }
|
||||
|
||||
writeValue (items: ItemSelectCheckboxValue[]) {
|
||||
if (Array.isArray(items)) {
|
||||
this.selectedItems = items.map(i => {
|
||||
if (typeof i === 'string' || typeof i === 'number') {
|
||||
return i + ''
|
||||
}
|
||||
|
||||
if (i.group) {
|
||||
return { group: i.group }
|
||||
}
|
||||
|
||||
return { id: i.id + '' }
|
||||
})
|
||||
} else {
|
||||
this.selectedItems = items
|
||||
}
|
||||
writeValue (items: string[]) {
|
||||
this.selectedItems = items
|
||||
}
|
||||
|
||||
registerOnChange (fn: (_: any) => void) {
|
||||
|
@ -68,20 +61,4 @@ export class SelectCheckboxComponent implements OnInit, ControlValueAccessor {
|
|||
setDisabledState (isDisabled: boolean) {
|
||||
this.disabled = isDisabled
|
||||
}
|
||||
|
||||
compareFn (item: SelectOptionsItem, selected: ItemSelectCheckboxValue) {
|
||||
if (typeof selected === 'string' || typeof selected === 'number') {
|
||||
return item.id === selected
|
||||
}
|
||||
|
||||
if (this.selectableGroup && item.group && selected.group) {
|
||||
return item.group === selected.group
|
||||
}
|
||||
|
||||
if (selected.id && item.id) {
|
||||
return item.id === selected.id
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,21 @@
|
|||
<div class="root">
|
||||
<div class="d-flex align-items-center">
|
||||
<my-select-options
|
||||
[items]="itemsWithCustom"
|
||||
[clearable]="clearable"
|
||||
[searchable]="searchable"
|
||||
[groupBy]="groupBy"
|
||||
[labelForId]="labelForId"
|
||||
class="flex-grow-1"
|
||||
|
||||
[inputId]="inputId"
|
||||
[disabled]="disabled"
|
||||
|
||||
[items]="itemsWithCustom"
|
||||
[(ngModel)]="selectedId"
|
||||
(ngModelChange)="onModelChange()"
|
||||
></my-select-options>
|
||||
|
||||
<ng-container *ngIf="isCustomValue()">
|
||||
<input [attr.aria-labelledby]="labelId" [(ngModel)]="customValue" (ngModelChange)="onModelChange()" [type]="inputType" class="form-control" />
|
||||
@if (isCustomValue()) {
|
||||
<input
|
||||
[attr.aria-labelledby]="labelId" [(ngModel)]="customValue" (ngModelChange)="onModelChange()"
|
||||
[type]="inputType" class="ms-2 form-control pt-input-text"
|
||||
/>
|
||||
|
||||
<span *ngIf="inputSuffix" class="input-suffix">{{ inputSuffix }}</span>
|
||||
</ng-container>
|
||||
<span *ngIf="inputSuffix" class="ms-1">{{ inputSuffix }}</span>
|
||||
}
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,6 @@ import { SelectOptionsComponent } from './select-options.component'
|
|||
|
||||
@Component({
|
||||
selector: 'my-select-custom-value',
|
||||
styleUrls: [ './select-shared.component.scss' ],
|
||||
templateUrl: './select-custom-value.component.html',
|
||||
providers: [
|
||||
{
|
||||
|
@ -19,14 +18,14 @@ import { SelectOptionsComponent } from './select-options.component'
|
|||
imports: [ SelectOptionsComponent, FormsModule, NgIf ]
|
||||
})
|
||||
export class SelectCustomValueComponent implements ControlValueAccessor, OnChanges {
|
||||
@Input({ required: true }) labelForId: string
|
||||
@Input({ required: true }) inputId: string
|
||||
@Input({ required: true }) labelId: string
|
||||
|
||||
@Input() items: SelectOptionsItem[] = []
|
||||
|
||||
@Input() clearable = false
|
||||
@Input() searchable = false
|
||||
@Input() groupBy: string
|
||||
|
||||
@Input() inputSuffix: string
|
||||
@Input() inputType = 'text'
|
||||
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
<my-select-checkbox-all
|
||||
[(ngModel)]="selectedLanguages"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[availableItems]="availableLanguages"
|
||||
[maxItems]="maxLanguages"
|
||||
i18n-placeholder placeholder="Add a new language"
|
||||
[allGroupLabel]="allLanguagesGroup"
|
||||
[labelForId]="labelForId"
|
||||
>
|
||||
</my-select-checkbox-all>
|
|
@ -1,14 +1,33 @@
|
|||
import { NgIf } from '@angular/common'
|
||||
import { Component, forwardRef, Input, OnInit } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { ServerService } from '@app/core'
|
||||
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { ItemSelectCheckboxValue } from './select-checkbox.component'
|
||||
import { SelectCheckboxAllComponent } from './select-checkbox-all.component'
|
||||
import { SelectCheckboxDefaultAllComponent } from './select-checkbox-default-all.component'
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-languages',
|
||||
styleUrls: [ './select-shared.component.scss' ],
|
||||
templateUrl: './select-languages.component.html',
|
||||
template: `
|
||||
<my-select-checkbox-default-all
|
||||
*ngIf="availableLanguages"
|
||||
|
||||
[availableItems]="availableLanguages"
|
||||
[(ngModel)]="selectedLanguages"
|
||||
(ngModelChange)="onModelChange()"
|
||||
|
||||
[inputId]="inputId"
|
||||
|
||||
[maxIndividualItems]="maxLanguages"
|
||||
|
||||
virtualScroll="true"
|
||||
virtualScrollItemSize="37"
|
||||
|
||||
i18n-allSelectedLabel allSelectedLabel="All languages"
|
||||
i18n-selectedLabel selectedLabel="{1} languages selected"
|
||||
i18n-placeholder placeholder="Add a new language"
|
||||
>
|
||||
</my-select-checkbox-default-all>
|
||||
`,
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
|
@ -17,20 +36,14 @@ import { SelectCheckboxAllComponent } from './select-checkbox-all.component'
|
|||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ SelectCheckboxAllComponent, FormsModule ]
|
||||
imports: [ SelectCheckboxDefaultAllComponent, FormsModule, NgIf ]
|
||||
})
|
||||
export class SelectLanguagesComponent implements ControlValueAccessor, OnInit {
|
||||
@Input({ required: true }) labelForId: string
|
||||
@Input({ required: true }) inputId: string
|
||||
@Input() maxLanguages: number
|
||||
|
||||
selectedLanguages: ItemSelectCheckboxValue[]
|
||||
availableLanguages: (SelectOptionsItem & { groupOrder: number })[] = []
|
||||
|
||||
allLanguagesGroup = $localize`All languages`
|
||||
|
||||
// Fix a bug on ng-select when we update items after we selected items
|
||||
private toWrite: any
|
||||
private loaded = false
|
||||
selectedLanguages: string[]
|
||||
availableLanguages: SelectOptionsItem[]
|
||||
|
||||
constructor (
|
||||
private server: ServerService
|
||||
|
@ -42,35 +55,27 @@ export class SelectLanguagesComponent implements ControlValueAccessor, OnInit {
|
|||
this.server.getVideoLanguages()
|
||||
.subscribe(
|
||||
languages => {
|
||||
this.availableLanguages = [ {
|
||||
label: $localize`Unknown language`,
|
||||
id: '_unknown',
|
||||
group: this.allLanguagesGroup,
|
||||
groupOrder: 1
|
||||
} ]
|
||||
const noLangSet = languages.find(l => l.id === 'zxx')
|
||||
|
||||
this.availableLanguages = this.availableLanguages
|
||||
.concat(languages.map(l => {
|
||||
if (l.id === 'zxx') return { label: l.label, id: l.id, group: $localize`Other`, groupOrder: 0 }
|
||||
return { label: l.label, id: l.id, group: this.allLanguagesGroup, groupOrder: 1 }
|
||||
}))
|
||||
this.availableLanguages = [
|
||||
{
|
||||
label: $localize`Unknown language`,
|
||||
id: '_unknown'
|
||||
},
|
||||
|
||||
this.availableLanguages.sort((a, b) => a.groupOrder - b.groupOrder)
|
||||
noLangSet,
|
||||
|
||||
this.loaded = true
|
||||
this.writeValue(this.toWrite)
|
||||
...languages
|
||||
.filter(l => l.id !== 'zxx')
|
||||
.map(l => ({ label: l.label, id: l.id }))
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
propagateChange = (_: any) => { /* empty */ }
|
||||
|
||||
writeValue (languages: ItemSelectCheckboxValue[]) {
|
||||
if (!this.loaded) {
|
||||
this.toWrite = languages
|
||||
return
|
||||
}
|
||||
|
||||
writeValue (languages: string[]) {
|
||||
this.selectedLanguages = languages
|
||||
}
|
||||
|
||||
|
|
|
@ -1,22 +1,44 @@
|
|||
<ng-select
|
||||
[items]="items"
|
||||
[groupBy]="groupBy"
|
||||
<p-dropdown
|
||||
[inputId]="inputId"
|
||||
|
||||
[options]="items"
|
||||
|
||||
[(ngModel)]="selectedId"
|
||||
(ngModelChange)="onModelChange()"
|
||||
[clearable]="clearable"
|
||||
[labelForId]="labelForId"
|
||||
[searchable]="searchable"
|
||||
[searchFn]="searchFn"
|
||||
[disabled]="disabled"
|
||||
|
||||
bindLabel="label"
|
||||
bindValue="id"
|
||||
optionValue="id"
|
||||
|
||||
[showClear]="clearable"
|
||||
|
||||
[filter]="filter"
|
||||
filterBy="label"
|
||||
|
||||
i18n-filterPlaceholder filterPlaceholder="Search"
|
||||
i18n-emptyFilterMessage emptyFilterMessage="No results found"
|
||||
i18n-emptyMessage emptyMessage="No items available"
|
||||
|
||||
[virtualScroll]="virtualScroll"
|
||||
[virtualScrollItemSize]="virtualScrollItemSize"
|
||||
>
|
||||
<ng-template ng-option-tmp let-item="item" let-index="index">
|
||||
{{ item.label }}
|
||||
<ng-container *ngIf="item.description">
|
||||
<br>
|
||||
<span [title]="item.description" class="muted">{{ item.description }}</span>
|
||||
</ng-container>
|
||||
<ng-template #itemTemplate let-item let-description="description">
|
||||
@if (customItemTemplate) {
|
||||
<ng-template [ngTemplateOutlet]="customItemTemplate" [ngTemplateOutletContext]="{ $implicit: item }"></ng-template>
|
||||
} @else {
|
||||
<div class="d-flex align-items-center">
|
||||
<img *ngIf="item.imageUrl" alt="" class="me-2" [src]="item.imageUrl" />
|
||||
|
||||
<span [ngClass]="item.classes">{{ item.label }}</span>
|
||||
</div>
|
||||
|
||||
<span *ngIf="description" class="muted">{{ description }}</span>
|
||||
}
|
||||
</ng-template>
|
||||
</ng-select>
|
||||
|
||||
<ng-template pTemplate="selectedItem">
|
||||
<ng-template [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: getSelectedItem() }"></ng-template>
|
||||
</ng-template>
|
||||
|
||||
<ng-template let-item pTemplate="item">
|
||||
<ng-template [ngTemplateOutlet]="itemTemplate" [ngTemplateOutletContext]="{ $implicit: item, description: item.description }"></ng-template>
|
||||
</ng-template>
|
||||
</p-dropdown>
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
margin-top: -1px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
font-size: 90%;
|
||||
}
|
|
@ -1,13 +1,28 @@
|
|||
import { booleanAttribute, Component, forwardRef, HostListener, Input } from '@angular/core'
|
||||
import { ControlValueAccessor, NG_VALUE_ACCESSOR, FormsModule } from '@angular/forms'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import {
|
||||
AfterContentInit,
|
||||
booleanAttribute,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ContentChildren,
|
||||
forwardRef,
|
||||
HostListener,
|
||||
Input,
|
||||
numberAttribute,
|
||||
QueryList,
|
||||
TemplateRef
|
||||
} from '@angular/core'
|
||||
import { ControlValueAccessor, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'
|
||||
import { PeerTubeTemplateDirective } from '@app/shared/shared-main/common/peertube-template.directive'
|
||||
import { DropdownModule } from 'primeng/dropdown'
|
||||
import { SelectOptionsItem } from '../../../../types/select-options-item.model'
|
||||
import { NgIf } from '@angular/common'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
|
||||
@Component({
|
||||
selector: 'my-select-options',
|
||||
styleUrls: [ './select-shared.component.scss' ],
|
||||
|
||||
templateUrl: './select-options.component.html',
|
||||
styleUrls: [ './select-options.component.scss' ],
|
||||
|
||||
providers: [
|
||||
{
|
||||
provide: NG_VALUE_ACCESSOR,
|
||||
|
@ -16,22 +31,39 @@ import { NgSelectModule } from '@ng-select/ng-select'
|
|||
}
|
||||
],
|
||||
standalone: true,
|
||||
imports: [ NgSelectModule, FormsModule, NgIf ]
|
||||
imports: [ DropdownModule, FormsModule, CommonModule ]
|
||||
})
|
||||
export class SelectOptionsComponent implements ControlValueAccessor {
|
||||
export class SelectOptionsComponent implements AfterContentInit, ControlValueAccessor {
|
||||
@Input() items: SelectOptionsItem[] = []
|
||||
|
||||
@Input({ required: true }) inputId: string
|
||||
|
||||
@Input({ transform: booleanAttribute }) clearable = false
|
||||
@Input({ transform: booleanAttribute }) searchable = false
|
||||
@Input({ transform: booleanAttribute }) filter = false
|
||||
|
||||
@Input() groupBy: string
|
||||
@Input() labelForId: string
|
||||
@Input({ transform: booleanAttribute }) virtualScroll = false
|
||||
@Input({ transform: numberAttribute }) virtualScrollItemSize = 39
|
||||
|
||||
@Input() searchFn: any
|
||||
@ContentChildren(PeerTubeTemplateDirective) templates: QueryList<PeerTubeTemplateDirective<'item'>>
|
||||
|
||||
customItemTemplate: TemplateRef<any>
|
||||
|
||||
selectedId: number | string
|
||||
disabled = false
|
||||
|
||||
wroteValue: number | string
|
||||
|
||||
constructor (private cd: ChangeDetectorRef) {
|
||||
|
||||
}
|
||||
|
||||
ngAfterContentInit () {
|
||||
{
|
||||
const t = this.templates.find(t => t.name === 'item')
|
||||
if (t) this.customItemTemplate = t.template
|
||||
}
|
||||
}
|
||||
|
||||
propagateChange = (_: any) => { /* empty */ }
|
||||
|
||||
// Allow plugins to update our value
|
||||
|
@ -43,6 +75,10 @@ export class SelectOptionsComponent implements ControlValueAccessor {
|
|||
|
||||
writeValue (id: number | string) {
|
||||
this.selectedId = id
|
||||
|
||||
// https://github.com/primefaces/primeng/issues/14609 workaround
|
||||
this.wroteValue = id
|
||||
this.cd.detectChanges()
|
||||
}
|
||||
|
||||
registerOnChange (fn: (_: any) => void) {
|
||||
|
@ -54,10 +90,18 @@ export class SelectOptionsComponent implements ControlValueAccessor {
|
|||
}
|
||||
|
||||
onModelChange () {
|
||||
if (this.wroteValue !== undefined && this.wroteValue === this.selectedId) {
|
||||
return
|
||||
}
|
||||
|
||||
this.propagateChange(this.selectedId)
|
||||
}
|
||||
|
||||
setDisabledState (isDisabled: boolean) {
|
||||
this.disabled = isDisabled
|
||||
}
|
||||
|
||||
getSelectedItem () {
|
||||
return this.items.find(i => i.id === this.selectedId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
$form-base-input-width: auto;
|
||||
|
||||
.muted {
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
ng-select {
|
||||
width: $form-base-input-width;
|
||||
|
||||
@media screen and (max-width: $form-base-input-width) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
ng-select ::ng-deep {
|
||||
.ng-value-container {
|
||||
max-height: 100px;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
// make sure the image is vertically adjusted
|
||||
.ng-value-label img {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
img {
|
||||
border-radius: 50%;
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.root {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
> my-select-options {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
my-select-options + input {
|
||||
display: block;
|
||||
|
||||
@include peertube-input-text($form-base-input-width);
|
||||
@include margin-left(5px);
|
||||
}
|
||||
|
||||
.input-suffix {
|
||||
@include margin-left(5px);
|
||||
}
|
|
@ -25,7 +25,6 @@ export class SelectTagsComponent implements ControlValueAccessor {
|
|||
|
||||
writeValue (items: string[]) {
|
||||
this.selectedItems = items
|
||||
this.propagateChange(this.selectedItems)
|
||||
}
|
||||
|
||||
registerOnChange (fn: (_: any) => void) {
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
</my-help>
|
||||
|
||||
<div>
|
||||
<my-select-languages labelForId="videoLanguages" [maxLanguages]="20" formControlName="videoLanguages"></my-select-languages>
|
||||
<my-select-languages inputId="videoLanguages" [maxLanguages]="20" formControlName="videoLanguages"></my-select-languages>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -47,34 +47,11 @@
|
|||
|
||||
<label class="visually-hidden" for="sort-videos">Sort videos</label>
|
||||
|
||||
<ng-select
|
||||
class="sort"
|
||||
formControlName="sort"
|
||||
labelForId="sort-videos"
|
||||
[clearable]="false"
|
||||
[searchable]="false"
|
||||
[bindValue]="null"
|
||||
>
|
||||
<ng-option i18n value="-publishedAt">Sort by <strong>"Recently Added"</strong></ng-option>
|
||||
<ng-option i18n value="-originallyPublishedAt">Sort by <strong>"Original Publication Date"</strong></ng-option>
|
||||
|
||||
<ng-option i18n value="name">Sort by <strong>"Name"</strong></ng-option>
|
||||
|
||||
@if (isTrendingSortEnabled('most-viewed')) {
|
||||
<ng-option i18n value="-trending">Sort by <strong>"Recent Views"</strong></ng-option>
|
||||
}
|
||||
|
||||
@if (isTrendingSortEnabled('hot')) {
|
||||
<ng-option i18n value="-hot">Sort by <strong>"Hot"</strong></ng-option>
|
||||
}
|
||||
|
||||
@if (isTrendingSortEnabled('most-liked')) {
|
||||
<ng-option i18n value="-likes">Sort by <strong>"Likes"</strong></ng-option>
|
||||
}
|
||||
|
||||
<ng-option i18n value="-views">Sort by <strong>"Global Views"</strong></ng-option>
|
||||
</ng-select>
|
||||
|
||||
<my-select-options inputId="sort-videos" class="sort" formControlName="sort" [items]="sortItems">
|
||||
<ng-template ptTemplate="item" let-item>
|
||||
<ng-container>Sort by <strong>"{{ item.label }}"</strong></ng-container>
|
||||
</ng-template>
|
||||
</my-select-options>
|
||||
</div>
|
||||
|
||||
<div [ngbCollapse]="areFiltersCollapsed" [animation]="true">
|
||||
|
@ -83,7 +60,7 @@
|
|||
<label class="with-description" for="languageOneOf" i18n>Languages:</label>
|
||||
<ng-template *ngTemplateOutlet="updateSettings; context: { $implicit: 'video-languages-subtitles' }"></ng-template>
|
||||
|
||||
<my-select-languages labelForId="languageOneOf" [maxLanguages]="20" formControlName="languageOneOf"></my-select-languages>
|
||||
<my-select-languages inputId="languageOneOf" [maxLanguages]="20" formControlName="languageOneOf"></my-select-languages>
|
||||
</div>
|
||||
|
||||
<div class="form-group" role="radiogroup">
|
||||
|
@ -137,7 +114,7 @@
|
|||
<div class="form-group">
|
||||
<label for="categoryOneOf" i18n>Categories:</label>
|
||||
|
||||
<my-select-categories labelForId="categoryOneOf" formControlName="categoryOneOf"></my-select-categories>
|
||||
<my-select-categories inputId="categoryOneOf" formControlName="categoryOneOf"></my-select-categories>
|
||||
</div>
|
||||
|
||||
<div class="form-group" *ngIf="canSeeAllVideos()">
|
||||
|
|
|
@ -111,16 +111,6 @@
|
|||
min-width: 250px;
|
||||
max-width: 300px;
|
||||
height: min-content;
|
||||
|
||||
::ng-deep {
|
||||
.ng-select-container {
|
||||
height: 33px !important;
|
||||
}
|
||||
|
||||
.ng-value strong {
|
||||
@include margin-left(5px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
my-select-languages,
|
||||
|
|
|
@ -5,14 +5,16 @@ import { RouterLink } from '@angular/router'
|
|||
import { AuthService } from '@app/core'
|
||||
import { ServerService } from '@app/core/server/server.service'
|
||||
import { NgbCollapse } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { NgSelectModule } from '@ng-select/ng-select'
|
||||
import { UserRight, VideoConstant } from '@peertube/peertube-models'
|
||||
import debug from 'debug'
|
||||
import { Subscription } from 'rxjs'
|
||||
import { SelectOptionsItem } from 'src/types'
|
||||
import { PeertubeCheckboxComponent } from '../shared-forms/peertube-checkbox.component'
|
||||
import { SelectCategoriesComponent } from '../shared-forms/select/select-categories.component'
|
||||
import { SelectLanguagesComponent } from '../shared-forms/select/select-languages.component'
|
||||
import { SelectOptionsComponent } from '../shared-forms/select/select-options.component'
|
||||
import { GlobalIconComponent } from '../shared-icons/global-icon.component'
|
||||
import { PeerTubeTemplateDirective } from '../shared-main/common/peertube-template.directive'
|
||||
import { PeertubeModalService } from '../shared-main/peertube-modal/peertube-modal.service'
|
||||
import { VideoFilterActive, VideoFilters } from './video-filters.model'
|
||||
|
||||
|
@ -31,12 +33,13 @@ const debugLogger = debug('peertube:videos:VideoFiltersHeaderComponent')
|
|||
NgIf,
|
||||
GlobalIconComponent,
|
||||
NgFor,
|
||||
NgSelectModule,
|
||||
NgbCollapse,
|
||||
NgTemplateOutlet,
|
||||
SelectLanguagesComponent,
|
||||
SelectCategoriesComponent,
|
||||
PeertubeCheckboxComponent
|
||||
PeertubeCheckboxComponent,
|
||||
SelectOptionsComponent,
|
||||
PeerTubeTemplateDirective
|
||||
]
|
||||
})
|
||||
export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
||||
|
@ -50,6 +53,8 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
|||
|
||||
form: FormGroup
|
||||
|
||||
sortItems: SelectOptionsItem[] = []
|
||||
|
||||
private videoCategories: VideoConstant<number>[] = []
|
||||
private videoLanguages: VideoConstant<string>[] = []
|
||||
|
||||
|
@ -92,6 +97,8 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
|||
|
||||
this.serverService.getVideoLanguages()
|
||||
.subscribe(languages => this.videoLanguages = languages)
|
||||
|
||||
this.buildSortItems()
|
||||
}
|
||||
|
||||
ngOnDestroy () {
|
||||
|
@ -105,7 +112,29 @@ export class VideoFiltersHeaderComponent implements OnInit, OnDestroy {
|
|||
return this.auth.getUser().hasRight(UserRight.SEE_ALL_VIDEOS)
|
||||
}
|
||||
|
||||
isTrendingSortEnabled (sort: 'most-viewed' | 'hot' | 'most-liked') {
|
||||
private buildSortItems () {
|
||||
this.sortItems = [
|
||||
{ id: '-publishedAt', label: 'Recently Added' },
|
||||
{ id: '-originallyPublishedAt', label: 'Original Publication Date' },
|
||||
{ id: 'name', label: 'Name' }
|
||||
]
|
||||
|
||||
if (this.isTrendingSortEnabled('most-viewed')) {
|
||||
this.sortItems.push({ id: '-trending', label: 'Recent Views' })
|
||||
}
|
||||
|
||||
if (this.isTrendingSortEnabled('hot')) {
|
||||
this.sortItems.push({ id: '-hot', label: 'Hot' })
|
||||
}
|
||||
|
||||
if (this.isTrendingSortEnabled('most-liked')) {
|
||||
this.sortItems.push({ id: '-likes', label: 'Likes' })
|
||||
}
|
||||
|
||||
this.sortItems.push({ id: '-views', label: 'Global Views' })
|
||||
}
|
||||
|
||||
private isTrendingSortEnabled (sort: 'most-viewed' | 'hot' | 'most-liked') {
|
||||
const serverConfig = this.serverService.getHTMLConfig()
|
||||
|
||||
return serverConfig.trending.videos.algorithms.enabled.includes(sort)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { splitIntoArray, toBoolean } from '@app/helpers'
|
||||
import { escapeHTML, getAllPrivacies } from '@peertube/peertube-core-utils'
|
||||
import { getAllPrivacies } from '@peertube/peertube-core-utils'
|
||||
import {
|
||||
BooleanBothQuery,
|
||||
NSFWPolicyType,
|
||||
|
@ -112,28 +112,19 @@ export class VideoFilters {
|
|||
// ---------------------------------------------------------------------------
|
||||
|
||||
load (obj: Partial<AttributesOnly<VideoFilters>>) {
|
||||
// FIXME: We may use <ng-option> that doesn't escape HTML so prefer to escape things
|
||||
// https://github.com/ng-select/ng-select/issues/1363
|
||||
if (obj.sort !== undefined) this.sort = obj.sort
|
||||
|
||||
const escapeIfNeeded = (value: any) => {
|
||||
if (typeof value === 'string') return escapeHTML(value)
|
||||
if (obj.nsfw !== undefined) this.nsfw = obj.nsfw
|
||||
|
||||
return value
|
||||
}
|
||||
if (obj.languageOneOf !== undefined) this.languageOneOf = splitIntoArray(obj.languageOneOf)
|
||||
if (obj.categoryOneOf !== undefined) this.categoryOneOf = splitIntoArray(obj.categoryOneOf)
|
||||
|
||||
if (obj.sort !== undefined) this.sort = escapeIfNeeded(obj.sort) as VideoSortField
|
||||
|
||||
if (obj.nsfw !== undefined) this.nsfw = escapeIfNeeded(obj.nsfw) as BooleanBothQuery
|
||||
|
||||
if (obj.languageOneOf !== undefined) this.languageOneOf = splitIntoArray(escapeIfNeeded(obj.languageOneOf))
|
||||
if (obj.categoryOneOf !== undefined) this.categoryOneOf = splitIntoArray(escapeIfNeeded(obj.categoryOneOf))
|
||||
|
||||
if (obj.scope !== undefined) this.scope = escapeIfNeeded(obj.scope) as VideoFilterScope
|
||||
if (obj.scope !== undefined) this.scope = obj.scope
|
||||
if (obj.allVideos !== undefined) this.allVideos = toBoolean(obj.allVideos)
|
||||
|
||||
if (obj.live !== undefined) this.live = escapeIfNeeded(obj.live) as BooleanBothQuery
|
||||
if (obj.live !== undefined) this.live = obj.live
|
||||
|
||||
if (obj.search !== undefined) this.search = escapeIfNeeded(obj.search)
|
||||
if (obj.search !== undefined) this.search = obj.search
|
||||
|
||||
this.buildActiveFilters()
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
@use '_icons' as *;
|
||||
@use '_fonts';
|
||||
@use './custom-markup';
|
||||
@use './ng-select';
|
||||
@use './bootstrap';
|
||||
@use './primeng-custom';
|
||||
@use './z-index';
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
.pt-input-text {
|
||||
@include peertube-input-text(100%);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
|
|
@ -1,99 +0,0 @@
|
|||
@use 'sass:math';
|
||||
@use 'sass:color';
|
||||
@use '_variables' as *;
|
||||
@use '_mixins' as *;
|
||||
|
||||
$ng-select-highlight: pvar(--mainColor);
|
||||
$ng-select-primary-text: pvar(--mainForegroundColor);
|
||||
// $ng-select-disabled-text: #f9f9f9 !default;
|
||||
$ng-select-border: $input-border-color;
|
||||
// $ng-select-border-radius: 4px !default;
|
||||
$ng-select-bg: pvar(--mainBackgroundColor);
|
||||
|
||||
// Cannot use a CSS variable as the default them use darken on this variable
|
||||
$ng-select-selected: color.adjust($main-color, $lightness: 40%);
|
||||
// $ng-select-selected-text: $ng-select-primary-text !default;
|
||||
|
||||
$ng-select-marked: pvar(--mainColorVeryLight);
|
||||
// $ng-select-marked-text: $ng-select-primary-text !default;
|
||||
|
||||
$ng-select-box-shadow: #{$focus-box-shadow-form} pvar(--mainColorLightest);
|
||||
$ng-select-placeholder: pvar(--greyForegroundColor);
|
||||
$ng-select-height: 30px;
|
||||
$ng-select-value-padding-left: 15px;
|
||||
$ng-select-value-font-size: $form-input-font-size;
|
||||
// $ng-select-value-text: $ng-select-primary-text !default;
|
||||
|
||||
// $ng-select-dropdown-bg: $ng-select-bg !default;
|
||||
// $ng-select-dropdown-border: $ng-select-border !default;
|
||||
// $ng-select-dropdown-optgroup-text: rgba(0, 0, 0, 0.54) !default;
|
||||
// $ng-select-dropdown-optgroup-marked: $ng-select-dropdown-optgroup-text !default;
|
||||
// $ng-select-dropdown-option-bg: $ng-select-dropdown-bg !default;
|
||||
// $ng-select-dropdown-option-text: rgba(0, 0, 0, 0.87) !default;
|
||||
$ng-select-dropdown-option-disabled: pvar(--greyForegroundColor);
|
||||
|
||||
$ng-select-input-text: pvar(--mainForegroundColor);
|
||||
|
||||
@import '@ng-select/ng-select/scss/default.theme';
|
||||
|
||||
.ng-select {
|
||||
font-size: $ng-select-value-font-size;
|
||||
|
||||
@include rounded-line-height-1-5($ng-select-value-font-size);
|
||||
|
||||
&.ng-select-focused {
|
||||
&:not(.ng-select-opened) > .ng-select-container {
|
||||
border-color: $ng-select-border !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ng-input > input {
|
||||
color: pvar(--inputForegroundColor) !important;
|
||||
}
|
||||
|
||||
.ng-dropdown-panel .ng-dropdown-panel-items .ng-option {
|
||||
&:not(.ng-option-marked, .ng-option-selected) {
|
||||
color: pvar(--mainForegroundColor);
|
||||
background-color: pvar(--mainBackgroundColor);
|
||||
}
|
||||
}
|
||||
|
||||
.ng-select-container {
|
||||
background-color: pvar(--inputBackgroundColor);
|
||||
}
|
||||
|
||||
.ng-arrow-wrapper {
|
||||
@include padding-right(12px);
|
||||
}
|
||||
|
||||
.ng-arrow {
|
||||
border-color: #000 transparent transparent !important;
|
||||
}
|
||||
|
||||
&.ng-select-opened .ng-arrow {
|
||||
border-color: transparent transparent #000 !important;
|
||||
}
|
||||
|
||||
&.ng-select-single .ng-value-container .ng-value {
|
||||
color: pvar(--inputForegroundColor);
|
||||
|
||||
.ng-value-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.ng-select-multiple .ng-select-container .ng-value-container {
|
||||
@include padding-left(12px);
|
||||
|
||||
.ng-value {
|
||||
background-color: pvar(--mainColorLightest);
|
||||
|
||||
@include margin-left(12px);
|
||||
|
||||
.ng-value-icon {
|
||||
border: 0 !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -146,19 +146,19 @@ body .p-paginator .p-dropdown {
|
|||
// Dropdown
|
||||
|
||||
body .p-dropdown {
|
||||
background: #ffffff;
|
||||
border: 1px solid #a6a6a6;
|
||||
background: pvar(--mainBackgroundColor);;
|
||||
border: 1px solid pvar(--inputBorderColor);
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
border-radius: 6px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
body .p-dropdown:not(.p-disabled):hover {
|
||||
border-color: #212121;
|
||||
border-color: pvar(--inputBorderColor);
|
||||
}
|
||||
body .p-dropdown:not(.p-disabled).p-focus {
|
||||
outline: 0 none;
|
||||
outline-offset: 0;
|
||||
box-shadow: 0 0 0 0.2em pvar(--mainColorLightest);
|
||||
border-color: pvar(--mainColor);
|
||||
border-color: pvar(--inputBorderColor);
|
||||
}
|
||||
body .p-dropdown.p-dropdown-clearable .p-dropdown-label {
|
||||
@include padding-right(0);
|
||||
|
@ -168,9 +168,9 @@ body .p-dropdown .p-dropdown-label {
|
|||
border: 0 none;
|
||||
}
|
||||
body .p-dropdown .p-dropdown-label.p-placeholder {
|
||||
color: #6c757d;
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
body .p-dropdown .p-dropdown-label:enabled:focus {
|
||||
.p-dropdown .p-dropdown-label:focus, .p-dropdown .p-dropdown-label:enabled:focus {
|
||||
outline: 0 none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@ -190,25 +190,24 @@ body .p-dropdown.p-dropdown-clearable .p-dropdown-label {
|
|||
@include padding-right(4em);
|
||||
}
|
||||
body .p-dropdown-panel {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.16);
|
||||
border: 0 none;
|
||||
background-color: pvar(--mainBackgroundColor);
|
||||
border: 1px solid pvar(--inputBorderColor);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-filter-container {
|
||||
.p-dropdown-panel .p-dropdown-header {
|
||||
padding: 0.429em 0.857em 0.429em 0.857em;
|
||||
border-bottom: 1px solid #eaeaea;
|
||||
color: #333333;
|
||||
background-color: #ffffff;
|
||||
border-bottom: 1px solid pvar(--mainColorVeryLight);
|
||||
color: pvar(--mainForegroundColor);
|
||||
background-color: pvar(--mainBackgroundColor);
|
||||
margin: 0;
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-filter-container .p-dropdown-filter {
|
||||
.p-dropdown-panel .p-dropdown-header .p-dropdown-filter {
|
||||
@include padding-right(2em);
|
||||
|
||||
width: 100%;
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-filter-container .p-dropdown-filter-icon {
|
||||
.p-dropdown-panel .p-dropdown-header .p-dropdown-filter-icon {
|
||||
top: 50%;
|
||||
margin-top: -0.5em;
|
||||
right: 1.357em;
|
||||
|
@ -216,29 +215,47 @@ body .p-dropdown-panel .p-dropdown-filter-container .p-dropdown-filter-icon {
|
|||
}
|
||||
body .p-dropdown-panel .p-dropdown-items {
|
||||
padding: 0;
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-items .p-dropdown-item, body .p-dropdown-panel .p-dropdown-items .p-dropdown-item-group {
|
||||
margin: 0;
|
||||
padding: 0.429em 0.857em;
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-items .p-dropdown-item {
|
||||
margin: 0;
|
||||
padding: 8px 16px;
|
||||
border: 0 none;
|
||||
color: #333333;
|
||||
color: pvar(--mainForegroundColor);
|
||||
background-color: transparent;
|
||||
-moz-border-radius: 0;
|
||||
-webkit-border-radius: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight,
|
||||
body .p-dropdown-panel .p-dropdown-items .p-dropdown-item-group.p-highlight {
|
||||
color: #ffffff;
|
||||
background-color: pvar(--mainColor);
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
body .p-dropdown-panel .p-dropdown-items .p-dropdown-item:not(.p-highlight):not(.p-disabled):hover,
|
||||
body .p-dropdown-panel .p-dropdown-items .p-dropdown-item-group:not(.p-highlight):not(.p-disabled):hover {
|
||||
color: #333333;
|
||||
background-color: #eaeaea;
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight {
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainColorLightest);
|
||||
}
|
||||
body p-dropdown.ng-dirty.ng-invalid > .p-dropdown {
|
||||
border: 1px solid #a80000;
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item.p-highlight.p-focus {
|
||||
background: pvar(--mainColorLightest);
|
||||
}
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item:not(.p-highlight):not(.p-disabled).p-focus {
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainColorVeryLight);;
|
||||
}
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item:not(.p-highlight):not(.p-disabled):hover {
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainColorVeryLight);;
|
||||
}
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-item-group {
|
||||
margin: 0;
|
||||
padding: 0.857rem;
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainColorVeryLight);;
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
.p-dropdown-panel .p-dropdown-items .p-dropdown-empty-message {
|
||||
padding: 8px 16px;
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
// p-toast
|
||||
|
@ -508,6 +525,178 @@ p-chips.p-chips-clearable .p-chips-clear-icon {
|
|||
right: 0.429rem;
|
||||
}
|
||||
|
||||
// multiselect
|
||||
|
||||
.p-multiselect {
|
||||
background: pvar(--mainBackgroundColor);
|
||||
border: 1px solid pvar(--inputBorderColor);
|
||||
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
|
||||
border-radius: 3px;
|
||||
font-size: 15px;
|
||||
}
|
||||
.p-multiselect:not(.p-disabled):hover {
|
||||
border-color: pvar(--inputBorderColor);;
|
||||
}
|
||||
.p-multiselect:not(.p-disabled).p-focus {
|
||||
outline: 0 none;
|
||||
outline-offset: 0;
|
||||
box-shadow: 0 0 0 0.25rem var(--mainColorLightest);
|
||||
}
|
||||
.p-multiselect .p-multiselect-label {
|
||||
padding: 4px 15px;
|
||||
line-height: normal;
|
||||
transition: background-color 0.2s, color 0.2s, border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.p-multiselect .p-multiselect-label.p-placeholder {
|
||||
color: pvar(--inputPlaceholderColor);
|
||||
}
|
||||
.p-multiselect.p-multiselect-chip .p-multiselect-token {
|
||||
padding: 2px 7px;
|
||||
margin-right: 0.25rem;
|
||||
background: pvar(--mainColorLightest);
|
||||
color: pvar(--mainForegroundColor);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.p-multiselect.p-multiselect-chip .p-multiselect-token .p-multiselect-token-icon {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.p-multiselect .p-multiselect-trigger {
|
||||
background: transparent;
|
||||
width: 2.357rem;
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
.p-multiselect.p-variant-filled {
|
||||
background: pvar(--mainColorLightest);
|
||||
}
|
||||
.p-multiselect.p-variant-filled:not(.p-disabled):hover {
|
||||
background-color: pvar(--mainColorLightest);
|
||||
}
|
||||
.p-multiselect.p-variant-filled:not(.p-disabled).p-focus {
|
||||
background-color: pvar(--mainColorLightest);
|
||||
}
|
||||
|
||||
.p-inputwrapper-filled .p-multiselect.p-multiselect-chip .p-multiselect-label {
|
||||
padding: 3px 15px;
|
||||
}
|
||||
|
||||
.p-multiselect-clearable .p-multiselect-label-container {
|
||||
padding-right: 1.429rem;
|
||||
}
|
||||
.p-multiselect-clearable .p-multiselect-clear-icon {
|
||||
color: pvar(--greyForegroundColor);
|
||||
right: 2.357rem;
|
||||
}
|
||||
|
||||
.p-multiselect-panel {
|
||||
background: pvar(--mainBackgroundColor);
|
||||
color: pvar(--mainForegroundColor);
|
||||
border: 1px solid pvar(--inputBorderColor);
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.16);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header {
|
||||
padding: 0.429rem 0.857rem;
|
||||
border-bottom: 1px solid pvar(--inputBorderColor);
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainBackgroundColor);
|
||||
margin: 0;
|
||||
border-top-right-radius: 3px;
|
||||
border-top-left-radius: 3px;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-filter-container .p-inputtext {
|
||||
padding-right: 1.429rem;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-filter-container .p-multiselect-filter-icon {
|
||||
right: 0.429rem;
|
||||
color: pvar(--greyForegroundColor);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header .p-checkbox {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-close {
|
||||
margin-left: 0.5rem;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
color: pvar(--greyForegroundColor);
|
||||
border: 0 none;
|
||||
background: transparent;
|
||||
border-radius: 50%;
|
||||
transition: background-color 0.2s, color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-close:enabled:hover {
|
||||
color: pvar(--mainColor);
|
||||
border-color: transparent;
|
||||
background: transparent;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-header .p-multiselect-close:focus-visible {
|
||||
outline: 0 none;
|
||||
outline-offset: 0;
|
||||
box-shadow: 0 0 0 0.2rem pvar(--mainColorVeryLight);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items {
|
||||
padding: 0;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item {
|
||||
margin: 0;
|
||||
padding: 6px 14px;
|
||||
border: 0 none;
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: transparent;
|
||||
transition: background-color 0.2s, box-shadow 0.2s;
|
||||
border-radius: 0;
|
||||
line-height: normal;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item.p-highlight {
|
||||
color: pvar(--mainBackgroundColor);
|
||||
background: pvar(--mainColor);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item.p-highlight.p-focus {
|
||||
background: pvar(--mainColor);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item:not(.p-highlight):not(.p-disabled).p-focus {
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainColorVeryLight);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item:not(.p-highlight):not(.p-disabled):hover {
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: pvar(--mainColorVeryLight);
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item .p-checkbox {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-item-group {
|
||||
margin: 0;
|
||||
padding: 0.857rem;
|
||||
color: pvar(--mainForegroundColor);
|
||||
background-color: pvar(--mainColorVeryLight);
|
||||
font-weight: $font-semibold;
|
||||
}
|
||||
.p-multiselect-panel .p-multiselect-items .p-multiselect-empty-message {
|
||||
padding: 0.429rem 0.857rem;
|
||||
color: pvar(--mainForegroundColor);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.p-input-filled .p-multiselect {
|
||||
background-color: pvar(--mainColorVeryLight);
|
||||
}
|
||||
.p-input-filled .p-multiselect:not(.p-disabled):hover {
|
||||
background-color: pvar(--mainColorVeryLight);
|
||||
}
|
||||
.p-input-filled .p-multiselect:not(.p-disabled).p-focus {
|
||||
background-color: pvar(--mainColorVeryLight);
|
||||
}
|
||||
|
||||
p-multiselect.ng-dirty.ng-invalid > .p-multiselect {
|
||||
border-color: pvar(--red);
|
||||
}
|
||||
|
||||
// input text (used by p-chips)
|
||||
|
||||
.p-inputtext {
|
||||
font-size: 15px;
|
||||
color: pvar(--mainForegroundColor);
|
||||
|
@ -1025,3 +1214,27 @@ p-chips {
|
|||
flex-basis: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
.p-multiselect {
|
||||
width: 100%;
|
||||
|
||||
&.p-multiselect-chip .p-multiselect-token {
|
||||
line-height: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.p-dropdown-clear-icon,
|
||||
.p-multiselect-clear-icon {
|
||||
margin-top: -.5em;
|
||||
}
|
||||
|
||||
.p-dropdown {
|
||||
width: 100%;
|
||||
font-size: 15px;
|
||||
|
||||
.p-dropdown-label {
|
||||
padding: 4px 15px;
|
||||
line-height: normal;
|
||||
min-height: 29px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import { JobState } from '@peertube/peertube-models'
|
||||
|
||||
export type JobStateClient = JobState
|
||||
export type JobStateClient = JobState | 'all'
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
export interface SelectOptionsItem {
|
||||
id: string | number
|
||||
label: string
|
||||
|
||||
description?: string
|
||||
group?: string
|
||||
groupLabel?: string
|
||||
imageUrl?: string
|
||||
classes?: string[]
|
||||
}
|
||||
|
||||
export interface SelectChannelItem extends SelectOptionsItem {
|
||||
id: number
|
||||
support: string
|
||||
id: number // Force number
|
||||
avatarPath?: string
|
||||
support?: string
|
||||
}
|
||||
|
|
|
@ -2034,13 +2034,6 @@
|
|||
dependencies:
|
||||
tslib "^2.3.0"
|
||||
|
||||
"@ng-select/ng-select@^13.8.1":
|
||||
version "13.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@ng-select/ng-select/-/ng-select-13.8.1.tgz#4884fa51aeccd534e3dd646385405248c867da68"
|
||||
integrity sha512-zN+uYkTOZliRxEm9zS7M08g21YXaNsBLp9/zJgjC5TzNrrfB6vxgOzbDjYzNUqSbZsbod70HoPgzmi81pJhMyg==
|
||||
dependencies:
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@ngtools/webpack@18.2.0":
|
||||
version "18.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-18.2.0.tgz#7977e2686020e00ace813a81445cb487dd5ee56b"
|
||||
|
@ -10890,7 +10883,7 @@ tsconfig-paths@^3.15.0:
|
|||
minimist "^1.2.6"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
tslib@2.6.3, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.6.2:
|
||||
tslib@2.6.3, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.3.0, tslib@^2.4.0, tslib@^2.6.2:
|
||||
version "2.6.3"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0"
|
||||
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==
|
||||
|
|
Loading…
Reference in New Issue