Browse Source

Dropdown improvements. (#396)

* Dropdown improvements.

* Fix disable state.

* Dropdown fix for up and down arrow key.
pull/397/head
Sebastian Stehle 7 years ago
committed by GitHub
parent
commit
29752323a6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      src/Squidex/app/features/content/shared/field-editor.component.html
  2. 72
      src/Squidex/app/features/content/shared/references-dropdown.component.ts
  3. 2
      src/Squidex/app/features/settings/pages/workflows/workflow-step.component.html
  4. 4
      src/Squidex/app/framework/angular/forms/autocomplete.component.html
  5. 18
      src/Squidex/app/framework/angular/forms/dropdown.component.html
  6. 15
      src/Squidex/app/framework/angular/forms/dropdown.component.scss
  7. 103
      src/Squidex/app/framework/angular/forms/dropdown.component.ts
  8. 4
      src/Squidex/app/framework/angular/forms/tag-editor.component.html
  9. 24
      src/Squidex/app/framework/angular/highlight.pipe.ts
  10. 2
      src/Squidex/app/framework/angular/scroll-active.directive.ts
  11. 22
      src/Squidex/app/framework/angular/template-wrapper.directive.ts
  12. 1
      src/Squidex/app/framework/declarations.ts
  13. 3
      src/Squidex/app/framework/module.ts

3
src/Squidex/app/features/content/shared/field-editor.component.html

@ -92,7 +92,8 @@
<sqx-references-dropdown <sqx-references-dropdown
[formControl]="control" [formControl]="control"
[language]="language" [language]="language"
[schemaId]="field.properties['schemaId']"> [schemaId]="field.properties['schemaId']"
[isRequired]="field.properties['isRequired']">
</sqx-references-dropdown> </sqx-references-dropdown>
</ng-container> </ng-container>
</ng-container> </ng-container>

72
src/Squidex/app/features/content/shared/references-dropdown.component.ts

@ -16,7 +16,6 @@ import {
ContentDto, ContentDto,
ContentsService, ContentsService,
getContentValue, getContentValue,
ImmutableArray,
MathHelper, MathHelper,
SchemaDetailsDto, SchemaDetailsDto,
SchemasService, SchemasService,
@ -31,28 +30,35 @@ export const SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
interface State { interface State {
schema?: SchemaDetailsDto | null; schema?: SchemaDetailsDto | null;
contentItems: ImmutableArray<ContentDto>; contentItems: ContentDto[];
contentNames: ImmutableArray<ContentName>; contentNames: ContentName[];
selectedItem?: ContentName;
} }
type ContentName = { name: string, id: string }; type ContentName = { name: string, id?: string };
@Component({ @Component({
selector: 'sqx-references-dropdown', selector: 'sqx-references-dropdown',
template: ` template: `
<select class="form-control" [formControl]="selectedId"> <sqx-dropdown [formControl]="selectionControl" [items]="snapshot.contentNames">
<option [ngValue]="null"></option> <ng-template let-content="$implicit" let-context="context">
<option *ngFor="let content of snapshot.contentNames" [ngValue]="content.id">{{content.name}}</option> <span class="truncate" [innerHTML]="content.name | sqxHighlight:context"></span>
</select>`, </ng-template>
</sqx-dropdown>`,
providers: [SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR], providers: [SQX_REFERENCES_DROPDOWN_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class ReferencesDropdownComponent extends StatefulControlComponent<State, string[]> implements OnInit { export class ReferencesDropdownComponent extends StatefulControlComponent<State, string[]> implements OnInit {
private languageField: AppLanguageDto; private languageField: AppLanguageDto;
private selectedId: string | undefined;
@Input() @Input()
public schemaId: string; public schemaId: string;
@Input()
public isRequired = false;
@Input() @Input()
public set language(value: AppLanguageDto) { public set language(value: AppLanguageDto) {
this.languageField = value; this.languageField = value;
@ -60,7 +66,7 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
this.next(s => ({ ...s, contentNames: this.createContentNames(s.schema, s.contentItems) })); this.next(s => ({ ...s, contentNames: this.createContentNames(s.schema, s.contentItems) }));
} }
public selectedId = new FormControl(''); public selectionControl = new FormControl('');
constructor(changeDetector: ChangeDetectorRef, constructor(changeDetector: ChangeDetectorRef,
private readonly appsState: AppsState, private readonly appsState: AppsState,
@ -69,16 +75,16 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
) { ) {
super(changeDetector, { super(changeDetector, {
schema: null, schema: null,
contentItems: ImmutableArray.empty(), contentItems: [],
contentNames: ImmutableArray.empty() contentNames: []
}); });
this.own( this.own(
this.selectedId.valueChanges this.selectionControl.valueChanges
.subscribe(value => { .subscribe((value: ContentName) => {
if (value) { if (value && value.id) {
this.callTouched(); this.callTouched();
this.callChange([value]); this.callChange([value.id]);
} else { } else {
this.callTouched(); this.callTouched();
this.callChange([]); this.callChange([]);
@ -88,7 +94,7 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
public ngOnInit() { public ngOnInit() {
if (this.schemaId === MathHelper.EMPTY_GUID) { if (this.schemaId === MathHelper.EMPTY_GUID) {
this.selectedId.disable(); this.selectionControl.disable();
return; return;
} }
@ -101,29 +107,43 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
} }
}, (schema, contents) => ({ schema, contents }))) }, (schema, contents) => ({ schema, contents })))
.subscribe(({ schema, contents }) => { .subscribe(({ schema, contents }) => {
const contentItems = ImmutableArray.of(contents.items); const contentItems = contents.items;
const contentNames = this.createContentNames(schema, contentItems); const contentNames = this.createContentNames(schema, contentItems);
this.next(s => ({ ...s, schema, contentItems, contentNames })); this.next(s => ({ ...s, schema, contentItems, contentNames }));
this.selectContent();
}, () => { }, () => {
this.selectedId.disable(); this.selectionControl.disable();
}); });
} }
public writeValue(obj: any) { public writeValue(obj: any) {
if (Types.isArrayOfString(obj)) { if (Types.isArrayOfString(obj)) {
this.selectedId.setValue(obj[0], { emitEvent: false }); this.selectedId = obj[0];
this.selectContent();
} else { } else {
this.selectedId.setValue(undefined, { emitEvent: false }); this.selectedId = undefined;
this.unselectContent();
} }
} }
private createContentNames(schema: SchemaDetailsDto | undefined | null, contents: ImmutableArray<ContentDto>): ImmutableArray<ContentName> { private selectContent() {
this.selectionControl.setValue(this.snapshot.contentNames.find(x => x.id === this.selectedId), { emitEvent: false });
}
private unselectContent() {
this.selectionControl.setValue(undefined, { emitEvent: false });
}
private createContentNames(schema: SchemaDetailsDto | undefined | null, contents: ContentDto[]): ContentName[] {
if (contents.length === 0 || !schema) { if (contents.length === 0 || !schema) {
return ImmutableArray.empty(); return [];
} }
return contents.map(content => { const names = contents.map(content => {
const name = const name =
schema.referenceFields schema.referenceFields
.map(f => getContentValue(content, this.languageField, f, false)) .map(f => getContentValue(content, this.languageField, f, false))
@ -132,6 +152,12 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
return { name, id: content.id }; return { name, id: content.id };
}); });
if (!this.isRequired) {
return [{ name: '- No Reference -' }, ...names];
}
return names;
} }
public trackByContent(content: ContentDto) { public trackByContent(content: ContentDto) {

2
src/Squidex/app/features/settings/pages/workflows/workflow-step.component.html

@ -49,7 +49,7 @@
<i class="icon-corner-down-right text-decent"></i> <i class="icon-corner-down-right text-decent"></i>
</div> </div>
<div class="col col-step"> <div class="col col-step">
<sqx-dropdown [items]="openSteps" [(ngModel)]="openStep"> <sqx-dropdown [items]="openSteps" [(ngModel)]="openStep" [canSearch]="false">
<ng-template let-target="$implicit"> <ng-template let-target="$implicit">
<div class="color-circle" [style.background]="target.color"></div> {{target.name}} <div class="color-circle" [style.background]="target.color"></div> {{target.name}}
</ng-template> </ng-template>

4
src/Squidex/app/framework/angular/forms/autocomplete.component.html

@ -9,10 +9,10 @@
<div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left" #container @fade> <div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left" #container @fade>
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable" <div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.suggestedIndex" [class.active]="i === snapshot.suggestedIndex"
[container]="container"
(mousedown)="selectItem(item)" (mousedown)="selectItem(item)"
(mouseover)="selectIndex(i)" (mouseover)="selectIndex(i)"
[sqxScrollActive]="i === snapshot.suggestedIndex"> [sqxScrollActive]="i === snapshot.suggestedIndex"
[sqxScrollContainer]="container">
<ng-container *ngIf="!itemTemplate">{{item}}</ng-container> <ng-container *ngIf="!itemTemplate">{{item}}</ng-container>

18
src/Squidex/app/framework/angular/forms/dropdown.component.html

@ -16,11 +16,19 @@
<div class="items-container"> <div class="items-container">
<ng-container *sqxModal="dropdown"> <ng-container *sqxModal="dropdown">
<div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left" #container> <div class="control-dropdown" [sqxAnchoredTo]="input" position="bottom-left">
<div *ngFor="let item of items; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === snapshot.selectedIndex" (mousedown)="selectIndexAndClose(i)" [sqxScrollActive]="i === snapshot.selectedIndex" [container]="container"> <div *ngIf="canSearch" class="search-form">
<ng-container *ngIf="!templateItem">{{item}}</ng-container> <input class="form-control search" [formControl]="queryInput" [disabled]="snapshot.isDisabled" placeholder="Search" (keydown)="onKeyDown($event)" sqxFocusOnInit />
</div>
<ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i"></ng-template>
<div class="control-dropdown-items" #container>
<div *ngFor="let item of snapshot.suggestedItems; let i = index;" class="control-dropdown-item control-dropdown-item-selectable" [class.active]="i === snapshot.selectedIndex" (mousedown)="selectIndexAndClose(i)"
[sqxScrollActive]="i === snapshot.selectedIndex"
[sqxScrollContainer]="container">
<ng-container *ngIf="!templateItem">{{item}}</ng-container>
<ng-template *ngIf="templateItem" [sqxTemplateWrapper]="templateItem" [item]="item" [index]="i" [context]="snapshot.query"></ng-template>
</div>
</div> </div>
</div> </div>
</ng-container> </ng-container>

15
src/Squidex/app/framework/angular/forms/dropdown.component.scss

@ -17,6 +17,21 @@ $color-input-disabled: #eef1f4;
} }
} }
.search-form {
padding: .5rem;
}
.control-dropdown {
max-width: 40rem;
max-height: none;
overflow-y: hidden;
}
.control-dropdown-items {
overflow-y: auto;
max-height: 15rem;
}
.selection { .selection {
& { & {
position: relative; position: relative;

103
src/Squidex/app/framework/angular/forms/dropdown.component.ts

@ -5,18 +5,26 @@
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/ */
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, QueryList, TemplateRef } from '@angular/core'; import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChildren, forwardRef, Input, OnChanges, OnInit, QueryList, SimpleChanges, TemplateRef } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR } from '@angular/forms';
import { map } from 'rxjs/operators';
import { Keys, ModalModel, StatefulControlComponent } from '@app/framework/internal'; import {
Keys,
ModalModel,
StatefulControlComponent,
Types
} from '@app/framework/internal';
export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = { export const SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => DropdownComponent), multi: true
}; };
interface State { interface State {
suggestedItems: any[];
selectedItem: any; selectedItem: any;
selectedIndex: number; selectedIndex: number;
query?: string;
} }
@Component({ @Component({
@ -26,10 +34,16 @@ interface State {
providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR], providers: [SQX_DROPDOWN_CONTROL_VALUE_ACCESSOR],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class DropdownComponent extends StatefulControlComponent<State, any[]> implements AfterContentInit, ControlValueAccessor { export class DropdownComponent extends StatefulControlComponent<State, any[]> implements AfterContentInit, ControlValueAccessor, OnChanges, OnInit {
@Input() @Input()
public items: any[] = []; public items: any[] = [];
@Input()
public searchProperty = 'name';
@Input()
public canSearch = true;
@ContentChildren(TemplateRef) @ContentChildren(TemplateRef)
public templates: QueryList<any>; public templates: QueryList<any>;
@ -38,13 +52,60 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
public templateSelection: TemplateRef<any>; public templateSelection: TemplateRef<any>;
public templateItem: TemplateRef<any>; public templateItem: TemplateRef<any>;
public queryInput = new FormControl();
constructor(changeDetector: ChangeDetectorRef) { constructor(changeDetector: ChangeDetectorRef) {
super(changeDetector, { super(changeDetector, {
selectedItem: undefined, selectedItem: undefined,
selectedIndex: -1 selectedIndex: -1,
suggestedItems: []
}); });
} }
public ngOnInit() {
this.own(
this.queryInput.valueChanges.pipe(
map((query: string) => {
if (!this.items || !query) {
return { query, items: this.items };
} else {
query = query.trim().toLocaleLowerCase();
const items = this.items.filter(x => {
if (Types.isString(x)) {
return x.toLocaleLowerCase().indexOf(query) >= 0;
} else {
const value: string = x[this.searchProperty];
return value && value.toLocaleLowerCase().indexOf(query) >= 0;
}
});
return { query, items };
}
}))
.subscribe(({ query, items }) => {
this.next(s => ({
...s,
suggestedIndex: 0,
suggestedItems: items || [],
query
}));
}));
}
public ngOnChanges(changes: SimpleChanges) {
if (changes['items']) {
this.resetSearch();
this.next(s => ({
...s,
suggestedIndex: 0,
suggestedItems: this.items || []
}));
}
}
public ngAfterContentInit() { public ngAfterContentInit() {
if (this.templates.length === 1) { if (this.templates.length === 1) {
this.templateItem = this.templateSelection = this.templates.first; this.templateItem = this.templateSelection = this.templates.first;
@ -64,7 +125,7 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
} }
public writeValue(obj: any) { public writeValue(obj: any) {
this.selectIndex(this.items && obj ? this.items.indexOf(obj) : 0); this.selectIndex(this.items && obj ? this.items.indexOf(obj) : 0, false);
} }
public onKeyDown(event: KeyboardEvent) { public onKeyDown(event: KeyboardEvent) {
@ -75,8 +136,10 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
case Keys.DOWN: case Keys.DOWN:
this.down(); this.down();
return false; return false;
case Keys.ESCAPE:
case Keys.ENTER: case Keys.ENTER:
this.selectIndexAndClose(this.snapshot.selectedIndex);
return false;
case Keys.ESCAPE:
if (this.dropdown.isOpen) { if (this.dropdown.isOpen) {
this.close(); this.close();
return false; return false;
@ -87,13 +150,17 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
} }
public open() { public open() {
if (!this.dropdown.isOpen) {
this.resetSearch();
}
this.dropdown.show(); this.dropdown.show();
this.callTouched(); this.callTouched();
} }
public selectIndexAndClose(selectedIndex: number) { public selectIndexAndClose(selectedIndex: number) {
this.selectIndex(selectedIndex); this.selectIndex(selectedIndex, true);
this.close(); this.close();
} }
@ -102,12 +169,16 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
this.dropdown.hide(); this.dropdown.hide();
} }
public selectIndex(selectedIndex: number) { private resetSearch() {
this.queryInput.setValue('');
}
public selectIndex(selectedIndex: number, emitEvents: boolean) {
if (selectedIndex < 0) { if (selectedIndex < 0) {
selectedIndex = 0; selectedIndex = 0;
} }
const items = this.items || []; const items = this.snapshot.suggestedItems || [];
if (selectedIndex >= items.length) { if (selectedIndex >= items.length) {
selectedIndex = items.length - 1; selectedIndex = items.length - 1;
@ -116,10 +187,10 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
const value = items[selectedIndex]; const value = items[selectedIndex];
if (value !== this.snapshot.selectedItem) { if (value !== this.snapshot.selectedItem) {
selectedIndex = selectedIndex; if (emitEvents) {
this.callChange(value);
this.callChange(value); this.callTouched();
this.callTouched(); }
this.next(s => ({ ...s, selectedIndex, selectedItem: value })); this.next(s => ({ ...s, selectedIndex, selectedItem: value }));
} }
@ -127,10 +198,10 @@ export class DropdownComponent extends StatefulControlComponent<State, any[]> im
} }
private up() { private up() {
this.selectIndex(this.snapshot.selectedIndex - 1); this.selectIndex(this.snapshot.selectedIndex - 1, true);
} }
private down() { private down() {
this.selectIndex(this.snapshot.selectedIndex + 1); this.selectIndex(this.snapshot.selectedIndex + 1, true);
} }
} }

4
src/Squidex/app/framework/angular/forms/tag-editor.component.html

@ -26,10 +26,10 @@
<div class="control-dropdown" [sqxAnchoredTo]="form" position="bottom-left" #container @fade> <div class="control-dropdown" [sqxAnchoredTo]="form" position="bottom-left" #container @fade>
<div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable" <div *ngFor="let item of snapshot.suggestedItems; let i = index" class="control-dropdown-item control-dropdown-item-selectable"
[class.active]="i === snapshot.suggestedIndex" [class.active]="i === snapshot.suggestedIndex"
[container]="container"
(mousedown)="selectValue(item)" (mousedown)="selectValue(item)"
(mouseover)="selectIndex(i)" (mouseover)="selectIndex(i)"
[sqxScrollActive]="i === snapshot.suggestedIndex"> [sqxScrollActive]="i === snapshot.suggestedIndex"
[sqxScrollContainer]="container">
<ng-container>{{item}}</ng-container> <ng-container>{{item}}</ng-container>
</div> </div>
</div> </div>

24
src/Squidex/app/framework/angular/highlight.pipe.ts

@ -0,0 +1,24 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved.
*/
// tslint:disable: no-pipe-impure
import { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'sqxHighlight',
pure: false
})
export class HighlightPipe implements PipeTransform {
public transform(text: string, highlight: string): string {
if (!highlight) {
return text;
}
return text.replace(new RegExp(highlight, 'i'), s => `<b>${s}</b>`);
}
}

2
src/Squidex/app/framework/angular/scroll-active.directive.ts

@ -14,7 +14,7 @@ export class ScrollActiveDirective implements AfterViewInit, OnChanges {
@Input('sqxScrollActive') @Input('sqxScrollActive')
public isActive = false; public isActive = false;
@Input() @Input('sqxScrollContainer')
public container: HTMLElement; public container: HTMLElement;
constructor( constructor(

22
src/Squidex/app/framework/angular/template-wrapper.directive.ts

@ -17,6 +17,9 @@ export class TemplateWrapperDirective implements OnDestroy, OnInit, OnChanges {
@Input() @Input()
public index: number; public index: number;
@Input()
public context: any;
@Input('sqxTemplateWrapper') @Input('sqxTemplateWrapper')
public templateRef: TemplateRef<any>; public templateRef: TemplateRef<any>;
@ -34,19 +37,30 @@ export class TemplateWrapperDirective implements OnDestroy, OnInit, OnChanges {
} }
public ngOnInit() { public ngOnInit() {
this.view = this.viewContainer.createEmbeddedView(this.templateRef, { const { index, context } = this;
const data = {
'\$implicit': this.item, '\$implicit': this.item,
'index': this.index index,
}); context
};
this.view = this.viewContainer.createEmbeddedView(this.templateRef, data);
} }
public ngOnChanges(changes: SimpleChanges) { public ngOnChanges(changes: SimpleChanges) {
if (this.view) { if (this.view) {
if (changes.item) { if (changes.item) {
this.view.context.$implicit = this.item; this.view.context.$implicit = this.item;
} else if (changes.index) { }
if (changes.index) {
this.view.context.index = this.index; this.view.context.index = this.index;
} }
if (changes.context) {
this.view.context.context = this.context;
}
} }
} }
} }

1
src/Squidex/app/framework/declarations.ts

@ -56,6 +56,7 @@ export * from './angular/routers/parent-link.directive';
export * from './angular/code.component'; export * from './angular/code.component';
export * from './angular/external-link.directive'; export * from './angular/external-link.directive';
export * from './angular/hover-background.directive'; export * from './angular/hover-background.directive';
export * from './angular/highlight.pipe';
export * from './angular/ignore-scrollbar.directive'; export * from './angular/ignore-scrollbar.directive';
export * from './angular/image-source.directive'; export * from './angular/image-source.directive';
export * from './angular/panel.component'; export * from './angular/panel.component';

3
src/Squidex/app/framework/module.ts

@ -44,6 +44,7 @@ import {
FormHintComponent, FormHintComponent,
FromNowPipe, FromNowPipe,
FullDateTimePipe, FullDateTimePipe,
HighlightPipe,
HoverBackgroundDirective, HoverBackgroundDirective,
IFrameEditorComponent, IFrameEditorComponent,
IgnoreScrollbarDirective, IgnoreScrollbarDirective,
@ -126,6 +127,7 @@ import {
FormHintComponent, FormHintComponent,
FromNowPipe, FromNowPipe,
FullDateTimePipe, FullDateTimePipe,
HighlightPipe,
HoverBackgroundDirective, HoverBackgroundDirective,
IFrameEditorComponent, IFrameEditorComponent,
IgnoreScrollbarDirective, IgnoreScrollbarDirective,
@ -194,6 +196,7 @@ import {
FormsModule, FormsModule,
FromNowPipe, FromNowPipe,
FullDateTimePipe, FullDateTimePipe,
HighlightPipe,
HoverBackgroundDirective, HoverBackgroundDirective,
IFrameEditorComponent, IFrameEditorComponent,
IgnoreScrollbarDirective, IgnoreScrollbarDirective,

Loading…
Cancel
Save