Feature/various layout improvements (#224)

* Various layout improvements

* Update changelog
pull/229/head
Thomas 3 years ago committed by GitHub
parent 6996e5a140
commit cdcbe3ab71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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
- Added a currency attribute to the symbol profile model
- Added a positions button on the home page which scrolls into the view
### Changed
- 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

@ -119,6 +119,7 @@ const routes: Routes = [
routes,
// Preload all lazy loaded modules with the attribute preload === true
{
anchorScrolling: 'enabled',
preloadingStrategy: ModulePreloadService,
// enableTracing: true // <-- debugging purposes only
relativeLinkResolution: 'legacy'

@ -9,17 +9,17 @@
</header>
<main role="main">
<div *ngIf="canCreateAccount" class="container create-account-container">
<div class="row mb-5">
<div class="col-md-6 offset-md-3">
<a [routerLink]="['/']">
<mat-card
class="create-account-box p-2 text-center"
<div *ngIf="canCreateAccount" class="container create-account-container mb-2">
<div class="row">
<div class="col-md-8 offset-md-2 text-center">
<a class="text-center" [routerLink]="['/']">
<div
class="create-account-box d-inline-block px-3 py-2"
(click)="onCreateAccount()"
>
<div class="mt-1" i18n>You are using the Live Demo.</div>
<button mat-button color="primary" i18n>Create Account</button>
</mat-card></a
<span i18n>You are using the Live Demo.</span>
<a class="ml-2" href="#" i18n>Create Account</a>
</div></a
>
</div>
</div>
@ -28,10 +28,7 @@
<router-outlet></router-outlet>
</main>
<footer
*ngIf="currentRoute === 'start' || deviceType !== 'mobile'"
class="footer d-flex justify-content-center position-absolute w-100"
>
<footer *ngIf="!user" class="footer d-flex justify-content-center w-100">
<div class="container text-center">
<div>
© {{ currentYear }} <a href="https://ghostfol.io">Ghostfolio</a>

@ -1,17 +1,31 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
display: block;
min-height: 100vh;
main {
padding: 5rem 0;
min-height: 100vh;
padding-top: 5rem;
.create-account-container {
margin-top: -0.5rem;
.create-account-box {
background-color: rgba(0, 0, 0, $alpha-hover);
border-radius: 2rem;
cursor: pointer;
font-size: 80%;
.create-account-box {
cursor: pointer;
font-size: 90%;
a {
color: rgba(var(--palette-primary-500), 1);
font-weight: bold;
}
}
}
}
.footer {
bottom: 0;
height: 5rem;
line-height: 1;
}

@ -1,8 +1,6 @@
import { Platform } from '@angular/cdk/platform';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import {
DateAdapter,
MAT_DATE_FORMATS,
@ -40,8 +38,6 @@ export function NgxStripeFactory(): string {
GfHeaderModule,
HttpClientModule,
MarkdownModule.forRoot(),
MatButtonModule,
MatCardModule,
MaterialCssVarsModule.forRoot({
darkThemeClass: 'is-dark-theme',
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 { ActivatedRoute, Router } from '@angular/router';
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 { DeviceDetectorService } from 'ngx-device-detector';
import { Subject, Subscription } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { first, takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-home-page',
templateUrl: './home-page.html',
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 dateRangeOptions: ToggleOption[] = [
{ label: 'Today', value: '1d' },
@ -50,6 +61,7 @@ export class HomePageComponent implements OnDestroy, OnInit {
public performance: PortfolioPerformance;
public positions: { [symbol: string]: PortfolioPosition };
public routeQueryParams: Subscription;
public showPositionsButton: boolean;
public user: User;
private unsubscribeSubject = new Subject<void>();
@ -66,7 +78,8 @@ export class HomePageComponent implements OnDestroy, OnInit {
private route: ActivatedRoute,
private router: Router,
private settingsStorageService: SettingsStorageService,
private userService: UserService
private userService: UserService,
private viewportScroller: ViewportScroller
) {
this.routeQueryParams = this.route.queryParams
.pipe(takeUntil(this.unsubscribeSubject))
@ -127,6 +140,12 @@ export class HomePageComponent implements OnDestroy, OnInit {
this.update();
}
public ngAfterViewInit(): void {
this.route.fragment
.pipe(first())
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
}
public onChangeDateRange(aDateRange: DateRange) {
this.dateRange = aDateRange;
this.settingsStorageService.setSetting(RANGE, this.dateRange);
@ -203,7 +222,8 @@ export class HomePageComponent implements OnDestroy, OnInit {
.subscribe((response) => {
this.positions = response;
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();
});

@ -1,87 +1,108 @@
<div class="container">
<div class="row">
<a
class="chart-container col mr-3"
[routerLink]="[]"
[queryParams]="{performanceChartDialog: true}"
>
<gf-line-chart
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
</a>
</div>
<div class="overview-container row mb-5 mt-1">
<div class="col">
<gf-portfolio-performance-summary
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
></gf-portfolio-performance-summary>
<div class="text-center">
<gf-toggle
[defaultValue]="dateRange"
<ng-container *ngIf="hasPositions || !historicalDataItems">
<div class="container overview position-relative">
<div class="row">
<a
class="chart-container col mr-3"
[routerLink]="[]"
[queryParams]="{performanceChartDialog: true}"
>
<gf-line-chart
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
</a>
</div>
<div class="overview-container row mb-5 mt-1">
<div class="col">
<gf-portfolio-performance-summary
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isLoading]="isLoadingPerformance"
[options]="dateRangeOptions"
(change)="onChangeDateRange($event.value)"
></gf-toggle>
[locale]="user?.settings?.locale"
[performance]="performance"
[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>
</div>
<div class="container">
<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
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 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 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="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 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>
</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,5 +1,6 @@
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 { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
@ -26,6 +27,7 @@ import { HomePageComponent } from './home-page.component';
GfPositionsModule,
GfToggleModule,
HomePageRoutingModule,
MatButtonModule,
MatCardModule,
RouterModule
],

@ -1,35 +1,59 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
color: rgb(var(--dark-primary-text));
display: block;
min-height: 100vh;
.container {
&.overview {
min-height: calc(100vh - 5rem);
.chart-container {
aspect-ratio: 16 / 9;
cursor: pointer;
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: '';
.button-container {
bottom: 3rem;
left: 0;
right: 0;
.mat-flat-button {
background-color: rgba(0, 0, 0, $alpha-hover);
border-radius: 2rem;
}
}
&::after {
display: block;
content: '';
clear: both;
.chart-container {
aspect-ratio: 16 / 9;
cursor: pointer;
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 {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
&.positions {
padding-top: 5rem;
min-height: calc(100vh - 5rem);
}
}
@ -54,4 +78,14 @@
:host-context(.is-dark-theme) {
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 { 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 { PortfolioPerformance, User } from '@ghostfolio/common/interfaces';
import {
PortfolioPerformance,
PortfolioPosition,
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';
import { first, takeUntil } from 'rxjs/operators';
@Component({
selector: 'gf-zen-page',
templateUrl: './zen-page.html',
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 deviceType: string;
public hasImpersonationId: boolean;
@ -24,6 +40,8 @@ export class ZenPageComponent implements OnDestroy, OnInit {
public historicalDataItems: LineChartItem[];
public isLoadingPerformance = true;
public performance: PortfolioPerformance;
public positions: { [symbol: string]: PortfolioPosition };
public showPositionsButton: boolean;
public user: User;
private unsubscribeSubject = new Subject<void>();
@ -32,11 +50,13 @@ export class ZenPageComponent implements OnDestroy, OnInit {
* @constructor
*/
public constructor(
private route: ActivatedRoute,
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private deviceService: DeviceDetectorService,
private impersonationStorageService: ImpersonationStorageService,
private userService: UserService
private userService: UserService,
private viewportScroller: ViewportScroller
) {
this.userService.stateChanged
.pipe(takeUntil(this.unsubscribeSubject))
@ -54,9 +74,6 @@ export class ZenPageComponent implements OnDestroy, OnInit {
});
}
/**
* Initializes the controller
*/
public ngOnInit() {
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
@ -70,6 +87,12 @@ export class ZenPageComponent implements OnDestroy, OnInit {
this.update();
}
public ngAfterViewInit(): void {
this.route.fragment
.pipe(first())
.subscribe((fragment) => this.viewportScroller.scrollToAnchor(fragment));
}
public ngOnDestroy() {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
@ -89,8 +112,6 @@ export class ZenPageComponent implements OnDestroy, OnInit {
};
});
this.hasPositions = this.historicalDataItems?.length > 0;
this.changeDetectorRef.markForCheck();
});
@ -104,6 +125,18 @@ export class ZenPageComponent implements OnDestroy, OnInit {
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();
}
}

@ -1,32 +1,65 @@
<div *ngIf="hasPositions || !historicalDataItems" class="container">
<div class="row">
<div class="chart-container col mr-3">
<gf-line-chart
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
<ng-container *ngIf="hasPositions || !historicalDataItems">
<div class="container overview position-relative">
<div class="row">
<div class="chart-container col mr-3">
<gf-line-chart
symbol="Performance"
[historicalDataItems]="historicalDataItems"
[showLoader]="false"
[showXAxis]="false"
[showYAxis]="false"
></gf-line-chart>
</div>
</div>
<div class="overview-container row mb-5 mt-1">
<div class="col">
<gf-portfolio-performance-summary
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
></gf-portfolio-performance-summary>
</div>
</div>
<div
class="button-container d-flex justify-content-center position-absolute"
>
<a
*ngIf="showPositionsButton"
[routerLink]="['/zen']"
fragment="positions-container"
i18n
mat-flat-button
(click)="showPositionsButton = false"
>Positions</a
>
</div>
</div>
<div class="overview-container row mb-5 mt-1">
<div class="col">
<gf-portfolio-performance-summary
class="pb-4"
[baseCurrency]="user?.settings?.baseCurrency"
[isLoading]="isLoadingPerformance"
[locale]="user?.settings?.locale"
[performance]="performance"
[showDetails]="!hasImpersonationId || hasPermissionToReadForeignPortfolio"
></gf-portfolio-performance-summary>
<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>
</div>
</ng-container>
<div
*ngIf="!hasPositions && historicalDataItems"
class="d-flex justify-content-center my-5"
>
<gf-no-transactions-info-indicator></gf-no-transactions-info-indicator>
</div>
<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 { 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 { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.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 { GfPositionsModule } from '@ghostfolio/client/components/positions/positions.module';
import { ZenPageRoutingModule } from './zen-page-routing.module';
import { ZenPageComponent } from './zen-page.component';
@ -16,7 +19,10 @@ import { ZenPageComponent } from './zen-page.component';
GfLineChartModule,
GfNoTransactionsInfoModule,
GfPortfolioPerformanceSummaryModule,
GfPositionsModule,
MatButtonModule,
MatCardModule,
RouterModule,
ZenPageRoutingModule
],
providers: [],

@ -1,38 +1,73 @@
@import '~apps/client/src/styles/ghostfolio-style';
:host {
color: rgb(var(--dark-primary-text));
display: block;
min-height: 100vh;
.container {
&.overview {
min-height: calc(100vh - 5rem);
.button-container {
bottom: 3rem;
left: 0;
right: 0;
.chart-container {
aspect-ratio: 16 / 9;
margin-top: 3rem;
max-height: 50vh;
// Fallback for aspect-ratio (using padding hack)
@supports not (aspect-ratio: 16 / 9) {
&::before {
float: left;
padding-top: 56.25%;
content: '';
.mat-flat-button {
background-color: rgba(0, 0, 0, $alpha-hover);
border-radius: 2rem;
}
}
&::after {
display: block;
content: '';
clear: both;
.chart-container {
aspect-ratio: 16 / 9;
margin-top: 3rem;
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 {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: -1;
&.positions {
padding-top: 5rem;
min-height: calc(100vh - 5rem);
}
}
}
:host-context(.is-dark-theme) {
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>
<html lang="en">
<html class="h-100 position-relative" lang="en">
<head>
<title>Ghostfolio Open Source Wealth Management Software</title>
<base href="/" />

@ -20,15 +20,11 @@ $mat-css-light-theme-selector: '.is-light-theme';
--light-background: rgb(255, 255, 255);
}
html {
position: relative;
min-height: 100%;
}
body {
font-family: var(--font-family-sans-serif);
margin: 0;
margin-bottom: 5rem;
min-height: 100%;
a {
color: var(--dark-primary-text);

Loading…
Cancel
Save