Support custom value in ng-select

pull/3745/head
Chocobozzz 2021-02-09 16:35:48 +01:00
parent b9c9fefe82
commit ead64cdf8d
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
13 changed files with 168 additions and 118 deletions

View File

@ -445,13 +445,11 @@
<span i18n>allows to import multiple videos in parallel. ⚠️ Requires a PeerTube restart.</span>
</span>
<div class="peertube-select-container">
<select id="importConcurrency" formControlName="concurrency" class="form-control">
<option *ngFor="let option of concurrencyOptions" [value]="option">
{{ option }}
</option>
</select>
<div class="number-with-unit">
<input type="number" name="importConcurrency" formControlName="concurrency" />
<span i18n>jobs in parallel</span>
</div>
<div *ngIf="formErrors.import.concurrency" class="form-error">{{ formErrors.import.concurrency }}</div>
</div>
@ -892,13 +890,13 @@
<ng-container *ngIf="!getTotalTranscodingThreads().atMost" i18n>will claim at least {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with live transcoding</ng-container>
</span>
<div class="peertube-select-container">
<select id="transcodingThreads" formControlName="threads" class="form-control">
<option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value">
{{ transcodingThreadOption.label }} {transcodingThreadOption.value, plural, =0 {} =1 {thread} other {threads}}
</option>
</select>
</div>
<my-select-custom-value
id="transcodingThreads"
[items]="transcodingThreadOptions"
formControlName="threads"
[clearable]="false"
></my-select-custom-value>
<div *ngIf="formErrors.transcoding.threads" class="form-error">{{ formErrors.transcoding.threads }}</div>
</div>
@ -908,13 +906,11 @@
<span i18n>allows to transcode multiple files in parallel. ⚠️ Requires a PeerTube restart.</span>
</span>
<div class="peertube-select-container">
<select id="transcodingConcurrency" formControlName="concurrency" class="form-control">
<option *ngFor="let option of concurrencyOptions" [value]="option">
{{ option }}
</option>
</select>
<div class="number-with-unit">
<input type="number" name="transcodingConcurrency" formControlName="concurrency" />
<span i18n>jobs in parallel</span>
</div>
<div *ngIf="formErrors.transcoding.concurrency" class="form-error">{{ formErrors.transcoding.concurrency }}</div>
</div>
@ -922,7 +918,7 @@
<label i18n for="transcodingProfile">Transcoding profile</label>
<span class="text-muted ml-1" i18n>new transcoding profiles can be added by PeerTube plugins</span>
<ng-select
<my-select-options
id="transcodingProfile"
formControlName="profile"
[items]="getAvailableTranscodingProfile('vod')"
@ -935,7 +931,7 @@
<span class="text-muted" i18n>x264, targeting maximum device compatibility</span>
</ng-container>
</ng-template>
</ng-select>
</my-select-options>
<div *ngIf="formErrors.transcoding.profile" class="form-error">{{ formErrors.transcoding.profile }}</div>
</div>
@ -1007,10 +1003,10 @@
<div class="form-group" [ngClass]="{ 'disabled-checkbox-extra': !isLiveEnabled() }">
<label i18n for="liveMaxDuration">Max live duration</label>
<ng-select
<my-select-options
labelForId="liveMaxDuration" [items]="liveMaxDurationOptions" formControlName="maxDuration"
bindLabel="label" bindValue="value" [clearable]="false" [searchable]="false"
></ng-select>
bindLabel="label" bindValue="value" [clearable]="false" [searchable]="true"
></my-select-options>
</div>
</ng-container>
@ -1069,13 +1065,12 @@
<ng-container *ngIf="!getTotalTranscodingThreads().atMost" i18n>will claim at least {{ getTotalTranscodingThreads().value }} {{ getTotalTranscodingThreads().unit }} with VOD transcoding</ng-container>
</span>
<div class="peertube-select-container">
<select id="liveTranscodingThreads" formControlName="threads" class="form-control">
<option *ngFor="let transcodingThreadOption of transcodingThreadOptions" [value]="transcodingThreadOption.value">
{{ transcodingThreadOption.label }} {transcodingThreadOption.value, plural, =0 {} =1 {thread} other {threads}}
</option>
</select>
</div>
<my-select-custom-value
id="liveTranscodingThreads"
[items]="transcodingThreadOptions"
formControlName="threads"
[clearable]="false"
></my-select-custom-value>
<div *ngIf="formErrors.live.transcoding.threads" class="form-error">{{ formErrors.live.transcoding.threads }}</div>
</div>
@ -1083,7 +1078,7 @@
<label i18n for="liveTranscodingProfile">Live transcoding profile</label>
<span class="text-muted ml-1" i18n>new live transcoding profiles can be added by PeerTube plugins</span>
<ng-select
<my-select-options
id="liveTranscodingProfile"
formControlName="profile"
[items]="getAvailableTranscodingProfile('live')"
@ -1096,7 +1091,7 @@
<span class="text-muted" i18n>x264, targeting maximum device compatibility</span>
</ng-container>
</ng-template>
</ng-select>
</my-select-options>
<div *ngIf="formErrors.live.transcoding.profile" class="form-error">{{ formErrors.live.transcoding.profile }}</div>
</div>

View File

@ -42,7 +42,8 @@ input[type=checkbox] {
@include peertube-select-container($form-base-input-width);
}
ng-select,
my-select-options,
my-select-custom-value,
my-select-checkbox {
@include responsive-width($form-base-input-width);

View File

@ -37,12 +37,9 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
resolutions: { id: string, label: string, description?: string }[] = []
liveResolutions: { id: string, label: string, description?: string }[] = []
concurrencyOptions: number[] = []
transcodingThreadOptions: { label: string, value: number }[] = []
liveMaxDurationOptions: { label: string, value: number }[] = []
vodTranscodingProfileOptions: string[] = []
liveTranscodingProfileOptions: string[] = []
transcodingThreadOptions: SelectOptionsItem[] = []
liveMaxDurationOptions: SelectOptionsItem[] = []
languageItems: SelectOptionsItem[] = []
categoryItems: SelectOptionsItem[] = []
@ -99,23 +96,22 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
this.liveResolutions = this.resolutions.filter(r => r.id !== '0p')
this.transcodingThreadOptions = [
{ value: 0, label: $localize`Auto (via ffmpeg)` },
{ value: 1, label: '1' },
{ value: 2, label: '2' },
{ value: 4, label: '4' },
{ value: 8, label: '8' }
{ id: 0, label: $localize`Auto (via ffmpeg)` },
{ id: 1, label: '1' },
{ id: 2, label: '2' },
{ id: 4, label: '4' },
{ id: 8, label: '8' },
{ id: 12, label: '12' },
{ id: 16, label: '16' },
{ id: 32, label: '32' }
]
this.concurrencyOptions = [ 1, 2, 3, 4, 5, 6 ]
this.vodTranscodingProfileOptions = [ 'default' ]
this.liveTranscodingProfileOptions = [ 'default' ]
this.liveMaxDurationOptions = [
{ value: -1, label: $localize`No limit` },
{ value: 1000 * 3600, label: $localize`1 hour` },
{ value: 1000 * 3600 * 3, label: $localize`3 hours` },
{ value: 1000 * 3600 * 5, label: $localize`5 hours` },
{ value: 1000 * 3600 * 10, label: $localize`10 hours` }
{ id: -1, label: $localize`No limit` },
{ id: 1000 * 3600, label: $localize`1 hour` },
{ id: 1000 * 3600 * 3, label: $localize`3 hours` },
{ id: 1000 * 3600 * 5, label: $localize`5 hours` },
{ id: 1000 * 3600 * 10, label: $localize`10 hours` }
]
}
@ -137,11 +133,11 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
}
getAvailableTranscodingProfile (type: 'live' | 'vod') {
if (type === 'live') {
return this.serverConfig.live.transcoding.availableProfiles
}
const profiles = type === 'live'
? this.serverConfig.live.transcoding.availableProfiles
: this.serverConfig.transcoding.availableProfiles
return this.serverConfig.transcoding.availableProfiles
return profiles.map(p => ({ id: p, label: p }))
}
getTotalTranscodingThreads () {

View File

@ -1,4 +1,5 @@
export * from './select-channel.component'
export * from './select-checkbox.component'
export * from './select-custom-value.component'
export * from './select-options.component'
export * from './select-tags.component'
export * from './select-checkbox.component'

View File

@ -1,14 +0,0 @@
<ng-select
[(ngModel)]="selectedId"
(ngModelChange)="onModelChange()"
[bindLabel]="bindLabel"
[bindValue]="bindValue"
[clearable]="clearable"
[searchable]="searchable"
>
<ng-option *ngFor="let item of items" [value]="item.id">
{{ channel.label }}
</ng-option>
<ng-content></ng-content>
</ng-select>

View File

@ -1,44 +0,0 @@
import { Component, forwardRef, Input } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
@Component({
selector: 'my-select-custom-input',
styleUrls: [ './select-custom-input.component.scss' ],
templateUrl: './select-custom-input.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectCustomInputComponent),
multi: true
}
]
})
export class SelectCustomInputComponent implements ControlValueAccessor {
@Input() items: any[] = []
selectedId: number
// ng-select options
bindLabel = 'label'
bindValue = 'id'
clearable = false
searchable = false
propagateChange = (_: any) => { /* empty */ }
writeValue (id: number) {
this.selectedId = id
}
registerOnChange (fn: (_: any) => void) {
this.propagateChange = fn
}
registerOnTouched () {
// Unused
}
onModelChange () {
this.propagateChange(this.selectedId)
}
}

View File

@ -0,0 +1,14 @@
<div class="root">
<my-select-options
[items]="itemsWithCustom"
[clearable]="clearable"
[searchable]="searchable"
[groupBy]="groupBy"
[labelForId]="labelForId"
[(ngModel)]="selectedId"
(ngModelChange)="onModelChange()"
></my-select-options>
<input *ngIf="isCustomValue()" [(ngModel)]="customValue" (ngModelChange)="onModelChange()" type="text" class="form-control" />
</div>

View File

@ -0,0 +1,76 @@
import { Component, forwardRef, Input, OnChanges } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { SelectOptionsItem } from './select-options.component'
@Component({
selector: 'my-select-custom-value',
styleUrls: [ './select-shared.component.scss' ],
templateUrl: './select-custom-value.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectCustomValueComponent),
multi: true
}
]
})
export class SelectCustomValueComponent implements ControlValueAccessor, OnChanges {
@Input() items: SelectOptionsItem[] = []
@Input() clearable = false
@Input() searchable = false
@Input() groupBy: string
@Input() labelForId: string
customValue: number | string = ''
selectedId: number | string
itemsWithCustom: SelectOptionsItem[] = []
ngOnChanges () {
this.itemsWithCustom = this.getItems()
}
propagateChange = (_: any) => { /* empty */ }
writeValue (id: number | string) {
this.selectedId = id
if (this.isSelectedIdInItems() !== true) {
this.selectedId = 'other'
this.customValue = id
}
}
registerOnChange (fn: (_: any) => void) {
this.propagateChange = fn
}
registerOnTouched () {
// Unused
}
onModelChange () {
if (this.selectedId === 'other') {
return this.propagateChange(this.customValue)
}
return this.propagateChange(this.selectedId)
}
isSelectedIdInItems () {
return !!this.items.find(i => i.id === this.selectedId)
}
getItems () {
const other: SelectOptionsItem = {
id: 'other',
label: $localize`Custom value...`
}
return this.items.concat([ other ])
}
isCustomValue () {
return this.selectedId === 'other'
}
}

View File

@ -6,6 +6,7 @@
[clearable]="clearable"
[labelForId]="labelForId"
[searchable]="searchable"
[searchFn]="searchFn"
bindLabel="label"
bindValue="id"

View File

@ -27,6 +27,7 @@ export class SelectOptionsComponent implements ControlValueAccessor {
@Input() searchable = false
@Input() groupBy: string
@Input() labelForId: string
@Input() searchFn: any
selectedId: number | string

View File

@ -30,3 +30,18 @@ ng-select ::ng-deep {
width: 20px;
}
}
.root {
display:flex;
> my-select-options {
flex-grow: 1;
}
}
input[type=text] {
margin-left: 5px;
@include peertube-input-text($form-base-input-width);
display: block;
}

View File

@ -7,13 +7,19 @@ import { SharedGlobalIconModule } from '../shared-icons'
import { SharedMainModule } from '../shared-main/shared-main.module'
import { DynamicFormFieldComponent } from './dynamic-form-field.component'
import { FormValidatorService } from './form-validator.service'
import { InputToggleHiddenComponent } from './input-toggle-hidden.component'
import { InputSwitchComponent } from './input-switch.component'
import { InputToggleHiddenComponent } from './input-toggle-hidden.component'
import { MarkdownTextareaComponent } from './markdown-textarea.component'
import { PeertubeCheckboxComponent } from './peertube-checkbox.component'
import { PreviewUploadComponent } from './preview-upload.component'
import { ReactiveFileComponent } from './reactive-file.component'
import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select'
import {
SelectChannelComponent,
SelectCheckboxComponent,
SelectCustomValueComponent,
SelectOptionsComponent,
SelectTagsComponent
} from './select'
import { TextareaAutoResizeDirective } from './textarea-autoresize.directive'
import { TimestampInputComponent } from './timestamp-input.component'
@ -44,6 +50,7 @@ import { TimestampInputComponent } from './timestamp-input.component'
SelectOptionsComponent,
SelectTagsComponent,
SelectCheckboxComponent,
SelectCustomValueComponent,
DynamicFormFieldComponent
],
@ -69,6 +76,7 @@ import { TimestampInputComponent } from './timestamp-input.component'
SelectOptionsComponent,
SelectTagsComponent,
SelectCheckboxComponent,
SelectCustomValueComponent,
DynamicFormFieldComponent
],