Feature/refactor appearance to color scheme (#1364)

* Refactor appearance to colorScheme

* Update changelog
This commit is contained in:
Thomas Kaul 2022-10-16 14:54:26 +02:00 committed by GitHub
parent 9ff8cd5471
commit eac52a215b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 119 additions and 64 deletions

View File

@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Persisted the language on url change - Persisted the language on url change
- Improved the portfolio evolution chart - Improved the portfolio evolution chart
- Removed the data source type `RAKUTEN` - Removed the data source type `RAKUTEN`
- Refactored the appearance (dark mode) in user settings (from `appearance` to `colorScheme`)
## 1.204.1 - 15.10.2022 ## 1.204.1 - 15.10.2022

View File

@ -1,4 +1,8 @@
import type { Appearance, DateRange, ViewMode } from '@ghostfolio/common/types'; import type {
ColorScheme,
DateRange,
ViewMode
} from '@ghostfolio/common/types';
import { import {
IsBoolean, IsBoolean,
IsIn, IsIn,
@ -8,10 +12,6 @@ import {
} from 'class-validator'; } from 'class-validator';
export class UpdateUserSettingDto { export class UpdateUserSettingDto {
@IsIn(<Appearance[]>['DARK', 'LIGHT'])
@IsOptional()
appearance?: Appearance;
@IsOptional() @IsOptional()
@IsString() @IsString()
baseCurrency?: string; baseCurrency?: string;
@ -20,6 +20,10 @@ export class UpdateUserSettingDto {
@IsOptional() @IsOptional()
benchmark?: string; benchmark?: string;
@IsIn(<ColorScheme[]>['DARK', 'LIGHT'])
@IsOptional()
colorScheme?: ColorScheme;
@IsIn(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd']) @IsIn(<DateRange[]>['1d', '1y', '5y', 'max', 'ytd'])
@IsOptional() @IsOptional()
dateRange?: DateRange; dateRange?: DateRange;

View File

@ -13,7 +13,7 @@ import {
} from '@ghostfolio/common/config'; } from '@ghostfolio/common/config';
import { InfoItem, User } from '@ghostfolio/common/interfaces'; import { InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { Appearance } from '@ghostfolio/common/types'; import { ColorScheme } from '@ghostfolio/common/types';
import { MaterialCssVarsService } from 'angular-material-css-vars'; import { MaterialCssVarsService } from 'angular-material-css-vars';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
@ -78,7 +78,7 @@ export class AppComponent implements OnDestroy, OnInit {
permissions.createUserAccount permissions.createUserAccount
); );
this.initializeTheme(this.user?.settings.appearance); this.initializeTheme(this.user?.settings.colorScheme);
this.changeDetectorRef.markForCheck(); this.changeDetectorRef.markForCheck();
}); });
@ -100,15 +100,15 @@ export class AppComponent implements OnDestroy, OnInit {
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private initializeTheme(userPreferredAppearance?: Appearance) { private initializeTheme(userPreferredColorScheme?: ColorScheme) {
const isDarkTheme = userPreferredAppearance const isDarkTheme = userPreferredColorScheme
? userPreferredAppearance === 'DARK' ? userPreferredColorScheme === 'DARK'
: window.matchMedia('(prefers-color-scheme: dark)').matches; : window.matchMedia('(prefers-color-scheme: dark)').matches;
this.materialCssVarsService.setDarkTheme(isDarkTheme); this.materialCssVarsService.setDarkTheme(isDarkTheme);
window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => { window.matchMedia('(prefers-color-scheme: dark)').addListener((event) => {
if (!this.user?.settings.appearance) { if (!this.user?.settings.colorScheme) {
this.materialCssVarsService.setDarkTheme(event.matches); this.materialCssVarsService.setDarkTheme(event.matches);
} }
}); });

View File

@ -23,6 +23,8 @@ import {
parseDate parseDate
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem, User } from '@ghostfolio/common/interfaces'; import { LineChartItem, User } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
import { SymbolProfile } from '@prisma/client';
import { import {
Chart, Chart,
LineController, LineController,
@ -33,7 +35,6 @@ import {
Tooltip Tooltip
} from 'chart.js'; } from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation'; import annotationPlugin from 'chartjs-plugin-annotation';
import { SymbolProfile } from '@prisma/client';
@Component({ @Component({
selector: 'gf-benchmark-comparator', selector: 'gf-benchmark-comparator',
@ -45,6 +46,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
@Input() benchmarkDataItems: LineChartItem[] = []; @Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmark: string; @Input() benchmark: string;
@Input() benchmarks: Partial<SymbolProfile>[]; @Input() benchmarks: Partial<SymbolProfile>[];
@Input() colorScheme: ColorScheme;
@Input() daysInMarket: number; @Input() daysInMarket: number;
@Input() isLoading: boolean; @Input() isLoading: boolean;
@Input() locale: string; @Input() locale: string;
@ -127,7 +129,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
tension: 0 tension: 0
}, },
point: { point: {
hoverBackgroundColor: getBackgroundColor(), hoverBackgroundColor: getBackgroundColor(this.colorScheme),
hoverRadius: 2, hoverRadius: 2,
radius: 0 radius: 0
} }
@ -138,7 +140,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
annotation: { annotation: {
annotations: { annotations: {
yAxis: { yAxis: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
borderWidth: 1, borderWidth: 1,
scaleID: 'y', scaleID: 'y',
type: 'line', type: 'line',
@ -151,7 +153,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
}, },
tooltip: this.getTooltipPluginConfiguration(), tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: { verticalHoverLine: {
color: `rgba(${getTextColor()}, 0.1)` color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
} }
}, },
responsive: true, responsive: true,
@ -159,9 +161,9 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
x: { x: {
display: true, display: true,
grid: { grid: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
borderWidth: 1, borderWidth: 1,
color: `rgba(${getTextColor()}, 0.8)`, color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
display: false display: false
}, },
type: 'time', type: 'time',
@ -173,8 +175,8 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
y: { y: {
display: true, display: true,
grid: { grid: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
color: `rgba(${getTextColor()}, 0.8)`, color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
display: false, display: false,
drawBorder: false drawBorder: false
}, },
@ -190,7 +192,9 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
} }
} }
}, },
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)], plugins: [
getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme)
],
type: 'line' type: 'line'
}); });
} }
@ -200,6 +204,7 @@ export class BenchmarkComparatorComponent implements OnChanges, OnDestroy {
private getTooltipPluginConfiguration() { private getTooltipPluginConfiguration() {
return { return {
...getTooltipOptions({ ...getTooltipOptions({
colorScheme: this.colorScheme,
locale: this.locale, locale: this.locale,
unit: '%' unit: '%'
}), }),

View File

@ -127,6 +127,7 @@ export class HomeHoldingsComponent implements OnDestroy, OnInit {
dataSource, dataSource,
symbol, symbol,
baseCurrency: this.user?.settings?.baseCurrency, baseCurrency: this.user?.settings?.baseCurrency,
colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
hasPermissionToReportDataGlitch: hasPermission( hasPermissionToReportDataGlitch: hasPermission(

View File

@ -16,8 +16,9 @@
class="position-absolute" class="position-absolute"
symbol="Performance" symbol="Performance"
unit="%" unit="%"
[historicalDataItems]="historicalDataItems" [colorScheme]="user?.settings?.colorScheme"
[hidden]="historicalDataItems?.length === 0" [hidden]="historicalDataItems?.length === 0"
[historicalDataItems]="historicalDataItems"
[isAnimated]="user?.settings?.dateRange === '1d' ? false : true" [isAnimated]="user?.settings?.dateRange === '1d' ? false : true"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
[ngClass]="{ 'pr-3': deviceType === 'mobile' }" [ngClass]="{ 'pr-3': deviceType === 'mobile' }"

View File

@ -24,7 +24,7 @@ import {
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces'; import { LineChartItem } from '@ghostfolio/common/interfaces';
import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface';
import { DateRange, GroupBy } from '@ghostfolio/common/types'; import { ColorScheme, DateRange, GroupBy } from '@ghostfolio/common/types';
import { import {
BarController, BarController,
BarElement, BarElement,
@ -47,6 +47,7 @@ import { addDays, format, isAfter, parseISO, subDays } from 'date-fns';
}) })
export class InvestmentChartComponent implements OnChanges, OnDestroy { export class InvestmentChartComponent implements OnChanges, OnDestroy {
@Input() benchmarkDataItems: InvestmentItem[] = []; @Input() benchmarkDataItems: InvestmentItem[] = [];
@Input() colorScheme: ColorScheme;
@Input() currency: string; @Input() currency: string;
@Input() daysInMarket: number; @Input() daysInMarket: number;
@Input() groupBy: GroupBy; @Input() groupBy: GroupBy;
@ -180,7 +181,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
tension: 0 tension: 0
}, },
point: { point: {
hoverBackgroundColor: getBackgroundColor(), hoverBackgroundColor: getBackgroundColor(this.colorScheme),
hoverRadius: 2, hoverRadius: 2,
radius: 0 radius: 0
} }
@ -213,7 +214,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
} }
: undefined, : undefined,
yAxis: { yAxis: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
borderWidth: 1, borderWidth: 1,
scaleID: 'y', scaleID: 'y',
type: 'line', type: 'line',
@ -226,7 +227,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
}, },
tooltip: this.getTooltipPluginConfiguration(), tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: { verticalHoverLine: {
color: `rgba(${getTextColor()}, 0.1)` color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
} }
}, },
responsive: true, responsive: true,
@ -234,9 +235,9 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
x: { x: {
display: true, display: true,
grid: { grid: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
borderWidth: this.groupBy ? 0 : 1, borderWidth: this.groupBy ? 0 : 1,
color: `rgba(${getTextColor()}, 0.8)`, color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
display: false display: false
}, },
type: 'time', type: 'time',
@ -248,8 +249,8 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
y: { y: {
display: !this.isInPercent, display: !this.isInPercent,
grid: { grid: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
color: `rgba(${getTextColor()}, 0.8)`, color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
display: false, display: false,
drawBorder: false drawBorder: false
}, },
@ -265,7 +266,9 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
} }
} }
}, },
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)], plugins: [
getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme)
],
type: this.groupBy ? 'bar' : 'line' type: this.groupBy ? 'bar' : 'line'
}); });
} }
@ -277,6 +280,7 @@ export class InvestmentChartComponent implements OnChanges, OnDestroy {
private getTooltipPluginConfiguration() { private getTooltipPluginConfiguration() {
return { return {
...getTooltipOptions({ ...getTooltipOptions({
colorScheme: this.colorScheme,
currency: this.isInPercent ? undefined : this.currency, currency: this.isInPercent ? undefined : this.currency,
locale: this.isInPercent ? undefined : this.locale, locale: this.isInPercent ? undefined : this.locale,
unit: this.isInPercent ? '%' : undefined unit: this.isInPercent ? '%' : undefined

View File

@ -1,7 +1,9 @@
import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
export interface PositionDetailDialogParams { export interface PositionDetailDialogParams {
baseCurrency: string; baseCurrency: string;
colorScheme: ColorScheme;
dataSource: DataSource; dataSource: DataSource;
deviceType: string; deviceType: string;
hasImpersonationId: boolean; hasImpersonationId: boolean;

View File

@ -20,9 +20,10 @@
</div> </div>
<gf-line-chart <gf-line-chart
class="mb-4"
benchmarkLabel="Average Unit Price" benchmarkLabel="Average Unit Price"
class="mb-4"
[benchmarkDataItems]="benchmarkDataItems" [benchmarkDataItems]="benchmarkDataItems"
[colorScheme]="data.colorScheme"
[currency]="SymbolProfile?.currency" [currency]="SymbolProfile?.currency"
[historicalDataItems]="historicalDataItems" [historicalDataItems]="historicalDataItems"
[isAnimated]="true" [isAnimated]="true"
@ -188,6 +189,7 @@
<div class="h5" i18n>Sectors</div> <div class="h5" i18n>Sectors</div>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="data.colorScheme"
[isInPercent]="true" [isInPercent]="true"
[keys]="['name']" [keys]="['name']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -199,6 +201,7 @@
<div class="h5" i18n>Countries</div> <div class="h5" i18n>Countries</div>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="data.colorScheme"
[isInPercent]="true" [isInPercent]="true"
[keys]="['name']" [keys]="['name']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"

View File

@ -200,12 +200,12 @@
class="compact-with-outline w-100 without-hint" class="compact-with-outline w-100 without-hint"
> >
<mat-select <mat-select
name="appearance"
class="with-placeholder-as-option" class="with-placeholder-as-option"
name="colorScheme"
[disabled]="!hasPermissionToUpdateUserSettings" [disabled]="!hasPermissionToUpdateUserSettings"
[placeholder]="appearancePlaceholder" [placeholder]="appearancePlaceholder"
[value]="user.settings.appearance" [value]="user?.settings?.colorScheme"
(selectionChange)="onChangeUserSetting('appearance', $event.value)" (selectionChange)="onChangeUserSetting('colorScheme', $event.value)"
> >
<mat-option i18n [value]="null">Auto</mat-option> <mat-option i18n [value]="null">Auto</mat-option>
<mat-option i18n value="LIGHT">Light</mat-option> <mat-option i18n value="LIGHT">Light</mat-option>
@ -267,8 +267,8 @@
class="align-items-center d-flex justify-content-center" class="align-items-center d-flex justify-content-center"
color="primary" color="primary"
mat-fab mat-fab
[routerLink]="[]"
[queryParams]="{ createDialog: true }" [queryParams]="{ createDialog: true }"
[routerLink]="[]"
> >
<ion-icon name="add-outline" size="large"></ion-icon> <ion-icon name="add-outline" size="large"></ion-icon>
</a> </a>

View File

@ -450,6 +450,7 @@ export class AllocationsPageComponent implements OnDestroy, OnInit {
dataSource, dataSource,
symbol, symbol,
baseCurrency: this.user?.settings?.baseCurrency, baseCurrency: this.user?.settings?.baseCurrency,
colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
hasPermissionToReportDataGlitch: hasPermission( hasPermissionToReportDataGlitch: hasPermission(

View File

@ -50,6 +50,7 @@
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
cursor="pointer" cursor="pointer"
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['id']" [keys]="['id']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -79,6 +80,7 @@
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['currency']" [keys]="['currency']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -107,6 +109,7 @@
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['assetClass', 'assetSubClass']" [keys]="['assetClass', 'assetSubClass']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -133,6 +136,7 @@
class="mx-auto" class="mx-auto"
cursor="pointer" cursor="pointer"
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['symbol']" [keys]="['symbol']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -163,6 +167,7 @@
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['name']" [keys]="['name']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -192,6 +197,7 @@
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['name']" [keys]="['name']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"
@ -220,6 +226,7 @@
<mat-card-content> <mat-card-content>
<gf-portfolio-proportion-chart <gf-portfolio-proportion-chart
[baseCurrency]="user?.settings?.baseCurrency" [baseCurrency]="user?.settings?.baseCurrency"
[colorScheme]="user?.settings?.colorScheme"
[isInPercent]="hasImpersonationId || user.settings.isRestrictedView" [isInPercent]="hasImpersonationId || user.settings.isRestrictedView"
[keys]="['name']" [keys]="['name']"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"

View File

@ -15,6 +15,7 @@
[benchmark]="user?.settings?.benchmark" [benchmark]="user?.settings?.benchmark"
[benchmarkDataItems]="benchmarkDataItems" [benchmarkDataItems]="benchmarkDataItems"
[benchmarks]="benchmarks" [benchmarks]="benchmarks"
[colorScheme]="user?.settings?.colorScheme"
[daysInMarket]="daysInMarket" [daysInMarket]="daysInMarket"
[isLoading]="isLoadingBenchmarkComparator" [isLoading]="isLoadingBenchmarkComparator"
[locale]="user?.settings?.locale" [locale]="user?.settings?.locale"

View File

@ -5,6 +5,7 @@
<div> <div>
<h4 class="mb-3" i18n>Calculator</h4> <h4 class="mb-3" i18n>Calculator</h4>
<gf-fire-calculator <gf-fire-calculator
[colorScheme]="user?.settings?.colorScheme"
[currency]="user?.settings?.baseCurrency" [currency]="user?.settings?.baseCurrency"
[deviceType]="deviceType" [deviceType]="deviceType"
[fireWealth]="fireWealth?.toNumber()" [fireWealth]="fireWealth?.toNumber()"

View File

@ -192,6 +192,7 @@ export class HoldingsPageComponent implements OnDestroy, OnInit {
dataSource, dataSource,
symbol, symbol,
baseCurrency: this.user?.settings?.baseCurrency, baseCurrency: this.user?.settings?.baseCurrency,
colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
hasPermissionToReportDataGlitch: hasPermission( hasPermissionToReportDataGlitch: hasPermission(

View File

@ -405,6 +405,7 @@ export class TransactionsPageComponent implements OnDestroy, OnInit {
dataSource, dataSource,
symbol, symbol,
baseCurrency: this.user?.settings?.baseCurrency, baseCurrency: this.user?.settings?.baseCurrency,
colorScheme: this.user?.settings?.colorScheme,
deviceType: this.deviceType, deviceType: this.deviceType,
hasImpersonationId: this.hasImpersonationId, hasImpersonationId: this.hasImpersonationId,
hasPermissionToReportDataGlitch: hasPermission( hasPermissionToReportDataGlitch: hasPermission(

View File

@ -1,21 +1,24 @@
import { Chart, TooltipPosition } from 'chart.js'; import { Chart, TooltipPosition } from 'chart.js';
import { getBackgroundColor, getTextColor } from './helper'; import { getBackgroundColor, getTextColor } from './helper';
import { ColorScheme } from './types';
export function getTooltipOptions({ export function getTooltipOptions({
colorScheme,
currency = '', currency = '',
locale = '', locale = '',
unit = '' unit = ''
}: { }: {
colorScheme?: ColorScheme;
currency?: string; currency?: string;
locale?: string; locale?: string;
unit?: string; unit?: string;
} = {}) { } = {}) {
return { return {
backgroundColor: getBackgroundColor(), backgroundColor: getBackgroundColor(colorScheme),
bodyColor: `rgb(${getTextColor()})`, bodyColor: `rgb(${getTextColor(colorScheme)})`,
borderWidth: 1, borderWidth: 1,
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(colorScheme)}, 0.1)`,
callbacks: { callbacks: {
label: (context) => { label: (context) => {
let label = context.dataset.label || ''; let label = context.dataset.label || '';
@ -39,12 +42,12 @@ export function getTooltipOptions({
}, },
caretSize: 0, caretSize: 0,
cornerRadius: 2, cornerRadius: 2,
footerColor: `rgb(${getTextColor()})`, footerColor: `rgb(${getTextColor(colorScheme)})`,
itemSort: (a, b) => { itemSort: (a, b) => {
// Reverse order // Reverse order
return b.datasetIndex - a.datasetIndex; return b.datasetIndex - a.datasetIndex;
}, },
titleColor: `rgb(${getTextColor()})`, titleColor: `rgb(${getTextColor(colorScheme)})`,
usePointStyle: true usePointStyle: true
}; };
} }
@ -62,7 +65,10 @@ export function getTooltipPositionerMapTop(
}; };
} }
export function getVerticalHoverLinePlugin(chartCanvas) { export function getVerticalHoverLinePlugin(
chartCanvas,
colorScheme?: ColorScheme
) {
return { return {
afterDatasetsDraw: (chart, x, options) => { afterDatasetsDraw: (chart, x, options) => {
const active = chart.getActiveElements(); const active = chart.getActiveElements();
@ -71,7 +77,7 @@ export function getVerticalHoverLinePlugin(chartCanvas) {
return; return;
} }
const color = options.color || `rgb(${getTextColor()})`; const color = options.color || `rgb(${getTextColor(colorScheme)})`;
const width = options.width || 1; const width = options.width || 1;
const { const {

View File

@ -5,6 +5,7 @@ import { de, es, it, nl } from 'date-fns/locale';
import { ghostfolioScraperApiSymbolPrefix, locale } from './config'; import { ghostfolioScraperApiSymbolPrefix, locale } from './config';
import { Benchmark } from './interfaces'; import { Benchmark } from './interfaces';
import { ColorScheme } from './types';
const NUMERIC_REGEXP = /[-]{0,1}[\d]*[.,]{0,1}[\d]+/g; const NUMERIC_REGEXP = /[-]{0,1}[\d]*[.,]{0,1}[\d]+/g;
@ -58,9 +59,10 @@ export function extractNumberFromString(aString: string): number {
} }
} }
export function getBackgroundColor() { export function getBackgroundColor(aColorScheme: ColorScheme) {
return getCssVariable( return getCssVariable(
window.matchMedia('(prefers-color-scheme: dark)').matches aColorScheme === 'DARK' ||
window.matchMedia('(prefers-color-scheme: dark)').matches
? '--dark-background' ? '--dark-background'
: '--light-background' : '--light-background'
); );
@ -133,9 +135,10 @@ export function getNumberFormatGroup(aLocale?: string) {
}).value; }).value;
} }
export function getTextColor() { export function getTextColor(aColorScheme: ColorScheme) {
const cssVariable = getCssVariable( const cssVariable = getCssVariable(
window.matchMedia('(prefers-color-scheme: dark)').matches aColorScheme === 'DARK' ||
window.matchMedia('(prefers-color-scheme: dark)').matches
? '--light-primary-text' ? '--light-primary-text'
: '--dark-primary-text' : '--dark-primary-text'
); );

View File

@ -1,9 +1,9 @@
import { DateRange, ViewMode, Appearance } from '@ghostfolio/common/types'; import { ColorScheme, DateRange, ViewMode } from '@ghostfolio/common/types';
export interface UserSettings { export interface UserSettings {
appearance?: Appearance;
baseCurrency?: string; baseCurrency?: string;
benchmark?: string; benchmark?: string;
colorScheme?: ColorScheme;
dateRange?: DateRange; dateRange?: DateRange;
emergencyFund?: number; emergencyFund?: number;
isExperimentalFeatures?: boolean; isExperimentalFeatures?: boolean;

View File

@ -1,5 +1,6 @@
import { SubscriptionType } from '@ghostfolio/common/types/subscription.type'; import { SubscriptionType } from '@ghostfolio/common/types/subscription.type';
import { Account, Settings, User } from '@prisma/client'; import { Account, Settings, User } from '@prisma/client';
import { UserSettings } from './user-settings.interface'; import { UserSettings } from './user-settings.interface';
export type UserWithSettings = User & { export type UserWithSettings = User & {

View File

@ -1 +0,0 @@
export type Appearance = 'DARK' | 'LIGHT';

View File

@ -0,0 +1 @@
export type ColorScheme = 'DARK' | 'LIGHT';

View File

@ -1,5 +1,6 @@
import type { AccessWithGranteeUser } from './access-with-grantee-user.type'; import type { AccessWithGranteeUser } from './access-with-grantee-user.type';
import { AccountWithValue } from './account-with-value.type'; import { AccountWithValue } from './account-with-value.type';
import type { ColorScheme } from './color-scheme';
import type { DateRange } from './date-range.type'; import type { DateRange } from './date-range.type';
import type { Granularity } from './granularity.type'; import type { Granularity } from './granularity.type';
import { GroupBy } from './group-by.type'; import { GroupBy } from './group-by.type';
@ -9,12 +10,11 @@ import type { OrderWithAccount } from './order-with-account.type';
import type { RequestWithUser } from './request-with-user.type'; import type { RequestWithUser } from './request-with-user.type';
import { ToggleOption } from './toggle-option.type'; import { ToggleOption } from './toggle-option.type';
import type { ViewMode } from './view-mode.type'; import type { ViewMode } from './view-mode.type';
import type { Appearance } from './appearance.type';
export type { export type {
Appearance,
AccessWithGranteeUser, AccessWithGranteeUser,
AccountWithValue, AccountWithValue,
ColorScheme,
DateRange, DateRange,
Granularity, Granularity,
GroupBy, GroupBy,

View File

@ -1,4 +1,5 @@
import '@angular/localize/init'; import '@angular/localize/init';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';

View File

@ -16,6 +16,7 @@ import { FormBuilder, FormControl } from '@angular/forms';
import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { primaryColorRgb } from '@ghostfolio/common/config'; import { primaryColorRgb } from '@ghostfolio/common/config';
import { transformTickToAbbreviation } from '@ghostfolio/common/helper'; import { transformTickToAbbreviation } from '@ghostfolio/common/helper';
import { ColorScheme } from '@ghostfolio/common/types';
import { import {
BarController, BarController,
BarElement, BarElement,
@ -40,6 +41,7 @@ import { FireCalculatorService } from './fire-calculator.service';
export class FireCalculatorComponent export class FireCalculatorComponent
implements AfterViewInit, OnChanges, OnDestroy implements AfterViewInit, OnChanges, OnDestroy
{ {
@Input() colorScheme: ColorScheme;
@Input() currency: string; @Input() currency: string;
@Input() deviceType: string; @Input() deviceType: string;
@Input() fireWealth: number; @Input() fireWealth: number;
@ -182,7 +184,7 @@ export class FireCalculatorComponent
options: { options: {
plugins: { plugins: {
tooltip: { tooltip: {
...getTooltipOptions(), ...getTooltipOptions({ colorScheme: this.colorScheme }),
mode: 'index', mode: 'index',
callbacks: { callbacks: {
footer: (items) => { footer: (items) => {

View File

@ -26,6 +26,7 @@ import {
getTextColor getTextColor
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { LineChartItem } from '@ghostfolio/common/interfaces'; import { LineChartItem } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
import { import {
Chart, Chart,
Filler, Filler,
@ -46,6 +47,7 @@ import {
export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy { export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
@Input() benchmarkDataItems: LineChartItem[] = []; @Input() benchmarkDataItems: LineChartItem[] = [];
@Input() benchmarkLabel = ''; @Input() benchmarkLabel = '';
@Input() colorScheme: ColorScheme;
@Input() currency: string; @Input() currency: string;
@Input() historicalDataItems: LineChartItem[]; @Input() historicalDataItems: LineChartItem[];
@Input() isAnimated = false; @Input() isAnimated = false;
@ -140,7 +142,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
0, 0,
`rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.01)` `rgba(${primaryColorRgb.r}, ${primaryColorRgb.g}, ${primaryColorRgb.b}, 0.01)`
); );
gradient.addColorStop(1, getBackgroundColor()); gradient.addColorStop(1, getBackgroundColor(this.colorScheme));
} }
const data = { const data = {
@ -192,7 +194,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
elements: { elements: {
point: { point: {
hoverBackgroundColor: getBackgroundColor(), hoverBackgroundColor: getBackgroundColor(this.colorScheme),
hoverRadius: 2 hoverRadius: 2
} }
}, },
@ -205,15 +207,15 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
}, },
tooltip: this.getTooltipPluginConfiguration(), tooltip: this.getTooltipPluginConfiguration(),
verticalHoverLine: { verticalHoverLine: {
color: `rgba(${getTextColor()}, 0.1)` color: `rgba(${getTextColor(this.colorScheme)}, 0.1)`
} }
}, },
scales: { scales: {
x: { x: {
display: this.showXAxis, display: this.showXAxis,
grid: { grid: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
color: `rgba(${getTextColor()}, 0.8)`, color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
display: false display: false
}, },
time: { time: {
@ -225,8 +227,8 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
y: { y: {
display: this.showYAxis, display: this.showYAxis,
grid: { grid: {
borderColor: `rgba(${getTextColor()}, 0.1)`, borderColor: `rgba(${getTextColor(this.colorScheme)}, 0.1)`,
color: `rgba(${getTextColor()}, 0.8)`, color: `rgba(${getTextColor(this.colorScheme)}, 0.8)`,
display: false display: false
}, },
max: this.yMax, max: this.yMax,
@ -263,7 +265,9 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
}, },
spanGaps: true spanGaps: true
}, },
plugins: [getVerticalHoverLinePlugin(this.chartCanvas)], plugins: [
getVerticalHoverLinePlugin(this.chartCanvas, this.colorScheme)
],
type: 'line' type: 'line'
}); });
} }
@ -300,6 +304,7 @@ export class LineChartComponent implements AfterViewInit, OnChanges, OnDestroy {
private getTooltipPluginConfiguration() { private getTooltipPluginConfiguration() {
return { return {
...getTooltipOptions({ ...getTooltipOptions({
colorScheme: this.colorScheme,
currency: this.currency, currency: this.currency,
locale: this.locale, locale: this.locale,
unit: this.unit unit: this.unit

View File

@ -14,6 +14,7 @@ import { getTooltipOptions } from '@ghostfolio/common/chart-helper';
import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { UNKNOWN_KEY } from '@ghostfolio/common/config';
import { getTextColor } from '@ghostfolio/common/helper'; import { getTextColor } from '@ghostfolio/common/helper';
import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces'; import { PortfolioPosition, UniqueAsset } from '@ghostfolio/common/interfaces';
import { ColorScheme } from '@ghostfolio/common/types';
import { DataSource } from '@prisma/client'; import { DataSource } from '@prisma/client';
import Big from 'big.js'; import Big from 'big.js';
import { ChartConfiguration, Tooltip } from 'chart.js'; import { ChartConfiguration, Tooltip } from 'chart.js';
@ -34,6 +35,7 @@ export class PortfolioProportionChartComponent
implements AfterViewInit, OnChanges, OnDestroy implements AfterViewInit, OnChanges, OnDestroy
{ {
@Input() baseCurrency: string; @Input() baseCurrency: string;
@Input() colorScheme: ColorScheme;
@Input() cursor: string; @Input() cursor: string;
@Input() isInPercent = false; @Input() isInPercent = false;
@Input() keys: string[] = []; @Input() keys: string[] = [];
@ -59,10 +61,7 @@ export class PortfolioProportionChartComponent
private colorMap: { private colorMap: {
[symbol: string]: string; [symbol: string]: string;
} = { } = {};
[this.OTHER_KEY]: `rgba(${getTextColor()}, 0.24)`,
[UNKNOWN_KEY]: `rgba(${getTextColor()}, 0.12)`
};
public constructor() { public constructor() {
Chart.register(ArcElement, DoughnutController, LinearScale, Tooltip); Chart.register(ArcElement, DoughnutController, LinearScale, Tooltip);
@ -94,6 +93,10 @@ export class PortfolioProportionChartComponent
value: Big; value: Big;
}; };
} = {}; } = {};
this.colorMap = {
[this.OTHER_KEY]: `rgba(${getTextColor(this.colorScheme)}, 0.24)`,
[UNKNOWN_KEY]: `rgba(${getTextColor(this.colorScheme)}, 0.12)`
};
Object.keys(this.positions).forEach((symbol) => { Object.keys(this.positions).forEach((symbol) => {
if (this.positions[symbol][this.keys[0]]) { if (this.positions[symbol][this.keys[0]]) {
@ -350,6 +353,7 @@ export class PortfolioProportionChartComponent
private getTooltipPluginConfiguration(data: ChartConfiguration['data']) { private getTooltipPluginConfiguration(data: ChartConfiguration['data']) {
return { return {
...getTooltipOptions({ ...getTooltipOptions({
colorScheme: this.colorScheme,
currency: this.baseCurrency, currency: this.baseCurrency,
locale: this.locale locale: this.locale
}), }),