Browse Source

Migrate create dialog to dedicated route

pull/7837/head
Thomas Kaul 2 days ago
parent
commit
c9b5e24ceb
  1. 66
      apps/client/src/app/components/home-watchlist/home-watchlist.component.ts
  2. 8
      apps/client/src/app/components/home-watchlist/home-watchlist.html
  3. 15
      apps/client/src/app/components/home-watchlist/home-watchlist.service.ts
  4. 114
      apps/client/src/app/components/home-watchlist/watchlist-dialog-host/watchlist-dialog-host.component.ts
  5. 9
      apps/client/src/app/pages/home/home-page.routes.ts
  6. 7
      libs/common/src/lib/routes/routes.ts

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

@ -6,6 +6,7 @@ import {
User User
} from '@ghostfolio/common/interfaces'; } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { internalRoutes } from '@ghostfolio/common/routes/routes';
import { hasScope, scopes } from '@ghostfolio/common/scopes'; import { hasScope, scopes } from '@ghostfolio/common/scopes';
import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark'; import { GfBenchmarkComponent } from '@ghostfolio/ui/benchmark';
import { GfFabComponent } from '@ghostfolio/ui/fab'; import { GfFabComponent } from '@ghostfolio/ui/fab';
@ -23,12 +24,10 @@ import {
OnInit OnInit
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatDialog } from '@angular/material/dialog'; import { RouterModule } from '@angular/router';
import { ActivatedRoute, Router, RouterModule } from '@angular/router';
import { DeviceDetectorService } from 'ngx-device-detector'; import { DeviceDetectorService } from 'ngx-device-detector';
import { GfCreateWatchlistItemDialogComponent } from './create-watchlist-item-dialog/create-watchlist-item-dialog.component'; import { HomeWatchlistService } from './home-watchlist.service';
import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -38,6 +37,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
GfPremiumIndicatorComponent, GfPremiumIndicatorComponent,
RouterModule RouterModule
], ],
providers: [HomeWatchlistService],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
selector: 'gf-home-watchlist', selector: 'gf-home-watchlist',
styleUrls: ['./home-watchlist.scss'], styleUrls: ['./home-watchlist.scss'],
@ -45,6 +45,7 @@ import { CreateWatchlistItemDialogParams } from './create-watchlist-item-dialog/
}) })
export class GfHomeWatchlistComponent implements OnInit { export class GfHomeWatchlistComponent implements OnInit {
protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE; protected readonly DEFAULT_LOCALE = DEFAULT_LOCALE;
protected readonly internalRoutes = internalRoutes;
protected hasPermissionToCreateWatchlistItem: boolean; protected hasPermissionToCreateWatchlistItem: boolean;
protected hasPermissionToDeleteWatchlistItem: boolean; protected hasPermissionToDeleteWatchlistItem: boolean;
@ -59,18 +60,14 @@ export class GfHomeWatchlistComponent implements OnInit {
private readonly dataService = inject(DataService); private readonly dataService = inject(DataService);
private readonly destroyRef = inject(DestroyRef); private readonly destroyRef = inject(DestroyRef);
private readonly deviceDetectorService = inject(DeviceDetectorService); private readonly deviceDetectorService = inject(DeviceDetectorService);
private readonly dialog = inject(MatDialog); private readonly homeWatchlistService = inject(HomeWatchlistService);
private readonly route = inject(ActivatedRoute);
private readonly router = inject(Router);
private readonly userService = inject(UserService); private readonly userService = inject(UserService);
public constructor() { public constructor() {
this.route.queryParams this.homeWatchlistService.refresh$
.pipe(takeUntilDestroyed(this.destroyRef)) .pipe(takeUntilDestroyed(this.destroyRef))
.subscribe((params) => { .subscribe(() => {
if (params['createWatchlistItemDialog']) { this.loadWatchlistData();
this.openCreateWatchlistItemDialog();
}
}); });
this.userService.stateChanged 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>
</div> </div>
@if (hasPermissionToCreateWatchlistItem) { @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 { GfHomeOverviewComponent } from '@ghostfolio/client/components/home-overview/home-overview.component';
import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component'; import { GfHomeSummaryComponent } from '@ghostfolio/client/components/home-summary/home-summary.component';
import { GfHomeWatchlistComponent } from '@ghostfolio/client/components/home-watchlist/home-watchlist.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 { GfMarketsComponent } from '@ghostfolio/client/components/markets/markets.component';
import { AuthGuard } from '@ghostfolio/client/core/auth.guard'; import { AuthGuard } from '@ghostfolio/client/core/auth.guard';
import { internalRoutes } from '@ghostfolio/common/routes/routes'; import { internalRoutes } from '@ghostfolio/common/routes/routes';
@ -34,6 +35,14 @@ export const routes: Routes = [
title: internalRoutes.home.subRoutes.markets.title 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, path: internalRoutes.home.subRoutes.watchlist.path,
component: GfHomeWatchlistComponent, component: GfHomeWatchlistComponent,
title: internalRoutes.home.subRoutes.watchlist.title title: internalRoutes.home.subRoutes.watchlist.title

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

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

Loading…
Cancel
Save