Browse Source

Add support for country codes to entity logo component

pull/7884/head
Thomas Kaul 1 month ago
parent
commit
7d5a95f9ea
  1. 13
      libs/ui/src/lib/currency-selector/currency-selector.component.html
  2. 15
      libs/ui/src/lib/currency-selector/currency-selector.component.ts
  3. 17
      libs/ui/src/lib/entity-logo/entity-logo.component.html
  4. 12
      libs/ui/src/lib/entity-logo/entity-logo.component.scss
  5. 8
      libs/ui/src/lib/entity-logo/entity-logo.component.stories.ts
  6. 5
      libs/ui/src/lib/entity-logo/entity-logo.component.ts
  7. 1
      libs/ui/src/lib/holdings-table/holdings-table.component.html
  8. 2
      libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts
  9. 12
      libs/ui/src/lib/holdings-table/holdings-table.component.ts
  10. 46
      libs/ui/src/lib/mocks/holdings.ts

13
libs/ui/src/lib/currency-selector/currency-selector.component.html

@ -1,5 +1,8 @@
@if (emojiFlagOfSelectedCurrency) { @if (countryCodeOfSelectedCurrency) {
<span class="flex-shrink-0 mr-1">{{ emojiFlagOfSelectedCurrency }}</span> <gf-entity-logo
class="flex-shrink-0 mr-1"
[countryCode]="countryCodeOfSelectedCurrency"
/>
} }
<input <input
@ -19,7 +22,11 @@
@for (currency of filteredCurrencies; track currency) { @for (currency of filteredCurrencies; track currency) {
<mat-option class="line-height-1" [value]="currency"> <mat-option class="line-height-1" [value]="currency">
<span class="align-items-center d-flex"> <span class="align-items-center d-flex">
<span class="mr-1">{{ getEmojiFlagFromCurrency(currency) }}</span> <gf-entity-logo
class="mr-1"
[countryCode]="getCountryCodeFromCurrency(currency)"
[hasPlaceholder]="true"
/>
<span>{{ currency }}</span> <span>{{ currency }}</span>
</span> </span>
</mat-option> </mat-option>

15
libs/ui/src/lib/currency-selector/currency-selector.component.ts

@ -1,7 +1,4 @@
import { import { getCountryCodeFromCurrency } from '@ghostfolio/common/helper';
getCountryCodeFromCurrency,
getEmojiFlag
} from '@ghostfolio/common/helper';
import { FocusMonitor } from '@angular/cdk/a11y'; import { FocusMonitor } from '@angular/cdk/a11y';
import { import {
@ -40,6 +37,7 @@ import {
import { MatInput, MatInputModule } from '@angular/material/input'; import { MatInput, MatInputModule } from '@angular/material/input';
import { map, startWith } from 'rxjs/operators'; import { map, startWith } from 'rxjs/operators';
import { GfEntityLogoComponent } from '../entity-logo/entity-logo.component';
import { AbstractMatFormField } from '../shared/abstract-mat-form-field'; import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
@Component({ @Component({
@ -51,6 +49,7 @@ import { AbstractMatFormField } from '../shared/abstract-mat-form-field';
}, },
imports: [ imports: [
FormsModule, FormsModule,
GfEntityLogoComponent,
MatAutocompleteModule, MatAutocompleteModule,
MatFormFieldModule, MatFormFieldModule,
MatInputModule, MatInputModule,
@ -99,12 +98,12 @@ export class GfCurrencySelectorComponent
return { elementRef: this.formField.getConnectedOverlayOrigin() }; return { elementRef: this.formField.getConnectedOverlayOrigin() };
} }
public get emojiFlagOfSelectedCurrency() { public get countryCodeOfSelectedCurrency() {
const selectedCurrency = this.currencies().find((currency) => { const selectedCurrency = this.currencies().find((currency) => {
return currency === this.control.value; return currency === this.control.value;
}); });
return this.getEmojiFlagFromCurrency(selectedCurrency); return getCountryCodeFromCurrency(selectedCurrency);
} }
public override get empty() { public override get empty() {
@ -126,8 +125,8 @@ export class GfCurrencySelectorComponent
this.input().focus(); this.input().focus();
} }
public getEmojiFlagFromCurrency(aCurrency = '') { public getCountryCodeFromCurrency(aCurrency = '') {
return getEmojiFlag(getCountryCodeFromCurrency(aCurrency)); return getCountryCodeFromCurrency(aCurrency);
} }
public ngOnInit() { public ngOnInit() {

17
libs/ui/src/lib/entity-logo/entity-logo.component.html

@ -1,4 +1,19 @@
@if (src && !hasError) { @if (countryCode) {
<span
class="cursor-default d-block logo overflow-hidden position-relative"
role="img"
[attr.aria-label]="tooltip ? tooltip : countryCode"
[class.large]="size === 'large'"
[title]="tooltip ? tooltip : ''"
>
<span
aria-hidden="true"
class="align-items-center d-flex emoji justify-content-center line-height-1 position-absolute"
>
{{ getEmojiFlag(countryCode) }}
</span>
</span>
} @else if (src && !hasError) {
<img <img
class="logo" class="logo"
[class.large]="size === 'large'" [class.large]="size === 'large'"

12
libs/ui/src/lib/entity-logo/entity-logo.component.scss

@ -1,7 +1,4 @@
:host { :host {
align-items: center;
display: flex;
.logo { .logo {
border-radius: 0.2rem; border-radius: 0.2rem;
height: 0.8rem; height: 0.8rem;
@ -10,6 +7,15 @@
&.large { &.large {
height: 1.4rem; height: 1.4rem;
width: 1.4rem; width: 1.4rem;
.emoji {
font-size: 1.9rem;
}
}
.emoji {
font-size: 1.1rem;
inset: 0;
} }
} }
} }

8
libs/ui/src/lib/entity-logo/entity-logo.component.stories.ts

@ -28,6 +28,14 @@ export default {
type Story = StoryObj<GfEntityLogoComponent>; type Story = StoryObj<GfEntityLogoComponent>;
export const FlagByCountryCode: Story = {
args: {
countryCode: 'US',
size: 'large',
tooltip: 'United States'
}
};
export const LogoByAssetProfileIdentifier: Story = { export const LogoByAssetProfileIdentifier: Story = {
args: { args: {
dataSource: 'YAHOO', dataSource: 'YAHOO',

5
libs/ui/src/lib/entity-logo/entity-logo.component.ts

@ -1,3 +1,4 @@
import { getEmojiFlag } from '@ghostfolio/common/helper';
import { EntityLogoImageSourceService } from '@ghostfolio/ui/entity-logo/entity-logo-image-source.service'; import { EntityLogoImageSourceService } from '@ghostfolio/ui/entity-logo/entity-logo-image-source.service';
import { import {
@ -11,12 +12,14 @@ import { DataSource } from '@prisma/client';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'align-items-center d-flex' },
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-entity-logo', selector: 'gf-entity-logo',
styleUrls: ['./entity-logo.component.scss'], styleUrls: ['./entity-logo.component.scss'],
templateUrl: './entity-logo.component.html' templateUrl: './entity-logo.component.html'
}) })
export class GfEntityLogoComponent implements OnChanges { export class GfEntityLogoComponent implements OnChanges {
@Input() countryCode: string;
@Input() dataSource: DataSource; @Input() dataSource: DataSource;
@Input() hasPlaceholder = false; @Input() hasPlaceholder = false;
@Input() size: 'large'; @Input() size: 'large';
@ -27,6 +30,8 @@ export class GfEntityLogoComponent implements OnChanges {
public hasError = false; public hasError = false;
public src?: string; public src?: string;
public readonly getEmojiFlag = getEmojiFlag;
public constructor( public constructor(
private readonly imageSourceService: EntityLogoImageSourceService private readonly imageSourceService: EntityLogoImageSourceService
) {} ) {}

1
libs/ui/src/lib/holdings-table/holdings-table.component.html

@ -17,6 +17,7 @@
[class.text-nowrap]="mode() === 'simple'" [class.text-nowrap]="mode() === 'simple'"
> >
<gf-entity-logo <gf-entity-logo
[countryCode]="getCountryCodeForCashPosition(element)"
[dataSource]="element.assetProfile.dataSource" [dataSource]="element.assetProfile.dataSource"
[symbol]="element.assetProfile.symbol" [symbol]="element.assetProfile.symbol"
[tooltip]="element.assetProfile.name" [tooltip]="element.assetProfile.name"

2
libs/ui/src/lib/holdings-table/holdings-table.component.stories.ts

@ -45,10 +45,10 @@ type Story = StoryObj<GfHoldingsTableComponent>;
export const Loading: Story = { export const Loading: Story = {
args: { args: {
holdings: undefined,
hasPermissionToOpenDetails: false, hasPermissionToOpenDetails: false,
hasPermissionToShowQuantities: true, hasPermissionToShowQuantities: true,
hasPermissionToShowValues: true, hasPermissionToShowValues: true,
holdings: undefined,
locale: 'en-US', locale: 'en-US',
pageSize: Number.MAX_SAFE_INTEGER pageSize: Number.MAX_SAFE_INTEGER
} }

12
libs/ui/src/lib/holdings-table/holdings-table.component.ts

@ -1,7 +1,9 @@
import { import {
canOpenHoldingDetail, canOpenHoldingDetail,
getCountryCodeFromCurrency,
getLocale, getLocale,
getLowercase getLowercase,
isCashPosition
} from '@ghostfolio/common/helper'; } from '@ghostfolio/common/helper';
import { import {
AssetProfileIdentifier, AssetProfileIdentifier,
@ -120,6 +122,14 @@ export class GfHoldingsTableComponent {
return this.hasPermissionToOpenDetails() && canOpenHoldingDetail(holding); return this.hasPermissionToOpenDetails() && canOpenHoldingDetail(holding);
} }
protected getCountryCodeForCashPosition({
assetProfile
}: PortfolioPosition): string {
return isCashPosition(assetProfile)
? getCountryCodeFromCurrency(assetProfile.currency)
: '';
}
protected onOpenHoldingDialog({ protected onOpenHoldingDialog({
dataSource, dataSource,
symbol symbol

46
libs/ui/src/lib/mocks/holdings.ts

@ -3,7 +3,7 @@ import { PortfolioPosition } from '@ghostfolio/common/interfaces';
export const holdings: PortfolioPosition[] = [ export const holdings: PortfolioPosition[] = [
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.042990776363386086, allocationInPercentage: 0.06447799495516787,
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity', assetClassLabel: 'Equity',
@ -48,7 +48,7 @@ export const holdings: PortfolioPosition[] = [
}, },
{ {
activitiesCount: 2, activitiesCount: 2,
allocationInPercentage: 0.02377401948293552, allocationInPercentage: 0.03565651141834935,
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity', assetClassLabel: 'Equity',
@ -93,7 +93,7 @@ export const holdings: PortfolioPosition[] = [
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.08038536990007467, allocationInPercentage: 0.12056277911976934,
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity', assetClassLabel: 'Equity',
@ -138,7 +138,7 @@ export const holdings: PortfolioPosition[] = [
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.19216416482928922, allocationInPercentage: 0.28820973005222217,
assetProfile: { assetProfile: {
assetClass: 'LIQUIDITY', assetClass: 'LIQUIDITY',
assetClassLabel: 'Liquidity', assetClassLabel: 'Liquidity',
@ -171,7 +171,7 @@ export const holdings: PortfolioPosition[] = [
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.04307127421937313, allocationInPercentage: 0.06459872644204223,
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity', assetClassLabel: 'Equity',
@ -216,7 +216,7 @@ export const holdings: PortfolioPosition[] = [
}, },
{ {
activitiesCount: 1, activitiesCount: 1,
allocationInPercentage: 0.18762679306394897, allocationInPercentage: 0.28140453464652826,
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity', assetClassLabel: 'Equity',
@ -261,7 +261,7 @@ export const holdings: PortfolioPosition[] = [
}, },
{ {
activitiesCount: 5, activitiesCount: 5,
allocationInPercentage: 0.053051250766657634, allocationInPercentage: 0.0795667947557967,
assetProfile: { assetProfile: {
assetClass: 'EQUITY', assetClass: 'EQUITY',
assetClassLabel: 'Equity', assetClassLabel: 'Equity',
@ -303,5 +303,37 @@ export const holdings: PortfolioPosition[] = [
quantity: 50, quantity: 50,
tags: [], tags: [],
valueInBaseCurrency: 15092 valueInBaseCurrency: 15092
},
{
activitiesCount: 3,
allocationInPercentage: 0.06552292861012407,
assetProfile: {
assetClass: 'LIQUIDITY',
assetClassLabel: 'Liquidity',
assetSubClass: 'CASH',
assetSubClassLabel: 'Cash',
countries: [],
currency: 'USD',
dataSource: 'YAHOO',
holdings: [],
name: 'USD',
sectors: [],
symbol: 'USD'
},
dateOfFirstActivity: new Date('2024-01-01T00:00:00.000Z'),
dividend: 0,
grossPerformance: 0,
grossPerformancePercent: 0,
grossPerformancePercentWithCurrencyEffect: 0,
grossPerformanceWithCurrencyEffect: 0,
investment: 12428.2,
marketPrice: 1,
netPerformance: 0,
netPerformancePercent: 0,
netPerformancePercentWithCurrencyEffect: 0,
netPerformanceWithCurrencyEffect: 0,
quantity: 12428.2,
tags: [],
valueInBaseCurrency: 12428.2
} }
]; ];

Loading…
Cancel
Save