Use ng select for multiselect

pull/3066/head
Chocobozzz 2020-08-11 16:07:53 +02:00
parent 3d25d5de33
commit 52c4976fcf
No known key found for this signature in database
GPG Key ID: 583A612D890159BE
31 changed files with 316 additions and 198 deletions

View File

@ -48,11 +48,12 @@
<label i18n for="instanceCategories">Main instance categories</label>
<div>
<p-multiSelect
inputId="instanceCategories" [options]="categoryItems" formControlName="categories" [showToggleAll]="false"
[defaultLabel]="getDefaultCategoryLabel()" [selectedItemsLabel]="getSelectedCategoryLabel()"
emptyFilterMessage="No results found" i18n-emptyFilterMessage
></p-multiSelect>
<my-select-checkbox
id="instanceCategories"
formControlName="categories" [availableItems]="categoryItems"
[selectableGroup]="false"
>
</my-select-checkbox>
</div>
</div>
@ -60,11 +61,12 @@
<label i18n for="instanceLanguages">Main languages you/your moderators speak</label>
<div>
<p-multiSelect
inputId="instanceLanguages" [options]="languageItems" formControlName="languages" [showToggleAll]="false"
[defaultLabel]="getDefaultLanguageLabel()" [selectedItemsLabel]="getSelectedLanguageLabel()"
emptyFilterMessage="No results found" i18n-emptyFilterMessage
></p-multiSelect>
<my-select-checkbox
id="instanceLanguages"
formControlName="languages" [availableItems]="languageItems"
[selectableGroup]="false"
>
</my-select-checkbox>
</div>
</div>

View File

@ -30,6 +30,10 @@ input[type=checkbox] {
@include peertube-select-container($form-base-input-width);
}
my-select-checkbox {
@include ng-select($form-base-input-width);
}
input[type=submit] {
@include peertube-button;
@include orange-button;

View File

@ -1,11 +1,16 @@
import { SelectItem } from 'primeng/api'
import { forkJoin } from 'rxjs'
import { ViewportScroller } from '@angular/common'
import { AfterViewChecked, Component, OnInit, ViewChild } from '@angular/core'
import { ConfigService } from '@app/+admin/config/shared/config.service'
import { Notifier } from '@app/core'
import { ServerService } from '@app/core/server/server.service'
import { CustomConfigValidatorsService, FormReactive, FormValidatorService, UserValidatorsService } from '@app/shared/shared-forms'
import {
CustomConfigValidatorsService,
FormReactive,
FormValidatorService,
SelectOptionsItem,
UserValidatorsService
} from '@app/shared/shared-forms'
import { NgbNav } from '@ng-bootstrap/ng-bootstrap'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { CustomConfig, ServerConfig } from '@shared/models'
@ -25,8 +30,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
resolutions: { id: string, label: string, description?: string }[] = []
transcodingThreadOptions: { label: string, value: number }[] = []
languageItems: SelectItem[] = []
categoryItems: SelectItem[] = []
languageItems: SelectOptionsItem[] = []
categoryItems: SelectOptionsItem[] = []
private serverConfig: ServerConfig
@ -290,22 +295,6 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
)
}
getSelectedLanguageLabel () {
return this.i18n('{{\'{0} languages selected')
}
getDefaultLanguageLabel () {
return this.i18n('No language')
}
getSelectedCategoryLabel () {
return this.i18n('{{\'{0} categories selected')
}
getDefaultCategoryLabel () {
return this.i18n('No category')
}
gotoAnchor () {
const hashToNav = {
'customizations': 'advanced-configuration'
@ -331,8 +320,8 @@ export class EditCustomConfigComponent extends FormReactive implements OnInit, A
([ config, languages, categories ]) => {
this.customConfig = config
this.languageItems = languages.map(l => ({ label: l.label, value: l.id }))
this.categoryItems = categories.map(l => ({ label: l.label, value: l.id }))
this.languageItems = languages.map(l => ({ label: l.label, id: l.id }))
this.categoryItems = categories.map(l => ({ label: l.label, id: l.id + '' }))
this.updateForm()
// Force form validation

View File

@ -57,12 +57,12 @@
<div role="menu" class="dropdown-menu" ngbDropdownMenu>
<div class="dropdown-header" i18n>Table parameters</div>
<div ngbDropdownItem class="dropdown-item">
<p-multiSelect
[options]="columns" [showToggleAll]="true" [(ngModel)]="selectedColumns" optionLabel="label"
emptyFilterMessage="No matching column found" i18n-emptyFilterMessage [filter]="false"
selectedItemsLabel="{0} columns displayed" i18n-emptyFilterMessage [showHeader]="false"
[maxSelectedLabels]="4"
></p-multiSelect>
<my-select-checkbox
name="columns"
[availableItems]="columns"
[selectableGroup]="false" [(ngModel)]="selectedColumns"
>
</my-select-checkbox>
</div>
<div ngbDropdownItem class="dropdown-item">
<my-peertube-checkbox inputName="highlightBannedUsers" [(ngModel)]="highlightBannedUsers"
@ -71,14 +71,14 @@
</div>
</div>
</th>
<th *ngIf="getColumn('username')" pResizableColumn i18n pSortableColumn="username">{{ getColumn('username').label }} <p-sortIcon field="username"></p-sortIcon></th>
<th *ngIf="getColumn('email')" i18n>{{ getColumn('email').label }}</th>
<th *ngIf="getColumn('quota')" style="width: 160px;" i18n pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
<th *ngIf="getColumn('quotaDaily')" style="width: 160px;" i18n>{{ getColumn('quotaDaily').label }}</th>
<th *ngIf="getColumn('role')" style="width: 120px;" i18n pSortableColumn="role">{{ getColumn('role').label }} <p-sortIcon field="role"></p-sortIcon></th>
<th *ngIf="getColumn('pluginAuth')" style="width: 140px;" pResizableColumn i18n>{{ getColumn('pluginAuth').label }}</th>
<th *ngIf="getColumn('createdAt')" style="width: 150px;" i18n pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th>
<th *ngIf="getColumn('lastLoginDate')" style="width: 150px;" i18n pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th>
<th *ngIf="isSelected('username')" pResizableColumn i18n pSortableColumn="username">{{ getColumn('username').label }} <p-sortIcon field="username"></p-sortIcon></th>
<th *ngIf="isSelected('email')" i18n>{{ getColumn('email').label }}</th>
<th *ngIf="isSelected('quota')" style="width: 160px;" i18n pSortableColumn="videoQuotaUsed">{{ getColumn('quota').label }} <p-sortIcon field="videoQuotaUsed"></p-sortIcon></th>
<th *ngIf="isSelected('quotaDaily')" style="width: 160px;" i18n>{{ getColumn('quotaDaily').label }}</th>
<th *ngIf="isSelected('role')" style="width: 120px;" i18n pSortableColumn="role">{{ getColumn('role').label }} <p-sortIcon field="role"></p-sortIcon></th>
<th *ngIf="isSelected('pluginAuth')" style="width: 140px;" pResizableColumn i18n>{{ getColumn('pluginAuth').label }}</th>
<th *ngIf="isSelected('createdAt')" style="width: 150px;" i18n pSortableColumn="createdAt">{{ getColumn('createdAt').label }} <p-sortIcon field="createdAt"></p-sortIcon></th>
<th *ngIf="isSelected('lastLoginDate')" style="width: 150px;" i18n pSortableColumn="lastLoginDate">{{ getColumn('lastLoginDate').label }} <p-sortIcon field="lastLoginDate"></p-sortIcon></th>
</tr>
</ng-template>
@ -101,7 +101,7 @@
</my-user-moderation-dropdown>
</td>
<td *ngIf="getColumn('username')">
<td *ngIf="isSelected('username')">
<a i18n-title title="Open account in a new tab" target="_blank" rel="noopener noreferrer" [routerLink]="[ '/accounts/' + user.username ]">
<div class="chip two-lines">
<img
@ -118,7 +118,7 @@
</a>
</td>
<td *ngIf="getColumn('email')" [title]="user.email">
<td *ngIf="isSelected('email')" [title]="user.email">
<ng-container *ngIf="!requiresEmailVerification || user.blocked; else emailWithVerificationStatus">
<a class="table-email" [href]="'mailto:' + user.email">{{ user.email }}</a>
</ng-container>
@ -135,7 +135,7 @@
</ng-template>
</ng-template>
<td *ngIf="getColumn('quota')">
<td *ngIf="isSelected('quota')">
<div class="progress" i18n-title title="Total video quota">
<div class="progress-bar" role="progressbar" [style]="{ width: getUserVideoQuotaPercentage(user) + '%' }"
[attr.aria-valuenow]="user.rawVideoQuotaUsed" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuota">
@ -145,7 +145,7 @@
</div>
</td>
<td *ngIf="getColumn('quotaDaily')">
<td *ngIf="isSelected('quotaDaily')">
<div class="progress" i18n-title title="Total daily video quota">
<div class="progress-bar secondary" role="progressbar" [style]="{ width: getUserVideoQuotaDailyPercentage(user) + '%' }"
[attr.aria-valuenow]="user.rawVideoQuotaUsedDaily" aria-valuemin="0" [attr.aria-valuemax]="user.rawVideoQuotaDaily">
@ -155,18 +155,18 @@
</div>
</td>
<td *ngIf="getColumn('role')">
<td *ngIf="isSelected('role')">
<span *ngIf="user.blocked" class="badge badge-banned" i18n-title title="The user was banned">{{ user.roleLabel }}</span>
<span *ngIf="!user.blocked" class="badge" [ngClass]="getRoleClass(user.role)">{{ user.roleLabel }}</span>
</td>
<td *ngIf="getColumn('pluginAuth')">
<td *ngIf="isSelected('pluginAuth')">
<ng-container *ngIf="user.pluginAuth">{{ user.pluginAuth }}</ng-container>
</td>
<td *ngIf="getColumn('createdAt')" [title]="user.createdAt">{{ user.createdAt | date: 'short' }}</td>
<td *ngIf="isSelected('createdAt')" [title]="user.createdAt">{{ user.createdAt | date: 'short' }}</td>
<td *ngIf="getColumn('lastLoginDate')" [title]="user.lastLoginDate">{{ user.lastLoginDate | date: 'short' }}</td>
<td *ngIf="isSelected('lastLoginDate')" [title]="user.lastLoginDate">{{ user.lastLoginDate | date: 'short' }}</td>
</tr>
</ng-template>

View File

@ -61,6 +61,7 @@ my-global-icon {
.input-group {
@include peertube-input-group(300px);
input {
flex: 1;
}

View File

@ -30,9 +30,9 @@ export class UserListComponent extends RestTable implements OnInit {
selectedUsers: User[] = []
bulkUserActions: DropdownAction<User[]>[][] = []
columns: { key: string, label: string }[]
columns: { id: string, label: string }[]
private _selectedColumns: { key: string, label: string }[]
private _selectedColumns: string[]
private serverConfig: ServerConfig
constructor (
@ -60,7 +60,7 @@ export class UserListComponent extends RestTable implements OnInit {
return this._selectedColumns
}
set selectedColumns (val) {
set selectedColumns (val: string[]) {
this._selectedColumns = val
}
@ -112,16 +112,18 @@ export class UserListComponent extends RestTable implements OnInit {
]
this.columns = [
{ key: 'username', label: 'Username' },
{ key: 'email', label: 'Email' },
{ key: 'quota', label: 'Video quota' },
{ key: 'role', label: 'Role' },
{ key: 'createdAt', label: 'Created' }
{ id: 'username', label: 'Username' },
{ id: 'email', label: 'Email' },
{ id: 'quota', label: 'Video quota' },
{ id: 'role', label: 'Role' },
{ id: 'createdAt', label: 'Created' }
]
this.selectedColumns = [ ...this.columns ] // make a full copy of the array
this.columns.push({ key: 'quotaDaily', label: 'Daily quota' })
this.columns.push({ key: 'pluginAuth', label: 'Auth plugin' })
this.columns.push({ key: 'lastLoginDate', label: 'Last login' })
this.selectedColumns = this.columns.map(c => c.id)
this.columns.push({ id: 'quotaDaily', label: 'Daily quota' })
this.columns.push({ id: 'pluginAuth', label: 'Auth plugin' })
this.columns.push({ id: 'lastLoginDate', label: 'Last login' })
}
getIdentifier () {
@ -139,8 +141,12 @@ export class UserListComponent extends RestTable implements OnInit {
}
}
getColumn (key: string) {
return this.selectedColumns.find((col: { key: string }) => col.key === key)
isSelected (id: string) {
return this.selectedColumns.find(c => c === id)
}
getColumn (id: string) {
return this.columns.find(c => c.id === id)
}
getUserVideoQuotaPercentage (user: UserForList) {

View File

@ -1,5 +1,4 @@
import { FormReactive } from '@app/shared/shared-forms'
import { SelectChannelItem } from '@app/shared/shared-forms/select-channel.component'
import { FormReactive, SelectChannelItem } from '@app/shared/shared-forms'
import { VideoConstant, VideoPlaylistPrivacy } from '@shared/models'
import { VideoPlaylist } from '@shared/models/videos/playlist/video-playlist.model'

View File

@ -4,8 +4,7 @@ import { Component, Input, NgZone, OnDestroy, OnInit, ViewChild } from '@angular
import { FormArray, FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'
import { ServerService } from '@app/core'
import { removeElementFromArray } from '@app/helpers'
import { FormReactiveValidationMessages, FormValidatorService, VideoValidatorsService } from '@app/shared/shared-forms'
import { SelectChannelItem } from '@app/shared/shared-forms/select-channel.component'
import { FormReactiveValidationMessages, FormValidatorService, SelectChannelItem, VideoValidatorsService } from '@app/shared/shared-forms'
import { InstanceService } from '@app/shared/shared-instance'
import { VideoCaptionEdit, VideoEdit, VideoService } from '@app/shared/shared-main'
import { I18n } from '@ngx-translate/i18n-polyfill'

View File

@ -2,8 +2,7 @@ import { catchError, switchMap, tap } from 'rxjs/operators'
import { Directive, EventEmitter, OnInit } from '@angular/core'
import { AuthService, CanComponentDeactivateResult, Notifier, ServerService } from '@app/core'
import { populateAsyncUserVideoChannels } from '@app/helpers'
import { FormReactive } from '@app/shared/shared-forms'
import { SelectChannelItem } from '@app/shared/shared-forms/select-channel.component'
import { FormReactive, SelectChannelItem } from '@app/shared/shared-forms'
import { VideoCaptionEdit, VideoCaptionService, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { ServerConfig, VideoConstant, VideoPrivacy } from '@shared/models'

View File

@ -2,8 +2,7 @@ import { map, switchMap } from 'rxjs/operators'
import { Component, HostListener, OnInit } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { Notifier } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { SelectChannelItem } from '@app/shared/shared-forms/select-channel.component'
import { FormReactive, FormValidatorService, SelectChannelItem } from '@app/shared/shared-forms'
import { VideoCaptionEdit, VideoCaptionService, VideoDetails, VideoEdit, VideoService } from '@app/shared/shared-main'
import { LoadingBarService } from '@ngx-loading-bar/core'
import { I18n } from '@ngx-translate/i18n-polyfill'

View File

@ -1,7 +1,7 @@
import { DatePipe } from '@angular/common'
import { SelectChannelItem } from '@app/shared/shared-forms'
import { environment } from '../../environments/environment'
import { AuthService } from '../core/auth'
import { SelectChannelItem } from '@app/shared/shared-forms/select-channel.component'
// Thanks: https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript
function getParameterByName (name: string, url: string) {

View File

@ -1,5 +1,6 @@
export * from './form-validators'
export * from './form-reactive'
export * from './select'
export * from './input-readonly-copy.component'
export * from './markdown-textarea.component'
export * from './peertube-checkbox.component'

View File

@ -1,20 +0,0 @@
$width-size: auto;
ng-select {
width: $width-size;
@media screen and (max-width: $width-size) {
width: 100%;
}
}
// make sure the image is vertically adjusted
ng-select ::ng-deep .ng-value-label img {
position: relative;
top: -1px;
}
ng-select ::ng-deep img {
border-radius: 50%;
height: 20px;
width: 20px;
}

View File

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

View File

@ -1,6 +1,6 @@
import { Component, forwardRef, Input } from '@angular/core'
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'
import { Actor } from '../shared-main'
import { Actor } from '@app/shared/shared-main/account/actor.model'
export type SelectChannelItem = {
id: number

View File

@ -0,0 +1,41 @@
<ng-select
[items]="availableItems"
[(ngModel)]="selectedItems"
(ngModelChange)="onModelChange()"
i18n-placeholder placeholder="Add a new language"
[clearable]="true"
[multiple]="true"
[searchable]="true"
[closeOnSelect]="false"
bindValue="id"
bindLabel="label"
notFoundText="No items found" i18n-notFoundText
[selectableGroup]="selectableGroup"
[selectableGroupAsModel]="selectableGroupAsModel"
groupBy="group"
[compareWith]="compareFn"
[maxSelectedItems]="maxSelectedItems"
>
<ng-template ng-optgroup-tmp let-item="item" let-item$="item$" let-index="index">
<div class="form-group-checkbox">
<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="form-group-checkbox">
<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>

View File

@ -0,0 +1,18 @@
@import '_variables';
@import '_mixins';
ng-select ::ng-deep {
.ng-option {
display: flex;
align-items: center;
}
.form-group-checkbox {
display: flex;
align-items: center;
input {
@include peertube-checkbox(1px);
}
}
}

View File

@ -0,0 +1,75 @@
import { Component, Input, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'
import { SelectOptionsItem } from './select-options.component'
export type ItemSelectCheckboxValue = { id?: string | number, group?: string } | string
@Component({
selector: 'my-select-checkbox',
styleUrls: [ './select-shared.component.scss', 'select-checkbox.component.scss' ],
templateUrl: './select-checkbox.component.html',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => SelectCheckboxComponent),
multi: true
}
]
})
export class SelectCheckboxComponent implements ControlValueAccessor {
@Input() availableItems: SelectOptionsItem[] = []
@Input() selectedItems: ItemSelectCheckboxValue[] = []
@Input() selectableGroup: boolean
@Input() selectableGroupAsModel: boolean
@Input() maxSelectedItems: number
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
}
this.propagateChange(this.selectedItems)
}
registerOnChange (fn: (_: any) => void) {
this.propagateChange = fn
}
registerOnTouched () {
// Unused
}
onModelChange () {
this.propagateChange(this.selectedItems)
}
compareFn (item: SelectOptionsItem, selected: ItemSelectCheckboxValue) {
if (typeof selected === 'string') {
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
}
}

View File

@ -3,10 +3,11 @@
[groupBy]="groupBy"
[(ngModel)]="selectedId"
(ngModelChange)="onModelChange()"
[bindLabel]="bindLabel"
[bindValue]="bindValue"
[clearable]="clearable"
[searchable]="searchable"
bindLabel="label"
bindValue="id"
>
<ng-template ng-option-tmp let-item="item" let-index="index">
{{ item.label }}

View File

@ -1,7 +1,13 @@
import { Component, Input, forwardRef } from '@angular/core'
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms'
export type SelectOptionsItem = { id: number | string, label: string, description?: string }
export type SelectOptionsItem = {
id: string | number
label: string
description?: string
group?: string
groupLabel?: string
}
@Component({
selector: 'my-select-options',
@ -19,14 +25,10 @@ export class SelectOptionsComponent implements ControlValueAccessor {
@Input() items: SelectOptionsItem[] = []
@Input() clearable = false
@Input() searchable = false
@Input() bindValue = 'id'
@Input() groupBy: string
selectedId: number | string
// ng-select options
bindLabel = 'label'
propagateChange = (_: any) => { /* empty */ }
writeValue (id: number | string) {

View File

@ -0,0 +1,32 @@
@import '_variables';
@import '_mixins';
$form-base-input-width: auto;
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;
}
}

View File

@ -33,8 +33,6 @@ export class SelectTagsComponent implements ControlValueAccessor {
}
onModelChange () {
console.log(this.selectedItems)
this.propagateChange(this.selectedItems)
}
}

View File

@ -3,7 +3,6 @@ import { NgModule } from '@angular/core'
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
import { InputMaskModule } from 'primeng/inputmask'
import { InputSwitchModule } from 'primeng/inputswitch'
import { MultiSelectModule } from 'primeng/multiselect'
import { NgSelectModule } from '@ng-select/ng-select'
import { BatchDomainsValidatorsService } from '@app/shared/shared-forms/form-validators/batch-domains-validators.service'
import { SharedGlobalIconModule } from '../shared-icons'
@ -32,9 +31,7 @@ import { PreviewUploadComponent } from './preview-upload.component'
import { ReactiveFileComponent } from './reactive-file.component'
import { TextareaAutoResizeDirective } from './textarea-autoresize.directive'
import { TimestampInputComponent } from './timestamp-input.component'
import { SelectChannelComponent } from './select-channel.component'
import { SelectOptionsComponent } from './select-options.component'
import { SelectTagsComponent } from './select-tags.component'
import { SelectChannelComponent, SelectCheckboxComponent, SelectOptionsComponent, SelectTagsComponent } from './select'
@NgModule({
imports: [
@ -43,7 +40,6 @@ import { SelectTagsComponent } from './select-tags.component'
InputMaskModule,
InputSwitchModule,
MultiSelectModule,
NgSelectModule,
SharedMainModule,
@ -58,9 +54,11 @@ import { SelectTagsComponent } from './select-tags.component'
ReactiveFileComponent,
TextareaAutoResizeDirective,
TimestampInputComponent,
SelectChannelComponent,
SelectOptionsComponent,
SelectTagsComponent
SelectTagsComponent,
SelectCheckboxComponent
],
exports: [
@ -69,7 +67,6 @@ import { SelectTagsComponent } from './select-tags.component'
InputMaskModule,
InputSwitchModule,
MultiSelectModule,
NgSelectModule,
InputReadonlyCopyComponent,
@ -79,9 +76,11 @@ import { SelectTagsComponent } from './select-tags.component'
ReactiveFileComponent,
TextareaAutoResizeDirective,
TimestampInputComponent,
SelectChannelComponent,
SelectOptionsComponent,
SelectTagsComponent
SelectTagsComponent,
SelectCheckboxComponent
],
providers: [

View File

@ -30,11 +30,11 @@
</my-help>
<div>
<p-multiSelect
inputId="videoLanguages" [options]="languageItems" formControlName="videoLanguages" [showToggleAll]="true"
[defaultLabel]="getDefaultVideoLanguageLabel()" [selectedItemsLabel]="getSelectedVideoLanguageLabel()"
emptyFilterMessage="No results found" i18n-emptyFilterMessage
></p-multiSelect>
<my-select-checkbox
formControlName="videoLanguages" [availableItems]="languageItems"
[selectableGroup]="true" [selectableGroupAsModel]="true"
>
</my-select-checkbox >
</div>
</div>

View File

@ -19,6 +19,10 @@ input[type=submit] {
margin-bottom: 30px;
}
my-select-checkbox {
@include ng-select(340px);
}
.form-group-select {
margin-bottom: 30px;
}

View File

@ -1,10 +1,9 @@
import { pick } from 'lodash-es'
import { SelectItem } from 'primeng/api'
import { forkJoin, Subject, Subscription } from 'rxjs'
import { first } from 'rxjs/operators'
import { Component, Input, OnDestroy, OnInit } from '@angular/core'
import { AuthService, Notifier, ServerService, User, UserService } from '@app/core'
import { FormReactive, FormValidatorService } from '@app/shared/shared-forms'
import { FormReactive, FormValidatorService, ItemSelectCheckboxValue, SelectOptionsItem } from '@app/shared/shared-forms'
import { I18n } from '@ngx-translate/i18n-polyfill'
import { UserUpdateMe } from '@shared/models'
import { NSFWPolicyType } from '@shared/models/videos/nsfw-policy.type'
@ -20,10 +19,12 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
@Input() notifyOnUpdate = true
@Input() userInformationLoaded: Subject<any>
languageItems: SelectItem[] = []
languageItems: SelectOptionsItem[] = []
defaultNSFWPolicy: NSFWPolicyType
formValuesWatcher: Subscription
private allLanguagesGroup: string
constructor (
protected formValidatorService: FormValidatorService,
private authService: AuthService,
@ -36,6 +37,8 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
}
ngOnInit () {
this.allLanguagesGroup = this.i18n('All languages')
let oldForm: any
this.buildForm({
@ -51,13 +54,15 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
this.serverService.getConfig(),
this.userInformationLoaded.pipe(first())
]).subscribe(([ languages, config ]) => {
this.languageItems = [ { label: this.i18n('Unknown language'), value: '_unknown' } ]
this.languageItems = this.languageItems
.concat(languages.map(l => ({ label: l.label, value: l.id })))
const group = this.allLanguagesGroup
const videoLanguages = this.user.videoLanguages
? this.user.videoLanguages
: this.languageItems.map(l => l.value)
this.languageItems = [ { label: this.i18n('Unknown language'), id: '_unknown', group } ]
this.languageItems = this.languageItems
.concat(languages.map(l => ({ label: l.label, id: l.id, group })))
const videoLanguages: ItemSelectCheckboxValue[] = this.user.videoLanguages
? this.user.videoLanguages.map(l => ({ id: l }))
: [ { group } ]
this.defaultNSFWPolicy = config.instance.defaultNSFWPolicy
@ -71,10 +76,12 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
if (this.reactiveUpdate) {
oldForm = { ...this.form.value }
this.formValuesWatcher = this.form.valueChanges.subscribe((formValue: any) => {
const updatedKey = Object.keys(formValue).find(k => formValue[k] !== oldForm[k])
oldForm = { ...this.form.value }
this.updateDetails([updatedKey])
this.updateDetails([ updatedKey ])
})
}
})
@ -91,15 +98,23 @@ export class UserVideoSettingsComponent extends FormReactive implements OnInit,
const autoPlayNextVideo = this.form.value['autoPlayNextVideo']
let videoLanguages: string[] = this.form.value['videoLanguages']
if (Array.isArray(videoLanguages)) {
if (videoLanguages.length === this.languageItems.length) {
if (videoLanguages.length > 20) {
this.notifier.error(this.i18n('Too many languages are enabled. Please enable them all or stay below 20 enabled languages.'))
return
}
if (videoLanguages.length === 0) {
this.notifier.error(this.i18n('You need to enable at least 1 video language.'))
return
}
if (
videoLanguages.length === this.languageItems.length ||
(videoLanguages.length === 1 && videoLanguages[0] === this.allLanguagesGroup)
) {
videoLanguages = null // null means "All"
} else if (videoLanguages.length > 20) {
this.notifier.error('Too many languages are enabled. Please enable them all or stay below 20 enabled languages.')
return
} else if (videoLanguages.length === 0) {
this.notifier.error('You need to enabled at least 1 video language.')
return
}
}

View File

@ -304,6 +304,17 @@
z-index: 100;
}
@mixin ng-select ($width) {
::ng-deep ng-select {
width: $width;
@media screen and (max-width: $width) {
width: 100%;
}
}
}
@mixin peertube-select-container ($width) {
padding: 0;
margin: 0;

View File

@ -303,67 +303,6 @@ p-table {
}
}
// multiselect customizations
p-multiselect {
.ui-multiselect {
border-color: #C6C6C6;
&:not(.ui-state-disabled) {
&:hover {
border-color: #C6C6C6;
}
&:focus,
&.ui-state-focus {
box-shadow: #{$focus-box-shadow-form} pvar(--mainColorLightest);
}
}
}
.ui-multiselect-label {
font-size: 15px !important;
padding: 4px 30px 4px 12px !important;
$width: 338px;
width: $width !important;
@media screen and (max-width: $width) {
width: 100% !important;
}
}
.pi.pi-chevron-down {
margin-left: 0 !important;
&::after {
@include select-arrow-down;
right: 0;
margin-top: 6px;
}
}
.ui-chkbox-icon {
//position: absolute !important;
width: 18px;
height: 18px;
//left: 0;
//&::after {
// left: -2px !important;
//}
}
.ui-multiselect-panel .ui-multiselect-items .ui-multiselect-item.ui-state-highlight {
background-color: pvar(--mainColorLighter);
}
.ui-inputtext:enabled:focus:not(.ui-state-error) {
border-color: pvar(--mainColorLighter) !important;
box-shadow: none;
}
}
// PrimeNG calendar tweaks
p-calendar .ui-datepicker {
a {