Compare commits
14 Commits
Author | SHA1 | Date | |
---|---|---|---|
bb9415cc15 | |||
b3baeb8a5d | |||
1f393e78f6 | |||
215f5eafa6 | |||
1916e5343d | |||
fa9863fc54 | |||
7bf48ef351 | |||
faef3606fd | |||
d0ccd4d238 | |||
51e3650790 | |||
db29e2b666 | |||
655a68a847 | |||
86296b3591 | |||
73c127f10c |
33
CHANGELOG.md
33
CHANGELOG.md
@ -5,6 +5,37 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## 1.272.0 - 2023-05-26
|
||||
|
||||
### Added
|
||||
|
||||
- Added support to set an asset profile as a benchmark
|
||||
|
||||
### Changed
|
||||
|
||||
- Decreased the density of the `@angular/material` tables
|
||||
- Improved the portfolio proportion chart component by supporting case insensitive names
|
||||
- Improved the breadcrumb navigation style in the blog post pages for mobile
|
||||
- Improved the error handling in the delete user endpoint
|
||||
- Improved the style of the _Changelog & License_ button on the about page
|
||||
- Upgraded `ionicons` from version `6.1.2` to `7.1.0`
|
||||
|
||||
## 1.271.0 - 2023-05-20
|
||||
|
||||
### Added
|
||||
|
||||
- Added the historical data and search functionality for the `FINANCIAL_MODELING_PREP` data source type
|
||||
- Added a blog post: _Unlock your Financial Potential with Ghostfolio_
|
||||
|
||||
### Changed
|
||||
|
||||
- Improved the local number formatting in the value component
|
||||
- Changed the uptime to the last 90 days on the _Open Startup_ (`/open`) page
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed the vertical alignment in the toggle component
|
||||
|
||||
## 1.270.1 - 2023-05-19
|
||||
|
||||
### Added
|
||||
@ -247,7 +278,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Changed the slide toggles to checkboxes on the account page
|
||||
- Changed the slide toggles to checkboxes in the admin control panel
|
||||
- Decreased the density of the theme
|
||||
- Increased the density of the theme
|
||||
- Migrated the style of various components to `@angular/material` `15` (mdc)
|
||||
- Upgraded `@angular/cdk` and `@angular/material` from version `15.2.5` to `15.2.6`
|
||||
- Upgraded `bull` from version `4.10.2` to `4.10.4`
|
||||
|
@ -2,23 +2,35 @@ import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interce
|
||||
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
|
||||
import {
|
||||
BenchmarkMarketDataDetails,
|
||||
BenchmarkResponse
|
||||
BenchmarkResponse,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import type { RequestWithUser } from '@ghostfolio/common/types';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpException,
|
||||
Inject,
|
||||
Param,
|
||||
Post,
|
||||
UseGuards,
|
||||
UseInterceptors
|
||||
} from '@nestjs/common';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { AuthGuard } from '@nestjs/passport';
|
||||
import { DataSource } from '@prisma/client';
|
||||
import { StatusCodes, getReasonPhrase } from 'http-status-codes';
|
||||
|
||||
import { BenchmarkService } from './benchmark.service';
|
||||
|
||||
@Controller('benchmark')
|
||||
export class BenchmarkController {
|
||||
public constructor(private readonly benchmarkService: BenchmarkService) {}
|
||||
public constructor(
|
||||
private readonly benchmarkService: BenchmarkService,
|
||||
@Inject(REQUEST) private readonly request: RequestWithUser
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@UseInterceptors(TransformDataSourceInRequestInterceptor)
|
||||
@ -45,4 +57,41 @@ export class BenchmarkController {
|
||||
symbol
|
||||
});
|
||||
}
|
||||
|
||||
@Post()
|
||||
@UseGuards(AuthGuard('jwt'))
|
||||
public async addBenchmark(@Body() { dataSource, symbol }: UniqueAsset) {
|
||||
if (
|
||||
!hasPermission(
|
||||
this.request.user.permissions,
|
||||
permissions.accessAdminControl
|
||||
)
|
||||
) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.FORBIDDEN),
|
||||
StatusCodes.FORBIDDEN
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const benchmark = await this.benchmarkService.addBenchmark({
|
||||
dataSource,
|
||||
symbol
|
||||
});
|
||||
|
||||
if (!benchmark) {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.NOT_FOUND),
|
||||
StatusCodes.NOT_FOUND
|
||||
);
|
||||
}
|
||||
|
||||
return benchmark;
|
||||
} catch {
|
||||
throw new HttpException(
|
||||
getReasonPhrase(StatusCodes.INTERNAL_SERVER_ERROR),
|
||||
StatusCodes.INTERNAL_SERVER_ERROR
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import { SymbolModule } from '@ghostfolio/api/app/symbol/symbol.module';
|
||||
import { ConfigurationModule } from '@ghostfolio/api/services/configuration/configuration.module';
|
||||
import { DataProviderModule } from '@ghostfolio/api/services/data-provider/data-provider.module';
|
||||
import { MarketDataModule } from '@ghostfolio/api/services/market-data/market-data.module';
|
||||
import { PrismaModule } from '@ghostfolio/api/services/prisma/prisma.module';
|
||||
import { PropertyModule } from '@ghostfolio/api/services/property/property.module';
|
||||
import { SymbolProfileModule } from '@ghostfolio/api/services/symbol-profile/symbol-profile.module';
|
||||
import { Module } from '@nestjs/common';
|
||||
@ -17,6 +18,7 @@ import { BenchmarkService } from './benchmark.service';
|
||||
ConfigurationModule,
|
||||
DataProviderModule,
|
||||
MarketDataModule,
|
||||
PrismaModule,
|
||||
PropertyModule,
|
||||
RedisCacheModule,
|
||||
SymbolModule,
|
||||
|
@ -4,7 +4,15 @@ describe('BenchmarkService', () => {
|
||||
let benchmarkService: BenchmarkService;
|
||||
|
||||
beforeAll(async () => {
|
||||
benchmarkService = new BenchmarkService(null, null, null, null, null, null);
|
||||
benchmarkService = new BenchmarkService(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
);
|
||||
});
|
||||
|
||||
it('calculateChangeInPercentage', async () => {
|
||||
|
@ -2,6 +2,7 @@ import { RedisCacheService } from '@ghostfolio/api/app/redis-cache/redis-cache.s
|
||||
import { SymbolService } from '@ghostfolio/api/app/symbol/symbol.service';
|
||||
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
|
||||
import { MarketDataService } from '@ghostfolio/api/services/market-data/market-data.service';
|
||||
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service';
|
||||
import { PropertyService } from '@ghostfolio/api/services/property/property.service';
|
||||
import { SymbolProfileService } from '@ghostfolio/api/services/symbol-profile/symbol-profile.service';
|
||||
import {
|
||||
@ -11,6 +12,7 @@ import {
|
||||
import { DATE_FORMAT } from '@ghostfolio/common/helper';
|
||||
import {
|
||||
BenchmarkMarketDataDetails,
|
||||
BenchmarkProperty,
|
||||
BenchmarkResponse,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
@ -18,6 +20,7 @@ import { Injectable } from '@nestjs/common';
|
||||
import { SymbolProfile } from '@prisma/client';
|
||||
import Big from 'big.js';
|
||||
import { format } from 'date-fns';
|
||||
import { uniqBy } from 'lodash';
|
||||
import ms from 'ms';
|
||||
|
||||
@Injectable()
|
||||
@ -27,6 +30,7 @@ export class BenchmarkService {
|
||||
public constructor(
|
||||
private readonly dataProviderService: DataProviderService,
|
||||
private readonly marketDataService: MarketDataService,
|
||||
private readonly prismaService: PrismaService,
|
||||
private readonly propertyService: PropertyService,
|
||||
private readonly redisCacheService: RedisCacheService,
|
||||
private readonly symbolProfileService: SymbolProfileService,
|
||||
@ -116,9 +120,9 @@ export class BenchmarkService {
|
||||
|
||||
public async getBenchmarkAssetProfiles(): Promise<Partial<SymbolProfile>[]> {
|
||||
const symbolProfileIds: string[] = (
|
||||
((await this.propertyService.getByKey(PROPERTY_BENCHMARKS)) as {
|
||||
symbolProfileId: string;
|
||||
}[]) ?? []
|
||||
((await this.propertyService.getByKey(
|
||||
PROPERTY_BENCHMARKS
|
||||
)) as BenchmarkProperty[]) ?? []
|
||||
).map(({ symbolProfileId }) => {
|
||||
return symbolProfileId;
|
||||
});
|
||||
@ -204,6 +208,43 @@ export class BenchmarkService {
|
||||
return response;
|
||||
}
|
||||
|
||||
public async addBenchmark({
|
||||
dataSource,
|
||||
symbol
|
||||
}: UniqueAsset): Promise<Partial<SymbolProfile>> {
|
||||
const assetProfile = await this.prismaService.symbolProfile.findFirst({
|
||||
where: {
|
||||
dataSource,
|
||||
symbol
|
||||
}
|
||||
});
|
||||
|
||||
if (!assetProfile) {
|
||||
return;
|
||||
}
|
||||
|
||||
let benchmarks =
|
||||
((await this.propertyService.getByKey(
|
||||
PROPERTY_BENCHMARKS
|
||||
)) as BenchmarkProperty[]) ?? [];
|
||||
|
||||
benchmarks.push({ symbolProfileId: assetProfile.id });
|
||||
|
||||
benchmarks = uniqBy(benchmarks, 'symbolProfileId');
|
||||
|
||||
await this.propertyService.put({
|
||||
key: PROPERTY_BENCHMARKS,
|
||||
value: JSON.stringify(benchmarks)
|
||||
});
|
||||
|
||||
return {
|
||||
dataSource,
|
||||
symbol,
|
||||
id: assetProfile.id,
|
||||
name: assetProfile.name
|
||||
};
|
||||
}
|
||||
|
||||
private getMarketCondition(aPerformanceInPercent: number) {
|
||||
return aPerformanceInPercent <= -0.2 ? 'BEAR_MARKET' : 'NEUTRAL_MARKET';
|
||||
}
|
||||
|
@ -94,6 +94,13 @@ export class FrontendMiddleware implements NestMiddleware {
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/1000-stars-on-github.jpg';
|
||||
title = `Ghostfolio reaches 1’000 Stars on GitHub - ${title}`;
|
||||
} else if (
|
||||
request.path.startsWith(
|
||||
'/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio'
|
||||
)
|
||||
) {
|
||||
featureGraphicPath = 'assets/images/blog/20230520.jpg';
|
||||
title = `Unlock your Financial Potential with Ghostfolio - ${title}`;
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -17,19 +17,22 @@ import {
|
||||
ghostfolioFearAndGreedIndexDataSource
|
||||
} from '@ghostfolio/common/config';
|
||||
import {
|
||||
DATE_FORMAT,
|
||||
encodeDataSource,
|
||||
extractNumberFromString
|
||||
} from '@ghostfolio/common/helper';
|
||||
import { InfoItem } from '@ghostfolio/common/interfaces';
|
||||
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||
import { Subscription } from '@ghostfolio/common/interfaces/subscription.interface';
|
||||
import {
|
||||
InfoItem,
|
||||
Statistics,
|
||||
Subscription
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { permissions } from '@ghostfolio/common/permissions';
|
||||
import { SubscriptionOffer } from '@ghostfolio/common/types';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { JwtService } from '@nestjs/jwt';
|
||||
import * as bent from 'bent';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { subDays } from 'date-fns';
|
||||
import { format, subDays } from 'date-fns';
|
||||
|
||||
@Injectable()
|
||||
export class InfoService {
|
||||
@ -344,7 +347,10 @@ export class InfoService {
|
||||
)) as string;
|
||||
|
||||
const get = bent(
|
||||
`https://betteruptime.com/api/v2/monitors/${monitorId}/sla`,
|
||||
`https://betteruptime.com/api/v2/monitors/${monitorId}/sla?from=${format(
|
||||
subDays(new Date(), 90),
|
||||
DATE_FORMAT
|
||||
)}&to${format(new Date(), DATE_FORMAT)}`,
|
||||
'GET',
|
||||
'json',
|
||||
200,
|
||||
|
@ -4,7 +4,7 @@ import {
|
||||
DEFAULT_LANGUAGE_CODE,
|
||||
PROPERTY_STRIPE_CONFIG
|
||||
} from '@ghostfolio/common/config';
|
||||
import { Subscription as SubscriptionInterface } from '@ghostfolio/common/interfaces/subscription.interface';
|
||||
import { Subscription as SubscriptionInterface } from '@ghostfolio/common/interfaces';
|
||||
import { UserWithSettings } from '@ghostfolio/common/types';
|
||||
import { SubscriptionType } from '@ghostfolio/common/types/subscription-type.type';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
@ -304,21 +304,29 @@ export class UserService {
|
||||
}
|
||||
|
||||
public async deleteUser(where: Prisma.UserWhereUniqueInput): Promise<User> {
|
||||
await this.prismaService.access.deleteMany({
|
||||
where: { OR: [{ granteeUserId: where.id }, { userId: where.id }] }
|
||||
});
|
||||
try {
|
||||
await this.prismaService.access.deleteMany({
|
||||
where: { OR: [{ granteeUserId: where.id }, { userId: where.id }] }
|
||||
});
|
||||
} catch {}
|
||||
|
||||
await this.prismaService.account.deleteMany({
|
||||
where: { userId: where.id }
|
||||
});
|
||||
try {
|
||||
await this.prismaService.account.deleteMany({
|
||||
where: { userId: where.id }
|
||||
});
|
||||
} catch {}
|
||||
|
||||
await this.prismaService.analytics.delete({
|
||||
where: { userId: where.id }
|
||||
});
|
||||
try {
|
||||
await this.prismaService.analytics.delete({
|
||||
where: { userId: where.id }
|
||||
});
|
||||
} catch {}
|
||||
|
||||
await this.prismaService.order.deleteMany({
|
||||
where: { userId: where.id }
|
||||
});
|
||||
try {
|
||||
await this.prismaService.order.deleteMany({
|
||||
where: { userId: where.id }
|
||||
});
|
||||
} catch {}
|
||||
|
||||
try {
|
||||
await this.prismaService.settings.delete({
|
||||
|
@ -5,11 +5,13 @@ import {
|
||||
IDataProviderHistoricalResponse,
|
||||
IDataProviderResponse
|
||||
} from '@ghostfolio/api/services/interfaces/interfaces';
|
||||
import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
|
||||
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
|
||||
import { Granularity } from '@ghostfolio/common/types';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { DataSource, SymbolProfile } from '@prisma/client';
|
||||
import bent from 'bent';
|
||||
import { format, isAfter, isBefore, isSameDay } from 'date-fns';
|
||||
|
||||
@Injectable()
|
||||
export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
@ -61,9 +63,42 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
): Promise<{
|
||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||
}> {
|
||||
return {
|
||||
[aSymbol]: {}
|
||||
};
|
||||
try {
|
||||
const get = bent(
|
||||
`${this.URL}/historical-price-full/${aSymbol}?apikey=${this.apiKey}`,
|
||||
'GET',
|
||||
'json',
|
||||
200
|
||||
);
|
||||
const { historical } = await get();
|
||||
|
||||
const result: {
|
||||
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
|
||||
} = {
|
||||
[aSymbol]: {}
|
||||
};
|
||||
|
||||
for (const { close, date } of historical) {
|
||||
if (
|
||||
(isSameDay(parseDate(date), from) ||
|
||||
isAfter(parseDate(date), from)) &&
|
||||
isBefore(parseDate(date), to)
|
||||
) {
|
||||
result[aSymbol][date] = {
|
||||
marketPrice: close
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Could not get historical market data for ${aSymbol} (${this.getName()}) from ${format(
|
||||
from,
|
||||
DATE_FORMAT
|
||||
)} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public getName(): DataSource {
|
||||
@ -109,7 +144,32 @@ export class FinancialModelingPrepService implements DataProviderInterface {
|
||||
}
|
||||
|
||||
public async search(aQuery: string): Promise<{ items: LookupItem[] }> {
|
||||
return { items: [] };
|
||||
let items: LookupItem[] = [];
|
||||
|
||||
try {
|
||||
const get = bent(
|
||||
`${this.URL}/search?query=${aQuery}&apikey=${this.apiKey}`,
|
||||
'GET',
|
||||
'json',
|
||||
200
|
||||
);
|
||||
const result = await get();
|
||||
|
||||
items = result.map(({ currency, name, symbol }) => {
|
||||
return {
|
||||
// TODO: Add assetClass
|
||||
// TODO: Add assetSubClass
|
||||
currency,
|
||||
name,
|
||||
symbol,
|
||||
dataSource: this.getName()
|
||||
};
|
||||
});
|
||||
} catch (error) {
|
||||
Logger.error(error, 'FinancialModelingPrepService');
|
||||
}
|
||||
|
||||
return { items };
|
||||
}
|
||||
|
||||
private getDataProviderInfo(): DataProviderInfo {
|
||||
|
@ -138,8 +138,7 @@ export class YahooFinanceService implements DataProviderInterface {
|
||||
marketPrice: this.getConvertedValue({
|
||||
symbol: aSymbol,
|
||||
value: historicalItem.close
|
||||
}),
|
||||
performance: historicalItem.open - historicalItem.close
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,6 @@ export interface IOrder {
|
||||
|
||||
export interface IDataProviderHistoricalResponse {
|
||||
marketPrice: number;
|
||||
performance?: number;
|
||||
}
|
||||
|
||||
export interface IDataProviderResponse {
|
||||
|
@ -137,6 +137,13 @@ const routes: Routes = [
|
||||
'./pages/blog/2023/03/1000-stars-on-github/1000-stars-on-github-page.module'
|
||||
).then((m) => m.ThousandStarsOnGitHubPageModule)
|
||||
},
|
||||
{
|
||||
path: 'blog/2023/05/unlock-your-financial-potential-with-ghostfolio',
|
||||
loadChildren: () =>
|
||||
import(
|
||||
'./pages/blog/2023/05/unlock-your-financial-potential-with-ghostfolio/unlock-your-financial-potential-with-ghostfolio-page.module'
|
||||
).then((m) => m.UnlockYourFinancialPotentialWithGhostfolioPageModule)
|
||||
},
|
||||
{
|
||||
path: 'demo',
|
||||
loadChildren: () =>
|
||||
|
@ -143,18 +143,6 @@
|
||||
<ion-icon name="ellipsis-vertical"></ion-icon>
|
||||
</button>
|
||||
<mat-menu #assetProfileActionsMenu="matMenu" xPosition="before">
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onGatherSymbol({dataSource: element.dataSource, symbol: element.symbol})"
|
||||
>
|
||||
<ng-container i18n>Gather Historical Data</ng-container>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
(click)="onGatherProfileDataBySymbol({dataSource: element.dataSource, symbol: element.symbol})"
|
||||
>
|
||||
<ng-container i18n>Gather Profile Data</ng-container>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="element.activitiesCount !== 0"
|
||||
|
@ -10,13 +10,13 @@ import { FormBuilder } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { UpdateAssetProfileDto } from '@ghostfolio/api/app/admin/update-asset-profile.dto';
|
||||
import { AdminService } from '@ghostfolio/client/services/admin.service';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import {
|
||||
AdminMarketDataDetails,
|
||||
EnhancedSymbolProfile,
|
||||
UniqueAsset
|
||||
} from '@ghostfolio/common/interfaces';
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
import { MarketData } from '@prisma/client';
|
||||
import { MarketData, SymbolProfile } from '@prisma/client';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
||||
@ -37,9 +37,11 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
symbolMapping: ''
|
||||
});
|
||||
public assetSubClass: string;
|
||||
public benchmarks: Partial<SymbolProfile>[];
|
||||
public countries: {
|
||||
[code: string]: { name: string; value: number };
|
||||
};
|
||||
public isBenchmark = false;
|
||||
public marketDataDetails: MarketData[] = [];
|
||||
public sectors: {
|
||||
[name: string]: { name: string; value: number };
|
||||
@ -51,11 +53,14 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
private adminService: AdminService,
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
@Inject(MAT_DIALOG_DATA) public data: AssetProfileDialogParams,
|
||||
private dataService: DataService,
|
||||
public dialogRef: MatDialogRef<AssetProfileDialog>,
|
||||
private formBuilder: FormBuilder
|
||||
) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.benchmarks = this.dataService.fetchInfo().benchmarks;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
|
||||
@ -72,6 +77,9 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
this.assetClass = translate(this.assetProfile?.assetClass);
|
||||
this.assetSubClass = translate(this.assetProfile?.assetSubClass);
|
||||
this.countries = {};
|
||||
this.isBenchmark = this.benchmarks.some(({ id }) => {
|
||||
return id === this.assetProfile.id;
|
||||
});
|
||||
this.marketDataDetails = marketData;
|
||||
this.sectors = {};
|
||||
|
||||
@ -128,6 +136,17 @@ export class AssetProfileDialog implements OnDestroy, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public onSetBenchmark({ dataSource, symbol }: UniqueAsset) {
|
||||
this.dataService
|
||||
.postBenchmark({ dataSource, symbol })
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe(() => {
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
});
|
||||
}
|
||||
|
||||
public onSubmit() {
|
||||
let symbolMapping = {};
|
||||
|
||||
|
@ -37,6 +37,13 @@
|
||||
>
|
||||
<ng-container i18n>Gather Profile Data</ng-container>
|
||||
</button>
|
||||
<button
|
||||
mat-menu-item
|
||||
[disabled]="isBenchmark"
|
||||
(click)="onSetBenchmark({dataSource: data.dataSource, symbol: data.symbol})"
|
||||
>
|
||||
<ng-container i18n>Set as Benchmark</ng-container>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</div>
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
<mat-radio-group
|
||||
class="text-nowrap"
|
||||
class="d-block text-nowrap"
|
||||
[formControl]="option"
|
||||
(change)="onValueChange()"
|
||||
>
|
||||
<mat-radio-button
|
||||
*ngFor="let option of options"
|
||||
class="d-inline-flex"
|
||||
[disabled]="isLoading"
|
||||
[ngClass]="{ 'cursor-pointer': !isLoading }"
|
||||
[value]="option.value"
|
||||
|
@ -3,8 +3,7 @@ import { environment } from '@ghostfolio/client/../environments/environment';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { DEFAULT_LANGUAGE_CODE } from '@ghostfolio/common/config';
|
||||
import { User } from '@ghostfolio/common/interfaces';
|
||||
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||
import { Statistics, User } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
|
@ -6,12 +6,26 @@
|
||||
<p>
|
||||
Ghostfolio is a lightweight wealth management application for
|
||||
individuals to keep track of stocks, ETFs or cryptocurrencies and make
|
||||
solid, data-driven investment decisions. We share aggregated
|
||||
<a href="https://ghostfol.io/{{ defaultLanguageCode }}/open"
|
||||
solid, data-driven investment decisions. The source code is fully
|
||||
available as
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>open source software</a
|
||||
>
|
||||
(OSS) under the
|
||||
<a
|
||||
href="https://www.gnu.org/licenses/agpl-3.0.html"
|
||||
title="GNU Affero General Public License"
|
||||
>AGPL-3.0 license</a
|
||||
>
|
||||
and we share aggregated
|
||||
<a
|
||||
href="https://ghostfol.io/{{ defaultLanguageCode }}/open"
|
||||
title="Open Startup"
|
||||
>key metrics</a
|
||||
>
|
||||
of our platform’s performance and the source code is fully available
|
||||
as open source software (OSS). The project has been initiated by
|
||||
of the platform’s performance. The project has been initiated by
|
||||
<a href="https://dotsilver.ch" title="Website of Thomas Kaul"
|
||||
>Thomas Kaul</a
|
||||
>
|
||||
@ -130,6 +144,7 @@
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 24 hours)"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.activeUsers1d ?? '-'"
|
||||
>Active Users</gf-value
|
||||
>
|
||||
@ -138,6 +153,7 @@
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 30 days)"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.newUsers30d ?? '-'"
|
||||
>New Users</gf-value
|
||||
>
|
||||
@ -146,6 +162,7 @@
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 30 days)"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.activeUsers30d ?? '-'"
|
||||
>Active Users</gf-value
|
||||
>
|
||||
@ -154,6 +171,7 @@
|
||||
<a class="d-block" href="https://ghostfolio.slack.com">
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.slackCommunityUsers ?? '-'"
|
||||
>Users in Slack community</gf-value
|
||||
>
|
||||
@ -166,6 +184,7 @@
|
||||
>
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.gitHubContributors ?? '-'"
|
||||
>Contributors on GitHub</gf-value
|
||||
>
|
||||
@ -178,6 +197,7 @@
|
||||
>
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.gitHubStargazers ?? '-'"
|
||||
>Stars on GitHub</gf-value
|
||||
>
|
||||
@ -201,7 +221,7 @@
|
||||
</div>
|
||||
<div
|
||||
class="col-md-3 col-xs-12 my-2"
|
||||
[ngClass]="{ 'offset-md-4': !hasPermissionForBlog }"
|
||||
[ngClass]="{ 'mx-auto': !hasPermissionForBlog }"
|
||||
>
|
||||
<a
|
||||
class="py-4 w-100"
|
||||
|
@ -202,7 +202,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Hallo Ghostfolio
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -182,7 +182,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Hello Ghostfolio
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -179,7 +179,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Ghostfolio: First months in Open Source
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -182,7 +182,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Ghostfolio meets Internet Identity
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -208,7 +208,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
How do I get my finances in order?
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -191,7 +191,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
500 Stars on GitHub
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -177,7 +177,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Hacktoberfest 2022
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -137,7 +137,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Black Friday 2022
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -167,7 +167,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
The importance of tracking your personal finances
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -177,7 +177,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Ghostfolio auf Sackgeld.com vorgestellt
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -199,7 +199,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Ghostfolio meets Umbrel
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -244,7 +244,10 @@
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li aria-current="page" class="breadcrumb-item active">
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Ghostfolio reaches 1’000 Stars on GitHub
|
||||
</li>
|
||||
</ol>
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
|
||||
|
||||
import { UnlockYourFinancialPotentialWithGhostfolioPageComponent } from './unlock-your-financial-potential-with-ghostfolio-page.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
canActivate: [AuthGuard],
|
||||
component: UnlockYourFinancialPotentialWithGhostfolioPageComponent,
|
||||
path: '',
|
||||
title: 'Unlock your Financial Potential with Ghostfolio'
|
||||
}
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule]
|
||||
})
|
||||
export class UnlockYourFinancialPotentialWithGhostfolioRoutingModule {}
|
@ -0,0 +1,9 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
selector: 'gf-unlock-your-financial-potential-with-ghostfolio-page',
|
||||
styleUrls: ['./unlock-your-financial-potential-with-ghostfolio-page.scss'],
|
||||
templateUrl: './unlock-your-financial-potential-with-ghostfolio-page.html'
|
||||
})
|
||||
export class UnlockYourFinancialPotentialWithGhostfolioPageComponent {}
|
@ -0,0 +1,244 @@
|
||||
<div class="blog container">
|
||||
<div class="row">
|
||||
<div class="col-md-8 offset-md-2">
|
||||
<article>
|
||||
<div class="mb-4 text-center">
|
||||
<h1 class="mb-1">Unlock your Financial Potential with Ghostfolio</h1>
|
||||
<div class="mb-3 text-muted"><small>2023-05-20</small></div>
|
||||
<img
|
||||
alt="Unlock your financial potential with Ghostfolio Teaser"
|
||||
class="border rounded w-100"
|
||||
src="../assets/images/blog/20230520.jpg"
|
||||
title="Unlock your financial potential with Ghostfolio"
|
||||
/>
|
||||
</div>
|
||||
<section class="mb-4">
|
||||
<p>
|
||||
Managing personal finances effectively is crucial for those striving
|
||||
for a secure future and financial independence. In today’s digital
|
||||
age, having a reliable wealth management software can greatly
|
||||
simplify the process. Ghostfolio is a powerful
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>open source solution</a
|
||||
>
|
||||
for individuals trading stocks, ETFs, or cryptocurrencies on
|
||||
multiple platforms. This article explores the key reasons why
|
||||
Ghostfolio is the ideal choice for those embracing diversification,
|
||||
pursuing a buy & hold strategy, and seeking portfolio insights while
|
||||
valuing privacy.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Effortless Management for Multi-Platform Investors</h2>
|
||||
<p>
|
||||
Ghostfolio offers a holistic solution to efficiently monitor and
|
||||
manage investment portfolios across multiple platforms. By
|
||||
consolidating data from various accounts, Ghostfolio eliminates the
|
||||
need to switch between platforms, saving users valuable time and
|
||||
effort.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Empowering Buy & Hold Strategies</h2>
|
||||
<p>
|
||||
For those committed to a
|
||||
<a [routerLink]="['/resources']">buy & hold strategy</a>, Ghostfolio
|
||||
provides an intuitive interface to monitor long-term investments.
|
||||
Users can track performance over time, gaining insights into
|
||||
portfolio growth and stability. With strong visualizations and
|
||||
reporting <a [routerLink]="['/features']">features</a>, Ghostfolio
|
||||
equips users to make well-informed decisions aligned with their
|
||||
long-term investment goals.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Deep Portfolio Insights</h2>
|
||||
<p>
|
||||
Understanding portfolio composition is vital for making informed
|
||||
financial decisions. Ghostfolio provides comprehensive insights into
|
||||
asset allocation, sector exposure, geographical diversification, and
|
||||
individual asset performance. These detailed analytics empower users
|
||||
to assess portfolio strengths and weaknesses, making necessary
|
||||
adjustments to optimize their allocation.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Privacy and Data Ownership</h2>
|
||||
<p>
|
||||
In the age of growing data security concerns, Ghostfolio sets itself
|
||||
apart by giving the highest priority to privacy and data ownership.
|
||||
As an open-source software, Ghostfolio ensures that users retain
|
||||
complete control over their financial data. By eliminating the need
|
||||
to trust third-party platforms with sensitive information,
|
||||
Ghostfolio offers peace of mind to those who value privacy and data
|
||||
security.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Streamlined Minimalism for Financial Efficiency</h2>
|
||||
<p>
|
||||
Ghostfolio embraces a lightweight approach to personal finance
|
||||
management, focusing on essential features without overwhelming
|
||||
users. Its streamlined user interface and clean design provide a
|
||||
seamless and clutter-free experience. This minimalist approach
|
||||
enhances user satisfaction and boosts efficiency by eliminating
|
||||
distractions and simplifying the financial management process.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Driving Financial Independence (FIRE)</h2>
|
||||
<p>
|
||||
Achieving
|
||||
<a [routerLink]="['/resources']">financial independence</a>
|
||||
including early retirement (FIRE) requires careful planning,
|
||||
monitoring, and forecasting. Ghostfolio’s robust features equip
|
||||
individuals with tools to analyze, optimize and simulate investment
|
||||
strategies. By providing insights, performance tracking, and
|
||||
portfolio analysis, Ghostfolio serves as a valuable companion in the
|
||||
pursuit of financial freedom.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Farewell to Spreadsheet Hassles</h2>
|
||||
<p>
|
||||
While spreadsheets have traditionally been used to manage personal
|
||||
finances, they can be time-consuming and prone to errors. Ghostfolio
|
||||
offers a user-friendly alternative by automating data aggregation,
|
||||
analysis, and reporting. Users can bid farewell to manual data entry
|
||||
and complex formulas, relying instead on Ghostfolio’s user-friendly
|
||||
and intuitive interface to efficiently manage their finances.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<h2 class="h4">Your Path to Financial Success with Ghostfolio</h2>
|
||||
<p>
|
||||
Ghostfolio, the open-source personal finance software, provides a
|
||||
wide range of benefits for individuals involved in trading stocks,
|
||||
ETFs, or cryptocurrencies. Whether you are pursuing a buy & hold
|
||||
strategy, seeking valuable portfolio insights, or diversifying
|
||||
financial resources while prioritizing privacy and data ownership,
|
||||
Ghostfolio proves to be an invaluable tool on your journey towards
|
||||
unlocking your financial potential. Say goodbye to spreadsheets and
|
||||
embrace the power of Ghostfolio for simplified, secure, and
|
||||
successful financial management.
|
||||
</p>
|
||||
</section>
|
||||
<section class="mb-4 py-3">
|
||||
<h2 class="h4 mb-0 text-center">
|
||||
Would you like to <strong>unlock</strong> your
|
||||
<strong>financial potential</strong>?
|
||||
</h2>
|
||||
<p class="lead mb-2 text-center">
|
||||
Ghostfolio empowers you to manage your personal finances
|
||||
effectively.
|
||||
</p>
|
||||
<div class="text-center">
|
||||
<a color="primary" href="https://ghostfol.io" mat-flat-button>
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
<section class="mb-4">
|
||||
<ul class="list-inline">
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">App</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Analysis</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Assets</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Budgeting</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Buy & Hold</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Cryptocurrencies</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Diversification</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">ETFs</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Finance</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Fintech</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">FIRE</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Ghostfolio</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Investment</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Management</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Minimalism</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Monitoring</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Open Source</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Personal Finance</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Planning</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Portfolio Tracker</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Privacy</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Retirement</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Software</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Spreadsheet</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Stock</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Strategy</span>
|
||||
</li>
|
||||
<li class="list-inline-item">
|
||||
<span class="badge badge-light">Wealth</span>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item">
|
||||
<a i18n [routerLink]="['/blog']">Blog</a>
|
||||
</li>
|
||||
<li
|
||||
aria-current="page"
|
||||
class="active breadcrumb-item text-truncate"
|
||||
>
|
||||
Unlock your Financial Potential with Ghostfolio
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { RouterModule } from '@angular/router';
|
||||
|
||||
import { UnlockYourFinancialPotentialWithGhostfolioRoutingModule } from './unlock-your-financial-potential-with-ghostfolio-page-routing.module';
|
||||
import { UnlockYourFinancialPotentialWithGhostfolioPageComponent } from './unlock-your-financial-potential-with-ghostfolio-page.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [UnlockYourFinancialPotentialWithGhostfolioPageComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatButtonModule,
|
||||
RouterModule,
|
||||
UnlockYourFinancialPotentialWithGhostfolioRoutingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
export class UnlockYourFinancialPotentialWithGhostfolioPageModule {}
|
@ -0,0 +1,3 @@
|
||||
:host {
|
||||
display: block;
|
||||
}
|
@ -2,6 +2,32 @@
|
||||
<div class="mb-5 row">
|
||||
<div class="col">
|
||||
<h3 class="d-none d-sm-block mb-3 text-center" i18n>Blog</h3>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
<div class="flex-nowrap no-gutters row">
|
||||
<a
|
||||
class="d-flex overflow-hidden w-100"
|
||||
href="../en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio"
|
||||
>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div class="h6 m-0 text-truncate">
|
||||
Unlock your Financial Potential with Ghostfolio
|
||||
</div>
|
||||
<div class="d-flex text-muted">2023-05-20</div>
|
||||
</div>
|
||||
<div class="align-items-center d-flex">
|
||||
<ion-icon
|
||||
class="chevron text-muted"
|
||||
name="chevron-forward-outline"
|
||||
size="small"
|
||||
></ion-icon>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card appearance="outlined" class="mb-3">
|
||||
<mat-card-content>
|
||||
<div class="container p-0">
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||
import { Statistics } from '@ghostfolio/common/interfaces';
|
||||
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
|
||||
import { format } from 'date-fns';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ChangeDetectorRef, Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { DataService } from '@ghostfolio/client/services/data.service';
|
||||
import { Statistics } from '@ghostfolio/common/interfaces/statistics.interface';
|
||||
import { Subject } from 'rxjs';
|
||||
import { UserService } from '@ghostfolio/client/services/user/user.service';
|
||||
import { Statistics, User } from '@ghostfolio/common/interfaces';
|
||||
import { Subject, takeUntil } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
host: { class: 'page' },
|
||||
@ -11,16 +12,31 @@ import { Subject } from 'rxjs';
|
||||
})
|
||||
export class OpenPageComponent implements OnDestroy, OnInit {
|
||||
public statistics: Statistics;
|
||||
public user: User;
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor(private dataService: DataService) {
|
||||
public constructor(
|
||||
private changeDetectorRef: ChangeDetectorRef,
|
||||
private dataService: DataService,
|
||||
private userService: UserService
|
||||
) {
|
||||
const { statistics } = this.dataService.fetchInfo();
|
||||
|
||||
this.statistics = statistics;
|
||||
}
|
||||
|
||||
public ngOnInit() {}
|
||||
public ngOnInit() {
|
||||
this.userService.stateChanged
|
||||
.pipe(takeUntil(this.unsubscribeSubject))
|
||||
.subscribe((state) => {
|
||||
if (state?.user) {
|
||||
this.user = state.user;
|
||||
|
||||
this.changeDetectorRef.markForCheck();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
this.unsubscribeSubject.next();
|
||||
|
@ -4,15 +4,21 @@
|
||||
<h3 class="d-none d-sm-block mb-3 text-center">Open Startup</h3>
|
||||
<div class="intro-container">
|
||||
<p>
|
||||
At Ghostfolio, transparency is at the core of our values. We openly
|
||||
share aggregated key metrics of our platform’s performance and publish
|
||||
At Ghostfolio, transparency is at the core of our values. We publish
|
||||
the source code as
|
||||
<a
|
||||
href="https://github.com/ghostfolio/ghostfolio"
|
||||
title="Contributors to Ghostfolio"
|
||||
title="Find Ghostfolio on GitHub"
|
||||
>open source software</a
|
||||
>
|
||||
(OSS).
|
||||
(OSS) under the
|
||||
<a
|
||||
href="https://www.gnu.org/licenses/agpl-3.0.html"
|
||||
title="GNU Affero General Public License"
|
||||
>AGPL-3.0 license</a
|
||||
>
|
||||
and we openly share aggregated key metrics of the platform’s
|
||||
performance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@ -27,6 +33,7 @@
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 24 hours)"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.activeUsers1d ?? '-'"
|
||||
>Active Users</gf-value
|
||||
>
|
||||
@ -35,6 +42,7 @@
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 30 days)"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.newUsers30d ?? '-'"
|
||||
>New Users</gf-value
|
||||
>
|
||||
@ -43,6 +51,7 @@
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 30 days)"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.activeUsers30d ?? '-'"
|
||||
>Active Users</gf-value
|
||||
>
|
||||
@ -51,6 +60,7 @@
|
||||
<a class="d-block" href="https://ghostfolio.slack.com">
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.slackCommunityUsers ?? '-'"
|
||||
>Users in Slack community</gf-value
|
||||
>
|
||||
@ -63,6 +73,7 @@
|
||||
>
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.gitHubContributors ?? '-'"
|
||||
>Contributors on GitHub</gf-value
|
||||
>
|
||||
@ -75,6 +86,7 @@
|
||||
>
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.gitHubStargazers ?? '-'"
|
||||
>Stars on GitHub</gf-value
|
||||
>
|
||||
@ -87,6 +99,7 @@
|
||||
>
|
||||
<gf-value
|
||||
size="large"
|
||||
[locale]="user?.settings?.locale"
|
||||
[value]="statistics?.dockerHubPulls ?? '-'"
|
||||
>Pulls on Docker Hub</gf-value
|
||||
>
|
||||
@ -96,7 +109,9 @@
|
||||
<a class="d-block" href="https://status.ghostfol.io">
|
||||
<gf-value
|
||||
size="large"
|
||||
subLabel="(Last 90 days)"
|
||||
[isPercent]="true"
|
||||
[locale]="user?.settings?.locale"
|
||||
[precision]="2"
|
||||
[value]="statistics?.uptime ?? '-'"
|
||||
>Uptime</gf-value
|
||||
|
@ -405,6 +405,10 @@ export class DataService {
|
||||
return this.http.post<OrderModel>(`/api/v1/account`, aAccount);
|
||||
}
|
||||
|
||||
public postBenchmark(benchmark: UniqueAsset) {
|
||||
return this.http.post(`/api/v1/benchmark`, benchmark);
|
||||
}
|
||||
|
||||
public postOrder(aOrder: CreateOrderDto) {
|
||||
return this.http.post<OrderModel>(`/api/v1/order`, aOrder);
|
||||
}
|
||||
|
BIN
apps/client/src/assets/images/blog/20230520.jpg
Normal file
BIN
apps/client/src/assets/images/blog/20230520.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
@ -6,106 +6,110 @@
|
||||
http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
|
||||
<url>
|
||||
<loc>https://ghostfol.io</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/de/blog</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/de/blog/2021/07/hallo-ghostfolio</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/de/blog/2023/01/ghostfolio-auf-sackgeld-vorgestellt</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/de/pricing</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/about</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/about/changelog</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2021/07/hello-ghostfolio</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/01/ghostfolio-first-months-in-open-source</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/07/ghostfolio-meets-internet-identity</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/07/how-do-i-get-my-finances-in-order</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/08/500-stars-on-github</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/10/hacktoberfest-2022</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/11/black-friday-2022</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2022/12/the-importance-of-tracking-your-personal-finances</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2023/02/ghostfolio-meets-umbrel</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2023/03/ghostfolio-reaches-1000-stars-on-github</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/blog/2023/05/unlock-your-financial-potential-with-ghostfolio</loc>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/demo</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/faq</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/features</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/markets</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/open</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/pricing</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/register</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://ghostfol.io/en/resources</loc>
|
||||
<lastmod>2023-05-15T00:00:00+00:00</lastmod>
|
||||
<lastmod>2023-05-20T00:00:00+00:00</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
|
@ -327,10 +327,13 @@ ngx-skeleton-loader {
|
||||
|
||||
.breadcrumb {
|
||||
background-color: unset;
|
||||
flex-wrap: nowrap;
|
||||
padding: unset;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
flex-wrap: nowrap;
|
||||
|
||||
&.active {
|
||||
color: unset;
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ $gf-theme-default: mat.define-light-theme(
|
||||
);
|
||||
@include mat.all-component-themes($gf-theme-default);
|
||||
@include mat.button-density(0);
|
||||
@include mat.table-density(-1);
|
||||
|
||||
// Create dark theme
|
||||
$gf-theme-dark: mat.define-dark-theme(
|
||||
@ -101,6 +102,7 @@ $gf-theme-dark: mat.define-dark-theme(
|
||||
.is-dark-theme {
|
||||
@include mat.all-component-colors($gf-theme-dark);
|
||||
@include mat.button-density(0);
|
||||
@include mat.table-density(-1);
|
||||
}
|
||||
|
||||
:root {
|
||||
|
@ -0,0 +1,3 @@
|
||||
export interface BenchmarkProperty {
|
||||
symbolProfileId: string;
|
||||
}
|
@ -8,6 +8,7 @@ import {
|
||||
AdminMarketDataItem
|
||||
} from './admin-market-data.interface';
|
||||
import { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
|
||||
import { BenchmarkProperty } from './benchmark-property.interface';
|
||||
import { Benchmark } from './benchmark.interface';
|
||||
import { Coupon } from './coupon.interface';
|
||||
import { DataProviderInfo } from './data-provider-info.interface';
|
||||
@ -37,6 +38,8 @@ import { ImportResponse } from './responses/import-response.interface';
|
||||
import { OAuthResponse } from './responses/oauth-response.interface';
|
||||
import { PortfolioPerformanceResponse } from './responses/portfolio-performance-response.interface';
|
||||
import { ScraperConfiguration } from './scraper-configuration.interface';
|
||||
import { Statistics } from './statistics.interface';
|
||||
import { Subscription } from './subscription.interface';
|
||||
import { TimelinePosition } from './timeline-position.interface';
|
||||
import { UniqueAsset } from './unique-asset.interface';
|
||||
import { UserSettings } from './user-settings.interface';
|
||||
@ -52,6 +55,7 @@ export {
|
||||
AdminMarketDataItem,
|
||||
Benchmark,
|
||||
BenchmarkMarketDataDetails,
|
||||
BenchmarkProperty,
|
||||
BenchmarkResponse,
|
||||
Coupon,
|
||||
DataProviderInfo,
|
||||
@ -80,6 +84,8 @@ export {
|
||||
Position,
|
||||
ResponseError,
|
||||
ScraperConfiguration,
|
||||
Statistics,
|
||||
Subscription,
|
||||
TimelinePosition,
|
||||
UniqueAsset,
|
||||
User,
|
||||
|
@ -100,38 +100,42 @@ export class PortfolioProportionChartComponent
|
||||
};
|
||||
|
||||
Object.keys(this.positions).forEach((symbol) => {
|
||||
if (this.positions[symbol][this.keys[0]]) {
|
||||
if (chartData[this.positions[symbol][this.keys[0]]]) {
|
||||
chartData[this.positions[symbol][this.keys[0]]].value = chartData[
|
||||
this.positions[symbol][this.keys[0]]
|
||||
].value.plus(this.positions[symbol].value);
|
||||
if (this.positions[symbol][this.keys[0]].toUpperCase()) {
|
||||
if (chartData[this.positions[symbol][this.keys[0]].toUpperCase()]) {
|
||||
chartData[this.positions[symbol][this.keys[0]].toUpperCase()].value =
|
||||
chartData[
|
||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||
].value.plus(this.positions[symbol].value);
|
||||
|
||||
if (
|
||||
chartData[this.positions[symbol][this.keys[0]]].subCategory[
|
||||
this.positions[symbol][this.keys[1]]
|
||||
]
|
||||
chartData[this.positions[symbol][this.keys[0]].toUpperCase()]
|
||||
.subCategory[this.positions[symbol][this.keys[1]]]
|
||||
) {
|
||||
chartData[this.positions[symbol][this.keys[0]]].subCategory[
|
||||
this.positions[symbol][this.keys[1]]
|
||||
].value = chartData[
|
||||
this.positions[symbol][this.keys[0]]
|
||||
].subCategory[this.positions[symbol][this.keys[1]]].value.plus(
|
||||
this.positions[symbol].value
|
||||
);
|
||||
chartData[
|
||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||
].subCategory[this.positions[symbol][this.keys[1]]].value =
|
||||
chartData[
|
||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||
].subCategory[this.positions[symbol][this.keys[1]]].value.plus(
|
||||
this.positions[symbol].value
|
||||
);
|
||||
} else {
|
||||
chartData[this.positions[symbol][this.keys[0]]].subCategory[
|
||||
this.positions[symbol][this.keys[1]] ?? UNKNOWN_KEY
|
||||
] = { value: new Big(this.positions[symbol].value) };
|
||||
chartData[
|
||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||
].subCategory[this.positions[symbol][this.keys[1]] ?? UNKNOWN_KEY] =
|
||||
{ value: new Big(this.positions[symbol].value) };
|
||||
}
|
||||
} else {
|
||||
chartData[this.positions[symbol][this.keys[0]]] = {
|
||||
name: this.positions[symbol].name,
|
||||
chartData[this.positions[symbol][this.keys[0]].toUpperCase()] = {
|
||||
name: this.positions[symbol][this.keys[0]],
|
||||
subCategory: {},
|
||||
value: new Big(this.positions[symbol].value ?? 0)
|
||||
};
|
||||
|
||||
if (this.positions[symbol][this.keys[1]]) {
|
||||
chartData[this.positions[symbol][this.keys[0]]].subCategory = {
|
||||
chartData[
|
||||
this.positions[symbol][this.keys[0]].toUpperCase()
|
||||
].subCategory = {
|
||||
[this.positions[symbol][this.keys[1]]]: {
|
||||
value: new Big(this.positions[symbol].value)
|
||||
}
|
||||
@ -232,8 +236,8 @@ export class PortfolioProportionChartComponent
|
||||
}
|
||||
];
|
||||
|
||||
let labels = chartDataSorted.map(([label]) => {
|
||||
return label;
|
||||
let labels = chartDataSorted.map(([symbol, { name }]) => {
|
||||
return name;
|
||||
});
|
||||
|
||||
if (this.keys[1]) {
|
||||
|
@ -21,7 +21,7 @@ export class ValueComponent implements OnChanges {
|
||||
@Input() isCurrency = false;
|
||||
@Input() isDate = false;
|
||||
@Input() isPercent = false;
|
||||
@Input() locale = getLocale();
|
||||
@Input() locale: string | undefined;
|
||||
@Input() position = '';
|
||||
@Input() precision: number | undefined;
|
||||
@Input() size: 'large' | 'medium' | 'small' = 'small';
|
||||
@ -92,7 +92,7 @@ export class ValueComponent implements OnChanges {
|
||||
});
|
||||
} catch {}
|
||||
} else {
|
||||
this.formattedValue = this.value?.toString();
|
||||
this.formattedValue = this.value?.toLocaleString(this.locale);
|
||||
}
|
||||
|
||||
if (this.isAbsolute) {
|
||||
@ -128,6 +128,11 @@ export class ValueComponent implements OnChanges {
|
||||
this.formattedValue = '';
|
||||
this.isNumber = false;
|
||||
this.isString = false;
|
||||
|
||||
if (!this.locale) {
|
||||
this.locale = getLocale();
|
||||
}
|
||||
|
||||
this.useAbsoluteValue = false;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ghostfolio",
|
||||
"version": "1.270.1",
|
||||
"version": "1.272.0",
|
||||
"homepage": "https://ghostfol.io",
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
@ -106,7 +106,7 @@
|
||||
"envalid": "7.3.1",
|
||||
"google-spreadsheet": "3.2.0",
|
||||
"http-status-codes": "2.2.0",
|
||||
"ionicons": "6.1.2",
|
||||
"ionicons": "7.1.0",
|
||||
"lodash": "4.17.21",
|
||||
"marked": "4.2.12",
|
||||
"ms": "3.0.0-canary.1",
|
||||
|
@ -11221,10 +11221,10 @@ invert-kv@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
|
||||
integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
|
||||
|
||||
ionicons@6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/ionicons/-/ionicons-6.1.2.tgz#805ed1ce272b653ac07a85f83514e5afa2c9677d"
|
||||
integrity sha512-EL3jjlUzjPo8h2PfI+BUEjVMF9weSfLAFriNlk9pHFMTJq+7G12sAJBZ3AnRN8nTWA2pOS279PvFIWS3hbat+w==
|
||||
ionicons@7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ionicons/-/ionicons-7.1.0.tgz#25daa91345acedcb0f4fb7da670f5aff2e1f266a"
|
||||
integrity sha512-iE4GuEdEHARJpp0sWL7WJZCzNCf5VxpNRhAjW0fLnZPnNL5qZOJUcfup2Z2Ty7Jk8Q5hacrHfGEB1lCwOdXqGg==
|
||||
dependencies:
|
||||
"@stencil/core" "^2.18.0"
|
||||
|
||||
|
Reference in New Issue
Block a user