From d3ebd6e0bf34f34d528d9d6f7917314724ad3fba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20G=C3=BCnther?= Date: Thu, 13 Jun 2024 13:42:54 +0200 Subject: [PATCH] Replace path visiting --- .../core/path-helper/path-helper.service.ts | 14 +++- .../app/core/url-params/url-params.service.ts | 4 ++ .../center/state/ian-center.service.ts | 57 +++------------- .../in-app-notification-entry.component.ts | 68 +++++++------------ 4 files changed, 52 insertions(+), 91 deletions(-) diff --git a/frontend/src/app/core/path-helper/path-helper.service.ts b/frontend/src/app/core/path-helper/path-helper.service.ts index 7b9c0b7a2927..2319f20252ac 100644 --- a/frontend/src/app/core/path-helper/path-helper.service.ts +++ b/frontend/src/app/core/path-helper/path-helper.service.ts @@ -120,6 +120,10 @@ export class PathHelperService { return `${this.staticBase}/notifications`; } + public notificationsDetailsPath(workPackageId:string, tab?:string):string { + return `${this.notificationsPath()}/details/${workPackageId}${tab ? `/${tab}` : ''}`; + } + public loginPath() { return `${this.staticBase}/login`; } @@ -260,8 +264,16 @@ export class PathHelperService { return `${this.workPackagePath(workPackageId)}/copy`; } + public workPackageDetailsPath(projectIdentifier:string, workPackageId:string|number, tab?:string) { + if (tab) { + return `${this.projectWorkPackagePath(projectIdentifier, workPackageId)}/details/${tab}`; + } + + return `${this.projectWorkPackagesPath(projectIdentifier)}/details/${workPackageId}`; + } + public workPackageDetailsCopyPath(projectIdentifier:string, workPackageId:string|number) { - return `${this.projectWorkPackagesPath(projectIdentifier)}/details/${workPackageId}/copy`; + return this.workPackageDetailsPath(projectIdentifier, workPackageId, 'copy'); } public workPackageSharePath(workPackageId:string|number) { diff --git a/frontend/src/app/core/url-params/url-params.service.ts b/frontend/src/app/core/url-params/url-params.service.ts index ba8061abd9e0..22123db3569d 100644 --- a/frontend/src/app/core/url-params/url-params.service.ts +++ b/frontend/src/app/core/url-params/url-params.service.ts @@ -34,6 +34,10 @@ export class UrlParamsService { return this.searchParams.get(key); } + public pathMatching(key:RegExp):string|null { + return window.location.pathname.match(key)?.[0] || null; + } + public has(key:string):boolean { return this.searchParams.has(key); } diff --git a/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts b/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts index e9d898d5762d..3d4d12b99808 100644 --- a/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts +++ b/frontend/src/app/features/in-app-notifications/center/state/ian-center.service.ts @@ -27,21 +27,9 @@ //++ import { Injectable, Injector } from '@angular/core'; -import { - debounceTime, - defaultIfEmpty, - distinctUntilChanged, - map, - mapTo, - pluck, - shareReplay, - switchMap, - take, - tap, -} from 'rxjs/operators'; -import { forkJoin, from, Observable, Subject } from 'rxjs'; +import { debounceTime, defaultIfEmpty, distinctUntilChanged, map, mapTo, switchMap, take, tap } from 'rxjs/operators'; +import { forkJoin, from, Observable, of, Subject } from 'rxjs'; import { ID, Query } from '@datorama/akita'; -import { UIRouterGlobals } from '@uirouter/core'; import { StateService } from '@uirouter/angular'; import { I18nService } from 'core-app/core/i18n/i18n.service'; @@ -49,7 +37,6 @@ import { IToast, ToastService } from 'core-app/shared/components/toaster/toast.s import { centerUpdatedInPlace, markNotificationsAsRead, - markNotificationsAsReadByFilters, notificationCountIncreased, notificationsMarkedRead, } from 'core-app/core/state/in-app-notifications/in-app-notifications.actions'; @@ -72,6 +59,8 @@ import idFromLink from 'core-app/features/hal/helpers/id-from-link'; import { DeviceService } from 'core-app/core/browser/device.service'; import { ApiV3ListFilter, ApiV3ListParameters } from 'core-app/core/apiv3/paths/apiv3-list-resource.interface'; import { FrameElement } from '@hotwired/turbo'; +import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; +import { UrlParamsService } from 'core-app/core/url-params/url-params.service'; export interface INotificationPageQueryParameters { filter?:string|null; @@ -95,20 +84,6 @@ export class IanCenterService extends UntilDestroyedMixin { menuFrame = document.getElementById('notifications_sidemenu') as FrameElement; - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - activeReason$:Observable = this.uiRouterGlobals.params$!.pipe( - this.untilDestroyed(), - distinctUntilChanged(), - map((params) => { - if (params.filter === 'reason') { - return params.name as string; - } - - return null; - }), - shareReplay(1), - ); - loading$:Observable = this.query.selectLoading(); selectNotifications$:Observable = this @@ -204,13 +179,7 @@ export class IanCenterService extends UntilDestroyedMixin { public selectedNotification:INotification; - stateChanged$ = this.uiRouterGlobals.params$?.pipe( - this.untilDestroyed(), - pluck('workPackageId'), - distinctUntilChanged(), - map((workPackageId:string) => (workPackageId ? this.apiV3Service.work_packages.id(workPackageId).path : undefined)), - shareReplay(), - ); + stateChanged$ = of(this.urlParams.pathMatching(/\/details\/\d+/)); constructor( readonly I18n:I18nService, @@ -219,9 +188,10 @@ export class IanCenterService extends UntilDestroyedMixin { readonly actions$:ActionsService, readonly apiV3Service:ApiV3Service, readonly toastService:ToastService, - readonly uiRouterGlobals:UIRouterGlobals, + readonly urlParams:UrlParamsService, readonly state:StateService, readonly deviceService:DeviceService, + readonly pathHelper:PathHelperService, ) { super(); this.reload.subscribe(); @@ -261,13 +231,8 @@ export class IanCenterService extends UntilDestroyedMixin { ); } - // eslint-disable-next-line @typescript-eslint/no-inferrable-types - openSplitScreen(workPackageId:string|null, tabIdentifier:string = 'activity'):void { - void this.state.go( - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/restrict-template-expressions - `${this.state.current.data.baseRoute}.details.tabs`, - { workPackageId, tabIdentifier }, - ); + openSplitScreen(workPackageId:string, tabIdentifier:string = 'activity'):void { + window.location.href = this.pathHelper.notificationsDetailsPath(workPackageId, tabIdentifier); } openFullView(workPackageId:string|null):void { @@ -358,7 +323,7 @@ export class IanCenterService extends UntilDestroyedMixin { } // Reload the sidemenu frame - this.menuFrame.reload(); + void this.menuFrame.reload(); } private sideLoadInvolvedWorkPackages(elements:INotification[]):Promise { @@ -396,7 +361,7 @@ export class IanCenterService extends UntilDestroyedMixin { (notifications:INotification[][]) => { for (let i = 0; i < notifications.length; ++i) { if (notifications[i][0]._links.resource - && idFromLink(notifications[i][0]._links.resource.href) === this.uiRouterGlobals.params.workPackageId) { + && idFromLink(notifications[i][0]._links.resource.href) === this.urlParams.pathMatching(/\/details\/(\d+)/)) { this.selectedNotificationIndex = i; [this.selectedNotification] = notifications[i]; return; diff --git a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts index 9f0e62269ba3..ee65c13541b9 100644 --- a/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts +++ b/frontend/src/app/features/in-app-notifications/entry/in-app-notification-entry.component.ts @@ -1,29 +1,16 @@ -import { - ChangeDetectionStrategy, - Component, - HostBinding, - Input, - OnInit, - ViewEncapsulation, -} from '@angular/core'; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnInit, ViewEncapsulation } from '@angular/core'; import { WorkPackageResource } from 'core-app/features/hal/resources/work-package-resource'; import { Observable } from 'rxjs'; import { ApiV3Service } from 'core-app/core/apiv3/api-v3.service'; import idFromLink from 'core-app/features/hal/helpers/id-from-link'; import { I18nService } from 'core-app/core/i18n/i18n.service'; import { TimezoneService } from 'core-app/core/datetime/timezone.service'; -import { - map, - shareReplay, - withLatestFrom, -} from 'rxjs/operators'; import { PathHelperService } from 'core-app/core/path-helper/path-helper.service'; -import { take } from 'rxjs/internal/operators/take'; -import { StateService } from '@uirouter/angular'; import { HalResource } from 'core-app/features/hal/resources/hal-resource'; import { INotification } from 'core-app/core/state/in-app-notifications/in-app-notification.model'; import { IanCenterService } from 'core-app/features/in-app-notifications/center/state/ian-center.service'; import { DeviceService } from 'core-app/core/browser/device.service'; +import { UrlParamsService } from 'core-app/core/url-params/url-params.service'; @Component({ selector: 'op-in-app-notification-entry', @@ -41,22 +28,10 @@ export class InAppNotificationEntryComponent implements OnInit { workPackage$:Observable|null = null; - showDateAlert$:Observable = this - .storeService - .activeReason$ - .pipe( - map((reason) => reason === 'date_alert'), - map((dateAlertFiltered) => { - const dateAlerts = this.aggregatedNotifications.filter((notification) => notification.reason === 'dateAlert'); - return dateAlertFiltered || dateAlerts.length === this.aggregatedNotifications.length; - }), - shareReplay(1), - ); + showDateAlert = false; loading$ = this.storeService.query.selectLoading(); - stateChanged$ = this.storeService.stateChanged$; - // The translated reason, if available translatedReasons:{ [reason:string]:number }; @@ -70,32 +45,45 @@ export class InAppNotificationEntryComponent implements OnInit { private clickTimer:ReturnType; + private workPackageId:string|null; + constructor( readonly apiV3Service:ApiV3Service, readonly I18n:I18nService, readonly storeService:IanCenterService, readonly timezoneService:TimezoneService, readonly pathHelper:PathHelperService, - readonly state:StateService, readonly deviceService:DeviceService, + readonly urlParams:UrlParamsService, ) { } ngOnInit():void { + const href = this.notification._links.resource?.href; + this.workPackageId = href && HalResource.matchFromLink(href, 'work_packages'); + + this.showDateAlert = this.hasActiveDateAlert(); this.buildTranslatedReason(); this.buildProject(); this.loadWorkPackage(); } + private hasActiveDateAlert():boolean { + if (this.urlParams.get('filter') === 'reason' && this.urlParams.get('name') === 'date_alert') { + return true; + } + + const dateAlerts = this.aggregatedNotifications.filter((notification) => notification.reason === 'dateAlert'); + return dateAlerts.length === this.aggregatedNotifications.length; + } + private loadWorkPackage() { - const href = this.notification._links.resource?.href; - const id = href && HalResource.matchFromLink(href, 'work_packages'); // not a work package reference - if (id) { + if (this.workPackageId) { this.workPackage$ = this .apiV3Service .work_packages - .id(id) + .id(this.workPackageId) .requireAndStream(); } } @@ -110,20 +98,12 @@ export class InAppNotificationEntryComponent implements OnInit { } showDetails():void { - if (!this.workPackage$) { + if (!this.workPackageId) { return; } - this - .workPackage$ - .pipe( - take(1), - withLatestFrom(this.showDateAlert$), - ) - .subscribe(([wp, openDetailsTab]) => { - const tab = openDetailsTab ? 'overview' : 'activity'; - this.storeService.openSplitScreen(wp.id, tab); - }); + const tab = this.showDateAlert ? 'overview' : 'activity'; + this.storeService.openSplitScreen(this.workPackageId, tab); } onDoubleClick():void {