Feature/zen mode (#110)
* Start with implementation * Refactor AuthGuard, persist displayMode in user settings * Refactor DisplayMode to ViewMode * Update changelogpull/111/head
parent
702ee956a2
commit
78a4946e8b
@ -1,7 +1,10 @@
|
|||||||
import { Currency } from '@prisma/client';
|
import { Currency, ViewMode } from '@prisma/client';
|
||||||
import { IsString } from 'class-validator';
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
export class UpdateUserSettingsDto {
|
export class UpdateUserSettingsDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
currency: Currency;
|
baseCurrency: Currency;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
viewMode: ViewMode;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||||
|
|
||||||
|
import { ZenPageComponent } from './zen-page.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: '', component: ZenPageComponent, canActivate: [AuthGuard] }
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class ZenPageRoutingModule {}
|
@ -0,0 +1,104 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||||
|
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 { TokenStorageService } from '@ghostfolio/client/services/token-storage.service';
|
||||||
|
import { PortfolioPerformance, 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-zen-page',
|
||||||
|
templateUrl: './zen-page.html',
|
||||||
|
styleUrls: ['./zen-page.scss']
|
||||||
|
})
|
||||||
|
export class ZenPageComponent implements OnDestroy, OnInit {
|
||||||
|
public dateRange: DateRange = 'max';
|
||||||
|
public deviceType: string;
|
||||||
|
public hasImpersonationId: boolean;
|
||||||
|
public hasPermissionToReadForeignPortfolio: boolean;
|
||||||
|
public historicalDataItems: LineChartItem[];
|
||||||
|
public isLoadingPerformance = true;
|
||||||
|
public performance: PortfolioPerformance;
|
||||||
|
public user: User;
|
||||||
|
|
||||||
|
private unsubscribeSubject = new Subject<void>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
public constructor(
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
private dataService: DataService,
|
||||||
|
private deviceService: DeviceDetectorService,
|
||||||
|
private impersonationStorageService: ImpersonationStorageService,
|
||||||
|
private tokenStorageService: TokenStorageService
|
||||||
|
) {
|
||||||
|
this.tokenStorageService
|
||||||
|
.onChangeHasToken()
|
||||||
|
.pipe(takeUntil(this.unsubscribeSubject))
|
||||||
|
.subscribe(() => {
|
||||||
|
this.dataService.fetchUser().subscribe((user) => {
|
||||||
|
this.user = user;
|
||||||
|
|
||||||
|
this.hasPermissionToReadForeignPortfolio = hasPermission(
|
||||||
|
user.permissions,
|
||||||
|
permissions.readForeignPortfolio
|
||||||
|
);
|
||||||
|
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the controller
|
||||||
|
*/
|
||||||
|
public ngOnInit() {
|
||||||
|
this.deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||||
|
|
||||||
|
this.impersonationStorageService
|
||||||
|
.onChangeHasImpersonation()
|
||||||
|
.subscribe((aId) => {
|
||||||
|
this.hasImpersonationId = !!aId;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ngOnDestroy() {
|
||||||
|
this.unsubscribeSubject.next();
|
||||||
|
this.unsubscribeSubject.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
private update() {
|
||||||
|
this.isLoadingPerformance = true;
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchChart({ range: this.dateRange })
|
||||||
|
.subscribe((chartData) => {
|
||||||
|
this.historicalDataItems = chartData.map((chartDataItem) => {
|
||||||
|
return {
|
||||||
|
date: chartDataItem.date,
|
||||||
|
value: chartDataItem.value
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.dataService
|
||||||
|
.fetchPortfolioPerformance({ range: this.dateRange })
|
||||||
|
.subscribe((response) => {
|
||||||
|
this.performance = response;
|
||||||
|
this.isLoadingPerformance = false;
|
||||||
|
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cd.markForCheck();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
<div 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>
|
||||||
|
</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>
|
@ -0,0 +1,23 @@
|
|||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { GfLineChartModule } from '@ghostfolio/client/components/line-chart/line-chart.module';
|
||||||
|
import { GfPortfolioPerformanceSummaryModule } from '@ghostfolio/client/components/portfolio-performance-summary/portfolio-performance-summary.module';
|
||||||
|
|
||||||
|
import { ZenPageRoutingModule } from './zen-page-routing.module';
|
||||||
|
import { ZenPageComponent } from './zen-page.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [ZenPageComponent],
|
||||||
|
exports: [],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
GfLineChartModule,
|
||||||
|
GfPortfolioPerformanceSummaryModule,
|
||||||
|
MatCardModule,
|
||||||
|
ZenPageRoutingModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||||
|
})
|
||||||
|
export class ZenPageModule {}
|
@ -0,0 +1,38 @@
|
|||||||
|
:host {
|
||||||
|
color: rgb(var(--dark-primary-text));
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:host-context(.is-dark-theme) {
|
||||||
|
color: rgb(var(--light-primary-text));
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { Currency } from '@prisma/client';
|
import { Currency, ViewMode } from '@prisma/client';
|
||||||
|
|
||||||
export interface UserSettings {
|
export interface UserSettings {
|
||||||
baseCurrency: Currency;
|
baseCurrency: Currency;
|
||||||
locale: string;
|
locale: string;
|
||||||
|
viewMode: ViewMode;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue