Feature/introduce tabs with routing to home page (#495)

* Introduce tabs with routing

* Update changelog
This commit is contained in:
Thomas Kaul 2021-11-28 12:52:37 +01:00 committed by GitHub
parent a24a094407
commit 2f402c0c8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 737 additions and 703 deletions

View File

@ -9,7 +9,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added ### Added
- Added tabs to the admin control panel - Added tabs with routing to the admin control panel
### Changed
- Introduced tabs with routing to the home page
## 1.81.0 - 27.11.2021 ## 1.81.0 - 27.11.2021

View File

@ -0,0 +1,84 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import {
RANGE,
SettingsStorageService
} from '@ghostfolio/client/services/settings-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Position, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-home-holdings',
styleUrls: ['./home-holdings.scss'],
templateUrl: './home-holdings.html'
})
export class HomeHoldingsComponent implements OnDestroy, OnInit {
public dateRange: DateRange;
public deviceType: string;
public hasPermissionToCreateOrder: boolean;
public positions: Position[];
public user: User;
private unsubscribeSubject = new Subject<void>();
/**
* @constructor
*/
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private settingsStorageService: SettingsStorageService,
private userService: UserService
) {
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
this.hasPermissionToCreateOrder = hasPermission(
this.user.permissions,
permissions.createOrder
);
this.changeDetectorRef.markForCheck();
}
});
}
/**
* Initializes the controller
*/
public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.dateRange =
<DateRange>this.settingsStorageService.getSetting(RANGE) || 'max';
this.update();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private update() {
this.dataService
.fetchPositions({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.positions = response.positions;
this.changeDetectorRef.markForCheck();
});
this.changeDetectorRef.markForCheck();
}
}

View File

@ -0,0 +1,26 @@
<div class="container justify-content-center pb-3 px-3">
<div class="row">
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
<mat-card class="p-0">
<mat-card-content>
<gf-positions
[baseCurrency]="user?.settings?.baseCurrency"
[deviceType]="deviceType"
[locale]="user?.settings?.locale"
[positions]="positions"
[range]="dateRange"
></gf-positions>
</mat-card-content>
</mat-card>
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
<a
class="mt-3"
i18n
mat-button
[routerLink]="['/portfolio', 'transactions']"
>Manage Transactions...</a
>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,23 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router';
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
import { HomeHoldingsComponent } from './home-holdings.component';
@NgModule({
declarations: [HomeHoldingsComponent],
exports: [],
imports: [
CommonModule,
GfPositionsModule,
MatButtonModule,
MatCardModule,
RouterModule
],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfHomeHoldingsModule {}

View File

@ -0,0 +1,5 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
display: block;
}

View File

@ -0,0 +1,74 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
import { User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DataSource } from '@prisma/client';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-home-market',
styleUrls: ['./home-market.scss'],
templateUrl: './home-market.html'
})
export class HomeMarketComponent implements OnDestroy, OnInit {
public fearAndGreedIndex: number;
public hasPermissionToAccessFearAndGreedIndex: boolean;
public isLoading = true;
public user: User;
private unsubscribeSubject = new Subject<void>();
/**
* @constructor
*/
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private userService: UserService
) {
this.isLoading = true;
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.user.permissions,
permissions.accessFearAndGreedIndex
);
if (this.hasPermissionToAccessFearAndGreedIndex) {
this.dataService
.fetchSymbolItem({
dataSource: DataSource.RAKUTEN,
symbol: ghostfolioFearAndGreedIndexSymbol
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => {
this.fearAndGreedIndex = marketPrice;
this.isLoading = false;
this.changeDetectorRef.markForCheck();
});
}
this.changeDetectorRef.markForCheck();
}
});
}
/**
* Initializes the controller
*/
public ngOnInit() {}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
}

View File

@ -0,0 +1,25 @@
<div
class="
align-items-center
container
d-flex
flex-grow-1
h-100
justify-content-center
w-100
"
>
<div class="row w-100">
<div class="col-xs-12 col-md-8 offset-md-2">
<mat-card class="h-100">
<mat-card-content>
<gf-fear-and-greed-index
class="d-flex justify-content-center"
[fearAndGreedIndex]="fearAndGreedIndex"
[hidden]="isLoading"
></gf-fear-and-greed-index>
</mat-card-content>
</mat-card>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module';
import { HomeMarketComponent } from './home-market.component';
@NgModule({
declarations: [HomeMarketComponent],
exports: [],
imports: [CommonModule, GfFearAndGreedIndexModule, MatCardModule],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfHomeMarketModule {}

View File

@ -0,0 +1,5 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
display: block;
}

View File

@ -0,0 +1,122 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { ToggleOption } from '@ghostfolio/client/components/toggle/interfaces/toggle-option.type';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import {
RANGE,
SettingsStorageService
} from '@ghostfolio/client/services/settings-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
import { DateRange } from '@ghostfolio/common/types';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-home-overview',
styleUrls: ['./home-overview.scss'],
templateUrl: './home-overview.html'
})
export class HomeOverviewComponent implements OnDestroy, OnInit {
public dateRange: DateRange;
public dateRangeOptions: ToggleOption[] = [
{ label: 'Today', value: '1d' },
{ label: 'YTD', value: 'ytd' },
{ label: '1Y', value: '1y' },
{ label: '5Y', value: '5y' },
{ label: 'Max', value: 'max' }
];
public hasImpersonationId: boolean;
public historicalDataItems: LineChartItem[];
public isAllTimeHigh: boolean;
public isAllTimeLow: boolean;
public isLoadingPerformance = true;
public performance: PortfolioPerformance;
public user: User;
private unsubscribeSubject = new Subject<void>();
/**
* @constructor
*/
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private impersonationStorageService: ImpersonationStorageService,
private settingsStorageService: SettingsStorageService,
private userService: UserService
) {
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
this.changeDetectorRef.markForCheck();
}
});
}
/**
* Initializes the controller
*/
public ngOnInit() {
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => {
this.hasImpersonationId = !!aId;
this.changeDetectorRef.markForCheck();
});
this.dateRange =
<DateRange>this.settingsStorageService.getSetting(RANGE) || 'max';
this.update();
}
public onChangeDateRange(aDateRange: DateRange) {
this.dateRange = aDateRange;
this.settingsStorageService.setSetting(RANGE, this.dateRange);
this.update();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private update() {
this.isLoadingPerformance = true;
this.dataService
.fetchChart({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => {
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
return {
date: chartDataItem.date,
value: chartDataItem.value
};
});
this.isAllTimeHigh = chartData.isAllTimeHigh;
this.isAllTimeLow = chartData.isAllTimeLow;
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchPortfolioPerformance({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.performance = response;
this.isLoadingPerformance = false;
this.changeDetectorRef.markForCheck();
});
this.changeDetectorRef.markForCheck();
}
}

View File

@ -0,0 +1,55 @@
<div
class="
align-items-center
container
d-flex
flex-column
h-100
justify-content-center
overview
position-relative
"
>
<div class="row w-100">
<div class="chart-container col">
<gf-line-chart
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showGradient]="true"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
<div
*ngIf="historicalDataItems?.length === 0"
class="align-items-center d-flex h-100 justify-content-center w-100"
>
<div class="d-flex justify-content-center">
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
</div>
</div>
</div>
</div>
<div class="overview-container row mt-1">
<div class="col">
<gf-portfolio-performance
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isAllTimeHigh]="isAllTimeHigh"
[isAllTimeLow]="isAllTimeLow"
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
></gf-portfolio-performance>
<div class="text-center">
<gf-toggle
[defaultValue]="dateRange"
[isLoading]="isLoadingPerformance"
[options]="dateRangeOptions"
(change)="onChangeDateRange($event.value)"
></gf-toggle>
</div>
</div>
</div>
</div>

View File

@ -0,0 +1,25 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module';
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
import { HomeOverviewComponent } from './home-overview.component';
@NgModule({
declarations: [HomeOverviewComponent],
exports: [],
imports: [
CommonModule,
GfLineChartModule,
GfNoTransactionsInfoModule,
GfPortfolioPerformanceModule,
GfToggleModule,
RouterModule
],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfHomeOverviewModule {}

View File

@ -0,0 +1,34 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
display: block;
.chart-container {
aspect-ratio: 16 / 9;
max-height: 50vh;
// Fallback for aspect-ratio (using padding hack)
@supports not (aspect-ratio: 16 / 9) {
&::before {
float: left;
padding-top: 56.25%;
content: '';
}
&::after {
display: block;
content: '';
clear: both;
}
}
gf-line-chart {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
}
}
}

View File

@ -0,0 +1,66 @@
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
import { DataService } from '@ghostfolio/client/services/data.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { PortfolioSummary, User } from '@ghostfolio/common/interfaces';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-home-summary',
styleUrls: ['./home-summary.scss'],
templateUrl: './home-summary.html'
})
export class HomeSummaryComponent implements OnDestroy, OnInit {
public isLoading = true;
public summary: PortfolioSummary;
public user: User;
private unsubscribeSubject = new Subject<void>();
/**
* @constructor
*/
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private userService: UserService
) {
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
this.changeDetectorRef.markForCheck();
}
});
}
/**
* Initializes the controller
*/
public ngOnInit() {
this.update();
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private update() {
this.isLoading = true;
this.dataService
.fetchPortfolioSummary()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.summary = response;
this.isLoading = false;
this.changeDetectorRef.markForCheck();
});
this.changeDetectorRef.markForCheck();
}
}

View File

@ -0,0 +1,19 @@
<div class="container pb-3 px-3">
<div class="row">
<div class="col-xs-12 col-md-8 offset-md-2">
<mat-card class="h-100">
<mat-card-header>
<mat-card-title i18n>Summary</mat-card-title>
</mat-card-header>
<mat-card-content>
<gf-portfolio-summary
[baseCurrency]="user?.settings?.baseCurrency"
[isLoading]="isLoading"
[locale]="user?.settings?.locale"
[summary]="summary"
></gf-portfolio-summary>
</mat-card-content>
</mat-card>
</div>
</div>
</div>

View File

@ -0,0 +1,21 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatCardModule } from '@angular/material/card';
import { RouterModule } from '@angular/router';
import { GfPortfolioSummaryModule } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.module';
import { HomeSummaryComponent } from './home-summary.component';
@NgModule({
declarations: [HomeSummaryComponent],
exports: [],
imports: [
CommonModule,
GfPortfolioSummaryModule,
MatCardModule,
RouterModule
],
providers: [],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfHomeSummaryModule {}

View File

@ -0,0 +1,5 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
display: block;
}

View File

@ -1,11 +1,26 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { HomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component';
import { HomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
import { HomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
import { HomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { HomePageComponent } from './home-page.component'; import { HomePageComponent } from './home-page.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: HomePageComponent, canActivate: [AuthGuard] } {
path: '',
component: HomePageComponent,
canActivate: [AuthGuard],
children: [
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
{ path: 'overview', component: HomeOverviewComponent },
{ path: 'holdings', component: HomeHoldingsComponent },
{ path: 'summary', component: HomeSummaryComponent },
{ path: 'market', component: HomeMarketComponent }
]
}
]; ];
@NgModule({ @NgModule({

View File

@ -1,35 +1,17 @@
import { import {
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef,
HostBinding, HostBinding,
OnDestroy, OnDestroy,
OnInit, OnInit
ViewChild
} from '@angular/core'; } from '@angular/core';
import { MatTabChangeEvent } from '@angular/material/tabs';
import { ToggleOption } from '@ghostfolio/client/components/toggle/interfaces/toggle-option.type';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import {
RANGE,
SettingsStorageService
} from '@ghostfolio/client/services/settings-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { ghostfolioFearAndGreedIndexSymbol } from '@ghostfolio/common/config';
import {
PortfolioPerformance,
PortfolioSummary,
Position,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { DataSource } from '@prisma/client';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { User } from '@ghostfolio/common/interfaces';
@Component({ @Component({
selector: 'gf-home-page', selector: 'gf-home-page',
@ -41,32 +23,9 @@ export class HomePageComponent implements OnDestroy, OnInit {
return this.canCreateAccount; return this.canCreateAccount;
} }
@ViewChild('positionsContainer') positionsContainer: ElementRef;
public canCreateAccount: boolean; public canCreateAccount: boolean;
public currentTabIndex = 0;
public dateRange: DateRange;
public dateRangeOptions: ToggleOption[] = [
{ label: 'Today', value: '1d' },
{ label: 'YTD', value: 'ytd' },
{ label: '1Y', value: '1y' },
{ label: '5Y', value: '5y' },
{ label: 'Max', value: 'max' }
];
public deviceType: string;
public fearAndGreedIndex: number;
public hasImpersonationId: boolean;
public hasPermissionToAccessFearAndGreedIndex: boolean; public hasPermissionToAccessFearAndGreedIndex: boolean;
public hasPermissionToCreateOrder: boolean; public tabs: { iconName: string; path: string }[] = [];
public historicalDataItems: LineChartItem[];
public isAllTimeHigh: boolean;
public isAllTimeLow: boolean;
public isLoadingPerformance = true;
public isLoadingSummary = true;
public performance: PortfolioPerformance;
public positions: Position[];
public routeQueryParams: Subscription;
public summary: PortfolioSummary;
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -76,16 +35,19 @@ export class HomePageComponent implements OnDestroy, OnInit {
*/ */
public constructor( public constructor(
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService, private deviceService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService, private impersonationStorageService: ImpersonationStorageService,
private settingsStorageService: SettingsStorageService,
private userService: UserService private userService: UserService
) { ) {
this.userService.stateChanged this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.tabs = [
{ iconName: 'analytics-outline', path: 'overview' },
{ iconName: 'wallet-outline', path: 'holdings' },
{ iconName: 'reader-outline', path: 'summary' }
];
this.user = state.user; this.user = state.user;
this.canCreateAccount = hasPermission( this.canCreateAccount = hasPermission(
@ -99,24 +61,9 @@ export class HomePageComponent implements OnDestroy, OnInit {
); );
if (this.hasPermissionToAccessFearAndGreedIndex) { if (this.hasPermissionToAccessFearAndGreedIndex) {
this.dataService this.tabs.push({ iconName: 'newspaper-outline', path: 'market' });
.fetchSymbolItem({
dataSource: DataSource.RAKUTEN,
symbol: ghostfolioFearAndGreedIndexSymbol
})
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ marketPrice }) => {
this.fearAndGreedIndex = marketPrice;
this.changeDetectorRef.markForCheck();
});
} }
this.hasPermissionToCreateOrder = hasPermission(
this.user.permissions,
permissions.createOrder
);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }
}); });
@ -125,93 +72,10 @@ export class HomePageComponent implements OnDestroy, OnInit {
/** /**
* Initializes the controller * Initializes the controller
*/ */
public ngOnInit() { public ngOnInit() {}
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => {
this.hasImpersonationId = !!aId;
this.changeDetectorRef.markForCheck();
});
this.dateRange =
<DateRange>this.settingsStorageService.getSetting(RANGE) || 'max';
this.update();
}
public onChangeDateRange(aDateRange: DateRange) {
this.dateRange = aDateRange;
this.settingsStorageService.setSetting(RANGE, this.dateRange);
this.update();
}
public onTabChanged(event: MatTabChangeEvent) {
this.currentTabIndex = event.index;
this.update();
}
public ngOnDestroy() { public ngOnDestroy() {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private update() {
if (this.currentTabIndex === 0) {
this.isLoadingPerformance = true;
this.dataService
.fetchChart({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => {
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
return {
date: chartDataItem.date,
value: chartDataItem.value
};
});
this.isAllTimeHigh = chartData.isAllTimeHigh;
this.isAllTimeLow = chartData.isAllTimeLow;
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchPortfolioPerformance({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.performance = response;
this.isLoadingPerformance = false;
this.changeDetectorRef.markForCheck();
});
} else if (this.currentTabIndex === 1) {
this.dataService
.fetchPositions({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.positions = response.positions;
this.changeDetectorRef.markForCheck();
});
} else if (this.currentTabIndex === 2) {
this.isLoadingSummary = true;
this.dataService
.fetchPortfolioSummary()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.summary = response;
this.isLoadingSummary = false;
this.changeDetectorRef.markForCheck();
});
}
this.changeDetectorRef.markForCheck();
}
} }

View File

@ -1,169 +1,14 @@
<mat-tab-group <router-outlet></router-outlet>
animationDuration="0ms"
class="position-absolute" <nav mat-align-tabs="center" mat-tab-nav-bar>
headerPosition="below" <a
mat-align-tabs="center" *ngFor="let tab of tabs"
[disablePagination]="true" #rla="routerLinkActive"
(selectedTabChange)="onTabChanged($event)" mat-tab-link
> routerLinkActive
<mat-tab> [active]="rla.isActive"
<ng-template mat-tab-label> [routerLink]="tab.path"
<ion-icon name="analytics-outline" size="large"></ion-icon> >
</ng-template> <ion-icon size="large" [name]="tab.iconName"></ion-icon>
<div </a>
class=" </nav>
align-items-center
container
d-flex
flex-column
h-100
justify-content-center
overview
position-relative
"
>
<div class="row w-100">
<div class="chart-container col">
<gf-line-chart
class="mr-3"
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showGradient]="true"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
<div
*ngIf="historicalDataItems?.length === 0"
class="
align-items-center
chart-container
d-flex
justify-content-center
w-100
"
>
<div class="d-flex justify-content-center">
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
</div>
</div>
</div>
</div>
<div class="overview-container row mt-1">
<div class="col">
<gf-portfolio-performance
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isAllTimeHigh]="isAllTimeHigh"
[isAllTimeLow]="isAllTimeLow"
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
></gf-portfolio-performance>
<div class="text-center">
<gf-toggle
[defaultValue]="dateRange"
[isLoading]="isLoadingPerformance"
[options]="dateRangeOptions"
(change)="onChangeDateRange($event.value)"
></gf-toggle>
</div>
</div>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<ion-icon name="wallet-outline" size="large"></ion-icon>
</ng-template>
<div class="container justify-content-center pb-3 px-3 positions">
<h3 class="d-flex justify-content-center mb-3" i18n>Holdings</h3>
<div class="row">
<div class="align-items-center col-xs-12 col-md-8 offset-md-2">
<div class="pb-2 text-center">
<gf-toggle
[defaultValue]="dateRange"
[isLoading]="isLoadingPerformance"
[options]="dateRangeOptions"
(change)="onChangeDateRange($event.value)"
></gf-toggle>
</div>
<mat-card class="p-0">
<mat-card-content>
<gf-positions
[baseCurrency]="user?.settings?.baseCurrency"
[deviceType]="deviceType"
[locale]="user?.settings?.locale"
[positions]="positions"
[range]="dateRange"
></gf-positions>
</mat-card-content>
</mat-card>
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
<a
class="mt-3"
i18n
mat-button
[routerLink]="['/portfolio', 'transactions']"
>Manage Transactions...</a
>
</div>
</div>
</div>
</div>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<ion-icon name="reader-outline" size="large"></ion-icon>
</ng-template>
<div class="container pb-3 px-3 positions">
<div class="row">
<div class="col-xs-12 col-md-8 offset-md-2">
<mat-card class="h-100">
<mat-card-header>
<mat-card-title i18n>Summary</mat-card-title>
</mat-card-header>
<mat-card-content>
<gf-portfolio-summary
[baseCurrency]="user?.settings?.baseCurrency"
[isLoading]="isLoadingSummary"
[locale]="user?.settings?.locale"
[summary]="summary"
></gf-portfolio-summary>
</mat-card-content>
</mat-card>
</div>
</div>
</div>
</mat-tab>
<mat-tab *ngIf="hasPermissionToAccessFearAndGreedIndex">
<ng-template mat-tab-label>
<ion-icon name="newspaper-outline" size="large"></ion-icon>
</ng-template>
<div
class="
align-items-center
container
d-flex
flex-grow-1
h-100
justify-content-center
w-100
"
>
<div class="row w-100">
<div class="col-xs-12 col-md-8 offset-md-2">
<mat-card class="h-100">
<mat-card-content>
<gf-fear-and-greed-index
class="d-flex justify-content-center"
[fearAndGreedIndex]="fearAndGreedIndex"
></gf-fear-and-greed-index>
</mat-card-content>
</mat-card>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>

View File

@ -1,17 +1,11 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { GfFearAndGreedIndexModule } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.module'; import { GfHomeHoldingsModule } from '@ghostfolio/client/components/home-holdings/home-holdings.module';
import { GfPerformanceChartDialogModule } from '@ghostfolio/client/components/performance-chart-dialog/performance-chart-dialog.module'; import { GfHomeMarketModule } from '@ghostfolio/client/components/home-market/home-market.module';
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module'; import { GfHomeOverviewModule } from '@ghostfolio/client/components/home-overview/home-overview.module';
import { GfPortfolioSummaryModule } from '@ghostfolio/client/components/portfolio-summary/portfolio-summary.module'; import { GfHomeSummaryModule } from '@ghostfolio/client/components/home-summary/home-summary.module';
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
import { GfToggleModule } from '@ghostfolio/client/components/toggle/toggle.module';
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
import { HomePageRoutingModule } from './home-page-routing.module'; import { HomePageRoutingModule } from './home-page-routing.module';
import { HomePageComponent } from './home-page.component'; import { HomePageComponent } from './home-page.component';
@ -21,17 +15,11 @@ import { HomePageComponent } from './home-page.component';
exports: [], exports: [],
imports: [ imports: [
CommonModule, CommonModule,
GfFearAndGreedIndexModule, GfHomeHoldingsModule,
GfLineChartModule, GfHomeMarketModule,
GfNoTransactionsInfoModule, GfHomeOverviewModule,
GfPerformanceChartDialogModule, GfHomeSummaryModule,
GfPortfolioPerformanceModule,
GfPortfolioSummaryModule,
GfPositionsModule,
GfToggleModule,
HomePageRoutingModule, HomePageRoutingModule,
MatButtonModule,
MatCardModule,
MatTabsModule, MatTabsModule,
RouterModule RouterModule
], ],

View File

@ -2,109 +2,42 @@
:host { :host {
color: rgb(var(--dark-primary-text)); color: rgb(var(--dark-primary-text));
display: block; display: flex;
min-height: calc(100vh - 5rem); flex-direction: column;
position: relative; height: calc(100vh - 5rem);
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom);
padding-bottom: constant(safe-area-inset-bottom);
&.with-create-account-container { &.with-create-account-container {
min-height: calc(100vh - 5rem - 3.5rem); height: calc(100vh - 5rem - 3.5rem);
}
.mat-tab-group {
bottom: 0;
left: 0;
right: 0;
top: 0;
margin-bottom: env(safe-area-inset-bottom);
margin-bottom: constant(safe-area-inset-bottom);
::ng-deep {
.mat-tab-body-wrapper {
height: 100%;
.container {
&.overview {
.chart-container {
aspect-ratio: 16 / 9;
max-height: 50vh;
// Fallback for aspect-ratio (using padding hack)
@supports not (aspect-ratio: 16 / 9) {
&::before {
float: left;
padding-top: 56.25%;
content: '';
}
&::after {
display: block;
content: '';
clear: both;
}
}
gf-line-chart {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
}
}
}
&.positions {
min-height: 100%;
}
}
}
.mat-tab-header {
border-top: 0;
.mat-ink-bar {
visibility: hidden !important;
}
.mat-tab-label-active {
color: rgba(var(--palette-primary-500), 1);
opacity: 1;
}
}
}
} }
::ng-deep { ::ng-deep {
.mat-form-field-infix { gf-home-holdings,
border-top: 0 solid transparent !important; gf-home-market,
gf-home-overview,
gf-home-summary {
flex: 1 1 auto;
overflow-y: auto;
} }
.mat-form-field-wrapper { .mat-tab-header {
padding-bottom: 0 !important; border-bottom: 0;
}
.mat-form-field-underline { .mat-ink-bar {
bottom: 0 !important; visibility: hidden !important;
} }
.mat-form-field-appearance-outline .mat-select-arrow-wrapper { .mat-tab-label-active {
transform: translateY(0); color: rgba(var(--palette-primary-500), 1);
opacity: 1;
}
} }
} }
} }
:host-context(.is-dark-theme) { :host-context(.is-dark-theme) {
color: rgb(var(--light-primary-text)); color: rgb(var(--light-primary-text));
.container {
&.overview {
.button-container {
.mat-flat-button {
background-color: rgba(255, 255, 255, $alpha-hover);
}
}
}
}
} }

View File

@ -1,11 +1,22 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router'; import { RouterModule, Routes } from '@angular/router';
import { HomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component';
import { HomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { ZenPageComponent } from './zen-page.component'; import { ZenPageComponent } from './zen-page.component';
const routes: Routes = [ const routes: Routes = [
{ path: '', component: ZenPageComponent, canActivate: [AuthGuard] } {
path: '',
component: ZenPageComponent,
canActivate: [AuthGuard],
children: [
{ path: '', redirectTo: 'overview', pathMatch: 'full' },
{ path: 'overview', component: HomeOverviewComponent },
{ path: 'holdings', component: HomeHoldingsComponent }
]
}
]; ];
@NgModule({ @NgModule({

View File

@ -3,25 +3,12 @@ import {
AfterViewInit, AfterViewInit,
ChangeDetectorRef, ChangeDetectorRef,
Component, Component,
ElementRef,
OnDestroy, OnDestroy,
OnInit, OnInit
ViewChild
} from '@angular/core'; } from '@angular/core';
import { MatTabChangeEvent } from '@angular/material/tabs';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { DataService } from '@ghostfolio/client/services/data.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { import { User } from '@ghostfolio/common/interfaces';
PortfolioPerformance,
Position,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DateRange } from '@ghostfolio/common/types';
import { LineChartItem } from '@ghostfolio/ui/line-chart/interfaces/line-chart.interface';
import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { first, takeUntil } from 'rxjs/operators'; import { first, takeUntil } from 'rxjs/operators';
@ -31,19 +18,7 @@ import { first, takeUntil } from 'rxjs/operators';
styleUrls: ['./zen-page.scss'] styleUrls: ['./zen-page.scss']
}) })
export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit { export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
@ViewChild('positionsContainer') positionsContainer: ElementRef; public tabs: { iconName: string; path: string }[] = [];
public currentTabIndex = 0;
public dateRange: DateRange = 'max';
public deviceType: string;
public hasImpersonationId: boolean;
public hasPermissionToCreateOrder: boolean;
public historicalDataItems: LineChartItem[];
public isAllTimeHigh: boolean;
public isAllTimeLow: boolean;
public isLoadingPerformance = true;
public performance: PortfolioPerformance;
public positions: Position[];
public user: User; public user: User;
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -54,9 +29,6 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
public constructor( public constructor(
private route: ActivatedRoute, private route: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef, private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService, private userService: UserService,
private viewportScroller: ViewportScroller private viewportScroller: ViewportScroller
) { ) {
@ -64,32 +36,18 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe((state) => { .subscribe((state) => {
if (state?.user) { if (state?.user) {
this.tabs = [
{ iconName: 'analytics-outline', path: 'overview' },
{ iconName: 'wallet-outline', path: 'holdings' }
];
this.user = state.user; this.user = state.user;
this.hasPermissionToCreateOrder = hasPermission(
this.user.permissions,
permissions.createOrder
);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
} }
}); });
} }
public ngOnInit() { public ngOnInit() {}
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
this.impersonationStorageService
.onChangeHasImpersonation()
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((aId) => {
this.hasImpersonationId = !!aId;
this.changeDetectorRef.markForCheck();
});
this.update();
}
public ngAfterViewInit(): void { public ngAfterViewInit(): void {
this.route.fragment this.route.fragment
@ -97,57 +55,8 @@ export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment)); .subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
} }
public onTabChanged(event: MatTabChangeEvent) {
this.currentTabIndex = event.index;
this.update();
}
public ngOnDestroy() { public ngOnDestroy() {
this.unsubscribeSubject.next(); this.unsubscribeSubject.next();
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private update() {
if (this.currentTabIndex === 0) {
this.isLoadingPerformance = true;
this.dataService
.fetchChart({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((chartData) => {
this.historicalDataItems = chartData.chart.map((chartDataItem) => {
return {
date: chartDataItem.date,
value: chartDataItem.value
};
});
this.isAllTimeHigh = chartData.isAllTimeHigh;
this.isAllTimeLow = chartData.isAllTimeLow;
this.changeDetectorRef.markForCheck();
});
this.dataService
.fetchPortfolioPerformance({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.performance = response;
this.isLoadingPerformance = false;
this.changeDetectorRef.markForCheck();
});
} else if (this.currentTabIndex === 1) {
this.dataService
.fetchPositions({ range: this.dateRange })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe((response) => {
this.positions = response.positions;
this.changeDetectorRef.markForCheck();
});
}
this.changeDetectorRef.markForCheck();
}
} }

View File

@ -1,94 +1,14 @@
<mat-tab-group <router-outlet></router-outlet>
animationDuration="0ms"
class="position-absolute"
headerPosition="below"
mat-align-tabs="center"
[disablePagination]="true"
(selectedTabChange)="onTabChanged($event)"
>
<mat-tab>
<ng-template mat-tab-label>
<ion-icon name="analytics-outline" size="large"></ion-icon>
</ng-template>
<div
class="
container
d-flex
flex-column
h-100
justify-content-center
overview
position-relative
"
>
<div class="row">
<div
class="chart-container d-flex flex-column col justify-content-center"
>
<gf-line-chart
class="mr-3"
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showGradient]="true"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
<div
*ngIf="historicalDataItems?.length === 0"
class="d-flex justify-content-center"
>
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
</div>
</div>
</div>
<div class="overview-container row mb-5 mt-1">
<div class="col">
<gf-portfolio-performance
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isAllTimeHigh]="isAllTimeHigh"
[isAllTimeLow]="isAllTimeLow"
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId && !user.settings.isRestrictedView"
></gf-portfolio-performance>
</div>
</div>
</div>
</mat-tab>
<mat-tab> <nav mat-align-tabs="center" mat-tab-nav-bar>
<ng-template mat-tab-label> <a
<ion-icon name="wallet-outline" size="large"></ion-icon> *ngFor="let tab of tabs"
</ng-template> #rla="routerLinkActive"
<div class="container justify-content-center pb-3 px-3 positions"> mat-tab-link
<h3 class="d-flex justify-content-center mb-3" i18n>Holdings</h3> routerLinkActive
<div class="row"> [active]="rla.isActive"
<div class="align-items-center col"> [routerLink]="tab.path"
<mat-card class="p-0"> >
<mat-card-content> <ion-icon size="large" [name]="tab.iconName"></ion-icon>
<gf-positions </a>
[baseCurrency]="user?.settings?.baseCurrency" </nav>
[deviceType]="deviceType"
[locale]="user?.settings?.locale"
[positions]="positions"
[range]="dateRange"
></gf-positions>
</mat-card-content>
</mat-card>
<div *ngIf="hasPermissionToCreateOrder" class="text-center">
<a
class="mt-3"
i18n
mat-button
[routerLink]="['/portfolio', 'transactions']"
>Manage Transactions...</a
>
</div>
</div>
</div>
</div>
</mat-tab>
</mat-tab-group>

View File

@ -1,13 +1,9 @@
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatTabsModule } from '@angular/material/tabs'; import { MatTabsModule } from '@angular/material/tabs';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { GfPortfolioPerformanceModule } from '@ghostfolio/client/components/portfolio-performance/portfolio-performance.module'; import { GfHomeHoldingsModule } from '@ghostfolio/client/components/home-holdings/home-holdings.module';
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module'; import { GfHomeOverviewModule } from '@ghostfolio/client/components/home-overview/home-overview.module';
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
import { GfNoTransactionsInfoModule } from '@ghostfolio/ui/no-transactions-info';
import { ZenPageRoutingModule } from './zen-page-routing.module'; import { ZenPageRoutingModule } from './zen-page-routing.module';
import { ZenPageComponent } from './zen-page.component'; import { ZenPageComponent } from './zen-page.component';
@ -17,12 +13,8 @@ import { ZenPageComponent } from './zen-page.component';
exports: [], exports: [],
imports: [ imports: [
CommonModule, CommonModule,
GfLineChartModule, GfHomeHoldingsModule,
GfNoTransactionsInfoModule, GfHomeOverviewModule,
GfPortfolioPerformanceModule,
GfPositionsModule,
MatButtonModule,
MatCardModule,
MatTabsModule, MatTabsModule,
RouterModule, RouterModule,
ZenPageRoutingModule ZenPageRoutingModule

View File

@ -2,72 +2,31 @@
:host { :host {
color: rgb(var(--dark-primary-text)); color: rgb(var(--dark-primary-text));
display: block; display: flex;
min-height: calc(100vh - 5rem); flex-direction: column;
position: relative; height: calc(100vh - 5rem);
overflow-y: auto;
.mat-tab-group { padding-bottom: env(safe-area-inset-bottom);
bottom: 0; padding-bottom: constant(safe-area-inset-bottom);
left: 0;
right: 0;
top: 0;
margin-bottom: env(safe-area-inset-bottom); ::ng-deep {
margin-bottom: constant(safe-area-inset-bottom); gf-home-holdings,
gf-home-overview {
flex: 1 1 auto;
overflow-y: auto;
}
::ng-deep { .mat-tab-header {
.mat-tab-body-wrapper { border-bottom: 0;
height: 100%;
.container { .mat-ink-bar {
&.overview { visibility: hidden !important;
.chart-container {
aspect-ratio: 16 / 9;
max-height: 50vh;
// Fallback for aspect-ratio (using padding hack)
@supports not (aspect-ratio: 16 / 9) {
&::before {
float: left;
padding-top: 56.25%;
content: '';
}
&::after {
display: block;
content: '';
clear: both;
}
}
gf-line-chart {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
}
}
}
&.positions {
min-height: 100%;
}
}
} }
.mat-tab-header { .mat-tab-label-active {
border-top: 0; color: rgba(var(--palette-primary-500), 1);
opacity: 1;
.mat-ink-bar {
visibility: hidden !important;
}
.mat-tab-label-active {
color: rgba(var(--palette-primary-500), 1);
opacity: 1;
}
} }
} }
} }
@ -75,14 +34,4 @@
:host-context(.is-dark-theme) { :host-context(.is-dark-theme) {
color: rgb(var(--light-primary-text)); color: rgb(var(--light-primary-text));
.container {
&.overview {
.button-container {
.mat-flat-button {
background-color: rgba(255, 255, 255, $alpha-hover);
}
}
}
}
} }