mirror of https://github.com/ghostfolio/ghostfolio
Thomas Kaul
3 years ago
committed by
GitHub
9 changed files with 228 additions and 150 deletions
@ -0,0 +1,33 @@ |
|||||
|
<mat-form-field appearance="outline" class="w-100"> |
||||
|
<ion-icon class="mr-1" matPrefix name="search-outline"></ion-icon> |
||||
|
<mat-chip-list #chipList aria-label="Search keywords"> |
||||
|
<mat-chip |
||||
|
*ngFor="let searchKeyword of searchKeywords" |
||||
|
class="mx-1 my-0 px-2 py-0" |
||||
|
matChipRemove |
||||
|
[removable]="true" |
||||
|
(removed)="removeKeyword(searchKeyword)" |
||||
|
> |
||||
|
{{ searchKeyword | gfSymbol }} |
||||
|
<ion-icon class="ml-2" matPrefix name="close-outline"></ion-icon> |
||||
|
</mat-chip> |
||||
|
<input |
||||
|
#searchInput |
||||
|
name="close-outline" |
||||
|
[formControl]="searchControl" |
||||
|
[matAutocomplete]="autocomplete" |
||||
|
[matChipInputFor]="chipList" |
||||
|
[matChipInputSeparatorKeyCodes]="separatorKeysCodes" |
||||
|
[placeholder]="placeholder" |
||||
|
(matChipInputTokenEnd)="addKeyword($event)" |
||||
|
/> |
||||
|
</mat-chip-list> |
||||
|
<mat-autocomplete |
||||
|
#autocomplete="matAutocomplete" |
||||
|
(optionSelected)="keywordSelected($event)" |
||||
|
> |
||||
|
<mat-option *ngFor="let filter of filters | async" [value]="filter"> |
||||
|
{{ filter | gfSymbol }} |
||||
|
</mat-option> |
||||
|
</mat-autocomplete> |
||||
|
</mat-form-field> |
@ -0,0 +1,22 @@ |
|||||
|
@import '~apps/client/src/styles/ghostfolio-style'; |
||||
|
|
||||
|
:host { |
||||
|
display: block; |
||||
|
|
||||
|
::ng-deep { |
||||
|
.mat-form-field-infix { |
||||
|
border-top: 0 solid transparent !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.mat-chip { |
||||
|
cursor: pointer; |
||||
|
min-height: 1.5rem !important; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
:host-context(.is-dark-theme) { |
||||
|
.mat-form-field { |
||||
|
color: rgba(var(--light-primary-text)); |
||||
|
} |
||||
|
} |
@ -0,0 +1,108 @@ |
|||||
|
import { COMMA, ENTER } from '@angular/cdk/keycodes'; |
||||
|
import { |
||||
|
ChangeDetectionStrategy, |
||||
|
Component, |
||||
|
ElementRef, |
||||
|
EventEmitter, |
||||
|
Input, |
||||
|
OnChanges, |
||||
|
OnDestroy, |
||||
|
Output, |
||||
|
ViewChild |
||||
|
} from '@angular/core'; |
||||
|
import { FormControl } from '@angular/forms'; |
||||
|
import { |
||||
|
MatAutocomplete, |
||||
|
MatAutocompleteSelectedEvent |
||||
|
} from '@angular/material/autocomplete'; |
||||
|
import { MatChipInputEvent } from '@angular/material/chips'; |
||||
|
import { BehaviorSubject, Observable, Subject } from 'rxjs'; |
||||
|
import { takeUntil } from 'rxjs/operators'; |
||||
|
|
||||
|
@Component({ |
||||
|
changeDetection: ChangeDetectionStrategy.OnPush, |
||||
|
selector: 'gf-activities-filter', |
||||
|
styleUrls: ['./activities-filter.component.scss'], |
||||
|
templateUrl: './activities-filter.component.html' |
||||
|
}) |
||||
|
export class ActivitiesFilterComponent implements OnChanges, OnDestroy { |
||||
|
@Input() allFilters: string[]; |
||||
|
@Input() placeholder: string; |
||||
|
|
||||
|
@Output() valueChanged = new EventEmitter<string[]>(); |
||||
|
|
||||
|
@ViewChild('autocomplete') matAutocomplete: MatAutocomplete; |
||||
|
@ViewChild('searchInput') searchInput: ElementRef<HTMLInputElement>; |
||||
|
|
||||
|
public filters$: Subject<string[]> = new BehaviorSubject([]); |
||||
|
public filters: Observable<string[]> = this.filters$.asObservable(); |
||||
|
public searchControl = new FormControl(); |
||||
|
public searchKeywords: string[] = []; |
||||
|
public separatorKeysCodes: number[] = [ENTER, COMMA]; |
||||
|
|
||||
|
private unsubscribeSubject = new Subject<void>(); |
||||
|
|
||||
|
public constructor() { |
||||
|
this.searchControl.valueChanges |
||||
|
.pipe(takeUntil(this.unsubscribeSubject)) |
||||
|
.subscribe((keyword) => { |
||||
|
if (keyword) { |
||||
|
const filterValue = keyword.toLowerCase(); |
||||
|
this.filters$.next( |
||||
|
this.allFilters.filter( |
||||
|
(filter) => filter.toLowerCase().indexOf(filterValue) === 0 |
||||
|
) |
||||
|
); |
||||
|
} else { |
||||
|
this.filters$.next(this.allFilters); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public ngOnChanges() { |
||||
|
if (this.allFilters) { |
||||
|
this.updateFilter(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public addKeyword({ input, value }: MatChipInputEvent): void { |
||||
|
if (value?.trim()) { |
||||
|
this.searchKeywords.push(value.trim()); |
||||
|
this.updateFilter(); |
||||
|
} |
||||
|
|
||||
|
// Reset the input value
|
||||
|
if (input) { |
||||
|
input.value = ''; |
||||
|
} |
||||
|
|
||||
|
this.searchControl.setValue(null); |
||||
|
} |
||||
|
|
||||
|
public keywordSelected(event: MatAutocompleteSelectedEvent): void { |
||||
|
this.searchKeywords.push(event.option.viewValue); |
||||
|
this.updateFilter(); |
||||
|
this.searchInput.nativeElement.value = ''; |
||||
|
this.searchControl.setValue(null); |
||||
|
} |
||||
|
|
||||
|
public removeKeyword(keyword: string): void { |
||||
|
const index = this.searchKeywords.indexOf(keyword); |
||||
|
|
||||
|
if (index >= 0) { |
||||
|
this.searchKeywords.splice(index, 1); |
||||
|
this.updateFilter(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public ngOnDestroy() { |
||||
|
this.unsubscribeSubject.next(); |
||||
|
this.unsubscribeSubject.complete(); |
||||
|
} |
||||
|
|
||||
|
private updateFilter() { |
||||
|
this.filters$.next(this.allFilters); |
||||
|
|
||||
|
this.valueChanged.emit(this.searchKeywords); |
||||
|
} |
||||
|
} |
@ -0,0 +1,24 @@ |
|||||
|
import { CommonModule } from '@angular/common'; |
||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; |
||||
|
import { ReactiveFormsModule } from '@angular/forms'; |
||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete'; |
||||
|
import { MatChipsModule } from '@angular/material/chips'; |
||||
|
import { MatInputModule } from '@angular/material/input'; |
||||
|
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module'; |
||||
|
|
||||
|
import { ActivitiesFilterComponent } from './activities-filter.component'; |
||||
|
|
||||
|
@NgModule({ |
||||
|
declarations: [ActivitiesFilterComponent], |
||||
|
exports: [ActivitiesFilterComponent], |
||||
|
imports: [ |
||||
|
CommonModule, |
||||
|
GfSymbolModule, |
||||
|
MatAutocompleteModule, |
||||
|
MatChipsModule, |
||||
|
MatInputModule, |
||||
|
ReactiveFormsModule |
||||
|
], |
||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
||||
|
}) |
||||
|
export class GfActivitiesFilterModule {} |
Loading…
Reference in new issue