Use ng select for multiselect
This commit is contained in:
parent
3d25d5de33
commit
52c4976fcf
|
@ -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>
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -61,6 +61,7 @@ my-global-icon {
|
|||
|
||||
.input-group {
|
||||
@include peertube-input-group(300px);
|
||||
|
||||
input {
|
||||
flex: 1;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './select-channel.component'
|
||||
export * from './select-options.component'
|
||||
export * from './select-tags.component'
|
||||
export * from './select-checkbox.component'
|
|
@ -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
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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 }}
|
|
@ -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) {
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -33,8 +33,6 @@ export class SelectTagsComponent implements ControlValueAccessor {
|
|||
}
|
||||
|
||||
onModelChange () {
|
||||
console.log(this.selectedItems)
|
||||
|
||||
this.propagateChange(this.selectedItems)
|
||||
}
|
||||
}
|
|
@ -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: [
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@ input[type=submit] {
|
|||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
my-select-checkbox {
|
||||
@include ng-select(340px);
|
||||
}
|
||||
|
||||
.form-group-select {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue