Feature/set up notification service (#3663)
* Set up notification service * Update changelog
This commit is contained in:
parent
716f979502
commit
706784f7a0
@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- Set up a notification service for alert and confirmation dialogs
|
||||
|
||||
### Changed
|
||||
|
||||
- Refactored the dark theme CSS selector
|
||||
|
@ -270,6 +270,7 @@ export class AppComponent implements OnDestroy, OnInit {
|
||||
locale: this.user?.settings?.locale
|
||||
},
|
||||
height: this.deviceType === 'mobile' ? '97.5vh' : '80vh',
|
||||
maxWidth: this.deviceType === 'mobile' ? '95vw' : '50rem',
|
||||
width: this.deviceType === 'mobile' ? '100vw' : '50rem'
|
||||
});
|
||||
|
||||
|
@ -33,6 +33,7 @@ import { GfSubscriptionInterstitialDialogModule } from './components/subscriptio
|
||||
import { authInterceptorProviders } from './core/auth.interceptor';
|
||||
import { httpResponseInterceptorProviders } from './core/http-response.interceptor';
|
||||
import { LanguageService } from './core/language.service';
|
||||
import { GfNotificationModule } from './core/notification/notification.module';
|
||||
|
||||
export function NgxStripeFactory(): string {
|
||||
return environment.stripePublicKey;
|
||||
@ -47,6 +48,7 @@ export function NgxStripeFactory(): string {
|
||||
BrowserModule,
|
||||
GfHeaderModule,
|
||||
GfLogoComponent,
|
||||
GfNotificationModule,
|
||||
GfSubscriptionInterstitialDialogModule,
|
||||
MarkdownModule.forRoot(),
|
||||
MatAutocompleteModule,
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { ConfirmationDialogType } from '@ghostfolio/client/core/notification/confirmation-dialog/confirmation-dialog.type';
|
||||
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
|
||||
import { getLocale } from '@ghostfolio/common/helper';
|
||||
|
||||
import {
|
||||
@ -54,7 +56,10 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
|
||||
private unsubscribeSubject = new Subject<void>();
|
||||
|
||||
public constructor(private router: Router) {}
|
||||
public constructor(
|
||||
private notificationService: NotificationService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
public ngOnInit() {}
|
||||
|
||||
@ -97,13 +102,13 @@ export class AccountsTableComponent implements OnChanges, OnDestroy, OnInit {
|
||||
}
|
||||
|
||||
public onDeleteAccount(aId: string) {
|
||||
const confirmation = confirm(
|
||||
$localize`Do you really want to delete this account?`
|
||||
);
|
||||
|
||||
if (confirmation) {
|
||||
this.notificationService.confirm({
|
||||
confirmFn: () => {
|
||||
this.accountDeleted.emit(aId);
|
||||
}
|
||||
},
|
||||
confirmType: ConfirmationDialogType.Warn,
|
||||
title: $localize`Do you really want to delete this account?`
|
||||
});
|
||||
}
|
||||
|
||||
public onOpenAccountDetailDialog(accountId: string) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { NotificationService } from '@ghostfolio/client/core/notification/notification.service';
|
||||
import {
|
||||
getLocale,
|
||||
getNumberFormatDecimal,
|
||||
@ -39,7 +40,7 @@ export class PortfolioPerformanceComponent implements OnChanges {
|
||||
|
||||
@ViewChild('value') value: ElementRef;
|
||||
|
||||
public constructor() {}
|
||||
public constructor(private notificationService: NotificationService) {}
|
||||
|
||||
public ngOnChanges() {
|
||||
this.precision = this.precision >= 0 ? this.precision : 2;
|
||||
@ -74,12 +75,15 @@ export class PortfolioPerformanceComponent implements OnChanges {
|
||||
}
|
||||
|
||||
public onShowErrors() {
|
||||
const errorMessageParts = [$localize`Market data is delayed for`];
|
||||
const errorMessageParts = [];
|
||||
|
||||
for (const error of this.errors) {
|
||||
errorMessageParts.push(`${error.symbol} (${error.dataSource})`);
|
||||
}
|
||||
|
||||
alert(errorMessageParts.join('\n'));
|
||||
this.notificationService.alert({
|
||||
message: errorMessageParts.join('<br />'),
|
||||
title: $localize`Market data is delayed for`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,39 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { DeviceDetectorService } from 'ngx-device-detector';
|
||||
import { Observable, Subject } from 'rxjs';
|
||||
|
||||
import { NotificationService } from './notification/notification.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LayoutService {
|
||||
public static readonly DEFAULT_NOTIFICATION_MAX_WIDTH = '50rem';
|
||||
public static readonly DEFAULT_NOTIFICATION_WIDTH = '75vw';
|
||||
|
||||
public shouldReloadContent$: Observable<void>;
|
||||
|
||||
private shouldReloadSubject = new Subject<void>();
|
||||
|
||||
public constructor() {
|
||||
public constructor(
|
||||
private deviceService: DeviceDetectorService,
|
||||
private notificationService: NotificationService
|
||||
) {
|
||||
this.shouldReloadContent$ = this.shouldReloadSubject.asObservable();
|
||||
|
||||
const deviceType = this.deviceService.getDeviceInfo().deviceType;
|
||||
|
||||
this.notificationService.setDialogWidth(
|
||||
deviceType === 'mobile'
|
||||
? '95vw'
|
||||
: LayoutService.DEFAULT_NOTIFICATION_WIDTH
|
||||
);
|
||||
|
||||
this.notificationService.setDialogMaxWidth(
|
||||
deviceType === 'mobile'
|
||||
? '95vw'
|
||||
: LayoutService.DEFAULT_NOTIFICATION_MAX_WIDTH
|
||||
);
|
||||
}
|
||||
|
||||
public getShouldReloadSubject() {
|
||||
|
@ -0,0 +1,27 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
import { IAlertDialogParams } from './interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
imports: [CommonModule, MatButtonModule, MatDialogModule],
|
||||
selector: 'gf-alert-dialog',
|
||||
standalone: true,
|
||||
styleUrls: ['./alert-dialog.scss'],
|
||||
templateUrl: './alert-dialog.html'
|
||||
})
|
||||
export class GfAlertDialogComponent {
|
||||
public discardLabel: string;
|
||||
public message: string;
|
||||
public title: string;
|
||||
|
||||
public constructor(public dialogRef: MatDialogRef<GfAlertDialogComponent>) {}
|
||||
|
||||
public initialize(aParams: IAlertDialogParams) {
|
||||
this.discardLabel = aParams.discardLabel;
|
||||
this.message = aParams.message;
|
||||
this.title = aParams.title;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
@if (title) {
|
||||
<div mat-dialog-title [innerHTML]="title"></div>
|
||||
}
|
||||
|
||||
@if (message) {
|
||||
<div mat-dialog-content [innerHTML]="message"></div>
|
||||
}
|
||||
|
||||
<div align="end" mat-dialog-actions>
|
||||
<button mat-button (click)="dialogRef.close()">{{ discardLabel }}</button>
|
||||
</div>
|
@ -0,0 +1,2 @@
|
||||
:host {
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
export interface IAlertDialogParams {
|
||||
confirmLabel?: string;
|
||||
discardLabel?: string;
|
||||
message?: string;
|
||||
title: string;
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Component, HostListener } from '@angular/core';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||
|
||||
import { ConfirmationDialogType } from './confirmation-dialog.type';
|
||||
import { IConfirmDialogParams } from './interfaces/interfaces';
|
||||
|
||||
@Component({
|
||||
imports: [CommonModule, MatButtonModule, MatDialogModule],
|
||||
selector: 'gf-confirmation-dialog',
|
||||
standalone: true,
|
||||
styleUrls: ['./confirmation-dialog.scss'],
|
||||
templateUrl: './confirmation-dialog.html'
|
||||
})
|
||||
export class GfConfirmationDialogComponent {
|
||||
public confirmLabel: string;
|
||||
public confirmType: ConfirmationDialogType;
|
||||
public discardLabel: string;
|
||||
public message: string;
|
||||
public title: string;
|
||||
|
||||
public constructor(
|
||||
public dialogRef: MatDialogRef<GfConfirmationDialogComponent>
|
||||
) {}
|
||||
|
||||
@HostListener('window:keyup', ['$event'])
|
||||
public keyEvent(event: KeyboardEvent) {
|
||||
if (event.key === 'Enter') {
|
||||
this.dialogRef.close('confirm');
|
||||
}
|
||||
}
|
||||
|
||||
public initialize(aParams: IConfirmDialogParams) {
|
||||
this.confirmLabel = aParams.confirmLabel;
|
||||
this.confirmType = aParams.confirmType;
|
||||
this.discardLabel = aParams.discardLabel;
|
||||
this.message = aParams.message;
|
||||
this.title = aParams.title;
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
@if (title) {
|
||||
<div mat-dialog-title [innerHTML]="title"></div>
|
||||
}
|
||||
|
||||
@if (message) {
|
||||
<div mat-dialog-content [innerHTML]="message"></div>
|
||||
}
|
||||
|
||||
<div align="end" mat-dialog-actions>
|
||||
<button mat-button (click)="dialogRef.close('discard')">
|
||||
{{ discardLabel }}
|
||||
</button>
|
||||
<button
|
||||
mat-flat-button
|
||||
[color]="confirmType"
|
||||
(click)="dialogRef.close('confirm')"
|
||||
>
|
||||
{{ confirmLabel }}
|
||||
</button>
|
||||
</div>
|
@ -0,0 +1,2 @@
|
||||
:host {
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export enum ConfirmationDialogType {
|
||||
Accent = 'accent',
|
||||
Primary = 'primary',
|
||||
Warn = 'warn'
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
import { ConfirmationDialogType } from '../confirmation-dialog.type';
|
||||
|
||||
export interface IConfirmDialogParams {
|
||||
confirmLabel?: string;
|
||||
confirmType: ConfirmationDialogType;
|
||||
discardLabel?: string;
|
||||
message?: string;
|
||||
title: string;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
import { ConfirmationDialogType } from '../confirmation-dialog/confirmation-dialog.type';
|
||||
|
||||
export interface IAlertParams {
|
||||
discardFn?: () => void;
|
||||
discardLabel?: string;
|
||||
message?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface IConfirmParams {
|
||||
confirmFn: () => void;
|
||||
confirmLabel?: string;
|
||||
confirmType?: ConfirmationDialogType;
|
||||
disableClose?: boolean;
|
||||
discardFn?: () => void;
|
||||
discardLabel?: string;
|
||||
message?: string;
|
||||
title: string;
|
||||
}
|
18
apps/client/src/app/core/notification/notification.module.ts
Normal file
18
apps/client/src/app/core/notification/notification.module.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { MatDialogModule } from '@angular/material/dialog';
|
||||
|
||||
import { GfAlertDialogComponent } from './alert-dialog/alert-dialog.component';
|
||||
import { GfConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
GfAlertDialogComponent,
|
||||
GfConfirmationDialogComponent,
|
||||
MatDialogModule
|
||||
],
|
||||
providers: [NotificationService]
|
||||
})
|
||||
export class GfNotificationModule {}
|
@ -0,0 +1,83 @@
|
||||
import { translate } from '@ghostfolio/ui/i18n';
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { isFunction } from 'lodash';
|
||||
|
||||
import { GfAlertDialogComponent } from './alert-dialog/alert-dialog.component';
|
||||
import { GfConfirmationDialogComponent } from './confirmation-dialog/confirmation-dialog.component';
|
||||
import { ConfirmationDialogType } from './confirmation-dialog/confirmation-dialog.type';
|
||||
import { IAlertParams, IConfirmParams } from './interfaces/interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationService {
|
||||
private dialogMaxWidth: string;
|
||||
private dialogWidth: string;
|
||||
|
||||
public constructor(private matDialog: MatDialog) {}
|
||||
|
||||
public alert(aParams: IAlertParams) {
|
||||
if (!aParams.discardLabel) {
|
||||
aParams.discardLabel = translate('CLOSE');
|
||||
}
|
||||
|
||||
const dialog = this.matDialog.open(GfAlertDialogComponent, {
|
||||
autoFocus: false,
|
||||
maxWidth: this.dialogMaxWidth,
|
||||
width: this.dialogWidth
|
||||
});
|
||||
|
||||
dialog.componentInstance.initialize({
|
||||
discardLabel: aParams.discardLabel,
|
||||
message: aParams.message,
|
||||
title: aParams.title
|
||||
});
|
||||
|
||||
return dialog.afterClosed().subscribe((result) => {
|
||||
if (isFunction(aParams.discardFn)) {
|
||||
aParams.discardFn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public confirm(aParams: IConfirmParams) {
|
||||
if (!aParams.confirmLabel) {
|
||||
aParams.confirmLabel = translate('YES');
|
||||
}
|
||||
|
||||
if (!aParams.discardLabel) {
|
||||
aParams.discardLabel = translate('CANCEL');
|
||||
}
|
||||
|
||||
const dialog = this.matDialog.open(GfConfirmationDialogComponent, {
|
||||
autoFocus: false,
|
||||
disableClose: aParams.disableClose || false,
|
||||
maxWidth: this.dialogMaxWidth,
|
||||
width: this.dialogWidth
|
||||
});
|
||||
|
||||
dialog.componentInstance.initialize({
|
||||
confirmLabel: aParams.confirmLabel,
|
||||
confirmType: aParams.confirmType || ConfirmationDialogType.Primary,
|
||||
discardLabel: aParams.discardLabel,
|
||||
message: aParams.message,
|
||||
title: aParams.title
|
||||
});
|
||||
|
||||
return dialog.afterClosed().subscribe((result) => {
|
||||
if (result === 'confirm' && isFunction(aParams.confirmFn)) {
|
||||
aParams.confirmFn();
|
||||
} else if (result === 'discard' && isFunction(aParams.discardFn)) {
|
||||
aParams.discardFn();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public setDialogMaxWidth(aDialogMaxWidth: string) {
|
||||
this.dialogMaxWidth = aDialogMaxWidth;
|
||||
}
|
||||
|
||||
public setDialogWidth(aDialogWidth: string) {
|
||||
this.dialogWidth = aDialogWidth;
|
||||
}
|
||||
}
|
@ -359,10 +359,6 @@ ngx-skeleton-loader {
|
||||
.cdk-global-overlay-wrapper {
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.cdk-overlay-pane {
|
||||
max-width: 95vw !important;
|
||||
}
|
||||
}
|
||||
|
||||
.cursor-default {
|
||||
|
@ -6,7 +6,9 @@ const locales = {
|
||||
ASSET_CLASS: $localize`Asset Class`,
|
||||
ASSET_SUB_CLASS: $localize`Asset Sub Class`,
|
||||
BUY_AND_SELL_ACTIVITIES_TOOLTIP: $localize`Buy and sell`,
|
||||
CANCEL: $localize`Cancel`,
|
||||
CORE: $localize`Core`,
|
||||
CLOSE: $localize`Close`,
|
||||
DATA_IMPORT_AND_EXPORT_TOOLTIP_BASIC: $localize`Switch to Ghostfolio Premium or Ghostfolio Open Source easily`,
|
||||
DATA_IMPORT_AND_EXPORT_TOOLTIP_OSS: $localize`Switch to Ghostfolio Premium easily`,
|
||||
DATA_IMPORT_AND_EXPORT_TOOLTIP_PREMIUM: $localize`Switch to Ghostfolio Open Source or Ghostfolio Basic easily`,
|
||||
@ -26,6 +28,7 @@ const locales = {
|
||||
TAG: $localize`Tag`,
|
||||
YEAR: $localize`Year`,
|
||||
YEARS: $localize`Years`,
|
||||
YES: $localize`Yes`,
|
||||
|
||||
// Activity types
|
||||
BUY: $localize`Buy`,
|
||||
|
Loading…
x
Reference in New Issue
Block a user