]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/commitdiff
Support custom value in ng-select
authorChocobozzz <me@florianbigard.com>
Tue, 9 Feb 2021 15:35:48 +0000 (16:35 +0100)
committerChocobozzz <me@florianbigard.com>
Wed, 10 Feb 2021 10:36:40 +0000 (11:36 +0100)
13 files changed:
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.html
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.scss
client/src/app/+admin/config/edit-custom-config/edit-custom-config.component.ts
client/src/app/shared/shared-forms/select/index.ts
client/src/app/shared/shared-forms/select/select-custom-input.component.html [deleted file]
client/src/app/shared/shared-forms/select/select-custom-input.component.scss [deleted file]
client/src/app/shared/shared-forms/select/select-custom-input.component.ts [deleted file]
client/src/app/shared/shared-forms/select/select-custom-value.component.html [new file with mode: 0644]
client/src/app/shared/shared-forms/select/select-custom-value.component.ts [new file with mode: 0644]
client/src/app/shared/shared-forms/select/select-options.component.html
client/src/app/shared/shared-forms/select/select-options.component.ts
client/src/app/shared/shared-forms/select/select-shared.component.scss
client/src/app/shared/shared-forms/shared-form.module.ts

index 5f0a5ff6c1659bf0aeef8b0f12b2949fbc0ea135..844620ca2f1cb5b6c4fa987bdff8913bb07afb4c 100644 (file)
                     <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>
 
                   <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>
 
                   <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>
 
                 <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')"
                       <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>
 
                     <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>
                     <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>
 
                   <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')"
                         <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>
 
index 66524736874cc607090d1294cfaa1e4be032915d..c12465d458a6e89da5eea73e9ec06ef734c27276 100644 (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);
 
index 48fb86968b6eeaecf8a5345ca8ca2998cbfe7ef4..a9f72d7dbdd45e52534f206d1d366edbb2d63dd7 100644 (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 () {
index 33459b23bff7fca695aa982807499f6001bf15b8..e387e1f48831858949af1716c1ff820137f5f7ad 100644 (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'
diff --git a/client/src/app/shared/shared-forms/select/select-custom-input.component.html b/client/src/app/shared/shared-forms/select/select-custom-input.component.html
deleted file mode 100644 (file)
index 84fa15c..0000000
+++ /dev/null
@@ -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>
diff --git a/client/src/app/shared/shared-forms/select/select-custom-input.component.scss b/client/src/app/shared/shared-forms/select/select-custom-input.component.scss
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/client/src/app/shared/shared-forms/select/select-custom-input.component.ts b/client/src/app/shared/shared-forms/select/select-custom-input.component.ts
deleted file mode 100644 (file)
index ba6fef8..0000000
+++ /dev/null
@@ -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)
-  }
-}
diff --git a/client/src/app/shared/shared-forms/select/select-custom-value.component.html b/client/src/app/shared/shared-forms/select/select-custom-value.component.html
new file mode 100644 (file)
index 0000000..5fdf432
--- /dev/null
@@ -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>
diff --git a/client/src/app/shared/shared-forms/select/select-custom-value.component.ts b/client/src/app/shared/shared-forms/select/select-custom-value.component.ts
new file mode 100644 (file)
index 0000000..a8e5ad0
--- /dev/null
@@ -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'
+  }
+}
index 6d0d7e9255fed40c8b779bd5f37124ab2b540a65..3b17612559fd0e6b6d7896f7b415d5d058bfec45 100644 (file)
@@ -6,6 +6,7 @@
   [clearable]="clearable"
   [labelForId]="labelForId"
   [searchable]="searchable"
+  [searchFn]="searchFn"
 
   bindLabel="label"
   bindValue="id"
index f0abd1a680e5f5a15999b6a1a25800a8342d184a..51a3f515da41de9edfc4b78cefd50310701871e3 100644 (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
 
index 0b4c6b784d74b7835062b5969679409bab3eab07..1a4192b55bbbbc7360debc5fbdc52feb7b9b8e03 100644 (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;
+}
index 22e8dd05ac8f1ee861c54f5ac94b418f5c8490bd..9bdd138a147dc1ee0ff2114b24e03c1a0e8692a1 100644 (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
   ],