Browse Source

Task/consolidate markets pages (#7471)

* Consolidate markets pages

* Update changelog
pull/7477/head
Thomas Kaul 1 week ago
committed by GitHub
parent
commit
52fa7c2240
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 5
      apps/api/src/app/user/user.service.ts
  3. 24
      apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html
  4. 8
      apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.scss
  5. 80
      apps/client/src/app/components/home-market/home-market.component.ts
  6. 35
      apps/client/src/app/components/home-market/home-market.html
  7. 122
      apps/client/src/app/components/markets/markets.component.ts
  8. 20
      apps/client/src/app/components/markets/markets.html
  9. 2
      apps/client/src/app/pages/api/api-page.component.ts
  10. 22
      apps/client/src/app/pages/api/api-page.html
  11. 15
      apps/client/src/app/pages/home/home-page.component.ts
  12. 8
      apps/client/src/app/pages/home/home-page.routes.ts
  13. 4
      apps/client/src/app/pages/markets/markets-page.component.ts
  14. 2
      apps/client/src/app/pages/markets/markets-page.html
  15. 6
      libs/common/src/lib/helper.ts
  16. 5
      libs/common/src/lib/routes/routes.ts
  17. 25
      libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.html
  18. 0
      libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss
  19. 39
      libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.stories.ts
  20. 23
      libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.ts
  21. 1
      libs/ui/src/lib/fear-and-greed-index/index.ts
  22. 1
      libs/ui/src/lib/i18n.ts

6
CHANGELOG.md

@ -7,10 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased
### Added
- Added an empty state to the _Fear & Greed Index_ component
- Added a _Storybook_ story for the _Fear & Greed Index_ component
### Changed
- Moved the tags to the overview tab of the account detail dialog (experimental)
- Moved the tags to the overview tab of the holding detail dialog
- Consolidated the markets pages into a single route where the _Fear & Greed Index_ is controlled by permission
- Refactored the line chart components to share the common chart configuration
- Improved the language localization for Spanish (`es`)
- Improved the language localization for Ukrainian (`uk`)

5
apps/api/src/app/user/user.service.ts

@ -555,7 +555,10 @@ export class UserService {
}
} else {
if (
await this.propertyService.getByKey<string>(PROPERTY_API_KEY_GHOSTFOLIO)
this.configurationService.get('ENABLE_FEATURE_FEAR_AND_GREED_INDEX') ||
(await this.propertyService.getByKey<string>(
PROPERTY_API_KEY_GHOSTFOLIO
))
) {
currentPermissions.push(permissions.readMarketDataOfMarkets);
}

24
apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.html

@ -1,24 +0,0 @@
<div class="position-relative">
<div class="align-items-center d-flex flex-row" [hidden]="!fearAndGreedIndex">
<div class="h2 mb-0 mr-2">{{ fearAndGreedIndexEmoji }}</div>
<div>
<div class="h4 mb-0">
<span class="mr-2">{{ fearAndGreedIndexText }}</span>
<small class="text-muted"
><strong>{{ fearAndGreedIndex | number: '1.0-0' }}</strong
>/100</small
>
</div>
<small class="d-block" i18n>Current Market Mood</small>
</div>
</div>
@if (!fearAndGreedIndex) {
<ngx-skeleton-loader
animation="pulse"
class="position-absolute w-100"
[theme]="{
height: '100%'
}"
/>
}
</div>

8
apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.scss

@ -1,8 +0,0 @@
:host {
display: block;
ngx-skeleton-loader {
bottom: 0;
top: 0;
}
}

80
apps/client/src/app/components/home-market/home-market.component.ts

@ -1,80 +0,0 @@
import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { Benchmark, InfoItem, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
import { DataService } from '@ghostfolio/ui/services';
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
inject,
OnInit,
signal
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DeviceDetectorService } from 'ngx-device-detector';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [GfBenchmarkComponent, GfFearAndGreedIndexComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-home-market',
styleUrls: ['./home-market.scss'],
templateUrl: './home-market.html'
})
export class GfHomeMarketComponent implements OnInit {
protected readonly benchmarks = signal<Benchmark[]>([]);
protected readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
);
protected fearAndGreedIndex: number | undefined;
protected hasPermissionToAccessFearAndGreedIndex: boolean;
protected user: User;
private readonly info: InfoItem;
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly userService = inject(UserService);
public constructor() {
this.info = this.dataService.fetchInfo();
this.userService.stateChanged
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((state) => {
if (state?.user) {
this.user = state.user;
this.changeDetectorRef.markForCheck();
}
});
}
public ngOnInit() {
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
if (this.hasPermissionToAccessFearAndGreedIndex) {
this.fearAndGreedIndex = this.info.fearAndGreedStocksMarketPrice;
}
this.dataService
.fetchBenchmarks()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ benchmarks }) => {
this.benchmarks.set(benchmarks);
});
}
}

35
apps/client/src/app/components/home-market/home-market.html

@ -1,35 +0,0 @@
<div class="container">
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1>
@if (hasPermissionToAccessFearAndGreedIndex) {
<div class="mb-4 row">
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-fear-and-greed-index
class="d-flex justify-content-center"
[fearAndGreedIndex]="fearAndGreedIndex"
/>
</div>
</div>
}
<div class="mb-3 row">
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-benchmark
[benchmarks]="benchmarks()"
[deviceType]="deviceType()"
[locale]="user?.settings?.locale || undefined"
[showSymbol]="false"
[user]="user"
/>
@if (benchmarks()?.length > 0) {
<div
class="gf-text-wrap-balance line-height-1 mt-3 text-center text-muted"
>
<small i18n>
Calculations are based on delayed market data and may not be
displayed in real-time.</small
>
</div>
}
</div>
</div>
</div>

122
apps/client/src/app/components/markets/markets.component.ts

@ -1,15 +1,17 @@
import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { resetHours } from '@ghostfolio/common/helper';
import {
Benchmark,
HistoricalDataItem,
InfoItem,
MarketDataOfMarketsResponse,
ToggleOption,
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { FearAndGreedIndexMode } from '@ghostfolio/common/types';
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
import { GfFearAndGreedIndexComponent } from '@ghostfolio/ui/fear-and-greed-index';
import { GfLineChartComponent } from '@ghostfolio/ui/line-chart';
import { DataService } from '@ghostfolio/ui/services';
import { GfToggleComponent } from '@ghostfolio/ui/toggle';
@ -18,9 +20,12 @@ import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
computed,
CUSTOM_ELEMENTS_SCHEMA,
DestroyRef,
OnInit
inject,
OnInit,
signal
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { DeviceDetectorService } from 'ngx-device-detector';
@ -39,29 +44,50 @@ import { DeviceDetectorService } from 'ngx-device-detector';
templateUrl: './markets.html'
})
export class GfMarketsComponent implements OnInit {
public benchmarks: Benchmark[];
public deviceType: string;
public fearAndGreedIndex: number;
public fearAndGreedIndexData: MarketDataOfMarketsResponse['fearAndGreedIndex'];
public fearLabel = $localize`Fear`;
public greedLabel = $localize`Greed`;
public historicalDataItems: HistoricalDataItem[];
public fearAndGreedIndexMode: FearAndGreedIndexMode = 'STOCKS';
public fearAndGreedIndexModeOptions: ToggleOption[] = [
protected readonly benchmarks = signal<Benchmark[]>([]);
protected readonly deviceType = computed(
() => this.deviceDetectorService.deviceInfo().deviceType
);
protected readonly fearAndGreedIndexModeOptions: ToggleOption[] = [
{ label: $localize`Stocks`, value: 'STOCKS' },
{ label: $localize`Cryptocurrencies`, value: 'CRYPTOCURRENCIES' }
];
public readonly numberOfDays = 365;
public user: User;
public constructor(
private changeDetectorRef: ChangeDetectorRef,
private dataService: DataService,
private destroyRef: DestroyRef,
private deviceDetectorService: DeviceDetectorService,
private userService: UserService
) {
this.deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
protected readonly fearLabel = $localize`Fear`;
protected readonly greedLabel = $localize`Greed`;
protected readonly numberOfDays = 365;
protected fearAndGreedIndex: number | undefined;
protected fearAndGreedIndexMode: FearAndGreedIndexMode = 'STOCKS';
protected hasPermissionToAccessFearAndGreedIndex: boolean;
protected hasPermissionToReadMarketDataOfMarkets: boolean;
protected historicalDataItems: HistoricalDataItem[];
protected isLoadingFearAndGreedIndex = true;
protected user: User;
private fearAndGreedIndexData: MarketDataOfMarketsResponse['fearAndGreedIndex'];
private readonly info: InfoItem;
private readonly changeDetectorRef = inject(ChangeDetectorRef);
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly userService = inject(UserService);
public constructor() {
this.info = this.dataService.fetchInfo();
this.hasPermissionToAccessFearAndGreedIndex = hasPermission(
this.info?.globalPermissions,
permissions.enableFearAndGreedIndex
);
if (this.hasPermissionToAccessFearAndGreedIndex) {
this.fearAndGreedIndex = this.info.fearAndGreedStocksMarketPrice;
}
this.userService.stateChanged
.pipe(takeUntilDestroyed(this.destroyRef))
@ -69,6 +95,20 @@ export class GfMarketsComponent implements OnInit {
if (state?.user) {
this.user = state.user;
this.hasPermissionToReadMarketDataOfMarkets = hasPermission(
this.user.permissions,
permissions.readMarketDataOfMarkets
);
if (
this.hasPermissionToReadMarketDataOfMarkets &&
!this.fearAndGreedIndexData
) {
this.fetchMarketDataOfMarkets();
} else {
this.isLoadingFearAndGreedIndex = false;
}
this.changeDetectorRef.markForCheck();
}
});
@ -76,27 +116,37 @@ export class GfMarketsComponent implements OnInit {
public ngOnInit() {
this.dataService
.fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays })
.fetchBenchmarks()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ fearAndGreedIndex }) => {
this.fearAndGreedIndexData = fearAndGreedIndex;
.subscribe(({ benchmarks }) => {
this.benchmarks.set(benchmarks);
});
}
this.initialize();
protected onChangeFearAndGreedIndexMode(
aFearAndGreedIndexMode: FearAndGreedIndexMode
) {
this.fearAndGreedIndexMode = aFearAndGreedIndexMode;
this.changeDetectorRef.markForCheck();
});
this.initializeFearAndGreedIndex();
}
private fetchMarketDataOfMarkets() {
this.dataService
.fetchBenchmarks()
.fetchMarketDataOfMarkets({ includeHistoricalData: this.numberOfDays })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ benchmarks }) => {
this.benchmarks = benchmarks;
.subscribe(({ fearAndGreedIndex }) => {
this.fearAndGreedIndexData = fearAndGreedIndex;
this.initializeFearAndGreedIndex();
this.isLoadingFearAndGreedIndex = false;
this.changeDetectorRef.markForCheck();
});
}
public initialize() {
private initializeFearAndGreedIndex() {
this.fearAndGreedIndex =
this.fearAndGreedIndexData[this.fearAndGreedIndexMode]?.marketPrice;
@ -109,12 +159,4 @@ export class GfMarketsComponent implements OnInit {
}
];
}
public onChangeFearAndGreedIndexMode(
aFearAndGreedIndexMode: FearAndGreedIndexMode
) {
this.fearAndGreedIndexMode = aFearAndGreedIndexMode;
this.initialize();
}
}

20
apps/client/src/app/components/markets/markets.html

@ -1,9 +1,16 @@
<div class="container">
<h1 class="d-none d-sm-block h3 mb-4 text-center" i18n>Markets</h1>
<div class="mb-5 row">
@if (
hasPermissionToAccessFearAndGreedIndex ||
hasPermissionToReadMarketDataOfMarkets
) {
<div class="mb-4 row">
<div class="col-xs-12 col-md-10 offset-md-1">
@if (hasPermissionToReadMarketDataOfMarkets) {
@if (user?.settings?.isExperimentalFeatures) {
<div class="align-items-center d-flex flex-grow-1 justify-content-end">
<div
class="align-items-center d-flex flex-grow-1 justify-content-end"
>
<gf-toggle
class="d-none d-lg-block"
[defaultValue]="fearAndGreedIndexMode"
@ -30,23 +37,26 @@
[yMin]="0"
[yMinLabel]="fearLabel"
/>
}
<gf-fear-and-greed-index
class="d-flex justify-content-center"
[fearAndGreedIndex]="fearAndGreedIndex"
[isLoading]="isLoadingFearAndGreedIndex"
/>
</div>
</div>
}
<div class="mb-3 row">
<div class="col-xs-12 col-md-10 offset-md-1">
<gf-benchmark
[benchmarks]="benchmarks"
[deviceType]="deviceType"
[benchmarks]="benchmarks()"
[deviceType]="deviceType()"
[locale]="user?.settings?.locale || undefined"
[showSymbol]="false"
[user]="user"
/>
@if (benchmarks?.length > 0) {
@if (benchmarks()?.length > 0) {
<div
class="gf-text-wrap-balance line-height-1 mt-3 text-center text-muted"
>

2
apps/client/src/app/pages/api/api-page.component.ts

@ -1,4 +1,3 @@
import { GfFearAndGreedIndexComponent } from '@ghostfolio/client/components/fear-and-greed-index/fear-and-greed-index.component';
import {
HEADER_KEY_SKIP_INTERCEPTOR,
HEADER_KEY_TOKEN
@ -14,6 +13,7 @@ import {
MarketDataOfMarketsResponse,
QuotesResponse
} from '@ghostfolio/common/interfaces';
import { GfFearAndGreedIndexComponent } from '@ghostfolio/ui/fear-and-greed-index';
import { CommonModule } from '@angular/common';
import {

22
apps/client/src/app/pages/api/api-page.html

@ -179,16 +179,12 @@
<mat-card-content class="pt-3">
@if (isFetchFailure(marketDataOfMarkets)) {
🔴 {{ marketDataOfMarkets.fetchError }}
} @else if (marketDataOfMarkets) {
} @else {
<gf-fear-and-greed-index
[fearAndGreedIndex]="
marketDataOfMarkets.fearAndGreedIndex.STOCKS.marketPrice
marketDataOfMarkets?.fearAndGreedIndex?.STOCKS?.marketPrice
"
/>
} @else {
<ngx-skeleton-loader
animation="pulse"
[theme]="{ height: '2.5rem', width: '100%' }"
[isLoading]="!marketDataOfMarkets"
/>
}
</mat-card-content>
@ -203,17 +199,13 @@
<mat-card-content class="pt-3">
@if (isFetchFailure(marketDataOfMarkets)) {
🔴 {{ marketDataOfMarkets.fetchError }}
} @else if (marketDataOfMarkets) {
} @else {
<gf-fear-and-greed-index
[fearAndGreedIndex]="
marketDataOfMarkets.fearAndGreedIndex.CRYPTOCURRENCIES
.marketPrice
marketDataOfMarkets?.fearAndGreedIndex?.CRYPTOCURRENCIES
?.marketPrice
"
/>
} @else {
<ngx-skeleton-loader
animation="pulse"
[theme]="{ height: '2.5rem', width: '100%' }"
[isLoading]="!marketDataOfMarkets"
/>
}
</mat-card-content>

15
apps/client/src/app/pages/home/home-page.component.ts

@ -1,7 +1,6 @@
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import {
GfPageTabsComponent,
@ -73,18 +72,8 @@ export class GfHomePageComponent implements OnInit {
},
{
iconName: 'newspaper-outline',
label: hasPermission(
this.user?.permissions,
permissions.readMarketDataOfMarkets
)
? internalRoutes.home.subRoutes.marketsPremium.title
: internalRoutes.home.subRoutes.markets.title,
routerLink: hasPermission(
this.user?.permissions,
permissions.readMarketDataOfMarkets
)
? internalRoutes.home.subRoutes.marketsPremium.routerLink
: internalRoutes.home.subRoutes.markets.routerLink
label: internalRoutes.home.subRoutes.markets.title,
routerLink: internalRoutes.home.subRoutes.markets.routerLink
}
];
}

8
apps/client/src/app/pages/home/home-page.routes.ts

@ -1,5 +1,4 @@
import { GfHomeHoldingsComponent } from '@ghostfolio/client/components/home-holdings/home-holdings.component';
import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
import { GfHomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component';
import { GfHomeWatchlistComponent } from '@ghostfolio/client/components/home-watchlist/home-watchlist.component';
@ -31,13 +30,8 @@ export const routes: Routes = [
},
{
path: internalRoutes.home.subRoutes.markets.path,
component: GfHomeMarketComponent,
title: internalRoutes.home.subRoutes.markets.title
},
{
path: internalRoutes.home.subRoutes.marketsPremium.path,
component: GfMarketsComponent,
title: internalRoutes.home.subRoutes.marketsPremium.title
title: internalRoutes.home.subRoutes.markets.title
},
{
path: internalRoutes.home.subRoutes.watchlist.path,

4
apps/client/src/app/pages/markets/markets-page.component.ts

@ -1,11 +1,11 @@
import { GfHomeMarketComponent } from '@ghostfolio/client/components/home-market/home-market.component';
import { GfMarketsComponent } from '@ghostfolio/client/components/markets/markets.component';
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'page' },
imports: [GfHomeMarketComponent],
imports: [GfMarketsComponent],
selector: 'gf-markets-page',
styleUrls: ['./markets-page.scss'],
templateUrl: './markets-page.html'

2
apps/client/src/app/pages/markets/markets-page.html

@ -1,7 +1,7 @@
<div class="container">
<div class="row">
<div class="col">
<gf-home-market />
<gf-markets />
</div>
</div>
</div>

6
libs/common/src/lib/helper.ts

@ -560,8 +560,10 @@ export function resetHours(aDate: Date) {
return new Date(Date.UTC(year, month, day));
}
export function resolveFearAndGreedIndex(aValue: number) {
if (aValue <= 25) {
export function resolveFearAndGreedIndex(aValue?: number) {
if (isNil(aValue)) {
return { emoji: '⚪', key: 'UNKNOWN', text: 'Unknown' };
} else if (aValue <= 25) {
return { emoji: '🥵', key: 'EXTREME_FEAR', text: 'Extreme Fear' };
} else if (aValue <= 45) {
return { emoji: '😨', key: 'FEAR', text: 'Fear' };

5
libs/common/src/lib/routes/routes.ts

@ -94,11 +94,6 @@ export const internalRoutes = {
routerLink: ['/home', 'markets'],
title: $localize`Markets`
},
marketsPremium: {
path: 'markets-premium',
routerLink: ['/home', 'markets-premium'],
title: $localize`Markets`
},
summary: {
path: 'summary',
routerLink: ['/home', 'summary'],

25
libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.html

@ -0,0 +1,25 @@
@if (isLoading() && !fearAndGreedIndex()) {
<ngx-skeleton-loader
animation="pulse"
class="w-100"
[theme]="{
height: '2.5rem'
}"
/>
} @else {
<div class="align-items-center d-flex flex-row">
<div class="h2 mb-0 mr-2">{{ fearAndGreedIndexEmoji() }}</div>
<div>
<div class="h4 mb-0">
<span class="mr-2">{{ fearAndGreedIndexText() }}</span>
<small class="text-muted"
><strong>{{
(fearAndGreedIndex() | number: '1.0-0') ?? placeholder
}}</strong
>/100</small
>
</div>
<small class="d-block" i18n>Current Market Mood</small>
</div>
</div>
}

0
apps/client/src/app/components/home-market/home-market.scss → libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.scss

39
libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.stories.ts

@ -0,0 +1,39 @@
import '@angular/localize/init';
import { moduleMetadata } from '@storybook/angular';
import type { Meta, StoryObj } from '@storybook/angular';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
import { GfFearAndGreedIndexComponent } from './fear-and-greed-index.component';
export default {
title: 'Fear & Greed Index',
component: GfFearAndGreedIndexComponent,
decorators: [
moduleMetadata({
imports: [NgxSkeletonLoaderModule]
})
]
} as Meta<GfFearAndGreedIndexComponent>;
type Story = StoryObj<GfFearAndGreedIndexComponent>;
export const Loading: Story = {
args: {
fearAndGreedIndex: undefined,
isLoading: true
}
};
export const Default: Story = {
args: {
fearAndGreedIndex: 50,
isLoading: false
}
};
export const Unknown: Story = {
args: {
fearAndGreedIndex: undefined,
isLoading: false
}
};

23
apps/client/src/app/components/fear-and-greed-index/fear-and-greed-index.component.ts → libs/ui/src/lib/fear-and-greed-index/fear-and-greed-index.component.ts

@ -5,8 +5,8 @@ import { DecimalPipe } from '@angular/common';
import {
ChangeDetectionStrategy,
Component,
Input,
OnChanges
computed,
input
} from '@angular/core';
import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
@ -17,16 +17,17 @@ import { NgxSkeletonLoaderModule } from 'ngx-skeleton-loader';
styleUrls: ['./fear-and-greed-index.component.scss'],
templateUrl: './fear-and-greed-index.component.html'
})
export class GfFearAndGreedIndexComponent implements OnChanges {
@Input() fearAndGreedIndex: number;
export class GfFearAndGreedIndexComponent {
public readonly fearAndGreedIndex = input<number>();
public readonly isLoading = input<boolean>(false);
public fearAndGreedIndexEmoji: string;
public fearAndGreedIndexText: string;
protected readonly placeholder = '—';
public ngOnChanges() {
const { emoji, key } = resolveFearAndGreedIndex(this.fearAndGreedIndex);
protected readonly fearAndGreedIndexEmoji = computed(() => {
return resolveFearAndGreedIndex(this.fearAndGreedIndex()).emoji;
});
this.fearAndGreedIndexEmoji = emoji;
this.fearAndGreedIndexText = translate(key);
}
protected readonly fearAndGreedIndexText = computed(() => {
return translate(resolveFearAndGreedIndex(this.fearAndGreedIndex()).key);
});
}

1
libs/ui/src/lib/fear-and-greed-index/index.ts

@ -0,0 +1 @@
export * from './fear-and-greed-index.component';

1
libs/ui/src/lib/i18n.ts

@ -85,6 +85,7 @@ const locales = {
FEAR: $localize`Fear`,
GREED: $localize`Greed`,
NEUTRAL: $localize`Neutral`,
UNKNOWN: $localize`Unknown`,
// Sectors
'Basic Materials': $localize`Basic Materials`,

Loading…
Cancel
Save