Feature/various layout improvements (#224)
* Various layout improvements * Update changelog
This commit is contained in:
parent
6996e5a140
commit
cdcbe3ab71
@ -11,10 +11,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
- Extended the data management by symbol profile data
|
- Extended the data management by symbol profile data
|
||||||
- Added a currency attribute to the symbol profile model
|
- Added a currency attribute to the symbol profile model
|
||||||
|
- Added a positions button on the home page which scrolls into the view
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Improved the style of the active page in the navigation on desktop
|
- Improved the style of the active page in the navigation on desktop
|
||||||
|
- Removed the footer for users
|
||||||
|
- Extended the _Zen Mode_ by positions
|
||||||
|
- Improved the _Create Account_ message in the _Live Demo_
|
||||||
|
|
||||||
## 1.27.0 - 18.07.2021
|
## 1.27.0 - 18.07.2021
|
||||||
|
|
||||||
|
@ -119,6 +119,7 @@ const routes: Routes = [
|
|||||||
routes,
|
routes,
|
||||||
// Preload all lazy loaded modules with the attribute preload === true
|
// Preload all lazy loaded modules with the attribute preload === true
|
||||||
{
|
{
|
||||||
|
anchorScrolling: 'enabled',
|
||||||
preloadingStrategy: ModulePreloadService,
|
preloadingStrategy: ModulePreloadService,
|
||||||
// enableTracing: true // <-- debugging purposes only
|
// enableTracing: true // <-- debugging purposes only
|
||||||
relativeLinkResolution: 'legacy'
|
relativeLinkResolution: 'legacy'
|
||||||
|
@ -9,17 +9,17 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main role="main">
|
<main role="main">
|
||||||
<div *ngIf="canCreateAccount" class="container create-account-container">
|
<div *ngIf="canCreateAccount" class="container create-account-container mb-2">
|
||||||
<div class="row mb-5">
|
<div class="row">
|
||||||
<div class="col-md-6 offset-md-3">
|
<div class="col-md-8 offset-md-2 text-center">
|
||||||
<a [routerLink]="['/']">
|
<a class="text-center" [routerLink]="['/']">
|
||||||
<mat-card
|
<div
|
||||||
class="create-account-box p-2 text-center"
|
class="create-account-box d-inline-block px-3 py-2"
|
||||||
(click)="onCreateAccount()"
|
(click)="onCreateAccount()"
|
||||||
>
|
>
|
||||||
<div class="mt-1" i18n>You are using the Live Demo.</div>
|
<span i18n>You are using the Live Demo.</span>
|
||||||
<button mat-button color="primary" i18n>Create Account</button>
|
<a class="ml-2" href="#" i18n>Create Account</a>
|
||||||
</mat-card></a
|
</div></a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -28,10 +28,7 @@
|
|||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<footer
|
<footer *ngIf="!user" class="footer d-flex justify-content-center w-100">
|
||||||
*ngIf="currentRoute === 'start' || deviceType !== 'mobile'"
|
|
||||||
class="footer d-flex justify-content-center position-absolute w-100"
|
|
||||||
>
|
|
||||||
<div class="container text-center">
|
<div class="container text-center">
|
||||||
<div>
|
<div>
|
||||||
© {{ currentYear }} <a href="https://ghostfol.io">Ghostfolio</a>
|
© {{ currentYear }} <a href="https://ghostfol.io">Ghostfolio</a>
|
||||||
|
@ -1,17 +1,31 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
display: block;
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
main {
|
main {
|
||||||
padding: 5rem 0;
|
min-height: 100vh;
|
||||||
|
padding-top: 5rem;
|
||||||
|
|
||||||
.create-account-box {
|
.create-account-container {
|
||||||
cursor: pointer;
|
margin-top: -0.5rem;
|
||||||
font-size: 90%;
|
|
||||||
|
.create-account-box {
|
||||||
|
background-color: rgba(0, 0, 0, $alpha-hover);
|
||||||
|
border-radius: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 80%;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: rgba(var(--palette-primary-500), 1);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
bottom: 0;
|
|
||||||
height: 5rem;
|
height: 5rem;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import { Platform } from '@angular/cdk/platform';
|
import { Platform } from '@angular/cdk/platform';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
|
||||||
import { MatCardModule } from '@angular/material/card';
|
|
||||||
import {
|
import {
|
||||||
DateAdapter,
|
DateAdapter,
|
||||||
MAT_DATE_FORMATS,
|
MAT_DATE_FORMATS,
|
||||||
@ -40,8 +38,6 @@ export function NgxStripeFactory(): string {
|
|||||||
GfHeaderModule,
|
GfHeaderModule,
|
||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
MarkdownModule.forRoot(),
|
MarkdownModule.forRoot(),
|
||||||
MatButtonModule,
|
|
||||||
MatCardModule,
|
|
||||||
MaterialCssVarsModule.forRoot({
|
MaterialCssVarsModule.forRoot({
|
||||||
darkThemeClass: 'is-dark-theme',
|
darkThemeClass: 'is-dark-theme',
|
||||||
isAutoContrast: true,
|
isAutoContrast: true,
|
||||||
|
@ -1,4 +1,13 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ViewportScroller } from '@angular/common';
|
||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface';
|
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface';
|
||||||
@ -21,14 +30,16 @@ import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
|||||||
import { DateRange } from '@ghostfolio/common/types';
|
import { DateRange } from '@ghostfolio/common/types';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { first, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-home-page',
|
selector: 'gf-home-page',
|
||||||
templateUrl: './home-page.html',
|
templateUrl: './home-page.html',
|
||||||
styleUrls: ['./home-page.scss']
|
styleUrls: ['./home-page.scss']
|
||||||
})
|
})
|
||||||
export class HomePageComponent implements OnDestroy, OnInit {
|
export class HomePageComponent implements AfterViewInit, OnDestroy, OnInit {
|
||||||
|
@ViewChild('positionsContainer') positionsContainer: ElementRef;
|
||||||
|
|
||||||
public dateRange: DateRange;
|
public dateRange: DateRange;
|
||||||
public dateRangeOptions: ToggleOption[] = [
|
public dateRangeOptions: ToggleOption[] = [
|
||||||
{ label: 'Today', value: '1d' },
|
{ label: 'Today', value: '1d' },
|
||||||
@ -50,6 +61,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
public performance: PortfolioPerformance;
|
public performance: PortfolioPerformance;
|
||||||
public positions: { [symbol: string]: PortfolioPosition };
|
public positions: { [symbol: string]: PortfolioPosition };
|
||||||
public routeQueryParams: Subscription;
|
public routeQueryParams: Subscription;
|
||||||
|
public showPositionsButton: boolean;
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -66,7 +78,8 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private settingsStorageService: SettingsStorageService,
|
private settingsStorageService: SettingsStorageService,
|
||||||
private userService: UserService
|
private userService: UserService,
|
||||||
|
private viewportScroller: ViewportScroller
|
||||||
) {
|
) {
|
||||||
this.routeQueryParams = this.route.queryParams
|
this.routeQueryParams = this.route.queryParams
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -127,6 +140,12 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ngAfterViewInit(): void {
|
||||||
|
this.route.fragment
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
|
||||||
|
}
|
||||||
|
|
||||||
public onChangeDateRange(aDateRange: DateRange) {
|
public onChangeDateRange(aDateRange: DateRange) {
|
||||||
this.dateRange = aDateRange;
|
this.dateRange = aDateRange;
|
||||||
this.settingsStorageService.setSetting(RANGE, this.dateRange);
|
this.settingsStorageService.setSetting(RANGE, this.dateRange);
|
||||||
@ -203,7 +222,8 @@ export class HomePageComponent implements OnDestroy, OnInit {
|
|||||||
.subscribe((response) => {
|
.subscribe((response) => {
|
||||||
this.positions = response;
|
this.positions = response;
|
||||||
this.hasPositions =
|
this.hasPositions =
|
||||||
this.positions && Object.keys(this.positions).length > 0;
|
this.positions && Object.keys(this.positions).length > 1;
|
||||||
|
this.showPositionsButton = this.hasPositions;
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
@ -1,87 +1,108 @@
|
|||||||
<div class="container">
|
<ng-container *ngIf="hasPositions || !historicalDataItems">
|
||||||
<div class="row">
|
<div class="container overview position-relative">
|
||||||
<a
|
<div class="row">
|
||||||
class="chart-container col mr-3"
|
<a
|
||||||
[routerLink]="[]"
|
class="chart-container col mr-3"
|
||||||
[queryParams]="{performanceChartDialog: true}"
|
[routerLink]="[]"
|
||||||
>
|
[queryParams]="{performanceChartDialog: true}"
|
||||||
<gf-line-chart
|
>
|
||||||
symbol="Performance"
|
<gf-line-chart
|
||||||
[historicalDataItems]="historicalDataItems"
|
symbol="Performance"
|
||||||
[showLoader]="false"
|
[historicalDataItems]="historicalDataItems"
|
||||||
[showXAxis]="false"
|
[showLoader]="false"
|
||||||
[showYAxis]="false"
|
[showXAxis]="false"
|
||||||
></gf-line-chart>
|
[showYAxis]="false"
|
||||||
</a>
|
></gf-line-chart>
|
||||||
</div>
|
</a>
|
||||||
<div class="overview-container row mb-5 mt-1">
|
</div>
|
||||||
<div class="col">
|
<div class="overview-container row mb-5 mt-1">
|
||||||
<gf-portfolio-performance-summary
|
<div class="col">
|
||||||
class="pb-4"
|
<gf-portfolio-performance-summary
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
class="pb-4"
|
||||||
[isLoading]="isLoadingPerformance"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[performance]="performance"
|
|
||||||
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
|
|
||||||
></gf-portfolio-performance-summary>
|
|
||||||
<div class="text-center">
|
|
||||||
<gf-toggle
|
|
||||||
[defaultValue]="dateRange"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
[isLoading]="isLoadingPerformance"
|
||||||
[options]="dateRangeOptions"
|
[locale]="user?.settings?.locale"
|
||||||
(change)="onChangeDateRange($event.value)"
|
[performance]="performance"
|
||||||
></gf-toggle>
|
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
|
||||||
|
></gf-portfolio-performance-summary>
|
||||||
|
<div class="text-center">
|
||||||
|
<gf-toggle
|
||||||
|
[defaultValue]="dateRange"
|
||||||
|
[isLoading]="isLoadingPerformance"
|
||||||
|
[options]="dateRangeOptions"
|
||||||
|
(change)="onChangeDateRange($event.value)"
|
||||||
|
></gf-toggle>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="button-container d-flex justify-content-center position-absolute"
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
*ngIf="showPositionsButton"
|
||||||
|
[routerLink]="['/home']"
|
||||||
|
fragment="positions-container"
|
||||||
|
i18n
|
||||||
|
mat-flat-button
|
||||||
|
(click)="showPositionsButton = false"
|
||||||
|
>Positions</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="positions-container" class="container positions">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-xs-12 col-md-6 mb-3">
|
||||||
|
<mat-card class="h-100">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title i18n>Performance</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-portfolio-performance
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[isLoading]="isLoadingPerformance"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[performance]="performance"
|
||||||
|
></gf-portfolio-performance>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
</div>
|
||||||
|
<div class="col-xs-12 col-md-6 mb-3">
|
||||||
|
<mat-card class="h-100">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title i18n>Summary</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<gf-portfolio-overview
|
||||||
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
|
[isLoading]="isLoadingOverview"
|
||||||
|
[locale]="user?.settings?.locale"
|
||||||
|
[overview]="overview"
|
||||||
|
></gf-portfolio-overview>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ng-container>
|
||||||
|
|
||||||
<div class="container">
|
<ng-container *ngIf="!hasPositions && historicalDataItems">
|
||||||
<div class="row mb-3">
|
<div class="d-flex justify-content-center my-5">
|
||||||
<div class="col">
|
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
|
||||||
<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>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
</ng-container>
|
||||||
<div class="col-xs-12 col-md-6 mb-3">
|
|
||||||
<mat-card class="h-100">
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title i18n>Performance</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-portfolio-performance
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[isLoading]="isLoadingPerformance"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[performance]="performance"
|
|
||||||
></gf-portfolio-performance>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
<div class="col-xs-12 col-md-6 mb-3">
|
|
||||||
<mat-card class="h-100">
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title i18n>Summary</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<gf-portfolio-overview
|
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
|
||||||
[isLoading]="isLoadingOverview"
|
|
||||||
[locale]="user?.settings?.locale"
|
|
||||||
[overview]="overview"
|
|
||||||
></gf-portfolio-overview>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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 { MatCardModule } from '@angular/material/card';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||||
@ -26,6 +27,7 @@ import { HomePageComponent } from './home-page.component';
|
|||||||
GfPositionsModule,
|
GfPositionsModule,
|
||||||
GfToggleModule,
|
GfToggleModule,
|
||||||
HomePageRoutingModule,
|
HomePageRoutingModule,
|
||||||
|
MatButtonModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
RouterModule
|
RouterModule
|
||||||
],
|
],
|
||||||
|
@ -1,35 +1,59 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
.chart-container {
|
.container {
|
||||||
aspect-ratio: 16 / 9;
|
&.overview {
|
||||||
cursor: pointer;
|
min-height: calc(100vh - 5rem);
|
||||||
margin-top: -1rem;
|
|
||||||
max-height: 50vh;
|
|
||||||
|
|
||||||
// Fallback for aspect-ratio (using padding hack)
|
.button-container {
|
||||||
@supports not (aspect-ratio: 16 / 9) {
|
bottom: 3rem;
|
||||||
&::before {
|
left: 0;
|
||||||
float: left;
|
right: 0;
|
||||||
padding-top: 56.25%;
|
.mat-flat-button {
|
||||||
content: '';
|
background-color: rgba(0, 0, 0, $alpha-hover);
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
.chart-container {
|
||||||
display: block;
|
aspect-ratio: 16 / 9;
|
||||||
content: '';
|
cursor: pointer;
|
||||||
clear: both;
|
margin-top: -1rem;
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gf-line-chart {
|
&.positions {
|
||||||
bottom: 0;
|
padding-top: 5rem;
|
||||||
left: 0;
|
min-height: calc(100vh - 5rem);
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,4 +78,14 @@
|
|||||||
|
|
||||||
: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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,37 @@
|
|||||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
import { ViewportScroller } from '@angular/common';
|
||||||
|
import {
|
||||||
|
AfterViewInit,
|
||||||
|
ChangeDetectorRef,
|
||||||
|
Component,
|
||||||
|
ElementRef,
|
||||||
|
OnDestroy,
|
||||||
|
OnInit,
|
||||||
|
ViewChild
|
||||||
|
} from '@angular/core';
|
||||||
|
import { ActivatedRoute } from '@angular/router';
|
||||||
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface';
|
import { LineChartItem } from '@ghostfolio/client/components/line-chart/interfaces/line-chart.interface';
|
||||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
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 { UserService } from '@ghostfolio/client/services/user/user.service';
|
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||||
import { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
|
import {
|
||||||
|
PortfolioPerformance,
|
||||||
|
PortfolioPosition,
|
||||||
|
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 { DateRange } from '@ghostfolio/common/types';
|
||||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { first, takeUntil } from 'rxjs/operators';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'gf-zen-page',
|
selector: 'gf-zen-page',
|
||||||
templateUrl: './zen-page.html',
|
templateUrl: './zen-page.html',
|
||||||
styleUrls: ['./zen-page.scss']
|
styleUrls: ['./zen-page.scss']
|
||||||
})
|
})
|
||||||
export class ZenPageComponent implements OnDestroy, OnInit {
|
export class ZenPageComponent implements AfterViewInit, OnDestroy, OnInit {
|
||||||
|
@ViewChild('positionsContainer') positionsContainer: ElementRef;
|
||||||
|
|
||||||
public dateRange: DateRange = 'max';
|
public dateRange: DateRange = 'max';
|
||||||
public deviceType: string;
|
public deviceType: string;
|
||||||
public hasImpersonationId: boolean;
|
public hasImpersonationId: boolean;
|
||||||
@ -24,6 +40,8 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
public historicalDataItems: LineChartItem[];
|
public historicalDataItems: LineChartItem[];
|
||||||
public isLoadingPerformance = true;
|
public isLoadingPerformance = true;
|
||||||
public performance: PortfolioPerformance;
|
public performance: PortfolioPerformance;
|
||||||
|
public positions: { [symbol: string]: PortfolioPosition };
|
||||||
|
public showPositionsButton: boolean;
|
||||||
public user: User;
|
public user: User;
|
||||||
|
|
||||||
private unsubscribeSubject = new Subject<void>();
|
private unsubscribeSubject = new Subject<void>();
|
||||||
@ -32,11 +50,13 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
public constructor(
|
public constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
private changeDetectorRef: ChangeDetectorRef,
|
private changeDetectorRef: ChangeDetectorRef,
|
||||||
private dataService: DataService,
|
private dataService: DataService,
|
||||||
private deviceService: DeviceDetectorService,
|
private deviceService: DeviceDetectorService,
|
||||||
private impersonationStorageService: ImpersonationStorageService,
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
private userService: UserService
|
private userService: UserService,
|
||||||
|
private viewportScroller: ViewportScroller
|
||||||
) {
|
) {
|
||||||
this.userService.stateChanged
|
this.userService.stateChanged
|
||||||
.pipe(takeUntil(this.unsubscribeSubject))
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
@ -54,9 +74,6 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the controller
|
|
||||||
*/
|
|
||||||
public ngOnInit() {
|
public ngOnInit() {
|
||||||
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
@ -70,6 +87,12 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ngAfterViewInit(): void {
|
||||||
|
this.route.fragment
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
|
||||||
|
}
|
||||||
|
|
||||||
public ngOnDestroy() {
|
public ngOnDestroy() {
|
||||||
this.unsubscribeSubject.next();
|
this.unsubscribeSubject.next();
|
||||||
this.unsubscribeSubject.complete();
|
this.unsubscribeSubject.complete();
|
||||||
@ -89,8 +112,6 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
this.hasPositions = this.historicalDataItems?.length > 0;
|
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -104,6 +125,18 @@ export class ZenPageComponent implements OnDestroy, OnInit {
|
|||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchPortfolioPositions({ range: this.dateRange })
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe((response) => {
|
||||||
|
this.positions = response;
|
||||||
|
this.hasPositions =
|
||||||
|
this.positions && Object.keys(this.positions).length > 1;
|
||||||
|
this.showPositionsButton = this.hasPositions;
|
||||||
|
|
||||||
|
this.changeDetectorRef.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
this.changeDetectorRef.markForCheck();
|
this.changeDetectorRef.markForCheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +1,65 @@
|
|||||||
<div *ngIf="hasPositions || !historicalDataItems" class="container">
|
<ng-container *ngIf="hasPositions || !historicalDataItems">
|
||||||
<div class="row">
|
<div class="container overview position-relative">
|
||||||
<div class="chart-container col mr-3">
|
<div class="row">
|
||||||
<gf-line-chart
|
<div class="chart-container col mr-3">
|
||||||
symbol="Performance"
|
<gf-line-chart
|
||||||
[historicalDataItems]="historicalDataItems"
|
symbol="Performance"
|
||||||
[showLoader]="false"
|
[historicalDataItems]="historicalDataItems"
|
||||||
[showXAxis]="false"
|
[showLoader]="false"
|
||||||
[showYAxis]="false"
|
[showXAxis]="false"
|
||||||
></gf-line-chart>
|
[showYAxis]="false"
|
||||||
|
></gf-line-chart>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="overview-container row mb-5 mt-1">
|
||||||
<div class="overview-container row mb-5 mt-1">
|
<div class="col">
|
||||||
<div class="col">
|
<gf-portfolio-performance-summary
|
||||||
<gf-portfolio-performance-summary
|
class="pb-4"
|
||||||
class="pb-4"
|
[baseCurrency]="user?.settings?.baseCurrency"
|
||||||
[baseCurrency]="user?.settings?.baseCurrency"
|
[isLoading]="isLoadingPerformance"
|
||||||
[isLoading]="isLoadingPerformance"
|
[locale]="user?.settings?.locale"
|
||||||
[locale]="user?.settings?.locale"
|
[performance]="performance"
|
||||||
[performance]="performance"
|
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
|
||||||
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
|
></gf-portfolio-performance-summary>
|
||||||
></gf-portfolio-performance-summary>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
*ngIf="!hasPositions && historicalDataItems"
|
class="button-container d-flex justify-content-center position-absolute"
|
||||||
class="d-flex justify-content-center my-5"
|
>
|
||||||
>
|
<a
|
||||||
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
|
*ngIf="showPositionsButton"
|
||||||
</div>
|
[routerLink]="['/zen']"
|
||||||
|
fragment="positions-container"
|
||||||
|
i18n
|
||||||
|
mat-flat-button
|
||||||
|
(click)="showPositionsButton = false"
|
||||||
|
>Positions</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="positions-container" class="container positions">
|
||||||
|
<div class="row mb-3">
|
||||||
|
<div class="col">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="!hasPositions && historicalDataItems">
|
||||||
|
<div class="d-flex justify-content-center my-5">
|
||||||
|
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
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 { MatCardModule } from '@angular/material/card';
|
||||||
|
import { RouterModule } from '@angular/router';
|
||||||
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||||
import { GfNoTransactionsInfoModule } from '@ghostfolio/client/components/no-transactions-info/no-transactions-info.module';
|
import { GfNoTransactionsInfoModule } from '@ghostfolio/client/components/no-transactions-info/no-transactions-info.module';
|
||||||
import { GfPortfolioPerformanceSummaryModule } from '@ghostfolio/client/components/portfolio-performance-summary/portfolio-performance-summary.module';
|
import { GfPortfolioPerformanceSummaryModule } from '@ghostfolio/client/components/portfolio-performance-summary/portfolio-performance-summary.module';
|
||||||
|
import { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
|
||||||
|
|
||||||
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';
|
||||||
@ -16,7 +19,10 @@ import { ZenPageComponent } from './zen-page.component';
|
|||||||
GfLineChartModule,
|
GfLineChartModule,
|
||||||
GfNoTransactionsInfoModule,
|
GfNoTransactionsInfoModule,
|
||||||
GfPortfolioPerformanceSummaryModule,
|
GfPortfolioPerformanceSummaryModule,
|
||||||
|
GfPositionsModule,
|
||||||
|
MatButtonModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
|
RouterModule,
|
||||||
ZenPageRoutingModule
|
ZenPageRoutingModule
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
@ -1,38 +1,73 @@
|
|||||||
|
@import '~apps/client/src/styles/ghostfolio-style';
|
||||||
|
|
||||||
:host {
|
:host {
|
||||||
color: rgb(var(--dark-primary-text));
|
color: rgb(var(--dark-primary-text));
|
||||||
display: block;
|
display: block;
|
||||||
|
min-height: 100vh;
|
||||||
|
|
||||||
.chart-container {
|
.container {
|
||||||
aspect-ratio: 16 / 9;
|
&.overview {
|
||||||
margin-top: 3rem;
|
min-height: calc(100vh - 5rem);
|
||||||
max-height: 50vh;
|
|
||||||
|
|
||||||
// Fallback for aspect-ratio (using padding hack)
|
.button-container {
|
||||||
@supports not (aspect-ratio: 16 / 9) {
|
bottom: 3rem;
|
||||||
&::before {
|
left: 0;
|
||||||
float: left;
|
right: 0;
|
||||||
padding-top: 56.25%;
|
|
||||||
content: '';
|
.mat-flat-button {
|
||||||
|
background-color: rgba(0, 0, 0, $alpha-hover);
|
||||||
|
border-radius: 2rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&::after {
|
.chart-container {
|
||||||
display: block;
|
aspect-ratio: 16 / 9;
|
||||||
content: '';
|
margin-top: 3rem;
|
||||||
clear: both;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gf-line-chart {
|
&.positions {
|
||||||
bottom: 0;
|
padding-top: 5rem;
|
||||||
left: 0;
|
min-height: calc(100vh - 5rem);
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html class="h-100 position-relative" lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title>Ghostfolio – Open Source Wealth Management Software</title>
|
<title>Ghostfolio – Open Source Wealth Management Software</title>
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
@ -20,15 +20,11 @@ $mat-css-light-theme-selector: '.is-light-theme';
|
|||||||
--light-background: rgb(255, 255, 255);
|
--light-background: rgb(255, 255, 255);
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
|
||||||
position: relative;
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-family: var(--font-family-sans-serif);
|
font-family: var(--font-family-sans-serif);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-bottom: 5rem;
|
margin-bottom: 5rem;
|
||||||
|
min-height: 100%;
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: var(--dark-primary-text);
|
color: var(--dark-primary-text);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user