Thomas Kaul 2 days ago
committed by GitHub
parent
commit
f430a85b11
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 6
      CHANGELOG.md
  2. 66
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  3. 8
      apps/client/src/app/components/home-watchlist/home-watchlist.html
  4. 15
      apps/client/src/app/components/home-watchlist/home-watchlist.service.ts
  5. 114
      apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts
  6. 9
      apps/client/src/app/pages/home/home-page.routes.ts
  7. 7
      libs/common/src/lib/routes/routes.ts

6
CHANGELOG.md

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Migrated the create dialog of the watchlist to a dedicated route
## 3.68.0 - 2026-09-06
### Added

66
apps/client/src/app/components/home-watchlist/home-watchlist.component.ts

@ -6,6 +6,7 @@ import {
User
} from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { hasScope, scopes } from '@ghostfolio/common/scopes';
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
import { GfFabComponent } from '@ghostfolio/ui/fab';
@ -23,12 +24,10 @@ import {
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog } from '@angular/material/dialog';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { RouterModule } from '@angular/router';
import { DeviceDetectorService } from 'ngx-device-detector';
import { GfCreateWatchlistItemDialogComponent } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component';
import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces';
import { HomeWatchlistService } from './home-watchlist.service';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
@ -38,6 +37,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
GfPremiumIndicatorComponent,
RouterModule
],
providers: [HomeWatchlistService],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-home-watchlist',
styleUrls: ['./home-watchlist.scss'],
@ -45,6 +45,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
})
export class GfHomeWatchlistComponent implements OnInit {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected readonly internalRoutes = internalRoutes;
protected hasPermissionToCreateWatchlistItem: boolean;
protected hasPermissionToDeleteWatchlistItem: boolean;
@ -59,18 +60,14 @@ export class GfHomeWatchlistComponent implements OnInit {
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly homeWatchlistService = inject(HomeWatchlistService);
private readonly userService = inject(UserService);
public constructor() {
this.route.queryParams
this.homeWatchlistService.refresh$
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((params) => {
if (params['createWatchlistItemDialog']) {
this.openCreateWatchlistItemDialog();
}
.subscribe(() => {
this.loadWatchlistData();
});
this.userService.stateChanged
@ -130,49 +127,4 @@ export class GfHomeWatchlistComponent implements OnInit {
}
});
}
private openCreateWatchlistItemDialog() {
this.userService
.get()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
this.user = user;
if (
!hasPermission(user?.permissions, permissions.createWatchlistItem) ||
!hasScope(user?.scopes, scopes.watchlistCreate)
) {
this.router.navigate(['.'], { relativeTo: this.route });
return;
}
const dialogRef = this.dialog.open<
GfCreateWatchlistItemDialogComponent,
CreateWatchlistItemDialogParams
>(GfCreateWatchlistItemDialogComponent, {
data: {
deviceType: this.deviceType(),
locale: this.user?.settings?.locale ?? DEFAULT_LOCALE
},
width: this.deviceType() === 'mobile' ? '100vw' : '50rem'
});
dialogRef
.afterClosed()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ dataSource, symbol } = {}) => {
if (dataSource && symbol) {
this.dataService
.postWatchlistItem({ dataSource, symbol })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: () => this.loadWatchlistData()
});
}
this.router.navigate(['.'], { relativeTo: this.route });
});
});
}
}

8
apps/client/src/app/components/home-watchlist/home-watchlist.html

@ -22,5 +22,11 @@
</div>
</div>
@if (hasPermissionToCreateWatchlistItem) {
<gf-fab [queryParams]="{ createWatchlistItemDialog: true }" />
<gf-fab
[routerLink]="
internalRoutes.home.subRoutes.watchlist.subRoutes.create.routerLink
"
/>
}
<router-outlet />

15
apps/client/src/app/components/home-watchlist/home-watchlist.service.ts

@ -0,0 +1,15 @@
import { Service } from '@angular/core';
import { Subject } from 'rxjs';
@Service({ autoProvided: false })
export class HomeWatchlistService {
private readonly refreshSubject = new Subject<void>();
public get refresh$() {
return this.refreshSubject.asObservable();
}
public triggerRefresh() {
this.refreshSubject.next();
}
}

114
apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts

@ -0,0 +1,114 @@
import { UserService } from '@ghostfolio/client/services/user/user.service';
import { DEFAULT_LOCALE } from '@ghostfolio/common/config';
import { User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { hasScope, scopes } from '@ghostfolio/common/scopes';
import { DataService } from '@ghostfolio/ui/services';
import {
ChangeDetectionStrategy,
Component,
DestroyRef,
inject,
OnDestroy,
OnInit
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog, MatDialogRef } from '@angular/material/dialog';
import { Router } from '@angular/router';
import { DeviceDetectorService } from 'ngx-device-detector';
import { GfCreateWatchlistItemDialogComponent } from '../create-watchlist-item-dialog/create-watchlist-item-dialog.component';
import { CreateWatchlistItemDialogParams } from '../create-watchlist-item-dialog/interfaces/interfaces';
import { HomeWatchlistService } from '../home-watchlist.service';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-watchlist-dialog-host',
template: ''
})
export class GfWatchlistDialogHostComponent implements OnDestroy, OnInit {
private dialogRef: MatDialogRef<GfCreateWatchlistItemDialogComponent>;
private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog);
private readonly homeWatchlistService = inject(HomeWatchlistService);
private readonly router = inject(Router);
private readonly userService = inject(UserService);
public ngOnInit() {
this.userService
.get()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((user) => {
if (
!hasPermission(user?.permissions, permissions.createWatchlistItem) ||
!hasScope(user?.scopes, scopes.watchlistCreate)
) {
this.navigateBack();
return;
}
this.openDialog(user);
});
}
public ngOnDestroy() {
// The dialog lives in an overlay outside of this component, so it needs to
// be closed explicitly when leaving the route (for example via the browser
// navigation)
this.dialogRef?.close();
}
private navigateBack() {
void this.router.navigate(
internalRoutes.home.subRoutes.watchlist.routerLink
);
}
private openDialog(user: User) {
const deviceType = this.deviceDetectorService.getDeviceInfo().deviceType;
const dialogRef = this.dialog.open<
GfCreateWatchlistItemDialogComponent,
CreateWatchlistItemDialogParams
>(GfCreateWatchlistItemDialogComponent, {
data: {
deviceType,
locale: user?.settings?.locale ?? DEFAULT_LOCALE
},
width: deviceType === 'mobile' ? '100vw' : '50rem'
});
this.dialogRef = dialogRef;
dialogRef
.afterClosed()
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe(({ dataSource, symbol } = {}) => {
if (!dataSource || !symbol) {
this.navigateBack();
return;
}
this.dataService
.postWatchlistItem({ dataSource, symbol })
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
error: () => {
this.navigateBack();
},
next: () => {
this.homeWatchlistService.triggerRefresh();
this.navigateBack();
}
});
});
}
}

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

@ -2,6 +2,7 @@ import { GfHomeHoldingsComponent } from '@ghostfolio/client/components/home-hold
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';
import { GfWatchlistDialogHostComponent } from '@ghostfolio/client/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component';
import { GfMarketsComponent } from '@ghostfolio/client/components/markets/markets.component';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
@ -34,6 +35,14 @@ export const routes: Routes = [
title: internalRoutes.home.subRoutes.markets.title
},
{
children: [
{
component: GfWatchlistDialogHostComponent,
path: internalRoutes.home.subRoutes.watchlist.subRoutes.create.path,
title:
internalRoutes.home.subRoutes.watchlist.subRoutes.create.title
}
],
path: internalRoutes.home.subRoutes.watchlist.path,
component: GfHomeWatchlistComponent,
title: internalRoutes.home.subRoutes.watchlist.title

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

@ -156,6 +156,13 @@ export const internalRoutes = {
watchlist: {
path: 'watchlist',
routerLink: ['/home', 'watchlist'],
subRoutes: {
create: {
path: 'create',
routerLink: ['/home', 'watchlist', 'create'],
title: $localize`Add to Watchlist`
}
},
title: $localize`Watchlist`
}
},

Loading…
Cancel
Save