diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ae63148f2..be16617266 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed the colors of the investment chart component (dark mode) +- Fixed the colors of the charts if the appearance is set to light and the operating system uses dark mode - Fixed the allocation column of the top holdings of ETFs on the allocations page after a change of the user settings - Fixed the value column of the top holdings of ETFs on the allocations page in the _Presenter View_ - Fixed the projected sustainable retirement income on the _FIRE_ page for a projected total amount of zero (experimental) diff --git a/apps/client/src/app/app.component.ts b/apps/client/src/app/app.component.ts index 7252ad1646..a1f8ebfe9c 100644 --- a/apps/client/src/app/app.component.ts +++ b/apps/client/src/app/app.component.ts @@ -1,4 +1,4 @@ -import { getCssVariable } from '@ghostfolio/common/helper'; +import { getCssVariable, isDarkColorScheme } from '@ghostfolio/common/helper'; import { AssetProfileIdentifier, InfoItem, @@ -260,11 +260,7 @@ export class GfAppComponent implements OnInit { } private initializeTheme(userPreferredColorScheme?: ColorScheme) { - const isDarkTheme = userPreferredColorScheme - ? userPreferredColorScheme === 'DARK' - : window.matchMedia('(prefers-color-scheme: dark)').matches; - - this.toggleTheme(isDarkTheme); + this.toggleTheme(isDarkColorScheme(userPreferredColorScheme)); // Default chart styles Chart.defaults.font.family = getCssVariable('--font-family-sans-serif'); diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts index af83ef4a7d..26af45e20b 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog-host/asset-profile-dialog-host.component.ts @@ -1,8 +1,5 @@ import { UserService } from '@ghostfolio/client/services/user/user.service'; -import { - DEFAULT_COLOR_SCHEME, - DEFAULT_LOCALE -} from '@ghostfolio/common/config'; +import { DEFAULT_LOCALE } from '@ghostfolio/common/config'; import { AssetProfileIdentifier, User } from '@ghostfolio/common/interfaces'; import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { DataSource } from '@ghostfolio/prisma/enums'; @@ -150,7 +147,7 @@ export class GfAssetProfileDialogHostComponent implements OnDestroy, OnInit { data: { dataSource, symbol, - colorScheme: user?.settings.colorScheme ?? DEFAULT_COLOR_SCHEME, + colorScheme: user?.settings?.colorScheme, deviceType: this.deviceType(), locale: user?.settings?.locale ?? DEFAULT_LOCALE } satisfies AssetProfileDialogParams, diff --git a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts index 0f000364ee..2e2bb7b118 100644 --- a/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts +++ b/apps/client/src/app/components/admin-market-data/asset-profile-dialog/interfaces/interfaces.ts @@ -2,7 +2,7 @@ import { ColorScheme } from '@ghostfolio/common/types'; import { DataSource } from '@ghostfolio/prisma/enums'; export interface AssetProfileDialogParams { - colorScheme: ColorScheme; + colorScheme?: ColorScheme; dataSource: DataSource; deviceType: string; locale: string; diff --git a/apps/client/src/app/components/investment-chart/investment-chart.component.ts b/apps/client/src/app/components/investment-chart/investment-chart.component.ts index f51fc4d641..b3cc0d753e 100644 --- a/apps/client/src/app/components/investment-chart/investment-chart.component.ts +++ b/apps/client/src/app/components/investment-chart/investment-chart.component.ts @@ -7,11 +7,7 @@ import { getZeroLineAnnotation, transformTickToAbbreviation } from '@ghostfolio/common/chart-helper'; -import { - DEFAULT_COLOR_SCHEME, - primaryColorRgb, - secondaryColorRgb -} from '@ghostfolio/common/config'; +import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config'; import { getLocale, parseDate } from '@ghostfolio/common/helper'; import { LineChartItem } from '@ghostfolio/common/interfaces'; import { InvestmentItem } from '@ghostfolio/common/interfaces/investment-item.interface'; @@ -174,15 +170,13 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { this.chart.update(); } else { - const colorScheme = this.colorScheme ?? DEFAULT_COLOR_SCHEME; - this.chart = new Chart<'bar' | 'line'>( this.chartCanvas().nativeElement, { data: chartData, options: { animation: false, - elements: getChartElementsOptions(colorScheme), + elements: getChartElementsOptions(this.colorScheme), interaction: { intersect: false, mode: 'index' }, maintainAspectRatio: true, plugins: { @@ -210,7 +204,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { value: this.savingsRate } : undefined, - yAxis: getZeroLineAnnotation(colorScheme) + yAxis: getZeroLineAnnotation(this.colorScheme) } }, legend: { @@ -218,18 +212,18 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { }, tooltip: this.getTooltipPluginConfiguration(), verticalHoverLine: { - color: getChartBorderColor(colorScheme) + color: getChartBorderColor(this.colorScheme) } }, responsive: true, scales: { x: getTimeAxisOptions({ - colorScheme, borderWidth: this.groupBy ? 0 : 1, + colorScheme: this.colorScheme, locale: this.locale }), y: getValueAxisOptions({ - colorScheme, + colorScheme: this.colorScheme, display: !this.isInPercentage, tickCallback: (tickValue) => { return transformTickToAbbreviation(Number(tickValue)); @@ -238,7 +232,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { } }, plugins: [ - getVerticalHoverLinePlugin(this.chartCanvas(), colorScheme) + getVerticalHoverLinePlugin(this.chartCanvas(), this.colorScheme) ], type: this.groupBy ? 'bar' : 'line' } @@ -251,7 +245,7 @@ export class GfInvestmentChartComponent implements OnChanges, OnDestroy { TooltipOptions<'bar' | 'line'> > { return getTimeSeriesTooltipOptions<'bar' | 'line'>({ - colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME, + colorScheme: this.colorScheme, currency: this.isInPercentage ? undefined : this.currency, groupBy: this.groupBy, locale: this.isInPercentage ? undefined : this.locale, diff --git a/libs/common/src/lib/chart-helper.ts b/libs/common/src/lib/chart-helper.ts index 23a00ca973..58f370d06d 100644 --- a/libs/common/src/lib/chart-helper.ts +++ b/libs/common/src/lib/chart-helper.ts @@ -41,12 +41,12 @@ export function formatGroupedDate({ return format(date, DATE_FORMAT); } -export function getChartBorderColor(colorScheme: ColorScheme) { +export function getChartBorderColor(colorScheme?: ColorScheme) { return `rgba(${getTextColor(colorScheme)}, 0.1)`; } export function getChartElementsOptions( - colorScheme: ColorScheme + colorScheme?: ColorScheme ): ChartOptions<'bar' | 'line'>['elements'] { return { line: { @@ -67,7 +67,7 @@ export function getTimeAxisOptions({ locale = getLocale() }: { borderWidth?: number; - colorScheme: ColorScheme; + colorScheme?: ColorScheme; display?: boolean; locale?: string; }): ScaleOptions<'time'> { @@ -95,7 +95,7 @@ export function getTooltipOptions({ locale = getLocale(), unit = '' }: { - colorScheme: ColorScheme; + colorScheme?: ColorScheme; currency?: string; groupBy?: GroupBy; locale?: string; @@ -174,7 +174,7 @@ export function getValueAxisOptions({ highlightedValues = [], tickCallback }: { - colorScheme: ColorScheme; + colorScheme?: ColorScheme; display?: boolean; highlightedValues?: number[]; tickCallback: ( @@ -214,7 +214,7 @@ export function getValueAxisOptions({ export function getVerticalHoverLinePlugin( chartCanvas: ElementRef, - colorScheme: ColorScheme + colorScheme?: ColorScheme ): Plugin { return { afterDatasetsDraw: (chart, _, options) => { @@ -249,7 +249,7 @@ export function getVerticalHoverLinePlugin( } export function getZeroLineAnnotation( - colorScheme: ColorScheme + colorScheme?: ColorScheme ): AnnotationOptions<'line'> { return { borderColor: getChartBorderColor(colorScheme), diff --git a/libs/common/src/lib/config.ts b/libs/common/src/lib/config.ts index 03d36f577b..6ff492f671 100644 --- a/libs/common/src/lib/config.ts +++ b/libs/common/src/lib/config.ts @@ -8,7 +8,7 @@ import { import { JobOptions, JobStatus } from 'bull'; import ms from 'ms'; -import { ColorScheme, DateRange } from './types'; +import { DateRange } from './types'; export const ghostfolioPrefix = 'GF'; @@ -100,7 +100,6 @@ export const PORTFOLIO_SNAPSHOT_COMPUTATION_QUEUE_PRIORITY_LOW = export const STATISTICS_GATHERING_QUEUE = 'STATISTICS_GATHERING_QUEUE'; -export const DEFAULT_COLOR_SCHEME: ColorScheme = 'LIGHT'; export const DEFAULT_CURRENCY = 'USD'; export const DEFAULT_DATE_FORMAT_MONTH_YEAR = 'MMM yyyy'; export const DEFAULT_DATE_RANGE: DateRange = 'max'; diff --git a/libs/common/src/lib/helper.spec.ts b/libs/common/src/lib/helper.spec.ts index d1130f0d75..b7e7635670 100644 --- a/libs/common/src/lib/helper.spec.ts +++ b/libs/common/src/lib/helper.spec.ts @@ -13,6 +13,7 @@ import { isAccountExcluded, isCurrency, isCurrencySymbol, + isDarkColorScheme, isSplitRatio, isValidCurrencyCode, isValidCustomAssetProfileSymbol, @@ -348,6 +349,40 @@ describe('Helper', () => { }); }); + describe('Is dark color scheme', () => { + const mockPrefersColorSchemeDark = (matches: boolean) => { + window.matchMedia = jest.fn(() => { + return { matches } as MediaQueryList; + }); + }; + + afterEach(() => { + Reflect.deleteProperty(window, 'matchMedia'); + }); + + it('Dark color scheme with operating system in light mode', () => { + mockPrefersColorSchemeDark(false); + expect(isDarkColorScheme('DARK')).toEqual(true); + }); + + it('Light color scheme with operating system in dark mode', () => { + mockPrefersColorSchemeDark(true); + expect(isDarkColorScheme('LIGHT')).toEqual(false); + }); + + it('Auto color scheme with operating system in dark mode', () => { + mockPrefersColorSchemeDark(true); + expect(isDarkColorScheme()).toEqual(true); + expect(isDarkColorScheme(null)).toEqual(true); + }); + + it('Auto color scheme with operating system in light mode', () => { + mockPrefersColorSchemeDark(false); + expect(isDarkColorScheme()).toEqual(false); + expect(isDarkColorScheme(null)).toEqual(false); + }); + }); + describe('Is split ratio', () => { it('Forward split', () => { expect(isSplitRatio({ denominator: 1, numerator: 2 })).toEqual(true); diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index e10ca215cc..2a76261dae 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -345,12 +345,9 @@ export function getAssetProfileIdentifier({ return `${dataSource}-${symbol}`; } -export function getBackgroundColor(aColorScheme: ColorScheme) { +export function getBackgroundColor(aColorScheme?: ColorScheme) { return getCssVariable( - aColorScheme === 'DARK' || - window.matchMedia('(prefers-color-scheme: dark)').matches - ? '--dark-background' - : '--light-background' + isDarkColorScheme(aColorScheme) ? '--dark-background' : '--light-background' ); } @@ -531,10 +528,9 @@ export function getSum(aArray: Big[]) { return new Big(0); } -export function getTextColor(aColorScheme: ColorScheme) { +export function getTextColor(aColorScheme?: ColorScheme) { const cssVariable = getCssVariable( - aColorScheme === 'DARK' || - window.matchMedia('(prefers-color-scheme: dark)').matches + isDarkColorScheme(aColorScheme) ? '--light-primary-text' : '--dark-primary-text' ); @@ -612,6 +608,14 @@ export function isCurrencySymbol(aSymbol: string) { ); } +export function isDarkColorScheme(aColorScheme?: ColorScheme | null) { + if (aColorScheme) { + return aColorScheme === 'DARK'; + } + + return window.matchMedia('(prefers-color-scheme: dark)').matches; +} + export function isDerivedCurrency(aCurrency: string) { if (aCurrency === 'USX') { return true; diff --git a/libs/ui/src/lib/chart/chart.options.ts b/libs/ui/src/lib/chart/chart.options.ts index cd2802cfb3..277704b76f 100644 --- a/libs/ui/src/lib/chart/chart.options.ts +++ b/libs/ui/src/lib/chart/chart.options.ts @@ -12,7 +12,7 @@ export function getTimeSeriesTooltipOptions({ locale, unit }: { - colorScheme: ColorScheme; + colorScheme?: ColorScheme; currency?: string; groupBy?: GroupBy; locale?: string; diff --git a/libs/ui/src/lib/line-chart/line-chart.component.ts b/libs/ui/src/lib/line-chart/line-chart.component.ts index 5dbb6cc31c..49eae2f5df 100644 --- a/libs/ui/src/lib/line-chart/line-chart.component.ts +++ b/libs/ui/src/lib/line-chart/line-chart.component.ts @@ -4,11 +4,7 @@ import { getValueAxisOptions, getVerticalHoverLinePlugin } from '@ghostfolio/common/chart-helper'; -import { - DEFAULT_COLOR_SCHEME, - primaryColorRgb, - secondaryColorRgb -} from '@ghostfolio/common/config'; +import { primaryColorRgb, secondaryColorRgb } from '@ghostfolio/common/config'; import { getBackgroundColor, getLocale } from '@ghostfolio/common/helper'; import { LineChartItem } from '@ghostfolio/common/interfaces'; import { ColorScheme } from '@ghostfolio/common/types'; @@ -56,7 +52,7 @@ export class GfLineChartComponent { @Input() benchmarkDataItems: LineChartItem[] = []; @Input() benchmarkLabel = ''; - @Input() colorScheme: ColorScheme = DEFAULT_COLOR_SCHEME; + @Input() colorScheme?: ColorScheme; @Input() currency: string; @Input() historicalDataItems: LineChartItem[]; @Input() isAnimated = false; diff --git a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts index e90c446b41..ff61830f04 100644 --- a/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts +++ b/libs/ui/src/lib/portfolio-proportion-chart/portfolio-proportion-chart.component.ts @@ -1,5 +1,5 @@ import { getTooltipOptions } from '@ghostfolio/common/chart-helper'; -import { DEFAULT_COLOR_SCHEME, UNKNOWN_KEY } from '@ghostfolio/common/config'; +import { UNKNOWN_KEY } from '@ghostfolio/common/config'; import { getLocale, getSum, getTextColor } from '@ghostfolio/common/helper'; import { PortfolioPosition } from '@ghostfolio/common/interfaces'; import { ColorScheme } from '@ghostfolio/common/types'; @@ -124,7 +124,7 @@ export class GfPortfolioProportionChartComponent }; } = {}; - const textColor = getTextColor(this.colorScheme ?? DEFAULT_COLOR_SCHEME); + const textColor = getTextColor(this.colorScheme); this.colorMap = { [this.OTHER_KEY]: `rgba(${textColor}, 0.24)`, @@ -448,7 +448,7 @@ export class GfPortfolioProportionChartComponent ): Partial> { return { ...getTooltipOptions({ - colorScheme: this.colorScheme ?? DEFAULT_COLOR_SCHEME, + colorScheme: this.colorScheme, currency: this.baseCurrency, locale: this.locale }), diff --git a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts index 04ea5c65f0..72f34e8027 100644 --- a/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts +++ b/libs/ui/src/lib/treemap-chart/treemap-chart.component.stories.ts @@ -1,5 +1,3 @@ -import { DEFAULT_COLOR_SCHEME } from '@ghostfolio/common/config'; - import { CommonModule } from '@angular/common'; import { moduleMetadata } from '@storybook/angular'; import type { Meta, StoryObj } from '@storybook/angular'; @@ -38,7 +36,7 @@ export const Default: Story = { args: { holdings, baseCurrency: 'USD', - colorScheme: DEFAULT_COLOR_SCHEME, + colorScheme: 'LIGHT', cursor: undefined, dateRange: 'mtd', locale: 'en-US'