diff options
author | Chocobozzz <me@florianbigard.com> | 2020-08-11 16:07:53 +0200 |
---|---|---|
committer | Chocobozzz <me@florianbigard.com> | 2020-08-11 16:18:42 +0200 |
commit | 52c4976fcf4ee255a3af68ff9778e4f5c4f84bd4 (patch) | |
tree | 887d2b6106548ad23cf016d82baf1977198027d9 /client/src/app/shared/shared-forms/select | |
parent | 3d25d5de33d8aa0ba00d7514522b25d22bf0e0a1 (diff) | |
download | PeerTube-52c4976fcf4ee255a3af68ff9778e4f5c4f84bd4.tar.gz PeerTube-52c4976fcf4ee255a3af68ff9778e4f5c4f84bd4.tar.zst PeerTube-52c4976fcf4ee255a3af68ff9778e4f5c4f84bd4.zip |
Use ng select for multiselect
Diffstat (limited to 'client/src/app/shared/shared-forms/select')
12 files changed, 366 insertions, 0 deletions
diff --git a/client/src/app/shared/shared-forms/select/index.ts b/client/src/app/shared/shared-forms/select/index.ts new file mode 100644 index 000000000..33459b23b --- /dev/null +++ b/client/src/app/shared/shared-forms/select/index.ts | |||
@@ -0,0 +1,4 @@ | |||
1 | export * from './select-channel.component' | ||
2 | export * from './select-options.component' | ||
3 | export * from './select-tags.component' | ||
4 | export * from './select-checkbox.component' | ||
diff --git a/client/src/app/shared/shared-forms/select/select-channel.component.html b/client/src/app/shared/shared-forms/select/select-channel.component.html new file mode 100644 index 000000000..897d13ee7 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-channel.component.html | |||
@@ -0,0 +1,16 @@ | |||
1 | <ng-select | ||
2 | [(ngModel)]="selectedId" | ||
3 | (ngModelChange)="onModelChange()" | ||
4 | [bindLabel]="bindLabel" | ||
5 | [bindValue]="bindValue" | ||
6 | [clearable]="clearable" | ||
7 | [searchable]="searchable" | ||
8 | > | ||
9 | <ng-option *ngFor="let channel of channels" [value]="channel.id"> | ||
10 | <img | ||
11 | class="avatar mr-1" | ||
12 | [src]="channel.avatarPath" | ||
13 | /> | ||
14 | {{ channel.label }} | ||
15 | </ng-option> | ||
16 | </ng-select> | ||
diff --git a/client/src/app/shared/shared-forms/select/select-channel.component.ts b/client/src/app/shared/shared-forms/select/select-channel.component.ts new file mode 100644 index 000000000..1b0db9b6f --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-channel.component.ts | |||
@@ -0,0 +1,58 @@ | |||
1 | import { Component, forwardRef, Input } from '@angular/core' | ||
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | ||
3 | import { Actor } from '@app/shared/shared-main/account/actor.model' | ||
4 | |||
5 | export type SelectChannelItem = { | ||
6 | id: number | ||
7 | label: string | ||
8 | support: string | ||
9 | avatarPath?: string | ||
10 | } | ||
11 | |||
12 | @Component({ | ||
13 | selector: 'my-select-channel', | ||
14 | styleUrls: [ './select-shared.component.scss' ], | ||
15 | templateUrl: './select-channel.component.html', | ||
16 | providers: [ | ||
17 | { | ||
18 | provide: NG_VALUE_ACCESSOR, | ||
19 | useExisting: forwardRef(() => SelectChannelComponent), | ||
20 | multi: true | ||
21 | } | ||
22 | ] | ||
23 | }) | ||
24 | export class SelectChannelComponent implements ControlValueAccessor { | ||
25 | @Input() items: SelectChannelItem[] = [] | ||
26 | |||
27 | selectedId: number | ||
28 | |||
29 | // ng-select options | ||
30 | bindLabel = 'label' | ||
31 | bindValue = 'id' | ||
32 | clearable = false | ||
33 | searchable = false | ||
34 | |||
35 | get channels () { | ||
36 | return this.items.map(c => Object.assign(c, { | ||
37 | avatarPath: c.avatarPath ? c.avatarPath : Actor.GET_DEFAULT_AVATAR_URL() | ||
38 | })) | ||
39 | } | ||
40 | |||
41 | propagateChange = (_: any) => { /* empty */ } | ||
42 | |||
43 | writeValue (id: number) { | ||
44 | this.selectedId = id | ||
45 | } | ||
46 | |||
47 | registerOnChange (fn: (_: any) => void) { | ||
48 | this.propagateChange = fn | ||
49 | } | ||
50 | |||
51 | registerOnTouched () { | ||
52 | // Unused | ||
53 | } | ||
54 | |||
55 | onModelChange () { | ||
56 | this.propagateChange(this.selectedId) | ||
57 | } | ||
58 | } | ||
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox.component.html b/client/src/app/shared/shared-forms/select/select-checkbox.component.html new file mode 100644 index 000000000..3f81dd152 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-checkbox.component.html | |||
@@ -0,0 +1,41 @@ | |||
1 | <ng-select | ||
2 | [items]="availableItems" | ||
3 | [(ngModel)]="selectedItems" | ||
4 | (ngModelChange)="onModelChange()" | ||
5 | i18n-placeholder placeholder="Add a new language" | ||
6 | [clearable]="true" | ||
7 | [multiple]="true" | ||
8 | [searchable]="true" | ||
9 | [closeOnSelect]="false" | ||
10 | |||
11 | bindValue="id" | ||
12 | bindLabel="label" | ||
13 | |||
14 | notFoundText="No items found" i18n-notFoundText | ||
15 | |||
16 | [selectableGroup]="selectableGroup" | ||
17 | [selectableGroupAsModel]="selectableGroupAsModel" | ||
18 | |||
19 | groupBy="group" | ||
20 | [compareWith]="compareFn" | ||
21 | |||
22 | [maxSelectedItems]="maxSelectedItems" | ||
23 | > | ||
24 | |||
25 | <ng-template ng-optgroup-tmp let-item="item" let-item$="item$" let-index="index"> | ||
26 | <div class="form-group-checkbox"> | ||
27 | <input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/> | ||
28 | <span role="checkbox" [attr.aria-checked]="item$.selected"></span> | ||
29 | <span>{{ item.group }}</span> | ||
30 | </div> | ||
31 | </ng-template> | ||
32 | |||
33 | <ng-template ng-option-tmp let-item="item" let-item$="item$" let-index="index"> | ||
34 | <div class="form-group-checkbox"> | ||
35 | <input id="item-{{index}}" type="checkbox" [ngModel]="item$.selected"/> | ||
36 | <span role="checkbox" [attr.aria-checked]="item$.selected"></span> | ||
37 | <span>{{ item.label }}</span> | ||
38 | </div> | ||
39 | </ng-template> | ||
40 | |||
41 | </ng-select> | ||
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox.component.scss b/client/src/app/shared/shared-forms/select/select-checkbox.component.scss new file mode 100644 index 000000000..145f6b26c --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-checkbox.component.scss | |||
@@ -0,0 +1,18 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | ng-select ::ng-deep { | ||
5 | .ng-option { | ||
6 | display: flex; | ||
7 | align-items: center; | ||
8 | } | ||
9 | |||
10 | .form-group-checkbox { | ||
11 | display: flex; | ||
12 | align-items: center; | ||
13 | |||
14 | input { | ||
15 | @include peertube-checkbox(1px); | ||
16 | } | ||
17 | } | ||
18 | } | ||
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox.component.ts b/client/src/app/shared/shared-forms/select/select-checkbox.component.ts new file mode 100644 index 000000000..93653fef1 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-checkbox.component.ts | |||
@@ -0,0 +1,75 @@ | |||
1 | import { Component, Input, forwardRef } from '@angular/core' | ||
2 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms' | ||
3 | import { SelectOptionsItem } from './select-options.component' | ||
4 | |||
5 | export type ItemSelectCheckboxValue = { id?: string | number, group?: string } | string | ||
6 | |||
7 | @Component({ | ||
8 | selector: 'my-select-checkbox', | ||
9 | styleUrls: [ './select-shared.component.scss', 'select-checkbox.component.scss' ], | ||
10 | templateUrl: './select-checkbox.component.html', | ||
11 | providers: [ | ||
12 | { | ||
13 | provide: NG_VALUE_ACCESSOR, | ||
14 | useExisting: forwardRef(() => SelectCheckboxComponent), | ||
15 | multi: true | ||
16 | } | ||
17 | ] | ||
18 | }) | ||
19 | export class SelectCheckboxComponent implements ControlValueAccessor { | ||
20 | @Input() availableItems: SelectOptionsItem[] = [] | ||
21 | @Input() selectedItems: ItemSelectCheckboxValue[] = [] | ||
22 | @Input() selectableGroup: boolean | ||
23 | @Input() selectableGroupAsModel: boolean | ||
24 | @Input() maxSelectedItems: number | ||
25 | |||
26 | propagateChange = (_: any) => { /* empty */ } | ||
27 | |||
28 | writeValue (items: ItemSelectCheckboxValue[]) { | ||
29 | if (Array.isArray(items)) { | ||
30 | this.selectedItems = items.map(i => { | ||
31 | if (typeof i === 'string' || typeof i === 'number') { | ||
32 | return i + '' | ||
33 | } | ||
34 | |||
35 | if (i.group) { | ||
36 | return { group: i.group } | ||
37 | } | ||
38 | |||
39 | return { id: i.id + '' } | ||
40 | }) | ||
41 | } else { | ||
42 | this.selectedItems = items | ||
43 | } | ||
44 | |||
45 | this.propagateChange(this.selectedItems) | ||
46 | } | ||
47 | |||
48 | registerOnChange (fn: (_: any) => void) { | ||
49 | this.propagateChange = fn | ||
50 | } | ||
51 | |||
52 | registerOnTouched () { | ||
53 | // Unused | ||
54 | } | ||
55 | |||
56 | onModelChange () { | ||
57 | this.propagateChange(this.selectedItems) | ||
58 | } | ||
59 | |||
60 | compareFn (item: SelectOptionsItem, selected: ItemSelectCheckboxValue) { | ||
61 | if (typeof selected === 'string') { | ||
62 | return item.id === selected | ||
63 | } | ||
64 | |||
65 | if (this.selectableGroup && item.group && selected.group) { | ||
66 | return item.group === selected.group | ||
67 | } | ||
68 | |||
69 | if (selected.id && item.id) { | ||
70 | return item.id === selected.id | ||
71 | } | ||
72 | |||
73 | return false | ||
74 | } | ||
75 | } | ||
diff --git a/client/src/app/shared/shared-forms/select/select-options.component.html b/client/src/app/shared/shared-forms/select/select-options.component.html new file mode 100644 index 000000000..48eca1cf5 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-options.component.html | |||
@@ -0,0 +1,19 @@ | |||
1 | <ng-select | ||
2 | [items]="items" | ||
3 | [groupBy]="groupBy" | ||
4 | [(ngModel)]="selectedId" | ||
5 | (ngModelChange)="onModelChange()" | ||
6 | [clearable]="clearable" | ||
7 | [searchable]="searchable" | ||
8 | |||
9 | bindLabel="label" | ||
10 | bindValue="id" | ||
11 | > | ||
12 | <ng-template ng-option-tmp let-item="item" let-index="index"> | ||
13 | {{ item.label }} | ||
14 | <ng-container *ngIf="item.description"> | ||
15 | <br> | ||
16 | <span [title]="item.description" class="text-muted">{{ item.description }}</span> | ||
17 | </ng-container> | ||
18 | </ng-template> | ||
19 | </ng-select> | ||
diff --git a/client/src/app/shared/shared-forms/select/select-options.component.ts b/client/src/app/shared/shared-forms/select/select-options.component.ts new file mode 100644 index 000000000..3ba24c732 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-options.component.ts | |||
@@ -0,0 +1,49 @@ | |||
1 | import { Component, Input, forwardRef } from '@angular/core' | ||
2 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms' | ||
3 | |||
4 | export type SelectOptionsItem = { | ||
5 | id: string | number | ||
6 | label: string | ||
7 | description?: string | ||
8 | group?: string | ||
9 | groupLabel?: string | ||
10 | } | ||
11 | |||
12 | @Component({ | ||
13 | selector: 'my-select-options', | ||
14 | styleUrls: [ './select-shared.component.scss' ], | ||
15 | templateUrl: './select-options.component.html', | ||
16 | providers: [ | ||
17 | { | ||
18 | provide: NG_VALUE_ACCESSOR, | ||
19 | useExisting: forwardRef(() => SelectOptionsComponent), | ||
20 | multi: true | ||
21 | } | ||
22 | ] | ||
23 | }) | ||
24 | export class SelectOptionsComponent implements ControlValueAccessor { | ||
25 | @Input() items: SelectOptionsItem[] = [] | ||
26 | @Input() clearable = false | ||
27 | @Input() searchable = false | ||
28 | @Input() groupBy: string | ||
29 | |||
30 | selectedId: number | string | ||
31 | |||
32 | propagateChange = (_: any) => { /* empty */ } | ||
33 | |||
34 | writeValue (id: number | string) { | ||
35 | this.selectedId = id | ||
36 | } | ||
37 | |||
38 | registerOnChange (fn: (_: any) => void) { | ||
39 | this.propagateChange = fn | ||
40 | } | ||
41 | |||
42 | registerOnTouched () { | ||
43 | // Unused | ||
44 | } | ||
45 | |||
46 | onModelChange () { | ||
47 | this.propagateChange(this.selectedId) | ||
48 | } | ||
49 | } | ||
diff --git a/client/src/app/shared/shared-forms/select/select-shared.component.scss b/client/src/app/shared/shared-forms/select/select-shared.component.scss new file mode 100644 index 000000000..0b4c6b784 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-shared.component.scss | |||
@@ -0,0 +1,32 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | $form-base-input-width: auto; | ||
5 | |||
6 | ng-select { | ||
7 | width: $form-base-input-width; | ||
8 | |||
9 | @media screen and (max-width: $form-base-input-width) { | ||
10 | width: 100%; | ||
11 | } | ||
12 | } | ||
13 | |||
14 | ng-select ::ng-deep { | ||
15 | .ng-value-container { | ||
16 | max-height: 100px; | ||
17 | overflow-y: auto; | ||
18 | overflow-x: hidden; | ||
19 | } | ||
20 | |||
21 | // make sure the image is vertically adjusted | ||
22 | .ng-value-label img { | ||
23 | position: relative; | ||
24 | top: -1px; | ||
25 | } | ||
26 | |||
27 | img { | ||
28 | border-radius: 50%; | ||
29 | height: 20px; | ||
30 | width: 20px; | ||
31 | } | ||
32 | } | ||
diff --git a/client/src/app/shared/shared-forms/select/select-tags.component.html b/client/src/app/shared/shared-forms/select/select-tags.component.html new file mode 100644 index 000000000..e1cd50882 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-tags.component.html | |||
@@ -0,0 +1,13 @@ | |||
1 | <ng-select | ||
2 | [items]="availableItems" | ||
3 | [(ngModel)]="selectedItems" | ||
4 | (ngModelChange)="onModelChange()" | ||
5 | i18n-placeholder placeholder="Enter a new tag" | ||
6 | [maxSelectedItems]="5" | ||
7 | [clearable]="true" | ||
8 | [addTag]="true" | ||
9 | [multiple]="true" | ||
10 | [isOpen]="false" | ||
11 | [searchable]="true" | ||
12 | > | ||
13 | </ng-select> | ||
diff --git a/client/src/app/shared/shared-forms/select/select-tags.component.scss b/client/src/app/shared/shared-forms/select/select-tags.component.scss new file mode 100644 index 000000000..ad76bc7ee --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-tags.component.scss | |||
@@ -0,0 +1,3 @@ | |||
1 | ng-select ::ng-deep .ng-arrow-wrapper { | ||
2 | display: none; | ||
3 | } | ||
diff --git a/client/src/app/shared/shared-forms/select/select-tags.component.ts b/client/src/app/shared/shared-forms/select/select-tags.component.ts new file mode 100644 index 000000000..93d199037 --- /dev/null +++ b/client/src/app/shared/shared-forms/select/select-tags.component.ts | |||
@@ -0,0 +1,38 @@ | |||
1 | import { Component, Input, forwardRef } from '@angular/core' | ||
2 | import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms' | ||
3 | |||
4 | @Component({ | ||
5 | selector: 'my-select-tags', | ||
6 | styleUrls: [ './select-shared.component.scss', './select-tags.component.scss' ], | ||
7 | templateUrl: './select-tags.component.html', | ||
8 | providers: [ | ||
9 | { | ||
10 | provide: NG_VALUE_ACCESSOR, | ||
11 | useExisting: forwardRef(() => SelectTagsComponent), | ||
12 | multi: true | ||
13 | } | ||
14 | ] | ||
15 | }) | ||
16 | export class SelectTagsComponent implements ControlValueAccessor { | ||
17 | @Input() availableItems: string[] = [] | ||
18 | @Input() selectedItems: string[] = [] | ||
19 | |||
20 | propagateChange = (_: any) => { /* empty */ } | ||
21 | |||
22 | writeValue (items: string[]) { | ||
23 | this.selectedItems = items | ||
24 | this.propagateChange(this.selectedItems) | ||
25 | } | ||
26 | |||
27 | registerOnChange (fn: (_: any) => void) { | ||
28 | this.propagateChange = fn | ||
29 | } | ||
30 | |||
31 | registerOnTouched () { | ||
32 | // Unused | ||
33 | } | ||
34 | |||
35 | onModelChange () { | ||
36 | this.propagateChange(this.selectedItems) | ||
37 | } | ||
38 | } | ||