mirror of https://github.com/ghostfolio/ghostfolio
committed by
GitHub
71 changed files with 18368 additions and 12287 deletions
@ -1,2 +1,3 @@ |
|||
/.nx/cache |
|||
/dist |
|||
/test/import |
|||
|
@ -1,7 +1,9 @@ |
|||
:host { |
|||
display: flex; |
|||
flex: 0 0 auto; |
|||
margin-bottom: 0; |
|||
min-height: 0; |
|||
padding: 0 !important; |
|||
|
|||
@media (min-width: 576px) { |
|||
padding: 0 !important; |
|||
} |
|||
} |
|||
|
@ -0,0 +1,31 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { Component } from '@angular/core'; |
|||
import { MatButtonModule } from '@angular/material/button'; |
|||
import { RouterModule } from '@angular/router'; |
|||
|
|||
import { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-beanvest-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class BeanvestPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'beanvest'; |
|||
}); |
|||
|
|||
public routerLinkAbout = ['/' + $localize`about`]; |
|||
public routerLinkFeatures = ['/' + $localize`features`]; |
|||
public routerLinkResourcesPersonalFinanceTools = [ |
|||
'/' + $localize`resources`, |
|||
'personal-finance-tools' |
|||
]; |
|||
} |
@ -0,0 +1,31 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { Component } from '@angular/core'; |
|||
import { MatButtonModule } from '@angular/material/button'; |
|||
import { RouterModule } from '@angular/router'; |
|||
|
|||
import { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-capitally-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class CapitallyPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'capitally'; |
|||
}); |
|||
|
|||
public routerLinkAbout = ['/' + $localize`about`]; |
|||
public routerLinkFeatures = ['/' + $localize`features`]; |
|||
public routerLinkResourcesPersonalFinanceTools = [ |
|||
'/' + $localize`resources`, |
|||
'personal-finance-tools' |
|||
]; |
|||
} |
@ -0,0 +1,31 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { Component } from '@angular/core'; |
|||
import { MatButtonModule } from '@angular/material/button'; |
|||
import { RouterModule } from '@angular/router'; |
|||
|
|||
import { products } from '../products'; |
|||
|
|||
@Component({ |
|||
host: { class: 'page' }, |
|||
imports: [CommonModule, MatButtonModule, RouterModule], |
|||
selector: 'gf-wealthica-page', |
|||
standalone: true, |
|||
styleUrls: ['../product-page-template.scss'], |
|||
templateUrl: '../product-page-template.html' |
|||
}) |
|||
export class WealthicaPageComponent { |
|||
public product1 = products.find(({ key }) => { |
|||
return key === 'ghostfolio'; |
|||
}); |
|||
|
|||
public product2 = products.find(({ key }) => { |
|||
return key === 'wealthica'; |
|||
}); |
|||
|
|||
public routerLinkAbout = ['/' + $localize`about`]; |
|||
public routerLinkFeatures = ['/' + $localize`features`]; |
|||
public routerLinkResourcesPersonalFinanceTools = [ |
|||
'/' + $localize`resources`, |
|||
'personal-finance-tools' |
|||
]; |
|||
} |
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,4 @@ |
|||
export interface Currency { |
|||
label: string; |
|||
value: string; |
|||
} |
@ -0,0 +1,21 @@ |
|||
<input |
|||
autocapitalize="off" |
|||
autocomplete="off" |
|||
matInput |
|||
[formControl]="control" |
|||
[matAutocomplete]="currencyAutocomplete" |
|||
/> |
|||
|
|||
<mat-autocomplete |
|||
#currencyAutocomplete="matAutocomplete" |
|||
[displayWith]="displayFn" |
|||
(optionSelected)="onUpdateCurrency($event)" |
|||
> |
|||
<mat-option |
|||
*ngFor="let currencyItem of filteredCurrencies" |
|||
class="line-height-1" |
|||
[value]="currencyItem" |
|||
> |
|||
{{ currencyItem.label }} |
|||
</mat-option> |
|||
</mat-autocomplete> |
@ -0,0 +1,3 @@ |
|||
:host { |
|||
display: block; |
|||
} |
@ -0,0 +1,167 @@ |
|||
import { FocusMonitor } from '@angular/cdk/a11y'; |
|||
import { |
|||
ChangeDetectionStrategy, |
|||
ChangeDetectorRef, |
|||
Component, |
|||
ElementRef, |
|||
Input, |
|||
OnDestroy, |
|||
OnInit, |
|||
ViewChild |
|||
} from '@angular/core'; |
|||
import { FormControl, FormGroupDirective, NgControl } from '@angular/forms'; |
|||
import { |
|||
MatAutocomplete, |
|||
MatAutocompleteSelectedEvent |
|||
} from '@angular/material/autocomplete'; |
|||
import { MatFormFieldControl } from '@angular/material/form-field'; |
|||
import { MatInput } from '@angular/material/input'; |
|||
import { Currency } from '@ghostfolio/common/interfaces/currency.interface'; |
|||
import { AbstractMatFormField } from '@ghostfolio/ui/shared/abstract-mat-form-field'; |
|||
import { Subject } from 'rxjs'; |
|||
import { map, startWith, takeUntil } from 'rxjs/operators'; |
|||
@Component({ |
|||
changeDetection: ChangeDetectionStrategy.OnPush, |
|||
host: { |
|||
'[attr.aria-describedBy]': 'describedBy', |
|||
'[id]': 'id' |
|||
}, |
|||
providers: [ |
|||
{ |
|||
provide: MatFormFieldControl, |
|||
useExisting: CurrencySelectorComponent |
|||
} |
|||
], |
|||
selector: 'gf-currency-selector', |
|||
styleUrls: ['./currency-selector.component.scss'], |
|||
templateUrl: 'currency-selector.component.html' |
|||
}) |
|||
export class CurrencySelectorComponent |
|||
extends AbstractMatFormField<Currency> |
|||
implements OnInit, OnDestroy |
|||
{ |
|||
@Input() private currencies: Currency[] = []; |
|||
@Input() private formControlName: string; |
|||
|
|||
@ViewChild(MatInput) private input: MatInput; |
|||
|
|||
@ViewChild('currencyAutocomplete') |
|||
public currencyAutocomplete: MatAutocomplete; |
|||
|
|||
public control = new FormControl(); |
|||
public filteredCurrencies: Currency[] = []; |
|||
|
|||
private unsubscribeSubject = new Subject<void>(); |
|||
|
|||
public constructor( |
|||
public readonly _elementRef: ElementRef, |
|||
public readonly _focusMonitor: FocusMonitor, |
|||
public readonly changeDetectorRef: ChangeDetectorRef, |
|||
private readonly formGroupDirective: FormGroupDirective, |
|||
public readonly ngControl: NgControl |
|||
) { |
|||
super(_elementRef, _focusMonitor, ngControl); |
|||
|
|||
this.controlType = 'currency-selector'; |
|||
} |
|||
|
|||
public ngOnInit() { |
|||
if (this.disabled) { |
|||
this.control.disable(); |
|||
} |
|||
|
|||
const formGroup = this.formGroupDirective.form; |
|||
|
|||
if (formGroup) { |
|||
const control = formGroup.get(this.formControlName); |
|||
|
|||
if (control) { |
|||
this.value = this.currencies.find(({ value }) => { |
|||
return value === control.value; |
|||
}); |
|||
} |
|||
} |
|||
|
|||
this.control.valueChanges |
|||
.pipe(takeUntil(this.unsubscribeSubject)) |
|||
.subscribe(() => { |
|||
if (super.value?.value) { |
|||
super.value.value = null; |
|||
} |
|||
}); |
|||
|
|||
this.control.valueChanges |
|||
.pipe( |
|||
takeUntil(this.unsubscribeSubject), |
|||
startWith(''), |
|||
map((value) => { |
|||
return value ? this.filter(value) : this.currencies.slice(); |
|||
}) |
|||
) |
|||
.subscribe((values) => { |
|||
this.filteredCurrencies = values; |
|||
}); |
|||
} |
|||
|
|||
public displayFn(currency: Currency) { |
|||
return currency?.label ?? ''; |
|||
} |
|||
|
|||
public get empty() { |
|||
return this.input?.empty; |
|||
} |
|||
|
|||
public focus() { |
|||
this.input.focus(); |
|||
} |
|||
|
|||
public ngDoCheck() { |
|||
if (this.ngControl) { |
|||
this.validateRequired(); |
|||
this.errorState = this.ngControl.invalid && this.ngControl.touched; |
|||
this.stateChanges.next(); |
|||
} |
|||
} |
|||
|
|||
public onUpdateCurrency(event: MatAutocompleteSelectedEvent) { |
|||
super.value = { |
|||
label: event.option.value.label, |
|||
value: event.option.value.value |
|||
} as Currency; |
|||
} |
|||
|
|||
public set value(value: Currency) { |
|||
const newValue = |
|||
typeof value === 'object' && value !== null ? { ...value } : value; |
|||
this.control.setValue(newValue); |
|||
super.value = newValue; |
|||
} |
|||
|
|||
public ngOnDestroy() { |
|||
super.ngOnDestroy(); |
|||
|
|||
this.unsubscribeSubject.next(); |
|||
this.unsubscribeSubject.complete(); |
|||
} |
|||
|
|||
private filter(value: Currency | string) { |
|||
const filterValue = |
|||
typeof value === 'string' |
|||
? value?.toLowerCase() |
|||
: value?.value.toLowerCase(); |
|||
|
|||
return this.currencies.filter((currency) => { |
|||
return currency.value.toLowerCase().startsWith(filterValue); |
|||
}); |
|||
} |
|||
|
|||
private validateRequired() { |
|||
const requiredCheck = super.required |
|||
? !super.value.label || !super.value.value |
|||
: false; |
|||
|
|||
if (requiredCheck) { |
|||
this.ngControl.control.setErrors({ invalidData: true }); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
import { CommonModule } from '@angular/common'; |
|||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; |
|||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; |
|||
import { MatAutocompleteModule } from '@angular/material/autocomplete'; |
|||
import { MatFormFieldModule } from '@angular/material/form-field'; |
|||
import { MatInputModule } from '@angular/material/input'; |
|||
|
|||
import { CurrencySelectorComponent } from './currency-selector.component'; |
|||
|
|||
@NgModule({ |
|||
declarations: [CurrencySelectorComponent], |
|||
exports: [CurrencySelectorComponent], |
|||
imports: [ |
|||
CommonModule, |
|||
FormsModule, |
|||
MatAutocompleteModule, |
|||
MatFormFieldModule, |
|||
MatInputModule, |
|||
ReactiveFormsModule |
|||
], |
|||
schemas: [CUSTOM_ELEMENTS_SCHEMA] |
|||
}) |
|||
export class GfCurrencySelectorModule {} |
File diff suppressed because it is too large
Loading…
Reference in new issue